@chriscode/hush 2.0.0 → 2.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/dist/cli.js +81 -14
- package/dist/commands/run.d.ts +3 -0
- package/dist/commands/run.d.ts.map +1 -0
- package/dist/commands/run.js +77 -0
- package/dist/commands/set.d.ts +3 -0
- package/dist/commands/set.d.ts.map +1 -0
- package/dist/commands/set.js +87 -0
- package/dist/commands/skill.d.ts +3 -0
- package/dist/commands/skill.d.ts.map +1 -0
- package/dist/commands/skill.js +1077 -0
- package/dist/core/sops.d.ts +5 -0
- package/dist/core/sops.d.ts.map +1 -1
- package/dist/core/sops.js +49 -1
- package/dist/formats/index.d.ts +2 -1
- package/dist/formats/index.d.ts.map +1 -1
- package/dist/formats/index.js +4 -1
- package/dist/formats/yaml.d.ts +13 -0
- package/dist/formats/yaml.d.ts.map +1 -0
- package/dist/formats/yaml.js +50 -0
- package/dist/types.d.ts +19 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +5 -0
- package/package.json +1 -1
|
@@ -0,0 +1,1077 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { createInterface } from 'node:readline';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import pc from 'picocolors';
|
|
6
|
+
const SKILL_FILES = {
|
|
7
|
+
'SKILL.md': `---
|
|
8
|
+
name: hush-secrets
|
|
9
|
+
description: Manage secrets safely using Hush CLI. Use when working with .env files, environment variables, secrets, API keys, database URLs, credentials, or configuration. Secrets are always encrypted at rest - .env files contain only encrypted data.
|
|
10
|
+
allowed-tools: Bash(hush:*), Bash(npx hush:*), Bash(brew:*), Bash(npm:*), Bash(pnpm:*), Bash(age-keygen:*), Read, Grep, Glob, Write, Bash(cat:*), Bash(grep:*)
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Hush - AI-Native Secrets Management
|
|
14
|
+
|
|
15
|
+
Hush keeps secrets **encrypted at rest**. All \`.env\` files contain encrypted data only—you can freely read them with \`cat\` or \`grep\` and you'll only see encrypted gibberish, never actual secrets.
|
|
16
|
+
|
|
17
|
+
## How It Works
|
|
18
|
+
|
|
19
|
+
Secrets are stored encrypted on disk. When you need to use them:
|
|
20
|
+
- \`hush run -- <command>\` decrypts to memory and runs your command
|
|
21
|
+
- \`hush set <KEY>\` adds secrets interactively (you invoke, user enters value)
|
|
22
|
+
- \`hush inspect\` shows what exists with masked values
|
|
23
|
+
- \`hush edit\` opens encrypted file in editor, re-encrypts on save
|
|
24
|
+
|
|
25
|
+
## Safe to Read (Always Encrypted)
|
|
26
|
+
|
|
27
|
+
You CAN freely read these files—they only contain encrypted data:
|
|
28
|
+
- \`.env.encrypted\`, \`.env.*.encrypted\` - encrypted secrets
|
|
29
|
+
- \`.env\`, \`.env.*\` - if they exist, they're encrypted too (Hush doesn't create plaintext files)
|
|
30
|
+
|
|
31
|
+
Feel free to use \`cat\`, \`grep\`, \`Read\` on any \`.env\` file. You'll see encrypted content like:
|
|
32
|
+
\`\`\`
|
|
33
|
+
DATABASE_URL=ENC[AES256_GCM,data:abc123...,type:str]
|
|
34
|
+
\`\`\`
|
|
35
|
+
|
|
36
|
+
## Commands Reference
|
|
37
|
+
|
|
38
|
+
### Primary Commands:
|
|
39
|
+
- \`npx hush run -- <command>\` - Run programs with secrets (decrypts to memory only!)
|
|
40
|
+
- \`npx hush set <KEY>\` - Add a secret interactively (you invoke, user enters value)
|
|
41
|
+
- \`npx hush edit\` - Let user edit all secrets in $EDITOR
|
|
42
|
+
- \`npx hush inspect\` - See what variables exist (values are masked)
|
|
43
|
+
- \`npx hush has <KEY>\` - Check if a specific variable is set
|
|
44
|
+
- \`npx hush status\` - View configuration
|
|
45
|
+
|
|
46
|
+
### Avoid These (Deprecated):
|
|
47
|
+
- \`hush decrypt\` / \`hush unsafe:decrypt\` - Writes unencrypted secrets to disk (defeats the purpose!)
|
|
48
|
+
|
|
49
|
+
## Quick Check: Is Hush Set Up?
|
|
50
|
+
|
|
51
|
+
\`\`\`bash
|
|
52
|
+
npx hush status
|
|
53
|
+
\`\`\`
|
|
54
|
+
|
|
55
|
+
**If this fails**, see [SETUP.md](SETUP.md) for first-time setup instructions.
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Running Programs with Secrets
|
|
60
|
+
|
|
61
|
+
**This is the primary way to use secrets - they never touch disk!**
|
|
62
|
+
|
|
63
|
+
\`\`\`bash
|
|
64
|
+
npx hush run -- npm start # Run with development secrets
|
|
65
|
+
npx hush run -e production -- npm build # Run with production secrets
|
|
66
|
+
npx hush run -t api -- wrangler dev # Run filtered for 'api' target
|
|
67
|
+
\`\`\`
|
|
68
|
+
|
|
69
|
+
The secrets are decrypted to memory and injected as environment variables.
|
|
70
|
+
The child process inherits them. No plaintext files are written.
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Checking Secrets
|
|
75
|
+
|
|
76
|
+
### See what variables exist (human-readable)
|
|
77
|
+
|
|
78
|
+
\`\`\`bash
|
|
79
|
+
npx hush inspect # Development
|
|
80
|
+
npx hush inspect -e production # Production
|
|
81
|
+
\`\`\`
|
|
82
|
+
|
|
83
|
+
Output shows **masked values**:
|
|
84
|
+
|
|
85
|
+
\`\`\`
|
|
86
|
+
Secrets for development:
|
|
87
|
+
|
|
88
|
+
DATABASE_URL = post****************... (45 chars)
|
|
89
|
+
STRIPE_SECRET_KEY = sk_t****************... (32 chars)
|
|
90
|
+
API_KEY = (not set)
|
|
91
|
+
|
|
92
|
+
Total: 3 variables
|
|
93
|
+
\`\`\`
|
|
94
|
+
|
|
95
|
+
### Check if a specific variable exists
|
|
96
|
+
|
|
97
|
+
\`\`\`bash
|
|
98
|
+
npx hush has DATABASE_URL # Verbose output
|
|
99
|
+
npx hush has API_KEY -q # Quiet: exit code only (0=set, 1=missing)
|
|
100
|
+
\`\`\`
|
|
101
|
+
|
|
102
|
+
### Read encrypted files directly
|
|
103
|
+
|
|
104
|
+
You can also just read the encrypted files:
|
|
105
|
+
\`\`\`bash
|
|
106
|
+
cat .env.encrypted # See encrypted content (safe!)
|
|
107
|
+
grep DATABASE .env.encrypted # Search for keys in encrypted file
|
|
108
|
+
\`\`\`
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Adding/Modifying Secrets
|
|
113
|
+
|
|
114
|
+
### Add a single secret interactively
|
|
115
|
+
|
|
116
|
+
\`\`\`bash
|
|
117
|
+
npx hush set DATABASE_URL # You invoke this, user types value
|
|
118
|
+
npx hush set API_KEY -e production # Set in production secrets
|
|
119
|
+
npx hush set DEBUG --local # Set personal local override
|
|
120
|
+
\`\`\`
|
|
121
|
+
|
|
122
|
+
The user will be prompted to enter the value (hidden input).
|
|
123
|
+
You never see the actual secret - just invoke the command!
|
|
124
|
+
|
|
125
|
+
### Edit all secrets in editor
|
|
126
|
+
|
|
127
|
+
\`\`\`bash
|
|
128
|
+
npx hush edit # Edit shared secrets
|
|
129
|
+
npx hush edit development # Edit development secrets
|
|
130
|
+
npx hush edit local # Edit personal overrides
|
|
131
|
+
\`\`\`
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Common Workflows
|
|
136
|
+
|
|
137
|
+
### "Help user add DATABASE_URL"
|
|
138
|
+
\`\`\`bash
|
|
139
|
+
npx hush set DATABASE_URL
|
|
140
|
+
\`\`\`
|
|
141
|
+
Tell user: "Enter your database URL when prompted"
|
|
142
|
+
|
|
143
|
+
### "Check all required secrets"
|
|
144
|
+
\`\`\`bash
|
|
145
|
+
npx hush has DATABASE_URL -q && npx hush has API_KEY -q && echo "All configured" || echo "Some missing"
|
|
146
|
+
\`\`\`
|
|
147
|
+
|
|
148
|
+
### "Run the development server"
|
|
149
|
+
\`\`\`bash
|
|
150
|
+
npx hush run -- npm run dev
|
|
151
|
+
\`\`\`
|
|
152
|
+
|
|
153
|
+
### "Build for production"
|
|
154
|
+
\`\`\`bash
|
|
155
|
+
npx hush run -e production -- npm run build
|
|
156
|
+
\`\`\`
|
|
157
|
+
|
|
158
|
+
### "See what's in the encrypted file"
|
|
159
|
+
\`\`\`bash
|
|
160
|
+
cat .env.encrypted # Safe! Shows encrypted data only
|
|
161
|
+
\`\`\`
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## Additional Resources
|
|
166
|
+
|
|
167
|
+
- **First-time setup**: [SETUP.md](SETUP.md)
|
|
168
|
+
- **Command reference**: [REFERENCE.md](REFERENCE.md)
|
|
169
|
+
- **Workflow examples**: [examples/workflows.md](examples/workflows.md)
|
|
170
|
+
`,
|
|
171
|
+
'SETUP.md': `# Hush First-Time Setup
|
|
172
|
+
|
|
173
|
+
This guide walks through setting up Hush from scratch. Follow these steps in order.
|
|
174
|
+
|
|
175
|
+
## Prerequisites Check
|
|
176
|
+
|
|
177
|
+
### 1. Check if SOPS and age are installed
|
|
178
|
+
|
|
179
|
+
\`\`\`bash
|
|
180
|
+
which sops && which age-keygen && echo "Prerequisites installed" || echo "Need to install prerequisites"
|
|
181
|
+
\`\`\`
|
|
182
|
+
|
|
183
|
+
If not installed:
|
|
184
|
+
|
|
185
|
+
**macOS:**
|
|
186
|
+
\`\`\`bash
|
|
187
|
+
brew install sops age
|
|
188
|
+
\`\`\`
|
|
189
|
+
|
|
190
|
+
**Linux (Debian/Ubuntu):**
|
|
191
|
+
\`\`\`bash
|
|
192
|
+
sudo apt install age
|
|
193
|
+
# SOPS: Download from https://github.com/getsops/sops/releases
|
|
194
|
+
wget https://github.com/getsops/sops/releases/download/v3.8.1/sops-v3.8.1.linux.amd64 -O /usr/local/bin/sops
|
|
195
|
+
chmod +x /usr/local/bin/sops
|
|
196
|
+
\`\`\`
|
|
197
|
+
|
|
198
|
+
**Windows (Chocolatey):**
|
|
199
|
+
\`\`\`powershell
|
|
200
|
+
choco install sops age
|
|
201
|
+
\`\`\`
|
|
202
|
+
|
|
203
|
+
### 2. Check for age encryption key
|
|
204
|
+
|
|
205
|
+
\`\`\`bash
|
|
206
|
+
test -f ~/.config/sops/age/key.txt && echo "Key exists" || echo "Need to create key"
|
|
207
|
+
\`\`\`
|
|
208
|
+
|
|
209
|
+
If no key exists, create one:
|
|
210
|
+
|
|
211
|
+
\`\`\`bash
|
|
212
|
+
mkdir -p ~/.config/sops/age
|
|
213
|
+
age-keygen -o ~/.config/sops/age/key.txt
|
|
214
|
+
\`\`\`
|
|
215
|
+
|
|
216
|
+
### 3. Get your public key
|
|
217
|
+
|
|
218
|
+
\`\`\`bash
|
|
219
|
+
grep "public key:" ~/.config/sops/age/key.txt
|
|
220
|
+
\`\`\`
|
|
221
|
+
|
|
222
|
+
Save this \`age1...\` value - you'll need it for the next step.
|
|
223
|
+
|
|
224
|
+
---
|
|
225
|
+
|
|
226
|
+
## Project Setup
|
|
227
|
+
|
|
228
|
+
### Step 1: Install Hush
|
|
229
|
+
|
|
230
|
+
\`\`\`bash
|
|
231
|
+
npm install -D @chriscode/hush
|
|
232
|
+
# or
|
|
233
|
+
pnpm add -D @chriscode/hush
|
|
234
|
+
\`\`\`
|
|
235
|
+
|
|
236
|
+
### Step 2: Create \`.sops.yaml\`
|
|
237
|
+
|
|
238
|
+
Create \`.sops.yaml\` in your repo root with your public key:
|
|
239
|
+
|
|
240
|
+
\`\`\`yaml
|
|
241
|
+
creation_rules:
|
|
242
|
+
- encrypted_regex: '.*'
|
|
243
|
+
age: age1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
244
|
+
\`\`\`
|
|
245
|
+
|
|
246
|
+
Replace \`age1xxx...\` with your actual public key from the prerequisites step.
|
|
247
|
+
|
|
248
|
+
### Step 3: Initialize Hush
|
|
249
|
+
|
|
250
|
+
\`\`\`bash
|
|
251
|
+
npx hush init
|
|
252
|
+
\`\`\`
|
|
253
|
+
|
|
254
|
+
This creates \`hush.yaml\` with auto-detected targets based on your project structure.
|
|
255
|
+
|
|
256
|
+
### Step 4: Review \`hush.yaml\`
|
|
257
|
+
|
|
258
|
+
The generated config looks like:
|
|
259
|
+
|
|
260
|
+
\`\`\`yaml
|
|
261
|
+
sources:
|
|
262
|
+
shared: .env
|
|
263
|
+
development: .env.development
|
|
264
|
+
production: .env.production
|
|
265
|
+
|
|
266
|
+
targets:
|
|
267
|
+
- name: root
|
|
268
|
+
path: .
|
|
269
|
+
format: dotenv
|
|
270
|
+
\`\`\`
|
|
271
|
+
|
|
272
|
+
Customize targets for your monorepo. Common patterns:
|
|
273
|
+
|
|
274
|
+
**Next.js app (client vars only):**
|
|
275
|
+
\`\`\`yaml
|
|
276
|
+
- name: web
|
|
277
|
+
path: ./apps/web
|
|
278
|
+
format: dotenv
|
|
279
|
+
include:
|
|
280
|
+
- NEXT_PUBLIC_*
|
|
281
|
+
\`\`\`
|
|
282
|
+
|
|
283
|
+
**API server (exclude client vars):**
|
|
284
|
+
\`\`\`yaml
|
|
285
|
+
- name: api
|
|
286
|
+
path: ./apps/api
|
|
287
|
+
format: wrangler # or dotenv
|
|
288
|
+
exclude:
|
|
289
|
+
- NEXT_PUBLIC_*
|
|
290
|
+
- VITE_*
|
|
291
|
+
\`\`\`
|
|
292
|
+
|
|
293
|
+
**Kubernetes:**
|
|
294
|
+
\`\`\`yaml
|
|
295
|
+
- name: k8s
|
|
296
|
+
path: ./k8s
|
|
297
|
+
format: yaml
|
|
298
|
+
\`\`\`
|
|
299
|
+
|
|
300
|
+
### Step 5: Create initial \`.env\` files
|
|
301
|
+
|
|
302
|
+
Create \`.env\` with shared secrets:
|
|
303
|
+
|
|
304
|
+
\`\`\`bash
|
|
305
|
+
# .env
|
|
306
|
+
DATABASE_URL=postgres://localhost/mydb
|
|
307
|
+
API_KEY=your_api_key_here
|
|
308
|
+
NEXT_PUBLIC_API_URL=http://localhost:3000
|
|
309
|
+
\`\`\`
|
|
310
|
+
|
|
311
|
+
Create \`.env.development\` for dev-specific values:
|
|
312
|
+
|
|
313
|
+
\`\`\`bash
|
|
314
|
+
# .env.development
|
|
315
|
+
DEBUG=true
|
|
316
|
+
LOG_LEVEL=debug
|
|
317
|
+
\`\`\`
|
|
318
|
+
|
|
319
|
+
Create \`.env.production\` for production values:
|
|
320
|
+
|
|
321
|
+
\`\`\`bash
|
|
322
|
+
# .env.production
|
|
323
|
+
DEBUG=false
|
|
324
|
+
LOG_LEVEL=error
|
|
325
|
+
\`\`\`
|
|
326
|
+
|
|
327
|
+
### Step 6: Encrypt secrets
|
|
328
|
+
|
|
329
|
+
\`\`\`bash
|
|
330
|
+
npx hush encrypt
|
|
331
|
+
\`\`\`
|
|
332
|
+
|
|
333
|
+
This creates:
|
|
334
|
+
- \`.env.encrypted\`
|
|
335
|
+
- \`.env.development.encrypted\`
|
|
336
|
+
- \`.env.production.encrypted\`
|
|
337
|
+
|
|
338
|
+
### Step 7: Verify setup
|
|
339
|
+
|
|
340
|
+
\`\`\`bash
|
|
341
|
+
npx hush status
|
|
342
|
+
npx hush inspect
|
|
343
|
+
\`\`\`
|
|
344
|
+
|
|
345
|
+
### Step 8: Update \`.gitignore\`
|
|
346
|
+
|
|
347
|
+
Add these lines to \`.gitignore\`:
|
|
348
|
+
|
|
349
|
+
\`\`\`gitignore
|
|
350
|
+
# Hush - plaintext env files (generated, not committed)
|
|
351
|
+
.env
|
|
352
|
+
.env.local
|
|
353
|
+
.env.development
|
|
354
|
+
.env.production
|
|
355
|
+
.dev.vars
|
|
356
|
+
|
|
357
|
+
# Keep encrypted files (these ARE committed)
|
|
358
|
+
!.env.encrypted
|
|
359
|
+
!.env.*.encrypted
|
|
360
|
+
\`\`\`
|
|
361
|
+
|
|
362
|
+
### Step 9: Commit encrypted files
|
|
363
|
+
|
|
364
|
+
\`\`\`bash
|
|
365
|
+
git add .sops.yaml hush.yaml .env*.encrypted .gitignore
|
|
366
|
+
git commit -m "chore: add Hush secrets management"
|
|
367
|
+
\`\`\`
|
|
368
|
+
|
|
369
|
+
---
|
|
370
|
+
|
|
371
|
+
## Team Member Setup
|
|
372
|
+
|
|
373
|
+
When a new team member joins:
|
|
374
|
+
|
|
375
|
+
1. **Get the age private key** from an existing team member
|
|
376
|
+
2. **Save it** to \`~/.config/sops/age/key.txt\`
|
|
377
|
+
3. **Run** \`npx hush run -- npm install\` to verify decryption works
|
|
378
|
+
4. **Start developing** with \`npx hush run -- npm run dev\`
|
|
379
|
+
|
|
380
|
+
The private key should be shared securely (password manager, encrypted channel, etc.)
|
|
381
|
+
|
|
382
|
+
---
|
|
383
|
+
|
|
384
|
+
## Verification Checklist
|
|
385
|
+
|
|
386
|
+
After setup, verify everything works:
|
|
387
|
+
|
|
388
|
+
- [ ] \`npx hush status\` shows configuration
|
|
389
|
+
- [ ] \`npx hush inspect\` shows masked variables
|
|
390
|
+
- [ ] \`npx hush run -- env\` can decrypt and run (secrets stay in memory!)
|
|
391
|
+
- [ ] \`.env.encrypted\` files are committed to git
|
|
392
|
+
- [ ] Plaintext \`.env\` files are in \`.gitignore\`
|
|
393
|
+
|
|
394
|
+
---
|
|
395
|
+
|
|
396
|
+
## Troubleshooting Setup
|
|
397
|
+
|
|
398
|
+
### "age: command not found"
|
|
399
|
+
\`\`\`bash
|
|
400
|
+
brew install age # macOS
|
|
401
|
+
\`\`\`
|
|
402
|
+
|
|
403
|
+
### "sops: command not found"
|
|
404
|
+
\`\`\`bash
|
|
405
|
+
brew install sops # macOS
|
|
406
|
+
\`\`\`
|
|
407
|
+
|
|
408
|
+
### "Error: no matching keys found"
|
|
409
|
+
Your age key doesn't match. Get the correct private key from a team member.
|
|
410
|
+
|
|
411
|
+
### "hush.yaml not found"
|
|
412
|
+
Run \`npx hush init\` to generate configuration.
|
|
413
|
+
|
|
414
|
+
### "No sources defined in hush.yaml"
|
|
415
|
+
Edit \`hush.yaml\` and add your source files under \`sources:\`.
|
|
416
|
+
`,
|
|
417
|
+
'REFERENCE.md': `# Hush Command Reference
|
|
418
|
+
|
|
419
|
+
Complete reference for all Hush CLI commands with flags, options, and examples.
|
|
420
|
+
|
|
421
|
+
## Security Model: Encrypted at Rest
|
|
422
|
+
|
|
423
|
+
All secrets are stored encrypted on disk. You can safely read any \`.env\` file—they contain only encrypted data. No special precautions needed for file reading.
|
|
424
|
+
|
|
425
|
+
## Global Options
|
|
426
|
+
|
|
427
|
+
| Option | Description |
|
|
428
|
+
|--------|-------------|
|
|
429
|
+
| \`-e, --env <env>\` | Environment: \`development\` / \`production\`. Default: \`development\` |
|
|
430
|
+
| \`-r, --root <dir>\` | Root directory containing \`hush.yaml\`. Default: current directory |
|
|
431
|
+
| \`-t, --target <name>\` | Target name from hush.yaml (for \`run\` command) |
|
|
432
|
+
| \`--local\` | Use local overrides (for \`set\` command) |
|
|
433
|
+
| \`-h, --help\` | Show help message |
|
|
434
|
+
| \`-v, --version\` | Show version number |
|
|
435
|
+
|
|
436
|
+
---
|
|
437
|
+
|
|
438
|
+
## Primary Commands
|
|
439
|
+
|
|
440
|
+
### hush run -- <command> ⭐
|
|
441
|
+
|
|
442
|
+
**The recommended way to run programs with secrets!**
|
|
443
|
+
|
|
444
|
+
Decrypts secrets to memory and runs a command with them as environment variables.
|
|
445
|
+
Secrets never touch the disk as plaintext.
|
|
446
|
+
|
|
447
|
+
\`\`\`bash
|
|
448
|
+
hush run -- npm start # Run with development secrets
|
|
449
|
+
hush run -e production -- npm build # Run with production secrets
|
|
450
|
+
hush run -t api -- wrangler dev # Run filtered for 'api' target
|
|
451
|
+
\`\`\`
|
|
452
|
+
|
|
453
|
+
**Options:**
|
|
454
|
+
| Option | Description |
|
|
455
|
+
|--------|-------------|
|
|
456
|
+
| \`-e, --env\` | Environment (development/production) |
|
|
457
|
+
| \`-t, --target\` | Filter secrets for a specific target from hush.yaml |
|
|
458
|
+
|
|
459
|
+
---
|
|
460
|
+
|
|
461
|
+
### hush set <KEY> ⭐
|
|
462
|
+
|
|
463
|
+
Add or update a single secret interactively. You invoke this, user enters the value.
|
|
464
|
+
|
|
465
|
+
\`\`\`bash
|
|
466
|
+
hush set DATABASE_URL # Set in shared secrets
|
|
467
|
+
hush set API_KEY -e production # Set in production secrets
|
|
468
|
+
hush set DEBUG --local # Set personal local override
|
|
469
|
+
\`\`\`
|
|
470
|
+
|
|
471
|
+
User will be prompted with hidden input - the value is never visible.
|
|
472
|
+
|
|
473
|
+
---
|
|
474
|
+
|
|
475
|
+
### hush edit [file]
|
|
476
|
+
|
|
477
|
+
Open all secrets in \`$EDITOR\` for bulk editing.
|
|
478
|
+
|
|
479
|
+
\`\`\`bash
|
|
480
|
+
hush edit # Edit shared secrets
|
|
481
|
+
hush edit development # Edit development secrets
|
|
482
|
+
hush edit production # Edit production secrets
|
|
483
|
+
hush edit local # Edit personal local overrides
|
|
484
|
+
\`\`\`
|
|
485
|
+
|
|
486
|
+
---
|
|
487
|
+
|
|
488
|
+
### hush inspect
|
|
489
|
+
|
|
490
|
+
List all variables with **masked values** (human-readable format).
|
|
491
|
+
|
|
492
|
+
\`\`\`bash
|
|
493
|
+
hush inspect # Development
|
|
494
|
+
hush inspect -e production # Production
|
|
495
|
+
\`\`\`
|
|
496
|
+
|
|
497
|
+
---
|
|
498
|
+
|
|
499
|
+
### hush has <KEY>
|
|
500
|
+
|
|
501
|
+
Check if a specific secret exists.
|
|
502
|
+
|
|
503
|
+
\`\`\`bash
|
|
504
|
+
hush has DATABASE_URL # Verbose output
|
|
505
|
+
hush has API_KEY -q # Quiet: exit code only (0=set, 1=missing)
|
|
506
|
+
\`\`\`
|
|
507
|
+
|
|
508
|
+
---
|
|
509
|
+
|
|
510
|
+
## Setup Commands
|
|
511
|
+
|
|
512
|
+
### hush init
|
|
513
|
+
|
|
514
|
+
Generate \`hush.yaml\` configuration with auto-detected targets.
|
|
515
|
+
|
|
516
|
+
\`\`\`bash
|
|
517
|
+
hush init
|
|
518
|
+
\`\`\`
|
|
519
|
+
|
|
520
|
+
---
|
|
521
|
+
|
|
522
|
+
### hush encrypt
|
|
523
|
+
|
|
524
|
+
Encrypt source \`.env\` files to \`.env.encrypted\` files.
|
|
525
|
+
|
|
526
|
+
\`\`\`bash
|
|
527
|
+
hush encrypt
|
|
528
|
+
\`\`\`
|
|
529
|
+
|
|
530
|
+
---
|
|
531
|
+
|
|
532
|
+
### hush status
|
|
533
|
+
|
|
534
|
+
Show configuration and file status.
|
|
535
|
+
|
|
536
|
+
\`\`\`bash
|
|
537
|
+
hush status
|
|
538
|
+
\`\`\`
|
|
539
|
+
|
|
540
|
+
---
|
|
541
|
+
|
|
542
|
+
## Deployment Commands
|
|
543
|
+
|
|
544
|
+
### hush push
|
|
545
|
+
|
|
546
|
+
Push production secrets to Cloudflare Workers.
|
|
547
|
+
|
|
548
|
+
\`\`\`bash
|
|
549
|
+
hush push # Push secrets
|
|
550
|
+
hush push --dry-run # Preview without pushing
|
|
551
|
+
\`\`\`
|
|
552
|
+
|
|
553
|
+
---
|
|
554
|
+
|
|
555
|
+
## Deprecated Commands (Avoid)
|
|
556
|
+
|
|
557
|
+
### hush decrypt / hush unsafe:decrypt ⚠️
|
|
558
|
+
|
|
559
|
+
**DEPRECATED:** Writes unencrypted secrets to disk, defeating the "encrypted at rest" model.
|
|
560
|
+
|
|
561
|
+
\`\`\`bash
|
|
562
|
+
hush decrypt # Writes plaintext .env files (avoid!)
|
|
563
|
+
hush unsafe:decrypt # Same, explicit unsafe mode
|
|
564
|
+
\`\`\`
|
|
565
|
+
|
|
566
|
+
Use \`hush run -- <command>\` instead.
|
|
567
|
+
|
|
568
|
+
---
|
|
569
|
+
|
|
570
|
+
## Quick Reference
|
|
571
|
+
|
|
572
|
+
| Command | Purpose |
|
|
573
|
+
|---------|---------|
|
|
574
|
+
| \`hush run -- <cmd>\` | Run with secrets (memory only) |
|
|
575
|
+
| \`hush set <KEY>\` | Add secret interactively |
|
|
576
|
+
| \`hush edit\` | Edit secrets in $EDITOR |
|
|
577
|
+
| \`hush inspect\` | See variables (masked) |
|
|
578
|
+
| \`hush has <KEY>\` | Check if variable exists |
|
|
579
|
+
| \`hush status\` | View configuration |
|
|
580
|
+
| \`cat .env.encrypted\` | Read encrypted file (safe!) |
|
|
581
|
+
|
|
582
|
+
---
|
|
583
|
+
|
|
584
|
+
### hush inspect (AI-Safe)
|
|
585
|
+
|
|
586
|
+
List all variables with **masked values**. Safe for AI agents.
|
|
587
|
+
|
|
588
|
+
\`\`\`bash
|
|
589
|
+
hush inspect # Development
|
|
590
|
+
hush inspect -e production # Production
|
|
591
|
+
\`\`\`
|
|
592
|
+
|
|
593
|
+
**Output format:**
|
|
594
|
+
\`\`\`
|
|
595
|
+
Secrets for development:
|
|
596
|
+
|
|
597
|
+
DATABASE_URL = post****************... (45 chars)
|
|
598
|
+
STRIPE_SECRET_KEY = sk_t****************... (32 chars)
|
|
599
|
+
API_KEY = (not set)
|
|
600
|
+
|
|
601
|
+
Total: 3 variables
|
|
602
|
+
|
|
603
|
+
Target distribution:
|
|
604
|
+
|
|
605
|
+
root (.) - 3 vars
|
|
606
|
+
app (./app/) - 1 vars
|
|
607
|
+
include: EXPO_PUBLIC_*
|
|
608
|
+
api (./api/) - 2 vars
|
|
609
|
+
exclude: EXPO_PUBLIC_*
|
|
610
|
+
\`\`\`
|
|
611
|
+
|
|
612
|
+
**What's visible:**
|
|
613
|
+
- Variable names
|
|
614
|
+
- First 4 characters (helps identify type: \`sk_\` = Stripe, \`ghp_\` = GitHub)
|
|
615
|
+
- Value length
|
|
616
|
+
- Which targets receive which variables
|
|
617
|
+
|
|
618
|
+
---
|
|
619
|
+
|
|
620
|
+
### hush has (AI-Safe)
|
|
621
|
+
|
|
622
|
+
Check if a specific secret exists.
|
|
623
|
+
|
|
624
|
+
\`\`\`bash
|
|
625
|
+
hush has <KEY> # Verbose output
|
|
626
|
+
hush has <KEY> -q # Quiet mode (exit code only)
|
|
627
|
+
hush has <KEY> --quiet # Same as -q
|
|
628
|
+
\`\`\`
|
|
629
|
+
|
|
630
|
+
**Exit codes:**
|
|
631
|
+
- \`0\` - Variable is set
|
|
632
|
+
- \`1\` - Variable not found
|
|
633
|
+
|
|
634
|
+
**Examples:**
|
|
635
|
+
\`\`\`bash
|
|
636
|
+
# Check with output
|
|
637
|
+
hush has DATABASE_URL
|
|
638
|
+
# Output: DATABASE_URL is set (45 chars)
|
|
639
|
+
|
|
640
|
+
# Check missing variable
|
|
641
|
+
hush has MISSING_KEY
|
|
642
|
+
# Output: MISSING_KEY not found
|
|
643
|
+
# Exit code: 1
|
|
644
|
+
|
|
645
|
+
# Use in scripts
|
|
646
|
+
hush has DATABASE_URL -q && echo "DB ready"
|
|
647
|
+
|
|
648
|
+
# Check multiple
|
|
649
|
+
hush has DB_URL -q && hush has API_KEY -q && echo "All set"
|
|
650
|
+
\`\`\`
|
|
651
|
+
|
|
652
|
+
---
|
|
653
|
+
|
|
654
|
+
### hush push
|
|
655
|
+
|
|
656
|
+
Push production secrets to Cloudflare Workers.
|
|
657
|
+
|
|
658
|
+
\`\`\`bash
|
|
659
|
+
hush push # Push secrets
|
|
660
|
+
hush push --dry-run # Preview without pushing
|
|
661
|
+
\`\`\`
|
|
662
|
+
|
|
663
|
+
**Options:**
|
|
664
|
+
|
|
665
|
+
| Option | Description |
|
|
666
|
+
|--------|-------------|
|
|
667
|
+
| \`--dry-run\` | Preview what would be pushed, don't actually push |
|
|
668
|
+
|
|
669
|
+
**Requirements:**
|
|
670
|
+
- Target must have \`format: wrangler\`
|
|
671
|
+
- \`wrangler.toml\` must exist in target path
|
|
672
|
+
- \`wrangler\` CLI must be installed and authenticated
|
|
673
|
+
|
|
674
|
+
---
|
|
675
|
+
|
|
676
|
+
### hush status
|
|
677
|
+
|
|
678
|
+
Show configuration and file status.
|
|
679
|
+
|
|
680
|
+
\`\`\`bash
|
|
681
|
+
hush status
|
|
682
|
+
\`\`\`
|
|
683
|
+
|
|
684
|
+
**Output includes:**
|
|
685
|
+
- Configuration file location
|
|
686
|
+
- Source files and their encryption status
|
|
687
|
+
- Target configuration (paths, formats, filters)
|
|
688
|
+
- Whether files are in sync
|
|
689
|
+
|
|
690
|
+
---
|
|
691
|
+
|
|
692
|
+
### hush check
|
|
693
|
+
|
|
694
|
+
Verify secrets are encrypted (useful for pre-commit hooks).
|
|
695
|
+
|
|
696
|
+
\`\`\`bash
|
|
697
|
+
hush check # Basic check
|
|
698
|
+
hush check --warn # Warn but don't fail
|
|
699
|
+
hush check --json # JSON output for CI
|
|
700
|
+
hush check --only-changed # Only check git-modified files
|
|
701
|
+
hush check --require-source # Fail if source file missing
|
|
702
|
+
\`\`\`
|
|
703
|
+
|
|
704
|
+
**Exit codes:**
|
|
705
|
+
- \`0\` - All in sync
|
|
706
|
+
- \`1\` - Drift detected (run \`hush encrypt\`)
|
|
707
|
+
- \`2\` - Config error
|
|
708
|
+
- \`3\` - Runtime error (sops missing, decrypt failed)
|
|
709
|
+
|
|
710
|
+
**Pre-commit hook (Husky):**
|
|
711
|
+
\`\`\`bash
|
|
712
|
+
# .husky/pre-commit
|
|
713
|
+
npx hush check || exit 1
|
|
714
|
+
\`\`\`
|
|
715
|
+
|
|
716
|
+
Bypass with: \`HUSH_SKIP_CHECK=1 git commit -m "message"\`
|
|
717
|
+
|
|
718
|
+
---
|
|
719
|
+
|
|
720
|
+
### hush skill
|
|
721
|
+
|
|
722
|
+
Install the Claude Code / OpenCode skill for AI-safe secrets management.
|
|
723
|
+
|
|
724
|
+
\`\`\`bash
|
|
725
|
+
hush skill # Interactive: choose global or local
|
|
726
|
+
hush skill --global # Install to ~/.claude/skills/
|
|
727
|
+
hush skill --local # Install to ./.claude/skills/
|
|
728
|
+
\`\`\`
|
|
729
|
+
|
|
730
|
+
**Global install:** Works across all your projects. Recommended for personal use.
|
|
731
|
+
|
|
732
|
+
**Local install:** Bundled with the project. Recommended for teams (skill travels with the repo).
|
|
733
|
+
|
|
734
|
+
## Configuration File (hush.yaml)
|
|
735
|
+
|
|
736
|
+
\`\`\`yaml
|
|
737
|
+
sources:
|
|
738
|
+
shared: .env
|
|
739
|
+
development: .env.development
|
|
740
|
+
production: .env.production
|
|
741
|
+
|
|
742
|
+
targets:
|
|
743
|
+
- name: root
|
|
744
|
+
path: .
|
|
745
|
+
format: dotenv
|
|
746
|
+
|
|
747
|
+
- name: app
|
|
748
|
+
path: ./packages/app
|
|
749
|
+
format: dotenv
|
|
750
|
+
include:
|
|
751
|
+
- EXPO_PUBLIC_*
|
|
752
|
+
- NEXT_PUBLIC_*
|
|
753
|
+
|
|
754
|
+
- name: api
|
|
755
|
+
path: ./packages/api
|
|
756
|
+
format: wrangler
|
|
757
|
+
exclude:
|
|
758
|
+
- EXPO_PUBLIC_*
|
|
759
|
+
\`\`\`
|
|
760
|
+
|
|
761
|
+
### Target Options
|
|
762
|
+
|
|
763
|
+
| Option | Description |
|
|
764
|
+
|--------|-------------|
|
|
765
|
+
| \`name\` | Identifier for the target |
|
|
766
|
+
| \`path\` | Directory to write output file |
|
|
767
|
+
| \`format\` | Output format: \`dotenv\`, \`wrangler\`, \`json\`, \`shell\`, \`yaml\` |
|
|
768
|
+
| \`include\` | Glob patterns to include (e.g., \`NEXT_PUBLIC_*\`) |
|
|
769
|
+
| \`exclude\` | Glob patterns to exclude |
|
|
770
|
+
|
|
771
|
+
### Output Formats
|
|
772
|
+
|
|
773
|
+
| Format | Output File | Use Case |
|
|
774
|
+
|--------|-------------|----------|
|
|
775
|
+
| \`dotenv\` | \`.env.development\` / \`.env.production\` | Next.js, Vite, Expo, Remix, Node.js |
|
|
776
|
+
| \`wrangler\` | \`.dev.vars\` | Cloudflare Workers & Pages |
|
|
777
|
+
| \`json\` | \`.env.development.json\` | AWS Lambda, serverless, JSON configs |
|
|
778
|
+
| \`shell\` | \`.env.development.sh\` | CI/CD pipelines, Docker builds |
|
|
779
|
+
| \`yaml\` | \`.env.development.yaml\` | Kubernetes ConfigMaps, Docker Compose |
|
|
780
|
+
|
|
781
|
+
### Framework Client Prefixes
|
|
782
|
+
|
|
783
|
+
| Framework | Client Prefix | Example |
|
|
784
|
+
|-----------|--------------|---------|
|
|
785
|
+
| Next.js | \`NEXT_PUBLIC_*\` | \`include: [NEXT_PUBLIC_*]\` |
|
|
786
|
+
| Vite | \`VITE_*\` | \`include: [VITE_*]\` |
|
|
787
|
+
| Create React App | \`REACT_APP_*\` | \`include: [REACT_APP_*]\` |
|
|
788
|
+
| Vue CLI | \`VUE_APP_*\` | \`include: [VUE_APP_*]\` |
|
|
789
|
+
| Nuxt | \`NUXT_PUBLIC_*\` | \`include: [NUXT_PUBLIC_*]\` |
|
|
790
|
+
| Astro | \`PUBLIC_*\` | \`include: [PUBLIC_*]\` |
|
|
791
|
+
| SvelteKit | \`PUBLIC_*\` | \`include: [PUBLIC_*]\` |
|
|
792
|
+
| Expo | \`EXPO_PUBLIC_*\` | \`include: [EXPO_PUBLIC_*]\` |
|
|
793
|
+
| Gatsby | \`GATSBY_*\` | \`include: [GATSBY_*]\` |
|
|
794
|
+
`,
|
|
795
|
+
'examples/workflows.md': `# Hush Workflow Examples
|
|
796
|
+
|
|
797
|
+
Step-by-step examples for common workflows when working with secrets.
|
|
798
|
+
|
|
799
|
+
**Remember:** All \`.env\` files are encrypted at rest. You can freely read them with \`cat\` or \`grep\`—you'll only see encrypted data, never actual secrets.
|
|
800
|
+
|
|
801
|
+
## Running Programs (Most Common)
|
|
802
|
+
|
|
803
|
+
### "Start the development server"
|
|
804
|
+
|
|
805
|
+
\`\`\`bash
|
|
806
|
+
hush run -- npm run dev
|
|
807
|
+
\`\`\`
|
|
808
|
+
|
|
809
|
+
### "Build for production"
|
|
810
|
+
|
|
811
|
+
\`\`\`bash
|
|
812
|
+
hush run -e production -- npm run build
|
|
813
|
+
\`\`\`
|
|
814
|
+
|
|
815
|
+
### "Run tests with secrets"
|
|
816
|
+
|
|
817
|
+
\`\`\`bash
|
|
818
|
+
hush run -- npm test
|
|
819
|
+
\`\`\`
|
|
820
|
+
|
|
821
|
+
### "Run Wrangler for Cloudflare Worker"
|
|
822
|
+
|
|
823
|
+
\`\`\`bash
|
|
824
|
+
hush run -t api -- wrangler dev
|
|
825
|
+
\`\`\`
|
|
826
|
+
|
|
827
|
+
---
|
|
828
|
+
|
|
829
|
+
## Checking Secrets
|
|
830
|
+
|
|
831
|
+
### "What environment variables does this project use?"
|
|
832
|
+
|
|
833
|
+
\`\`\`bash
|
|
834
|
+
hush inspect # Human-readable masked output
|
|
835
|
+
# or
|
|
836
|
+
cat .env.encrypted # Raw encrypted file (safe!)
|
|
837
|
+
\`\`\`
|
|
838
|
+
|
|
839
|
+
### "Is the database configured?"
|
|
840
|
+
|
|
841
|
+
\`\`\`bash
|
|
842
|
+
hush has DATABASE_URL
|
|
843
|
+
\`\`\`
|
|
844
|
+
|
|
845
|
+
If "not found", help user add it with \`hush set DATABASE_URL\`.
|
|
846
|
+
|
|
847
|
+
### "Check all required secrets"
|
|
848
|
+
|
|
849
|
+
\`\`\`bash
|
|
850
|
+
hush has DATABASE_URL -q && \\
|
|
851
|
+
hush has API_KEY -q && \\
|
|
852
|
+
echo "All configured" || \\
|
|
853
|
+
echo "Some missing"
|
|
854
|
+
\`\`\`
|
|
855
|
+
|
|
856
|
+
### "Search for a key in encrypted files"
|
|
857
|
+
|
|
858
|
+
\`\`\`bash
|
|
859
|
+
grep DATABASE .env.encrypted # Safe! Shows encrypted line
|
|
860
|
+
\`\`\`
|
|
861
|
+
|
|
862
|
+
---
|
|
863
|
+
|
|
864
|
+
## Adding Secrets
|
|
865
|
+
|
|
866
|
+
### "Help me add DATABASE_URL"
|
|
867
|
+
|
|
868
|
+
\`\`\`bash
|
|
869
|
+
hush set DATABASE_URL
|
|
870
|
+
\`\`\`
|
|
871
|
+
|
|
872
|
+
Tell user: "Enter your database URL when prompted (input will be hidden)"
|
|
873
|
+
|
|
874
|
+
### "Add a production-only secret"
|
|
875
|
+
|
|
876
|
+
\`\`\`bash
|
|
877
|
+
hush set STRIPE_SECRET_KEY -e production
|
|
878
|
+
\`\`\`
|
|
879
|
+
|
|
880
|
+
### "Add a personal local override"
|
|
881
|
+
|
|
882
|
+
\`\`\`bash
|
|
883
|
+
hush set DEBUG --local
|
|
884
|
+
\`\`\`
|
|
885
|
+
|
|
886
|
+
### "Edit multiple secrets at once"
|
|
887
|
+
|
|
888
|
+
\`\`\`bash
|
|
889
|
+
hush edit
|
|
890
|
+
\`\`\`
|
|
891
|
+
|
|
892
|
+
Tell user: "Your editor will open. Add or modify secrets, then save and close."
|
|
893
|
+
|
|
894
|
+
---
|
|
895
|
+
|
|
896
|
+
## Debugging
|
|
897
|
+
|
|
898
|
+
### "My app can't find DATABASE_URL"
|
|
899
|
+
|
|
900
|
+
1. Check if it exists:
|
|
901
|
+
\`\`\`bash
|
|
902
|
+
hush has DATABASE_URL
|
|
903
|
+
\`\`\`
|
|
904
|
+
|
|
905
|
+
2. Check target distribution:
|
|
906
|
+
\`\`\`bash
|
|
907
|
+
hush inspect
|
|
908
|
+
\`\`\`
|
|
909
|
+
|
|
910
|
+
3. Check hush.yaml for filtering:
|
|
911
|
+
\`\`\`bash
|
|
912
|
+
cat hush.yaml
|
|
913
|
+
\`\`\`
|
|
914
|
+
|
|
915
|
+
4. Look at the encrypted file:
|
|
916
|
+
\`\`\`bash
|
|
917
|
+
grep DATABASE .env.encrypted # Safe to read!
|
|
918
|
+
\`\`\`
|
|
919
|
+
|
|
920
|
+
5. Try running directly:
|
|
921
|
+
\`\`\`bash
|
|
922
|
+
hush run -- env | grep DATABASE
|
|
923
|
+
\`\`\`
|
|
924
|
+
|
|
925
|
+
---
|
|
926
|
+
|
|
927
|
+
## Team Workflows
|
|
928
|
+
|
|
929
|
+
### "New team member setup"
|
|
930
|
+
|
|
931
|
+
Guide them:
|
|
932
|
+
> 1. Get the age private key from a team member
|
|
933
|
+
> 2. Save it to \`~/.config/sops/age/key.txt\`
|
|
934
|
+
> 3. Run \`hush run -- npm install\` to verify setup
|
|
935
|
+
> 4. Start developing with \`hush run -- npm run dev\`
|
|
936
|
+
|
|
937
|
+
### "Someone added new secrets"
|
|
938
|
+
|
|
939
|
+
\`\`\`bash
|
|
940
|
+
git pull
|
|
941
|
+
hush inspect # See what's new
|
|
942
|
+
\`\`\`
|
|
943
|
+
|
|
944
|
+
---
|
|
945
|
+
|
|
946
|
+
## Deployment
|
|
947
|
+
|
|
948
|
+
### "Push to Cloudflare Workers"
|
|
949
|
+
|
|
950
|
+
\`\`\`bash
|
|
951
|
+
hush push --dry-run # Preview first
|
|
952
|
+
hush push # Actually push
|
|
953
|
+
\`\`\`
|
|
954
|
+
|
|
955
|
+
### "Build and deploy"
|
|
956
|
+
|
|
957
|
+
\`\`\`bash
|
|
958
|
+
hush run -e production -- npm run build
|
|
959
|
+
hush push
|
|
960
|
+
\`\`\`
|
|
961
|
+
|
|
962
|
+
---
|
|
963
|
+
|
|
964
|
+
## Understanding the Output
|
|
965
|
+
|
|
966
|
+
### hush inspect output explained
|
|
967
|
+
|
|
968
|
+
\`\`\`
|
|
969
|
+
Secrets for development:
|
|
970
|
+
|
|
971
|
+
DATABASE_URL = post****************... (45 chars)
|
|
972
|
+
STRIPE_SECRET_KEY = sk_t****************... (32 chars)
|
|
973
|
+
API_KEY = (not set)
|
|
974
|
+
|
|
975
|
+
Total: 3 variables
|
|
976
|
+
|
|
977
|
+
Target distribution:
|
|
978
|
+
|
|
979
|
+
root (.) - 3 vars
|
|
980
|
+
app (./app/) - 1 vars
|
|
981
|
+
include: EXPO_PUBLIC_*
|
|
982
|
+
api (./api/) - 2 vars
|
|
983
|
+
exclude: EXPO_PUBLIC_*
|
|
984
|
+
\`\`\`
|
|
985
|
+
|
|
986
|
+
**Reading this:**
|
|
987
|
+
- \`DATABASE_URL\` is set, starts with "post", is 45 characters (likely a postgres:// URL)
|
|
988
|
+
- \`STRIPE_SECRET_KEY\` starts with "sk_t" (Stripe test key format)
|
|
989
|
+
- \`API_KEY\` is not set - user needs to add it
|
|
990
|
+
- The \`app\` folder only gets \`EXPO_PUBLIC_*\` variables
|
|
991
|
+
- The \`api\` folder gets everything except \`EXPO_PUBLIC_*\`
|
|
992
|
+
|
|
993
|
+
### Reading encrypted files directly
|
|
994
|
+
|
|
995
|
+
\`\`\`bash
|
|
996
|
+
$ cat .env.encrypted
|
|
997
|
+
DATABASE_URL=ENC[AES256_GCM,data:7xH2kL9...,iv:abc...,tag:xyz...,type:str]
|
|
998
|
+
STRIPE_SECRET_KEY=ENC[AES256_GCM,data:mN3pQ8...,iv:def...,tag:uvw...,type:str]
|
|
999
|
+
\`\`\`
|
|
1000
|
+
|
|
1001
|
+
This is safe to view—the actual values are encrypted. You can see what keys exist without exposing secrets.
|
|
1002
|
+
`,
|
|
1003
|
+
};
|
|
1004
|
+
function getSkillPath(location, root) {
|
|
1005
|
+
if (location === 'global') {
|
|
1006
|
+
return join(homedir(), '.claude', 'skills', 'hush-secrets');
|
|
1007
|
+
}
|
|
1008
|
+
return join(root, '.claude', 'skills', 'hush-secrets');
|
|
1009
|
+
}
|
|
1010
|
+
async function promptForLocation() {
|
|
1011
|
+
const rl = createInterface({
|
|
1012
|
+
input: process.stdin,
|
|
1013
|
+
output: process.stdout,
|
|
1014
|
+
});
|
|
1015
|
+
return new Promise((resolve) => {
|
|
1016
|
+
console.log(pc.bold('\nWhere would you like to install the Claude skill?\n'));
|
|
1017
|
+
console.log(` ${pc.cyan('1)')} ${pc.bold('Global')} ${pc.dim('(~/.claude/skills/)')}`);
|
|
1018
|
+
console.log(` Works across all your projects. Recommended for personal use.\n`);
|
|
1019
|
+
console.log(` ${pc.cyan('2)')} ${pc.bold('Local')} ${pc.dim('(.claude/skills/)')}`);
|
|
1020
|
+
console.log(` Bundled with this project. Recommended for teams.\n`);
|
|
1021
|
+
rl.question(`${pc.bold('Choice')} ${pc.dim('[1/2]')}: `, (answer) => {
|
|
1022
|
+
rl.close();
|
|
1023
|
+
const choice = answer.trim();
|
|
1024
|
+
if (choice === '2' || choice.toLowerCase() === 'local') {
|
|
1025
|
+
resolve('local');
|
|
1026
|
+
}
|
|
1027
|
+
else {
|
|
1028
|
+
resolve('global');
|
|
1029
|
+
}
|
|
1030
|
+
});
|
|
1031
|
+
});
|
|
1032
|
+
}
|
|
1033
|
+
function writeSkillFiles(skillPath) {
|
|
1034
|
+
mkdirSync(skillPath, { recursive: true });
|
|
1035
|
+
mkdirSync(join(skillPath, 'examples'), { recursive: true });
|
|
1036
|
+
for (const [filename, content] of Object.entries(SKILL_FILES)) {
|
|
1037
|
+
const filePath = join(skillPath, filename);
|
|
1038
|
+
writeFileSync(filePath, content, 'utf-8');
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
export async function skillCommand(options) {
|
|
1042
|
+
const { root, global: isGlobal, local: isLocal } = options;
|
|
1043
|
+
let location;
|
|
1044
|
+
if (isGlobal) {
|
|
1045
|
+
location = 'global';
|
|
1046
|
+
}
|
|
1047
|
+
else if (isLocal) {
|
|
1048
|
+
location = 'local';
|
|
1049
|
+
}
|
|
1050
|
+
else {
|
|
1051
|
+
location = await promptForLocation();
|
|
1052
|
+
}
|
|
1053
|
+
const skillPath = getSkillPath(location, root);
|
|
1054
|
+
const alreadyInstalled = existsSync(join(skillPath, 'SKILL.md'));
|
|
1055
|
+
if (alreadyInstalled) {
|
|
1056
|
+
console.log(pc.yellow(`\nSkill already installed at: ${skillPath}`));
|
|
1057
|
+
console.log(pc.dim('To reinstall, delete the directory first.\n'));
|
|
1058
|
+
return;
|
|
1059
|
+
}
|
|
1060
|
+
console.log(pc.blue(`\nInstalling Claude skill to: ${skillPath}`));
|
|
1061
|
+
writeSkillFiles(skillPath);
|
|
1062
|
+
console.log(pc.green('\n✓ Skill installed successfully!\n'));
|
|
1063
|
+
if (location === 'global') {
|
|
1064
|
+
console.log(pc.dim('The skill is now active for all projects using Claude Code.\n'));
|
|
1065
|
+
}
|
|
1066
|
+
else {
|
|
1067
|
+
console.log(pc.dim('The skill is now bundled with this project.'));
|
|
1068
|
+
console.log(pc.dim('Commit the .claude/ directory to share with your team.\n'));
|
|
1069
|
+
console.log(pc.bold('Suggested:'));
|
|
1070
|
+
console.log(` git add .claude/`);
|
|
1071
|
+
console.log(` git commit -m "chore: add Hush Claude skill"\n`);
|
|
1072
|
+
}
|
|
1073
|
+
console.log(pc.bold('What the skill does:'));
|
|
1074
|
+
console.log(` • Teaches AI to use ${pc.cyan('hush inspect')} instead of reading .env files`);
|
|
1075
|
+
console.log(` • Prevents accidental exposure of secrets to LLMs`);
|
|
1076
|
+
console.log(` • Guides AI through adding/modifying secrets safely\n`);
|
|
1077
|
+
}
|