@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.
Files changed (70) hide show
  1. package/README.md +250 -0
  2. package/dist/commands/audit.d.ts +6 -0
  3. package/dist/commands/audit.d.ts.map +1 -0
  4. package/dist/commands/audit.js +82 -0
  5. package/dist/commands/audit.js.map +1 -0
  6. package/dist/commands/auth.d.ts +7 -0
  7. package/dist/commands/auth.d.ts.map +1 -0
  8. package/dist/commands/auth.js +310 -0
  9. package/dist/commands/auth.js.map +1 -0
  10. package/dist/commands/create.d.ts +6 -0
  11. package/dist/commands/create.d.ts.map +1 -0
  12. package/dist/commands/create.js +88 -0
  13. package/dist/commands/create.js.map +1 -0
  14. package/dist/commands/exec.d.ts +8 -0
  15. package/dist/commands/exec.d.ts.map +1 -0
  16. package/dist/commands/exec.js +163 -0
  17. package/dist/commands/exec.js.map +1 -0
  18. package/dist/commands/import.d.ts +7 -0
  19. package/dist/commands/import.d.ts.map +1 -0
  20. package/dist/commands/import.js +1166 -0
  21. package/dist/commands/import.js.map +1 -0
  22. package/dist/commands/init.d.ts +6 -0
  23. package/dist/commands/init.d.ts.map +1 -0
  24. package/dist/commands/init.js +50 -0
  25. package/dist/commands/init.js.map +1 -0
  26. package/dist/commands/list.d.ts +6 -0
  27. package/dist/commands/list.d.ts.map +1 -0
  28. package/dist/commands/list.js +91 -0
  29. package/dist/commands/list.js.map +1 -0
  30. package/dist/commands/migrate.d.ts +7 -0
  31. package/dist/commands/migrate.d.ts.map +1 -0
  32. package/dist/commands/migrate.js +105 -0
  33. package/dist/commands/migrate.js.map +1 -0
  34. package/dist/commands/provision.d.ts +7 -0
  35. package/dist/commands/provision.d.ts.map +1 -0
  36. package/dist/commands/provision.js +303 -0
  37. package/dist/commands/provision.js.map +1 -0
  38. package/dist/commands/revoke.d.ts +6 -0
  39. package/dist/commands/revoke.d.ts.map +1 -0
  40. package/dist/commands/revoke.js +70 -0
  41. package/dist/commands/revoke.js.map +1 -0
  42. package/dist/commands/scan.d.ts +16 -0
  43. package/dist/commands/scan.d.ts.map +1 -0
  44. package/dist/commands/scan.js +700 -0
  45. package/dist/commands/scan.js.map +1 -0
  46. package/dist/commands/share.d.ts +6 -0
  47. package/dist/commands/share.d.ts.map +1 -0
  48. package/dist/commands/share.js +144 -0
  49. package/dist/commands/share.js.map +1 -0
  50. package/dist/commands/show.d.ts +6 -0
  51. package/dist/commands/show.d.ts.map +1 -0
  52. package/dist/commands/show.js +64 -0
  53. package/dist/commands/show.js.map +1 -0
  54. package/dist/index.d.ts +7 -0
  55. package/dist/index.d.ts.map +1 -0
  56. package/dist/index.js +76 -0
  57. package/dist/index.js.map +1 -0
  58. package/dist/utils/display.d.ts +78 -0
  59. package/dist/utils/display.d.ts.map +1 -0
  60. package/dist/utils/display.js +290 -0
  61. package/dist/utils/display.js.map +1 -0
  62. package/dist/utils/prompts.d.ts +67 -0
  63. package/dist/utils/prompts.d.ts.map +1 -0
  64. package/dist/utils/prompts.js +353 -0
  65. package/dist/utils/prompts.js.map +1 -0
  66. package/dist/utils/vault-helpers.d.ts +21 -0
  67. package/dist/utils/vault-helpers.d.ts.map +1 -0
  68. package/dist/utils/vault-helpers.js +45 -0
  69. package/dist/utils/vault-helpers.js.map +1 -0
  70. 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,6 @@
1
+ /**
2
+ * Audit command: idw audit [id]
3
+ */
4
+ import { Command } from 'commander';
5
+ export declare function createAuditCommand(): Command;
6
+ //# sourceMappingURL=audit.d.ts.map
@@ -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