@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.
@@ -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
+ }