@lifestreamdynamics/vault-cli 1.1.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 (56) 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.d.ts +2 -0
  6. package/dist/commands/ai.js +124 -0
  7. package/dist/commands/analytics.d.ts +2 -0
  8. package/dist/commands/analytics.js +84 -0
  9. package/dist/commands/auth.js +10 -105
  10. package/dist/commands/booking.d.ts +2 -0
  11. package/dist/commands/booking.js +739 -0
  12. package/dist/commands/calendar.js +778 -6
  13. package/dist/commands/completion.d.ts +5 -0
  14. package/dist/commands/completion.js +60 -0
  15. package/dist/commands/config.js +17 -16
  16. package/dist/commands/connectors.js +12 -1
  17. package/dist/commands/custom-domains.d.ts +2 -0
  18. package/dist/commands/custom-domains.js +154 -0
  19. package/dist/commands/docs.js +152 -5
  20. package/dist/commands/hooks.js +6 -1
  21. package/dist/commands/links.js +9 -2
  22. package/dist/commands/mfa.js +1 -70
  23. package/dist/commands/plugins.d.ts +2 -0
  24. package/dist/commands/plugins.js +172 -0
  25. package/dist/commands/publish-vault.d.ts +2 -0
  26. package/dist/commands/publish-vault.js +117 -0
  27. package/dist/commands/publish.js +63 -2
  28. package/dist/commands/saml.d.ts +2 -0
  29. package/dist/commands/saml.js +220 -0
  30. package/dist/commands/scim.d.ts +2 -0
  31. package/dist/commands/scim.js +238 -0
  32. package/dist/commands/shares.js +25 -3
  33. package/dist/commands/subscription.js +9 -2
  34. package/dist/commands/sync.js +3 -0
  35. package/dist/commands/teams.js +233 -4
  36. package/dist/commands/user.js +444 -0
  37. package/dist/commands/vaults.js +240 -8
  38. package/dist/commands/webhooks.js +6 -1
  39. package/dist/config.d.ts +2 -0
  40. package/dist/config.js +7 -3
  41. package/dist/index.js +28 -1
  42. package/dist/lib/credential-manager.js +32 -7
  43. package/dist/lib/migration.js +2 -2
  44. package/dist/lib/profiles.js +4 -4
  45. package/dist/sync/config.js +2 -2
  46. package/dist/sync/daemon-worker.js +13 -6
  47. package/dist/sync/daemon.js +2 -1
  48. package/dist/sync/remote-poller.js +7 -3
  49. package/dist/sync/state.js +2 -2
  50. package/dist/utils/confirm.d.ts +11 -0
  51. package/dist/utils/confirm.js +23 -0
  52. package/dist/utils/format.js +1 -1
  53. package/dist/utils/output.js +4 -1
  54. package/dist/utils/prompt.d.ts +29 -0
  55. package/dist/utils/prompt.js +146 -0
  56. 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,
@@ -0,0 +1,2 @@
1
+ import type { Command } from 'commander';
2
+ export declare function registerAiCommands(program: Command): void;
@@ -0,0 +1,124 @@
1
+ import chalk from 'chalk';
2
+ import { getClientAsync } from '../client.js';
3
+ import { addGlobalFlags, resolveFlags } from '../utils/flags.js';
4
+ import { createOutput, handleError } from '../utils/output.js';
5
+ export function registerAiCommands(program) {
6
+ const ai = program.command('ai').description('AI chat and document summarization');
7
+ const sessions = ai.command('sessions').description('AI chat session management');
8
+ addGlobalFlags(sessions.command('list')
9
+ .description('List AI chat sessions'))
10
+ .action(async (_opts) => {
11
+ const flags = resolveFlags(_opts);
12
+ const out = createOutput(flags);
13
+ out.startSpinner('Fetching AI sessions...');
14
+ try {
15
+ const client = await getClientAsync();
16
+ const list = await client.ai.listSessions();
17
+ out.stopSpinner();
18
+ out.list(list.map(s => ({ id: s.id, title: s.title ?? 'Untitled', createdAt: s.createdAt })), {
19
+ emptyMessage: 'No AI sessions found.',
20
+ columns: [
21
+ { key: 'id', header: 'ID' },
22
+ { key: 'title', header: 'Title' },
23
+ { key: 'createdAt', header: 'Created' },
24
+ ],
25
+ textFn: (s) => `${chalk.cyan(String(s.id))} — ${String(s.title)}`,
26
+ });
27
+ }
28
+ catch (err) {
29
+ handleError(out, err, 'Failed to fetch AI sessions');
30
+ }
31
+ });
32
+ addGlobalFlags(sessions.command('get')
33
+ .description('Get an AI chat session with messages')
34
+ .argument('<sessionId>', 'Session ID'))
35
+ .action(async (sessionId, _opts) => {
36
+ const flags = resolveFlags(_opts);
37
+ const out = createOutput(flags);
38
+ out.startSpinner('Fetching AI session...');
39
+ try {
40
+ const client = await getClientAsync();
41
+ const result = await client.ai.getSession(sessionId);
42
+ out.stopSpinner();
43
+ if (flags.output === 'json') {
44
+ out.raw(JSON.stringify(result, null, 2) + '\n');
45
+ }
46
+ else {
47
+ process.stdout.write(`Session: ${chalk.cyan(result.session.id)}\n`);
48
+ process.stdout.write(`Title: ${result.session.title ?? 'Untitled'}\n\n`);
49
+ for (const msg of result.messages ?? []) {
50
+ const role = msg.role === 'assistant' ? chalk.green('AI') : chalk.blue('You');
51
+ process.stdout.write(`${role}: ${msg.content}\n\n`);
52
+ }
53
+ }
54
+ }
55
+ catch (err) {
56
+ handleError(out, err, 'Failed to fetch AI session');
57
+ }
58
+ });
59
+ addGlobalFlags(sessions.command('delete')
60
+ .description('Delete an AI chat session')
61
+ .argument('<sessionId>', 'Session ID')
62
+ .option('-y, --yes', 'Skip confirmation prompt'))
63
+ .action(async (sessionId, _opts) => {
64
+ const flags = resolveFlags(_opts);
65
+ const out = createOutput(flags);
66
+ if (!_opts.yes) {
67
+ out.status(chalk.yellow(`Pass --yes to delete AI session ${sessionId}.`));
68
+ return;
69
+ }
70
+ out.startSpinner('Deleting AI session...');
71
+ try {
72
+ const client = await getClientAsync();
73
+ await client.ai.deleteSession(sessionId);
74
+ out.success('Session deleted', { id: sessionId });
75
+ }
76
+ catch (err) {
77
+ handleError(out, err, 'Failed to delete AI session');
78
+ }
79
+ });
80
+ addGlobalFlags(ai.command('chat')
81
+ .description('Send a message in an AI chat session')
82
+ .argument('<sessionId>', 'Session ID')
83
+ .argument('<message>', 'Message to send'))
84
+ .action(async (sessionId, message, _opts) => {
85
+ const flags = resolveFlags(_opts);
86
+ const out = createOutput(flags);
87
+ out.startSpinner('Sending message...');
88
+ try {
89
+ const client = await getClientAsync();
90
+ const response = await client.ai.chat({ message, sessionId });
91
+ out.stopSpinner();
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
+ }
97
+ }
98
+ catch (err) {
99
+ handleError(out, err, 'Failed to send AI message');
100
+ }
101
+ });
102
+ addGlobalFlags(ai.command('summarize')
103
+ .description('Summarize a document with AI')
104
+ .argument('<vaultId>', 'Vault ID')
105
+ .argument('<docPath>', 'Document path'))
106
+ .action(async (vaultId, docPath, _opts) => {
107
+ const flags = resolveFlags(_opts);
108
+ const out = createOutput(flags);
109
+ out.startSpinner('Summarizing document...');
110
+ try {
111
+ const client = await getClientAsync();
112
+ const summary = await client.ai.summarize(vaultId, docPath);
113
+ out.stopSpinner();
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
+ }
119
+ }
120
+ catch (err) {
121
+ handleError(out, err, 'Failed to summarize document');
122
+ }
123
+ });
124
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from 'commander';
2
+ export declare function registerAnalyticsCommands(program: Command): void;
@@ -0,0 +1,84 @@
1
+ import chalk from 'chalk';
2
+ import { getClientAsync } from '../client.js';
3
+ import { addGlobalFlags, resolveFlags } from '../utils/flags.js';
4
+ import { createOutput, handleError } from '../utils/output.js';
5
+ export function registerAnalyticsCommands(program) {
6
+ const analytics = program.command('analytics').description('Analytics for published documents and share links');
7
+ addGlobalFlags(analytics.command('published')
8
+ .description('Summary of published document views'))
9
+ .action(async (_opts) => {
10
+ const flags = resolveFlags(_opts);
11
+ const out = createOutput(flags);
12
+ out.startSpinner('Fetching analytics...');
13
+ try {
14
+ const client = await getClientAsync();
15
+ const summary = await client.analytics.getPublishedSummary();
16
+ out.stopSpinner();
17
+ if (flags.output === 'json') {
18
+ out.raw(JSON.stringify(summary, null, 2) + '\n');
19
+ }
20
+ else {
21
+ process.stdout.write(`Total published: ${summary.totalPublished}, Total views: ${summary.totalViews}\n\n`);
22
+ out.list(summary.documents.map(d => ({ slug: d.slug, title: d.title ?? '', viewCount: d.viewCount, publishedAt: d.publishedAt })), {
23
+ emptyMessage: 'No published documents.',
24
+ columns: [
25
+ { key: 'slug', header: 'Slug' },
26
+ { key: 'title', header: 'Title' },
27
+ { key: 'viewCount', header: 'Views' },
28
+ { key: 'publishedAt', header: 'Published' },
29
+ ],
30
+ textFn: (d) => `${chalk.cyan(String(d.slug))} — ${String(d.viewCount)} views`,
31
+ });
32
+ }
33
+ }
34
+ catch (err) {
35
+ handleError(out, err, 'Failed to fetch analytics');
36
+ }
37
+ });
38
+ addGlobalFlags(analytics.command('share')
39
+ .description('Analytics for a share link')
40
+ .argument('<vaultId>', 'Vault ID')
41
+ .argument('<shareId>', 'Share ID'))
42
+ .action(async (vaultId, shareId, _opts) => {
43
+ const flags = resolveFlags(_opts);
44
+ const out = createOutput(flags);
45
+ out.startSpinner('Fetching share analytics...');
46
+ try {
47
+ const client = await getClientAsync();
48
+ const data = await client.analytics.getShareAnalytics(vaultId, shareId);
49
+ out.stopSpinner();
50
+ out.record({
51
+ shareId: data.shareId,
52
+ viewCount: data.viewCount,
53
+ uniqueViewers: data.uniqueViewers,
54
+ lastViewedAt: data.lastViewedAt,
55
+ });
56
+ }
57
+ catch (err) {
58
+ handleError(out, err, 'Failed to fetch share analytics');
59
+ }
60
+ });
61
+ addGlobalFlags(analytics.command('doc')
62
+ .description('Analytics for a published document')
63
+ .argument('<vaultId>', 'Vault ID')
64
+ .argument('<publishedDocId>', 'Published document ID'))
65
+ .action(async (vaultId, publishedDocId, _opts) => {
66
+ const flags = resolveFlags(_opts);
67
+ const out = createOutput(flags);
68
+ out.startSpinner('Fetching document analytics...');
69
+ try {
70
+ const client = await getClientAsync();
71
+ const data = await client.analytics.getPublishedDocAnalytics(vaultId, publishedDocId);
72
+ out.stopSpinner();
73
+ out.record({
74
+ publishedDocId: data.publishedDocId,
75
+ viewCount: data.viewCount,
76
+ uniqueViewers: data.uniqueViewers,
77
+ lastViewedAt: data.lastViewedAt,
78
+ });
79
+ }
80
+ catch (err) {
81
+ handleError(out, err, 'Failed to fetch document analytics');
82
+ }
83
+ });
84
+ }