@verbatims/cli 0.1.16 → 0.1.18

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.
package/README.md CHANGED
@@ -23,9 +23,17 @@ verbatims quotes list --language fr --limit 10
23
23
  verbatims quotes list --reference 42
24
24
  verbatims quotes get 123 --format json
25
25
 
26
+ # Manage provenance (moderator/admin API key)
27
+ verbatims quotes attributions list 123
28
+ verbatims quotes attributions create 123 --author-id 1 --reference-id 2 --status manually_verified --primary
29
+ verbatims quotes sources list 123
30
+ verbatims quotes sources create 123 --source-type book --source-url https://example.com/edition --status externally_verified
31
+
26
32
  # Create a quote with a source
27
33
  verbatims quotes create --source-type book --source-url https://example.com/edition
28
34
 
35
+ The quote-level `--author-id`, `--reference-id`, `--source-type`, and `--source-url` options are deprecated compatibility options. New integrations should use the SDK `provenance` payload; the dedicated `quotes attributions` and `quotes sources` commands manage secondary provenance.
36
+
29
37
  # Browse interactively
30
38
  verbatims quotes browse
31
39
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@verbatims/cli",
3
- "version": "0.1.16",
3
+ "version": "0.1.18",
4
4
  "description": "CLI for the Verbatims quotes API",
5
5
  "type": "module",
6
6
  "bin": {
@@ -10,6 +10,13 @@ function getFormat(program: Command): Format {
10
10
  return (program.opts().format ?? 'table') as Format
11
11
  }
12
12
 
13
+ function warnLegacyQuoteOptions(options: Record<string, unknown>): void {
14
+ const fields = ['authorId', 'referenceId', 'sourceType', 'sourceUrl']
15
+ if (fields.some(field => options[field] !== undefined)) {
16
+ console.error(chalk.yellow('Warning: quote provenance options are deprecated; use the provenance API fields instead.'))
17
+ }
18
+ }
19
+
13
20
  export function registerQuotesCommand(program: Command) {
14
21
  const quotes = program.command('quotes').description('Manage quotes')
15
22
 
@@ -66,13 +73,14 @@ export function registerQuotesCommand(program: Command) {
66
73
  .description('Create a quote')
67
74
  .option('--name <text>', 'Quote text')
68
75
  .option('--language <lang>', 'Language')
69
- .option('--author-id <id>', 'Author ID')
70
- .option('--reference-id <id>', 'Reference ID')
71
- .option('--source-type <type>', 'Source type')
72
- .option('--source-url <url>', 'Source URL')
76
+ .option('--author-id <id>', 'Author ID (deprecated)')
77
+ .option('--reference-id <id>', 'Reference ID (deprecated)')
78
+ .option('--source-type <type>', 'Source type (deprecated)')
79
+ .option('--source-url <url>', 'Source URL (deprecated)')
73
80
  .action(async (options: Record<string, string>) => {
74
81
  const client = await getClient()
75
82
  const format = getFormat(program)
83
+ warnLegacyQuoteOptions(options)
76
84
 
77
85
  const name = options.name ?? await text({ message: 'Quote text', validate: (v) => !v ? 'Required' : undefined })
78
86
  if (isCancel(name)) cancel('Cancelled')
@@ -84,7 +92,16 @@ export function registerQuotesCommand(program: Command) {
84
92
  const referenceId = options.referenceId ? Number(options.referenceId) : undefined
85
93
 
86
94
  const { data } = await withSpinner('Creating quote', () =>
87
- client.quotes.create({ content: name as string, name: name as string, language: language as string, author_id: authorId, reference_id: referenceId, source_type: options.sourceType, source_url: options.sourceUrl }),
95
+ client.quotes.create({
96
+ content: name as string,
97
+ name: name as string,
98
+ language: language as string,
99
+ provenance: {
100
+ author_id: authorId,
101
+ reference_id: referenceId,
102
+ source: options.sourceType ? { source_type: options.sourceType, source_url: options.sourceUrl } : undefined,
103
+ },
104
+ }),
88
105
  format,
89
106
  )
90
107
  console.log(chalk.green(' Quote created'))
@@ -96,21 +113,26 @@ export function registerQuotesCommand(program: Command) {
96
113
  .description('Update a quote')
97
114
  .option('--name <text>', 'Quote text')
98
115
  .option('--language <lang>', 'Language')
99
- .option('--author-id <id>', 'Author ID')
100
- .option('--reference-id <id>', 'Reference ID')
101
- .option('--source-type <type>', 'Source type')
102
- .option('--source-url <url>', 'Source URL')
116
+ .option('--author-id <id>', 'Author ID (deprecated)')
117
+ .option('--reference-id <id>', 'Reference ID (deprecated)')
118
+ .option('--source-type <type>', 'Source type (deprecated)')
119
+ .option('--source-url <url>', 'Source URL (deprecated)')
103
120
  .action(async (id: string, options: Record<string, string>) => {
104
121
  const client = await getClient()
105
122
  const format = getFormat(program)
123
+ warnLegacyQuoteOptions(options)
106
124
 
107
125
  const data_: Record<string, unknown> = {}
108
126
  if (options.name) { data_.name = options.name; data_.content = options.name }
109
127
  if (options.language) data_.language = options.language
110
- if (options.authorId !== undefined) data_.author_id = options.authorId === 'null' ? null : Number(options.authorId)
111
- if (options.referenceId !== undefined) data_.reference_id = options.referenceId === 'null' ? null : Number(options.referenceId)
112
- if (options.sourceType !== undefined) data_.source_type = options.sourceType
113
- if (options.sourceUrl !== undefined) data_.source_url = options.sourceUrl
128
+ if (options.authorId !== undefined) data_.provenance = { ...(data_.provenance as object), author_id: options.authorId === 'null' ? null : Number(options.authorId) }
129
+ if (options.referenceId !== undefined) data_.provenance = { ...(data_.provenance as object), reference_id: options.referenceId === 'null' ? null : Number(options.referenceId) }
130
+ if (options.sourceType !== undefined || options.sourceUrl !== undefined) {
131
+ data_.provenance = {
132
+ ...(data_.provenance as object),
133
+ source: { source_type: options.sourceType ?? 'manual', source_url: options.sourceUrl },
134
+ }
135
+ }
114
136
 
115
137
  const { data } = await withSpinner('Updating quote', () => client.quotes.update(Number(id), data_ as any), format)
116
138
  console.log(chalk.green(' Quote updated'))
@@ -206,6 +228,123 @@ export function registerQuotesCommand(program: Command) {
206
228
  )
207
229
  })
208
230
 
231
+ quotes
232
+ .command('attributions <action> <quoteId> [attributionId]')
233
+ .description('Manage quote attributions (moderator/admin API key required)')
234
+ .option('--author-id <id>', 'Author ID')
235
+ .option('--reference-id <id>', 'Reference ID')
236
+ .option('--status <status>', 'Verification status')
237
+ .option('--notes <text>', 'Internal notes')
238
+ .option('--primary', 'Set as primary attribution')
239
+ .option('--yes', 'Skip confirmation')
240
+ .action(async (action: string, quoteId: string, attributionId: string | undefined, options: Record<string, any>) => {
241
+ const client = await getClient()
242
+ const format = getFormat(program)
243
+ const id = Number(quoteId)
244
+
245
+ if (action === 'list') {
246
+ const { data } = await withSpinner('Fetching attributions', () => client.quotes.listAttributions(id), format)
247
+ output(data, format)
248
+ return
249
+ }
250
+ if (action === 'create') {
251
+ const { data } = await withSpinner('Creating attribution', () => client.quotes.createAttribution(id, {
252
+ author_id: options.authorId ? Number(options.authorId) : null,
253
+ reference_id: options.referenceId ? Number(options.referenceId) : null,
254
+ status: options.status,
255
+ notes: options.notes,
256
+ is_primary: options.primary,
257
+ }), format)
258
+ output(data, format)
259
+ return
260
+ }
261
+ if (!attributionId) throw new Error('Attribution ID is required')
262
+ const attribution = Number(attributionId)
263
+ if (action === 'update') {
264
+ const data_: Record<string, unknown> = {}
265
+ if (options.authorId !== undefined) data_.author_id = Number(options.authorId)
266
+ if (options.referenceId !== undefined) data_.reference_id = Number(options.referenceId)
267
+ if (options.status !== undefined) data_.status = options.status
268
+ if (options.notes !== undefined) data_.notes = options.notes
269
+ if (options.primary !== undefined) data_.is_primary = options.primary
270
+ const { data } = await withSpinner('Updating attribution', () => client.quotes.updateAttribution(id, attribution, data_), format)
271
+ output(data, format)
272
+ return
273
+ }
274
+ if (action === 'delete') {
275
+ if (!options.yes) {
276
+ const confirmed = await confirm({ message: `Delete attribution #${attribution} from quote #${id}?` })
277
+ if (isCancel(confirmed) || !confirmed) cancel('Cancelled')
278
+ }
279
+ await withSpinner('Deleting attribution', () => client.quotes.deleteAttribution(id, attribution), format)
280
+ console.log(chalk.green(' Attribution deleted'))
281
+ return
282
+ }
283
+ throw new Error('Action must be list, create, update, or delete')
284
+ })
285
+
286
+ quotes
287
+ .command('sources <action> <quoteId> [sourceId]')
288
+ .description('Manage quote sources (moderator/admin API key required)')
289
+ .option('--attribution-id <id>', 'Attribution ID')
290
+ .option('--source-type <type>', 'Source type')
291
+ .option('--source-url <url>', 'Source URL')
292
+ .option('--label <label>', 'Source label')
293
+ .option('--status <status>', 'Verification status')
294
+ .option('--notes <text>', 'Internal notes')
295
+ .option('--primary', 'Set as primary source')
296
+ .option('--yes', 'Skip confirmation')
297
+ .action(async (action: string, quoteId: string, sourceId: string | undefined, options: Record<string, any>) => {
298
+ const client = await getClient()
299
+ const format = getFormat(program)
300
+ const id = Number(quoteId)
301
+
302
+ if (action === 'list') {
303
+ const { data } = await withSpinner('Fetching sources', () => client.quotes.listSources(id), format)
304
+ output(data, format)
305
+ return
306
+ }
307
+ if (action === 'create') {
308
+ if (!options.sourceType) throw new Error('--source-type is required')
309
+ const { data } = await withSpinner('Creating source', () => client.quotes.createSource(id, {
310
+ attribution_id: options.attributionId ? Number(options.attributionId) : null,
311
+ source_type: options.sourceType,
312
+ source_url: options.sourceUrl,
313
+ label: options.label,
314
+ verification_status: options.status,
315
+ notes: options.notes,
316
+ is_primary: options.primary,
317
+ }), format)
318
+ output(data, format)
319
+ return
320
+ }
321
+ if (!sourceId) throw new Error('Source ID is required')
322
+ const source = Number(sourceId)
323
+ if (action === 'update') {
324
+ const data_: Record<string, unknown> = {}
325
+ if (options.attributionId !== undefined) data_.attribution_id = Number(options.attributionId)
326
+ if (options.sourceType !== undefined) data_.source_type = options.sourceType
327
+ if (options.sourceUrl !== undefined) data_.source_url = options.sourceUrl
328
+ if (options.label !== undefined) data_.label = options.label
329
+ if (options.status !== undefined) data_.verification_status = options.status
330
+ if (options.notes !== undefined) data_.notes = options.notes
331
+ if (options.primary !== undefined) data_.is_primary = options.primary
332
+ const { data } = await withSpinner('Updating source', () => client.quotes.updateSource(id, source, data_), format)
333
+ output(data, format)
334
+ return
335
+ }
336
+ if (action === 'delete') {
337
+ if (!options.yes) {
338
+ const confirmed = await confirm({ message: `Delete source #${source} from quote #${id}?` })
339
+ if (isCancel(confirmed) || !confirmed) cancel('Cancelled')
340
+ }
341
+ await withSpinner('Deleting source', () => client.quotes.deleteSource(id, source), format)
342
+ console.log(chalk.green(' Source deleted'))
343
+ return
344
+ }
345
+ throw new Error('Action must be list, create, update, or delete')
346
+ })
347
+
209
348
  const tags = quotes.command('tags').description('Manage quote tags')
210
349
 
211
350
  tags