@lifestreamdynamics/vault-cli 1.3.4 → 1.3.6

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,16 +2,18 @@ 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 registerPublishCommands(program) {
6
7
  const publish = program.command('publish').description('Publish documents to public profile pages');
7
8
  addGlobalFlags(publish.command('list')
8
9
  .description('List your published documents')
9
- .argument('<vaultId>', 'Vault ID (required by route)'))
10
+ .argument('<vaultId>', 'Vault ID or slug (required by route)'))
10
11
  .action(async (vaultId, _opts) => {
11
12
  const flags = resolveFlags(_opts);
12
13
  const out = createOutput(flags);
13
14
  out.startSpinner('Fetching published documents...');
14
15
  try {
16
+ vaultId = await resolveVaultId(vaultId);
15
17
  const client = await getClientAsync();
16
18
  const docs = await client.publish.listMine(vaultId);
17
19
  out.stopSpinner();
@@ -49,7 +51,7 @@ export function registerPublishCommands(program) {
49
51
  });
50
52
  addGlobalFlags(publish.command('create')
51
53
  .description('Publish a document')
52
- .argument('<vaultId>', 'Vault ID')
54
+ .argument('<vaultId>', 'Vault ID or slug')
53
55
  .argument('<docPath>', 'Document path (e.g., blog/post.md)')
54
56
  .requiredOption('--slug <slug>', 'URL-friendly slug for the published page')
55
57
  .option('--title <title>', 'SEO title')
@@ -60,6 +62,7 @@ export function registerPublishCommands(program) {
60
62
  const out = createOutput(flags);
61
63
  out.startSpinner('Publishing document...');
62
64
  try {
65
+ vaultId = await resolveVaultId(vaultId);
63
66
  const client = await getClientAsync();
64
67
  const params = {
65
68
  slug: String(_opts.slug),
@@ -73,6 +76,7 @@ export function registerPublishCommands(program) {
73
76
  const pub = await client.publish.create(vaultId, docPath, params);
74
77
  out.success('Document published successfully!', {
75
78
  slug: pub.slug,
79
+ url: `/${pub.publishedBy}/${pub.slug}`,
76
80
  isPublished: pub.isPublished,
77
81
  seoTitle: pub.seoTitle || null,
78
82
  seoDescription: pub.seoDescription || null,
@@ -85,7 +89,7 @@ export function registerPublishCommands(program) {
85
89
  });
86
90
  addGlobalFlags(publish.command('update')
87
91
  .description('Update a published document')
88
- .argument('<vaultId>', 'Vault ID')
92
+ .argument('<vaultId>', 'Vault ID or slug')
89
93
  .argument('<docPath>', 'Document path (e.g., blog/post.md)')
90
94
  .requiredOption('--slug <slug>', 'URL-friendly slug (required for updates)')
91
95
  .option('--title <title>', 'SEO title')
@@ -96,6 +100,7 @@ export function registerPublishCommands(program) {
96
100
  const out = createOutput(flags);
97
101
  out.startSpinner('Updating published document...');
98
102
  try {
103
+ vaultId = await resolveVaultId(vaultId);
99
104
  const client = await getClientAsync();
100
105
  const params = {
101
106
  slug: String(_opts.slug),
@@ -120,7 +125,7 @@ export function registerPublishCommands(program) {
120
125
  });
121
126
  addGlobalFlags(publish.command('delete')
122
127
  .description('Unpublish a document')
123
- .argument('<vaultId>', 'Vault ID')
128
+ .argument('<vaultId>', 'Vault ID or slug')
124
129
  .argument('<docPath>', 'Document path (e.g., blog/post.md)')
125
130
  .option('-y, --yes', 'Skip confirmation prompt'))
126
131
  .action(async (vaultId, docPath, _opts) => {
@@ -132,6 +137,7 @@ export function registerPublishCommands(program) {
132
137
  }
133
138
  out.startSpinner('Unpublishing document...');
134
139
  try {
140
+ vaultId = await resolveVaultId(vaultId);
135
141
  const client = await getClientAsync();
136
142
  await client.publish.delete(vaultId, docPath);
137
143
  out.success('Document unpublished successfully', { path: docPath, unpublished: true });
@@ -143,16 +149,25 @@ export function registerPublishCommands(program) {
143
149
  const subdomain = publish.command('subdomain').description('Subdomain management for published vaults');
144
150
  addGlobalFlags(subdomain.command('get')
145
151
  .description('Get the subdomain for a published vault')
146
- .argument('<vaultId>', 'Vault ID'))
152
+ .argument('<vaultId>', 'Vault ID or slug'))
147
153
  .action(async (vaultId, _opts) => {
148
154
  const flags = resolveFlags(_opts);
149
155
  const out = createOutput(flags);
150
156
  out.startSpinner('Fetching subdomain...');
151
157
  try {
158
+ vaultId = await resolveVaultId(vaultId);
152
159
  const client = await getClientAsync();
153
160
  const result = await client.publish.getSubdomain(vaultId);
154
161
  out.stopSpinner();
155
- out.record({ subdomain: result.subdomain });
162
+ if (flags.output === 'json') {
163
+ out.record({ subdomain: result.subdomain });
164
+ }
165
+ else if (result.subdomain == null) {
166
+ out.status('No subdomain configured.');
167
+ }
168
+ else {
169
+ out.record({ subdomain: result.subdomain });
170
+ }
156
171
  }
157
172
  catch (err) {
158
173
  handleError(out, err, 'Failed to fetch subdomain');
@@ -160,13 +175,14 @@ export function registerPublishCommands(program) {
160
175
  });
161
176
  addGlobalFlags(subdomain.command('set')
162
177
  .description('Set a subdomain for a published vault')
163
- .argument('<vaultId>', 'Vault ID')
178
+ .argument('<vaultId>', 'Vault ID or slug')
164
179
  .argument('<subdomain>', 'Subdomain to assign'))
165
180
  .action(async (vaultId, subdomainArg, _opts) => {
166
181
  const flags = resolveFlags(_opts);
167
182
  const out = createOutput(flags);
168
183
  out.startSpinner('Setting subdomain...');
169
184
  try {
185
+ vaultId = await resolveVaultId(vaultId);
170
186
  const client = await getClientAsync();
171
187
  const result = await client.publish.setSubdomain(vaultId, subdomainArg);
172
188
  out.success(`Subdomain set: ${result.subdomain}`, { subdomain: result.subdomain });
@@ -177,7 +193,7 @@ export function registerPublishCommands(program) {
177
193
  });
178
194
  addGlobalFlags(subdomain.command('delete')
179
195
  .description('Remove the subdomain for a published vault')
180
- .argument('<vaultId>', 'Vault ID')
196
+ .argument('<vaultId>', 'Vault ID or slug')
181
197
  .option('-y, --yes', 'Skip confirmation prompt'))
182
198
  .action(async (vaultId, _opts) => {
183
199
  const flags = resolveFlags(_opts);
@@ -188,6 +204,7 @@ export function registerPublishCommands(program) {
188
204
  }
189
205
  out.startSpinner('Removing subdomain...');
190
206
  try {
207
+ vaultId = await resolveVaultId(vaultId);
191
208
  const client = await getClientAsync();
192
209
  const result = await client.publish.deleteSubdomain(vaultId);
193
210
  out.success(result.message, { message: result.message });
@@ -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 registerSearchCommands(program) {
6
7
  addGlobalFlags(program.command('search')
7
8
  .description('Full-text search across all documents')
@@ -21,6 +22,8 @@ EXAMPLES
21
22
  const out = createOutput(flags);
22
23
  out.startSpinner('Searching...');
23
24
  try {
25
+ if (_opts.vault)
26
+ _opts.vault = await resolveVaultId(String(_opts.vault));
24
27
  const client = await getClientAsync();
25
28
  const mode = _opts.mode;
26
29
  const response = await client.search.search({
@@ -31,6 +34,9 @@ EXAMPLES
31
34
  mode: mode,
32
35
  });
33
36
  out.stopSpinner();
37
+ if (response.results.length === 0 && mode === 'semantic') {
38
+ out.warn('Semantic search returned no results. Ensure document embeddings have been generated (the embedding worker must be running).');
39
+ }
34
40
  if (flags.output === 'text') {
35
41
  const modeInfo = mode && mode !== 'text' ? `[${mode}] ` : '';
36
42
  out.status(chalk.dim(`${modeInfo}${response.total} result(s) for "${response.query}":\n`));
@@ -39,6 +45,7 @@ EXAMPLES
39
45
  title: r.title || r.path,
40
46
  path: r.path,
41
47
  vaultName: r.vaultName,
48
+ rank: r.rank,
42
49
  tags: r.tags.join(', '),
43
50
  snippet: r.snippet ? r.snippet.replace(/<[^>]+>/g, '') : '',
44
51
  })), {
@@ -1,19 +1,22 @@
1
1
  import chalk from 'chalk';
2
2
  import { getClientAsync } from '../client.js';
3
+ import { loadConfigAsync } from '../config.js';
3
4
  import { addGlobalFlags, resolveFlags } from '../utils/flags.js';
4
5
  import { createOutput, handleError } from '../utils/output.js';
5
6
  import { promptPassword, readPasswordFromStdin } from '../utils/prompt.js';
7
+ import { resolveVaultId } from '../utils/resolve-vault.js';
6
8
  export function registerShareCommands(program) {
7
9
  const shares = program.command('shares').description('Create, list, and revoke document share links');
8
10
  addGlobalFlags(shares.command('list')
9
11
  .description('List share links for a document')
10
- .argument('<vaultId>', 'Vault ID')
12
+ .argument('<vaultId>', 'Vault ID or slug')
11
13
  .argument('<docPath>', 'Document path (e.g., notes/meeting.md)'))
12
14
  .action(async (vaultId, docPath, _opts) => {
13
15
  const flags = resolveFlags(_opts);
14
16
  const out = createOutput(flags);
15
17
  out.startSpinner('Fetching share links...');
16
18
  try {
19
+ vaultId = await resolveVaultId(vaultId);
17
20
  const client = await getClientAsync();
18
21
  const links = await client.shares.list(vaultId, docPath);
19
22
  out.stopSpinner();
@@ -53,7 +56,7 @@ export function registerShareCommands(program) {
53
56
  });
54
57
  addGlobalFlags(shares.command('create')
55
58
  .description('Create a share link for a document')
56
- .argument('<vaultId>', 'Vault ID')
59
+ .argument('<vaultId>', 'Vault ID or slug')
57
60
  .argument('<docPath>', 'Document path (e.g., notes/meeting.md)')
58
61
  .option('--permission <perm>', 'Permission level: view or edit', 'view')
59
62
  .option('--protect-with-password', 'Prompt for a password to protect the link (interactive TTY only)')
@@ -85,6 +88,7 @@ export function registerShareCommands(program) {
85
88
  }
86
89
  out.startSpinner('Creating share link...');
87
90
  try {
91
+ vaultId = await resolveVaultId(vaultId);
88
92
  const client = await getClientAsync();
89
93
  const params = {};
90
94
  if (_opts.permission)
@@ -97,9 +101,12 @@ export function registerShareCommands(program) {
97
101
  params.maxViews = parseInt(String(_opts.maxViews), 10);
98
102
  const result = await client.shares.create(vaultId, docPath, params);
99
103
  out.stopSpinner();
104
+ const config = await loadConfigAsync();
105
+ const shareUrl = `${config.apiUrl}/share/${result.fullToken}`;
100
106
  if (flags.output === 'json') {
101
107
  out.record({
102
108
  token: result.fullToken,
109
+ url: shareUrl,
103
110
  id: result.shareLink.id,
104
111
  permission: result.shareLink.permission,
105
112
  expiresAt: result.shareLink.expiresAt || null,
@@ -109,6 +116,7 @@ export function registerShareCommands(program) {
109
116
  else {
110
117
  out.warn('\nIMPORTANT: Save this token. It cannot be retrieved later.\n');
111
118
  process.stdout.write(chalk.green.bold(`Token: ${result.fullToken}\n`));
119
+ process.stdout.write(chalk.green(`URL: ${shareUrl}\n`));
112
120
  process.stdout.write(`\nID: ${result.shareLink.id}\n`);
113
121
  process.stdout.write(`Permission: ${result.shareLink.permission}\n`);
114
122
  if (result.shareLink.expiresAt) {
@@ -125,13 +133,14 @@ export function registerShareCommands(program) {
125
133
  });
126
134
  addGlobalFlags(shares.command('revoke')
127
135
  .description('Revoke a share link')
128
- .argument('<vaultId>', 'Vault ID')
136
+ .argument('<vaultId>', 'Vault ID or slug')
129
137
  .argument('<shareId>', 'Share link ID'))
130
138
  .action(async (vaultId, shareId, _opts) => {
131
139
  const flags = resolveFlags(_opts);
132
140
  const out = createOutput(flags);
133
141
  out.startSpinner('Revoking share link...');
134
142
  try {
143
+ vaultId = await resolveVaultId(vaultId);
135
144
  const client = await getClientAsync();
136
145
  await client.shares.revoke(vaultId, shareId);
137
146
  out.success('Share link revoked successfully', { id: shareId, revoked: true });
@@ -24,7 +24,17 @@ export function registerSyncCommands(program) {
24
24
  .option('--on-conflict <strategy>', 'Conflict strategy: newer, local, remote, ask (default: newer)')
25
25
  .option('--ignore <patterns...>', 'Glob patterns to ignore')
26
26
  .option('--interval <interval>', 'Auto-sync interval (e.g., 5m, 1h)')
27
- .option('--auto-sync', 'Enable auto-sync'))
27
+ .option('--auto-sync', 'Enable auto-sync')
28
+ .addHelpText('after', `
29
+ Examples:
30
+ lsvault sync init <vaultId> ~/my-vault
31
+ lsvault sync init <vaultId> ~/mirror --mode pull --on-conflict remote
32
+ lsvault sync init <vaultId> ~/docs --mode push --on-conflict local --auto-sync
33
+
34
+ Sync modes:
35
+ pull Download remote changes only (ideal for cron/automation)
36
+ push Upload local changes only (ideal for CI pipelines)
37
+ sync Bidirectional with conflict detection (default)`))
28
38
  .action(async (vaultId, localPath, _opts) => {
29
39
  const flags = resolveFlags(_opts);
30
40
  const out = createOutput(flags);
@@ -158,26 +168,37 @@ export function registerSyncCommands(program) {
158
168
  out.debug(`Found ${Object.keys(remoteFiles).length} remote files`);
159
169
  out.startSpinner('Computing diff...');
160
170
  const diff = computePullDiff(localFiles, remoteFiles, lastState);
171
+ const unchanged = Object.keys(remoteFiles).length - diff.downloads.length;
161
172
  const totalOps = diff.downloads.length + diff.deletes.length;
162
173
  if (totalOps === 0) {
163
174
  out.succeedSpinner('Everything is up to date');
164
175
  if (flags.output === 'json') {
165
- out.record({ status: 'up-to-date', changes: 0 });
176
+ out.record({
177
+ status: 'up-to-date',
178
+ downloaded: 0,
179
+ deleted: 0,
180
+ unchanged: Object.keys(remoteFiles).length,
181
+ bytesTransferred: 0,
182
+ errors: 0,
183
+ });
166
184
  }
167
185
  return;
168
186
  }
169
187
  out.stopSpinner();
170
188
  if (flags.dryRun) {
171
- out.status(chalk.yellow('Dry run — no changes will be made:'));
172
- out.status(formatDiff(diff));
173
189
  if (flags.output === 'json') {
174
190
  out.record({
175
191
  dryRun: true,
176
192
  downloads: diff.downloads.length,
177
193
  deletes: diff.deletes.length,
194
+ unchanged,
178
195
  totalBytes: diff.totalBytes,
179
196
  });
180
197
  }
198
+ else {
199
+ out.status(chalk.yellow('Dry run — no changes will be made:'));
200
+ out.status(formatDiff(diff));
201
+ }
181
202
  return;
182
203
  }
183
204
  if (flags.verbose) {
@@ -201,6 +222,7 @@ export function registerSyncCommands(program) {
201
222
  out.success('', {
202
223
  downloaded: result.filesDownloaded,
203
224
  deleted: result.filesDeleted,
225
+ unchanged,
204
226
  bytesTransferred: result.bytesTransferred,
205
227
  errors: result.errors.length,
206
228
  });
@@ -234,11 +256,19 @@ export function registerSyncCommands(program) {
234
256
  out.debug(`Found ${Object.keys(remoteFiles).length} remote files`);
235
257
  out.startSpinner('Computing diff...');
236
258
  const diff = computePushDiff(localFiles, remoteFiles, lastState);
259
+ const unchanged = Object.keys(localFiles).length - diff.uploads.length;
237
260
  const totalOps = diff.uploads.length + diff.deletes.length;
238
261
  if (totalOps === 0) {
239
262
  out.succeedSpinner('Everything is up to date');
240
263
  if (flags.output === 'json') {
241
- out.record({ status: 'up-to-date', changes: 0 });
264
+ out.record({
265
+ status: 'up-to-date',
266
+ uploaded: 0,
267
+ deleted: 0,
268
+ unchanged: Object.keys(localFiles).length,
269
+ bytesTransferred: 0,
270
+ errors: 0,
271
+ });
242
272
  }
243
273
  return;
244
274
  }
@@ -251,6 +281,7 @@ export function registerSyncCommands(program) {
251
281
  dryRun: true,
252
282
  uploads: diff.uploads.length,
253
283
  deletes: diff.deletes.length,
284
+ unchanged,
254
285
  totalBytes: diff.totalBytes,
255
286
  });
256
287
  }
@@ -277,6 +308,7 @@ export function registerSyncCommands(program) {
277
308
  out.success('', {
278
309
  uploaded: result.filesUploaded,
279
310
  deleted: result.filesDeleted,
311
+ unchanged,
280
312
  bytesTransferred: result.bytesTransferred,
281
313
  errors: result.errors.length,
282
314
  });
@@ -409,9 +409,19 @@ export function registerUserCommands(program) {
409
409
  addGlobalFlags(consents.command('set')
410
410
  .description('Record a consent decision')
411
411
  .requiredOption('--type <t>', 'Consent type')
412
- .requiredOption('--version <v>', 'Policy version')
412
+ .requiredOption('--policy-version <v>', 'Policy version')
413
413
  .option('--granted', 'Grant consent')
414
- .option('--no-granted', 'Deny consent'))
414
+ .option('--no-granted', 'Deny consent')
415
+ .addHelpText('after', `
416
+ CONSENT TYPES
417
+ terms_of_service Terms of Service acceptance
418
+ privacy_policy Privacy Policy acceptance
419
+ ai_processing AI feature data processing consent
420
+ marketing Marketing communications consent
421
+
422
+ EXAMPLES
423
+ lsvault user consents set --type terms_of_service --policy-version 1.0 --granted
424
+ lsvault user consents set --type ai_processing --policy-version 1.0 --no-granted`))
415
425
  .action(async (_opts) => {
416
426
  const flags = resolveFlags(_opts);
417
427
  const out = createOutput(flags);
@@ -420,7 +430,7 @@ export function registerUserCommands(program) {
420
430
  const client = await getClientAsync();
421
431
  const result = await client.user.recordConsent({
422
432
  consentType: _opts.type,
423
- version: _opts.version,
433
+ version: _opts.policyVersion,
424
434
  granted: _opts.granted !== false,
425
435
  });
426
436
  out.success(result.message, { message: result.message });
@@ -4,6 +4,7 @@ import { addGlobalFlags, resolveFlags } from '../utils/flags.js';
4
4
  import { createOutput, handleError } from '../utils/output.js';
5
5
  import { generateVaultKey } from '@lifestreamdynamics/vault-sdk';
6
6
  import { getCredentialManager } from '../config.js';
7
+ import { resolveVaultId } from '../utils/resolve-vault.js';
7
8
  export function registerVaultCommands(program) {
8
9
  const vaults = program.command('vaults').description('Create, list, and inspect document vaults');
9
10
  addGlobalFlags(vaults.command('list')
@@ -16,7 +17,7 @@ export function registerVaultCommands(program) {
16
17
  const client = await getClientAsync();
17
18
  const vaultList = await client.vaults.list();
18
19
  out.stopSpinner();
19
- out.list(vaultList.map(v => ({ name: v.name, slug: v.slug, encrypted: v.encryptionEnabled ? 'yes' : 'no', description: v.description || 'No description', id: v.id })), {
20
+ out.list(vaultList.map(v => ({ name: v.name, slug: v.slug, encrypted: v.encryptionEnabled ? 'yes' : 'no', description: v.description ?? null, id: v.id })), {
20
21
  emptyMessage: 'No vaults found.',
21
22
  columns: [
22
23
  { key: 'name', header: 'Name' },
@@ -27,7 +28,8 @@ export function registerVaultCommands(program) {
27
28
  ],
28
29
  textFn: (v) => {
29
30
  const encIcon = v.encrypted === 'yes' ? chalk.green(' [encrypted]') : '';
30
- return `${chalk.cyan(String(v.name))} ${chalk.dim(`(${String(v.slug)})`)}${encIcon} -- ${String(v.description)}`;
31
+ const desc = v.description != null ? ` -- ${String(v.description)}` : '';
32
+ return `${chalk.cyan(String(v.name))} ${chalk.dim(`(${String(v.slug)})`)}${encIcon}${desc}`;
31
33
  },
32
34
  });
33
35
  }
@@ -160,12 +162,13 @@ EXAMPLES
160
162
  // vault tree
161
163
  addGlobalFlags(vaults.command('tree')
162
164
  .description('Show vault file tree')
163
- .argument('<vaultId>', 'Vault ID'))
165
+ .argument('<vaultId>', 'Vault ID or slug'))
164
166
  .action(async (vaultId, _opts) => {
165
167
  const flags = resolveFlags(_opts);
166
168
  const out = createOutput(flags);
167
169
  out.startSpinner('Fetching vault tree...');
168
170
  try {
171
+ vaultId = await resolveVaultId(vaultId);
169
172
  const client = await getClientAsync();
170
173
  const tree = await client.vaults.getTree(vaultId);
171
174
  out.stopSpinner();
@@ -2,11 +2,12 @@ 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 registerVersionCommands(program) {
6
7
  const versions = program.command('versions').description('View and manage document version history');
7
8
  addGlobalFlags(versions.command('list')
8
9
  .description('List version history for a document')
9
- .argument('<vaultId>', 'Vault ID')
10
+ .argument('<vaultId>', 'Vault ID or slug')
10
11
  .argument('<path>', 'Document path (e.g., notes/todo.md)')
11
12
  .addHelpText('after', `
12
13
  EXAMPLES
@@ -16,6 +17,7 @@ EXAMPLES
16
17
  const out = createOutput(flags);
17
18
  out.startSpinner('Fetching versions...');
18
19
  try {
20
+ vaultId = await resolveVaultId(vaultId);
19
21
  const client = await getClientAsync();
20
22
  const versionList = await client.documents.listVersions(vaultId, docPath);
21
23
  out.stopSpinner();
@@ -23,7 +25,7 @@ EXAMPLES
23
25
  version: v.versionNum,
24
26
  source: v.changeSource,
25
27
  sizeBytes: v.sizeBytes,
26
- pinned: v.isPinned ? 'yes' : 'no',
28
+ pinned: flags.output === 'json' ? v.isPinned : (v.isPinned ? 'yes' : 'no'),
27
29
  createdAt: v.createdAt,
28
30
  })), {
29
31
  emptyMessage: 'No versions found.',
@@ -49,7 +51,7 @@ EXAMPLES
49
51
  });
50
52
  addGlobalFlags(versions.command('view')
51
53
  .description('View content of a specific version')
52
- .argument('<vaultId>', 'Vault ID')
54
+ .argument('<vaultId>', 'Vault ID or slug')
53
55
  .argument('<path>', 'Document path')
54
56
  .argument('<version>', 'Version number')
55
57
  .addHelpText('after', `
@@ -65,6 +67,7 @@ EXAMPLES
65
67
  return;
66
68
  }
67
69
  try {
70
+ vaultId = await resolveVaultId(vaultId);
68
71
  const client = await getClientAsync();
69
72
  const version = await client.documents.getVersion(vaultId, docPath, versionNum);
70
73
  if (version.content === null) {
@@ -72,7 +75,12 @@ EXAMPLES
72
75
  process.exitCode = 1;
73
76
  return;
74
77
  }
75
- out.raw(version.content);
78
+ if (flags.output === 'json') {
79
+ out.raw(JSON.stringify({ version: version.versionNum, createdAt: version.createdAt, content: version.content }) + '\n');
80
+ }
81
+ else {
82
+ out.raw(version.content);
83
+ }
76
84
  }
77
85
  catch (err) {
78
86
  handleError(out, err, 'Failed to get version');
@@ -80,7 +88,7 @@ EXAMPLES
80
88
  });
81
89
  addGlobalFlags(versions.command('diff')
82
90
  .description('Show diff between two versions')
83
- .argument('<vaultId>', 'Vault ID')
91
+ .argument('<vaultId>', 'Vault ID or slug')
84
92
  .argument('<path>', 'Document path')
85
93
  .argument('<from>', 'Source version number')
86
94
  .argument('<to>', 'Target version number')
@@ -99,6 +107,7 @@ EXAMPLES
99
107
  }
100
108
  out.startSpinner('Computing diff...');
101
109
  try {
110
+ vaultId = await resolveVaultId(vaultId);
102
111
  const client = await getClientAsync();
103
112
  const diff = await client.documents.diffVersions(vaultId, docPath, from, to);
104
113
  out.stopSpinner();
@@ -121,12 +130,18 @@ EXAMPLES
121
130
  }
122
131
  }
123
132
  catch (err) {
124
- handleError(out, err, 'Failed to compute diff');
133
+ const message = err instanceof Error ? err.message : String(err);
134
+ if (message.includes('not found') || message.includes('Not found')) {
135
+ handleError(out, new Error(`Version ${from} or ${to} not found for document "${docPath}". Use 'versions list' to see available versions.`), 'Failed to compute diff');
136
+ }
137
+ else {
138
+ handleError(out, err, 'Failed to compute diff');
139
+ }
125
140
  }
126
141
  });
127
142
  addGlobalFlags(versions.command('restore')
128
143
  .description('Restore a document to a previous version')
129
- .argument('<vaultId>', 'Vault ID')
144
+ .argument('<vaultId>', 'Vault ID or slug')
130
145
  .argument('<path>', 'Document path')
131
146
  .argument('<version>', 'Version number to restore')
132
147
  .addHelpText('after', `
@@ -143,6 +158,7 @@ EXAMPLES
143
158
  }
144
159
  out.startSpinner(`Restoring to version ${versionNum}...`);
145
160
  try {
161
+ vaultId = await resolveVaultId(vaultId);
146
162
  const client = await getClientAsync();
147
163
  const doc = await client.documents.restoreVersion(vaultId, docPath, versionNum);
148
164
  out.success(`Restored ${chalk.cyan(docPath)} to version ${versionNum}`, {
@@ -156,7 +172,7 @@ EXAMPLES
156
172
  });
157
173
  addGlobalFlags(versions.command('pin')
158
174
  .description('Pin a version to prevent pruning')
159
- .argument('<vaultId>', 'Vault ID')
175
+ .argument('<vaultId>', 'Vault ID or slug')
160
176
  .argument('<path>', 'Document path')
161
177
  .argument('<version>', 'Version number to pin')
162
178
  .addHelpText('after', `
@@ -173,6 +189,7 @@ EXAMPLES
173
189
  }
174
190
  out.startSpinner(`Pinning version ${versionNum}...`);
175
191
  try {
192
+ vaultId = await resolveVaultId(vaultId);
176
193
  const client = await getClientAsync();
177
194
  await client.documents.pinVersion(vaultId, docPath, versionNum);
178
195
  out.success(`Pinned version ${versionNum} of ${chalk.cyan(docPath)}`, {
@@ -187,7 +204,7 @@ EXAMPLES
187
204
  });
188
205
  addGlobalFlags(versions.command('unpin')
189
206
  .description('Unpin a version, allowing it to be pruned')
190
- .argument('<vaultId>', 'Vault ID')
207
+ .argument('<vaultId>', 'Vault ID or slug')
191
208
  .argument('<path>', 'Document path')
192
209
  .argument('<version>', 'Version number to unpin')
193
210
  .addHelpText('after', `
@@ -204,6 +221,7 @@ EXAMPLES
204
221
  }
205
222
  out.startSpinner(`Unpinning version ${versionNum}...`);
206
223
  try {
224
+ vaultId = await resolveVaultId(vaultId);
207
225
  const client = await getClientAsync();
208
226
  await client.documents.unpinVersion(vaultId, docPath, versionNum);
209
227
  out.success(`Unpinned version ${versionNum} of ${chalk.cyan(docPath)}`, {