@chriscode/hush 2.1.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/LICENSE +21 -0
- package/dist/cli.js +64 -18
- 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.map +1 -1
- package/dist/commands/skill.js +295 -223
- 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/types.d.ts +13 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -0
- package/package.json +9 -10
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Chris Hasson
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/cli.js
CHANGED
|
@@ -3,6 +3,8 @@ import pc from 'picocolors';
|
|
|
3
3
|
import { decryptCommand } from './commands/decrypt.js';
|
|
4
4
|
import { encryptCommand } from './commands/encrypt.js';
|
|
5
5
|
import { editCommand } from './commands/edit.js';
|
|
6
|
+
import { setCommand } from './commands/set.js';
|
|
7
|
+
import { runCommand } from './commands/run.js';
|
|
6
8
|
import { statusCommand } from './commands/status.js';
|
|
7
9
|
import { pushCommand } from './commands/push.js';
|
|
8
10
|
import { initCommand } from './commands/init.js';
|
|
@@ -22,8 +24,9 @@ ${pc.bold('Usage:')}
|
|
|
22
24
|
${pc.bold('Commands:')}
|
|
23
25
|
init Initialize hush.yaml config
|
|
24
26
|
encrypt Encrypt source .env files
|
|
25
|
-
|
|
26
|
-
set
|
|
27
|
+
run -- <cmd> Run command with secrets in memory (AI-safe)
|
|
28
|
+
set <KEY> Set a single secret interactively (AI-safe)
|
|
29
|
+
edit [file] Edit all secrets in $EDITOR
|
|
27
30
|
list List all variables (shows values)
|
|
28
31
|
inspect List all variables (masked values, AI-safe)
|
|
29
32
|
has <key> Check if a secret exists (exit 0 if set, 1 if not)
|
|
@@ -31,10 +34,14 @@ ${pc.bold('Commands:')}
|
|
|
31
34
|
push Push secrets to Cloudflare Workers
|
|
32
35
|
status Show configuration and status
|
|
33
36
|
skill Install Claude Code / OpenCode skill
|
|
37
|
+
|
|
38
|
+
${pc.bold('Deprecated Commands:')}
|
|
39
|
+
decrypt Write secrets to disk (unsafe - use 'run' instead)
|
|
34
40
|
|
|
35
41
|
${pc.bold('Options:')}
|
|
36
42
|
-e, --env <env> Environment: development or production (default: development)
|
|
37
43
|
-r, --root <dir> Root directory (default: current directory)
|
|
44
|
+
-t, --target <t> Target name from hush.yaml (run only)
|
|
38
45
|
-q, --quiet Suppress output (has/check commands)
|
|
39
46
|
--dry-run Preview changes without applying (push only)
|
|
40
47
|
--warn Warn but exit 0 on drift (check only)
|
|
@@ -42,29 +49,29 @@ ${pc.bold('Options:')}
|
|
|
42
49
|
--only-changed Only check git-modified files (check only)
|
|
43
50
|
--require-source Fail if source file is missing (check only)
|
|
44
51
|
--global Install skill to ~/.claude/skills/ (skill only)
|
|
45
|
-
--local Install skill to ./.claude/skills/ (skill only)
|
|
52
|
+
--local Install skill to ./.claude/skills/ (skill/set only)
|
|
46
53
|
-h, --help Show this help message
|
|
47
54
|
-v, --version Show version number
|
|
48
55
|
|
|
49
56
|
${pc.bold('Examples:')}
|
|
50
57
|
hush init Initialize hush.yaml config
|
|
51
58
|
hush encrypt Encrypt .env files
|
|
52
|
-
hush
|
|
53
|
-
hush
|
|
54
|
-
hush
|
|
55
|
-
hush set
|
|
56
|
-
hush
|
|
59
|
+
hush run -- npm start Run with secrets in memory (AI-safe!)
|
|
60
|
+
hush run -e prod -- npm build Run with production secrets
|
|
61
|
+
hush run -t api -- wrangler dev Run filtered for 'api' target
|
|
62
|
+
hush set DATABASE_URL Set a secret interactively (AI-safe)
|
|
63
|
+
hush set API_KEY -e prod Set a production secret
|
|
64
|
+
hush set API_KEY --local Set a personal local override
|
|
65
|
+
hush edit Edit all shared secrets in $EDITOR
|
|
66
|
+
hush edit development Edit development secrets in $EDITOR
|
|
67
|
+
hush edit local Edit personal local overrides
|
|
57
68
|
hush inspect List all variables (masked, AI-safe)
|
|
58
69
|
hush has DATABASE_URL Check if DATABASE_URL is set
|
|
59
70
|
hush has API_KEY -q && echo "API_KEY is configured"
|
|
60
71
|
hush check Verify secrets are encrypted
|
|
61
|
-
hush check --warn Check but don't fail on drift
|
|
62
|
-
hush check --json Output JSON for CI
|
|
63
72
|
hush push --dry-run Preview push to Cloudflare
|
|
64
73
|
hush status Show current status
|
|
65
74
|
hush skill Install Claude skill (interactive)
|
|
66
|
-
hush skill --global Install skill for all projects
|
|
67
|
-
hush skill --local Install skill for this project only
|
|
68
75
|
`);
|
|
69
76
|
}
|
|
70
77
|
function parseEnvironment(value) {
|
|
@@ -75,7 +82,7 @@ function parseEnvironment(value) {
|
|
|
75
82
|
return null;
|
|
76
83
|
}
|
|
77
84
|
function parseFileKey(value) {
|
|
78
|
-
if (value === 'shared' || value === 'development' || value === 'production')
|
|
85
|
+
if (value === 'shared' || value === 'development' || value === 'production' || value === 'local')
|
|
79
86
|
return value;
|
|
80
87
|
if (value === 'dev')
|
|
81
88
|
return 'development';
|
|
@@ -86,6 +93,7 @@ function parseFileKey(value) {
|
|
|
86
93
|
function parseArgs(args) {
|
|
87
94
|
let command = '';
|
|
88
95
|
let env = 'development';
|
|
96
|
+
let envExplicit = false;
|
|
89
97
|
let root = process.cwd();
|
|
90
98
|
let dryRun = false;
|
|
91
99
|
let quiet = false;
|
|
@@ -97,6 +105,8 @@ function parseArgs(args) {
|
|
|
97
105
|
let local = false;
|
|
98
106
|
let file;
|
|
99
107
|
let key;
|
|
108
|
+
let target;
|
|
109
|
+
let cmdArgs = [];
|
|
100
110
|
for (let i = 0; i < args.length; i++) {
|
|
101
111
|
const arg = args[i];
|
|
102
112
|
if (arg === '-h' || arg === '--help') {
|
|
@@ -112,6 +122,7 @@ function parseArgs(args) {
|
|
|
112
122
|
const parsed = parseEnvironment(nextArg);
|
|
113
123
|
if (parsed) {
|
|
114
124
|
env = parsed;
|
|
125
|
+
envExplicit = true;
|
|
115
126
|
}
|
|
116
127
|
else {
|
|
117
128
|
console.error(pc.red(`Invalid environment: ${nextArg}`));
|
|
@@ -156,28 +167,40 @@ function parseArgs(args) {
|
|
|
156
167
|
local = true;
|
|
157
168
|
continue;
|
|
158
169
|
}
|
|
170
|
+
if (arg === '-t' || arg === '--target') {
|
|
171
|
+
target = args[++i];
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
if (arg === '--') {
|
|
175
|
+
cmdArgs = args.slice(i + 1);
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
159
178
|
if (!command && !arg.startsWith('-')) {
|
|
160
179
|
command = arg;
|
|
161
180
|
continue;
|
|
162
181
|
}
|
|
163
|
-
if (
|
|
182
|
+
if (command === 'edit' && !arg.startsWith('-')) {
|
|
164
183
|
const parsed = parseFileKey(arg);
|
|
165
184
|
if (parsed) {
|
|
166
185
|
file = parsed;
|
|
167
186
|
}
|
|
168
187
|
else {
|
|
169
188
|
console.error(pc.red(`Invalid file: ${arg}`));
|
|
170
|
-
console.error(pc.dim('Use: shared, development, or
|
|
189
|
+
console.error(pc.dim('Use: shared, development, production, or local'));
|
|
171
190
|
process.exit(1);
|
|
172
191
|
}
|
|
173
192
|
continue;
|
|
174
193
|
}
|
|
194
|
+
if (command === 'set' && !arg.startsWith('-') && !key) {
|
|
195
|
+
key = arg;
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
175
198
|
if (command === 'has' && !arg.startsWith('-') && !key) {
|
|
176
199
|
key = arg;
|
|
177
200
|
continue;
|
|
178
201
|
}
|
|
179
202
|
}
|
|
180
|
-
return { command, env, root, dryRun, quiet, warn, json, onlyChanged, requireSource, global, local, file, key };
|
|
203
|
+
return { command, env, envExplicit, root, dryRun, quiet, warn, json, onlyChanged, requireSource, global, local, file, key, target, cmdArgs };
|
|
181
204
|
}
|
|
182
205
|
async function main() {
|
|
183
206
|
const args = process.argv.slice(2);
|
|
@@ -185,7 +208,7 @@ async function main() {
|
|
|
185
208
|
printHelp();
|
|
186
209
|
process.exit(0);
|
|
187
210
|
}
|
|
188
|
-
const { command, env, root, dryRun, quiet, warn, json, onlyChanged, requireSource, global, local, file, key } = parseArgs(args);
|
|
211
|
+
const { command, env, envExplicit, root, dryRun, quiet, warn, json, onlyChanged, requireSource, global, local, file, key, target, cmdArgs } = parseArgs(args);
|
|
189
212
|
try {
|
|
190
213
|
switch (command) {
|
|
191
214
|
case 'init':
|
|
@@ -195,9 +218,32 @@ async function main() {
|
|
|
195
218
|
await encryptCommand({ root });
|
|
196
219
|
break;
|
|
197
220
|
case 'decrypt':
|
|
221
|
+
console.warn(pc.yellow('⚠️ Warning: "hush decrypt" is deprecated and writes unencrypted secrets to disk.'));
|
|
222
|
+
console.warn(pc.yellow(' Use "hush run -- <command>" instead for better security.'));
|
|
223
|
+
console.warn(pc.dim(' To suppress this warning, use "hush unsafe:decrypt"'));
|
|
224
|
+
console.warn('');
|
|
198
225
|
await decryptCommand({ root, env });
|
|
199
226
|
break;
|
|
200
|
-
case '
|
|
227
|
+
case 'unsafe:decrypt':
|
|
228
|
+
console.warn(pc.red('⚠️ UNSAFE MODE: Writing unencrypted secrets to disk.'));
|
|
229
|
+
console.warn(pc.red(' These files will be readable by AI assistants and other tools.'));
|
|
230
|
+
console.warn('');
|
|
231
|
+
await decryptCommand({ root, env });
|
|
232
|
+
break;
|
|
233
|
+
case 'run':
|
|
234
|
+
await runCommand({ root, env, target, command: cmdArgs });
|
|
235
|
+
break;
|
|
236
|
+
case 'set': {
|
|
237
|
+
let setFile = 'shared';
|
|
238
|
+
if (local) {
|
|
239
|
+
setFile = 'local';
|
|
240
|
+
}
|
|
241
|
+
else if (envExplicit) {
|
|
242
|
+
setFile = env;
|
|
243
|
+
}
|
|
244
|
+
await setCommand({ root, file: setFile, key });
|
|
245
|
+
break;
|
|
246
|
+
}
|
|
201
247
|
case 'edit':
|
|
202
248
|
await editCommand({ root, file });
|
|
203
249
|
break;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/commands/run.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,UAAU,EAAmC,MAAM,aAAa,CAAC;AAoC/E,wBAAsB,UAAU,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAkDnE"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import pc from 'picocolors';
|
|
5
|
+
import { loadConfig } from '../config/loader.js';
|
|
6
|
+
import { filterVarsForTarget } from '../core/filter.js';
|
|
7
|
+
import { interpolateVars, getUnresolvedVars } from '../core/interpolate.js';
|
|
8
|
+
import { mergeVars } from '../core/merge.js';
|
|
9
|
+
import { parseEnvContent } from '../core/parse.js';
|
|
10
|
+
import { decrypt as sopsDecrypt } from '../core/sops.js';
|
|
11
|
+
function getEncryptedPath(sourcePath) {
|
|
12
|
+
return sourcePath + '.encrypted';
|
|
13
|
+
}
|
|
14
|
+
function getDecryptedSecrets(root, env, config) {
|
|
15
|
+
const sharedEncrypted = join(root, getEncryptedPath(config.sources.shared));
|
|
16
|
+
const envEncrypted = join(root, getEncryptedPath(config.sources[env]));
|
|
17
|
+
const localEncrypted = join(root, getEncryptedPath(config.sources.local));
|
|
18
|
+
const varSources = [];
|
|
19
|
+
if (existsSync(sharedEncrypted)) {
|
|
20
|
+
const content = sopsDecrypt(sharedEncrypted);
|
|
21
|
+
varSources.push(parseEnvContent(content));
|
|
22
|
+
}
|
|
23
|
+
if (existsSync(envEncrypted)) {
|
|
24
|
+
const content = sopsDecrypt(envEncrypted);
|
|
25
|
+
varSources.push(parseEnvContent(content));
|
|
26
|
+
}
|
|
27
|
+
if (existsSync(localEncrypted)) {
|
|
28
|
+
const content = sopsDecrypt(localEncrypted);
|
|
29
|
+
varSources.push(parseEnvContent(content));
|
|
30
|
+
}
|
|
31
|
+
if (varSources.length === 0) {
|
|
32
|
+
throw new Error(`No encrypted files found. Expected: ${sharedEncrypted}`);
|
|
33
|
+
}
|
|
34
|
+
const merged = mergeVars(...varSources);
|
|
35
|
+
return interpolateVars(merged);
|
|
36
|
+
}
|
|
37
|
+
export async function runCommand(options) {
|
|
38
|
+
const { root, env, target, command } = options;
|
|
39
|
+
if (!command || command.length === 0) {
|
|
40
|
+
console.error(pc.red('Usage: hush run -- <command>'));
|
|
41
|
+
console.error(pc.dim('Example: hush run -- npm start'));
|
|
42
|
+
console.error(pc.dim(' hush run -e production -- npm run build'));
|
|
43
|
+
console.error(pc.dim(' hush run --target api -- wrangler dev'));
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
const config = loadConfig(root);
|
|
47
|
+
let vars = getDecryptedSecrets(root, env, config);
|
|
48
|
+
if (target) {
|
|
49
|
+
const targetConfig = config.targets.find(t => t.name === target);
|
|
50
|
+
if (!targetConfig) {
|
|
51
|
+
console.error(pc.red(`Target "${target}" not found in hush.yaml`));
|
|
52
|
+
console.error(pc.dim(`Available targets: ${config.targets.map(t => t.name).join(', ')}`));
|
|
53
|
+
process.exit(1);
|
|
54
|
+
}
|
|
55
|
+
vars = filterVarsForTarget(vars, targetConfig);
|
|
56
|
+
}
|
|
57
|
+
const unresolved = getUnresolvedVars(vars);
|
|
58
|
+
if (unresolved.length > 0) {
|
|
59
|
+
console.warn(pc.yellow(`Warning: ${unresolved.length} vars have unresolved references`));
|
|
60
|
+
}
|
|
61
|
+
const childEnv = {
|
|
62
|
+
...process.env,
|
|
63
|
+
...Object.fromEntries(vars.map(v => [v.key, v.value])),
|
|
64
|
+
};
|
|
65
|
+
const [cmd, ...args] = command;
|
|
66
|
+
const result = spawnSync(cmd, args, {
|
|
67
|
+
stdio: 'inherit',
|
|
68
|
+
env: childEnv,
|
|
69
|
+
shell: true,
|
|
70
|
+
cwd: root,
|
|
71
|
+
});
|
|
72
|
+
if (result.error) {
|
|
73
|
+
console.error(pc.red(`Failed to execute: ${result.error.message}`));
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
process.exit(result.status ?? 1);
|
|
77
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"set.d.ts","sourceRoot":"","sources":["../../src/commands/set.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAuD9C,wBAAsB,UAAU,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CA0CnE"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import pc from 'picocolors';
|
|
4
|
+
import { loadConfig } from '../config/loader.js';
|
|
5
|
+
import { setKey } from '../core/sops.js';
|
|
6
|
+
function promptForValue(key) {
|
|
7
|
+
return new Promise((resolve, reject) => {
|
|
8
|
+
if (!process.stdin.isTTY) {
|
|
9
|
+
reject(new Error('Interactive input requires a terminal (TTY)'));
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
process.stdout.write(`Enter value for ${pc.cyan(key)}: `);
|
|
13
|
+
const stdin = process.stdin;
|
|
14
|
+
stdin.setRawMode(true);
|
|
15
|
+
stdin.resume();
|
|
16
|
+
stdin.setEncoding('utf8');
|
|
17
|
+
let value = '';
|
|
18
|
+
const onData = (char) => {
|
|
19
|
+
switch (char) {
|
|
20
|
+
case '\n':
|
|
21
|
+
case '\r':
|
|
22
|
+
case '\u0004': // Ctrl+D
|
|
23
|
+
stdin.setRawMode(false);
|
|
24
|
+
stdin.pause();
|
|
25
|
+
stdin.removeListener('data', onData);
|
|
26
|
+
process.stdout.write('\n');
|
|
27
|
+
resolve(value);
|
|
28
|
+
break;
|
|
29
|
+
case '\u0003': // Ctrl+C
|
|
30
|
+
stdin.setRawMode(false);
|
|
31
|
+
stdin.pause();
|
|
32
|
+
stdin.removeListener('data', onData);
|
|
33
|
+
process.stdout.write('\n');
|
|
34
|
+
reject(new Error('Cancelled'));
|
|
35
|
+
break;
|
|
36
|
+
case '\u007F': // Backspace
|
|
37
|
+
case '\b':
|
|
38
|
+
if (value.length > 0) {
|
|
39
|
+
value = value.slice(0, -1);
|
|
40
|
+
process.stdout.write('\b \b');
|
|
41
|
+
}
|
|
42
|
+
break;
|
|
43
|
+
default:
|
|
44
|
+
value += char;
|
|
45
|
+
process.stdout.write('\u2022'); // Bullet character for hidden input
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
stdin.on('data', onData);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
export async function setCommand(options) {
|
|
52
|
+
const { root, file, key } = options;
|
|
53
|
+
const config = loadConfig(root);
|
|
54
|
+
const fileKey = file ?? 'shared';
|
|
55
|
+
const sourcePath = config.sources[fileKey];
|
|
56
|
+
const encryptedPath = join(root, sourcePath + '.encrypted');
|
|
57
|
+
if (!key) {
|
|
58
|
+
console.error(pc.red('Usage: hush set <KEY> [-e environment]'));
|
|
59
|
+
console.error(pc.dim('Example: hush set DATABASE_URL'));
|
|
60
|
+
console.error(pc.dim(' hush set API_KEY -e production'));
|
|
61
|
+
console.error(pc.dim('\nTo edit all secrets in an editor, use: hush edit'));
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
if (!existsSync(encryptedPath) && !existsSync(join(root, '.sops.yaml'))) {
|
|
65
|
+
console.error(pc.red('Hush is not initialized in this directory'));
|
|
66
|
+
console.error(pc.dim('Run "hush init" first, then "hush encrypt"'));
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
const value = await promptForValue(key);
|
|
71
|
+
if (!value) {
|
|
72
|
+
console.error(pc.yellow('No value entered, aborting'));
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
setKey(encryptedPath, key, value);
|
|
76
|
+
const envLabel = fileKey === 'shared' ? '' : ` in ${fileKey}`;
|
|
77
|
+
console.log(pc.green(`\n${key} set${envLabel} (${value.length} chars, encrypted)`));
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
const err = error;
|
|
81
|
+
if (err.message === 'Cancelled') {
|
|
82
|
+
console.log(pc.yellow('Cancelled'));
|
|
83
|
+
process.exit(1);
|
|
84
|
+
}
|
|
85
|
+
throw err;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"skill.d.ts","sourceRoot":"","sources":["../../src/commands/skill.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"skill.d.ts","sourceRoot":"","sources":["../../src/commands/skill.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAwhChD,wBAAsB,YAAY,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CA0CvE"}
|
package/dist/commands/skill.js
CHANGED
|
@@ -6,51 +6,81 @@ import pc from 'picocolors';
|
|
|
6
6
|
const SKILL_FILES = {
|
|
7
7
|
'SKILL.md': `---
|
|
8
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.
|
|
10
|
-
allowed-tools: Bash(hush:*), Bash(npx hush:*), Bash(brew:*), Bash(npm:*), Bash(pnpm:*), Bash(age-keygen:*), Read, Grep, Glob, Write
|
|
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
11
|
---
|
|
12
12
|
|
|
13
13
|
# Hush - AI-Native Secrets Management
|
|
14
14
|
|
|
15
|
-
Hush
|
|
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
16
|
|
|
17
|
-
##
|
|
17
|
+
## How It Works
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
-
|
|
24
|
-
- Write secrets directly to \`.env\` files
|
|
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
|
|
25
24
|
|
|
26
|
-
|
|
27
|
-
- \`npx hush inspect\` to see what variables exist (values are masked)
|
|
28
|
-
- \`npx hush has <KEY>\` to check if a specific variable is set
|
|
29
|
-
- \`npx hush set\` to add or modify secrets (opens secure editor)
|
|
30
|
-
- \`npx hush status\` to view configuration
|
|
25
|
+
## Safe to Read (Always Encrypted)
|
|
31
26
|
|
|
32
|
-
|
|
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
|
|
33
45
|
|
|
34
|
-
|
|
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?
|
|
35
50
|
|
|
36
51
|
\`\`\`bash
|
|
37
52
|
npx hush status
|
|
38
53
|
\`\`\`
|
|
39
54
|
|
|
40
|
-
**If this fails
|
|
55
|
+
**If this fails**, see [SETUP.md](SETUP.md) for first-time setup instructions.
|
|
41
56
|
|
|
42
57
|
---
|
|
43
58
|
|
|
44
|
-
##
|
|
59
|
+
## Running Programs with Secrets
|
|
45
60
|
|
|
46
|
-
|
|
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)
|
|
47
77
|
|
|
48
78
|
\`\`\`bash
|
|
49
79
|
npx hush inspect # Development
|
|
50
80
|
npx hush inspect -e production # Production
|
|
51
81
|
\`\`\`
|
|
52
82
|
|
|
53
|
-
Output shows **masked values
|
|
83
|
+
Output shows **masked values**:
|
|
54
84
|
|
|
55
85
|
\`\`\`
|
|
56
86
|
Secrets for development:
|
|
@@ -69,73 +99,66 @@ npx hush has DATABASE_URL # Verbose output
|
|
|
69
99
|
npx hush has API_KEY -q # Quiet: exit code only (0=set, 1=missing)
|
|
70
100
|
\`\`\`
|
|
71
101
|
|
|
72
|
-
###
|
|
102
|
+
### Read encrypted files directly
|
|
73
103
|
|
|
104
|
+
You can also just read the encrypted files:
|
|
74
105
|
\`\`\`bash
|
|
75
|
-
|
|
106
|
+
cat .env.encrypted # See encrypted content (safe!)
|
|
107
|
+
grep DATABASE .env.encrypted # Search for keys in encrypted file
|
|
76
108
|
\`\`\`
|
|
77
109
|
|
|
78
|
-
|
|
110
|
+
---
|
|
79
111
|
|
|
80
|
-
|
|
81
|
-
npx hush set # Set shared secrets
|
|
82
|
-
npx hush set development # Set dev secrets
|
|
83
|
-
npx hush set production # Set prod secrets
|
|
84
|
-
\`\`\`
|
|
112
|
+
## Adding/Modifying Secrets
|
|
85
113
|
|
|
86
|
-
|
|
114
|
+
### Add a single secret interactively
|
|
87
115
|
|
|
88
116
|
\`\`\`bash
|
|
89
|
-
npx hush
|
|
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
|
|
90
120
|
\`\`\`
|
|
91
121
|
|
|
92
|
-
|
|
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
|
|
93
126
|
|
|
94
127
|
\`\`\`bash
|
|
95
|
-
npx hush
|
|
96
|
-
npx hush
|
|
128
|
+
npx hush edit # Edit shared secrets
|
|
129
|
+
npx hush edit development # Edit development secrets
|
|
130
|
+
npx hush edit local # Edit personal overrides
|
|
97
131
|
\`\`\`
|
|
98
132
|
|
|
99
133
|
---
|
|
100
134
|
|
|
101
135
|
## Common Workflows
|
|
102
136
|
|
|
103
|
-
### "
|
|
104
|
-
\`\`\`bash
|
|
105
|
-
npx hush inspect
|
|
106
|
-
\`\`\`
|
|
107
|
-
|
|
108
|
-
### "Is DATABASE_URL set?"
|
|
137
|
+
### "Help user add DATABASE_URL"
|
|
109
138
|
\`\`\`bash
|
|
110
|
-
npx hush
|
|
139
|
+
npx hush set DATABASE_URL
|
|
111
140
|
\`\`\`
|
|
112
|
-
|
|
113
|
-
### "Help user add a new secret"
|
|
114
|
-
1. Tell user to run: \`npx hush set\`
|
|
115
|
-
2. They add the variable in their editor
|
|
116
|
-
3. They save and close
|
|
117
|
-
4. Tell them to run: \`npx hush encrypt\`
|
|
118
|
-
5. Verify: \`npx hush inspect\`
|
|
141
|
+
Tell user: "Enter your database URL when prompted"
|
|
119
142
|
|
|
120
143
|
### "Check all required secrets"
|
|
121
144
|
\`\`\`bash
|
|
122
145
|
npx hush has DATABASE_URL -q && npx hush has API_KEY -q && echo "All configured" || echo "Some missing"
|
|
123
146
|
\`\`\`
|
|
124
147
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
These contain plaintext secrets - NEVER read them:
|
|
130
|
-
- \`.env\`, \`.env.local\`, \`.env.development\`, \`.env.production\`
|
|
131
|
-
- \`.dev.vars\`
|
|
132
|
-
- Any \`*/.env\` or \`*/.env.*\` files
|
|
148
|
+
### "Run the development server"
|
|
149
|
+
\`\`\`bash
|
|
150
|
+
npx hush run -- npm run dev
|
|
151
|
+
\`\`\`
|
|
133
152
|
|
|
134
|
-
|
|
153
|
+
### "Build for production"
|
|
154
|
+
\`\`\`bash
|
|
155
|
+
npx hush run -e production -- npm run build
|
|
156
|
+
\`\`\`
|
|
135
157
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
158
|
+
### "See what's in the encrypted file"
|
|
159
|
+
\`\`\`bash
|
|
160
|
+
cat .env.encrypted # Safe! Shows encrypted data only
|
|
161
|
+
\`\`\`
|
|
139
162
|
|
|
140
163
|
---
|
|
141
164
|
|
|
@@ -351,8 +374,8 @@ When a new team member joins:
|
|
|
351
374
|
|
|
352
375
|
1. **Get the age private key** from an existing team member
|
|
353
376
|
2. **Save it** to \`~/.config/sops/age/key.txt\`
|
|
354
|
-
3. **Run** \`npx hush
|
|
355
|
-
4. **Start developing**
|
|
377
|
+
3. **Run** \`npx hush run -- npm install\` to verify decryption works
|
|
378
|
+
4. **Start developing** with \`npx hush run -- npm run dev\`
|
|
356
379
|
|
|
357
380
|
The private key should be shared securely (password manager, encrypted channel, etc.)
|
|
358
381
|
|
|
@@ -364,7 +387,7 @@ After setup, verify everything works:
|
|
|
364
387
|
|
|
365
388
|
- [ ] \`npx hush status\` shows configuration
|
|
366
389
|
- [ ] \`npx hush inspect\` shows masked variables
|
|
367
|
-
- [ ] \`npx hush
|
|
390
|
+
- [ ] \`npx hush run -- env\` can decrypt and run (secrets stay in memory!)
|
|
368
391
|
- [ ] \`.env.encrypted\` files are committed to git
|
|
369
392
|
- [ ] Plaintext \`.env\` files are in \`.gitignore\`
|
|
370
393
|
|
|
@@ -395,91 +418,166 @@ Edit \`hush.yaml\` and add your source files under \`sources:\`.
|
|
|
395
418
|
|
|
396
419
|
Complete reference for all Hush CLI commands with flags, options, and examples.
|
|
397
420
|
|
|
398
|
-
##
|
|
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.
|
|
399
424
|
|
|
400
|
-
|
|
425
|
+
## Global Options
|
|
401
426
|
|
|
402
427
|
| Option | Description |
|
|
403
428
|
|--------|-------------|
|
|
404
|
-
| \`-e, --env <env>\` | Environment: \`development\`
|
|
429
|
+
| \`-e, --env <env>\` | Environment: \`development\` / \`production\`. Default: \`development\` |
|
|
405
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) |
|
|
406
433
|
| \`-h, --help\` | Show help message |
|
|
407
434
|
| \`-v, --version\` | Show version number |
|
|
408
435
|
|
|
409
|
-
|
|
436
|
+
---
|
|
410
437
|
|
|
411
|
-
|
|
438
|
+
## Primary Commands
|
|
412
439
|
|
|
413
|
-
|
|
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.
|
|
414
446
|
|
|
415
447
|
\`\`\`bash
|
|
416
|
-
hush
|
|
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
|
|
417
451
|
\`\`\`
|
|
418
452
|
|
|
419
|
-
|
|
453
|
+
**Options:**
|
|
454
|
+
| Option | Description |
|
|
455
|
+
|--------|-------------|
|
|
456
|
+
| \`-e, --env\` | Environment (development/production) |
|
|
457
|
+
| \`-t, --target\` | Filter secrets for a specific target from hush.yaml |
|
|
420
458
|
|
|
421
459
|
---
|
|
422
460
|
|
|
423
|
-
### hush
|
|
461
|
+
### hush set <KEY> ⭐
|
|
424
462
|
|
|
425
|
-
|
|
463
|
+
Add or update a single secret interactively. You invoke this, user enters the value.
|
|
426
464
|
|
|
427
465
|
\`\`\`bash
|
|
428
|
-
hush
|
|
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
|
|
429
469
|
\`\`\`
|
|
430
470
|
|
|
431
|
-
|
|
432
|
-
- \`.env\` -> \`.env.encrypted\`
|
|
433
|
-
- \`.env.development\` -> \`.env.development.encrypted\`
|
|
434
|
-
- \`.env.production\` -> \`.env.production.encrypted\`
|
|
471
|
+
User will be prompted with hidden input - the value is never visible.
|
|
435
472
|
|
|
436
473
|
---
|
|
437
474
|
|
|
438
|
-
### hush
|
|
475
|
+
### hush edit [file]
|
|
439
476
|
|
|
440
|
-
|
|
477
|
+
Open all secrets in \`$EDITOR\` for bulk editing.
|
|
441
478
|
|
|
442
479
|
\`\`\`bash
|
|
443
|
-
hush
|
|
444
|
-
hush
|
|
445
|
-
hush
|
|
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
|
|
446
484
|
\`\`\`
|
|
447
485
|
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
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
|
+
\`\`\`
|
|
454
496
|
|
|
455
497
|
---
|
|
456
498
|
|
|
457
|
-
### hush
|
|
499
|
+
### hush has <KEY>
|
|
458
500
|
|
|
459
|
-
|
|
501
|
+
Check if a specific secret exists.
|
|
460
502
|
|
|
461
503
|
\`\`\`bash
|
|
462
|
-
hush
|
|
463
|
-
hush
|
|
464
|
-
hush set production # Set production secrets
|
|
504
|
+
hush has DATABASE_URL # Verbose output
|
|
505
|
+
hush has API_KEY -q # Quiet: exit code only (0=set, 1=missing)
|
|
465
506
|
\`\`\`
|
|
466
507
|
|
|
467
|
-
|
|
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.
|
|
468
547
|
|
|
469
|
-
|
|
548
|
+
\`\`\`bash
|
|
549
|
+
hush push # Push secrets
|
|
550
|
+
hush push --dry-run # Preview without pushing
|
|
551
|
+
\`\`\`
|
|
470
552
|
|
|
471
553
|
---
|
|
472
554
|
|
|
473
|
-
|
|
555
|
+
## Deprecated Commands (Avoid)
|
|
556
|
+
|
|
557
|
+
### hush decrypt / hush unsafe:decrypt ⚠️
|
|
474
558
|
|
|
475
|
-
|
|
559
|
+
**DEPRECATED:** Writes unencrypted secrets to disk, defeating the "encrypted at rest" model.
|
|
476
560
|
|
|
477
561
|
\`\`\`bash
|
|
478
|
-
hush
|
|
479
|
-
hush
|
|
562
|
+
hush decrypt # Writes plaintext .env files (avoid!)
|
|
563
|
+
hush unsafe:decrypt # Same, explicit unsafe mode
|
|
480
564
|
\`\`\`
|
|
481
565
|
|
|
482
|
-
|
|
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!) |
|
|
483
581
|
|
|
484
582
|
---
|
|
485
583
|
|
|
@@ -696,162 +794,136 @@ targets:
|
|
|
696
794
|
`,
|
|
697
795
|
'examples/workflows.md': `# Hush Workflow Examples
|
|
698
796
|
|
|
699
|
-
Step-by-step examples for common
|
|
797
|
+
Step-by-step examples for common workflows when working with secrets.
|
|
700
798
|
|
|
701
|
-
|
|
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.
|
|
702
800
|
|
|
703
|
-
|
|
801
|
+
## Running Programs (Most Common)
|
|
802
|
+
|
|
803
|
+
### "Start the development server"
|
|
704
804
|
|
|
705
805
|
\`\`\`bash
|
|
706
|
-
hush
|
|
806
|
+
hush run -- npm run dev
|
|
707
807
|
\`\`\`
|
|
708
808
|
|
|
709
|
-
|
|
809
|
+
### "Build for production"
|
|
710
810
|
|
|
711
|
-
|
|
811
|
+
\`\`\`bash
|
|
812
|
+
hush run -e production -- npm run build
|
|
813
|
+
\`\`\`
|
|
814
|
+
|
|
815
|
+
### "Run tests with secrets"
|
|
712
816
|
|
|
713
817
|
\`\`\`bash
|
|
714
|
-
hush
|
|
818
|
+
hush run -- npm test
|
|
715
819
|
\`\`\`
|
|
716
820
|
|
|
717
|
-
|
|
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
|
+
\`\`\`
|
|
718
838
|
|
|
719
|
-
### "
|
|
839
|
+
### "Is the database configured?"
|
|
720
840
|
|
|
721
841
|
\`\`\`bash
|
|
722
|
-
|
|
723
|
-
hush has DATABASE_URL -q || echo "Missing: DATABASE_URL"
|
|
724
|
-
hush has API_KEY -q || echo "Missing: API_KEY"
|
|
725
|
-
hush has STRIPE_SECRET_KEY -q || echo "Missing: STRIPE_SECRET_KEY"
|
|
842
|
+
hush has DATABASE_URL
|
|
726
843
|
\`\`\`
|
|
727
844
|
|
|
728
|
-
|
|
845
|
+
If "not found", help user add it with \`hush set DATABASE_URL\`.
|
|
846
|
+
|
|
847
|
+
### "Check all required secrets"
|
|
848
|
+
|
|
729
849
|
\`\`\`bash
|
|
730
850
|
hush has DATABASE_URL -q && \\
|
|
731
851
|
hush has API_KEY -q && \\
|
|
732
|
-
|
|
733
|
-
echo "
|
|
734
|
-
|
|
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
|
|
735
860
|
\`\`\`
|
|
736
861
|
|
|
737
862
|
---
|
|
738
863
|
|
|
739
|
-
##
|
|
864
|
+
## Adding Secrets
|
|
740
865
|
|
|
741
|
-
### "Help me add
|
|
866
|
+
### "Help me add DATABASE_URL"
|
|
742
867
|
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
\`\`\`
|
|
868
|
+
\`\`\`bash
|
|
869
|
+
hush set DATABASE_URL
|
|
870
|
+
\`\`\`
|
|
747
871
|
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
> hush set
|
|
752
|
-
> \`\`\`
|
|
753
|
-
> Add a line like: \`NEW_API_KEY=your_actual_key_here\`
|
|
754
|
-
> Save and close the editor, then run:
|
|
755
|
-
> \`\`\`bash
|
|
756
|
-
> hush encrypt
|
|
757
|
-
> \`\`\`
|
|
758
|
-
|
|
759
|
-
3. **Verify it was added:**
|
|
760
|
-
\`\`\`bash
|
|
761
|
-
hush has NEW_API_KEY
|
|
762
|
-
\`\`\`
|
|
872
|
+
Tell user: "Enter your database URL when prompted (input will be hidden)"
|
|
873
|
+
|
|
874
|
+
### "Add a production-only secret"
|
|
763
875
|
|
|
764
|
-
|
|
876
|
+
\`\`\`bash
|
|
877
|
+
hush set STRIPE_SECRET_KEY -e production
|
|
878
|
+
\`\`\`
|
|
765
879
|
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
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."
|
|
770
893
|
|
|
771
894
|
---
|
|
772
895
|
|
|
773
|
-
## Debugging
|
|
896
|
+
## Debugging
|
|
774
897
|
|
|
775
898
|
### "My app can't find DATABASE_URL"
|
|
776
899
|
|
|
777
|
-
1.
|
|
900
|
+
1. Check if it exists:
|
|
778
901
|
\`\`\`bash
|
|
779
902
|
hush has DATABASE_URL
|
|
780
903
|
\`\`\`
|
|
781
904
|
|
|
782
|
-
2.
|
|
905
|
+
2. Check target distribution:
|
|
783
906
|
\`\`\`bash
|
|
784
907
|
hush inspect
|
|
785
908
|
\`\`\`
|
|
786
|
-
Look at the "Target distribution" section to see which targets receive it.
|
|
787
909
|
|
|
788
|
-
3.
|
|
910
|
+
3. Check hush.yaml for filtering:
|
|
789
911
|
\`\`\`bash
|
|
790
912
|
cat hush.yaml
|
|
791
913
|
\`\`\`
|
|
792
|
-
Look for \`include\`/\`exclude\` patterns that might filter the variable.
|
|
793
914
|
|
|
794
|
-
4.
|
|
915
|
+
4. Look at the encrypted file:
|
|
795
916
|
\`\`\`bash
|
|
796
|
-
|
|
917
|
+
grep DATABASE .env.encrypted # Safe to read!
|
|
797
918
|
\`\`\`
|
|
798
919
|
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
1. **Check target configuration:**
|
|
920
|
+
5. Try running directly:
|
|
802
921
|
\`\`\`bash
|
|
803
|
-
hush
|
|
804
|
-
\`\`\`
|
|
805
|
-
Verify the API target path and format are correct.
|
|
806
|
-
|
|
807
|
-
2. **Check filters:**
|
|
808
|
-
\`\`\`bash
|
|
809
|
-
cat hush.yaml
|
|
810
|
-
\`\`\`
|
|
811
|
-
If there's an \`exclude: EXPO_PUBLIC_*\` pattern, that's intentional.
|
|
812
|
-
If there's an \`include\` pattern, only matching variables are sent.
|
|
813
|
-
|
|
814
|
-
3. **Run inspect to see distribution:**
|
|
815
|
-
\`\`\`bash
|
|
816
|
-
hush inspect
|
|
922
|
+
hush run -- env | grep DATABASE
|
|
817
923
|
\`\`\`
|
|
818
924
|
|
|
819
925
|
---
|
|
820
926
|
|
|
821
|
-
## Deployment Workflows
|
|
822
|
-
|
|
823
|
-
### "Deploy to production"
|
|
824
|
-
|
|
825
|
-
\`\`\`bash
|
|
826
|
-
# Decrypt production secrets to all targets
|
|
827
|
-
hush decrypt -e production
|
|
828
|
-
\`\`\`
|
|
829
|
-
|
|
830
|
-
### "Push secrets to Cloudflare Workers"
|
|
831
|
-
|
|
832
|
-
\`\`\`bash
|
|
833
|
-
# Preview what would be pushed
|
|
834
|
-
hush push --dry-run
|
|
835
|
-
|
|
836
|
-
# Actually push (requires wrangler auth)
|
|
837
|
-
hush push
|
|
838
|
-
\`\`\`
|
|
839
|
-
|
|
840
|
-
### "Verify before deploying"
|
|
841
|
-
|
|
842
|
-
\`\`\`bash
|
|
843
|
-
# Check all encrypted files are up to date
|
|
844
|
-
hush check
|
|
845
|
-
|
|
846
|
-
# If drift detected, encrypt first
|
|
847
|
-
hush encrypt
|
|
848
|
-
|
|
849
|
-
# Then decrypt for production
|
|
850
|
-
hush decrypt -e production
|
|
851
|
-
\`\`\`
|
|
852
|
-
|
|
853
|
-
---
|
|
854
|
-
|
|
855
927
|
## Team Workflows
|
|
856
928
|
|
|
857
929
|
### "New team member setup"
|
|
@@ -859,30 +931,32 @@ hush decrypt -e production
|
|
|
859
931
|
Guide them:
|
|
860
932
|
> 1. Get the age private key from a team member
|
|
861
933
|
> 2. Save it to \`~/.config/sops/age/key.txt\`
|
|
862
|
-
> 3. Run \`hush
|
|
863
|
-
> 4. Start developing
|
|
934
|
+
> 3. Run \`hush run -- npm install\` to verify setup
|
|
935
|
+
> 4. Start developing with \`hush run -- npm run dev\`
|
|
864
936
|
|
|
865
|
-
### "Someone added new secrets
|
|
937
|
+
### "Someone added new secrets"
|
|
866
938
|
|
|
867
939
|
\`\`\`bash
|
|
868
|
-
# Pull latest changes
|
|
869
940
|
git pull
|
|
870
|
-
|
|
871
|
-
# Regenerate env files
|
|
872
|
-
hush decrypt
|
|
941
|
+
hush inspect # See what's new
|
|
873
942
|
\`\`\`
|
|
874
943
|
|
|
875
|
-
|
|
944
|
+
---
|
|
945
|
+
|
|
946
|
+
## Deployment
|
|
947
|
+
|
|
948
|
+
### "Push to Cloudflare Workers"
|
|
876
949
|
|
|
877
950
|
\`\`\`bash
|
|
878
|
-
hush
|
|
951
|
+
hush push --dry-run # Preview first
|
|
952
|
+
hush push # Actually push
|
|
879
953
|
\`\`\`
|
|
880
954
|
|
|
881
|
-
|
|
955
|
+
### "Build and deploy"
|
|
956
|
+
|
|
882
957
|
\`\`\`bash
|
|
883
|
-
hush
|
|
884
|
-
|
|
885
|
-
git commit -m "chore: encrypt new secrets"
|
|
958
|
+
hush run -e production -- npm run build
|
|
959
|
+
hush push
|
|
886
960
|
\`\`\`
|
|
887
961
|
|
|
888
962
|
---
|
|
@@ -916,17 +990,15 @@ Target distribution:
|
|
|
916
990
|
- The \`app\` folder only gets \`EXPO_PUBLIC_*\` variables
|
|
917
991
|
- The \`api\` folder gets everything except \`EXPO_PUBLIC_*\`
|
|
918
992
|
|
|
919
|
-
###
|
|
993
|
+
### Reading encrypted files directly
|
|
920
994
|
|
|
921
995
|
\`\`\`bash
|
|
922
|
-
$
|
|
923
|
-
DATABASE_URL
|
|
924
|
-
|
|
925
|
-
$ hush has MISSING_VAR
|
|
926
|
-
MISSING_VAR not found
|
|
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]
|
|
927
999
|
\`\`\`
|
|
928
1000
|
|
|
929
|
-
|
|
1001
|
+
This is safe to view—the actual values are encrypted. You can see what keys exist without exposing secrets.
|
|
930
1002
|
`,
|
|
931
1003
|
};
|
|
932
1004
|
function getSkillPath(location, root) {
|
package/dist/core/sops.d.ts
CHANGED
|
@@ -3,4 +3,9 @@ export declare function isAgeKeyConfigured(): boolean;
|
|
|
3
3
|
export declare function decrypt(filePath: string): string;
|
|
4
4
|
export declare function encrypt(inputPath: string, outputPath: string): void;
|
|
5
5
|
export declare function edit(filePath: string): void;
|
|
6
|
+
/**
|
|
7
|
+
* Set a single key in an encrypted file.
|
|
8
|
+
* Decrypts to memory, updates the key, re-encrypts.
|
|
9
|
+
*/
|
|
10
|
+
export declare function setKey(filePath: string, key: string, value: string): void;
|
|
6
11
|
//# sourceMappingURL=sops.d.ts.map
|
package/dist/core/sops.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sops.d.ts","sourceRoot":"","sources":["../../src/core/sops.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sops.d.ts","sourceRoot":"","sources":["../../src/core/sops.ts"],"names":[],"mappings":"AA0BA,wBAAgB,eAAe,IAAI,OAAO,CAOzC;AAED,wBAAgB,kBAAkB,IAAI,OAAO,CAE5C;AAED,wBAAgB,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CA6BhD;AAED,wBAAgB,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAuBnE;AAED,wBAAgB,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAqB3C;AAED;;;GAGG;AACH,wBAAgB,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAqDzE"}
|
package/dist/core/sops.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { execSync, spawnSync } from 'node:child_process';
|
|
2
|
-
import { existsSync } from 'node:fs';
|
|
2
|
+
import { existsSync, writeFileSync, unlinkSync } from 'node:fs';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
4
5
|
function getAgeKeyFile() {
|
|
5
6
|
if (process.env.SOPS_AGE_KEY_FILE) {
|
|
6
7
|
return process.env.SOPS_AGE_KEY_FILE;
|
|
@@ -89,3 +90,50 @@ export function edit(filePath) {
|
|
|
89
90
|
throw new Error(`SOPS edit failed with exit code ${result.status}`);
|
|
90
91
|
}
|
|
91
92
|
}
|
|
93
|
+
/**
|
|
94
|
+
* Set a single key in an encrypted file.
|
|
95
|
+
* Decrypts to memory, updates the key, re-encrypts.
|
|
96
|
+
*/
|
|
97
|
+
export function setKey(filePath, key, value) {
|
|
98
|
+
if (!isSopsInstalled()) {
|
|
99
|
+
throw new Error('SOPS is not installed. Install with: brew install sops');
|
|
100
|
+
}
|
|
101
|
+
let content = '';
|
|
102
|
+
// If file exists, decrypt it first
|
|
103
|
+
if (existsSync(filePath)) {
|
|
104
|
+
content = decrypt(filePath);
|
|
105
|
+
}
|
|
106
|
+
// Parse existing content into lines
|
|
107
|
+
const lines = content.split('\n').filter(line => line.trim() !== '');
|
|
108
|
+
// Find and update or add the key
|
|
109
|
+
let found = false;
|
|
110
|
+
const updatedLines = lines.map(line => {
|
|
111
|
+
const match = line.match(/^([^=]+)=/);
|
|
112
|
+
if (match && match[1] === key) {
|
|
113
|
+
found = true;
|
|
114
|
+
return `${key}=${value}`;
|
|
115
|
+
}
|
|
116
|
+
return line;
|
|
117
|
+
});
|
|
118
|
+
if (!found) {
|
|
119
|
+
updatedLines.push(`${key}=${value}`);
|
|
120
|
+
}
|
|
121
|
+
const newContent = updatedLines.join('\n') + '\n';
|
|
122
|
+
const tempFile = join(tmpdir(), `hush-temp-${Date.now()}.env`);
|
|
123
|
+
try {
|
|
124
|
+
writeFileSync(tempFile, newContent, 'utf-8');
|
|
125
|
+
// Encrypt temp file to the target
|
|
126
|
+
execSync(`sops --input-type dotenv --output-type dotenv --encrypt "${tempFile}" > "${filePath}"`, {
|
|
127
|
+
encoding: 'utf-8',
|
|
128
|
+
shell: '/bin/bash',
|
|
129
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
130
|
+
env: getSopsEnv(),
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
finally {
|
|
134
|
+
// Always clean up temp file
|
|
135
|
+
if (existsSync(tempFile)) {
|
|
136
|
+
unlinkSync(tempFile);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
package/dist/types.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ export interface SourceFiles {
|
|
|
11
11
|
shared: string;
|
|
12
12
|
development: string;
|
|
13
13
|
production: string;
|
|
14
|
+
local: string;
|
|
14
15
|
}
|
|
15
16
|
export interface HushConfig {
|
|
16
17
|
sources: SourceFiles;
|
|
@@ -29,7 +30,18 @@ export interface EncryptOptions {
|
|
|
29
30
|
}
|
|
30
31
|
export interface EditOptions {
|
|
31
32
|
root: string;
|
|
32
|
-
file?: 'shared' | 'development' | 'production';
|
|
33
|
+
file?: 'shared' | 'development' | 'production' | 'local';
|
|
34
|
+
}
|
|
35
|
+
export interface SetOptions {
|
|
36
|
+
root: string;
|
|
37
|
+
file?: 'shared' | 'development' | 'production' | 'local';
|
|
38
|
+
key?: string;
|
|
39
|
+
}
|
|
40
|
+
export interface RunOptions {
|
|
41
|
+
root: string;
|
|
42
|
+
env: Environment;
|
|
43
|
+
target?: string;
|
|
44
|
+
command: string[];
|
|
33
45
|
}
|
|
34
46
|
export interface PushOptions {
|
|
35
47
|
root: string;
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,UAAU,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;AAC7E,MAAM,MAAM,WAAW,GAAG,aAAa,GAAG,YAAY,CAAC;AAEvD,MAAM,WAAW,MAAM;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,YAAY,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chriscode/hush",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "SOPS-based secrets management for monorepos. Encrypt once, decrypt everywhere.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -12,14 +12,6 @@
|
|
|
12
12
|
"types": "./dist/index.d.ts"
|
|
13
13
|
}
|
|
14
14
|
},
|
|
15
|
-
"scripts": {
|
|
16
|
-
"build": "tsc",
|
|
17
|
-
"dev": "tsc --watch",
|
|
18
|
-
"test": "vitest run",
|
|
19
|
-
"test:watch": "vitest",
|
|
20
|
-
"prepublishOnly": "pnpm build && pnpm test",
|
|
21
|
-
"type-check": "tsc --noEmit"
|
|
22
|
-
},
|
|
23
15
|
"keywords": [
|
|
24
16
|
"secrets",
|
|
25
17
|
"sops",
|
|
@@ -61,5 +53,12 @@
|
|
|
61
53
|
],
|
|
62
54
|
"publishConfig": {
|
|
63
55
|
"access": "public"
|
|
56
|
+
},
|
|
57
|
+
"scripts": {
|
|
58
|
+
"build": "tsc",
|
|
59
|
+
"dev": "tsc --watch",
|
|
60
|
+
"test": "vitest run",
|
|
61
|
+
"test:watch": "vitest",
|
|
62
|
+
"type-check": "tsc --noEmit"
|
|
64
63
|
}
|
|
65
|
-
}
|
|
64
|
+
}
|