@chriscode/hush 5.0.0 → 5.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/dist/cli.js +39 -26
  2. package/dist/commands/check.d.ts +3 -3
  3. package/dist/commands/check.d.ts.map +1 -1
  4. package/dist/commands/check.js +27 -31
  5. package/dist/commands/decrypt.d.ts +2 -2
  6. package/dist/commands/decrypt.d.ts.map +1 -1
  7. package/dist/commands/decrypt.js +52 -55
  8. package/dist/commands/edit.d.ts +2 -2
  9. package/dist/commands/edit.d.ts.map +1 -1
  10. package/dist/commands/edit.js +10 -12
  11. package/dist/commands/encrypt.d.ts +2 -2
  12. package/dist/commands/encrypt.d.ts.map +1 -1
  13. package/dist/commands/encrypt.js +27 -29
  14. package/dist/commands/expansions.d.ts +2 -2
  15. package/dist/commands/expansions.d.ts.map +1 -1
  16. package/dist/commands/expansions.js +46 -44
  17. package/dist/commands/has.d.ts +2 -2
  18. package/dist/commands/has.d.ts.map +1 -1
  19. package/dist/commands/has.js +12 -15
  20. package/dist/commands/init.d.ts +2 -2
  21. package/dist/commands/init.d.ts.map +1 -1
  22. package/dist/commands/init.js +92 -100
  23. package/dist/commands/inspect.d.ts +2 -2
  24. package/dist/commands/inspect.d.ts.map +1 -1
  25. package/dist/commands/inspect.js +14 -16
  26. package/dist/commands/keys.d.ts +2 -1
  27. package/dist/commands/keys.d.ts.map +1 -1
  28. package/dist/commands/keys.js +47 -49
  29. package/dist/commands/list.d.ts +2 -2
  30. package/dist/commands/list.d.ts.map +1 -1
  31. package/dist/commands/list.js +11 -14
  32. package/dist/commands/migrate.d.ts +2 -1
  33. package/dist/commands/migrate.d.ts.map +1 -1
  34. package/dist/commands/migrate.js +38 -37
  35. package/dist/commands/push.d.ts +2 -2
  36. package/dist/commands/push.d.ts.map +1 -1
  37. package/dist/commands/push.js +41 -45
  38. package/dist/commands/resolve.d.ts +2 -2
  39. package/dist/commands/resolve.d.ts.map +1 -1
  40. package/dist/commands/resolve.js +25 -28
  41. package/dist/commands/run.d.ts +2 -2
  42. package/dist/commands/run.d.ts.map +1 -1
  43. package/dist/commands/run.js +35 -39
  44. package/dist/commands/set.d.ts +2 -2
  45. package/dist/commands/set.d.ts.map +1 -1
  46. package/dist/commands/set.js +61 -70
  47. package/dist/commands/skill.d.ts +2 -2
  48. package/dist/commands/skill.d.ts.map +1 -1
  49. package/dist/commands/skill.js +149 -459
  50. package/dist/commands/status.d.ts +2 -2
  51. package/dist/commands/status.d.ts.map +1 -1
  52. package/dist/commands/status.js +48 -52
  53. package/dist/commands/template.d.ts +2 -2
  54. package/dist/commands/template.d.ts.map +1 -1
  55. package/dist/commands/template.js +36 -39
  56. package/dist/commands/trace.d.ts +2 -2
  57. package/dist/commands/trace.d.ts.map +1 -1
  58. package/dist/commands/trace.js +16 -19
  59. package/dist/config/loader.js +3 -3
  60. package/dist/context.d.ts +3 -0
  61. package/dist/context.d.ts.map +1 -0
  62. package/dist/context.js +60 -0
  63. package/dist/core/parse.js +3 -3
  64. package/dist/core/sops.js +9 -9
  65. package/dist/core/template.d.ts +2 -2
  66. package/dist/core/template.d.ts.map +1 -1
  67. package/dist/core/template.js +11 -12
  68. package/dist/lib/age.js +9 -9
  69. package/dist/lib/fs.d.ts +25 -0
  70. package/dist/lib/fs.d.ts.map +1 -0
  71. package/dist/lib/fs.js +36 -0
  72. package/dist/lib/onepassword.d.ts.map +1 -1
  73. package/dist/lib/onepassword.js +41 -4
  74. package/dist/types.d.ts +92 -0
  75. package/dist/types.d.ts.map +1 -1
  76. package/dist/utils/version-check.js +5 -5
  77. package/package.json +3 -2
@@ -1,7 +1,5 @@
1
- import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
2
1
  import { createInterface } from 'node:readline';
3
2
  import { homedir } from 'node:os';
4
- import { join } from 'node:path';
5
3
  import pc from 'picocolors';
6
4
  const SKILL_FILES = {
7
5
  'SKILL.md': `---
@@ -14,7 +12,7 @@ allowed-tools: Bash(hush:*), Bash(npx hush:*), Bash(brew:*), Bash(npm:*), Bash(p
14
12
 
15
13
  **CRITICAL: NEVER read .hush files directly.** Always use \`npx hush status\`, \`npx hush inspect\`, or \`npx hush has\` to check secrets.
16
14
 
17
- Hush keeps secrets **encrypted at rest** at the project root using \`.hush.encrypted\` files. Subdirectory \`.env\` files are **templates** (safe to commit and read) that reference root secrets via \`\${VAR}\` syntax.
15
+ Hush keeps secrets **encrypted at rest** at the project root using \`.hush.encrypted\` files. Subdirectory \`.hush\` files are **templates** (safe to commit) that reference root secrets via \`\${VAR}\` syntax.
18
16
 
19
17
  ## First Step: Investigate Current State
20
18
 
@@ -41,7 +39,7 @@ This tells you:
41
39
  | \`age key not found\` | Missing encryption key | \`npx hush keys setup\` |
42
40
  | \`Project: not set\` | Key management limited | Add \`project:\` to hush.yaml |
43
41
 
44
- **Note:** Security warnings only apply to root-level \`.env\` files. Subdirectory \`.env\` files are templates (safe to commit).
42
+ **Note:** Any \`.env\` file is suspect - Hush uses \`.hush\` files everywhere. Subdirectory \`.hush\` files are templates (safe to commit).
45
43
 
46
44
  ## Decision Tree: What Do I Do?
47
45
 
@@ -72,7 +70,8 @@ npx hush inspect # See what secrets exist
72
70
  ### Scenario 4: Need to Add/Modify Secrets
73
71
 
74
72
  \`\`\`bash
75
- npx hush set <KEY> # Add interactively (you invoke, user types value)
73
+ npx hush set <VALUE> <KEY> # Add inline (value and key provided)
74
+ npx hush set <KEY> # Add interactively (prompts for value)
76
75
  npx hush edit # Edit all secrets in $EDITOR
77
76
  npx hush inspect # Verify changes
78
77
  \`\`\`
@@ -110,12 +109,12 @@ targets:
110
109
  include: [NEXT_PUBLIC_*] # All matching vars auto-flow
111
110
  \`\`\`
112
111
 
113
- ### Pull (subdirectory .env templates)
112
+ ### Pull (subdirectory .hush templates)
114
113
 
115
114
  Best for transformation, renaming, or explicit dependencies:
116
115
 
117
116
  \`\`\`bash
118
- # apps/mobile/.env (committed - it's just a template)
117
+ # apps/mobile/.hush (committed - it's just a template)
119
118
  EXPO_PUBLIC_API_URL=\${API_URL} # Rename from root
120
119
  PORT=\${PORT:-8081} # Default value
121
120
  \`\`\`
@@ -126,7 +125,7 @@ PORT=\${PORT:-8081} # Default value
126
125
 
127
126
  ## Subdirectory Templates (Pull-Based)
128
127
 
129
- When a subdirectory needs to rename, transform, or add defaults to root secrets, create a \`.env\` template file in that subdirectory.
128
+ When a subdirectory needs to rename, transform, or add defaults to root secrets, create a \`.hush\` template file in that subdirectory.
130
129
 
131
130
  ### Step-by-Step Setup
132
131
 
@@ -137,7 +136,7 @@ npx hush inspect # From repo root - verify secrets are configured
137
136
 
138
137
  **2. Create subdirectory template (this file is committed to git):**
139
138
  \`\`\`bash
140
- # apps/mobile/.env
139
+ # apps/mobile/.hush
141
140
  EXPO_PUBLIC_API_URL=\${API_URL} # Pull API_URL from root, rename it
142
141
  EXPO_PUBLIC_STRIPE_KEY=\${STRIPE_KEY} # Pull and rename
143
142
  PORT=\${PORT:-8081} # Use root PORT, or default to 8081
@@ -153,7 +152,7 @@ npx hush run -- npm start
153
152
  Hush automatically:
154
153
  1. Finds the project root (where \`hush.yaml\` is)
155
154
  2. Decrypts root secrets
156
- 3. Loads the local \`.env\` template
155
+ 3. Loads the local \`.hush\` template
157
156
  4. Resolves \`\${VAR}\` references against root secrets
158
157
  5. **Filters root secrets based on target config (include/exclude)**
159
158
  6. **Merges them (Template overrides Target)**
@@ -171,7 +170,7 @@ Hush automatically:
171
170
 
172
171
  **Expo/React Native app:**
173
172
  \`\`\`bash
174
- # apps/mobile/.env
173
+ # apps/mobile/.hush
175
174
  EXPO_PUBLIC_API_URL=\${API_URL}
176
175
  EXPO_PUBLIC_STRIPE_KEY=\${STRIPE_PUBLISHABLE_KEY}
177
176
  EXPO_PUBLIC_ENV=\${ENV:-development}
@@ -179,7 +178,7 @@ EXPO_PUBLIC_ENV=\${ENV:-development}
179
178
 
180
179
  **Next.js app:**
181
180
  \`\`\`bash
182
- # apps/web/.env
181
+ # apps/web/.hush
183
182
  NEXT_PUBLIC_API_URL=\${API_URL}
184
183
  NEXT_PUBLIC_STRIPE_KEY=\${STRIPE_PUBLISHABLE_KEY}
185
184
  DATABASE_URL=\${DATABASE_URL}
@@ -187,20 +186,30 @@ DATABASE_URL=\${DATABASE_URL}
187
186
 
188
187
  **API server with defaults:**
189
188
  \`\`\`bash
190
- # apps/api/.env
189
+ # apps/api/.hush
191
190
  DATABASE_URL=\${DATABASE_URL}
192
191
  PORT=\${PORT:-8787}
193
192
  LOG_LEVEL=\${LOG_LEVEL:-info}
194
193
  \`\`\`
195
194
 
196
- ### Important Notes
195
+ ### Important Notes: File Conventions
197
196
 
198
- - **Subdirectory .env files ARE committed to git** - they're templates, not secrets
199
- - **Can contain expansions AND constants** - \`APP_NAME=MyApp\` alongside \`API_URL=\${API_URL}\`
197
+ **All Hush files use \`.hush\` extension - never \`.env\`:**
198
+
199
+ | Location | File Type | Contains | Committed? |
200
+ |----------|-----------|----------|------------|
201
+ | **Root** | \`.hush\` | Actual secrets | NO (gitignored) |
202
+ | **Root** | \`.hush.encrypted\` | Encrypted secrets | YES |
203
+ | **Subdirectory** | \`.hush\` | Templates with \`\${VAR}\` | YES (safe) |
204
+ | **Subdirectory** | \`.hush.development\` | Dev-specific templates | YES (safe) |
205
+ | **Anywhere** | \`.env\` | ⚠️ LEGACY - delete! | NO |
206
+
207
+ **Key rules:**
208
+ - **Everything is \`.hush\`** - consistent naming throughout
209
+ - **Any \`.env\` file is wrong** - legacy file that should be deleted
210
+ - Subdirectory \`.hush\` templates are safe to commit (no actual secrets)
200
211
  - **Run from the subdirectory** - \`hush run\` auto-detects the project root
201
- - **Root secrets stay encrypted** - subdirectory templates just reference them
202
212
  - **Self-reference works** - \`PORT=\${PORT:-3000}\` uses root PORT if set, else 3000
203
- - **Security warnings only apply to root** - subdirectory .env files are always safe
204
213
 
205
214
  ---
206
215
 
@@ -211,7 +220,7 @@ LOG_LEVEL=\${LOG_LEVEL:-info}
211
220
  | \`npx hush status\` | **Full diagnostic** | First step, always |
212
221
  | \`npx hush inspect\` | See variables (masked) | Check what's configured |
213
222
  | \`npx hush has <KEY>\` | Check specific variable | Verify a secret exists |
214
- | \`npx hush set <KEY>\` | Add secret interactively | User needs to enter a value |
223
+ | \`npx hush set [VALUE] <KEY>\` | Add secret (prompts if no value) | User needs to set a value |
215
224
  | \`npx hush edit\` | Edit all secrets | Bulk editing |
216
225
  | \`npx hush run -- <cmd>\` | Run with secrets in memory | Actually use the secrets |
217
226
  | \`npx hush init\` | Initialize Hush | First-time setup |
@@ -304,25 +313,34 @@ npx hush has API_KEY -q # Quiet: exit code only (0=set, 1=missing)
304
313
 
305
314
  ## Adding/Modifying Secrets
306
315
 
307
- ### Add a single secret interactively
316
+ ### Add a single secret (three methods)
317
+
318
+ **Method 1: Inline value (recommended for AI agents)**
319
+ \`\`\`bash
320
+ npx hush set "postgres://user:pass@host/db" DATABASE_URL
321
+ npx hush set "sk_live_xxx" STRIPE_KEY -e production
322
+ \`\`\`
308
323
 
324
+ **Method 2: Interactive prompt (for users)**
309
325
  \`\`\`bash
310
- npx hush set DATABASE_URL # You invoke this, user types value
326
+ npx hush set DATABASE_URL # Prompts user for value
311
327
  npx hush set API_KEY -e production # Set in production secrets
312
328
  npx hush set DEBUG --local # Set personal local override
313
329
  \`\`\`
314
330
 
315
- The user will be prompted to enter the value (hidden input).
316
- **You never see the actual secret - just invoke the command!**
317
-
318
- ### Add a secret via pipe (for scripts/automation)
319
-
331
+ **Method 3: Pipe (for scripts/automation)**
320
332
  \`\`\`bash
321
333
  echo "my-secret-value" | npx hush set MY_KEY
322
334
  cat secret.txt | npx hush set CERT_CONTENT
323
335
  \`\`\`
324
336
 
325
- When stdin has piped data, Hush reads from it instead of prompting.
337
+ ### GUI dialog for AI agents
338
+
339
+ When running in a non-TTY environment (like AI agents), use \`--gui\`:
340
+ \`\`\`bash
341
+ npx hush set API_KEY --gui # Opens visible dialog
342
+ \`\`\`
343
+ The dialog shows the pasted value for easy verification.
326
344
 
327
345
  ---
328
346
 
@@ -463,13 +481,15 @@ Customize targets for your monorepo. Common patterns:
463
481
 
464
482
  ### Step 5: Create initial \`.hush\` files
465
483
 
466
- Create \`.hush\` with shared secrets:
484
+ Create \`.hush\` with shared secrets at the **repository root**:
467
485
 
468
486
  \`\`\`bash
469
- # .hush
487
+ # .hush (root level - contains actual secrets)
470
488
  DATABASE_URL=postgres://localhost/mydb
489
+ SUPABASE_URL=https://xxx.supabase.co
490
+ SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
491
+ STRIPE_SECRET_KEY=sk_test_xxx
471
492
  API_KEY=your_api_key_here
472
- NEXT_PUBLIC_API_URL=http://localhost:3000
473
493
  \`\`\`
474
494
 
475
495
  Create \`.hush.development\` for dev-specific values:
@@ -488,6 +508,50 @@ DEBUG=false
488
508
  LOG_LEVEL=error
489
509
  \`\`\`
490
510
 
511
+ ### Step 5b: Set up subdirectory templates (for monorepos)
512
+
513
+ For packages that need secrets with different prefixes, create a **template** \`.hush\` file in the subdirectory.
514
+
515
+ **Example: Expo app needs Supabase with EXPO_PUBLIC_ prefix**
516
+
517
+ \`\`\`bash
518
+ # apps/mobile/.hush (committed to git - template with variable references)
519
+ EXPO_PUBLIC_SUPABASE_URL=\${SUPABASE_URL}
520
+ EXPO_PUBLIC_SUPABASE_ANON_KEY=\${SUPABASE_ANON_KEY}
521
+ EXPO_PUBLIC_API_URL=\${API_URL:-http://localhost:3000}
522
+ \`\`\`
523
+
524
+ **Example: Next.js app needs different prefixes**
525
+
526
+ \`\`\`bash
527
+ # apps/web/.hush (committed to git - template)
528
+ NEXT_PUBLIC_SUPABASE_URL=\${SUPABASE_URL}
529
+ NEXT_PUBLIC_SUPABASE_ANON_KEY=\${SUPABASE_ANON_KEY}
530
+ DATABASE_URL=\${DATABASE_URL}
531
+ \`\`\`
532
+
533
+ **File structure:**
534
+ \`\`\`
535
+ repo-root/
536
+ ├── .hush # Actual secrets (gitignored)
537
+ ├── .hush.encrypted # Encrypted secrets (committed)
538
+ ├── apps/
539
+ │ ├── mobile/
540
+ │ │ └── .hush # Template with \${VAR} refs (committed)
541
+ │ └── web/
542
+ │ └── .hush # Template with \${VAR} refs (committed)
543
+ \`\`\`
544
+
545
+ **How it works:**
546
+ 1. Root \`.hush\` contains actual secrets → encrypted to \`.hush.encrypted\`
547
+ 2. Subdirectory \`.hush\` templates reference root secrets via \`\${VAR}\`
548
+ 3. Run \`hush run\` from subdirectory - it resolves templates automatically
549
+
550
+ \`\`\`bash
551
+ cd apps/mobile
552
+ npx hush run -- expo start # Template vars resolved from root secrets
553
+ \`\`\`
554
+
491
555
  ### Step 6: Encrypt secrets
492
556
 
493
557
  \`\`\`bash
@@ -598,7 +662,7 @@ Complete reference for all Hush CLI commands with flags, options, and examples.
598
662
 
599
663
  ## Security Model: Encrypted at Rest
600
664
 
601
- 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.
665
+ All secrets are stored encrypted in \`.hush.encrypted\` files. Never read \`.env\` files directly - they indicate legacy/misconfigured setup. Use \`hush inspect\` or \`hush has\` to check secrets safely.
602
666
 
603
667
  ## Global Options
604
668
 
@@ -636,23 +700,27 @@ hush run -t api -- wrangler dev # Run filtered for 'api' target
636
700
 
637
701
  ---
638
702
 
639
- ### hush set <KEY> ⭐
703
+ ### hush set [VALUE] <KEY> ⭐
640
704
 
641
- Add or update a single secret interactively. You invoke this, user enters the value.
705
+ Add or update a single secret. Prompts for value if not provided inline.
642
706
 
643
707
  \`\`\`bash
644
- hush set DATABASE_URL # Set in shared secrets
708
+ hush set DATABASE_URL # Prompts for value interactively
709
+ hush set "postgres://..." DATABASE_URL # Inline value (no prompt)
645
710
  hush set API_KEY -e production # Set in production secrets
646
711
  hush set DEBUG --local # Set personal local override
647
712
  \`\`\`
648
713
 
649
- User will be prompted with hidden input - the value is never visible.
714
+ **Input methods (in priority order):**
715
+ 1. **Inline value**: \`hush set "myvalue" KEY\` - value provided directly
716
+ 2. **Piped input**: \`echo "myvalue" | hush set KEY\` - reads from stdin
717
+ 3. **Interactive prompt**: Opens dialog/prompt for user input
650
718
 
651
- **Pipe support:** You can also pipe values directly:
719
+ **GUI dialog (--gui flag):**
652
720
  \`\`\`bash
653
- echo "my-secret" | hush set MY_KEY
654
- cat cert.pem | hush set CERTIFICATE
721
+ hush set API_KEY --gui # Opens visible dialog (for AI agents)
655
722
  \`\`\`
723
+ The GUI dialog shows the value as you type/paste for easier verification.
656
724
 
657
725
  ---
658
726
 
@@ -786,7 +854,7 @@ hush trace STRIPE_SECRET_KEY # Trace another variable
786
854
 
787
855
  ### hush template
788
856
 
789
- Show the resolved template for the current directory's \`.env\` file.
857
+ Show the resolved template for the current directory's \`.hush\` file.
790
858
 
791
859
  \`\`\`bash
792
860
  cd apps/mobile
@@ -805,7 +873,7 @@ hush template -e production # Show for production
805
873
 
806
874
  ### hush expansions
807
875
 
808
- Show the expansion graph across all subdirectories that have \`.env\` templates.
876
+ Show the expansion graph across all subdirectories that have \`.hush\` templates.
809
877
 
810
878
  \`\`\`bash
811
879
  hush expansions # Scan all subdirectories
@@ -813,7 +881,7 @@ hush expansions -e production # Show for production
813
881
  \`\`\`
814
882
 
815
883
  **Output shows:**
816
- - Which subdirectories have \`.env\` templates
884
+ - Which subdirectories have \`.hush\` templates
817
885
  - What variables each template references from root
818
886
  - Resolution status for each reference
819
887
 
@@ -1065,8 +1133,8 @@ PORT=\${PORT:-3000}
1065
1133
  # System environment (explicit opt-in)
1066
1134
  CI=\${env:CI}
1067
1135
 
1068
- # Pull from root (subdirectory .env can reference root secrets)
1069
- # apps/mobile/.env:
1136
+ # Pull from root (subdirectory .hush can reference root secrets)
1137
+ # apps/mobile/.hush:
1070
1138
  EXPO_PUBLIC_API_URL=\${API_URL} # Renamed from root
1071
1139
  \`\`\`
1072
1140
 
@@ -1083,385 +1151,7 @@ targets:
1083
1151
 
1084
1152
  **Pull (subdirectory templates):** Transformation, renaming, defaults
1085
1153
  \`\`\`bash
1086
- # apps/mobile/.env
1087
- EXPO_PUBLIC_API_URL=\${API_URL} # Rename required
1088
- PORT=\${PORT:-3000} # Default value
1089
- \`\`\`
1090
-
1091
- **Decision:** Use push for "all X → Y". Use pull for rename/transform/defaults.
1092
- `,
1093
- 'examples/workflows.md': `# Hush Workflow Examples
1094
-
1095
- Step-by-step examples for common workflows when working with secrets.
1096
-
1097
- **CRITICAL: NEVER read .env files directly. Use hush commands instead.**
1098
-
1099
- ---
1100
-
1101
- ## First-Time Setup (Most Important!)
1102
-
1103
- ### "Help me set up Hush for this project"
1104
-
1105
- **Step 1: Check current state**
1106
- \`\`\`bash
1107
- npx hush status
1108
- \`\`\`
1109
-
1110
- This will show:
1111
- - If Hush is already configured
1112
- - If there are unencrypted .env files (security risk!)
1113
- - What prerequisites are missing
1114
-
1115
- **Step 2: Based on the output, follow the appropriate path:**
1116
-
1117
- #### Path A: "SECURITY WARNING: Unencrypted .env files detected"
1118
- \`\`\`bash
1119
- # If migrating from v4 (has .env.encrypted files):
1120
- npx hush migrate # Converts to .hush.encrypted format
1121
-
1122
- # If new setup with plaintext .env files:
1123
- mv .env .hush # Rename to .hush
1124
- npx hush init # If no hush.yaml exists
1125
- npx hush encrypt # Encrypts .hush files
1126
- npx hush status # Verify the warning is gone
1127
- \`\`\`
1128
-
1129
- #### Path B: "No hush.yaml found"
1130
- \`\`\`bash
1131
- npx hush init # Creates config and sets up keys
1132
- npx hush set <KEY> # Add secrets (if none exist yet)
1133
- \`\`\`
1134
-
1135
- #### Path C: "age key not found"
1136
- \`\`\`bash
1137
- npx hush keys setup # Pull from 1Password or generate new key
1138
- \`\`\`
1139
-
1140
- #### Path D: Everything looks good
1141
- \`\`\`bash
1142
- npx hush inspect # See what secrets are configured
1143
- \`\`\`
1144
-
1145
- ---
1146
-
1147
- ## Running Programs (Most Common)
1148
-
1149
- ### "Start the development server"
1150
- \`\`\`bash
1151
- npx hush run -- npm run dev
1152
- \`\`\`
1153
-
1154
- ### "Build for production"
1155
- \`\`\`bash
1156
- npx hush run -e production -- npm run build
1157
- \`\`\`
1158
-
1159
- ### "Run tests with secrets"
1160
- \`\`\`bash
1161
- npx hush run -- npm test
1162
- \`\`\`
1163
-
1164
- ### "Run Wrangler for Cloudflare Worker"
1165
- \`\`\`bash
1166
- npx hush run -t api -- wrangler dev
1167
- \`\`\`
1168
-
1169
- ---
1170
-
1171
- ## Checking Secrets
1172
-
1173
- ### "What environment variables does this project use?"
1174
- \`\`\`bash
1175
- npx hush inspect # Shows all variables with masked values
1176
- \`\`\`
1177
-
1178
- ### "Is the database configured?"
1179
- \`\`\`bash
1180
- npx hush has DATABASE_URL
1181
- \`\`\`
1182
-
1183
- If "not found", help user add it:
1184
- \`\`\`bash
1185
- npx hush set DATABASE_URL
1186
- \`\`\`
1187
- Tell user: "Enter your database URL when prompted"
1188
-
1189
- ### "Check all required secrets"
1190
- \`\`\`bash
1191
- npx hush has DATABASE_URL -q && \\
1192
- npx hush has API_KEY -q && \\
1193
- echo "All configured" || \\
1194
- echo "Some missing"
1195
- \`\`\`
1196
-
1197
- ---
1198
-
1199
- ## Adding Secrets
1200
-
1201
- ### "Help me add DATABASE_URL"
1202
- \`\`\`bash
1203
- npx hush set DATABASE_URL
1204
- \`\`\`
1205
- Tell user: "Enter your database URL when prompted (input will be hidden)"
1206
-
1207
- ### "Add a production-only secret"
1208
- \`\`\`bash
1209
- npx hush set STRIPE_SECRET_KEY -e production
1210
- \`\`\`
1211
-
1212
- ### "Add a personal local override"
1213
- \`\`\`bash
1214
- npx hush set DEBUG --local
1215
- \`\`\`
1216
-
1217
- ### "Edit multiple secrets at once"
1218
- \`\`\`bash
1219
- npx hush edit
1220
- \`\`\`
1221
- Tell user: "Your editor will open. Add or modify secrets, then save and close."
1222
-
1223
- ---
1224
-
1225
- ## Debugging
1226
-
1227
- ### "My app can't find DATABASE_URL"
1228
-
1229
- 1. **Trace the variable** to see where it exists and where it goes:
1230
- \`\`\`bash
1231
- npx hush trace DATABASE_URL
1232
- \`\`\`
1233
- This shows which source files have it and which targets include/exclude it.
1234
-
1235
- 2. **Check if it exists** in your current environment:
1236
- \`\`\`bash
1237
- npx hush has DATABASE_URL
1238
- \`\`\`
1239
-
1240
- 3. **Resolve the target** to see what variables it receives:
1241
- \`\`\`bash
1242
- npx hush resolve api-workers
1243
- \`\`\`
1244
-
1245
- ### "Target is missing expected variables"
1246
-
1247
- \`\`\`bash
1248
- npx hush resolve <target-name> # See included/excluded variables
1249
- npx hush resolve <target-name> -e prod # Check production
1250
- \`\`\`
1251
-
1252
- Look at the 🚫 EXCLUDED section to see which pattern is filtering out your variable.
1253
-
1254
- ### "Wrangler dev not seeing secrets"
1255
-
1256
- If you are using \`hush run -- wrangler dev\` and secrets are missing:
1257
-
1258
- **Step 1: Check for blocking files**
1259
- \`\`\`bash
1260
- ls -la .dev.vars # If this exists, it blocks Hush secrets
1261
- \`\`\`
1262
-
1263
- **Step 2: Delete the blocking file**
1264
- \`\`\`bash
1265
- rm .dev.vars
1266
- \`\`\`
1267
-
1268
- **Step 3: Run normally**
1269
- \`\`\`bash
1270
- npx hush run -t api -- wrangler dev
1271
- \`\`\`
1272
-
1273
- **Step 4: If still not working, update Wrangler**
1274
- \`\`\`bash
1275
- npm update wrangler
1276
- \`\`\`
1277
-
1278
- **Why this happens:**
1279
- - Wrangler has a strict rule: if \`.dev.vars\` exists (even empty!), it ignores ALL environment variables
1280
- - Hush automatically sets \`CLOUDFLARE_INCLUDE_PROCESS_ENV=true\` for you
1281
- - But Wrangler only respects this when no \`.dev.vars\` file exists
1282
- - Older Wrangler versions may not support \`CLOUDFLARE_INCLUDE_PROCESS_ENV\` at all
1283
-
1284
- **Prevention tip:** Never use \`hush decrypt\` for Wrangler targets—always use \`hush run\`.
1285
-
1286
- ### "Variable appears in wrong places"
1287
-
1288
- \`\`\`bash
1289
- npx hush trace <VARIABLE_NAME>
1290
- \`\`\`
1291
-
1292
- This shows the full disposition across all targets - which include it and which exclude it.
1293
-
1294
- ### "Push is missing some secrets"
1295
-
1296
- \`\`\`bash
1297
- npx hush push --dry-run --verbose
1298
- \`\`\`
1299
-
1300
- This shows exactly what would be pushed to each target.
1301
-
1302
- ---
1303
-
1304
- ## Team Workflows
1305
-
1306
- ### "New team member setup"
1307
-
1308
- Guide them through these steps:
1309
- \`\`\`bash
1310
- # 1. Pull key from 1Password (or get from team member)
1311
- npx hush keys setup
1312
-
1313
- # 2. Verify setup
1314
- npx hush status
1315
-
1316
- # 3. Check secrets are accessible
1317
- npx hush inspect
1318
-
1319
- # 4. Start developing
1320
- npx hush run -- npm run dev
1321
- \`\`\`
1322
-
1323
- ### "Someone added new secrets"
1324
- \`\`\`bash
1325
- git pull
1326
- npx hush inspect # See what's new
1327
- \`\`\`
1328
-
1329
- ---
1330
-
1331
- ## Deployment
1332
-
1333
- ### "Push to Cloudflare Workers"
1334
- \`\`\`bash
1335
- npx hush push --dry-run # Preview first
1336
- npx hush push # Actually push
1337
- npx hush push -t api # Push specific target
1338
- \`\`\`
1339
-
1340
- ### "Push to Cloudflare Pages"
1341
-
1342
- First, add \`push_to\` to your target in \`hush.yaml\`:
1343
- \`\`\`yaml
1344
- targets:
1345
- - name: app
1346
- path: ./app
1347
- format: dotenv
1348
- push_to:
1349
- type: cloudflare-pages
1350
- project: my-pages-project
1351
- \`\`\`
1352
-
1353
- Then push:
1354
- \`\`\`bash
1355
- npx hush push -t app --dry-run # Preview first
1356
- npx hush push -t app # Actually push
1357
- \`\`\`
1358
-
1359
- ### "Build and deploy"
1360
- \`\`\`bash
1361
- npx hush run -e production -- npm run build
1362
- npx hush push
1363
- \`\`\`
1364
-
1365
- ---
1366
-
1367
- ## Setting Up Subdirectory Templates (Pull-Based Secrets)
1368
-
1369
- ### "Set up secrets for a subdirectory app (Expo, Next.js, etc.)"
1370
-
1371
- **Use this when:** You need to rename, transform, or add defaults to root secrets for a specific package.
1372
-
1373
- **Step 1: Verify root secrets exist**
1374
- \`\`\`bash
1375
- cd /path/to/repo/root
1376
- npx hush inspect
1377
- \`\`\`
1378
-
1379
- **Step 2: Create the subdirectory template file**
1380
-
1381
- Create a \`.env\` file in the subdirectory. This file is committed to git - it's just a template, not actual secrets.
1382
-
1383
- \`\`\`bash
1384
- # Example: apps/mobile/.env
1385
- EXPO_PUBLIC_API_URL=\${API_URL} # Pulls API_URL from root, renames it
1386
- EXPO_PUBLIC_STRIPE_KEY=\${STRIPE_KEY} # Pulls and renames
1387
- PORT=\${PORT:-8081} # Uses root PORT if set, otherwise 8081
1388
- DEBUG=\${DEBUG:-false} # Uses root DEBUG if set, otherwise false
1389
- \`\`\`
1390
-
1391
- **Step 3: Run from the subdirectory**
1392
- \`\`\`bash
1393
- cd apps/mobile
1394
- npx hush run -- npm start
1395
- \`\`\`
1396
-
1397
- ### Variable Expansion Syntax Reference
1398
-
1399
- | Syntax | What It Does | Example |
1400
- |--------|--------------|---------|
1401
- | \`\${VAR}\` | Pull VAR from root secrets | \`API_URL=\${API_URL}\` |
1402
- | \`\${VAR:-default}\` | Pull VAR, use default if not set | \`PORT=\${PORT:-3000}\` |
1403
- | \`\${env:VAR}\` | Read from system environment | \`CI=\${env:CI}\` |
1404
-
1405
- ### Framework Examples
1406
-
1407
- **Expo/React Native:**
1408
- \`\`\`bash
1409
- # apps/mobile/.env
1410
- EXPO_PUBLIC_API_URL=\${API_URL}
1411
- EXPO_PUBLIC_STRIPE_KEY=\${STRIPE_PUBLISHABLE_KEY}
1412
- EXPO_PUBLIC_ENV=\${ENV:-development}
1413
- \`\`\`
1414
-
1415
- **Next.js:**
1416
- \`\`\`bash
1417
- # apps/web/.env
1418
- NEXT_PUBLIC_API_URL=\${API_URL}
1419
- NEXT_PUBLIC_STRIPE_KEY=\${STRIPE_PUBLISHABLE_KEY}
1420
- DATABASE_URL=\${DATABASE_URL}
1421
- \`\`\`
1422
-
1423
- **Cloudflare Worker:**
1424
- \`\`\`bash
1425
- # apps/api/.env
1426
- DATABASE_URL=\${DATABASE_URL}
1427
- STRIPE_SECRET_KEY=\${STRIPE_SECRET_KEY}
1428
- PORT=\${PORT:-8787}
1429
- \`\`\`
1430
-
1431
- ### Important Notes
1432
-
1433
- - **Template files ARE committed** to git (they contain no secrets)
1434
- - **Root secrets stay encrypted** - templates just reference them
1435
- - **Run from subdirectory** - \`hush run\` finds the project root automatically
1436
- - **Self-reference works** - \`PORT=\${PORT:-3000}\` uses root PORT if set
1437
-
1438
- ---
1439
-
1440
- ## Choosing Push vs Pull (Monorepos)
1441
-
1442
- ### "How should I set up secrets for a new package?"
1443
-
1444
- **Ask yourself:** Does this package need to rename variables or add defaults?
1445
-
1446
- #### If NO (simple filtering) → Use Push
1447
-
1448
- Edit \`hush.yaml\` to add a target:
1449
- \`\`\`yaml
1450
- targets:
1451
- - name: new-package
1452
- path: ./packages/new-package
1453
- format: dotenv
1454
- include:
1455
- - NEXT_PUBLIC_* # Or whatever pattern fits
1456
- \`\`\`
1457
-
1458
- **Benefits:** New \`NEXT_PUBLIC_*\` vars at root auto-flow. Zero maintenance.
1459
-
1460
- #### If YES (transformation needed) → Use Pull
1461
-
1462
- Create a template \`.env\` in the package:
1463
- \`\`\`bash
1464
- # packages/mobile/.env (committed to git)
1154
+ # packages/mobile/.hush
1465
1155
  EXPO_PUBLIC_API_URL=\${API_URL} # Rename from root
1466
1156
  EXPO_PUBLIC_DEBUG=\${DEBUG:-false} # With default
1467
1157
  PORT=\${PORT:-8081} # Local default
@@ -1474,7 +1164,7 @@ PORT=\${PORT:-8081} # Local default
1474
1164
  | Scenario | Update |
1475
1165
  |----------|--------|
1476
1166
  | New \`NEXT_PUBLIC_*\` var, web uses push | Nothing! Auto-flows |
1477
- | New var mobile needs, mobile uses pull | \`packages/mobile/.env\` template |
1167
+ | New var mobile needs, mobile uses pull | \`packages/mobile/.hush\` template |
1478
1168
  | New package needs secrets | \`hush.yaml\` (push) or new template (pull) |
1479
1169
  | Change var routing | \`hush.yaml\` include/exclude patterns |
1480
1170
 
@@ -1528,23 +1218,23 @@ Total: 3 variables
1528
1218
  - \`API_KEY\` is not set - user needs to add it
1529
1219
  `,
1530
1220
  };
1531
- function getSkillPath(location, root) {
1221
+ function getSkillPath(ctx, location, root) {
1532
1222
  if (location === 'global') {
1533
- return join(homedir(), '.claude', 'skills', 'hush-secrets');
1223
+ return ctx.path.join(homedir(), '.claude', 'skills', 'hush-secrets');
1534
1224
  }
1535
- return join(root, '.claude', 'skills', 'hush-secrets');
1225
+ return ctx.path.join(root, '.claude', 'skills', 'hush-secrets');
1536
1226
  }
1537
- async function promptForLocation() {
1227
+ async function promptForLocation(ctx) {
1538
1228
  const rl = createInterface({
1539
- input: process.stdin,
1540
- output: process.stdout,
1229
+ input: ctx.process.stdin,
1230
+ output: ctx.process.stdout,
1541
1231
  });
1542
1232
  return new Promise((resolve) => {
1543
- console.log(pc.bold('\nWhere would you like to install the Claude skill?\n'));
1544
- console.log(` ${pc.cyan('1)')} ${pc.bold('Global')} ${pc.dim('(~/.claude/skills/)')}`);
1545
- console.log(` Works across all your projects. Recommended for personal use.\n`);
1546
- console.log(` ${pc.cyan('2)')} ${pc.bold('Local')} ${pc.dim('(.claude/skills/)')}`);
1547
- console.log(` Bundled with this project. Recommended for teams.\n`);
1233
+ ctx.logger.log(pc.bold('\nWhere would you like to install the Claude skill?\n'));
1234
+ ctx.logger.log(` ${pc.cyan('1)')} ${pc.bold('Global')} ${pc.dim('(~/.claude/skills/)')}`);
1235
+ ctx.logger.log(` Works across all your projects. Recommended for personal use.\n`);
1236
+ ctx.logger.log(` ${pc.cyan('2)')} ${pc.bold('Local')} ${pc.dim('(.claude/skills/)')}`);
1237
+ ctx.logger.log(` Bundled with this project. Recommended for teams.\n`);
1548
1238
  rl.question(`${pc.bold('Choice')} ${pc.dim('[1/2]')}: `, (answer) => {
1549
1239
  rl.close();
1550
1240
  const choice = answer.trim();
@@ -1557,16 +1247,16 @@ async function promptForLocation() {
1557
1247
  });
1558
1248
  });
1559
1249
  }
1560
- function writeSkillFiles(skillPath) {
1561
- mkdirSync(skillPath, { recursive: true });
1562
- mkdirSync(join(skillPath, 'examples'), { recursive: true });
1250
+ function writeSkillFiles(ctx, skillPath) {
1251
+ ctx.fs.mkdirSync(skillPath, { recursive: true });
1252
+ ctx.fs.mkdirSync(ctx.path.join(skillPath, 'examples'), { recursive: true });
1563
1253
  for (const [filename, content] of Object.entries(SKILL_FILES)) {
1564
- const filePath = join(skillPath, filename);
1565
- writeFileSync(filePath, content, 'utf-8');
1254
+ const filePath = ctx.path.join(skillPath, filename);
1255
+ ctx.fs.writeFileSync(filePath, content, 'utf-8');
1566
1256
  }
1567
1257
  }
1568
- export async function skillCommand(options) {
1569
- const { root, global: isGlobal, local: isLocal } = options;
1258
+ export async function skillCommand(ctx, options) {
1259
+ const { global: isGlobal, local: isLocal } = options;
1570
1260
  let location;
1571
1261
  if (isGlobal) {
1572
1262
  location = 'global';
@@ -1575,30 +1265,30 @@ export async function skillCommand(options) {
1575
1265
  location = 'local';
1576
1266
  }
1577
1267
  else {
1578
- location = await promptForLocation();
1268
+ location = await promptForLocation(ctx);
1579
1269
  }
1580
- const skillPath = getSkillPath(location, root);
1581
- const alreadyInstalled = existsSync(join(skillPath, 'SKILL.md'));
1270
+ const skillPath = getSkillPath(ctx, location, ctx.process.cwd());
1271
+ const alreadyInstalled = ctx.fs.existsSync(ctx.path.join(skillPath, 'SKILL.md'));
1582
1272
  if (alreadyInstalled) {
1583
- console.log(pc.yellow(`\nSkill already installed at: ${skillPath}`));
1584
- console.log(pc.dim('To reinstall, delete the directory first.\n'));
1273
+ ctx.logger.log(pc.yellow(`\nSkill already installed at: ${skillPath}`));
1274
+ ctx.logger.log(pc.dim('To reinstall, delete the directory first.\n'));
1585
1275
  return;
1586
1276
  }
1587
- console.log(pc.blue(`\nInstalling Claude skill to: ${skillPath}`));
1588
- writeSkillFiles(skillPath);
1589
- console.log(pc.green('\n✓ Skill installed successfully!\n'));
1277
+ ctx.logger.log(pc.blue(`\nInstalling Claude skill to: ${skillPath}`));
1278
+ writeSkillFiles(ctx, skillPath);
1279
+ ctx.logger.log(pc.green('\n✓ Skill installed successfully!\n'));
1590
1280
  if (location === 'global') {
1591
- console.log(pc.dim('The skill is now active for all projects using Claude Code.\n'));
1281
+ ctx.logger.log(pc.dim('The skill is now active for all projects using Claude Code.\n'));
1592
1282
  }
1593
1283
  else {
1594
- console.log(pc.dim('The skill is now bundled with this project.'));
1595
- console.log(pc.dim('Commit the .claude/ directory to share with your team.\n'));
1596
- console.log(pc.bold('Suggested:'));
1597
- console.log(` git add .claude/`);
1598
- console.log(` git commit -m "chore: add Hush Claude skill"\n`);
1284
+ ctx.logger.log(pc.dim('The skill is now bundled with this project.'));
1285
+ ctx.logger.log(pc.dim('Commit the .claude/ directory to share with your team.\n'));
1286
+ ctx.logger.log(pc.bold('Suggested:'));
1287
+ ctx.logger.log(` git add .claude/`);
1288
+ ctx.logger.log(` git commit -m "chore: add Hush Claude skill"\n`);
1599
1289
  }
1600
- console.log(pc.bold('What the skill does:'));
1601
- console.log(` • Teaches AI to use ${pc.cyan('hush inspect')} instead of reading .env files`);
1602
- console.log(` • Prevents accidental exposure of secrets to LLMs`);
1603
- console.log(` • Guides AI through adding/modifying secrets safely\n`);
1290
+ ctx.logger.log(pc.bold('What the skill does:'));
1291
+ ctx.logger.log(` • Teaches AI to use ${pc.cyan('hush inspect')} instead of reading .env files`);
1292
+ ctx.logger.log(` • Prevents accidental exposure of secrets to LLMs`);
1293
+ ctx.logger.log(` • Guides AI through adding/modifying secrets safely\n`);
1604
1294
  }