@lifestreamdynamics/vault-cli 1.2.0 → 1.3.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 (50) hide show
  1. package/README.md +140 -30
  2. package/dist/client.d.ts +4 -0
  3. package/dist/client.js +12 -11
  4. package/dist/commands/admin.js +5 -5
  5. package/dist/commands/ai.js +17 -4
  6. package/dist/commands/auth.js +10 -105
  7. package/dist/commands/booking.d.ts +2 -0
  8. package/dist/commands/booking.js +739 -0
  9. package/dist/commands/calendar.js +725 -6
  10. package/dist/commands/completion.d.ts +5 -0
  11. package/dist/commands/completion.js +60 -0
  12. package/dist/commands/config.js +17 -16
  13. package/dist/commands/connectors.js +12 -1
  14. package/dist/commands/custom-domains.js +6 -1
  15. package/dist/commands/docs.js +12 -5
  16. package/dist/commands/hooks.js +6 -1
  17. package/dist/commands/links.js +9 -2
  18. package/dist/commands/mfa.js +1 -70
  19. package/dist/commands/plugins.d.ts +2 -0
  20. package/dist/commands/plugins.js +172 -0
  21. package/dist/commands/publish.js +13 -3
  22. package/dist/commands/saml.d.ts +2 -0
  23. package/dist/commands/saml.js +220 -0
  24. package/dist/commands/scim.d.ts +2 -0
  25. package/dist/commands/scim.js +238 -0
  26. package/dist/commands/shares.js +25 -3
  27. package/dist/commands/subscription.js +9 -2
  28. package/dist/commands/sync.js +3 -0
  29. package/dist/commands/teams.js +141 -8
  30. package/dist/commands/user.js +122 -9
  31. package/dist/commands/vaults.js +17 -8
  32. package/dist/commands/webhooks.js +6 -1
  33. package/dist/config.d.ts +2 -0
  34. package/dist/config.js +7 -3
  35. package/dist/index.js +20 -1
  36. package/dist/lib/credential-manager.js +32 -7
  37. package/dist/lib/migration.js +2 -2
  38. package/dist/lib/profiles.js +4 -4
  39. package/dist/sync/config.js +2 -2
  40. package/dist/sync/daemon-worker.js +13 -6
  41. package/dist/sync/daemon.js +2 -1
  42. package/dist/sync/remote-poller.js +7 -3
  43. package/dist/sync/state.js +2 -2
  44. package/dist/utils/confirm.d.ts +11 -0
  45. package/dist/utils/confirm.js +23 -0
  46. package/dist/utils/format.js +1 -1
  47. package/dist/utils/output.js +4 -1
  48. package/dist/utils/prompt.d.ts +29 -0
  49. package/dist/utils/prompt.js +146 -0
  50. package/package.json +2 -2
package/README.md CHANGED
@@ -19,8 +19,14 @@ A powerful command-line interface for Lifestream Vault - the multi-user Markdown
19
19
  - [Search Commands](#search-commands)
20
20
  - [Team Commands](#team-commands)
21
21
  - [Sharing & Publishing](#sharing--publishing)
22
+ - [Publish Vault Commands](#publish-vault-commands)
22
23
  - [Hooks & Webhooks](#hooks--webhooks)
23
24
  - [Links & Backlinks](#links--backlinks)
25
+ - [Calendar Commands](#calendar)
26
+ - [Booking Commands](#booking-commands)
27
+ - [AI Commands](#ai-commands)
28
+ - [Analytics Commands](#analytics-commands)
29
+ - [Custom Domain Commands](#custom-domain-commands)
24
30
  - [Admin Commands](#admin-commands)
25
31
  - [Sync & Watch Mode](#-sync--watch-mode)
26
32
  - [Configuration](#️-configuration)
@@ -135,7 +141,7 @@ lsvault sync watch <syncId>
135
141
  lsvault search "project notes"
136
142
 
137
143
  # Semantic search (AI-powered)
138
- lsvault search semantic "how to deploy the app"
144
+ lsvault search "how to deploy the app" --mode semantic
139
145
  ```
140
146
 
141
147
  ## 🔐 Authentication
@@ -202,16 +208,17 @@ lsvault auth logout
202
208
  | Command | Description |
203
209
  |---------|-------------|
204
210
  | `lsvault vaults list` | List all accessible vaults |
205
- | `lsvault vaults create` | Create a new vault |
211
+ | `lsvault vaults create <name>` | Create a new vault |
206
212
  | `lsvault vaults get <vaultId>` | Get vault details |
207
- | `lsvault vaults update <vaultId>` | Update vault settings |
208
- | `lsvault vaults delete <vaultId>` | Delete a vault |
209
213
  | `lsvault vaults tree <vaultId>` | Display vault directory tree |
214
+ | `lsvault vaults archive <vaultId>` | Archive a vault |
215
+ | `lsvault vaults unarchive <vaultId>` | Unarchive a vault |
216
+ | `lsvault vaults transfer <vaultId> <targetEmail>` | Transfer vault ownership |
210
217
 
211
218
  **Example:**
212
219
  ```bash
213
220
  # Create a vault
214
- lsvault vaults create --name "Work Notes" --description "Professional documentation"
221
+ lsvault vaults create "Work Notes" --description "Professional documentation"
215
222
 
216
223
  # Get vault details
217
224
  lsvault vaults get vault_abc123
@@ -223,9 +230,10 @@ lsvault vaults get vault_abc123
223
230
  |---------|-------------|
224
231
  | `lsvault docs list <vaultId>` | List all documents in a vault |
225
232
  | `lsvault docs get <vaultId> <path>` | Get document content |
226
- | `lsvault docs create <vaultId> <path>` | Create a new document |
227
- | `lsvault docs update <vaultId> <path>` | Update a document |
233
+ | `lsvault docs put <vaultId> <path>` | Create or update a document (reads from stdin) |
228
234
  | `lsvault docs delete <vaultId> <path>` | Delete a document |
235
+ | `lsvault docs move <vaultId> <source> <dest>` | Move or rename a document |
236
+ | `lsvault docs mkdir <vaultId> <path>` | Create a directory |
229
237
 
230
238
  **Example:**
231
239
  ```bash
@@ -233,16 +241,16 @@ lsvault vaults get vault_abc123
233
241
  lsvault docs list vault_abc123
234
242
 
235
243
  # Read a document (outputs to stdout)
236
- lsvault docs get vault_abc123 /notes/meeting.md
244
+ lsvault docs get vault_abc123 notes/meeting.md
237
245
 
238
- # Create a document from file
239
- lsvault docs create vault_abc123 /notes/new.md --file ~/draft.md
246
+ # Create or update a document from a local file (via stdin)
247
+ cat ~/draft.md | lsvault docs put vault_abc123 notes/new.md
240
248
 
241
249
  # Create with inline content
242
- lsvault docs create vault_abc123 /notes/quick.md --content "# Quick Note\n\nThis is a test."
250
+ echo "# Quick Note\n\nThis is a test." | lsvault docs put vault_abc123 notes/quick.md
243
251
 
244
- # Update a document
245
- lsvault docs update vault_abc123 /notes/meeting.md --file ~/updated.md
252
+ # Show document metadata
253
+ lsvault docs get vault_abc123 notes/meeting.md --meta
246
254
  ```
247
255
 
248
256
  ### Sync Commands
@@ -285,7 +293,8 @@ lsvault sync daemon start
285
293
  | Command | Description |
286
294
  |---------|-------------|
287
295
  | `lsvault search <query>` | Full-text search across all documents |
288
- | `lsvault search semantic <query>` | Semantic search using AI embeddings |
296
+ | `lsvault search <query> --mode semantic` | Semantic search using AI embeddings |
297
+ | `lsvault search <query> --mode hybrid` | Hybrid text + semantic search |
289
298
 
290
299
  **Example:**
291
300
  ```bash
@@ -293,7 +302,7 @@ lsvault sync daemon start
293
302
  lsvault search "project timeline" --vault vault_abc123
294
303
 
295
304
  # Semantic search
296
- lsvault search semantic "explain the deployment process"
305
+ lsvault search "explain the deployment process" --mode semantic
297
306
 
298
307
  # Search with filters
299
308
  lsvault search "meeting" --tags work,urgent --limit 10
@@ -346,6 +355,32 @@ lsvault shares create vault_abc123 /reports/Q1.md \
346
355
  lsvault publish create vault_abc123 /blog/post.md --slug my-first-post
347
356
  ```
348
357
 
358
+ ### Publish Vault Commands
359
+
360
+ Publish a whole vault as a multi-document public site (Pro tier).
361
+
362
+ | Command | Description |
363
+ |---------|-------------|
364
+ | `lsvault publish-vault list` | List your published vault sites |
365
+ | `lsvault publish-vault publish <vaultId>` | Publish a vault as a public site |
366
+ | `lsvault publish-vault update <vaultId>` | Update a published vault site |
367
+ | `lsvault publish-vault unpublish <vaultId>` | Unpublish a vault site |
368
+
369
+ **Example:**
370
+ ```bash
371
+ # Publish a vault as a public site
372
+ lsvault publish-vault publish vault_abc123 \
373
+ --slug my-docs \
374
+ --title "My Documentation" \
375
+ --enable-search
376
+
377
+ # List published vault sites
378
+ lsvault publish-vault list
379
+
380
+ # Unpublish
381
+ lsvault publish-vault unpublish vault_abc123
382
+ ```
383
+
349
384
  ### Hooks & Webhooks
350
385
 
351
386
  | Command | Description |
@@ -384,6 +419,81 @@ lsvault webhooks create \
384
419
  | `lsvault calendar update-event <vaultId> <eventId>` | Update a calendar event |
385
420
  | `lsvault calendar delete-event <vaultId> <eventId>` | Delete a calendar event |
386
421
 
422
+ ### Booking Commands
423
+
424
+ Manage bookable event slots and guest bookings. Slot CRUD requires Pro tier; team booking groups and waitlist require Business tier.
425
+
426
+ | Command | Description |
427
+ |---------|-------------|
428
+ | `lsvault booking slots list <vaultId>` | List all event slots for a vault |
429
+ | `lsvault booking slots create <vaultId>` | Create a new bookable event slot |
430
+ | `lsvault booking slots update <vaultId> <slotId>` | Update an event slot |
431
+ | `lsvault booking slots delete <vaultId> <slotId>` | Delete an event slot |
432
+ | `lsvault booking list <vaultId>` | List bookings for a vault |
433
+ | `lsvault booking confirm <vaultId> <bookingId>` | Confirm a pending booking |
434
+ | `lsvault booking cancel <vaultId> <bookingId>` | Cancel a booking |
435
+ | `lsvault booking reschedule <token> <newStartAt>` | Reschedule via guest token |
436
+ | `lsvault booking analytics <vaultId>` | View booking analytics (Business tier) |
437
+ | `lsvault booking templates list <vaultId>` | List event templates |
438
+ | `lsvault booking templates create <vaultId>` | Create an event template |
439
+ | `lsvault booking templates delete <vaultId> <templateId>` | Delete an event template |
440
+ | `lsvault booking groups list <teamId>` | List team booking groups |
441
+ | `lsvault booking groups create <teamId>` | Create a team booking group |
442
+ | `lsvault booking groups update <teamId> <groupId>` | Update a team booking group |
443
+ | `lsvault booking groups delete <teamId> <groupId>` | Delete a team booking group |
444
+ | `lsvault booking group-members list <teamId> <groupId>` | List booking group members |
445
+ | `lsvault booking group-members add <teamId> <groupId>` | Add member to booking group |
446
+ | `lsvault booking group-members remove <teamId> <groupId> <userId>` | Remove member from group |
447
+ | `lsvault booking waitlist list <vaultId> <slotId>` | List waitlist entries for a slot |
448
+
449
+ ### AI Commands
450
+
451
+ | Command | Description |
452
+ |---------|-------------|
453
+ | `lsvault ai sessions list` | List AI chat sessions |
454
+ | `lsvault ai sessions get <sessionId>` | Get session with messages |
455
+ | `lsvault ai sessions delete <sessionId>` | Delete an AI chat session |
456
+ | `lsvault ai chat <sessionId> <message>` | Send a message in a session |
457
+ | `lsvault ai summarize <vaultId> <docPath>` | Summarize a document with AI |
458
+
459
+ **Example:**
460
+ ```bash
461
+ # Chat with AI
462
+ lsvault ai chat session_abc123 "Summarize the key points"
463
+
464
+ # Summarize a document
465
+ lsvault ai summarize vault_abc123 notes/meeting.md
466
+ ```
467
+
468
+ ### Analytics Commands
469
+
470
+ | Command | Description |
471
+ |---------|-------------|
472
+ | `lsvault analytics published` | Summary of published document views |
473
+ | `lsvault analytics share <vaultId> <shareId>` | Analytics for a share link |
474
+ | `lsvault analytics doc <vaultId> <publishedDocId>` | Analytics for a published document |
475
+
476
+ ### Custom Domain Commands
477
+
478
+ | Command | Description |
479
+ |---------|-------------|
480
+ | `lsvault custom-domains list` | List custom domains |
481
+ | `lsvault custom-domains get <domainId>` | Get a custom domain |
482
+ | `lsvault custom-domains add <domain>` | Add a custom domain |
483
+ | `lsvault custom-domains update <domainId>` | Update a custom domain |
484
+ | `lsvault custom-domains remove <domainId>` | Remove a custom domain |
485
+ | `lsvault custom-domains verify <domainId>` | Verify domain via DNS TXT record |
486
+ | `lsvault custom-domains check <domainId>` | Check DNS configuration |
487
+
488
+ **Example:**
489
+ ```bash
490
+ # Add a custom domain
491
+ lsvault custom-domains add docs.example.com
492
+
493
+ # Verify after adding DNS TXT record
494
+ lsvault custom-domains verify domain_abc123
495
+ ```
496
+
387
497
  ### Links & Backlinks
388
498
 
389
499
  | Command | Description |
@@ -416,10 +526,12 @@ lsvault links broken vault_abc123
416
526
  |---------|-------------|
417
527
  | `lsvault admin users list` | List all users |
418
528
  | `lsvault admin users get <userId>` | Get user details |
419
- | `lsvault admin users update <userId>` | Update user settings |
420
- | `lsvault admin users delete <userId>` | Delete a user |
529
+ | `lsvault admin users update <userId>` | Update user (role, active status) |
421
530
  | `lsvault admin stats` | View system statistics |
422
- | `lsvault audit logs` | View audit logs |
531
+ | `lsvault admin stats timeseries` | Show timeseries data for a metric |
532
+ | `lsvault admin activity` | Show recent system-wide activity |
533
+ | `lsvault admin subscriptions` | Show subscription tier distribution |
534
+ | `lsvault admin health` | Check system health (DB, Redis, uptime) |
423
535
 
424
536
  **Example:**
425
537
  ```bash
@@ -429,8 +541,8 @@ lsvault admin users list
429
541
  # View system stats
430
542
  lsvault admin stats
431
543
 
432
- # View audit logs
433
- lsvault audit logs --limit 100 --filter-action document.created
544
+ # View system health
545
+ lsvault admin health
434
546
  ```
435
547
 
436
548
  ## 🔄 Sync & Watch Mode
@@ -562,8 +674,6 @@ Sync configurations are stored per vault in `~/.lsvault/sync/`:
562
674
  |----------|-------------|---------|
563
675
  | `LSVAULT_API_URL` | API server base URL | `https://vault.lifestreamdynamics.com` |
564
676
  | `LSVAULT_API_KEY` | API key for authentication | - |
565
- | `LSVAULT_CONFIG_DIR` | Configuration directory | `~/.lsvault` |
566
- | `LSVAULT_PROFILE` | Active configuration profile | `default` |
567
677
 
568
678
  **Example:**
569
679
  ```bash
@@ -646,7 +756,7 @@ lsvault sync watch sync_xyz789
646
756
 
647
757
  ```bash
648
758
  # Search for documents
649
- lsvault search "quarterly report" --vault vault_abc123 --json
759
+ lsvault search "quarterly report" --vault vault_abc123 -o json
650
760
 
651
761
  # Create a share link for the found document
652
762
  lsvault shares create vault_abc123 /reports/Q4-2025.md \
@@ -664,8 +774,8 @@ lsvault publish create vault_abc123 /blog/announcement.md \
664
774
  # Create a team
665
775
  lsvault teams create --name "Product Team" --description "Product docs"
666
776
 
667
- # Create a shared vault
668
- lsvault vaults create --name "Product Docs" --team team_abc123
777
+ # Create a vault
778
+ lsvault vaults create "Product Docs" --description "Product documentation"
669
779
 
670
780
  # Invite team members
671
781
  lsvault teams invite team_abc123 --email pm@example.com --role admin
@@ -689,7 +799,7 @@ lsvault keys create \
689
799
 
690
800
  # Use API key in scripts
691
801
  export LSVAULT_API_KEY=lsv_k_generated_key
692
- lsvault vaults list --json | jq '.[] | .name'
802
+ lsvault vaults list -o json | jq '.[] | .name'
693
803
  ```
694
804
 
695
805
  ## 🐛 Troubleshooting
@@ -762,11 +872,11 @@ lsvault auth migrate
762
872
 
763
873
  **Problem:** Need machine-readable output
764
874
 
765
- **Solution:** Use `--json` or `--quiet` flags:
875
+ **Solution:** Use `-o json` (or `--output json`) or `--quiet` flags:
766
876
  ```bash
767
- lsvault vaults list --json
768
- lsvault search "query" --json | jq '.[] | .path'
769
- lsvault docs get vault_abc123 /path.md --quiet > output.md
877
+ lsvault vaults list -o json
878
+ lsvault search "query" -o json | jq '.[] | .path'
879
+ lsvault docs get vault_abc123 path.md --quiet > output.md
770
880
  ```
771
881
 
772
882
  ## 🔗 Related Packages
package/dist/client.d.ts CHANGED
@@ -3,10 +3,14 @@ import { LifestreamVaultClient } from '@lifestreamdynamics/vault-sdk';
3
3
  * Create an SDK client from CLI configuration.
4
4
  * Supports both API key and JWT (access + refresh token) authentication.
5
5
  * When using JWT tokens, auto-refresh is enabled and new tokens are persisted.
6
+ *
7
+ * @throws {Error} If no credentials are configured.
6
8
  */
7
9
  export declare function getClient(): LifestreamVaultClient;
8
10
  /**
9
11
  * Create an SDK client from async config resolution (secure credential manager).
10
12
  * This resolves credentials from keychain/encrypted storage.
13
+ *
14
+ * @throws {Error} If no credentials are configured.
11
15
  */
12
16
  export declare function getClientAsync(): Promise<LifestreamVaultClient>;
package/dist/client.js CHANGED
@@ -1,10 +1,11 @@
1
1
  import { LifestreamVaultClient } from '@lifestreamdynamics/vault-sdk';
2
2
  import { loadConfig, loadConfigAsync, getCredentialManager } from './config.js';
3
- import chalk from 'chalk';
4
3
  /**
5
4
  * Create an SDK client from CLI configuration.
6
5
  * Supports both API key and JWT (access + refresh token) authentication.
7
6
  * When using JWT tokens, auto-refresh is enabled and new tokens are persisted.
7
+ *
8
+ * @throws {Error} If no credentials are configured.
8
9
  */
9
10
  export function getClient() {
10
11
  const config = loadConfig();
@@ -35,15 +36,16 @@ export function getClient() {
35
36
  apiKey: config.apiKey,
36
37
  });
37
38
  }
38
- console.error(chalk.red('No credentials configured.'));
39
- console.error('Run: lsvault auth login --api-key <key>');
40
- console.error(' or: lsvault auth login --email <email>');
41
- console.error('Or set LSVAULT_API_KEY environment variable');
42
- process.exit(1);
39
+ throw new Error('No credentials configured.\n' +
40
+ 'Run: lsvault auth login --api-key <key>\n' +
41
+ ' or: lsvault auth login --email <email>\n' +
42
+ 'Or set LSVAULT_API_KEY environment variable');
43
43
  }
44
44
  /**
45
45
  * Create an SDK client from async config resolution (secure credential manager).
46
46
  * This resolves credentials from keychain/encrypted storage.
47
+ *
48
+ * @throws {Error} If no credentials are configured.
47
49
  */
48
50
  export async function getClientAsync() {
49
51
  const config = await loadConfigAsync();
@@ -71,9 +73,8 @@ export async function getClientAsync() {
71
73
  apiKey: config.apiKey,
72
74
  });
73
75
  }
74
- console.error(chalk.red('No credentials configured.'));
75
- console.error('Run: lsvault auth login --api-key <key>');
76
- console.error(' or: lsvault auth login --email <email>');
77
- console.error('Or set LSVAULT_API_KEY environment variable');
78
- process.exit(1);
76
+ throw new Error('No credentials configured.\n' +
77
+ 'Run: lsvault auth login --api-key <key>\n' +
78
+ ' or: lsvault auth login --email <email>\n' +
79
+ 'Or set LSVAULT_API_KEY environment variable');
79
80
  }
@@ -93,7 +93,7 @@ export function registerAdminCommands(program) {
93
93
  out.list(result.users.map(u => ({
94
94
  email: u.email,
95
95
  id: u.id,
96
- name: u.name || '',
96
+ displayName: u.displayName || '',
97
97
  role: u.role,
98
98
  subscriptionTier: u.subscriptionTier,
99
99
  isActive: u.isActive,
@@ -101,15 +101,15 @@ export function registerAdminCommands(program) {
101
101
  emptyMessage: 'No users found.',
102
102
  columns: [
103
103
  { key: 'email', header: 'Email' },
104
- { key: 'name', header: 'Name' },
104
+ { key: 'displayName', header: 'Name' },
105
105
  { key: 'role', header: 'Role' },
106
106
  { key: 'subscriptionTier', header: 'Tier' },
107
107
  { key: 'isActive', header: 'Active' },
108
108
  ],
109
109
  textFn: (u) => {
110
110
  const active = u.isActive ? chalk.green('active') : chalk.red('inactive');
111
- const name = u.name || chalk.dim('no name');
112
- return ` ${chalk.cyan(String(u.email))} ${chalk.dim(`(${String(u.id)})`)} -- ${name} -- ${chalk.magenta(String(u.role))} -- ${String(u.subscriptionTier)} -- ${active}`;
111
+ const displayName = u.displayName || chalk.dim('no name');
112
+ return ` ${chalk.cyan(String(u.email))} ${chalk.dim(`(${String(u.id)})`)} -- ${displayName} -- ${chalk.magenta(String(u.role))} -- ${String(u.subscriptionTier)} -- ${active}`;
113
113
  },
114
114
  });
115
115
  }
@@ -131,7 +131,7 @@ export function registerAdminCommands(program) {
131
131
  out.record({
132
132
  email: user.email,
133
133
  id: user.id,
134
- name: user.name,
134
+ displayName: user.displayName,
135
135
  role: user.role,
136
136
  isActive: user.isActive,
137
137
  subscriptionTier: user.subscriptionTier,
@@ -48,7 +48,7 @@ export function registerAiCommands(program) {
48
48
  process.stdout.write(`Title: ${result.session.title ?? 'Untitled'}\n\n`);
49
49
  for (const msg of result.messages ?? []) {
50
50
  const role = msg.role === 'assistant' ? chalk.green('AI') : chalk.blue('You');
51
- process.stdout.write(`${role}: ${String(msg.content ?? '')}\n\n`);
51
+ process.stdout.write(`${role}: ${msg.content}\n\n`);
52
52
  }
53
53
  }
54
54
  }
@@ -58,10 +58,15 @@ export function registerAiCommands(program) {
58
58
  });
59
59
  addGlobalFlags(sessions.command('delete')
60
60
  .description('Delete an AI chat session')
61
- .argument('<sessionId>', 'Session ID'))
61
+ .argument('<sessionId>', 'Session ID')
62
+ .option('-y, --yes', 'Skip confirmation prompt'))
62
63
  .action(async (sessionId, _opts) => {
63
64
  const flags = resolveFlags(_opts);
64
65
  const out = createOutput(flags);
66
+ if (!_opts.yes) {
67
+ out.status(chalk.yellow(`Pass --yes to delete AI session ${sessionId}.`));
68
+ return;
69
+ }
65
70
  out.startSpinner('Deleting AI session...');
66
71
  try {
67
72
  const client = await getClientAsync();
@@ -84,7 +89,11 @@ export function registerAiCommands(program) {
84
89
  const client = await getClientAsync();
85
90
  const response = await client.ai.chat({ message, sessionId });
86
91
  out.stopSpinner();
87
- process.stdout.write(String(response.content ?? JSON.stringify(response)) + '\n');
92
+ // response.message is typed as { role: string; content: string; sources: string[] }
93
+ process.stdout.write(response.message.content + '\n');
94
+ if (response.message.sources.length > 0) {
95
+ process.stdout.write(chalk.dim(`Sources: ${response.message.sources.join(', ')}`) + '\n');
96
+ }
88
97
  }
89
98
  catch (err) {
90
99
  handleError(out, err, 'Failed to send AI message');
@@ -102,7 +111,11 @@ export function registerAiCommands(program) {
102
111
  const client = await getClientAsync();
103
112
  const summary = await client.ai.summarize(vaultId, docPath);
104
113
  out.stopSpinner();
105
- process.stdout.write(String(summary.summary ?? JSON.stringify(summary)) + '\n');
114
+ // summary is typed as { summary: string; keyTopics: string[]; tokensUsed: number }
115
+ process.stdout.write(summary.summary + '\n');
116
+ if (summary.keyTopics.length > 0) {
117
+ process.stdout.write(chalk.dim(`Key topics: ${summary.keyTopics.join(', ')}`) + '\n');
118
+ }
106
119
  }
107
120
  catch (err) {
108
121
  handleError(out, err, 'Failed to summarize document');
@@ -4,6 +4,7 @@ import { LifestreamVaultClient } from '@lifestreamdynamics/vault-sdk';
4
4
  import { loadConfigAsync, getCredentialManager } from '../config.js';
5
5
  import { getClientAsync } from '../client.js';
6
6
  import { migrateCredentials, hasPlaintextCredentials, checkAndPromptMigration } from '../lib/migration.js';
7
+ import { promptPassword, promptMfaCode } from '../utils/prompt.js';
7
8
  export function registerAuthCommands(program) {
8
9
  const auth = program.command('auth').description('Authentication and credential management');
9
10
  auth.command('login')
@@ -38,6 +39,7 @@ EXAMPLES
38
39
  const password = opts.password ?? await promptPassword();
39
40
  if (!password) {
40
41
  console.error(chalk.red('Password is required for email login.'));
42
+ process.exitCode = 1;
41
43
  return;
42
44
  }
43
45
  const config = await loadConfigAsync();
@@ -64,7 +66,7 @@ EXAMPLES
64
66
  refreshToken: refreshToken ?? undefined,
65
67
  });
66
68
  spinner.succeed(`Logged in as ${chalk.cyan(tokens.user.email)}`);
67
- console.log(` Name: ${tokens.user.name || chalk.dim('not set')}`);
69
+ console.log(` Name: ${tokens.user.displayName || chalk.dim('not set')}`);
68
70
  console.log(` Role: ${tokens.user.role}`);
69
71
  if (!refreshToken) {
70
72
  console.log(chalk.yellow(' Note: No refresh token received. Session will expire.'));
@@ -73,6 +75,7 @@ EXAMPLES
73
75
  catch (err) {
74
76
  spinner.fail('Login failed');
75
77
  console.error(err instanceof Error ? err.message : String(err));
78
+ process.exitCode = 1;
76
79
  }
77
80
  return;
78
81
  }
@@ -87,6 +90,7 @@ EXAMPLES
87
90
  catch (err) {
88
91
  spinner.fail('Failed to save API key to secure storage');
89
92
  console.error(err instanceof Error ? err.message : String(err));
93
+ process.exitCode = 1;
90
94
  }
91
95
  return;
92
96
  }
@@ -102,6 +106,7 @@ EXAMPLES
102
106
  const config = await loadConfigAsync();
103
107
  if (!config.refreshToken) {
104
108
  console.error(chalk.red('No refresh token stored. Login first with --email.'));
109
+ process.exitCode = 1;
105
110
  return;
106
111
  }
107
112
  const spinner = ora('Refreshing access token...').start();
@@ -126,6 +131,7 @@ EXAMPLES
126
131
  spinner.fail('Token refresh failed');
127
132
  console.error(err instanceof Error ? err.message : String(err));
128
133
  console.log(chalk.dim('You may need to log in again: lsvault auth login --email <email>'));
134
+ process.exitCode = 1;
129
135
  }
130
136
  });
131
137
  auth.command('logout')
@@ -198,119 +204,18 @@ EXAMPLES
198
204
  const user = await client.user.me();
199
205
  spinner.stop();
200
206
  console.log(`User: ${chalk.cyan(user.email)}`);
201
- console.log(`Name: ${user.name || chalk.dim('not set')}`);
207
+ console.log(`Name: ${user.displayName || chalk.dim('not set')}`);
202
208
  console.log(`Role: ${user.role}`);
203
209
  console.log(`Plan: ${chalk.green(user.subscriptionTier)}`);
204
210
  }
205
211
  catch (err) {
206
212
  spinner.fail('Could not fetch user info');
207
- console.error(err instanceof Error ? err.message : err);
213
+ console.error(err instanceof Error ? err.message : String(err));
214
+ process.exitCode = 1;
208
215
  }
209
216
  }
210
217
  });
211
218
  }
212
- /**
213
- * Prompt for a password from stdin (non-echoing).
214
- * Returns the password or null if stdin is not a TTY.
215
- */
216
- async function promptPassword() {
217
- // In non-interactive mode, cannot prompt
218
- if (!process.stdin.isTTY) {
219
- return null;
220
- }
221
- const readline = await import('node:readline');
222
- return new Promise((resolve) => {
223
- const rl = readline.createInterface({
224
- input: process.stdin,
225
- output: process.stderr,
226
- terminal: true,
227
- });
228
- // Disable echoing
229
- process.stderr.write('Password: ');
230
- process.stdin.setRawMode?.(true);
231
- let password = '';
232
- const onData = (chunk) => {
233
- const char = chunk.toString('utf-8');
234
- if (char === '\n' || char === '\r' || char === '\u0004') {
235
- process.stderr.write('\n');
236
- process.stdin.setRawMode?.(false);
237
- process.stdin.removeListener('data', onData);
238
- rl.close();
239
- resolve(password);
240
- }
241
- else if (char === '\u0003') {
242
- // Ctrl+C
243
- process.stderr.write('\n');
244
- process.stdin.setRawMode?.(false);
245
- process.stdin.removeListener('data', onData);
246
- rl.close();
247
- resolve(null);
248
- }
249
- else if (char === '\u007F' || char === '\b') {
250
- // Backspace
251
- if (password.length > 0) {
252
- password = password.slice(0, -1);
253
- }
254
- }
255
- else {
256
- password += char;
257
- }
258
- };
259
- process.stdin.on('data', onData);
260
- process.stdin.resume();
261
- });
262
- }
263
- /**
264
- * Prompt for an MFA code from stdin (6 digits, non-echoing).
265
- * Returns the code or null if stdin is not a TTY.
266
- */
267
- async function promptMfaCode() {
268
- // In non-interactive mode, cannot prompt
269
- if (!process.stdin.isTTY) {
270
- return null;
271
- }
272
- const readline = await import('node:readline');
273
- return new Promise((resolve) => {
274
- const rl = readline.createInterface({
275
- input: process.stdin,
276
- output: process.stderr,
277
- terminal: true,
278
- });
279
- // Disable echoing
280
- process.stderr.write('MFA code: ');
281
- process.stdin.setRawMode?.(true);
282
- let code = '';
283
- const onData = (chunk) => {
284
- const char = chunk.toString('utf-8');
285
- if (char === '\n' || char === '\r' || char === '\u0004') {
286
- process.stderr.write('\n');
287
- process.stdin.setRawMode?.(false);
288
- process.stdin.removeListener('data', onData);
289
- rl.close();
290
- resolve(code);
291
- }
292
- else if (char === '\u0003') {
293
- // Ctrl+C
294
- process.stderr.write('\n');
295
- process.stdin.setRawMode?.(false);
296
- process.stdin.removeListener('data', onData);
297
- rl.close();
298
- resolve(null);
299
- }
300
- else if (char === '\u007F' || char === '\b') {
301
- // Backspace
302
- if (code.length > 0) {
303
- code = code.slice(0, -1);
304
- }
305
- }
306
- else {
307
- code += char;
308
- }
309
- };
310
- process.stdin.on('data', onData);
311
- process.stdin.resume();
312
- });
313
- }
314
219
  function formatMethod(method) {
315
220
  switch (method) {
316
221
  case 'keychain': return chalk.green('OS Keychain');
@@ -0,0 +1,2 @@
1
+ import type { Command } from 'commander';
2
+ export declare function registerBookingCommands(program: Command): void;