@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
@@ -0,0 +1,1166 @@
1
+ /**
2
+ * Import command: idw import <file>
3
+ * Supports .env, .json, and OpenClaw format imports
4
+ */
5
+ import { Command } from 'commander';
6
+ import ora from 'ora';
7
+ import { readFile, readdir, access, stat } from 'fs/promises';
8
+ import { basename, extname, join, resolve, relative } from 'path';
9
+ import { homedir } from 'os';
10
+ import chalk from 'chalk';
11
+ import { unlockVault, createPassport, detectCredentials, classifyCredential, vaultExists, getDefaultVaultPath, } from '@id-wispera/core';
12
+ import { promptPassphrase, confirmImport } from '../utils/prompts.js';
13
+ import { error, success, info, warning, maskCredential, title } from '../utils/display.js';
14
+ import { walkDirectory, scanFile } from './scan.js';
15
+ /**
16
+ * Parse .env file
17
+ */
18
+ function parseEnvFile(content) {
19
+ const credentials = [];
20
+ const lines = content.split('\n');
21
+ for (let i = 0; i < lines.length; i++) {
22
+ const line = lines[i]?.trim();
23
+ if (!line || line.startsWith('#'))
24
+ continue;
25
+ const match = line.match(/^([A-Z_][A-Z0-9_]*)=(.+)$/);
26
+ if (match) {
27
+ const [, name, value] = match;
28
+ if (name && value) {
29
+ // Remove quotes if present
30
+ const cleanValue = value.replace(/^["']|["']$/g, '');
31
+ // Check if it looks like a credential
32
+ const type = classifyCredential(cleanValue);
33
+ const looksLikeSecret = type !== 'custom' ||
34
+ name.toLowerCase().includes('key') ||
35
+ name.toLowerCase().includes('secret') ||
36
+ name.toLowerCase().includes('token') ||
37
+ name.toLowerCase().includes('password') ||
38
+ name.toLowerCase().includes('api');
39
+ if (looksLikeSecret && cleanValue.length > 5) {
40
+ credentials.push({
41
+ name,
42
+ type: type === 'custom' ? 'secret' : type,
43
+ value: cleanValue,
44
+ line: i + 1,
45
+ });
46
+ }
47
+ }
48
+ }
49
+ }
50
+ return credentials;
51
+ }
52
+ /**
53
+ * Parse JSON config file
54
+ */
55
+ function parseJsonFile(content, filename) {
56
+ const credentials = [];
57
+ try {
58
+ const json = JSON.parse(content);
59
+ function findCredentials(obj, path = '') {
60
+ for (const [key, value] of Object.entries(obj)) {
61
+ const currentPath = path ? `${path}.${key}` : key;
62
+ if (typeof value === 'string' && value.length > 5) {
63
+ const type = classifyCredential(value);
64
+ const looksLikeSecret = type !== 'custom' ||
65
+ key.toLowerCase().includes('key') ||
66
+ key.toLowerCase().includes('secret') ||
67
+ key.toLowerCase().includes('token') ||
68
+ key.toLowerCase().includes('password') ||
69
+ key.toLowerCase().includes('api');
70
+ if (looksLikeSecret) {
71
+ credentials.push({
72
+ name: currentPath,
73
+ type: type === 'custom' ? 'secret' : type,
74
+ value,
75
+ });
76
+ }
77
+ }
78
+ else if (typeof value === 'object' && value !== null) {
79
+ findCredentials(value, currentPath);
80
+ }
81
+ }
82
+ }
83
+ findCredentials(json);
84
+ }
85
+ catch {
86
+ // Not valid JSON, try regex detection
87
+ const detected = detectCredentials(content);
88
+ for (const result of detected) {
89
+ credentials.push({
90
+ name: `${filename}:${result.line}`,
91
+ type: result.type,
92
+ value: result.value,
93
+ line: result.line,
94
+ });
95
+ }
96
+ }
97
+ return credentials;
98
+ }
99
+ /**
100
+ * Guess platform from credential name/value
101
+ */
102
+ function guessPlatform(name, value) {
103
+ const nameLower = name.toLowerCase();
104
+ if (nameLower.includes('anthropic') || value.startsWith('sk-ant-'))
105
+ return 'anthropic';
106
+ if (nameLower.includes('openai') || value.startsWith('sk-'))
107
+ return 'openai';
108
+ if (nameLower.includes('github') || value.startsWith('ghp_') || value.startsWith('gho_'))
109
+ return 'github';
110
+ if (nameLower.includes('aws') || value.startsWith('AKIA'))
111
+ return 'aws';
112
+ if (nameLower.includes('azure'))
113
+ return 'azure-ai';
114
+ if (nameLower.includes('google') || value.startsWith('AIza'))
115
+ return 'google-a2a';
116
+ if (nameLower.includes('stripe') || value.startsWith('sk_live_') || value.startsWith('sk_test_'))
117
+ return 'custom';
118
+ return 'custom';
119
+ }
120
+ /**
121
+ * Capitalize first letter
122
+ */
123
+ function capitalizeFirst(str) {
124
+ return str.charAt(0).toUpperCase() + str.slice(1);
125
+ }
126
+ /**
127
+ * Scan and collect all OpenClaw credentials
128
+ */
129
+ async function scanOpenClawCredentials() {
130
+ const credentials = [];
131
+ const openclawPath = join(homedir(), '.openclaw');
132
+ try {
133
+ await access(openclawPath);
134
+ }
135
+ catch {
136
+ return credentials;
137
+ }
138
+ // Scan WhatsApp credentials
139
+ const whatsappPath = join(openclawPath, 'credentials', 'whatsapp');
140
+ try {
141
+ const accounts = await readdir(whatsappPath);
142
+ for (const accountId of accounts) {
143
+ const credsPath = join(whatsappPath, accountId, 'creds.json');
144
+ try {
145
+ const content = await readFile(credsPath, 'utf-8');
146
+ const data = JSON.parse(content);
147
+ credentials.push({
148
+ name: `OpenClaw — WhatsApp Session (${accountId})`,
149
+ type: 'session-keys',
150
+ visaType: 'access',
151
+ value: content,
152
+ platforms: ['openclaw'],
153
+ tags: ['openclaw', 'whatsapp', 'session', 'imported'],
154
+ filePath: credsPath,
155
+ metadata: { accountId, botName: data.me?.name },
156
+ });
157
+ }
158
+ catch {
159
+ // Skip unreadable files
160
+ }
161
+ }
162
+ }
163
+ catch {
164
+ // WhatsApp dir doesn't exist
165
+ }
166
+ // Scan auth profiles (LLM API keys)
167
+ const agentsPath = join(openclawPath, 'agents');
168
+ try {
169
+ const agents = await readdir(agentsPath);
170
+ for (const agentId of agents) {
171
+ const authPath = join(agentsPath, agentId, 'agent', 'auth-profiles.json');
172
+ try {
173
+ const content = await readFile(authPath, 'utf-8');
174
+ const data = JSON.parse(content);
175
+ for (const [provider, profile] of Object.entries(data)) {
176
+ if (profile.type === 'api-key' && profile.key) {
177
+ credentials.push({
178
+ name: `OpenClaw — ${capitalizeFirst(provider)} API Key`,
179
+ type: 'api-key',
180
+ visaType: 'privilege',
181
+ value: profile.key,
182
+ platforms: ['openclaw', 'mcp', provider].filter(Boolean),
183
+ tags: ['openclaw', provider, 'api-key', 'llm', 'imported'],
184
+ filePath: authPath,
185
+ metadata: { agentId, provider, model: profile.model },
186
+ });
187
+ }
188
+ }
189
+ }
190
+ catch {
191
+ // Skip unreadable files
192
+ }
193
+ }
194
+ }
195
+ catch {
196
+ // Agents dir doesn't exist
197
+ }
198
+ // Scan OAuth tokens
199
+ const oauthPath = join(openclawPath, 'credentials', 'oauth.json');
200
+ try {
201
+ const content = await readFile(oauthPath, 'utf-8');
202
+ const data = JSON.parse(content);
203
+ for (const [provider, tokens] of Object.entries(data)) {
204
+ if (tokens.access_token) {
205
+ const expiresAt = tokens.expires_at ? new Date(tokens.expires_at * 1000) : undefined;
206
+ credentials.push({
207
+ name: `OpenClaw — ${capitalizeFirst(provider)} OAuth Token`,
208
+ type: 'oauth-token',
209
+ visaType: 'access',
210
+ value: tokens.access_token,
211
+ platforms: ['openclaw'],
212
+ tags: ['openclaw', provider, 'oauth', 'imported'],
213
+ filePath: oauthPath,
214
+ metadata: { provider, hasRefreshToken: !!tokens.refresh_token },
215
+ expiresAt,
216
+ });
217
+ }
218
+ }
219
+ }
220
+ catch {
221
+ // OAuth file doesn't exist
222
+ }
223
+ // Scan openclaw.json for channel tokens
224
+ const configPath = join(openclawPath, 'openclaw.json');
225
+ try {
226
+ const content = await readFile(configPath, 'utf-8');
227
+ const data = JSON.parse(content);
228
+ // Telegram
229
+ if (data.channels?.telegram?.token) {
230
+ credentials.push({
231
+ name: 'OpenClaw — Telegram Bot Token',
232
+ type: 'bot-token',
233
+ visaType: 'access',
234
+ value: data.channels.telegram.token,
235
+ platforms: ['openclaw'],
236
+ tags: ['openclaw', 'telegram', 'bot-token', 'imported'],
237
+ filePath: configPath,
238
+ metadata: { channel: 'telegram' },
239
+ });
240
+ }
241
+ // Slack Bot Token
242
+ if (data.channels?.slack?.botToken) {
243
+ credentials.push({
244
+ name: 'OpenClaw — Slack Bot Token',
245
+ type: 'bot-token',
246
+ visaType: 'access',
247
+ value: data.channels.slack.botToken,
248
+ platforms: ['openclaw'],
249
+ tags: ['openclaw', 'slack', 'bot-token', 'imported'],
250
+ filePath: configPath,
251
+ metadata: { channel: 'slack' },
252
+ });
253
+ }
254
+ // Slack App Token
255
+ if (data.channels?.slack?.appToken) {
256
+ credentials.push({
257
+ name: 'OpenClaw — Slack App Token',
258
+ type: 'bot-token',
259
+ visaType: 'access',
260
+ value: data.channels.slack.appToken,
261
+ platforms: ['openclaw'],
262
+ tags: ['openclaw', 'slack', 'app-token', 'imported'],
263
+ filePath: configPath,
264
+ metadata: { channel: 'slack' },
265
+ });
266
+ }
267
+ // Discord
268
+ if (data.channels?.discord?.token) {
269
+ credentials.push({
270
+ name: 'OpenClaw — Discord Bot Token',
271
+ type: 'bot-token',
272
+ visaType: 'access',
273
+ value: data.channels.discord.token,
274
+ platforms: ['openclaw'],
275
+ tags: ['openclaw', 'discord', 'bot-token', 'imported'],
276
+ filePath: configPath,
277
+ metadata: { channel: 'discord' },
278
+ });
279
+ }
280
+ // Gateway token
281
+ if (data.gateway?.token) {
282
+ credentials.push({
283
+ name: 'OpenClaw — Gateway Token',
284
+ type: 'api-key',
285
+ visaType: 'privilege',
286
+ value: data.gateway.token,
287
+ platforms: ['openclaw'],
288
+ tags: ['openclaw', 'gateway', 'admin', 'imported'],
289
+ filePath: configPath,
290
+ metadata: { gatewayPort: data.gateway.port },
291
+ });
292
+ }
293
+ }
294
+ catch {
295
+ // Config file doesn't exist
296
+ }
297
+ // Scan allowlists
298
+ const credsPath = join(openclawPath, 'credentials');
299
+ try {
300
+ const files = await readdir(credsPath);
301
+ for (const file of files) {
302
+ if (file.endsWith('-allowFrom.json')) {
303
+ const filePath = join(credsPath, file);
304
+ try {
305
+ const content = await readFile(filePath, 'utf-8');
306
+ const data = JSON.parse(content);
307
+ const channel = file.replace('-allowFrom.json', '');
308
+ const pairedCount = Object.keys(data).length;
309
+ credentials.push({
310
+ name: `OpenClaw — ${capitalizeFirst(channel)} Pairing Allowlist`,
311
+ type: 'custom',
312
+ visaType: 'access',
313
+ value: content,
314
+ platforms: ['openclaw'],
315
+ tags: ['openclaw', channel, 'allowlist', 'imported'],
316
+ filePath,
317
+ metadata: { channel, pairedCount },
318
+ });
319
+ }
320
+ catch {
321
+ // Skip unreadable files
322
+ }
323
+ }
324
+ }
325
+ }
326
+ catch {
327
+ // Credentials dir doesn't exist
328
+ }
329
+ return credentials;
330
+ }
331
+ /**
332
+ * Display OpenClaw credentials for selection
333
+ */
334
+ function displayOpenClawCredentials(credentials) {
335
+ console.log();
336
+ title('OpenClaw Credentials Found');
337
+ console.log();
338
+ for (let i = 0; i < credentials.length; i++) {
339
+ const cred = credentials[i];
340
+ if (!cred)
341
+ continue;
342
+ const visaLabel = cred.visaType === 'privilege' ? chalk.red('Privilege') : chalk.green('Access');
343
+ const valuePreview = maskCredential(cred.value, 6);
344
+ console.log(` ${chalk.cyan(`[${i + 1}]`)} ${cred.name}`);
345
+ console.log(` Type: ${cred.type} | Visa: ${visaLabel}`);
346
+ console.log(` Preview: ${chalk.dim(valuePreview)}`);
347
+ console.log(` Source: ${chalk.dim(cred.filePath)}`);
348
+ console.log();
349
+ }
350
+ }
351
+ /**
352
+ * Discover .env files in a directory (non-recursive, top-level only)
353
+ */
354
+ async function discoverEnvFiles(dir) {
355
+ const found = [];
356
+ try {
357
+ const entries = await readdir(dir, { withFileTypes: true });
358
+ for (const entry of entries) {
359
+ if (entry.isFile() && (entry.name === '.env' || entry.name.startsWith('.env.'))) {
360
+ found.push(join(dir, entry.name));
361
+ }
362
+ }
363
+ }
364
+ catch {
365
+ // Directory not readable
366
+ }
367
+ return found.sort();
368
+ }
369
+ /**
370
+ * Discover JSON config files in a directory (non-recursive, top-level only)
371
+ * Excludes well-known non-credential files
372
+ */
373
+ const JSON_SKIP = new Set([
374
+ 'package.json',
375
+ 'package-lock.json',
376
+ 'tsconfig.json',
377
+ 'tsconfig.base.json',
378
+ 'tsconfig.build.json',
379
+ 'turbo.json',
380
+ 'lerna.json',
381
+ 'nx.json',
382
+ 'jest.config.json',
383
+ 'biome.json',
384
+ '.eslintrc.json',
385
+ '.prettierrc.json',
386
+ 'renovate.json',
387
+ 'manifest.json',
388
+ 'composer.json',
389
+ ]);
390
+ async function discoverJsonFiles(dir) {
391
+ const found = [];
392
+ try {
393
+ const entries = await readdir(dir, { withFileTypes: true });
394
+ for (const entry of entries) {
395
+ if (entry.isFile() && entry.name.endsWith('.json') && !JSON_SKIP.has(entry.name)) {
396
+ found.push(join(dir, entry.name));
397
+ }
398
+ }
399
+ }
400
+ catch {
401
+ // Directory not readable
402
+ }
403
+ return found.sort();
404
+ }
405
+ export function createImportCommand() {
406
+ const command = new Command('import')
407
+ .description('Import credentials from files, directory scans, or OpenClaw')
408
+ .argument('[path]', 'File or directory to import from')
409
+ .option('--format <format>', 'Import format: env, json, openclaw')
410
+ .option('--all', 'Import all detected credentials from a directory scan')
411
+ .option('--min-confidence <level>', 'Minimum confidence threshold for scan import (0-1)')
412
+ .option('--owner <owner>', 'Human owner email')
413
+ .option('--auto-name', 'Auto-generate passport names')
414
+ .option('-y, --yes', 'Import all without confirmation')
415
+ .option('-p, --path <path>', 'Custom vault path')
416
+ .addHelpText('after', `
417
+ Examples:
418
+ $ idw import .env # Import from a specific .env file
419
+ $ idw import config.json # Import from a JSON config file
420
+ $ idw import --format env # Auto-discover .env files in current directory
421
+ $ idw import --format json # Auto-discover JSON config files in current directory
422
+ $ idw import --format openclaw # Auto-scan ~/.openclaw credentials
423
+ $ idw import ./project --all # Scan directory and import all detected credentials
424
+ $ idw import --all # Scan current directory for all credentials
425
+ $ idw import --min-confidence 0.9 # Only import high-confidence detections
426
+ $ idw import --format env -y # Auto-discover .env files and import all without prompting
427
+ `)
428
+ .action(async (file, options) => {
429
+ const vaultPath = options.path ?? getDefaultVaultPath();
430
+ // Check vault exists
431
+ if (!(await vaultExists(vaultPath))) {
432
+ error('Vault not found. Run `idw init` first.');
433
+ process.exit(1);
434
+ }
435
+ // Handle OpenClaw format
436
+ if (options.format === 'openclaw') {
437
+ console.log();
438
+ title('OpenClaw Credential Import');
439
+ console.log();
440
+ const spinner = ora('Scanning OpenClaw credentials...').start();
441
+ const openclawCreds = await scanOpenClawCredentials();
442
+ spinner.stop();
443
+ if (openclawCreds.length === 0) {
444
+ info('No OpenClaw credentials found at ~/.openclaw/');
445
+ return;
446
+ }
447
+ console.log(chalk.green(`Found ${openclawCreds.length} credential(s) in OpenClaw installation`));
448
+ // Display credentials
449
+ displayOpenClawCredentials(openclawCreds);
450
+ // Confirm import
451
+ let selectedIndexes;
452
+ if (options.yes) {
453
+ selectedIndexes = openclawCreds.map((_, i) => i);
454
+ console.log(chalk.yellow(`Importing all ${openclawCreds.length} credentials (--yes flag)`));
455
+ }
456
+ else {
457
+ const inquirer = await import('inquirer');
458
+ const { selected } = await inquirer.default.prompt([
459
+ {
460
+ type: 'checkbox',
461
+ name: 'selected',
462
+ message: 'Select credentials to import:',
463
+ choices: openclawCreds.map((c, i) => ({
464
+ name: `${c.name} (${c.type})`,
465
+ value: i,
466
+ checked: true,
467
+ })),
468
+ },
469
+ ]);
470
+ selectedIndexes = selected;
471
+ }
472
+ if (selectedIndexes.length === 0) {
473
+ console.log('No credentials selected for import.');
474
+ return;
475
+ }
476
+ // Get owner if not provided
477
+ let owner = options.owner;
478
+ if (!owner) {
479
+ const inquirer = await import('inquirer');
480
+ const { ownerEmail } = await inquirer.default.prompt([
481
+ {
482
+ type: 'input',
483
+ name: 'ownerEmail',
484
+ message: 'Human owner email (who is responsible for these credentials):',
485
+ validate: (input) => input.includes('@') || 'Valid email required',
486
+ },
487
+ ]);
488
+ owner = ownerEmail;
489
+ }
490
+ // Unlock vault
491
+ const passphrase = await promptPassphrase();
492
+ const unlockSpinner = ora('Unlocking vault...').start();
493
+ let vault;
494
+ try {
495
+ vault = await unlockVault(passphrase, vaultPath);
496
+ unlockSpinner.succeed('Vault unlocked');
497
+ }
498
+ catch (err) {
499
+ unlockSpinner.fail('Failed to unlock vault');
500
+ error(err instanceof Error ? err.message : 'Invalid passphrase');
501
+ process.exit(1);
502
+ }
503
+ // Import selected credentials
504
+ const importSpinner = ora('Importing credentials...').start();
505
+ let imported = 0;
506
+ let failed = 0;
507
+ for (const idx of selectedIndexes) {
508
+ const cred = openclawCreds[idx];
509
+ if (!cred)
510
+ continue;
511
+ try {
512
+ // Build delegation chain
513
+ const delegationChain = [
514
+ {
515
+ from: owner,
516
+ to: 'OpenClaw Instance',
517
+ grantedAt: new Date().toISOString(),
518
+ scope: ['*'],
519
+ },
520
+ ];
521
+ if (cred.metadata?.agentId) {
522
+ delegationChain.push({
523
+ from: 'OpenClaw Instance',
524
+ to: `Agent: ${cred.metadata.agentId}`,
525
+ grantedAt: new Date().toISOString(),
526
+ scope: ['*'],
527
+ });
528
+ }
529
+ await createPassport(vault, {
530
+ name: cred.name,
531
+ credentialType: cred.type,
532
+ credentialValue: cred.value,
533
+ visaType: cred.visaType,
534
+ issuingAuthority: 'OpenClaw (self-managed)',
535
+ platforms: cred.platforms,
536
+ scope: ['*'],
537
+ validFrom: new Date().toISOString(),
538
+ validUntil: cred.expiresAt?.toISOString(),
539
+ delegationChain,
540
+ humanOwner: owner,
541
+ tags: cred.tags,
542
+ notes: `Imported from OpenClaw: ${cred.filePath}`,
543
+ });
544
+ imported++;
545
+ }
546
+ catch (err) {
547
+ failed++;
548
+ }
549
+ }
550
+ importSpinner.stop();
551
+ console.log();
552
+ if (imported > 0) {
553
+ success(`Imported ${imported} credential(s) as passports.`);
554
+ }
555
+ if (failed > 0) {
556
+ warning(`Failed to import ${failed} credential(s).`);
557
+ }
558
+ console.log();
559
+ info('Run `idw list --tag openclaw` to see your OpenClaw passports.');
560
+ warning('Source files still contain plaintext credentials. Consider securely deleting them.');
561
+ return;
562
+ }
563
+ // Auto-discovery for --format env (no path given)
564
+ if (options.format === 'env' && !file) {
565
+ const searchDir = resolve('.');
566
+ console.log();
567
+ title('Environment File Auto-Discovery');
568
+ console.log();
569
+ const spinner = ora('Searching for .env files...').start();
570
+ const envFiles = await discoverEnvFiles(searchDir);
571
+ spinner.stop();
572
+ if (envFiles.length === 0) {
573
+ info(`No .env files found in ${searchDir}`);
574
+ return;
575
+ }
576
+ console.log(chalk.green(`Found ${envFiles.length} .env file(s):`));
577
+ for (const f of envFiles) {
578
+ console.log(` ${chalk.dim('•')} ${relative(searchDir, f) || basename(f)}`);
579
+ }
580
+ console.log();
581
+ // Parse all env files and combine credentials
582
+ const allDetected = [];
583
+ for (const envFile of envFiles) {
584
+ try {
585
+ const content = await readFile(envFile, 'utf-8');
586
+ const creds = parseEnvFile(content);
587
+ for (const c of creds) {
588
+ allDetected.push({ ...c, sourceFile: envFile });
589
+ }
590
+ }
591
+ catch {
592
+ warning(`Cannot read: ${relative(searchDir, envFile) || basename(envFile)}`);
593
+ }
594
+ }
595
+ if (allDetected.length === 0) {
596
+ info('No credentials detected in .env files.');
597
+ return;
598
+ }
599
+ info(`Found ${allDetected.length} potential credential(s)`);
600
+ console.log();
601
+ for (let i = 0; i < allDetected.length; i++) {
602
+ const d = allDetected[i];
603
+ if (!d)
604
+ continue;
605
+ const relFile = relative(searchDir, d.sourceFile) || basename(d.sourceFile);
606
+ console.log(` ${chalk.cyan(`[${i + 1}]`)} ${chalk.bold(d.name)}`);
607
+ console.log(` File: ${chalk.dim(relFile)}${d.line ? ` | Line: ${d.line}` : ''}`);
608
+ console.log(` Type: ${d.type} | Value: ${chalk.dim(maskCredential(d.value, 4))}`);
609
+ console.log();
610
+ }
611
+ // Confirm import
612
+ let selectedIndexes;
613
+ if (options.yes) {
614
+ selectedIndexes = allDetected.map((_, i) => i);
615
+ console.log(chalk.yellow(`Importing all ${allDetected.length} credentials (--yes flag)`));
616
+ }
617
+ else {
618
+ const inquirer = await import('inquirer');
619
+ const { selected } = await inquirer.default.prompt([
620
+ {
621
+ type: 'checkbox',
622
+ name: 'selected',
623
+ message: 'Select credentials to import:',
624
+ choices: allDetected.map((c, i) => ({
625
+ name: `${c.name} (${c.type}) — ${relative(searchDir, c.sourceFile) || basename(c.sourceFile)}`,
626
+ value: i,
627
+ checked: true,
628
+ })),
629
+ },
630
+ ]);
631
+ selectedIndexes = selected;
632
+ }
633
+ if (selectedIndexes.length === 0) {
634
+ console.log('No credentials selected for import.');
635
+ return;
636
+ }
637
+ // Get owner if not provided
638
+ let envOwner = options.owner;
639
+ if (!envOwner) {
640
+ const inquirer = await import('inquirer');
641
+ const { ownerEmail } = await inquirer.default.prompt([
642
+ {
643
+ type: 'input',
644
+ name: 'ownerEmail',
645
+ message: 'Human owner email:',
646
+ validate: (input) => input.includes('@') || 'Valid email required',
647
+ },
648
+ ]);
649
+ envOwner = ownerEmail;
650
+ }
651
+ // Unlock vault
652
+ const envPassphrase = await promptPassphrase();
653
+ const unlockSpinner = ora('Unlocking vault...').start();
654
+ let envVault;
655
+ try {
656
+ envVault = await unlockVault(envPassphrase, vaultPath);
657
+ unlockSpinner.succeed('Vault unlocked');
658
+ }
659
+ catch (err) {
660
+ unlockSpinner.fail('Failed to unlock vault');
661
+ error(err instanceof Error ? err.message : 'Invalid passphrase');
662
+ process.exit(1);
663
+ }
664
+ // Import selected credentials
665
+ const importSpinner = ora('Importing credentials...').start();
666
+ let envImported = 0;
667
+ let envFailed = 0;
668
+ for (const idx of selectedIndexes) {
669
+ const cred = allDetected[idx];
670
+ if (!cred)
671
+ continue;
672
+ try {
673
+ const platform = guessPlatform(cred.name, cred.value);
674
+ const sourceTag = basename(cred.sourceFile).replace(/\./g, '-');
675
+ await createPassport(envVault, {
676
+ name: options.autoName ? `${cred.name} (imported)` : cred.name,
677
+ credentialType: cred.type,
678
+ credentialValue: cred.value,
679
+ visaType: 'access',
680
+ platforms: [platform],
681
+ scope: [],
682
+ humanOwner: envOwner,
683
+ tags: ['imported', sourceTag],
684
+ notes: `Imported from ${cred.sourceFile}${cred.line ? ` (line ${cred.line})` : ''}`,
685
+ });
686
+ envImported++;
687
+ }
688
+ catch {
689
+ envFailed++;
690
+ }
691
+ }
692
+ importSpinner.stop();
693
+ console.log();
694
+ if (envImported > 0) {
695
+ success(`Imported ${envImported} credential(s) as passports.`);
696
+ }
697
+ if (envFailed > 0) {
698
+ warning(`Failed to import ${envFailed} credential(s).`);
699
+ }
700
+ info('Run `idw list` to see your passports.');
701
+ warning('Source files still contain plaintext credentials. Consider securely deleting them.');
702
+ return;
703
+ }
704
+ // Auto-discovery for --format json (no path given)
705
+ if (options.format === 'json' && !file) {
706
+ const searchDir = resolve('.');
707
+ console.log();
708
+ title('JSON Config Auto-Discovery');
709
+ console.log();
710
+ const spinner = ora('Searching for JSON config files...').start();
711
+ const jsonFiles = await discoverJsonFiles(searchDir);
712
+ spinner.stop();
713
+ if (jsonFiles.length === 0) {
714
+ info(`No JSON config files found in ${searchDir}`);
715
+ return;
716
+ }
717
+ console.log(chalk.green(`Found ${jsonFiles.length} JSON file(s):`));
718
+ for (const f of jsonFiles) {
719
+ console.log(` ${chalk.dim('•')} ${relative(searchDir, f) || basename(f)}`);
720
+ }
721
+ console.log();
722
+ // Parse all JSON files and combine credentials
723
+ const allDetected = [];
724
+ for (const jsonFile of jsonFiles) {
725
+ try {
726
+ const content = await readFile(jsonFile, 'utf-8');
727
+ const creds = parseJsonFile(content, basename(jsonFile));
728
+ for (const c of creds) {
729
+ allDetected.push({ ...c, sourceFile: jsonFile });
730
+ }
731
+ }
732
+ catch {
733
+ warning(`Cannot read: ${relative(searchDir, jsonFile) || basename(jsonFile)}`);
734
+ }
735
+ }
736
+ if (allDetected.length === 0) {
737
+ info('No credentials detected in JSON files.');
738
+ return;
739
+ }
740
+ info(`Found ${allDetected.length} potential credential(s)`);
741
+ console.log();
742
+ for (let i = 0; i < allDetected.length; i++) {
743
+ const d = allDetected[i];
744
+ if (!d)
745
+ continue;
746
+ const relFile = relative(searchDir, d.sourceFile) || basename(d.sourceFile);
747
+ console.log(` ${chalk.cyan(`[${i + 1}]`)} ${chalk.bold(d.name)}`);
748
+ console.log(` File: ${chalk.dim(relFile)}${d.line ? ` | Line: ${d.line}` : ''}`);
749
+ console.log(` Type: ${d.type} | Value: ${chalk.dim(maskCredential(d.value, 4))}`);
750
+ console.log();
751
+ }
752
+ // Confirm import
753
+ let selectedIndexes;
754
+ if (options.yes) {
755
+ selectedIndexes = allDetected.map((_, i) => i);
756
+ console.log(chalk.yellow(`Importing all ${allDetected.length} credentials (--yes flag)`));
757
+ }
758
+ else {
759
+ const inquirer = await import('inquirer');
760
+ const { selected } = await inquirer.default.prompt([
761
+ {
762
+ type: 'checkbox',
763
+ name: 'selected',
764
+ message: 'Select credentials to import:',
765
+ choices: allDetected.map((c, i) => ({
766
+ name: `${c.name} (${c.type}) — ${relative(searchDir, c.sourceFile) || basename(c.sourceFile)}`,
767
+ value: i,
768
+ checked: true,
769
+ })),
770
+ },
771
+ ]);
772
+ selectedIndexes = selected;
773
+ }
774
+ if (selectedIndexes.length === 0) {
775
+ console.log('No credentials selected for import.');
776
+ return;
777
+ }
778
+ // Get owner if not provided
779
+ let jsonOwner = options.owner;
780
+ if (!jsonOwner) {
781
+ const inquirer = await import('inquirer');
782
+ const { ownerEmail } = await inquirer.default.prompt([
783
+ {
784
+ type: 'input',
785
+ name: 'ownerEmail',
786
+ message: 'Human owner email:',
787
+ validate: (input) => input.includes('@') || 'Valid email required',
788
+ },
789
+ ]);
790
+ jsonOwner = ownerEmail;
791
+ }
792
+ // Unlock vault
793
+ const jsonPassphrase = await promptPassphrase();
794
+ const unlockSpinner = ora('Unlocking vault...').start();
795
+ let jsonVault;
796
+ try {
797
+ jsonVault = await unlockVault(jsonPassphrase, vaultPath);
798
+ unlockSpinner.succeed('Vault unlocked');
799
+ }
800
+ catch (err) {
801
+ unlockSpinner.fail('Failed to unlock vault');
802
+ error(err instanceof Error ? err.message : 'Invalid passphrase');
803
+ process.exit(1);
804
+ }
805
+ // Import selected credentials
806
+ const importSpinner = ora('Importing credentials...').start();
807
+ let jsonImported = 0;
808
+ let jsonFailed = 0;
809
+ for (const idx of selectedIndexes) {
810
+ const cred = allDetected[idx];
811
+ if (!cred)
812
+ continue;
813
+ try {
814
+ const platform = guessPlatform(cred.name, cred.value);
815
+ const sourceTag = basename(cred.sourceFile).replace(/\./g, '-');
816
+ await createPassport(jsonVault, {
817
+ name: options.autoName ? `${cred.name} (imported)` : cred.name,
818
+ credentialType: cred.type,
819
+ credentialValue: cred.value,
820
+ visaType: 'access',
821
+ platforms: [platform],
822
+ scope: [],
823
+ humanOwner: jsonOwner,
824
+ tags: ['imported', sourceTag],
825
+ notes: `Imported from ${cred.sourceFile}${cred.line ? ` (line ${cred.line})` : ''}`,
826
+ });
827
+ jsonImported++;
828
+ }
829
+ catch {
830
+ jsonFailed++;
831
+ }
832
+ }
833
+ importSpinner.stop();
834
+ console.log();
835
+ if (jsonImported > 0) {
836
+ success(`Imported ${jsonImported} credential(s) as passports.`);
837
+ }
838
+ if (jsonFailed > 0) {
839
+ warning(`Failed to import ${jsonFailed} credential(s).`);
840
+ }
841
+ info('Run `idw list` to see your passports.');
842
+ warning('Source files still contain plaintext credentials. Consider securely deleting them.');
843
+ return;
844
+ }
845
+ // Scan-based import (--all or --min-confidence)
846
+ if (options.all || options.minConfidence) {
847
+ const scanPath = resolve(file || '.');
848
+ const minConf = options.all ? 0 : parseFloat(options.minConfidence);
849
+ console.log();
850
+ title('Scan-Based Import');
851
+ info(`Scanning: ${scanPath}`);
852
+ if (!options.all) {
853
+ info(`Minimum confidence: ${minConf}`);
854
+ }
855
+ console.log();
856
+ const spinner = ora('Scanning for credentials...').start();
857
+ const toLine = (r) => r.line ?? 0;
858
+ const toCol = (r) => r.column ?? 0;
859
+ const detections = [];
860
+ let filesScanned = 0;
861
+ try {
862
+ const pathStats = await stat(scanPath);
863
+ if (pathStats.isFile()) {
864
+ filesScanned = 1;
865
+ const results = await scanFile(scanPath);
866
+ const fileName = basename(scanPath);
867
+ for (const r of results) {
868
+ if (r.confidence >= minConf) {
869
+ detections.push({
870
+ file: scanPath,
871
+ name: `${r.pattern || r.type} in ${fileName}`,
872
+ type: r.type,
873
+ value: r.value,
874
+ line: toLine(r),
875
+ column: toCol(r),
876
+ confidence: r.confidence,
877
+ pattern: r.pattern || String(r.type),
878
+ context: r.context,
879
+ });
880
+ }
881
+ }
882
+ }
883
+ else {
884
+ for await (const filePath of walkDirectory(scanPath)) {
885
+ filesScanned++;
886
+ if (filesScanned % 100 === 0) {
887
+ spinner.text = `Scanned ${filesScanned} files...`;
888
+ }
889
+ const results = await scanFile(filePath);
890
+ const fileName = basename(filePath);
891
+ for (const r of results) {
892
+ if (r.confidence >= minConf) {
893
+ detections.push({
894
+ file: filePath,
895
+ name: `${r.pattern || r.type} in ${fileName}`,
896
+ type: r.type,
897
+ value: r.value,
898
+ line: toLine(r),
899
+ column: toCol(r),
900
+ confidence: r.confidence,
901
+ pattern: r.pattern || String(r.type),
902
+ context: r.context,
903
+ });
904
+ }
905
+ }
906
+ }
907
+ }
908
+ }
909
+ catch (err) {
910
+ spinner.fail('Scan failed');
911
+ error(err instanceof Error ? err.message : 'Cannot access path');
912
+ process.exit(1);
913
+ }
914
+ spinner.succeed(`Scanned ${filesScanned} file(s)`);
915
+ if (detections.length === 0) {
916
+ info('No credentials detected matching criteria.');
917
+ return;
918
+ }
919
+ console.log();
920
+ warning(`Found ${detections.length} credential(s):`);
921
+ console.log();
922
+ // Display detections
923
+ for (let i = 0; i < detections.length; i++) {
924
+ const d = detections[i];
925
+ if (!d)
926
+ continue;
927
+ const confLabel = d.confidence >= 0.9 ? chalk.red('HIGH') :
928
+ d.confidence >= 0.7 ? chalk.yellow('MEDIUM') :
929
+ chalk.dim('LOW');
930
+ const relPath = relative(resolve(scanPath), d.file) || basename(d.file);
931
+ console.log(` ${chalk.cyan(`[${i + 1}]`)} ${chalk.bold(d.pattern)}`);
932
+ console.log(` File: ${chalk.dim(relPath)}`);
933
+ console.log(` Line: ${d.line} | Confidence: ${confLabel} (${d.confidence.toFixed(2)})`);
934
+ console.log(` Value: ${chalk.dim(maskCredential(d.value, 4))}`);
935
+ console.log();
936
+ }
937
+ // Confirm import
938
+ let scanSelectedIndexes;
939
+ if (options.yes) {
940
+ scanSelectedIndexes = detections.map((_, i) => i);
941
+ console.log(chalk.yellow(`Importing all ${detections.length} credentials (--yes flag)`));
942
+ }
943
+ else {
944
+ const inquirer = await import('inquirer');
945
+ const { selected } = await inquirer.default.prompt([
946
+ {
947
+ type: 'checkbox',
948
+ name: 'selected',
949
+ message: 'Select credentials to import:',
950
+ choices: detections.map((d, i) => ({
951
+ name: `${d.name} (confidence: ${d.confidence.toFixed(2)})`,
952
+ value: i,
953
+ checked: true,
954
+ })),
955
+ },
956
+ ]);
957
+ scanSelectedIndexes = selected;
958
+ }
959
+ if (scanSelectedIndexes.length === 0) {
960
+ console.log('No credentials selected for import.');
961
+ return;
962
+ }
963
+ // Get owner if not provided
964
+ let scanOwner = options.owner;
965
+ if (!scanOwner) {
966
+ const inquirer = await import('inquirer');
967
+ const { ownerEmail } = await inquirer.default.prompt([
968
+ {
969
+ type: 'input',
970
+ name: 'ownerEmail',
971
+ message: 'Human owner email:',
972
+ validate: (input) => input.includes('@') || 'Valid email required',
973
+ },
974
+ ]);
975
+ scanOwner = ownerEmail;
976
+ }
977
+ // Unlock vault
978
+ const scanPassphrase = await promptPassphrase();
979
+ const unlockSpinner = ora('Unlocking vault...').start();
980
+ let scanVault;
981
+ try {
982
+ scanVault = await unlockVault(scanPassphrase, vaultPath);
983
+ unlockSpinner.succeed('Vault unlocked');
984
+ }
985
+ catch (err) {
986
+ unlockSpinner.fail('Failed to unlock vault');
987
+ error(err instanceof Error ? err.message : 'Invalid passphrase');
988
+ process.exit(1);
989
+ }
990
+ // Import selected credentials
991
+ const importSpinner = ora('Importing credentials...').start();
992
+ let scanImported = 0;
993
+ let scanFailed = 0;
994
+ for (const idx of scanSelectedIndexes) {
995
+ const d = detections[idx];
996
+ if (!d)
997
+ continue;
998
+ try {
999
+ const platform = guessPlatform(d.pattern, d.value);
1000
+ const confLevel = d.confidence >= 0.9 ? 'high' : d.confidence >= 0.7 ? 'medium' : 'low';
1001
+ const fileTag = basename(d.file).replace(/\./g, '-');
1002
+ await createPassport(scanVault, {
1003
+ name: `${d.pattern} in ${basename(d.file)}`,
1004
+ credentialType: d.type,
1005
+ credentialValue: d.value,
1006
+ visaType: 'access',
1007
+ platforms: [platform],
1008
+ scope: [],
1009
+ humanOwner: scanOwner,
1010
+ tags: ['imported', 'scan', `confidence-${confLevel}`, fileTag],
1011
+ notes: `Detected in ${d.file} at line ${d.line}, col ${d.column}. Confidence: ${d.confidence.toFixed(2)}. Pattern: ${d.pattern}.`,
1012
+ });
1013
+ scanImported++;
1014
+ }
1015
+ catch {
1016
+ scanFailed++;
1017
+ }
1018
+ }
1019
+ importSpinner.stop();
1020
+ console.log();
1021
+ if (scanImported > 0) {
1022
+ success(`Imported ${scanImported} credential(s) as passports.`);
1023
+ }
1024
+ if (scanFailed > 0) {
1025
+ warning(`Failed to import ${scanFailed} credential(s).`);
1026
+ }
1027
+ info('Run `idw list --tag scan` to see your scanned passports.');
1028
+ warning('Source files still contain plaintext credentials. Consider securely deleting them.');
1029
+ return;
1030
+ }
1031
+ // File-based import (existing behavior)
1032
+ if (!file) {
1033
+ error('No file path provided. Try one of these:');
1034
+ console.log();
1035
+ console.log(` ${chalk.cyan('idw import .env')} Import a specific file`);
1036
+ console.log(` ${chalk.cyan('idw import --format env')} Auto-discover .env files in current directory`);
1037
+ console.log(` ${chalk.cyan('idw import --format json')} Auto-discover JSON config files`);
1038
+ console.log(` ${chalk.cyan('idw import --format openclaw')} Auto-scan ~/.openclaw credentials`);
1039
+ console.log(` ${chalk.cyan('idw import --all')} Scan current directory for all credentials`);
1040
+ console.log(` ${chalk.cyan('idw import --min-confidence 0.9')} Import only high-confidence detections`);
1041
+ console.log();
1042
+ process.exit(1);
1043
+ }
1044
+ // Read file
1045
+ let content;
1046
+ try {
1047
+ content = await readFile(file, 'utf-8');
1048
+ }
1049
+ catch {
1050
+ error(`Cannot read file: ${file}`);
1051
+ process.exit(1);
1052
+ }
1053
+ // Parse credentials based on file type
1054
+ const ext = extname(file).toLowerCase();
1055
+ const filename = basename(file);
1056
+ let detected;
1057
+ if (ext === '.env' || filename.startsWith('.env')) {
1058
+ detected = parseEnvFile(content);
1059
+ }
1060
+ else if (ext === '.json') {
1061
+ detected = parseJsonFile(content, filename);
1062
+ }
1063
+ else {
1064
+ // Generic detection
1065
+ const results = detectCredentials(content);
1066
+ detected = results.map(r => ({
1067
+ name: `${filename}:${r.line}`,
1068
+ type: r.type,
1069
+ value: r.value,
1070
+ line: r.line,
1071
+ }));
1072
+ }
1073
+ if (detected.length === 0) {
1074
+ info('No credentials detected in file.');
1075
+ return;
1076
+ }
1077
+ console.log();
1078
+ info(`Found ${detected.length} potential credential(s) in ${file}`);
1079
+ console.log();
1080
+ // Confirm import
1081
+ let selectedIndexes;
1082
+ if (options.yes) {
1083
+ selectedIndexes = detected.map((_, i) => i.toString());
1084
+ }
1085
+ else {
1086
+ selectedIndexes = await confirmImport(detected.map(c => ({
1087
+ name: c.name,
1088
+ type: c.type,
1089
+ preview: maskCredential(c.value, 4),
1090
+ })));
1091
+ }
1092
+ if (selectedIndexes.length === 0) {
1093
+ console.log('No credentials selected for import.');
1094
+ return;
1095
+ }
1096
+ // Get owner if not provided
1097
+ let owner = options.owner;
1098
+ if (!owner) {
1099
+ const inquirer = await import('inquirer');
1100
+ const { ownerEmail } = await inquirer.default.prompt([
1101
+ {
1102
+ type: 'input',
1103
+ name: 'ownerEmail',
1104
+ message: 'Human owner email:',
1105
+ validate: (input) => input.includes('@') || 'Valid email required',
1106
+ },
1107
+ ]);
1108
+ owner = ownerEmail;
1109
+ }
1110
+ // Unlock vault
1111
+ const passphrase = await promptPassphrase();
1112
+ const spinner = ora('Unlocking vault...').start();
1113
+ let vault;
1114
+ try {
1115
+ vault = await unlockVault(passphrase, vaultPath);
1116
+ spinner.succeed('Vault unlocked');
1117
+ }
1118
+ catch (err) {
1119
+ spinner.fail('Failed to unlock vault');
1120
+ error(err instanceof Error ? err.message : 'Invalid passphrase');
1121
+ process.exit(1);
1122
+ }
1123
+ // Import selected credentials
1124
+ spinner.start('Importing credentials...');
1125
+ let imported = 0;
1126
+ let failed = 0;
1127
+ for (const idx of selectedIndexes) {
1128
+ const cred = detected[parseInt(idx, 10)];
1129
+ if (!cred)
1130
+ continue;
1131
+ try {
1132
+ const platform = guessPlatform(cred.name, cred.value);
1133
+ const passportName = options.autoName
1134
+ ? `${cred.name} (imported)`
1135
+ : cred.name;
1136
+ await createPassport(vault, {
1137
+ name: passportName,
1138
+ credentialType: cred.type,
1139
+ credentialValue: cred.value,
1140
+ visaType: 'access',
1141
+ platforms: [platform],
1142
+ scope: [],
1143
+ humanOwner: owner,
1144
+ tags: ['imported', filename.replace(/\./g, '-')],
1145
+ notes: `Imported from ${file}${cred.line ? ` (line ${cred.line})` : ''}`,
1146
+ });
1147
+ imported++;
1148
+ }
1149
+ catch {
1150
+ failed++;
1151
+ }
1152
+ }
1153
+ spinner.stop();
1154
+ console.log();
1155
+ if (imported > 0) {
1156
+ success(`Imported ${imported} credential(s) as passports.`);
1157
+ }
1158
+ if (failed > 0) {
1159
+ warning(`Failed to import ${failed} credential(s).`);
1160
+ }
1161
+ info('Run `idw list` to see your passports.');
1162
+ warning('Source file still contains plaintext credentials. Consider securely deleting it.');
1163
+ });
1164
+ return command;
1165
+ }
1166
+ //# sourceMappingURL=import.js.map