@verbatims/cli 0.1.16 → 0.1.17

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,6 +23,12 @@ 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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@verbatims/cli",
3
- "version": "0.1.16",
3
+ "version": "0.1.17",
4
4
  "description": "CLI for the Verbatims quotes API",
5
5
  "type": "module",
6
6
  "bin": {
@@ -84,7 +84,16 @@ export function registerQuotesCommand(program: Command) {
84
84
  const referenceId = options.referenceId ? Number(options.referenceId) : undefined
85
85
 
86
86
  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 }),
87
+ client.quotes.create({
88
+ content: name as string,
89
+ name: name as string,
90
+ language: language as string,
91
+ provenance: {
92
+ author_id: authorId,
93
+ reference_id: referenceId,
94
+ source: options.sourceType ? { source_type: options.sourceType, source_url: options.sourceUrl } : undefined,
95
+ },
96
+ }),
88
97
  format,
89
98
  )
90
99
  console.log(chalk.green(' Quote created'))
@@ -107,10 +116,14 @@ export function registerQuotesCommand(program: Command) {
107
116
  const data_: Record<string, unknown> = {}
108
117
  if (options.name) { data_.name = options.name; data_.content = options.name }
109
118
  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
119
+ if (options.authorId !== undefined) data_.provenance = { ...(data_.provenance as object), author_id: options.authorId === 'null' ? null : Number(options.authorId) }
120
+ if (options.referenceId !== undefined) data_.provenance = { ...(data_.provenance as object), reference_id: options.referenceId === 'null' ? null : Number(options.referenceId) }
121
+ if (options.sourceType !== undefined || options.sourceUrl !== undefined) {
122
+ data_.provenance = {
123
+ ...(data_.provenance as object),
124
+ source: { source_type: options.sourceType ?? 'manual', source_url: options.sourceUrl },
125
+ }
126
+ }
114
127
 
115
128
  const { data } = await withSpinner('Updating quote', () => client.quotes.update(Number(id), data_ as any), format)
116
129
  console.log(chalk.green(' Quote updated'))
@@ -206,6 +219,123 @@ export function registerQuotesCommand(program: Command) {
206
219
  )
207
220
  })
208
221
 
222
+ quotes
223
+ .command('attributions <action> <quoteId> [attributionId]')
224
+ .description('Manage quote attributions (moderator/admin API key required)')
225
+ .option('--author-id <id>', 'Author ID')
226
+ .option('--reference-id <id>', 'Reference ID')
227
+ .option('--status <status>', 'Verification status')
228
+ .option('--notes <text>', 'Internal notes')
229
+ .option('--primary', 'Set as primary attribution')
230
+ .option('--yes', 'Skip confirmation')
231
+ .action(async (action: string, quoteId: string, attributionId: string | undefined, options: Record<string, any>) => {
232
+ const client = await getClient()
233
+ const format = getFormat(program)
234
+ const id = Number(quoteId)
235
+
236
+ if (action === 'list') {
237
+ const { data } = await withSpinner('Fetching attributions', () => client.quotes.listAttributions(id), format)
238
+ output(data, format)
239
+ return
240
+ }
241
+ if (action === 'create') {
242
+ const { data } = await withSpinner('Creating attribution', () => client.quotes.createAttribution(id, {
243
+ author_id: options.authorId ? Number(options.authorId) : null,
244
+ reference_id: options.referenceId ? Number(options.referenceId) : null,
245
+ status: options.status,
246
+ notes: options.notes,
247
+ is_primary: options.primary,
248
+ }), format)
249
+ output(data, format)
250
+ return
251
+ }
252
+ if (!attributionId) throw new Error('Attribution ID is required')
253
+ const attribution = Number(attributionId)
254
+ if (action === 'update') {
255
+ const data_: Record<string, unknown> = {}
256
+ if (options.authorId !== undefined) data_.author_id = Number(options.authorId)
257
+ if (options.referenceId !== undefined) data_.reference_id = Number(options.referenceId)
258
+ if (options.status !== undefined) data_.status = options.status
259
+ if (options.notes !== undefined) data_.notes = options.notes
260
+ if (options.primary !== undefined) data_.is_primary = options.primary
261
+ const { data } = await withSpinner('Updating attribution', () => client.quotes.updateAttribution(id, attribution, data_), format)
262
+ output(data, format)
263
+ return
264
+ }
265
+ if (action === 'delete') {
266
+ if (!options.yes) {
267
+ const confirmed = await confirm({ message: `Delete attribution #${attribution} from quote #${id}?` })
268
+ if (isCancel(confirmed) || !confirmed) cancel('Cancelled')
269
+ }
270
+ await withSpinner('Deleting attribution', () => client.quotes.deleteAttribution(id, attribution), format)
271
+ console.log(chalk.green(' Attribution deleted'))
272
+ return
273
+ }
274
+ throw new Error('Action must be list, create, update, or delete')
275
+ })
276
+
277
+ quotes
278
+ .command('sources <action> <quoteId> [sourceId]')
279
+ .description('Manage quote sources (moderator/admin API key required)')
280
+ .option('--attribution-id <id>', 'Attribution ID')
281
+ .option('--source-type <type>', 'Source type')
282
+ .option('--source-url <url>', 'Source URL')
283
+ .option('--label <label>', 'Source label')
284
+ .option('--status <status>', 'Verification status')
285
+ .option('--notes <text>', 'Internal notes')
286
+ .option('--primary', 'Set as primary source')
287
+ .option('--yes', 'Skip confirmation')
288
+ .action(async (action: string, quoteId: string, sourceId: string | undefined, options: Record<string, any>) => {
289
+ const client = await getClient()
290
+ const format = getFormat(program)
291
+ const id = Number(quoteId)
292
+
293
+ if (action === 'list') {
294
+ const { data } = await withSpinner('Fetching sources', () => client.quotes.listSources(id), format)
295
+ output(data, format)
296
+ return
297
+ }
298
+ if (action === 'create') {
299
+ if (!options.sourceType) throw new Error('--source-type is required')
300
+ const { data } = await withSpinner('Creating source', () => client.quotes.createSource(id, {
301
+ attribution_id: options.attributionId ? Number(options.attributionId) : null,
302
+ source_type: options.sourceType,
303
+ source_url: options.sourceUrl,
304
+ label: options.label,
305
+ verification_status: options.status,
306
+ notes: options.notes,
307
+ is_primary: options.primary,
308
+ }), format)
309
+ output(data, format)
310
+ return
311
+ }
312
+ if (!sourceId) throw new Error('Source ID is required')
313
+ const source = Number(sourceId)
314
+ if (action === 'update') {
315
+ const data_: Record<string, unknown> = {}
316
+ if (options.attributionId !== undefined) data_.attribution_id = Number(options.attributionId)
317
+ if (options.sourceType !== undefined) data_.source_type = options.sourceType
318
+ if (options.sourceUrl !== undefined) data_.source_url = options.sourceUrl
319
+ if (options.label !== undefined) data_.label = options.label
320
+ if (options.status !== undefined) data_.verification_status = options.status
321
+ if (options.notes !== undefined) data_.notes = options.notes
322
+ if (options.primary !== undefined) data_.is_primary = options.primary
323
+ const { data } = await withSpinner('Updating source', () => client.quotes.updateSource(id, source, data_), format)
324
+ output(data, format)
325
+ return
326
+ }
327
+ if (action === 'delete') {
328
+ if (!options.yes) {
329
+ const confirmed = await confirm({ message: `Delete source #${source} from quote #${id}?` })
330
+ if (isCancel(confirmed) || !confirmed) cancel('Cancelled')
331
+ }
332
+ await withSpinner('Deleting source', () => client.quotes.deleteSource(id, source), format)
333
+ console.log(chalk.green(' Source deleted'))
334
+ return
335
+ }
336
+ throw new Error('Action must be list, create, update, or delete')
337
+ })
338
+
209
339
  const tags = quotes.command('tags').description('Manage quote tags')
210
340
 
211
341
  tags