@lifestreamdynamics/vault-cli 1.3.5 → 1.3.7

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.
@@ -2,6 +2,7 @@ import chalk from 'chalk';
2
2
  import { getClientAsync } from '../client.js';
3
3
  import { addGlobalFlags, resolveFlags } from '../utils/flags.js';
4
4
  import { createOutput, handleError } from '../utils/output.js';
5
+ import { resolveVaultId } from '../utils/resolve-vault.js';
5
6
  export function registerAnalyticsCommands(program) {
6
7
  const analytics = program.command('analytics').description('Analytics for published documents and share links');
7
8
  addGlobalFlags(analytics.command('published')
@@ -37,13 +38,14 @@ export function registerAnalyticsCommands(program) {
37
38
  });
38
39
  addGlobalFlags(analytics.command('share')
39
40
  .description('Analytics for a share link')
40
- .argument('<vaultId>', 'Vault ID')
41
+ .argument('<vaultId>', 'Vault ID or slug')
41
42
  .argument('<shareId>', 'Share ID'))
42
43
  .action(async (vaultId, shareId, _opts) => {
43
44
  const flags = resolveFlags(_opts);
44
45
  const out = createOutput(flags);
45
46
  out.startSpinner('Fetching share analytics...');
46
47
  try {
48
+ vaultId = await resolveVaultId(vaultId);
47
49
  const client = await getClientAsync();
48
50
  const data = await client.analytics.getShareAnalytics(vaultId, shareId);
49
51
  out.stopSpinner();
@@ -60,13 +62,18 @@ export function registerAnalyticsCommands(program) {
60
62
  });
61
63
  addGlobalFlags(analytics.command('doc')
62
64
  .description('Analytics for a published document')
63
- .argument('<vaultId>', 'Vault ID')
64
- .argument('<publishedDocId>', 'Published document ID'))
65
+ .argument('<vaultId>', 'Vault ID or slug')
66
+ .argument('<publishedDocId>', 'Published document ID (UUID from "publish list")')
67
+ .addHelpText('after', `
68
+ NOTE
69
+ The publishedDocId is a UUID, NOT a document path. Get it from:
70
+ lsvault publish list <vaultId>`))
65
71
  .action(async (vaultId, publishedDocId, _opts) => {
66
72
  const flags = resolveFlags(_opts);
67
73
  const out = createOutput(flags);
68
74
  out.startSpinner('Fetching document analytics...');
69
75
  try {
76
+ vaultId = await resolveVaultId(vaultId);
70
77
  const client = await getClientAsync();
71
78
  const data = await client.analytics.getPublishedDocAnalytics(vaultId, publishedDocId);
72
79
  out.stopSpinner();
@@ -24,13 +24,43 @@ EXAMPLES
24
24
  const flags = resolveFlags(_opts);
25
25
  const out = createOutput(flags);
26
26
  try {
27
- const logger = new AuditLogger({ logPath: String(_opts.logPath || DEFAULT_LOG_PATH) });
27
+ if (_opts.since) {
28
+ const d = new Date(String(_opts.since));
29
+ if (isNaN(d.getTime())) {
30
+ out.error('Invalid --since date. Use ISO 8601 format (e.g., 2024-01-01).');
31
+ process.exitCode = 1;
32
+ return;
33
+ }
34
+ }
35
+ if (_opts.until) {
36
+ const d = new Date(String(_opts.until));
37
+ if (isNaN(d.getTime())) {
38
+ out.error('Invalid --until date. Use ISO 8601 format (e.g., 2024-01-31).');
39
+ process.exitCode = 1;
40
+ return;
41
+ }
42
+ }
43
+ const logPath = String(_opts.logPath || DEFAULT_LOG_PATH);
44
+ if (!fs.existsSync(logPath)) {
45
+ out.warn(`Audit log file not found at ${logPath}`);
46
+ if (flags.output === 'json') {
47
+ process.stdout.write('[]\n');
48
+ }
49
+ return;
50
+ }
51
+ if (flags.verbose) {
52
+ out.debug(`Reading audit log from: ${logPath}`);
53
+ }
54
+ const logger = new AuditLogger({ logPath });
28
55
  const entries = logger.readEntries({
29
56
  tail: _opts.tail,
30
57
  status: _opts.status,
31
58
  since: _opts.since,
32
59
  until: _opts.until,
33
60
  });
61
+ if (flags.verbose) {
62
+ out.debug(`Found ${entries.length} entries`);
63
+ }
34
64
  out.list(entries.map(e => ({
35
65
  timestamp: e.timestamp,
36
66
  method: e.method,
@@ -76,7 +106,31 @@ EXAMPLES
76
106
  process.exitCode = 2;
77
107
  return;
78
108
  }
79
- const logger = new AuditLogger({ logPath: String(_opts.logPath || DEFAULT_LOG_PATH) });
109
+ if (_opts.since) {
110
+ const d = new Date(String(_opts.since));
111
+ if (isNaN(d.getTime())) {
112
+ out.error('Invalid --since date. Use ISO 8601 format (e.g., 2024-01-01).');
113
+ process.exitCode = 1;
114
+ return;
115
+ }
116
+ }
117
+ if (_opts.until) {
118
+ const d = new Date(String(_opts.until));
119
+ if (isNaN(d.getTime())) {
120
+ out.error('Invalid --until date. Use ISO 8601 format (e.g., 2024-01-31).');
121
+ process.exitCode = 1;
122
+ return;
123
+ }
124
+ }
125
+ const logPath = String(_opts.logPath || DEFAULT_LOG_PATH);
126
+ if (!fs.existsSync(logPath)) {
127
+ out.warn(`Audit log file not found at ${logPath}`);
128
+ if (flags.output === 'json') {
129
+ process.stdout.write('[]\n');
130
+ }
131
+ return;
132
+ }
133
+ const logger = new AuditLogger({ logPath });
80
134
  const entries = logger.readEntries({
81
135
  status: _opts.status,
82
136
  since: _opts.since,
@@ -206,7 +206,15 @@ EXAMPLES
206
206
  console.log(`User: ${chalk.cyan(user.email)}`);
207
207
  console.log(`Name: ${user.displayName || chalk.dim('not set')}`);
208
208
  console.log(`Role: ${user.role}`);
209
- console.log(`Plan: ${chalk.green(user.subscriptionTier)}`);
209
+ let plan = user.subscriptionTier;
210
+ if (!plan) {
211
+ try {
212
+ const sub = await client.subscription.get();
213
+ plan = sub.subscription.tier;
214
+ }
215
+ catch { /* API key may not have scope */ }
216
+ }
217
+ console.log(`Plan: ${plan ? chalk.green(plan) : chalk.dim('unknown')}`);
210
218
  }
211
219
  catch (err) {
212
220
  spinner.fail('Could not fetch user info');
@@ -2,6 +2,7 @@ import chalk from 'chalk';
2
2
  import { getClientAsync } from '../client.js';
3
3
  import { addGlobalFlags, resolveFlags } from '../utils/flags.js';
4
4
  import { createOutput, handleError } from '../utils/output.js';
5
+ import { resolveVaultId } from '../utils/resolve-vault.js';
5
6
  export function registerBookingCommands(program) {
6
7
  const booking = program.command('booking').description('Booking slot and guest booking management');
7
8
  // ---------------------------------------------------------------------------
@@ -11,12 +12,13 @@ export function registerBookingCommands(program) {
11
12
  // booking slots list
12
13
  addGlobalFlags(slots.command('list')
13
14
  .description('List all event slots for a vault')
14
- .argument('<vaultId>', 'Vault ID'))
15
+ .argument('<vaultId>', 'Vault ID or slug'))
15
16
  .action(async (vaultId, _opts) => {
16
17
  const flags = resolveFlags(_opts);
17
18
  const out = createOutput(flags);
18
19
  out.startSpinner('Loading slots...');
19
20
  try {
21
+ vaultId = await resolveVaultId(vaultId);
20
22
  const client = await getClientAsync();
21
23
  const slotList = await client.booking.listSlots(vaultId);
22
24
  out.stopSpinner();
@@ -55,7 +57,7 @@ export function registerBookingCommands(program) {
55
57
  // booking slots create
56
58
  addGlobalFlags(slots.command('create')
57
59
  .description('Create a new bookable event slot')
58
- .argument('<vaultId>', 'Vault ID')
60
+ .argument('<vaultId>', 'Vault ID or slug')
59
61
  .requiredOption('--title <title>', 'Slot title')
60
62
  .requiredOption('--duration <minutes>', 'Slot duration in minutes')
61
63
  .requiredOption('--start-time <HH:mm>', 'Availability window start time (HH:mm)')
@@ -70,13 +72,14 @@ export function registerBookingCommands(program) {
70
72
  const out = createOutput(flags);
71
73
  out.startSpinner('Creating slot...');
72
74
  try {
75
+ vaultId = await resolveVaultId(vaultId);
73
76
  const client = await getClientAsync();
74
77
  const created = await client.booking.createSlot(vaultId, {
75
78
  title: _opts.title,
76
79
  durationMin: Number(_opts.duration),
77
80
  startTime: _opts.startTime,
78
81
  endTime: _opts.endTime,
79
- daysOfWeek: _opts.days.split(',').map((d) => d.trim()),
82
+ daysOfWeek: _opts.days.split(',').map((d) => d.trim().toLowerCase()),
80
83
  timezone: _opts.timezone,
81
84
  bufferMin: Number(_opts.buffer),
82
85
  maxConcurrent: Number(_opts.maxConcurrent),
@@ -97,7 +100,7 @@ export function registerBookingCommands(program) {
97
100
  // booking slots update
98
101
  addGlobalFlags(slots.command('update')
99
102
  .description('Update an existing event slot')
100
- .argument('<vaultId>', 'Vault ID')
103
+ .argument('<vaultId>', 'Vault ID or slug')
101
104
  .argument('<slotId>', 'Slot ID')
102
105
  .option('--title <title>', 'Slot title')
103
106
  .option('--duration <minutes>', 'Slot duration in minutes')
@@ -113,6 +116,7 @@ export function registerBookingCommands(program) {
113
116
  const out = createOutput(flags);
114
117
  out.startSpinner('Updating slot...');
115
118
  try {
119
+ vaultId = await resolveVaultId(vaultId);
116
120
  const client = await getClientAsync();
117
121
  const data = {};
118
122
  if (_opts.title)
@@ -124,7 +128,7 @@ export function registerBookingCommands(program) {
124
128
  if (_opts.endTime)
125
129
  data.endTime = _opts.endTime;
126
130
  if (_opts.days)
127
- data.daysOfWeek = _opts.days.split(',').map((d) => d.trim());
131
+ data.daysOfWeek = _opts.days.split(',').map((d) => d.trim().toLowerCase());
128
132
  if (_opts.timezone)
129
133
  data.timezone = _opts.timezone;
130
134
  if (_opts.buffer)
@@ -149,18 +153,20 @@ export function registerBookingCommands(program) {
149
153
  // booking slots delete
150
154
  addGlobalFlags(slots.command('delete')
151
155
  .description('Delete an event slot')
152
- .argument('<vaultId>', 'Vault ID')
156
+ .argument('<vaultId>', 'Vault ID or slug')
153
157
  .argument('<slotId>', 'Slot ID')
154
- .option('--confirm', 'Skip confirmation prompt'))
158
+ .option('-y, --yes', 'Skip confirmation prompt')
159
+ .option('--confirm', 'Alias for --yes (deprecated)'))
155
160
  .action(async (vaultId, slotId, _opts) => {
156
161
  const flags = resolveFlags(_opts);
157
162
  const out = createOutput(flags);
158
- if (!_opts.confirm) {
159
- out.status(chalk.yellow(`Pass --confirm to delete slot ${slotId}.`));
163
+ if (!_opts.yes && !_opts.confirm) {
164
+ out.status(chalk.yellow(`Pass -y/--yes to delete slot ${slotId}.`));
160
165
  return;
161
166
  }
162
167
  out.startSpinner('Deleting slot...');
163
168
  try {
169
+ vaultId = await resolveVaultId(vaultId);
164
170
  const client = await getClientAsync();
165
171
  await client.booking.deleteSlot(vaultId, slotId);
166
172
  out.stopSpinner();
@@ -176,7 +182,7 @@ export function registerBookingCommands(program) {
176
182
  const VALID_BOOKING_STATUSES = ['pending', 'confirmed', 'cancelled', 'no_show', 'completed'];
177
183
  addGlobalFlags(booking.command('list')
178
184
  .description('List bookings for a vault')
179
- .argument('<vaultId>', 'Vault ID')
185
+ .argument('<vaultId>', 'Vault ID or slug')
180
186
  .option('--status <status>', 'Filter by status (pending/confirmed/cancelled/no_show/completed)')
181
187
  .option('--slot-id <slotId>', 'Filter by slot ID')
182
188
  .option('--from <date>', 'Filter bookings from this date (YYYY-MM-DD)')
@@ -192,6 +198,7 @@ export function registerBookingCommands(program) {
192
198
  }
193
199
  out.startSpinner('Loading bookings...');
194
200
  try {
201
+ vaultId = await resolveVaultId(vaultId);
195
202
  const client = await getClientAsync();
196
203
  const result = await client.booking.listBookings(vaultId, {
197
204
  status: bookingStatus,
@@ -240,13 +247,14 @@ export function registerBookingCommands(program) {
240
247
  // ---------------------------------------------------------------------------
241
248
  addGlobalFlags(booking.command('confirm')
242
249
  .description('Confirm a pending booking')
243
- .argument('<vaultId>', 'Vault ID')
250
+ .argument('<vaultId>', 'Vault ID or slug')
244
251
  .argument('<bookingId>', 'Booking ID'))
245
252
  .action(async (vaultId, bookingId, _opts) => {
246
253
  const flags = resolveFlags(_opts);
247
254
  const out = createOutput(flags);
248
255
  out.startSpinner('Confirming booking...');
249
256
  try {
257
+ vaultId = await resolveVaultId(vaultId);
250
258
  const client = await getClientAsync();
251
259
  const updated = await client.booking.updateBookingStatus(vaultId, bookingId, 'confirmed');
252
260
  out.stopSpinner();
@@ -266,13 +274,14 @@ export function registerBookingCommands(program) {
266
274
  // ---------------------------------------------------------------------------
267
275
  addGlobalFlags(booking.command('cancel')
268
276
  .description('Cancel a booking')
269
- .argument('<vaultId>', 'Vault ID')
277
+ .argument('<vaultId>', 'Vault ID or slug')
270
278
  .argument('<bookingId>', 'Booking ID'))
271
279
  .action(async (vaultId, bookingId, _opts) => {
272
280
  const flags = resolveFlags(_opts);
273
281
  const out = createOutput(flags);
274
282
  out.startSpinner('Cancelling booking...');
275
283
  try {
284
+ vaultId = await resolveVaultId(vaultId);
276
285
  const client = await getClientAsync();
277
286
  const updated = await client.booking.updateBookingStatus(vaultId, bookingId, 'cancelled');
278
287
  out.stopSpinner();
@@ -294,12 +303,13 @@ export function registerBookingCommands(program) {
294
303
  // booking templates list
295
304
  addGlobalFlags(templates.command('list')
296
305
  .description('List event templates for a vault')
297
- .argument('<vaultId>', 'Vault ID'))
306
+ .argument('<vaultId>', 'Vault ID or slug'))
298
307
  .action(async (vaultId, _opts) => {
299
308
  const flags = resolveFlags(_opts);
300
309
  const out = createOutput(flags);
301
310
  out.startSpinner('Loading templates...');
302
311
  try {
312
+ vaultId = await resolveVaultId(vaultId);
303
313
  const client = await getClientAsync();
304
314
  const list = await client.booking.listTemplates(vaultId);
305
315
  out.stopSpinner();
@@ -326,7 +336,7 @@ export function registerBookingCommands(program) {
326
336
  // booking templates create
327
337
  addGlobalFlags(templates.command('create')
328
338
  .description('Create an event template')
329
- .argument('<vaultId>', 'Vault ID')
339
+ .argument('<vaultId>', 'Vault ID or slug')
330
340
  .requiredOption('--name <name>', 'Template name')
331
341
  .option('--description <desc>', 'Template description')
332
342
  .option('--defaults <json>', 'Default values (JSON string)', '{}'))
@@ -344,6 +354,7 @@ export function registerBookingCommands(program) {
344
354
  }
345
355
  out.startSpinner('Creating template...');
346
356
  try {
357
+ vaultId = await resolveVaultId(vaultId);
347
358
  const client = await getClientAsync();
348
359
  const created = await client.booking.createTemplate(vaultId, {
349
360
  name: _opts.name,
@@ -365,18 +376,20 @@ export function registerBookingCommands(program) {
365
376
  // booking templates delete
366
377
  addGlobalFlags(templates.command('delete')
367
378
  .description('Delete an event template')
368
- .argument('<vaultId>', 'Vault ID')
379
+ .argument('<vaultId>', 'Vault ID or slug')
369
380
  .argument('<templateId>', 'Template ID')
370
- .option('--confirm', 'Skip confirmation prompt'))
381
+ .option('-y, --yes', 'Skip confirmation prompt')
382
+ .option('--confirm', 'Alias for --yes (deprecated)'))
371
383
  .action(async (vaultId, templateId, _opts) => {
372
384
  const flags = resolveFlags(_opts);
373
385
  const out = createOutput(flags);
374
- if (!_opts.confirm) {
375
- out.status(chalk.yellow(`Pass --confirm to delete template ${templateId}.`));
386
+ if (!_opts.yes && !_opts.confirm) {
387
+ out.status(chalk.yellow(`Pass -y/--yes to delete template ${templateId}.`));
376
388
  return;
377
389
  }
378
390
  out.startSpinner('Deleting template...');
379
391
  try {
392
+ vaultId = await resolveVaultId(vaultId);
380
393
  const client = await getClientAsync();
381
394
  await client.booking.deleteTemplate(vaultId, templateId);
382
395
  out.stopSpinner();
@@ -418,7 +431,7 @@ export function registerBookingCommands(program) {
418
431
  const VALID_ANALYTICS_VIEWS = ['volume', 'funnel', 'peak-times'];
419
432
  addGlobalFlags(booking.command('analytics')
420
433
  .description('View booking analytics (Business tier)')
421
- .argument('<vaultId>', 'Vault ID')
434
+ .argument('<vaultId>', 'Vault ID or slug')
422
435
  .option('--view <view>', 'Analytics view: volume, funnel, peak-times', 'volume')
423
436
  .option('--from <date>', 'Start date (YYYY-MM-DD)')
424
437
  .option('--to <date>', 'End date (YYYY-MM-DD)')
@@ -434,6 +447,7 @@ export function registerBookingCommands(program) {
434
447
  }
435
448
  out.startSpinner('Loading analytics...');
436
449
  try {
450
+ vaultId = await resolveVaultId(vaultId);
437
451
  const client = await getClientAsync();
438
452
  const result = await client.booking.getBookingAnalytics(vaultId, {
439
453
  view: analyticsView,
@@ -572,12 +586,13 @@ export function registerBookingCommands(program) {
572
586
  .description('Delete a booking group')
573
587
  .argument('<teamId>', 'Team ID')
574
588
  .argument('<groupId>', 'Group ID')
575
- .option('--confirm', 'Skip confirmation prompt'))
589
+ .option('-y, --yes', 'Skip confirmation prompt')
590
+ .option('--confirm', 'Alias for --yes (deprecated)'))
576
591
  .action(async (teamId, groupId, _opts) => {
577
592
  const flags = resolveFlags(_opts);
578
593
  const out = createOutput(flags);
579
- if (!_opts.confirm) {
580
- out.status(chalk.yellow(`Pass --confirm to delete booking group ${groupId}.`));
594
+ if (!_opts.yes && !_opts.confirm) {
595
+ out.status(chalk.yellow(`Pass -y/--yes to delete booking group ${groupId}.`));
581
596
  return;
582
597
  }
583
598
  out.startSpinner('Deleting booking group...');
@@ -686,7 +701,7 @@ export function registerBookingCommands(program) {
686
701
  // booking waitlist list <vaultId> <slotId>
687
702
  addGlobalFlags(waitlist.command('list')
688
703
  .description('List waitlist entries for a booking slot')
689
- .argument('<vaultId>', 'Vault ID')
704
+ .argument('<vaultId>', 'Vault ID or slug')
690
705
  .argument('<slotId>', 'Slot ID')
691
706
  .option('--status <status>', 'Filter by status (waiting/notified/expired/left)')
692
707
  .option('--start-at <iso>', 'Filter by specific start time (ISO 8601)'))
@@ -695,6 +710,7 @@ export function registerBookingCommands(program) {
695
710
  const out = createOutput(flags);
696
711
  out.startSpinner('Loading waitlist...');
697
712
  try {
713
+ vaultId = await resolveVaultId(vaultId);
698
714
  const client = await getClientAsync();
699
715
  const result = await client.booking.getWaitlist(vaultId, slotId, {
700
716
  status: _opts.status,