vaulter-cli 2.3.0 → 2.3.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.
- package/README.md +47 -0
- package/package.json +1 -1
- package/src/commands/help.js +1 -0
- package/src/commands/view.js +112 -0
- package/src/index.js +13 -1
package/README.md
CHANGED
|
@@ -30,11 +30,18 @@ vaulter add my-openai-key
|
|
|
30
30
|
# Remove a key
|
|
31
31
|
vaulter remove my-openai-key
|
|
32
32
|
|
|
33
|
+
# View decrypted values in your terminal
|
|
34
|
+
vaulter view STRIPE_SECRET
|
|
35
|
+
vaulter view KEY1 KEY2
|
|
36
|
+
|
|
33
37
|
# Generate a .env file from your vault
|
|
34
38
|
vaulter make .env
|
|
35
39
|
|
|
36
40
|
# Upload a local .env file to your vault
|
|
37
41
|
vaulter save .env
|
|
42
|
+
|
|
43
|
+
# Initialize current directory as a Vaulter project
|
|
44
|
+
vaulter init
|
|
38
45
|
```
|
|
39
46
|
|
|
40
47
|
## Commands
|
|
@@ -46,11 +53,32 @@ vaulter save .env
|
|
|
46
53
|
| `vaulter ls` | List all API keys in your vault |
|
|
47
54
|
| `vaulter add <name>` | Add a new API key to your vault |
|
|
48
55
|
| `vaulter remove <name-or-id>` | Remove an API key from your vault |
|
|
56
|
+
| `vaulter view [key_names...]` | Decrypt and display one or more API keys in your terminal |
|
|
49
57
|
| `vaulter make [file]` | Generate a .env file from your vault keys |
|
|
50
58
|
| `vaulter save [file]` | Upload a local .env file to your vault |
|
|
59
|
+
| `vaulter init` | Initialize current directory as a Vaulter project |
|
|
51
60
|
| `vaulter web-app` | Open the Vaulter web app in your browser |
|
|
52
61
|
| `vaulter help` | Show all available commands |
|
|
53
62
|
|
|
63
|
+
## Viewing Keys
|
|
64
|
+
|
|
65
|
+
### `vaulter view [key_names...]`
|
|
66
|
+
|
|
67
|
+
Decrypt and print key values directly to your terminal — without writing them to a file.
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
# Interactive: select which keys to view via checkbox
|
|
71
|
+
vaulter view
|
|
72
|
+
|
|
73
|
+
# View a specific key by name
|
|
74
|
+
vaulter view STRIPE_SECRET
|
|
75
|
+
|
|
76
|
+
# View multiple keys at once
|
|
77
|
+
vaulter view STRIPE_SECRET OPENAI_API_KEY
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Key names are matched case-insensitively. If a name doesn't match any key in your vault, a warning is printed and the rest continue. A security reminder is shown below the output table — clear your terminal when done.
|
|
81
|
+
|
|
54
82
|
## .env Support
|
|
55
83
|
|
|
56
84
|
### `vaulter make [file]`
|
|
@@ -80,6 +108,25 @@ vaulter save
|
|
|
80
108
|
vaulter save .env.production
|
|
81
109
|
```
|
|
82
110
|
|
|
111
|
+
## Project Initialization
|
|
112
|
+
|
|
113
|
+
### `vaulter init`
|
|
114
|
+
|
|
115
|
+
Initialize the current directory as a Vaulter project. This creates a `.vaulter/config.json` file that associates the directory with a named project, and automatically adds `.vaulter/` to your `.gitignore` if one exists.
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
# Interactive setup (prompts for project name)
|
|
119
|
+
vaulter init
|
|
120
|
+
|
|
121
|
+
# Non-interactive, use directory name as project name
|
|
122
|
+
vaulter init --yes
|
|
123
|
+
|
|
124
|
+
# Specify a project name directly
|
|
125
|
+
vaulter init --name my-project
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
After initialization, if no `.env` is found and you're signed in, you'll be offered the option to generate one from your vault immediately.
|
|
129
|
+
|
|
83
130
|
## Authentication
|
|
84
131
|
|
|
85
132
|
Vaulter uses browser-based device auth. Running `vaulter sign-in` opens your browser where you log in, and the CLI receives a token automatically. Credentials are stored locally at `~/.vaulter/credentials.json` with restricted file permissions.
|
package/package.json
CHANGED
package/src/commands/help.js
CHANGED
|
@@ -22,6 +22,7 @@ export async function showHelp() {
|
|
|
22
22
|
{ name: 'ls', desc: 'List all API keys in your vault' },
|
|
23
23
|
{ name: 'add <name>', desc: 'Add a new API key to your vault' },
|
|
24
24
|
{ name: 'remove <name-or-id>', desc: 'Remove an API key from your vault' },
|
|
25
|
+
{ name: 'view [key_names...]', desc: 'Decrypt and display one or more API keys in your terminal' },
|
|
25
26
|
{ name: 'make [file]', desc: 'Generate a .env file from your vault keys' },
|
|
26
27
|
{ name: 'save [file]', desc: 'Upload a local .env file to your vault' },
|
|
27
28
|
{ name: 'web-app', desc: 'Open the Vaulter web app in your browser' },
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import inquirer from 'inquirer';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import Table from 'cli-table3';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import { apiFetch } from '../lib/api.js';
|
|
6
|
+
import { purple, dim, error, warn } from '../lib/ui.js';
|
|
7
|
+
|
|
8
|
+
export async function viewKeys(names) {
|
|
9
|
+
const spinner = ora({ text: 'Fetching vault keys...', color: 'magenta' }).start();
|
|
10
|
+
|
|
11
|
+
let keys;
|
|
12
|
+
try {
|
|
13
|
+
const data = await apiFetch('/api/keys');
|
|
14
|
+
keys = data.keys || [];
|
|
15
|
+
} catch (err) {
|
|
16
|
+
spinner.fail('Failed to fetch keys');
|
|
17
|
+
error(err.message);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
spinner.stop();
|
|
22
|
+
|
|
23
|
+
if (keys.length === 0) {
|
|
24
|
+
console.log('');
|
|
25
|
+
warn('No keys in your vault. Run `vaulter add <name>` to add one.');
|
|
26
|
+
console.log('');
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
let selected;
|
|
31
|
+
|
|
32
|
+
if (names.length === 0) {
|
|
33
|
+
// Interactive checkbox selection
|
|
34
|
+
console.log('');
|
|
35
|
+
const { selectedKeys } = await inquirer.prompt([
|
|
36
|
+
{
|
|
37
|
+
type: 'checkbox',
|
|
38
|
+
name: 'selectedKeys',
|
|
39
|
+
message: purple('Select keys to view:'),
|
|
40
|
+
choices: keys.map((k) => ({
|
|
41
|
+
name: `${k.name} ${dim(`(${k.masked_key})`)}`,
|
|
42
|
+
value: k,
|
|
43
|
+
checked: false,
|
|
44
|
+
})),
|
|
45
|
+
},
|
|
46
|
+
]);
|
|
47
|
+
|
|
48
|
+
if (selectedKeys.length === 0) {
|
|
49
|
+
console.log('');
|
|
50
|
+
warn('No keys selected.');
|
|
51
|
+
console.log('');
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
selected = selectedKeys;
|
|
56
|
+
} else {
|
|
57
|
+
// Match provided names case-insensitively
|
|
58
|
+
selected = [];
|
|
59
|
+
for (const name of names) {
|
|
60
|
+
const match = keys.find((k) => k.name.toLowerCase() === name.toLowerCase());
|
|
61
|
+
if (!match) {
|
|
62
|
+
warn(`No key found matching "${name}".`);
|
|
63
|
+
} else {
|
|
64
|
+
selected.push(match);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (selected.length === 0) {
|
|
69
|
+
console.log('');
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Decrypt each selected key
|
|
75
|
+
const decryptSpinner = ora({ text: `Decrypting ${selected.length} key(s)...`, color: 'magenta' }).start();
|
|
76
|
+
|
|
77
|
+
const rows = [];
|
|
78
|
+
for (const key of selected) {
|
|
79
|
+
try {
|
|
80
|
+
const data = await apiFetch(`/api/keys/${key.id}?decrypt=true`);
|
|
81
|
+
rows.push([chalk.white(key.name), chalk.hex('#a78bfa')(data.decrypted_key)]);
|
|
82
|
+
} catch (err) {
|
|
83
|
+
rows.push([chalk.white(key.name), dim('(failed to decrypt)')]);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
decryptSpinner.stop();
|
|
88
|
+
|
|
89
|
+
const table = new Table({
|
|
90
|
+
head: [purple.bold('Name'), purple.bold('Decrypted Value')],
|
|
91
|
+
style: {
|
|
92
|
+
head: [],
|
|
93
|
+
border: ['dim'],
|
|
94
|
+
},
|
|
95
|
+
chars: {
|
|
96
|
+
'top': '─', 'top-mid': '┬', 'top-left': '┌', 'top-right': '┐',
|
|
97
|
+
'bottom': '─', 'bottom-mid': '┴', 'bottom-left': '└', 'bottom-right': '┘',
|
|
98
|
+
'left': '│', 'left-mid': '├', 'mid': '─', 'mid-mid': '┼',
|
|
99
|
+
'right': '│', 'right-mid': '┤', 'middle': '│',
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
for (const row of rows) {
|
|
104
|
+
table.push(row);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
console.log('');
|
|
108
|
+
console.log(table.toString());
|
|
109
|
+
console.log('');
|
|
110
|
+
console.log(dim(' These values are sensitive. Clear your terminal when done.'));
|
|
111
|
+
console.log('');
|
|
112
|
+
}
|
package/src/index.js
CHANGED
|
@@ -8,6 +8,7 @@ import { addKey } from './commands/add.js';
|
|
|
8
8
|
import { removeKey } from './commands/remove.js';
|
|
9
9
|
import { makeEnv } from './commands/make.js';
|
|
10
10
|
import { saveEnv } from './commands/save.js';
|
|
11
|
+
import { viewKeys } from './commands/view.js';
|
|
11
12
|
import { initProject } from './commands/init.js';
|
|
12
13
|
import { openWebApp } from './commands/web-app.js';
|
|
13
14
|
import { showHelp } from './commands/help.js';
|
|
@@ -20,7 +21,7 @@ const program = new Command();
|
|
|
20
21
|
program
|
|
21
22
|
.name('vaulter')
|
|
22
23
|
.description('Vaulter CLI - Secure API Key Manager')
|
|
23
|
-
.version('2.3.
|
|
24
|
+
.version('2.3.2')
|
|
24
25
|
.action(async () => {
|
|
25
26
|
await showHelp();
|
|
26
27
|
});
|
|
@@ -91,6 +92,17 @@ program
|
|
|
91
92
|
await saveEnv(filename);
|
|
92
93
|
});
|
|
93
94
|
|
|
95
|
+
program
|
|
96
|
+
.command('view [key_names...]')
|
|
97
|
+
.description('Decrypt and display one or more API keys in your terminal')
|
|
98
|
+
.action(async (names) => {
|
|
99
|
+
if (!isAuthenticated()) {
|
|
100
|
+
error('Not authenticated. Run `vaulter sign-in` first.');
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
await viewKeys(names);
|
|
104
|
+
});
|
|
105
|
+
|
|
94
106
|
program
|
|
95
107
|
.command('init')
|
|
96
108
|
.description('Initialize current directory as a Vaulter project')
|