@id-wispera/cli 0.1.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/README.md +250 -0
- package/dist/commands/audit.d.ts +6 -0
- package/dist/commands/audit.d.ts.map +1 -0
- package/dist/commands/audit.js +82 -0
- package/dist/commands/audit.js.map +1 -0
- package/dist/commands/auth.d.ts +7 -0
- package/dist/commands/auth.d.ts.map +1 -0
- package/dist/commands/auth.js +310 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/create.d.ts +6 -0
- package/dist/commands/create.d.ts.map +1 -0
- package/dist/commands/create.js +88 -0
- package/dist/commands/create.js.map +1 -0
- package/dist/commands/exec.d.ts +8 -0
- package/dist/commands/exec.d.ts.map +1 -0
- package/dist/commands/exec.js +163 -0
- package/dist/commands/exec.js.map +1 -0
- package/dist/commands/import.d.ts +7 -0
- package/dist/commands/import.d.ts.map +1 -0
- package/dist/commands/import.js +1166 -0
- package/dist/commands/import.js.map +1 -0
- package/dist/commands/init.d.ts +6 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +50 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/list.d.ts +6 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/list.js +91 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/migrate.d.ts +7 -0
- package/dist/commands/migrate.d.ts.map +1 -0
- package/dist/commands/migrate.js +105 -0
- package/dist/commands/migrate.js.map +1 -0
- package/dist/commands/provision.d.ts +7 -0
- package/dist/commands/provision.d.ts.map +1 -0
- package/dist/commands/provision.js +303 -0
- package/dist/commands/provision.js.map +1 -0
- package/dist/commands/revoke.d.ts +6 -0
- package/dist/commands/revoke.d.ts.map +1 -0
- package/dist/commands/revoke.js +70 -0
- package/dist/commands/revoke.js.map +1 -0
- package/dist/commands/scan.d.ts +16 -0
- package/dist/commands/scan.d.ts.map +1 -0
- package/dist/commands/scan.js +700 -0
- package/dist/commands/scan.js.map +1 -0
- package/dist/commands/share.d.ts +6 -0
- package/dist/commands/share.d.ts.map +1 -0
- package/dist/commands/share.js +144 -0
- package/dist/commands/share.js.map +1 -0
- package/dist/commands/show.d.ts +6 -0
- package/dist/commands/show.d.ts.map +1 -0
- package/dist/commands/show.js +64 -0
- package/dist/commands/show.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +76 -0
- package/dist/index.js.map +1 -0
- package/dist/utils/display.d.ts +78 -0
- package/dist/utils/display.d.ts.map +1 -0
- package/dist/utils/display.js +290 -0
- package/dist/utils/display.js.map +1 -0
- package/dist/utils/prompts.d.ts +67 -0
- package/dist/utils/prompts.d.ts.map +1 -0
- package/dist/utils/prompts.js +353 -0
- package/dist/utils/prompts.js.map +1 -0
- package/dist/utils/vault-helpers.d.ts +21 -0
- package/dist/utils/vault-helpers.d.ts.map +1 -0
- package/dist/utils/vault-helpers.js +45 -0
- package/dist/utils/vault-helpers.js.map +1 -0
- package/package.json +71 -0
package/README.md
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
# @id-wispera/cli
|
|
2
|
+
|
|
3
|
+
Command-line interface for ID Wispera - the Identity Whisperer for AI Agents.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Install globally
|
|
9
|
+
npm install -g @id-wispera/cli
|
|
10
|
+
|
|
11
|
+
# Or use npx
|
|
12
|
+
npx @id-wispera/cli --help
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Commands
|
|
16
|
+
|
|
17
|
+
### Auth
|
|
18
|
+
|
|
19
|
+
Manage authentication sessions and tokens. The `auth` command group implements the zero-plaintext credential architecture -- passphrases are never stored in environment variables or passed as CLI arguments.
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# Log in interactively (passphrase is cached in OS keychain)
|
|
23
|
+
idw auth login
|
|
24
|
+
|
|
25
|
+
# Log out (clears cached key from keychain)
|
|
26
|
+
idw auth logout
|
|
27
|
+
|
|
28
|
+
# Check current auth status
|
|
29
|
+
idw auth status
|
|
30
|
+
|
|
31
|
+
# Create a scoped session token (for CI/headless use)
|
|
32
|
+
idw auth token create --name "ci-deploy" --scope read,list --ttl 24h
|
|
33
|
+
|
|
34
|
+
# List active session tokens
|
|
35
|
+
idw auth token list
|
|
36
|
+
|
|
37
|
+
# Revoke a session token
|
|
38
|
+
idw auth token revoke <token-id>
|
|
39
|
+
|
|
40
|
+
# Bootstrap a new vault with admin passport for provisioning
|
|
41
|
+
idw auth bootstrap
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
| Subcommand | Description |
|
|
45
|
+
|------------|-------------|
|
|
46
|
+
| `login` | Authenticate interactively; derived key is cached in OS keychain |
|
|
47
|
+
| `logout` | Clear cached authentication from keychain |
|
|
48
|
+
| `status` | Show current authentication state (logged in, token, expiry) |
|
|
49
|
+
| `token create` | Create a scoped session token for headless/CI environments |
|
|
50
|
+
| `token list` | List all active session tokens |
|
|
51
|
+
| `token revoke` | Revoke a session token by ID |
|
|
52
|
+
| `bootstrap` | Initialize vault and create an admin passport for provisioning |
|
|
53
|
+
|
|
54
|
+
### Initialize Vault
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
# Create a new encrypted vault
|
|
58
|
+
idw init
|
|
59
|
+
|
|
60
|
+
# Initialize with custom path
|
|
61
|
+
idw init --path ~/.my-vault/vault.json
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Create Passport
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
# Interactive creation
|
|
68
|
+
idw create
|
|
69
|
+
|
|
70
|
+
# Non-interactive (pipe credential value via stdin)
|
|
71
|
+
echo "sk-..." | idw create \
|
|
72
|
+
--name "OpenAI Production" \
|
|
73
|
+
--type api-key \
|
|
74
|
+
--stdin \
|
|
75
|
+
--visa access \
|
|
76
|
+
--platform openai \
|
|
77
|
+
--scope "chat,completions" \
|
|
78
|
+
--owner "alice@company.com"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
> **Breaking change**: The `--value` flag has been removed. Use `--stdin` to pipe credential values, which prevents secrets from appearing in shell history and process listings.
|
|
82
|
+
|
|
83
|
+
### List Passports
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
# List all passports
|
|
87
|
+
idw list
|
|
88
|
+
|
|
89
|
+
# Filter by status
|
|
90
|
+
idw list --status active
|
|
91
|
+
|
|
92
|
+
# Filter by platform
|
|
93
|
+
idw list --platform openai
|
|
94
|
+
|
|
95
|
+
# Filter by visa type
|
|
96
|
+
idw list --visa privilege
|
|
97
|
+
|
|
98
|
+
# Search by name
|
|
99
|
+
idw list --search "production"
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### Show Passport Details
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
# Show passport by ID
|
|
106
|
+
idw show <passport-id>
|
|
107
|
+
|
|
108
|
+
# Show with credential value (requires confirmation)
|
|
109
|
+
idw show <passport-id> --reveal
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Revoke Passport
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
# Revoke a passport
|
|
116
|
+
idw revoke <passport-id> --reason "Security concern"
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Share Passport
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
# Create a share link
|
|
123
|
+
idw share <passport-id>
|
|
124
|
+
|
|
125
|
+
# Share with options
|
|
126
|
+
idw share <passport-id> \
|
|
127
|
+
--scope read-only \
|
|
128
|
+
--expires 24h \
|
|
129
|
+
--max-views 1
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### View Audit Log
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
# View all audit entries
|
|
136
|
+
idw audit
|
|
137
|
+
|
|
138
|
+
# View for specific passport
|
|
139
|
+
idw audit <passport-id>
|
|
140
|
+
|
|
141
|
+
# Export audit log
|
|
142
|
+
idw audit --export audit.csv
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Scan for Credentials
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
# Scan current directory
|
|
149
|
+
idw scan
|
|
150
|
+
|
|
151
|
+
# Scan specific path
|
|
152
|
+
idw scan ./config
|
|
153
|
+
|
|
154
|
+
# Scan with verbose output
|
|
155
|
+
idw scan -v
|
|
156
|
+
|
|
157
|
+
# Export results
|
|
158
|
+
idw scan --output report.json
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### import - Import credentials
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
# From a single file
|
|
165
|
+
idw import .env
|
|
166
|
+
idw import config.json --owner dev@company.com
|
|
167
|
+
|
|
168
|
+
# Scan a directory and import all detected credentials
|
|
169
|
+
idw import ./project --all --owner dev@company.com -y
|
|
170
|
+
|
|
171
|
+
# Scan and import only high-confidence detections
|
|
172
|
+
idw import ./project --min-confidence 0.9 --owner dev@company.com
|
|
173
|
+
|
|
174
|
+
# Import from OpenClaw
|
|
175
|
+
idw import --format openclaw
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
#### Import Options
|
|
179
|
+
|
|
180
|
+
| Option | Description |
|
|
181
|
+
|--------|-------------|
|
|
182
|
+
| `--all` | Import all detected credentials from scan |
|
|
183
|
+
| `--min-confidence <level>` | Minimum confidence threshold (0-1) |
|
|
184
|
+
| `--format <format>` | Import format (env, json, openclaw) |
|
|
185
|
+
| `--owner <owner>` | Human owner email |
|
|
186
|
+
| `--auto-name` | Auto-generate passport names |
|
|
187
|
+
| `-y, --yes` | Import without confirmation |
|
|
188
|
+
| `-p, --path <path>` | Custom vault path |
|
|
189
|
+
|
|
190
|
+
#### What Gets Imported
|
|
191
|
+
|
|
192
|
+
Each imported passport stores:
|
|
193
|
+
- Source filename in tags (e.g., `file:config-json`)
|
|
194
|
+
- Confidence level tag (`confidence-high`, `confidence-medium`, `confidence-low`)
|
|
195
|
+
- Detection details in notes (file path, line number, confidence score, pattern)
|
|
196
|
+
|
|
197
|
+
## Configuration
|
|
198
|
+
|
|
199
|
+
The CLI stores its configuration in `~/.id-wispera/`:
|
|
200
|
+
|
|
201
|
+
- `vault.json` - Encrypted credential vault
|
|
202
|
+
- `config.json` - CLI configuration
|
|
203
|
+
|
|
204
|
+
### Environment Variables
|
|
205
|
+
|
|
206
|
+
| Variable | Description | Notes |
|
|
207
|
+
|----------|-------------|-------|
|
|
208
|
+
| `IDW_SESSION_TOKEN` | Session token for headless/CI authentication | Recommended for non-interactive use |
|
|
209
|
+
| `IDW_VAULT_PATH` | Custom vault location | Defaults to `~/.id-wispera/vault.json` |
|
|
210
|
+
| `IDW_NO_COLOR` | Disable colored output | |
|
|
211
|
+
| `IDW_PASSPHRASE` | Vault passphrase | Also read from `$CWD/.env` or `~/.id-wispera/.env` |
|
|
212
|
+
|
|
213
|
+
## Examples
|
|
214
|
+
|
|
215
|
+
### Quick Setup
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
# Initialize, authenticate, and create your first passport
|
|
219
|
+
idw init
|
|
220
|
+
idw auth login
|
|
221
|
+
echo "sk-..." | idw create --name "My API Key" --type api-key --stdin --platform openai --owner "me@company.com"
|
|
222
|
+
idw list
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Security Audit
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
# Scan project for exposed credentials
|
|
229
|
+
idw scan ./project
|
|
230
|
+
|
|
231
|
+
# Review audit history
|
|
232
|
+
idw audit
|
|
233
|
+
|
|
234
|
+
# Export compliance report
|
|
235
|
+
idw audit --export compliance-report.csv --format csv
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Credential Rotation
|
|
239
|
+
|
|
240
|
+
```bash
|
|
241
|
+
# Revoke old credential
|
|
242
|
+
idw revoke <old-passport-id> --reason "Scheduled rotation"
|
|
243
|
+
|
|
244
|
+
# Create new one
|
|
245
|
+
idw create --name "API Key v2" ...
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
## License
|
|
249
|
+
|
|
250
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../../src/commands/audit.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAWpC,wBAAgB,kBAAkB,IAAI,OAAO,CAqF5C"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Audit command: idw audit [id]
|
|
3
|
+
*/
|
|
4
|
+
import { Command } from 'commander';
|
|
5
|
+
import ora from 'ora';
|
|
6
|
+
import { getAuditLog, exportAuditLog, } from '@id-wispera/core';
|
|
7
|
+
import { error, displayAuditLog, title, info } from '../utils/display.js';
|
|
8
|
+
import { withUnlockedVault } from '../utils/vault-helpers.js';
|
|
9
|
+
import { writeFile } from 'fs/promises';
|
|
10
|
+
export function createAuditCommand() {
|
|
11
|
+
const command = new Command('audit')
|
|
12
|
+
.description('View audit log')
|
|
13
|
+
.argument('[id]', 'Passport ID (optional, shows all if omitted)')
|
|
14
|
+
.option('--action <action>', 'Filter by action (created, viewed, shared, modified, revoked, accessed)')
|
|
15
|
+
.option('--actor <actor>', 'Filter by actor')
|
|
16
|
+
.option('--since <date>', 'Show entries since date (YYYY-MM-DD)')
|
|
17
|
+
.option('--until <date>', 'Show entries until date (YYYY-MM-DD)')
|
|
18
|
+
.option('--limit <count>', 'Maximum entries to show', parseInt)
|
|
19
|
+
.option('--export <file>', 'Export to file')
|
|
20
|
+
.option('--format <format>', 'Export format (json, csv)', 'json')
|
|
21
|
+
.option('--json', 'Output as JSON')
|
|
22
|
+
.option('-p, --path <path>', 'Custom vault path')
|
|
23
|
+
.action(async (id, options) => {
|
|
24
|
+
const vault = await withUnlockedVault(options);
|
|
25
|
+
// Build filters
|
|
26
|
+
const filters = {};
|
|
27
|
+
if (options.action) {
|
|
28
|
+
filters.action = options.action;
|
|
29
|
+
}
|
|
30
|
+
if (options.actor) {
|
|
31
|
+
filters.actor = options.actor;
|
|
32
|
+
}
|
|
33
|
+
if (options.since) {
|
|
34
|
+
filters.startDate = new Date(options.since).toISOString();
|
|
35
|
+
}
|
|
36
|
+
if (options.until) {
|
|
37
|
+
filters.endDate = new Date(options.until).toISOString();
|
|
38
|
+
}
|
|
39
|
+
if (options.limit) {
|
|
40
|
+
filters.limit = options.limit;
|
|
41
|
+
}
|
|
42
|
+
// Get audit log
|
|
43
|
+
const spinner = ora('Loading audit log...').start();
|
|
44
|
+
try {
|
|
45
|
+
const entries = await getAuditLog(vault, id, Object.keys(filters).length > 0 ? filters : undefined);
|
|
46
|
+
spinner.stop();
|
|
47
|
+
// Export if requested
|
|
48
|
+
if (options.export) {
|
|
49
|
+
const format = options.format === 'csv' ? 'csv' : 'json';
|
|
50
|
+
const exported = await exportAuditLog(vault, format, filters);
|
|
51
|
+
await writeFile(options.export, exported, 'utf-8');
|
|
52
|
+
info(`Audit log exported to ${options.export}`);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
// JSON output
|
|
56
|
+
if (options.json) {
|
|
57
|
+
console.log(JSON.stringify(entries, null, 2));
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
// Display
|
|
61
|
+
console.log();
|
|
62
|
+
title(`Audit Log${id ? ` for ${id}` : ''} (${entries.length} entries)`);
|
|
63
|
+
console.log();
|
|
64
|
+
if (entries.length === 0) {
|
|
65
|
+
console.log('No audit entries found.');
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
console.log(displayAuditLog(entries));
|
|
69
|
+
if (options.limit && entries.length === options.limit) {
|
|
70
|
+
console.log();
|
|
71
|
+
info(`Showing ${options.limit} entries. Use --limit to see more.`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
spinner.fail('Failed to load audit log');
|
|
76
|
+
error(err instanceof Error ? err.message : 'Unknown error');
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
return command;
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=audit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit.js","sourceRoot":"","sources":["../../src/commands/audit.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth command group: idw auth <subcommand>
|
|
3
|
+
* Manages vault authentication, OS keychain, session tokens, and admin bootstrapping.
|
|
4
|
+
*/
|
|
5
|
+
import { Command } from 'commander';
|
|
6
|
+
export declare function createAuthCommand(): Command;
|
|
7
|
+
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/commands/auth.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA+UpC,wBAAgB,iBAAiB,IAAI,OAAO,CAW3C"}
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth command group: idw auth <subcommand>
|
|
3
|
+
* Manages vault authentication, OS keychain, session tokens, and admin bootstrapping.
|
|
4
|
+
*/
|
|
5
|
+
import { Command } from 'commander';
|
|
6
|
+
import ora from 'ora';
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
import inquirer from 'inquirer';
|
|
9
|
+
import { SessionTokenManager, KeychainProvider, unlockVault, vaultExists, } from '@id-wispera/core';
|
|
10
|
+
import { createAdminPassport } from '@id-wispera/core/provisioning';
|
|
11
|
+
import { promptPassphrase } from '../utils/prompts.js';
|
|
12
|
+
import { error, success, info, warning, title } from '../utils/display.js';
|
|
13
|
+
import { resolveVaultPath, withUnlockedVault } from '../utils/vault-helpers.js';
|
|
14
|
+
// ============================================================================
|
|
15
|
+
// idw auth login
|
|
16
|
+
// ============================================================================
|
|
17
|
+
function createLoginCommand() {
|
|
18
|
+
return new Command('login')
|
|
19
|
+
.description('Store vault passphrase in OS keychain')
|
|
20
|
+
.option('-p, --path <path>', 'Custom vault path')
|
|
21
|
+
.action(async (options) => {
|
|
22
|
+
const keychain = new KeychainProvider();
|
|
23
|
+
const available = await keychain.isAvailable();
|
|
24
|
+
if (!available) {
|
|
25
|
+
error('OS keychain is not available on this system.');
|
|
26
|
+
info('Use session tokens for headless environments: idw auth token create');
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
const vaultPath = resolveVaultPath(options);
|
|
30
|
+
if (!(await vaultExists(vaultPath))) {
|
|
31
|
+
error('Vault not found. Run `idw init` first.');
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
// Prompt for passphrase and verify it unlocks the vault
|
|
35
|
+
const passphrase = await promptPassphrase('Enter vault passphrase to store in keychain:');
|
|
36
|
+
const spinner = ora('Verifying passphrase...').start();
|
|
37
|
+
try {
|
|
38
|
+
await unlockVault(passphrase, vaultPath);
|
|
39
|
+
spinner.succeed('Passphrase verified');
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
spinner.fail('Invalid passphrase');
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
const stored = await keychain.store(passphrase);
|
|
46
|
+
if (stored) {
|
|
47
|
+
success('Passphrase stored in OS keychain.');
|
|
48
|
+
info('The vault will now unlock automatically without prompting.');
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
error('Failed to store passphrase in keychain.');
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
// ============================================================================
|
|
57
|
+
// idw auth logout
|
|
58
|
+
// ============================================================================
|
|
59
|
+
function createLogoutCommand() {
|
|
60
|
+
return new Command('logout')
|
|
61
|
+
.description('Remove vault passphrase from OS keychain')
|
|
62
|
+
.action(async () => {
|
|
63
|
+
const keychain = new KeychainProvider();
|
|
64
|
+
const removed = await keychain.remove();
|
|
65
|
+
if (removed) {
|
|
66
|
+
success('Passphrase removed from OS keychain.');
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
info('No passphrase was stored in the keychain (or keychain unavailable).');
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
// ============================================================================
|
|
74
|
+
// idw auth status
|
|
75
|
+
// ============================================================================
|
|
76
|
+
function createStatusCommand() {
|
|
77
|
+
return new Command('status')
|
|
78
|
+
.description('Show current authentication status')
|
|
79
|
+
.option('-p, --path <path>', 'Custom vault path')
|
|
80
|
+
.action(async (options) => {
|
|
81
|
+
console.log();
|
|
82
|
+
title('Auth Status');
|
|
83
|
+
console.log();
|
|
84
|
+
const vaultPath = resolveVaultPath(options);
|
|
85
|
+
const vaultFound = await vaultExists(vaultPath);
|
|
86
|
+
console.log(` Vault: ${vaultFound ? chalk.green('found') : chalk.red('not found')} (${chalk.dim(vaultPath)})`);
|
|
87
|
+
// Check keychain
|
|
88
|
+
const keychain = new KeychainProvider();
|
|
89
|
+
const keychainAvailable = await keychain.isAvailable();
|
|
90
|
+
const keychainStored = keychainAvailable ? (await keychain.retrieve()) !== null : false;
|
|
91
|
+
console.log(` OS keychain: ${keychainAvailable ? (keychainStored ? chalk.green('passphrase stored') : chalk.yellow('available, no passphrase')) : chalk.dim('not available')}`);
|
|
92
|
+
// Check session token env var
|
|
93
|
+
const token = SessionTokenManager.getTokenFromEnv();
|
|
94
|
+
console.log(` Session token: ${token ? chalk.green('IDW_SESSION_TOKEN set') : chalk.dim('not set')}`);
|
|
95
|
+
// Check IDW_PASSPHRASE env var
|
|
96
|
+
const envPassphrase = process.env.IDW_PASSPHRASE;
|
|
97
|
+
if (envPassphrase) {
|
|
98
|
+
console.log(` IDW_PASSPHRASE: ${chalk.green('set')}`);
|
|
99
|
+
}
|
|
100
|
+
// List session tokens
|
|
101
|
+
const tokenMgr = new SessionTokenManager();
|
|
102
|
+
const tokens = await tokenMgr.list();
|
|
103
|
+
if (tokens.length > 0) {
|
|
104
|
+
console.log();
|
|
105
|
+
console.log(` Session tokens: ${tokens.length}`);
|
|
106
|
+
for (const t of tokens) {
|
|
107
|
+
const expired = t.expiresAt && new Date(t.expiresAt) < new Date();
|
|
108
|
+
const status = expired ? chalk.red('expired') : chalk.green('active');
|
|
109
|
+
const hash = t.tokenHash.substring(0, 8);
|
|
110
|
+
console.log(` ${chalk.dim(hash)} ${t.label} ${status} created ${chalk.dim(t.createdAt)}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
console.log();
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
// ============================================================================
|
|
117
|
+
// idw auth token create / list / revoke
|
|
118
|
+
// ============================================================================
|
|
119
|
+
function createTokenCommand() {
|
|
120
|
+
const token = new Command('token').description('Manage session tokens');
|
|
121
|
+
token
|
|
122
|
+
.command('create')
|
|
123
|
+
.description('Create a new session token for headless / CI environments')
|
|
124
|
+
.option('-l, --label <label>', 'Label for the token', 'default')
|
|
125
|
+
.option('-e, --expires <duration>', 'Expiry duration (e.g., 24h, 7d, 30d)')
|
|
126
|
+
.option('-p, --path <path>', 'Custom vault path')
|
|
127
|
+
.action(async (options) => {
|
|
128
|
+
const vaultPath = resolveVaultPath(options);
|
|
129
|
+
if (!(await vaultExists(vaultPath))) {
|
|
130
|
+
error('Vault not found. Run `idw init` first.');
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
// Prompt for passphrase and verify
|
|
134
|
+
const passphrase = await promptPassphrase('Enter vault passphrase:');
|
|
135
|
+
const spinner = ora('Verifying passphrase...').start();
|
|
136
|
+
try {
|
|
137
|
+
await unlockVault(passphrase, vaultPath);
|
|
138
|
+
spinner.succeed('Passphrase verified');
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
spinner.fail('Invalid passphrase');
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
144
|
+
// Parse expiry
|
|
145
|
+
let expiresInSeconds;
|
|
146
|
+
if (options.expires) {
|
|
147
|
+
const match = options.expires.match(/^(\d+)(h|d)$/);
|
|
148
|
+
if (!match) {
|
|
149
|
+
error('Invalid expiry format. Use e.g., 24h, 7d, 30d');
|
|
150
|
+
process.exit(1);
|
|
151
|
+
}
|
|
152
|
+
const num = parseInt(match[1], 10);
|
|
153
|
+
const unit = match[2];
|
|
154
|
+
expiresInSeconds = unit === 'h' ? num * 3600 : num * 86400;
|
|
155
|
+
}
|
|
156
|
+
const tokenMgr = new SessionTokenManager();
|
|
157
|
+
const tokenValue = await tokenMgr.create(passphrase, {
|
|
158
|
+
label: options.label,
|
|
159
|
+
expiresInSeconds,
|
|
160
|
+
});
|
|
161
|
+
console.log();
|
|
162
|
+
success('Session token created.');
|
|
163
|
+
console.log();
|
|
164
|
+
console.log(` ${chalk.bold('Token:')} ${chalk.cyan(tokenValue)}`);
|
|
165
|
+
console.log();
|
|
166
|
+
warning('This token is shown once. Store it securely.');
|
|
167
|
+
console.log();
|
|
168
|
+
info('Set it as an environment variable in your CI/headless environment:');
|
|
169
|
+
console.log(` ${chalk.dim('export IDW_SESSION_TOKEN=')}${chalk.cyan(tokenValue)}`);
|
|
170
|
+
console.log();
|
|
171
|
+
});
|
|
172
|
+
token
|
|
173
|
+
.command('list')
|
|
174
|
+
.description('List all session tokens')
|
|
175
|
+
.action(async () => {
|
|
176
|
+
const tokenMgr = new SessionTokenManager();
|
|
177
|
+
const tokens = await tokenMgr.list();
|
|
178
|
+
if (tokens.length === 0) {
|
|
179
|
+
info('No session tokens found.');
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
console.log();
|
|
183
|
+
title('Session Tokens');
|
|
184
|
+
console.log();
|
|
185
|
+
for (const t of tokens) {
|
|
186
|
+
const expired = t.expiresAt && new Date(t.expiresAt) < new Date();
|
|
187
|
+
const status = expired ? chalk.red('expired') : chalk.green('active');
|
|
188
|
+
const hash = t.tokenHash.substring(0, 12);
|
|
189
|
+
console.log(` ${chalk.cyan(hash)} ${chalk.bold(t.label)} ${status}`);
|
|
190
|
+
console.log(` Created: ${chalk.dim(t.createdAt)}${t.expiresAt ? ` Expires: ${chalk.dim(t.expiresAt)}` : ''}`);
|
|
191
|
+
console.log();
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
token
|
|
195
|
+
.command('revoke <hash-prefix>')
|
|
196
|
+
.description('Revoke a session token by its hash prefix')
|
|
197
|
+
.action(async (hashPrefix) => {
|
|
198
|
+
const tokenMgr = new SessionTokenManager();
|
|
199
|
+
const revoked = await tokenMgr.revoke(hashPrefix);
|
|
200
|
+
if (revoked) {
|
|
201
|
+
success(`Token ${hashPrefix}... revoked.`);
|
|
202
|
+
}
|
|
203
|
+
else {
|
|
204
|
+
error(`No token found matching prefix '${hashPrefix}'.`);
|
|
205
|
+
info("Run 'idw auth token list' to see available tokens.");
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
return token;
|
|
209
|
+
}
|
|
210
|
+
// ============================================================================
|
|
211
|
+
// idw auth bootstrap <provider>
|
|
212
|
+
// ============================================================================
|
|
213
|
+
function createBootstrapCommand() {
|
|
214
|
+
return new Command('bootstrap')
|
|
215
|
+
.description('Store admin credentials for a provisioning provider')
|
|
216
|
+
.argument('<provider>', 'Provider name (openai, aws, google-cloud, azure-entra, github, twilio, sendgrid, anthropic)')
|
|
217
|
+
.option('-p, --path <path>', 'Custom vault path')
|
|
218
|
+
.action(async (provider, options) => {
|
|
219
|
+
const validProviders = ['openai', 'aws', 'google-cloud', 'azure-entra', 'github', 'twilio', 'sendgrid', 'anthropic'];
|
|
220
|
+
if (!validProviders.includes(provider)) {
|
|
221
|
+
error(`Unknown provider: ${provider}`);
|
|
222
|
+
info(`Valid providers: ${validProviders.join(', ')}`);
|
|
223
|
+
process.exit(1);
|
|
224
|
+
}
|
|
225
|
+
console.log();
|
|
226
|
+
title(`Bootstrap: ${provider}`);
|
|
227
|
+
console.log();
|
|
228
|
+
info('This will store admin credentials for the provisioning provider in your vault.');
|
|
229
|
+
info('The credentials will be encrypted at rest and used only during provisioning.');
|
|
230
|
+
console.log();
|
|
231
|
+
// Collect auth based on provider type
|
|
232
|
+
let auth;
|
|
233
|
+
if (provider === 'aws') {
|
|
234
|
+
const answers = await inquirer.prompt([
|
|
235
|
+
{ type: 'password', name: 'accessKeyId', message: 'AWS Access Key ID:', mask: '*' },
|
|
236
|
+
{ type: 'password', name: 'secretAccessKey', message: 'AWS Secret Access Key:', mask: '*' },
|
|
237
|
+
{ type: 'input', name: 'region', message: 'AWS Region:', default: 'us-east-1' },
|
|
238
|
+
]);
|
|
239
|
+
auth = { type: 'aws-sigv4', ...answers };
|
|
240
|
+
}
|
|
241
|
+
else if (provider === 'azure-entra') {
|
|
242
|
+
const answers = await inquirer.prompt([
|
|
243
|
+
{ type: 'password', name: 'token', message: 'Azure OAuth2 Token:', mask: '*' },
|
|
244
|
+
{ type: 'input', name: 'tenantId', message: 'Tenant ID:' },
|
|
245
|
+
]);
|
|
246
|
+
auth = { type: 'oauth2', ...answers };
|
|
247
|
+
}
|
|
248
|
+
else if (provider === 'github') {
|
|
249
|
+
const answers = await inquirer.prompt([
|
|
250
|
+
{ type: 'password', name: 'privateKey', message: 'GitHub App Private Key (PEM):', mask: '*' },
|
|
251
|
+
{ type: 'input', name: 'appId', message: 'GitHub App ID:' },
|
|
252
|
+
]);
|
|
253
|
+
auth = { type: 'jwt', ...answers };
|
|
254
|
+
}
|
|
255
|
+
else if (provider === 'twilio') {
|
|
256
|
+
const answers = await inquirer.prompt([
|
|
257
|
+
{ type: 'input', name: 'username', message: 'Twilio Account SID:' },
|
|
258
|
+
{ type: 'password', name: 'password', message: 'Twilio Auth Token:', mask: '*' },
|
|
259
|
+
]);
|
|
260
|
+
auth = { type: 'basic', ...answers };
|
|
261
|
+
}
|
|
262
|
+
else {
|
|
263
|
+
// openai, google-cloud, sendgrid, anthropic — all use api-key
|
|
264
|
+
const answers = await inquirer.prompt([
|
|
265
|
+
{ type: 'password', name: 'key', message: `${provider} Admin API Key:`, mask: '*' },
|
|
266
|
+
]);
|
|
267
|
+
auth = { type: 'api-key', ...answers };
|
|
268
|
+
}
|
|
269
|
+
// Get human owner
|
|
270
|
+
const { owner } = await inquirer.prompt([
|
|
271
|
+
{
|
|
272
|
+
type: 'input',
|
|
273
|
+
name: 'owner',
|
|
274
|
+
message: 'Human owner email:',
|
|
275
|
+
validate: (input) => input.includes('@') || 'Valid email required',
|
|
276
|
+
},
|
|
277
|
+
]);
|
|
278
|
+
// Unlock vault
|
|
279
|
+
const vault = await withUnlockedVault(options);
|
|
280
|
+
// Store admin passport
|
|
281
|
+
const spinner = ora('Storing admin credentials...').start();
|
|
282
|
+
try {
|
|
283
|
+
await createAdminPassport(vault, provider, auth, owner);
|
|
284
|
+
spinner.succeed('Admin credentials stored');
|
|
285
|
+
}
|
|
286
|
+
catch (err) {
|
|
287
|
+
spinner.fail('Failed to store admin credentials');
|
|
288
|
+
error(err instanceof Error ? err.message : 'Unknown error');
|
|
289
|
+
process.exit(1);
|
|
290
|
+
}
|
|
291
|
+
console.log();
|
|
292
|
+
success(`Admin credentials for ${provider} are now stored in the vault.`);
|
|
293
|
+
info(`Use 'idw auth status' to verify, or provision keys with the provisioning commands.`);
|
|
294
|
+
console.log();
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
// ============================================================================
|
|
298
|
+
// Root: idw auth
|
|
299
|
+
// ============================================================================
|
|
300
|
+
export function createAuthCommand() {
|
|
301
|
+
const auth = new Command('auth')
|
|
302
|
+
.description('Manage vault authentication and provider credentials');
|
|
303
|
+
auth.addCommand(createLoginCommand());
|
|
304
|
+
auth.addCommand(createLogoutCommand());
|
|
305
|
+
auth.addCommand(createStatusCommand());
|
|
306
|
+
auth.addCommand(createTokenCommand());
|
|
307
|
+
auth.addCommand(createBootstrapCommand());
|
|
308
|
+
return auth;
|
|
309
|
+
}
|
|
310
|
+
//# sourceMappingURL=auth.js.map
|