@purveyors/cli 0.35.1 → 0.36.1

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 (49) hide show
  1. package/README.md +51 -12
  2. package/dist/commands/auth.d.ts.map +1 -1
  3. package/dist/commands/auth.js +6 -6
  4. package/dist/commands/auth.js.map +1 -1
  5. package/dist/commands/catalog.d.ts.map +1 -1
  6. package/dist/commands/catalog.js +84 -89
  7. package/dist/commands/catalog.js.map +1 -1
  8. package/dist/commands/context.js +2 -2
  9. package/dist/commands/context.js.map +1 -1
  10. package/dist/commands/inventory.js +32 -32
  11. package/dist/commands/inventory.js.map +1 -1
  12. package/dist/commands/manifest.js +3 -3
  13. package/dist/commands/manifest.js.map +1 -1
  14. package/dist/commands/market.d.ts.map +1 -1
  15. package/dist/commands/market.js +24 -24
  16. package/dist/commands/market.js.map +1 -1
  17. package/dist/commands/price-index.d.ts.map +1 -1
  18. package/dist/commands/price-index.js +17 -17
  19. package/dist/commands/price-index.js.map +1 -1
  20. package/dist/commands/procurement.d.ts.map +1 -1
  21. package/dist/commands/procurement.js +2 -2
  22. package/dist/commands/procurement.js.map +1 -1
  23. package/dist/commands/reference-profile.d.ts.map +1 -1
  24. package/dist/commands/reference-profile.js +15 -15
  25. package/dist/commands/reference-profile.js.map +1 -1
  26. package/dist/commands/roast.d.ts.map +1 -1
  27. package/dist/commands/roast.js +50 -50
  28. package/dist/commands/roast.js.map +1 -1
  29. package/dist/commands/sales.js +21 -21
  30. package/dist/commands/sales.js.map +1 -1
  31. package/dist/commands/skill.d.ts +3 -0
  32. package/dist/commands/skill.d.ts.map +1 -0
  33. package/dist/commands/skill.js +145 -0
  34. package/dist/commands/skill.js.map +1 -0
  35. package/dist/commands/tasting.d.ts.map +1 -1
  36. package/dist/commands/tasting.js +16 -16
  37. package/dist/commands/tasting.js.map +1 -1
  38. package/dist/lib/agent-skill.d.ts +110 -0
  39. package/dist/lib/agent-skill.d.ts.map +1 -0
  40. package/dist/lib/agent-skill.js +611 -0
  41. package/dist/lib/agent-skill.js.map +1 -0
  42. package/dist/lib/manifest.d.ts +2 -0
  43. package/dist/lib/manifest.d.ts.map +1 -1
  44. package/dist/lib/manifest.js +992 -351
  45. package/dist/lib/manifest.js.map +1 -1
  46. package/dist/program.d.ts.map +1 -1
  47. package/dist/program.js +68 -12
  48. package/dist/program.js.map +1 -1
  49. package/package.json +1 -1
@@ -23,11 +23,17 @@ const roles = [
23
23
  },
24
24
  ];
25
25
  const globalOptions = [
26
- { flags: '--json', description: 'Output compact JSON explicitly' },
27
- { flags: '--pretty', description: 'Pretty-print JSON output with colors' },
28
- { flags: '--csv', description: 'Output array results as CSV where supported' },
29
- { flags: '--help', description: 'Show help for any command' },
30
- { flags: '--version', description: 'Show version number' },
26
+ {
27
+ flags: '--json',
28
+ description: 'Print results as compact JSON, the default for data commands; use it to be explicit',
29
+ },
30
+ { flags: '--pretty', description: 'Print results as indented, colorized JSON for reading' },
31
+ {
32
+ flags: '--csv',
33
+ description: 'Print list results as CSV on commands that support it',
34
+ },
35
+ { flags: '--help', description: 'Show help for purvey or any command' },
36
+ { flags: '--version', description: 'Print the installed CLI version' },
31
37
  ];
32
38
  const outputModes = [
33
39
  { mode: 'json', description: 'compact JSON, suitable for agents and scripts' },
@@ -62,7 +68,7 @@ const exitCodes = [
62
68
  const idTypes = [
63
69
  {
64
70
  name: 'catalog_id',
65
- source: 'coffee_catalog row',
71
+ source: 'a coffee listed in the Purveyors catalog',
66
72
  usedBy: [
67
73
  'catalog get',
68
74
  'catalog similar',
@@ -73,7 +79,7 @@ const idTypes = [
73
79
  },
74
80
  {
75
81
  name: 'inventory_id',
76
- source: 'green_coffee_inv row',
82
+ source: 'a coffee in your green inventory',
77
83
  usedBy: [
78
84
  'inventory get/update/delete',
79
85
  'roast --coffee-id',
@@ -83,7 +89,7 @@ const idTypes = [
83
89
  },
84
90
  {
85
91
  name: 'roast_id',
86
- source: 'roast_data row',
92
+ source: 'one of your roast profiles',
87
93
  usedBy: [
88
94
  'roast get/chart/delete',
89
95
  'roast list --roast-id',
@@ -93,12 +99,12 @@ const idTypes = [
93
99
  },
94
100
  {
95
101
  name: 'sale_id',
96
- source: 'coffee_sales row',
102
+ source: 'one of your recorded sales',
97
103
  usedBy: ['sales update/delete'],
98
104
  },
99
105
  {
100
106
  name: 'reference_profile_id',
101
- source: 'owner-scoped Studio reference profile',
107
+ source: 'one of your Studio reference profiles',
102
108
  usedBy: [
103
109
  'reference-profile get',
104
110
  'reference-profile chart',
@@ -118,10 +124,12 @@ const idTypes = [
118
124
  ],
119
125
  },
120
126
  ];
127
+ const PROCESS_FILTER_ACCESS = 'exact match; member access required';
128
+ const SIGNED_IN_SIMILAR_ACCESS = 'Signed in with `purvey auth login`, your account needs member access. An API key in PURVEYORS_API_KEY or PARCHMENT_API_KEY works on any API plan when it has the catalog:read scope.';
121
129
  const commandGroups = [
122
130
  {
123
131
  name: 'auth',
124
- summary: 'Manage authentication with purveyors.io',
132
+ summary: 'Sign in to purveyors.io, check your login, and sign out',
125
133
  auth: 'none',
126
134
  subcommands: [
127
135
  {
@@ -130,7 +138,10 @@ const commandGroups = [
130
138
  auth: 'none',
131
139
  sdkMethods: ['cliAuth.create', 'cliAuth.exchange'],
132
140
  options: [
133
- { flags: '--headless', description: 'Print approval URL without opening a browser' },
141
+ {
142
+ flags: '--headless',
143
+ description: 'Print the approval URL instead of opening a browser; use it for agents, SSH sessions, and remote machines. The CLI finishes on its own once you approve',
144
+ },
134
145
  ],
135
146
  examples: ['purvey auth login', 'purvey auth login --headless'],
136
147
  },
@@ -139,7 +150,10 @@ const commandGroups = [
139
150
  summary: 'Show current login status and role',
140
151
  auth: 'none',
141
152
  sdkMethods: ['me'],
142
- options: [{ flags: '--pretty' }, { flags: '--csv' }],
153
+ options: [
154
+ { flags: '--pretty', description: 'Print the status as indented, colorized JSON' },
155
+ { flags: '--csv', description: 'Print the status as a CSV row' },
156
+ ],
143
157
  examples: [
144
158
  'purvey auth status',
145
159
  'purvey auth status --pretty',
@@ -148,11 +162,11 @@ const commandGroups = [
148
162
  },
149
163
  {
150
164
  name: 'whoami',
151
- summary: 'Show the canonical identity, plan, scopes, and capabilities for the active credential',
165
+ summary: 'Show the identity, plan, scopes, and capabilities of the active credential',
152
166
  auth: 'none',
153
167
  sdkMethods: ['me'],
154
168
  notes: [
155
- 'Prints the GET /v1/me response unchanged, including capabilities.profileStudio.',
169
+ 'Prints the account details unchanged: authenticated, userId, appRoles, primaryAppRole, apiPlan, ppiAccess, apiScopes, and capabilities (capabilities.profileStudio shows Studio access).',
156
170
  'Uses PARCHMENT_API_KEY or PURVEYORS_API_KEY when set, otherwise the stored login key.',
157
171
  'Without a credential it prints the anonymous principal (authenticated: false).',
158
172
  ],
@@ -171,7 +185,7 @@ const commandGroups = [
171
185
  },
172
186
  {
173
187
  name: 'catalog',
174
- summary: 'Browse the coffee catalog; structured process filters require member access',
188
+ summary: 'Search, rank, and compare catalog coffees and suppliers; structured process filters need member access',
175
189
  auth: 'viewer',
176
190
  subcommands: [
177
191
  {
@@ -180,57 +194,103 @@ const commandGroups = [
180
194
  auth: 'viewer',
181
195
  sdkMethods: ['catalog.list'],
182
196
  options: [
183
- { flags: '--origin <origin>' },
184
- { flags: '--process <method>' },
185
- { flags: '--processing-base-method <method>' },
186
- { flags: '--fermentation-type <type>' },
187
- { flags: '--process-additive <additive>' },
188
- { flags: '--processing-disclosure-level <level>' },
189
- { flags: '--processing-confidence-min <n>' },
190
- { flags: '--price-min <n>' },
191
- { flags: '--price-max <n>' },
192
- { flags: '--name <text>' },
193
- { flags: '--ids <n,n,...>' },
194
- { flags: '--variety <text>' },
195
- { flags: '--stocked-days <n>' },
197
+ {
198
+ flags: '--origin <origin>',
199
+ description: 'Only coffees from this origin; matches continent, country, or region names, case-insensitive and partial',
200
+ },
201
+ {
202
+ flags: '--process <method>',
203
+ description: 'Only coffees whose processing label contains this text, such as natural or washed (case-insensitive)',
204
+ },
205
+ {
206
+ flags: '--processing-base-method <method>',
207
+ description: `Only coffees with this structured base process, such as Natural or Washed; ${PROCESS_FILTER_ACCESS}. List values with \`purvey catalog facets processing_base_method\``,
208
+ },
209
+ {
210
+ flags: '--fermentation-type <type>',
211
+ description: `Only coffees with this structured fermentation type, such as Anaerobic; ${PROCESS_FILTER_ACCESS}. List values with \`purvey catalog facets fermentation_type\``,
212
+ },
213
+ {
214
+ flags: '--process-additive <additive>',
215
+ description: `Only coffees whose process discloses this additive, such as hops; ${PROCESS_FILTER_ACCESS}`,
216
+ },
217
+ {
218
+ flags: '--processing-disclosure-level <level>',
219
+ description: `Only coffees whose supplier disclosed process details at this level; ${PROCESS_FILTER_ACCESS}`,
220
+ },
221
+ {
222
+ flags: '--processing-confidence-min <n>',
223
+ description: 'Only coffees whose process details were identified with at least this confidence; member access required',
224
+ minimum: 0,
225
+ maximum: 1,
226
+ },
227
+ {
228
+ flags: '--price-min <n>',
229
+ description: 'Lowest price per pound to include, in US dollars',
230
+ },
231
+ {
232
+ flags: '--price-max <n>',
233
+ description: 'Highest price per pound to include, in US dollars',
234
+ },
235
+ {
236
+ flags: '--name <text>',
237
+ description: 'Only coffees whose name contains this text (case-insensitive)',
238
+ },
239
+ {
240
+ flags: '--ids <n,n,...>',
241
+ description: 'Fetch these catalog IDs, comma-separated, instead of searching; --limit and --offset are ignored',
242
+ },
243
+ {
244
+ flags: '--variety <text>',
245
+ description: 'Only coffees whose variety or cultivar contains this text (case-insensitive)',
246
+ },
247
+ {
248
+ flags: '--stocked-days <n>',
249
+ description: 'Only coffees that came into stock within the last N days',
250
+ },
196
251
  {
197
252
  flags: '--supplier <name>',
198
- description: 'Canonical /v1/catalog supplier filter (partial source-name match)',
253
+ description: 'Only coffees from suppliers whose name contains this text (case-insensitive)',
199
254
  },
200
255
  {
201
256
  flags: '--drying-method <method>',
202
- description: 'Canonical /v1/catalog dryingMethod filter; Parchment owns matching',
257
+ description: 'Only coffees whose drying method or processing text contains this value, such as raised bed (case-insensitive)',
203
258
  },
204
259
  {
205
260
  flags: '--flavor <keywords>',
206
- description: 'Comma-separated canonical /v1/catalog flavorKeywords; a row matches any keyword',
261
+ description: 'Comma-separated flavor keywords searched in coffee descriptions and tasting notes; a coffee matches if it mentions any of them',
262
+ },
263
+ {
264
+ flags: '--stocked',
265
+ description: 'Only coffees currently in stock; without it, results also include coffees no longer stocked',
266
+ },
267
+ {
268
+ flags: '--sort <field>',
269
+ description: 'Sort by price (cheapest first), price-desc, name, or origin (by country); without it, the most recently stocked coffees come first',
270
+ },
271
+ {
272
+ flags: '--offset <n>',
273
+ description: 'Number of results to skip when paging; must be a multiple of --limit',
274
+ defaultValue: 0,
207
275
  },
208
- { flags: '--stocked' },
209
- { flags: '--sort <field>' },
210
- { flags: '--offset <n>', defaultValue: 0 },
211
276
  {
212
277
  flags: '--limit <n>',
213
- description: `Maximum results to return, ${CLI_NUMERIC_BOUNDS.catalogSearchLimit.minimum}-${CLI_NUMERIC_BOUNDS.catalogSearchLimit.maximum}`,
278
+ description: 'Maximum number of coffees to return',
214
279
  defaultValue: 10,
215
280
  minimum: CLI_NUMERIC_BOUNDS.catalogSearchLimit.minimum,
216
281
  maximum: CLI_NUMERIC_BOUNDS.catalogSearchLimit.maximum,
217
282
  },
218
283
  {
219
284
  flags: '--include-proof',
220
- description: 'Request canonical proof summaries from /v1/catalog?include=proof',
221
- notes: [
222
- 'Consumes the API proof summary; the CLI does not compute proof fields locally.',
223
- 'The SDK supplies the canonical proof-summary-v1 row projection.',
224
- ],
285
+ description: 'Add a proof summary to each coffee: how well its process, provenance, freshness, and pricing details are supported (strong, partial, limited, or not available)',
225
286
  },
226
287
  ],
227
288
  notes: [
228
289
  'All filters are optional. Without flags, returns up to --limit results.',
229
- 'Structured process filters map to canonical /v1/catalog query names.',
230
290
  'Structured process filters require member access through the scoped API key created by `purvey auth login` or an explicit environment override.',
231
291
  '--ids fetches specific catalog items by ID, ignoring --limit and --offset.',
232
292
  '--offset + --limit enables pagination through large result sets.',
233
- '--include-proof uses the canonical /v1/catalog proof summary include and preserves the default output shape when omitted.',
293
+ 'Without --include-proof, the output shape is unchanged.',
234
294
  'PURVEYORS_API_KEY or PARCHMENT_API_KEY overrides the scoped API key stored by `purvey auth login`.',
235
295
  ],
236
296
  examples: [
@@ -252,7 +312,7 @@ const commandGroups = [
252
312
  {
253
313
  name: 'catalog_id',
254
314
  cliToken: 'id',
255
- description: 'coffee_catalog.catalog_id',
315
+ description: 'Catalog ID of the coffee',
256
316
  required: true,
257
317
  idType: 'catalog_id',
258
318
  },
@@ -260,10 +320,7 @@ const commandGroups = [
260
320
  options: [
261
321
  {
262
322
  flags: '--include-proof',
263
- description: 'Request the canonical proof summary from /v1/catalog?include=proof',
264
- notes: [
265
- 'Consumes the API proof summary; the CLI does not compute proof fields locally.',
266
- ],
323
+ description: "Add the proof summary: how well the coffee's process, provenance, freshness, and pricing details are supported",
267
324
  },
268
325
  ],
269
326
  examples: [
@@ -293,14 +350,14 @@ const commandGroups = [
293
350
  options: [
294
351
  {
295
352
  flags: '--all',
296
- description: 'Use all visible catalog rows instead of the default stocked-only scope.',
353
+ description: 'Count every coffee you can see, including ones no longer stocked; without it, only coffees currently in stock are counted',
297
354
  },
298
355
  ],
299
356
  notes: [
300
- 'Without a field, prints the canonical /v1/catalog/facets envelope (values, facets, meta) unchanged.',
301
- "With a field, prints { field, facet, data, meta }: that facet's counted values and Parchment's meta.",
302
- 'Counts are computed by Parchment; counts for multi-valued dimensions can overlap, so do not sum them.',
303
- 'Defaults to currently stocked catalog rows; use --all for all visible rows.',
357
+ 'Without a field, prints every facet with its counted values and meta (values, facets, meta).',
358
+ "With a field, prints { field, facet, data, meta }: that facet's counted values and meta.",
359
+ 'Counts for multi-valued dimensions can overlap, so do not sum them.',
360
+ 'Defaults to currently stocked coffees; use --all for every coffee you can see.',
304
361
  ],
305
362
  examples: [
306
363
  'purvey catalog facets supplier --pretty',
@@ -314,30 +371,55 @@ const commandGroups = [
314
371
  auth: 'viewer',
315
372
  sdkMethods: ['catalog.rank'],
316
373
  options: [
317
- { flags: '--objective <objective>', defaultValue: 'premium' },
374
+ {
375
+ flags: '--objective <objective>',
376
+ description: 'What to rank for: premium (highest Purveyor Score), value (score per dollar), fresh_arrival (newest arrivals), or rare_origin (least common origins in the sample)',
377
+ defaultValue: 'premium',
378
+ },
318
379
  {
319
380
  flags: '--supplier <name>',
320
- description: 'Canonical /v1/catalog/rank supplier filter',
381
+ description: 'Only coffees from suppliers whose name contains this text',
382
+ },
383
+ { flags: '--country <country>', description: 'Only coffees from this country' },
384
+ {
385
+ flags: '--process <method>',
386
+ description: 'Only coffees with this processing method, such as washed or natural',
387
+ },
388
+ {
389
+ flags: '--stocked',
390
+ description: 'Only coffees currently in stock; this is already the default unless you pass --all',
321
391
  },
322
- { flags: '--country <country>' },
323
- { flags: '--process <method>' },
324
- { flags: '--stocked' },
325
392
  {
326
393
  flags: '--all',
327
- description: 'Use all visible catalog rows instead of the default stocked-only scope.',
394
+ description: 'Rank every coffee you can see, including ones no longer stocked, instead of only coffees in stock',
328
395
  },
329
- { flags: '--price-max <n>' },
330
- { flags: '--min-score <n>' },
396
+ {
397
+ flags: '--price-max <n>',
398
+ description: 'Highest price per pound to include, in US dollars',
399
+ },
400
+ { flags: '--min-score <n>', description: 'Lowest Purveyor Score to include' },
331
401
  {
332
402
  flags: '--non-wholesale-only',
333
- description: 'Apply a query-level filter for non-wholesale or unknown-wholesale listings before sampling.',
403
+ description: 'Leave out wholesale listings, keeping retail and listings with unknown wholesale status, before sampling',
404
+ },
405
+ {
406
+ flags: '--sample-size <n>',
407
+ description: 'Number of matching coffees to consider before ranking; a smaller sample is faster but may miss candidates',
408
+ defaultValue: 5000,
409
+ minimum: 1,
410
+ maximum: 5000,
411
+ },
412
+ {
413
+ flags: '--limit <n>',
414
+ description: 'Maximum number of ranked coffees to return',
415
+ defaultValue: 10,
416
+ minimum: 1,
417
+ maximum: 50,
334
418
  },
335
- { flags: '--sample-size <n>', defaultValue: 5000 },
336
- { flags: '--limit <n>', defaultValue: 10 },
337
419
  ],
338
420
  notes: [
339
421
  'Objectives are premium, value, fresh_arrival, and rare_origin.',
340
- 'Uses coffee_catalog.purveyor_score as the canonical quality signal.',
422
+ 'Uses the Purveyor Score as the quality signal.',
341
423
  'Output metadata reports stocked_only/scope, sample scope, and truncation; rare_origin is rarity within sampled matching candidates.',
342
424
  ],
343
425
  examples: [
@@ -352,17 +434,44 @@ const commandGroups = [
352
434
  auth: 'viewer',
353
435
  sdkMethods: ['catalog.rankPremium'],
354
436
  options: [
355
- { flags: '--origin <origin>' },
356
- { flags: '--process <method>' },
357
- { flags: '--stocked' },
358
- { flags: '--price-max <n>' },
359
- { flags: '--min-score <n>' },
360
- { flags: '--include-unscored' },
361
- { flags: '--sample-size <n>', defaultValue: 250 },
362
- { flags: '--limit <n>', defaultValue: 10 },
437
+ {
438
+ flags: '--origin <origin>',
439
+ description: 'Only coffees from this continent, country, or region (case-insensitive)',
440
+ },
441
+ {
442
+ flags: '--process <method>',
443
+ description: 'Only coffees with this processing method, such as washed or natural',
444
+ },
445
+ {
446
+ flags: '--stocked',
447
+ description: 'Only coffees currently in stock; without it, coffees no longer stocked are ranked too',
448
+ },
449
+ {
450
+ flags: '--price-max <n>',
451
+ description: 'Highest price per pound to include, in US dollars',
452
+ },
453
+ { flags: '--min-score <n>', description: 'Lowest Purveyor Score to include' },
454
+ {
455
+ flags: '--include-unscored',
456
+ description: 'Also list coffees without a Purveyor Score, after all scored coffees',
457
+ },
458
+ {
459
+ flags: '--sample-size <n>',
460
+ description: 'Number of matching coffees to consider before ranking',
461
+ defaultValue: 250,
462
+ minimum: 1,
463
+ maximum: 5000,
464
+ },
465
+ {
466
+ flags: '--limit <n>',
467
+ description: 'Maximum number of ranked coffees to return',
468
+ defaultValue: 10,
469
+ minimum: 1,
470
+ maximum: 50,
471
+ },
363
472
  ],
364
473
  notes: [
365
- 'Exposes coffee_catalog.purveyor_score plus confidence, tier, factor breakdown, version, and update metadata; the CLI does not recompute the upstream score model.',
474
+ 'Shows the Purveyor Score with its confidence, tier, factor breakdown, version, and update metadata.',
366
475
  'Output includes rank, catalog context, pricing, stocked status, Purveyor Score qualifiers, and transparent ranking signals for agents.',
367
476
  'Output metadata reports sample ordering and whether more rows matched than the requested sample size.',
368
477
  ],
@@ -377,15 +486,33 @@ const commandGroups = [
377
486
  auth: 'viewer',
378
487
  sdkMethods: ['catalog.suppliers'],
379
488
  options: [
380
- { flags: '--country <country>' },
381
- { flags: '--stocked' },
382
- { flags: '--non-wholesale-only' },
383
- { flags: '--sample-size <n>', defaultValue: 5000 },
384
- { flags: '--limit <n>', defaultValue: 25 },
489
+ { flags: '--country <country>', description: 'Only count coffees from this country' },
490
+ {
491
+ flags: '--stocked',
492
+ description: 'Only count coffees currently in stock; without it, coffees no longer stocked count too',
493
+ },
494
+ {
495
+ flags: '--non-wholesale-only',
496
+ description: 'Leave out wholesale listings before suppliers are summarized',
497
+ },
498
+ {
499
+ flags: '--sample-size <n>',
500
+ description: 'Number of catalog coffees to read per page before summarizing suppliers',
501
+ defaultValue: 5000,
502
+ minimum: 1,
503
+ maximum: 5000,
504
+ },
505
+ {
506
+ flags: '--limit <n>',
507
+ description: 'Maximum number of suppliers to return',
508
+ defaultValue: 25,
509
+ minimum: 1,
510
+ maximum: 100,
511
+ },
385
512
  ],
386
513
  notes: [
387
514
  'Summarizes supplier counts, stocked counts, Purveyor Score coverage, average score, average confidence, price range, origin/process coverage, and representative top coffees with score qualifiers.',
388
- 'Country and non-wholesale filters are applied at the catalog query layer before supplier aggregation.',
515
+ 'Country and non-wholesale filters apply to coffees before suppliers are summarized.',
389
516
  'Output metadata reports source-ordered pagination, rows examined, and whether the aggregate is sample-limited.',
390
517
  ],
391
518
  examples: ['purvey catalog supplier-list --country Ethiopia --non-wholesale-only --pretty'],
@@ -398,16 +525,34 @@ const commandGroups = [
398
525
  arguments: [
399
526
  {
400
527
  name: 'supplier',
401
- description: 'supplier/source name query',
528
+ description: 'Supplier name to look up (case-insensitive, partial match)',
402
529
  required: true,
403
530
  },
404
531
  ],
405
532
  options: [
406
- { flags: '--country <country>' },
407
- { flags: '--stocked' },
408
- { flags: '--non-wholesale-only' },
409
- { flags: '--top-coffees <n>', defaultValue: 5 },
410
- { flags: '--sample-size <n>', defaultValue: 5000 },
533
+ { flags: '--country <country>', description: 'Only count coffees from this country' },
534
+ {
535
+ flags: '--stocked',
536
+ description: 'Only count coffees currently in stock; without it, coffees no longer stocked count too',
537
+ },
538
+ {
539
+ flags: '--non-wholesale-only',
540
+ description: 'Leave out wholesale listings before the supplier is summarized',
541
+ },
542
+ {
543
+ flags: '--top-coffees <n>',
544
+ description: 'Number of representative top coffees to include',
545
+ defaultValue: 5,
546
+ minimum: 1,
547
+ maximum: 25,
548
+ },
549
+ {
550
+ flags: '--sample-size <n>',
551
+ description: 'Number of catalog coffees to read per page before summarizing',
552
+ defaultValue: 5000,
553
+ minimum: 1,
554
+ maximum: 5000,
555
+ },
411
556
  ],
412
557
  notes: [
413
558
  'Supplier matching is case-insensitive and partial, mirroring catalog search.',
@@ -421,22 +566,40 @@ const commandGroups = [
421
566
  auth: 'viewer',
422
567
  sdkMethods: ['catalog.supplierRank'],
423
568
  options: [
424
- { flags: '--country <country>' },
425
- { flags: '--stocked' },
426
- { flags: '--non-wholesale-only' },
569
+ { flags: '--country <country>', description: 'Only count coffees from this country' },
570
+ {
571
+ flags: '--stocked',
572
+ description: 'Only count coffees currently in stock; without it, coffees no longer stocked count too',
573
+ },
574
+ {
575
+ flags: '--non-wholesale-only',
576
+ description: 'Leave out wholesale listings before suppliers are ranked',
577
+ },
427
578
  {
428
579
  flags: '--min-coffees <n>',
429
- description: `Minimum catalog rows required per supplier, ${CLI_NUMERIC_BOUNDS.supplierMinCoffees.minimum}-${CLI_NUMERIC_BOUNDS.supplierMinCoffees.maximum}`,
580
+ description: 'Minimum number of matching coffees a supplier needs to be ranked',
430
581
  defaultValue: 1,
431
582
  minimum: CLI_NUMERIC_BOUNDS.supplierMinCoffees.minimum,
432
583
  maximum: CLI_NUMERIC_BOUNDS.supplierMinCoffees.maximum,
433
584
  },
434
- { flags: '--sample-size <n>', defaultValue: 5000 },
435
- { flags: '--limit <n>', defaultValue: 25 },
585
+ {
586
+ flags: '--sample-size <n>',
587
+ description: 'Number of catalog coffees to read per page before ranking suppliers',
588
+ defaultValue: 5000,
589
+ minimum: 1,
590
+ maximum: 5000,
591
+ },
592
+ {
593
+ flags: '--limit <n>',
594
+ description: 'Maximum number of suppliers to return',
595
+ defaultValue: 25,
596
+ minimum: 1,
597
+ maximum: 100,
598
+ },
436
599
  ],
437
600
  notes: [
438
601
  'Ranks suppliers by average Purveyor Score, then currently stocked count.',
439
- 'Country and non-wholesale filters are applied at the catalog query layer before supplier aggregation.',
602
+ 'Country and non-wholesale filters apply to coffees before suppliers are ranked.',
440
603
  'Output metadata reports source-ordered pagination, rows examined, and whether the aggregate is sample-limited.',
441
604
  ],
442
605
  examples: [
@@ -445,14 +608,14 @@ const commandGroups = [
445
608
  },
446
609
  {
447
610
  name: 'similar',
448
- summary: 'Fetch beta canonical /v1/catalog/{id}/similar groups for likely same-lot candidates and similar recommendations',
611
+ summary: 'Beta: find likely same-lot candidates and similar coffees for a catalog coffee',
449
612
  auth: 'member',
450
613
  sdkMethods: ['catalog.similar'],
451
614
  arguments: [
452
615
  {
453
616
  name: 'catalog_id',
454
617
  cliToken: 'id',
455
- description: 'coffee_catalog.catalog_id',
618
+ description: 'Catalog ID of the coffee to match',
456
619
  required: true,
457
620
  idType: 'catalog_id',
458
621
  },
@@ -460,23 +623,34 @@ const commandGroups = [
460
623
  options: [
461
624
  {
462
625
  flags: '--threshold <score>',
626
+ description: 'Minimum similarity score a match needs; higher values return fewer, closer matches',
463
627
  defaultValue: 0.7,
464
- description: 'Minimum canonical similarity threshold, 0.5-0.99',
628
+ minimum: 0.5,
629
+ maximum: 0.99,
630
+ },
631
+ {
632
+ flags: '--limit <count>',
633
+ description: 'Maximum number of matches to return',
634
+ defaultValue: 10,
635
+ minimum: 1,
636
+ maximum: 25,
637
+ },
638
+ {
639
+ flags: '--stocked-only',
640
+ description: 'Only return coffees currently in stock; without it, matches can include coffees no longer stocked',
465
641
  },
466
- { flags: '--limit <count>', defaultValue: 10, description: 'Maximum results, 1-25' },
467
- { flags: '--stocked-only', description: 'Restrict results to stocked coffees' },
468
642
  {
469
643
  flags: '--mode <mode>',
644
+ description: 'Which matches to return: all, likely_same (probably the same lot listed elsewhere), or similar_profile (substitutes with a similar profile)',
470
645
  defaultValue: 'all',
471
- description: 'Filter canonical groups: all, likely_same, or similar_profile',
472
646
  },
473
647
  ],
474
648
  notes: [
475
- 'Uses the beta canonical /v1/catalog/{id}/similar API contract, not the legacy direct RPC path.',
476
- 'Default JSON output is the grouped canonical response object with data.target, data.groups.canonical_candidates, data.groups.similar_recommendations, optional data.matches, and meta.',
477
- 'canonical_candidates are likely same-lot candidates; similar_recommendations are profile substitutes and expose blocker reasons when identity gates disagree.',
478
- 'Preserves classification_version, query_strategy, proof summaries, pricing metadata, blocker details, and score dimensions supplied by the API.',
479
- 'Use the scoped member API key created by `purvey auth login`, or override it with PURVEYORS_API_KEY or PARCHMENT_API_KEY.',
649
+ SIGNED_IN_SIMILAR_ACCESS,
650
+ 'Results are beta candidates to review, not confirmed matches.',
651
+ 'Default JSON output groups results under data.target, data.groups.canonical_candidates, data.groups.similar_recommendations, optional data.matches, and meta.',
652
+ 'canonical_candidates are likely same-lot candidates; similar_recommendations are profile substitutes and expose blocker reasons when identity details disagree.',
653
+ 'Keeps classification_version, query_strategy, proof summaries, pricing metadata, blocker details, and score dimensions as returned.',
480
654
  ],
481
655
  examples: [
482
656
  'purvey catalog similar 1182 --threshold 0.85 --stocked-only --json',
@@ -496,16 +670,36 @@ const commandGroups = [
496
670
  auth: 'member',
497
671
  sdkMethods: ['inventory.list'],
498
672
  options: [
499
- { flags: '--stocked' },
500
- { flags: '--catalog-id <id>' },
501
- { flags: '--purchase-date-start <YYYY-MM-DD>' },
502
- { flags: '--purchase-date-end <YYYY-MM-DD>' },
503
- { flags: '--origin <country>' },
504
- { flags: '--limit <n>', defaultValue: 20 },
505
- { flags: '--offset <n>', defaultValue: 0 },
673
+ { flags: '--stocked', description: 'Only coffees you have marked as in stock' },
674
+ {
675
+ flags: '--catalog-id <id>',
676
+ description: 'Only inventory items bought from this catalog coffee (a catalog ID)',
677
+ },
678
+ {
679
+ flags: '--purchase-date-start <YYYY-MM-DD>',
680
+ description: 'Only purchases made on or after this date',
681
+ },
682
+ {
683
+ flags: '--purchase-date-end <YYYY-MM-DD>',
684
+ description: 'Only purchases made on or before this date',
685
+ },
686
+ {
687
+ flags: '--origin <country>',
688
+ description: 'Only coffees whose country of origin contains this text',
689
+ },
690
+ {
691
+ flags: '--limit <n>',
692
+ description: 'Maximum number of items to return',
693
+ defaultValue: 20,
694
+ },
695
+ {
696
+ flags: '--offset <n>',
697
+ description: 'Number of items to skip when paging',
698
+ defaultValue: 0,
699
+ },
506
700
  ],
507
701
  notes: [
508
- 'Returns green_coffee_inv rows joined with catalog details.',
702
+ 'Returns your inventory items with their catalog details.',
509
703
  'The returned id field is the inventory ID, distinct from catalog_id.',
510
704
  '--offset + --limit enables pagination through large result sets.',
511
705
  ],
@@ -523,7 +717,7 @@ const commandGroups = [
523
717
  {
524
718
  name: 'inventory_id',
525
719
  cliToken: 'id',
526
- description: 'green_coffee_inv.id',
720
+ description: 'Inventory ID of the item',
527
721
  required: true,
528
722
  idType: 'inventory_id',
529
723
  },
@@ -539,25 +733,41 @@ const commandGroups = [
539
733
  options: [
540
734
  {
541
735
  flags: '--catalog-id <id>',
542
- description: 'Catalog lot to add; exactly one of --catalog-id or --manual-name',
736
+ description: 'Catalog ID of the coffee you bought; use exactly one of --catalog-id or --manual-name',
543
737
  },
544
738
  {
545
739
  flags: '--manual-name <name>',
546
- description: 'Coffee name for a lot that is not in the catalog',
740
+ description: 'Name for a coffee that is not in the catalog; use exactly one of --catalog-id or --manual-name',
741
+ },
742
+ {
743
+ flags: '--qty <lbs>',
744
+ description: 'Quantity purchased, in pounds',
745
+ requiredInFlagMode: true,
746
+ },
747
+ {
748
+ flags: '--cost <dollars>',
749
+ description: 'Total amount paid for the beans, in US dollars (not the price per pound)',
750
+ },
751
+ {
752
+ flags: '--tax-ship <dollars>',
753
+ description: 'Tax and shipping paid for this purchase, in US dollars',
754
+ },
755
+ { flags: '--notes <text>', description: 'Free-text notes about this purchase' },
756
+ {
757
+ flags: '--purchase-date <YYYY-MM-DD>',
758
+ description: 'Date of purchase; defaults to today',
759
+ },
760
+ {
761
+ flags: '--form',
762
+ description: 'Prompt for a catalog coffee, quantity, total cost, and notes; the purchase date is set to today',
547
763
  },
548
- { flags: '--qty <lbs>', requiredInFlagMode: true },
549
- { flags: '--cost <dollars>' },
550
- { flags: '--tax-ship <dollars>' },
551
- { flags: '--notes <text>' },
552
- { flags: '--purchase-date <YYYY-MM-DD>' },
553
- { flags: '--form' },
554
764
  ],
555
765
  notes: [
556
766
  'Flag mode requires --qty and exactly one of --catalog-id or --manual-name.',
557
- '--manual-name creates a manual coffee record when Parchment has manual inventory writes enabled.',
767
+ '--manual-name creates a manual coffee record when manual inventory entries are enabled.',
558
768
  ],
559
769
  examples: [
560
- 'purvey inventory add --catalog-id 128 --qty 10 --cost 8.50 --pretty',
770
+ 'purvey inventory add --catalog-id 128 --qty 10 --cost 85.00 --pretty',
561
771
  'purvey inventory add --manual-name "Farm-gate Ethiopia lot 7" --qty 12 --cost 96 --pretty',
562
772
  ],
563
773
  },
@@ -571,18 +781,30 @@ const commandGroups = [
571
781
  {
572
782
  name: 'inventory_id',
573
783
  cliToken: 'id',
574
- description: 'green_coffee_inv.id',
784
+ description: 'Inventory ID of the item',
575
785
  required: true,
576
786
  idType: 'inventory_id',
577
787
  },
578
788
  ],
579
789
  options: [
580
- { flags: '--qty <lbs>' },
581
- { flags: '--cost <dollars>' },
582
- { flags: '--tax-ship <dollars>' },
583
- { flags: '--notes <text>' },
584
- { flags: '--stocked <true|false>' },
585
- { flags: '--rank <n>', description: 'Owner-assigned integer rank for this lot' },
790
+ { flags: '--qty <lbs>', description: 'New purchased quantity, in pounds' },
791
+ {
792
+ flags: '--cost <dollars>',
793
+ description: 'New total bean cost, in US dollars (not the price per pound)',
794
+ },
795
+ {
796
+ flags: '--tax-ship <dollars>',
797
+ description: 'New tax and shipping amount, in US dollars',
798
+ },
799
+ { flags: '--notes <text>', description: 'Replace the notes for this item' },
800
+ {
801
+ flags: '--stocked <true|false>',
802
+ description: 'Mark the coffee as in stock (true) or used up (false)',
803
+ },
804
+ {
805
+ flags: '--rank <n>',
806
+ description: 'Your own whole-number ranking for this coffee, used to order your inventory',
807
+ },
586
808
  ],
587
809
  },
588
810
  {
@@ -594,18 +816,23 @@ const commandGroups = [
594
816
  {
595
817
  name: 'inventory_id',
596
818
  cliToken: 'id',
597
- description: 'green_coffee_inv.id',
819
+ description: 'Inventory ID of the item',
598
820
  required: true,
599
821
  idType: 'inventory_id',
600
822
  },
601
823
  ],
602
- options: [{ flags: '--yes' }],
824
+ options: [
825
+ {
826
+ flags: '--yes',
827
+ description: 'Delete without asking for confirmation; needed in scripts and agents',
828
+ },
829
+ ],
603
830
  },
604
831
  ],
605
832
  },
606
833
  {
607
834
  name: 'roast',
608
- summary: 'Browse and manage your roast profiles',
835
+ summary: 'Record roasts, import Artisan .alog files, and watch a folder for new roasts',
609
836
  auth: 'member',
610
837
  subcommands: [
611
838
  {
@@ -614,20 +841,46 @@ const commandGroups = [
614
841
  auth: 'member',
615
842
  sdkMethods: ['roasts.list'],
616
843
  options: [
617
- { flags: '--coffee-id <id>' },
618
- { flags: '--roast-id <id>' },
619
- { flags: '--batch-name <text>' },
620
- { flags: '--coffee-name <text>' },
621
- { flags: '--date-start <YYYY-MM-DD>' },
622
- { flags: '--date-end <YYYY-MM-DD>' },
623
- { flags: '--stocked' },
624
- { flags: '--catalog-id <id>' },
625
- { flags: '--limit <n>', defaultValue: 20 },
626
- { flags: '--offset <n>', defaultValue: 0 },
844
+ {
845
+ flags: '--coffee-id <id>',
846
+ description: 'Only roasts of this inventory item (an inventory ID, not a catalog ID)',
847
+ },
848
+ { flags: '--roast-id <id>', description: 'Only the roast with this roast ID' },
849
+ {
850
+ flags: '--batch-name <text>',
851
+ description: 'Only roasts whose batch name contains this text (case-insensitive)',
852
+ },
853
+ {
854
+ flags: '--coffee-name <text>',
855
+ description: 'Only roasts of coffees whose name contains this text (case-insensitive)',
856
+ },
857
+ {
858
+ flags: '--date-start <YYYY-MM-DD>',
859
+ description: 'Only roasts on or after this date',
860
+ },
861
+ { flags: '--date-end <YYYY-MM-DD>', description: 'Only roasts on or before this date' },
862
+ {
863
+ flags: '--stocked',
864
+ description: 'Only roasts of coffees still marked as in stock in your inventory',
865
+ },
866
+ {
867
+ flags: '--catalog-id <id>',
868
+ description: 'Only roasts of coffees bought from this catalog coffee (a catalog ID)',
869
+ },
870
+ {
871
+ flags: '--limit <n>',
872
+ description: 'Maximum number of roasts to return',
873
+ defaultValue: 20,
874
+ },
875
+ {
876
+ flags: '--offset <n>',
877
+ description: 'Number of roasts to skip when paging',
878
+ defaultValue: 0,
879
+ },
627
880
  ],
628
881
  notes: [
629
882
  '--coffee-id expects inventory_id, not catalog_id.',
630
- '--catalog-id filters by coffee_catalog.catalog_id.',
883
+ '--catalog-id filters by catalog ID.',
631
884
  '--date-start and --date-end accept YYYY-MM-DD format.',
632
885
  '--offset + --limit enables pagination through large result sets.',
633
886
  ],
@@ -642,23 +895,32 @@ const commandGroups = [
642
895
  {
643
896
  name: 'roast_id',
644
897
  cliToken: 'id',
645
- description: 'roast_data.roast_id',
898
+ description: 'Roast ID',
646
899
  required: true,
647
900
  idType: 'roast_id',
648
901
  },
649
902
  ],
650
- options: [{ flags: '--include-temps' }, { flags: '--include-events' }],
903
+ options: [
904
+ {
905
+ flags: '--include-temps',
906
+ description: 'Add the full temperature curve; output can be large',
907
+ },
908
+ {
909
+ flags: '--include-events',
910
+ description: 'Add roast event markers such as first crack and drop',
911
+ },
912
+ ],
651
913
  },
652
914
  {
653
915
  name: 'chart',
654
- summary: 'Fetch the sampled chart model for one roast (series, events, revision)',
916
+ summary: 'Get sampled chart data for one roast: temperature series, events, and chart revision',
655
917
  auth: 'member',
656
918
  sdkMethods: ['roasts.chartData'],
657
919
  arguments: [
658
920
  {
659
921
  name: 'roast_id',
660
922
  cliToken: 'id',
661
- description: 'roast_data.roast_id',
923
+ description: 'Roast ID',
662
924
  required: true,
663
925
  idType: 'roast_id',
664
926
  },
@@ -666,13 +928,14 @@ const commandGroups = [
666
928
  options: [
667
929
  {
668
930
  flags: '--target-points <n>',
669
- description: 'Approximate samples per series, 50-1000; Parchment defaults to 400',
931
+ description: 'Approximate number of samples per temperature series',
932
+ defaultValue: 400,
670
933
  minimum: 50,
671
934
  maximum: 1000,
672
935
  },
673
936
  ],
674
937
  notes: [
675
- "Returns Parchment's canonical chart-data envelope unchanged: sampled series, events, and metadata.",
938
+ 'Returns the roast chart data unchanged: sampled series, events, and metadata.',
676
939
  'data.metadata.revision identifies the immutable chart revision used by reference-profile compare.',
677
940
  ],
678
941
  examples: [
@@ -687,15 +950,34 @@ const commandGroups = [
687
950
  sdkMethods: ['roasts.create'],
688
951
  confirmedActionEquivalents: ['create_roast_session'],
689
952
  options: [
690
- { flags: '--coffee-id <id>', requiredInFlagMode: true },
691
- { flags: '--batch-name <name>' },
692
- { flags: '--oz-in <oz>' },
693
- { flags: '--oz-out <oz>' },
694
- { flags: '--roast-date <YYYY-MM-DD>' },
695
- { flags: '--notes <text>' },
696
- { flags: '--targets <text>' },
697
- { flags: '--roaster-type <text>' },
698
- { flags: '--form' },
953
+ {
954
+ flags: '--coffee-id <id>',
955
+ description: 'Inventory ID of the coffee you roasted (not a catalog ID)',
956
+ requiredInFlagMode: true,
957
+ },
958
+ {
959
+ flags: '--batch-name <name>',
960
+ description: "Name for this roast batch; defaults to the coffee name plus today's date",
961
+ },
962
+ { flags: '--oz-in <oz>', description: 'Green coffee weight going in, in ounces' },
963
+ { flags: '--oz-out <oz>', description: 'Roasted coffee weight coming out, in ounces' },
964
+ {
965
+ flags: '--roast-date <YYYY-MM-DD>',
966
+ description: 'Date of the roast; defaults to today',
967
+ },
968
+ { flags: '--notes <text>', description: 'Free-text notes about the roast' },
969
+ {
970
+ flags: '--targets <text>',
971
+ description: 'What you were aiming for, such as "FC at 390F, 18% development"',
972
+ },
973
+ {
974
+ flags: '--roaster-type <text>',
975
+ description: 'Roaster model or type, such as Aillio Bullet',
976
+ },
977
+ {
978
+ flags: '--form',
979
+ description: 'Prompt for an inventory coffee, batch name, weight in, notes, and targets; the roast date is set to today and targets are saved in the notes',
980
+ },
699
981
  ],
700
982
  },
701
983
  {
@@ -708,16 +990,19 @@ const commandGroups = [
708
990
  {
709
991
  name: 'roast_id',
710
992
  cliToken: 'id',
711
- description: 'roast_data.roast_id',
993
+ description: 'Roast ID',
712
994
  required: true,
713
995
  idType: 'roast_id',
714
996
  },
715
997
  ],
716
998
  options: [
717
- { flags: '--notes <text>' },
718
- { flags: '--oz-out <oz>' },
719
- { flags: '--batch-name <name>' },
720
- { flags: '--targets <text>' },
999
+ { flags: '--notes <text>', description: 'Replace the roast notes' },
1000
+ {
1001
+ flags: '--oz-out <oz>',
1002
+ description: 'New roasted weight, in ounces; weight loss is recalculated when the green weight is known',
1003
+ },
1004
+ { flags: '--batch-name <name>', description: 'New batch name' },
1005
+ { flags: '--targets <text>', description: 'Replace the roast targets' },
721
1006
  ],
722
1007
  },
723
1008
  {
@@ -729,12 +1014,17 @@ const commandGroups = [
729
1014
  {
730
1015
  name: 'roast_id',
731
1016
  cliToken: 'id',
732
- description: 'roast_data.roast_id',
1017
+ description: 'Roast ID',
733
1018
  required: true,
734
1019
  idType: 'roast_id',
735
1020
  },
736
1021
  ],
737
- options: [{ flags: '--yes' }],
1022
+ options: [
1023
+ {
1024
+ flags: '--yes',
1025
+ description: 'Delete without asking for confirmation; needed in scripts and agents',
1026
+ },
1027
+ ],
738
1028
  },
739
1029
  {
740
1030
  name: 'import',
@@ -743,12 +1033,28 @@ const commandGroups = [
743
1033
  sdkMethods: ['roasts.import'],
744
1034
  arguments: [{ name: 'file', description: 'Path to Artisan .alog file', required: false }],
745
1035
  options: [
746
- { flags: '--coffee-id <id>', requiredInFlagMode: true },
747
- { flags: '--batch-name <name>' },
748
- { flags: '--oz-in <oz>' },
749
- { flags: '--roast-notes <text>' },
750
- { flags: '--roast-targets <text>' },
751
- { flags: '--form' },
1036
+ {
1037
+ flags: '--coffee-id <id>',
1038
+ description: 'Inventory ID of the coffee you roasted (not a catalog ID)',
1039
+ requiredInFlagMode: true,
1040
+ },
1041
+ {
1042
+ flags: '--batch-name <name>',
1043
+ description: 'Name for this roast batch; defaults to the coffee name plus the roast date',
1044
+ },
1045
+ {
1046
+ flags: '--oz-in <oz>',
1047
+ description: 'Green coffee weight going in, in ounces; overrides the weight read from the .alog file',
1048
+ },
1049
+ { flags: '--roast-notes <text>', description: 'Notes to save with the imported roast' },
1050
+ {
1051
+ flags: '--roast-targets <text>',
1052
+ description: 'What you were aiming for, saved with the imported roast',
1053
+ },
1054
+ {
1055
+ flags: '--form',
1056
+ description: 'Pick the file and inventory item interactively',
1057
+ },
752
1058
  ],
753
1059
  },
754
1060
  {
@@ -758,20 +1064,48 @@ const commandGroups = [
758
1064
  sdkMethods: ['roasts.import', 'inventory.list', 'roasts.classify'],
759
1065
  arguments: [{ name: 'directory', description: 'Directory to watch', required: false }],
760
1066
  options: [
761
- { flags: '--coffee-id <inventory_id>' },
762
- { flags: '--batch-prefix <name>' },
763
- { flags: '--prompt-each' },
764
- { flags: '--auto-match' },
765
- { flags: '--commit-mode <batch|individual>', defaultValue: 'batch' },
766
- { flags: '--oz-in <oz>' },
767
- { flags: '--roast-notes <text>' },
768
- { flags: '--roast-targets <text>' },
769
- { flags: '--resume' },
770
- { flags: '--form' },
1067
+ {
1068
+ flags: '--coffee-id <inventory_id>',
1069
+ description: 'Inventory ID to attach every new roast to; required unless you use --auto-match, --resume, or --form',
1070
+ },
1071
+ {
1072
+ flags: '--batch-prefix <name>',
1073
+ description: 'Prefix for batch names, which are numbered like "<prefix> #1"; defaults to the coffee name',
1074
+ },
1075
+ {
1076
+ flags: '--prompt-each',
1077
+ description: 'Ask which inventory item each new file belongs to instead of attaching every file to --coffee-id',
1078
+ },
1079
+ {
1080
+ flags: '--auto-match',
1081
+ description: 'Match each new roast to a stocked inventory item automatically from details in its .alog file; cannot be combined with --coffee-id',
1082
+ },
1083
+ {
1084
+ flags: '--commit-mode <batch|individual>',
1085
+ description: 'batch queues new roasts and saves them together when you stop watching; individual saves each roast as soon as its file appears',
1086
+ defaultValue: 'batch',
1087
+ },
1088
+ {
1089
+ flags: '--oz-in <oz>',
1090
+ description: 'Green coffee weight, in ounces, to record for every watched import',
1091
+ },
1092
+ { flags: '--roast-notes <text>', description: 'Notes to save with every watched import' },
1093
+ {
1094
+ flags: '--roast-targets <text>',
1095
+ description: 'Roast targets to save with every watched import',
1096
+ },
1097
+ {
1098
+ flags: '--resume',
1099
+ description: 'Continue the last watch session with its saved directory, settings, and import progress',
1100
+ },
1101
+ {
1102
+ flags: '--form',
1103
+ description: 'Set up the directory, coffee, and commit mode interactively',
1104
+ },
771
1105
  ],
772
1106
  notes: [
773
1107
  '--auto-match is mutually exclusive with --coffee-id.',
774
- '--auto-match classifies roast metadata against stocked inventory through the canonical Parchment POST /v1/roasts/classify SDK operation.',
1108
+ '--auto-match matches each new roast to a stocked inventory item from its metadata.',
775
1109
  '--commit-mode defaults to batch so new roasts are queued until the session ends.',
776
1110
  ],
777
1111
  },
@@ -788,15 +1122,29 @@ const commandGroups = [
788
1122
  auth: 'member',
789
1123
  sdkMethods: ['sales.list'],
790
1124
  options: [
791
- { flags: '--coffee-id <id>' },
792
- { flags: '--date-start <YYYY-MM-DD>' },
793
- { flags: '--date-end <YYYY-MM-DD>' },
794
- { flags: '--buyer <name>' },
795
- { flags: '--limit <n>', defaultValue: 20 },
796
- { flags: '--offset <n>', defaultValue: 0 },
1125
+ {
1126
+ flags: '--coffee-id <id>',
1127
+ description: 'Only sales of this inventory item (an inventory ID)',
1128
+ },
1129
+ { flags: '--date-start <YYYY-MM-DD>', description: 'Only sales on or after this date' },
1130
+ { flags: '--date-end <YYYY-MM-DD>', description: 'Only sales on or before this date' },
1131
+ {
1132
+ flags: '--buyer <name>',
1133
+ description: 'Only sales to buyers whose name contains this text (case-insensitive)',
1134
+ },
1135
+ {
1136
+ flags: '--limit <n>',
1137
+ description: 'Maximum number of sales to return',
1138
+ defaultValue: 20,
1139
+ },
1140
+ {
1141
+ flags: '--offset <n>',
1142
+ description: 'Number of sales to skip when paging',
1143
+ defaultValue: 0,
1144
+ },
797
1145
  ],
798
1146
  notes: [
799
- '--coffee-id filters by green_coffee_inv.id through the canonical sales API.',
1147
+ '--coffee-id filters by inventory ID.',
800
1148
  '--date-start and --date-end accept YYYY-MM-DD and compose into a range.',
801
1149
  '--offset + --limit enables pagination through large result sets.',
802
1150
  ],
@@ -808,18 +1156,37 @@ const commandGroups = [
808
1156
  sdkMethods: ['sales.create', 'roasts.list', 'roasts.get'],
809
1157
  confirmedActionEquivalents: ['record_sale'],
810
1158
  options: [
811
- { flags: '--roast-id <id>' },
812
- { flags: '--coffee-id <id>' },
813
- { flags: '--batch-name <name>' },
814
- { flags: '--oz <amount>', requiredInFlagMode: true },
815
- { flags: '--price <dollars>', requiredInFlagMode: true },
816
- { flags: '--buyer <name>' },
817
- { flags: '--sell-date <YYYY-MM-DD>' },
818
- { flags: '--form' },
1159
+ {
1160
+ flags: '--roast-id <id>',
1161
+ description: 'Roast ID the coffee came from; the CLI looks up its inventory item and batch. Use instead of --coffee-id with --batch-name',
1162
+ },
1163
+ {
1164
+ flags: '--coffee-id <id>',
1165
+ description: 'Inventory ID of the coffee sold; use with --batch-name instead of --roast-id',
1166
+ },
1167
+ {
1168
+ flags: '--batch-name <name>',
1169
+ description: 'Batch name of the roast sold; use with --coffee-id',
1170
+ },
1171
+ {
1172
+ flags: '--oz <amount>',
1173
+ description: 'Roasted coffee sold, in ounces',
1174
+ requiredInFlagMode: true,
1175
+ },
1176
+ {
1177
+ flags: '--price <dollars>',
1178
+ description: 'Total sale price, in US dollars (not per ounce)',
1179
+ requiredInFlagMode: true,
1180
+ },
1181
+ { flags: '--buyer <name>', description: 'Buyer name or identifier' },
1182
+ { flags: '--sell-date <YYYY-MM-DD>', description: 'Date of the sale; defaults to today' },
1183
+ {
1184
+ flags: '--form',
1185
+ description: 'Prompt for a roast, ounces sold, sale price, and buyer; the sale date is set to today',
1186
+ },
819
1187
  ],
820
1188
  notes: [
821
1189
  'Selector modes: --roast-id resolves its inventory + batch, or pass --coffee-id + --batch-name directly.',
822
- 'Creates use the canonical sales API with an idempotency key; selectors resolve through canonical roast endpoints.',
823
1190
  'Use exactly one selector mode.',
824
1191
  'Sales retain inventory + batch, not roast ID; duplicate batch names on one inventory item are rejected.',
825
1192
  ],
@@ -833,16 +1200,16 @@ const commandGroups = [
833
1200
  {
834
1201
  name: 'sale_id',
835
1202
  cliToken: 'id',
836
- description: 'coffee_sales row id',
1203
+ description: 'Sale ID',
837
1204
  required: true,
838
1205
  idType: 'sale_id',
839
1206
  },
840
1207
  ],
841
1208
  options: [
842
- { flags: '--oz <amount>' },
843
- { flags: '--price <dollars>' },
844
- { flags: '--buyer <name>' },
845
- { flags: '--sell-date <YYYY-MM-DD>' },
1209
+ { flags: '--oz <amount>', description: 'New amount sold, in ounces' },
1210
+ { flags: '--price <dollars>', description: 'New total sale price, in US dollars' },
1211
+ { flags: '--buyer <name>', description: 'New buyer name or identifier' },
1212
+ { flags: '--sell-date <YYYY-MM-DD>', description: 'New sale date' },
846
1213
  ],
847
1214
  },
848
1215
  {
@@ -854,18 +1221,23 @@ const commandGroups = [
854
1221
  {
855
1222
  name: 'sale_id',
856
1223
  cliToken: 'id',
857
- description: 'coffee_sales row id',
1224
+ description: 'Sale ID',
858
1225
  required: true,
859
1226
  idType: 'sale_id',
860
1227
  },
861
1228
  ],
862
- options: [{ flags: '--yes' }],
1229
+ options: [
1230
+ {
1231
+ flags: '--yes',
1232
+ description: 'Delete without asking for confirmation; needed in scripts and agents',
1233
+ },
1234
+ ],
863
1235
  },
864
1236
  ],
865
1237
  },
866
1238
  {
867
1239
  name: 'tasting',
868
- summary: 'View and record tasting notes for a coffee bean',
1240
+ summary: 'Read supplier tasting notes and record your own cupping scores',
869
1241
  auth: 'member',
870
1242
  subcommands: [
871
1243
  {
@@ -877,12 +1249,18 @@ const commandGroups = [
877
1249
  {
878
1250
  name: 'catalog_id',
879
1251
  cliToken: 'bean-id',
880
- description: 'coffee_catalog.catalog_id, not inventory id',
1252
+ description: 'Catalog ID of the coffee, not an inventory ID',
881
1253
  required: true,
882
1254
  idType: 'catalog_id',
883
1255
  },
884
1256
  ],
885
- options: [{ flags: '--filter <user|supplier|both>', defaultValue: 'both' }],
1257
+ options: [
1258
+ {
1259
+ flags: '--filter <user|supplier|both>',
1260
+ description: "Which notes to return: user (your cupping scores), supplier (the supplier's flavor notes), or both",
1261
+ defaultValue: 'both',
1262
+ },
1263
+ ],
886
1264
  },
887
1265
  {
888
1266
  name: 'rate',
@@ -893,27 +1271,64 @@ const commandGroups = [
893
1271
  {
894
1272
  name: 'inventory_id',
895
1273
  cliToken: 'bean-id',
896
- description: 'green_coffee_inv.id, not catalog_id',
1274
+ description: 'Inventory ID of the coffee, not a catalog ID',
897
1275
  required: false,
898
1276
  idType: 'inventory_id',
899
1277
  },
900
1278
  ],
901
1279
  options: [
902
- { flags: '--aroma <1-5>', requiredInFlagMode: true },
903
- { flags: '--body <1-5>', requiredInFlagMode: true },
904
- { flags: '--acidity <1-5>', requiredInFlagMode: true },
905
- { flags: '--sweetness <1-5>', requiredInFlagMode: true },
906
- { flags: '--aftertaste <1-5>', requiredInFlagMode: true },
907
- { flags: '--brew-method <method>' },
908
- { flags: '--notes <text>' },
909
- { flags: '--form' },
1280
+ {
1281
+ flags: '--aroma <1-5>',
1282
+ description: 'Aroma score, a whole number; higher is better',
1283
+ minimum: 1,
1284
+ maximum: 5,
1285
+ requiredInFlagMode: true,
1286
+ },
1287
+ {
1288
+ flags: '--body <1-5>',
1289
+ description: 'Body score, a whole number; higher is better',
1290
+ minimum: 1,
1291
+ maximum: 5,
1292
+ requiredInFlagMode: true,
1293
+ },
1294
+ {
1295
+ flags: '--acidity <1-5>',
1296
+ description: 'Acidity score, a whole number; higher is better',
1297
+ minimum: 1,
1298
+ maximum: 5,
1299
+ requiredInFlagMode: true,
1300
+ },
1301
+ {
1302
+ flags: '--sweetness <1-5>',
1303
+ description: 'Sweetness score, a whole number; higher is better',
1304
+ minimum: 1,
1305
+ maximum: 5,
1306
+ requiredInFlagMode: true,
1307
+ },
1308
+ {
1309
+ flags: '--aftertaste <1-5>',
1310
+ description: 'Aftertaste score, a whole number; higher is better',
1311
+ minimum: 1,
1312
+ maximum: 5,
1313
+ requiredInFlagMode: true,
1314
+ },
1315
+ {
1316
+ flags: '--brew-method <method>',
1317
+ description: 'How you brewed the coffee, such as pour_over, french_press, or espresso',
1318
+ },
1319
+ { flags: '--notes <text>', description: 'Free-text tasting notes' },
1320
+ {
1321
+ flags: '--form',
1322
+ description: 'Pick the coffee, then enter the five scores and notes interactively',
1323
+ },
910
1324
  ],
1325
+ notes: ['Rating again replaces the earlier scores for that inventory item.'],
911
1326
  },
912
1327
  ],
913
1328
  },
914
1329
  {
915
1330
  name: 'config',
916
- summary: 'Manage purvey CLI settings',
1331
+ summary: 'Manage local purvey CLI settings',
917
1332
  auth: 'none',
918
1333
  subcommands: [
919
1334
  {
@@ -969,13 +1384,22 @@ const commandGroups = [
969
1384
  },
970
1385
  {
971
1386
  name: 'context',
972
- summary: 'Emit dense human-readable operator reference or manifest-parity JSON',
1387
+ summary: 'Print a dense human-readable CLI reference, or the manifest as JSON',
973
1388
  auth: 'none',
974
1389
  command: {
975
1390
  name: 'context',
976
- summary: 'Emit dense human-readable operator reference or manifest-parity JSON',
1391
+ summary: 'Print a dense human-readable CLI reference, or the manifest as JSON',
977
1392
  auth: 'none',
978
- options: [{ flags: '--json' }, { flags: '--pretty' }],
1393
+ options: [
1394
+ {
1395
+ flags: '--json',
1396
+ description: 'Print the machine-readable manifest as compact JSON instead of the text reference',
1397
+ },
1398
+ {
1399
+ flags: '--pretty',
1400
+ description: 'Print the machine-readable manifest as indented JSON instead of the text reference',
1401
+ },
1402
+ ],
979
1403
  notes: [
980
1404
  'Use `purvey context` for dense human-readable operator reference text.',
981
1405
  'Prefer `purvey manifest` for the stable machine-readable CLI contract.',
@@ -987,13 +1411,16 @@ const commandGroups = [
987
1411
  },
988
1412
  {
989
1413
  name: 'manifest',
990
- summary: 'Emit the preferred stable machine-readable CLI manifest contract',
1414
+ summary: 'Print the stable machine-readable CLI contract as JSON',
991
1415
  auth: 'none',
992
1416
  command: {
993
1417
  name: 'manifest',
994
- summary: 'Emit the preferred stable machine-readable CLI manifest contract',
1418
+ summary: 'Print the stable machine-readable CLI contract as JSON',
995
1419
  auth: 'none',
996
- options: [{ flags: '--json' }, { flags: '--pretty' }],
1420
+ options: [
1421
+ { flags: '--json', description: 'Print the manifest as compact JSON, the default' },
1422
+ { flags: '--pretty', description: 'Print the manifest as indented JSON' },
1423
+ ],
997
1424
  notes: [
998
1425
  '`purvey manifest` is the preferred machine-readable entrypoint and emits compact JSON by default.',
999
1426
  '`purvey context --json` remains available for compatibility with existing context-based callers.',
@@ -1002,9 +1429,94 @@ const commandGroups = [
1002
1429
  examples: ['purvey manifest', 'purvey manifest --pretty'],
1003
1430
  },
1004
1431
  },
1432
+ {
1433
+ name: 'skill',
1434
+ summary: 'Print or install agent instructions generated from this manifest',
1435
+ auth: 'none',
1436
+ subcommands: [
1437
+ {
1438
+ name: 'print',
1439
+ summary: 'Write the generated SKILL.md, workflows.md, or the AGENTS.md block to stdout',
1440
+ auth: 'none',
1441
+ options: [
1442
+ {
1443
+ flags: '--file <file>',
1444
+ description: 'Skill file to print: SKILL.md, workflows.md, or all; all needs --json or --pretty',
1445
+ defaultValue: 'SKILL.md',
1446
+ notes: [
1447
+ 'The skill is a folder: SKILL.md, which agents load when the skill triggers, and workflows.md, the step-by-step workflows SKILL.md points to',
1448
+ ],
1449
+ },
1450
+ {
1451
+ flags: '--agents-md',
1452
+ description: 'Print the compact AGENTS.md block instead of the skill',
1453
+ },
1454
+ ],
1455
+ notes: [
1456
+ 'Prints one file as Markdown by default; --json or --pretty wraps it as { name, file, cliVersion, bytes, content }, or every file with --file all as { name, cliVersion, files: [{ file, bytes, content }] }.',
1457
+ '--csv is not supported, and --agents-md cannot be combined with --file.',
1458
+ 'Rendered from this manifest, so it changes only when the CLI contract changes.',
1459
+ ],
1460
+ examples: [
1461
+ 'purvey skill print',
1462
+ 'purvey skill print --file workflows.md',
1463
+ 'purvey skill print --file all --json',
1464
+ 'purvey skill print --agents-md',
1465
+ ],
1466
+ },
1467
+ {
1468
+ name: 'install',
1469
+ summary: 'Install the generated instructions for Claude Code, Agent Skills clients such as Codex and Cursor, or a repository AGENTS.md',
1470
+ auth: 'none',
1471
+ options: [
1472
+ {
1473
+ flags: '--target <target>',
1474
+ description: "Required. Where to install: claude (Claude Code), agents (Codex, Cursor, and other Agent Skills clients), or agents-md (this repository's AGENTS.md)",
1475
+ notes: [
1476
+ 'claude: SKILL.md and workflows.md in ~/.claude/skills/purveyors/ (project scope: .claude/skills/purveyors/); use this for Claude Code, which loads skills only from .claude/skills',
1477
+ 'agents: SKILL.md and workflows.md in ~/.agents/skills/purveyors/, read by Codex, Cursor, and other Agent Skills clients (project scope: .agents/skills/purveyors/); Claude Code does not read .agents/',
1478
+ 'agents-md: ./AGENTS.md in the current directory; adds or refreshes one marked block and leaves the rest of the file alone. Claude Code reads AGENTS.md only when no CLAUDE.md, .claude/CLAUDE.md, or CLAUDE.local.md exists in the current directory or above it; see --link-claude-md',
1479
+ ],
1480
+ },
1481
+ {
1482
+ flags: '--scope <scope>',
1483
+ description: 'user (your home directory) or project (the current directory); defaults to user, or project for agents-md',
1484
+ },
1485
+ {
1486
+ flags: '--force',
1487
+ description: 'Replace skill files or an AGENTS.md block that have local edits',
1488
+ },
1489
+ { flags: '--dry-run', description: 'Report the path and action without writing' },
1490
+ {
1491
+ flags: '--link-claude-md',
1492
+ description: 'agents-md only: add an @AGENTS.md import to ./CLAUDE.md so Claude Code loads the block',
1493
+ notes: [
1494
+ 'Appends one @AGENTS.md line to ./CLAUDE.md, or @../AGENTS.md to ./.claude/CLAUDE.md, and creates ./CLAUDE.md when neither exists',
1495
+ 'Only acts when a CLAUDE.md, .claude/CLAUDE.md, or CLAUDE.local.md on the path hides AGENTS.md and none imports it; idempotent and honors --dry-run',
1496
+ 'Never edits CLAUDE.local.md or a CLAUDE.md in a parent directory',
1497
+ ],
1498
+ },
1499
+ ],
1500
+ notes: [
1501
+ 'Needs no credentials and makes no network calls.',
1502
+ 'Emits { target, scope, path, action, written, dryRun, cliVersion, bytes } as JSON on stdout; action is create, update, unchanged, append, or overwrite.',
1503
+ 'claude and agents also emit files: [{ file, path, action, written, bytes }] for SKILL.md and workflows.md. The top-level path is SKILL.md, action is the most significant file action (overwrite, update, create, then unchanged), written is true when any file was written, and bytes is the total.',
1504
+ 'agents-md also emits claudeCode: { visible, via, reason, claudeMdFiles, link? }. visible is false when a CLAUDE.md, .claude/CLAUDE.md, or CLAUDE.local.md in the current directory or above it keeps Claude Code from reading AGENTS.md and none of them imports it; a warning then goes to stderr.',
1505
+ 'Re-running is safe: an identical file is left unchanged, an unedited file from an earlier CLI version is updated in place, and a missing workflows.md (for example after a SKILL.md-only install) is created.',
1506
+ 'A file with local edits, or one purvey did not write, is refused with exit 6 unless --force is passed. Every skill file is checked before any is written, so a refusal changes nothing.',
1507
+ ],
1508
+ examples: [
1509
+ 'purvey skill install --target claude',
1510
+ 'purvey skill install --target agents --dry-run',
1511
+ 'purvey skill install --target agents-md',
1512
+ 'purvey skill install --target agents-md --link-claude-md',
1513
+ ],
1514
+ },
1515
+ ],
1516
+ },
1005
1517
  {
1006
1518
  name: 'market',
1007
- summary: 'Market Index decision surface: value signals, movement stats, metadata trends, overview, and evidence via the canonical API',
1519
+ summary: 'Market Index value signals, price movement, metadata trends, market overview, and named-lot evidence',
1008
1520
  auth: 'mixed',
1009
1521
  subcommands: [
1010
1522
  {
@@ -1013,28 +1525,48 @@ const commandGroups = [
1013
1525
  auth: 'none',
1014
1526
  sdkMethods: ['market.signals'],
1015
1527
  options: [
1016
- { flags: '--summary' },
1528
+ {
1529
+ flags: '--summary',
1530
+ description: 'Return only signal counts; the one view that works without signing in or Parchment Intelligence',
1531
+ },
1017
1532
  {
1018
1533
  flags: '--type <type>',
1019
- notes: ['repeatable or comma-separated: price_drop|below_market|value_quality'],
1534
+ description: 'Signal types to include: price_drop, below_market, or value_quality; repeat the flag or separate with commas. Without it, all three',
1535
+ },
1536
+ { flags: '--origin <origin>', description: 'Only signals for this origin (exact name)' },
1537
+ {
1538
+ flags: '--process <method>',
1539
+ description: 'Only signals for this process group (exact name), such as washed',
1540
+ },
1541
+ {
1542
+ flags: '--market <retail|wholesale|all>',
1543
+ description: 'Which listings to read: retail, wholesale, or all',
1544
+ defaultValue: 'retail',
1545
+ },
1546
+ {
1547
+ flags: '--min-discount <n>',
1548
+ description: 'Only signals at least this large, as a percent price drop or discount',
1549
+ },
1550
+ {
1551
+ flags: '--min-score <n>',
1552
+ description: 'Only coffees with at least this supplier-stated cupping score',
1553
+ },
1554
+ {
1555
+ flags: '--window <7d|30d>',
1556
+ description: 'Time window for price-movement signals; value_quality signals are not time-windowed and always appear',
1557
+ defaultValue: '30d',
1020
1558
  },
1021
- { flags: '--origin <origin>' },
1022
- { flags: '--process <method>' },
1023
- { flags: '--market <retail|wholesale|all>' },
1024
- { flags: '--min-discount <n>' },
1025
- { flags: '--min-score <n>' },
1026
- { flags: '--window <7d|30d>' },
1027
1559
  {
1028
1560
  flags: '--limit <n>',
1029
- description: `Results per page, ${CLI_NUMERIC_BOUNDS.marketSignalsLimit.minimum}-${CLI_NUMERIC_BOUNDS.marketSignalsLimit.maximum}`,
1561
+ description: 'Number of signals per page',
1562
+ defaultValue: 20,
1030
1563
  minimum: CLI_NUMERIC_BOUNDS.marketSignalsLimit.minimum,
1031
1564
  maximum: CLI_NUMERIC_BOUNDS.marketSignalsLimit.maximum,
1032
1565
  },
1033
1566
  ],
1034
1567
  notes: [
1035
- 'Backed by the canonical API GET /v1/market/signals via @purveyors/sdk against api.purveyors.io.',
1036
- '--summary is the only unauthenticated slice (counts only); any filter requires Parchment Intelligence access, enforced server-side (403 on denial).',
1037
- '--json emits the API response verbatim (§3.3 evidence object, §3.4 enums); no client-side reshaping.',
1568
+ '--summary is the only view that works without credentials (counts only); every filter requires Parchment Intelligence access (exit 3 on denial).',
1569
+ '--json emits the API response unchanged.',
1038
1570
  ],
1039
1571
  examples: [
1040
1572
  'purvey market signals --summary --pretty',
@@ -1047,15 +1579,31 @@ const commandGroups = [
1047
1579
  auth: 'none',
1048
1580
  sdkMethods: ['priceIndex.stats'],
1049
1581
  options: [
1050
- { flags: '--origin <origin>' },
1051
- { flags: '--process <method>' },
1052
- { flags: '--market <retail|wholesale|all>' },
1053
- { flags: '--window <7d|30d>' },
1054
- { flags: '--baseline-weeks <n>' },
1582
+ { flags: '--origin <origin>', description: 'Only this origin (exact name)' },
1583
+ {
1584
+ flags: '--process <method>',
1585
+ description: 'Only this process group (exact name), such as washed',
1586
+ },
1587
+ {
1588
+ flags: '--market <retail|wholesale|all>',
1589
+ description: 'Which listings to measure: retail, wholesale, or all',
1590
+ defaultValue: 'retail',
1591
+ },
1592
+ {
1593
+ flags: '--window <7d|30d>',
1594
+ description: 'Length of the price move being measured',
1595
+ defaultValue: '7d',
1596
+ },
1597
+ {
1598
+ flags: '--baseline-weeks <n>',
1599
+ description: 'Weeks of history the move is compared against to judge how unusual it is',
1600
+ defaultValue: 12,
1601
+ minimum: 8,
1602
+ maximum: 52,
1603
+ },
1055
1604
  ],
1056
1605
  notes: [
1057
- 'Backed by the canonical API GET /v1/price-index/stats via @purveyors/sdk.',
1058
- 'The no-origin/no-process retail slice is public; origin/process/wholesale filters require Intelligence access.',
1606
+ 'The view with no origin or process at market=retail works without credentials; origin, process, and wholesale views require Parchment Intelligence access.',
1059
1607
  ],
1060
1608
  examples: [
1061
1609
  'purvey market stats --pretty',
@@ -1068,17 +1616,28 @@ const commandGroups = [
1068
1616
  auth: 'none',
1069
1617
  sdkMethods: ['market.metadataIndex'],
1070
1618
  options: [
1071
- { flags: '--dimension <process|disclosure|score>' },
1072
- { flags: '--origin <origin>' },
1073
- { flags: '--market <retail|wholesale|all>' },
1074
- { flags: '--grain <week|month>' },
1075
- { flags: '--from <date>' },
1076
- { flags: '--to <date>' },
1619
+ {
1620
+ flags: '--dimension <process|disclosure|score>',
1621
+ description: 'What to trend: process (process mix), disclosure (how much suppliers disclose), or score (Purveyor Score)',
1622
+ defaultValue: 'process',
1623
+ },
1624
+ { flags: '--origin <origin>', description: 'Only this origin (exact name)' },
1625
+ {
1626
+ flags: '--market <retail|wholesale|all>',
1627
+ description: 'Which listings to read: retail, wholesale, or all',
1628
+ defaultValue: 'retail',
1629
+ },
1630
+ {
1631
+ flags: '--grain <week|month>',
1632
+ description: 'Size of each period in the trend',
1633
+ defaultValue: 'month',
1634
+ },
1635
+ { flags: '--from <date>', description: 'First period to include, as YYYY-MM-DD' },
1636
+ { flags: '--to <date>', description: 'Last period to include, as YYYY-MM-DD' },
1077
1637
  ],
1078
1638
  notes: [
1079
- 'Backed by the canonical API GET /v1/market/metadata-index via @purveyors/sdk.',
1080
- 'Public slice: dimension=process, no origin, market=retail, grain=month; anything else requires Intelligence access.',
1081
- 'cultivar and drying dimensions are out of scope for v1 (await taxonomy normalization).',
1639
+ 'Public view: dimension=process, no origin, market=retail, grain=month; anything else requires Parchment Intelligence access.',
1640
+ 'Cultivar and drying dimensions are not available yet.',
1082
1641
  ],
1083
1642
  examples: [
1084
1643
  'purvey market metadata --pretty',
@@ -1091,9 +1650,8 @@ const commandGroups = [
1091
1650
  auth: 'viewer',
1092
1651
  sdkMethods: ['market.overview'],
1093
1652
  notes: [
1094
- 'Backed by the canonical API GET /v1/market/overview via @purveyors/sdk.',
1095
- 'The API rejects anonymous requests; any signed-in session or API key with catalog read access receives the same public evidence.',
1096
- 'Emits the API response verbatim: daily change, coverage, movement, process distribution, and origin price distributions.',
1653
+ 'Anonymous requests are rejected; any signed-in account or API key with catalog read access receives the same public evidence.',
1654
+ 'Emits the API response unchanged: daily change, coverage, movement, process distribution, and origin price distributions.',
1097
1655
  ],
1098
1656
  examples: ['purvey market overview --pretty'],
1099
1657
  },
@@ -1102,41 +1660,57 @@ const commandGroups = [
1102
1660
  summary: 'Named arrivals, delistings, comparable lots, and supplier health and price ranges',
1103
1661
  auth: 'member',
1104
1662
  sdkMethods: ['market.evidence'],
1105
- notes: [
1106
- 'Backed by the canonical API GET /v1/market/evidence via @purveyors/sdk.',
1107
- 'Requires Parchment Intelligence plus catalog read access, enforced server-side (401/403 on denial).',
1108
- ],
1663
+ notes: ['Requires Parchment Intelligence plus catalog read access (exit 3 on denial).'],
1109
1664
  examples: ['purvey market evidence --json'],
1110
1665
  },
1111
1666
  ],
1112
1667
  },
1113
1668
  {
1114
1669
  name: 'price-index',
1115
- summary: 'Parchment Price Index snapshots, matched 30-day comparisons, and chart history via the canonical API',
1670
+ summary: 'Parchment Price Index snapshots, matched 30-day price comparisons, and chart history',
1116
1671
  auth: 'mixed',
1117
1672
  command: {
1118
1673
  name: 'price-index',
1119
- summary: 'Fetch Parchment Price Index aggregate snapshots from the canonical API',
1674
+ summary: 'Fetch Parchment Price Index aggregate snapshots',
1120
1675
  auth: 'member',
1121
1676
  sdkMethods: ['priceIndex.list'],
1122
1677
  options: [
1123
- { flags: '--origin <origin>' },
1124
- { flags: '--process <method>' },
1125
- { flags: '--grade <grade>' },
1126
- { flags: '--from <date>' },
1127
- { flags: '--to <date>' },
1128
- { flags: '--wholesale <true|false>' },
1129
- { flags: '--page <n>' },
1678
+ {
1679
+ flags: '--origin <origin>',
1680
+ description: 'Only this origin (case-insensitive exact name)',
1681
+ },
1682
+ {
1683
+ flags: '--process <method>',
1684
+ description: 'Only this process (case-insensitive exact name), such as washed',
1685
+ },
1686
+ { flags: '--grade <grade>', description: 'Only this grade (case-insensitive exact name)' },
1687
+ {
1688
+ flags: '--from <date>',
1689
+ description: 'Only snapshots on or after this date, as YYYY-MM-DD',
1690
+ },
1691
+ {
1692
+ flags: '--to <date>',
1693
+ description: 'Only snapshots on or before this date, as YYYY-MM-DD',
1694
+ },
1695
+ {
1696
+ flags: '--wholesale <true|false>',
1697
+ description: 'true for wholesale prices only, false for retail only; without it, both',
1698
+ },
1699
+ {
1700
+ flags: '--page <n>',
1701
+ description: 'Page number, starting at 1',
1702
+ defaultValue: 1,
1703
+ },
1130
1704
  {
1131
1705
  flags: '--limit <n>',
1132
- description: `Results per page, ${CLI_NUMERIC_BOUNDS.priceIndexLimit.minimum}-${CLI_NUMERIC_BOUNDS.priceIndexLimit.maximum}`,
1706
+ description: 'Number of snapshots per page',
1707
+ defaultValue: 100,
1133
1708
  minimum: CLI_NUMERIC_BOUNDS.priceIndexLimit.minimum,
1134
1709
  maximum: CLI_NUMERIC_BOUNDS.priceIndexLimit.maximum,
1135
1710
  },
1136
1711
  ],
1137
1712
  notes: [
1138
- 'Backed by the canonical API GET /v1/price-index via @purveyors/sdk against api.purveyors.io.',
1139
- 'Requires a scoped member API key with price-index (PPI) access; the API enforces the entitlement.',
1713
+ 'Requires a scoped member API key with Parchment Price Index access.',
1140
1714
  'PARCHMENT_API_KEY or PURVEYORS_API_KEY overrides the scoped API key stored by `purvey auth login`.',
1141
1715
  '--from and --to accept ISO dates (YYYY-MM-DD); --wholesale accepts "true" or "false".',
1142
1716
  ],
@@ -1152,12 +1726,17 @@ const commandGroups = [
1152
1726
  summary: 'Available exact 30-day matched price comparisons with significance',
1153
1727
  auth: 'member',
1154
1728
  sdkMethods: ['priceIndex.comparisons'],
1155
- options: [{ flags: '--wholesale <true|false|all>' }],
1729
+ options: [
1730
+ {
1731
+ flags: '--wholesale <true|false|all>',
1732
+ description: 'true for wholesale listings, false for retail, or all for both',
1733
+ defaultValue: 'false',
1734
+ },
1735
+ ],
1156
1736
  notes: [
1157
- 'Backed by the canonical API GET /v1/price-index/comparisons via @purveyors/sdk.',
1158
- 'Requires Parchment Intelligence access, enforced server-side.',
1159
- 'The API discovers origins with an exact 30-day matched comparison; an empty comparisons list means none qualify.',
1160
- 'Each comparison carries significance verbatim; classification (quiet|normal|notable|exceptional) is null until eight baseline windows exist, meaning not enough history, never quiet.',
1737
+ 'Requires Parchment Intelligence access.',
1738
+ 'Lists every origin with an exact 30-day matched comparison; an empty comparisons list means none qualify.',
1739
+ 'Each comparison carries significance unchanged; classification (quiet|normal|notable|exceptional) is null until eight baseline windows exist, meaning not enough history, never quiet.',
1161
1740
  ],
1162
1741
  examples: [
1163
1742
  'purvey price-index comparisons --pretty',
@@ -1170,14 +1749,23 @@ const commandGroups = [
1170
1749
  auth: 'member',
1171
1750
  sdkMethods: ['priceIndex.comparison'],
1172
1751
  options: [
1173
- { flags: '--origin <origin>', notes: ['required'] },
1174
- { flags: '--from <date>', notes: ['required; ISO date YYYY-MM-DD'] },
1175
- { flags: '--to <date>', notes: ['required; ISO date within 365 days of --from'] },
1176
- { flags: '--wholesale <true|false>' },
1752
+ { flags: '--origin <origin>', description: 'Required. Origin to compare' },
1753
+ {
1754
+ flags: '--from <date>',
1755
+ description: 'Required. Starting date, as YYYY-MM-DD (UTC)',
1756
+ },
1757
+ {
1758
+ flags: '--to <date>',
1759
+ description: 'Required. Ending date, as YYYY-MM-DD (UTC), within 365 days of --from',
1760
+ },
1761
+ {
1762
+ flags: '--wholesale <true|false>',
1763
+ description: 'true to compare wholesale listings, false for retail',
1764
+ defaultValue: 'false',
1765
+ },
1177
1766
  ],
1178
1767
  notes: [
1179
- 'Backed by the canonical API GET /v1/price-index/comparison via @purveyors/sdk.',
1180
- 'Requires Parchment Intelligence access, enforced server-side.',
1768
+ 'Requires Parchment Intelligence access.',
1181
1769
  'status insufficient_fresh_coverage returns a null changePercent, never zero.',
1182
1770
  ],
1183
1771
  examples: [
@@ -1186,28 +1774,37 @@ const commandGroups = [
1186
1774
  },
1187
1775
  {
1188
1776
  name: 'history',
1189
- summary: 'Tier-one price-index chart history; windows up to 90 days are public',
1777
+ summary: 'Daily price-index history for charts; windows up to 90 days are public',
1190
1778
  auth: 'none',
1191
1779
  sdkMethods: ['priceIndex.history'],
1192
1780
  options: [
1193
1781
  {
1194
1782
  flags: '--window-days <n>',
1195
- description: `Trailing window in days, ${CLI_NUMERIC_BOUNDS.priceIndexHistoryWindowDays.minimum}-${CLI_NUMERIC_BOUNDS.priceIndexHistoryWindowDays.maximum}`,
1783
+ description: 'Number of past days to include; up to 90 works without signing in, longer windows need Parchment Intelligence',
1784
+ defaultValue: 90,
1196
1785
  minimum: CLI_NUMERIC_BOUNDS.priceIndexHistoryWindowDays.minimum,
1197
1786
  maximum: CLI_NUMERIC_BOUNDS.priceIndexHistoryWindowDays.maximum,
1198
1787
  },
1199
- { flags: '--page <n>' },
1788
+ {
1789
+ flags: '--page <n>',
1790
+ description: 'Page number, starting at 1',
1791
+ defaultValue: 1,
1792
+ },
1200
1793
  {
1201
1794
  flags: '--limit <n>',
1202
- description: `Results per page, ${CLI_NUMERIC_BOUNDS.priceIndexHistoryLimit.minimum}-${CLI_NUMERIC_BOUNDS.priceIndexHistoryLimit.maximum}`,
1795
+ description: 'Number of data points per page',
1796
+ defaultValue: 100,
1203
1797
  minimum: CLI_NUMERIC_BOUNDS.priceIndexHistoryLimit.minimum,
1204
1798
  maximum: CLI_NUMERIC_BOUNDS.priceIndexHistoryLimit.maximum,
1205
1799
  },
1206
- { flags: '--order <asc|desc>' },
1800
+ {
1801
+ flags: '--order <asc|desc>',
1802
+ description: 'Date order: asc (oldest first) or desc (newest first)',
1803
+ defaultValue: 'asc',
1804
+ },
1207
1805
  ],
1208
1806
  notes: [
1209
- 'Backed by the canonical API GET /v1/price-index/history via @purveyors/sdk.',
1210
- 'Windows up to 90 days (the default) run without credentials; 91-365 days require Parchment Intelligence access, enforced server-side.',
1807
+ 'Windows up to 90 days (the default) run without credentials; 91-365 days require Parchment Intelligence access.',
1211
1808
  ],
1212
1809
  examples: [
1213
1810
  'purvey price-index history --pretty',
@@ -1218,7 +1815,7 @@ const commandGroups = [
1218
1815
  },
1219
1816
  {
1220
1817
  name: 'procurement',
1221
- summary: 'Read saved sourcing briefs and their catalog matches from the canonical API',
1818
+ summary: 'Read your saved sourcing briefs and the catalog coffees that match them',
1222
1819
  auth: 'member',
1223
1820
  subcommands: [
1224
1821
  {
@@ -1226,10 +1823,7 @@ const commandGroups = [
1226
1823
  summary: 'List your saved sourcing briefs',
1227
1824
  auth: 'member',
1228
1825
  sdkMethods: ['procurement.briefs.list'],
1229
- notes: [
1230
- 'Backed by the canonical API GET /v1/procurement/briefs via @purveyors/sdk.',
1231
- 'Brief creation is a write handled by the Phase 2 write build-out (PADR-0016), not this read surface.',
1232
- ],
1826
+ notes: ['The CLI reads saved briefs; it does not create them.'],
1233
1827
  examples: ['purvey procurement list --pretty'],
1234
1828
  },
1235
1829
  {
@@ -1245,7 +1839,6 @@ const commandGroups = [
1245
1839
  required: true,
1246
1840
  },
1247
1841
  ],
1248
- notes: ['Backed by the canonical API GET /v1/procurement/briefs/{id} via @purveyors/sdk.'],
1249
1842
  examples: ['purvey procurement get <brief-id> --pretty'],
1250
1843
  },
1251
1844
  {
@@ -1262,18 +1855,19 @@ const commandGroups = [
1262
1855
  },
1263
1856
  ],
1264
1857
  options: [
1265
- { flags: '--page <n>' },
1858
+ {
1859
+ flags: '--page <n>',
1860
+ description: 'Page number, starting at 1',
1861
+ defaultValue: 1,
1862
+ },
1266
1863
  {
1267
1864
  flags: '--limit <n>',
1268
- description: `Matches per page, ${CLI_NUMERIC_BOUNDS.procurementMatchesLimit.minimum}-${CLI_NUMERIC_BOUNDS.procurementMatchesLimit.maximum}`,
1865
+ description: 'Number of matching coffees per page',
1866
+ defaultValue: 25,
1269
1867
  minimum: CLI_NUMERIC_BOUNDS.procurementMatchesLimit.minimum,
1270
1868
  maximum: CLI_NUMERIC_BOUNDS.procurementMatchesLimit.maximum,
1271
1869
  },
1272
1870
  ],
1273
- notes: [
1274
- 'Backed by the canonical API GET /v1/procurement/briefs/{id}/matches via @purveyors/sdk.',
1275
- '--limit maxes at 100 (default 25); --page is 1-based (default 1).',
1276
- ],
1277
1871
  examples: [
1278
1872
  'purvey procurement matches <brief-id> --pretty',
1279
1873
  'purvey procurement matches <brief-id> --page 2 --limit 50 --json',
@@ -1283,7 +1877,7 @@ const commandGroups = [
1283
1877
  },
1284
1878
  {
1285
1879
  name: 'reference-profile',
1286
- summary: 'Compare, preview, save, and export Studio reference plans through the canonical API',
1880
+ summary: 'Import, compare, adjust, save, and export Studio reference roast plans',
1287
1881
  auth: 'member',
1288
1882
  subcommands: [
1289
1883
  {
@@ -1291,11 +1885,13 @@ const commandGroups = [
1291
1885
  summary: 'List your Studio reference profiles',
1292
1886
  auth: 'member',
1293
1887
  sdkMethods: ['referenceProfiles.list'],
1294
- options: [{ flags: '--include-archived' }],
1295
- notes: [
1296
- 'Backed by GET /v1/reference-profiles via @purveyors/sdk.',
1297
- 'Requires a member credential and Studio access; entitlement is enforced by Parchment.',
1888
+ options: [
1889
+ {
1890
+ flags: '--include-archived',
1891
+ description: 'Also list reference profiles you have archived',
1892
+ },
1298
1893
  ],
1894
+ notes: ['Requires a member credential and Studio access on your account.'],
1299
1895
  examples: ['purvey reference-profile list --pretty'],
1300
1896
  },
1301
1897
  {
@@ -1307,7 +1903,7 @@ const commandGroups = [
1307
1903
  {
1308
1904
  name: 'reference_profile_id',
1309
1905
  cliToken: 'profile-id',
1310
- description: 'owner-scoped reference profile UUID',
1906
+ description: 'Reference profile UUID',
1311
1907
  required: true,
1312
1908
  idType: 'reference_profile_id',
1313
1909
  },
@@ -1317,28 +1913,25 @@ const commandGroups = [
1317
1913
  },
1318
1914
  {
1319
1915
  name: 'chart',
1320
- summary: 'Get the typed Artisan chart for a reference revision',
1916
+ summary: 'Get the Artisan chart for a reference revision',
1321
1917
  auth: 'member',
1322
1918
  sdkMethods: ['referenceProfiles.chart'],
1323
1919
  arguments: [
1324
1920
  {
1325
1921
  name: 'reference_profile_id',
1326
1922
  cliToken: 'profile-id',
1327
- description: 'owner-scoped reference profile UUID',
1923
+ description: 'Reference profile UUID',
1328
1924
  required: true,
1329
1925
  idType: 'reference_profile_id',
1330
1926
  },
1331
1927
  {
1332
1928
  name: 'reference_revision_id',
1333
1929
  cliToken: 'revision-id',
1334
- description: 'immutable revision UUID belonging to the profile',
1930
+ description: 'Revision UUID belonging to the profile',
1335
1931
  required: true,
1336
1932
  idType: 'reference_revision_id',
1337
1933
  },
1338
1934
  ],
1339
- notes: [
1340
- 'Backed by GET /v1/reference-profiles/{id}/revisions/{revisionId}/chart via @purveyors/sdk.',
1341
- ],
1342
1935
  examples: [
1343
1936
  'purvey reference-profile chart 5ea1af6f-234c-43a9-9bf8-5678dd24f854 8d2c41e0-7b9a-4f3e-a6d1-2c9e5f07b3a4 --pretty',
1344
1937
  ],
@@ -1356,13 +1949,16 @@ const commandGroups = [
1356
1949
  },
1357
1950
  ],
1358
1951
  options: [
1359
- { flags: '--title <text>' },
1360
- { flags: '--notes <text>' },
1361
- { flags: '--idempotency-key <key>', description: 'Stable key for safe retries' },
1952
+ { flags: '--title <text>', description: 'Title for the new reference profile' },
1953
+ { flags: '--notes <text>', description: 'Notes about the reference profile' },
1954
+ {
1955
+ flags: '--idempotency-key <key>',
1956
+ description: 'Key that makes retries safe: repeating the upload with the same key does not create a second profile. A new key is generated when omitted',
1957
+ },
1362
1958
  ],
1363
1959
  notes: [
1364
- 'Backed by POST /v1/reference-profiles/imports via @purveyors/sdk; parsing and private retention are server-owned.',
1365
- 'A new idempotency key is generated when omitted; reuse an explicit key to replay the same upload.',
1960
+ 'The uploaded file is parsed on purveyors.io and kept private to your account.',
1961
+ 'Reuse an explicit idempotency key to replay the same upload.',
1366
1962
  'Import creates a reference profile, not executed roast history.',
1367
1963
  ],
1368
1964
  examples: [
@@ -1387,19 +1983,23 @@ const commandGroups = [
1387
1983
  },
1388
1984
  ],
1389
1985
  options: [
1390
- { flags: '--unit <F|C>', defaultValue: 'F' },
1986
+ {
1987
+ flags: '--unit <F|C>',
1988
+ description: 'Temperature unit for the compared series: F (Fahrenheit) or C (Celsius)',
1989
+ defaultValue: 'F',
1990
+ },
1391
1991
  {
1392
1992
  flags: '--target-points <n>',
1393
- description: 'Samples per aligned series, 50-1000',
1993
+ description: 'Number of samples in each aligned temperature series',
1394
1994
  defaultValue: 400,
1395
1995
  minimum: 50,
1396
1996
  maximum: 1000,
1397
1997
  },
1398
1998
  ],
1399
1999
  notes: [
1400
- 'Backed by POST /v1/profile-comparisons via @purveyors/sdk; output is the canonical comparison envelope.',
2000
+ 'Output is the comparison result unchanged.',
1401
2001
  'profile:<uuid> resolves the profile currentRevisionId; roast:<roast-id> resolves data.metadata.revision from roast chart data.',
1402
- 'Parchment aligns both sides at charge; a reference profile is never executed roast history.',
2002
+ 'Both sides are aligned at charge; a reference profile is never executed roast history.',
1403
2003
  ],
1404
2004
  examples: [
1405
2005
  'purvey reference-profile compare roast:123 profile:5ea1af6f-234c-43a9-9bf8-5678dd24f854 --pretty',
@@ -1415,24 +2015,28 @@ const commandGroups = [
1415
2015
  {
1416
2016
  name: 'reference_profile_id',
1417
2017
  cliToken: 'profile-id',
1418
- description: 'owner-scoped reference profile UUID',
2018
+ description: 'Reference profile UUID',
1419
2019
  required: true,
1420
2020
  idType: 'reference_profile_id',
1421
2021
  },
1422
2022
  {
1423
2023
  name: 'reference_revision_id',
1424
2024
  cliToken: 'revision-id',
1425
- description: 'immutable parent revision UUID',
2025
+ description: 'Revision UUID to adjust; it is never changed',
1426
2026
  required: true,
1427
2027
  idType: 'reference_revision_id',
1428
2028
  },
1429
2029
  ],
1430
- options: [{ flags: '--request <file>', requiredInFlagMode: true }],
2030
+ options: [
2031
+ {
2032
+ flags: '--request <file>',
2033
+ description: 'Required. JSON file with a title and up to 12 temperature adjustments; save accepts the same file',
2034
+ },
2035
+ ],
1431
2036
  notes: [
1432
- 'Backed by POST /v1/reference-profiles/{id}/revisions/{revisionId}/preview via @purveyors/sdk.',
1433
2037
  'Request JSON contains title, optional notes, optional userGoal (600 chars), modelRecommendation (800), and userEdits (800) provenance, and changes.temperatureAdjustments; each adjustment has kind, startMilliseconds, endMilliseconds, and delta.',
1434
2038
  'At most 12 adjustments are accepted; intervals must be ordered and each nonzero delta is bounded to -20 through 20.',
1435
- 'Parchment recalculates from the immutable parent; preview never persists or changes the source.',
2039
+ 'The plan is recalculated from the unchanged parent revision; preview never saves anything.',
1436
2040
  ],
1437
2041
  examples: [
1438
2042
  'purvey reference-profile preview 5ea1af6f-234c-43a9-9bf8-5678dd24f854 8d2c41e0-7b9a-4f3e-a6d1-2c9e5f07b3a4 --request changes.json --pretty',
@@ -1448,25 +2052,30 @@ const commandGroups = [
1448
2052
  {
1449
2053
  name: 'reference_profile_id',
1450
2054
  cliToken: 'profile-id',
1451
- description: 'owner-scoped reference profile UUID',
2055
+ description: 'Reference profile UUID',
1452
2056
  required: true,
1453
2057
  idType: 'reference_profile_id',
1454
2058
  },
1455
2059
  {
1456
2060
  name: 'reference_revision_id',
1457
2061
  cliToken: 'revision-id',
1458
- description: 'immutable parent revision UUID',
2062
+ description: 'Revision UUID the new plan is based on; it is never changed',
1459
2063
  required: true,
1460
2064
  idType: 'reference_revision_id',
1461
2065
  },
1462
2066
  ],
1463
2067
  options: [
1464
- { flags: '--request <file>', requiredInFlagMode: true },
1465
- { flags: '--idempotency-key <key>', description: 'Stable key for safe retries' },
2068
+ {
2069
+ flags: '--request <file>',
2070
+ description: 'Required. The same JSON request file you previewed',
2071
+ },
2072
+ {
2073
+ flags: '--idempotency-key <key>',
2074
+ description: 'Key that makes retries safe: repeating the save with the same key does not create a second plan. A new key is generated when omitted',
2075
+ },
1466
2076
  ],
1467
2077
  notes: [
1468
- 'Backed by POST /v1/reference-profiles/{id}/revisions/{revisionId}/generated via @purveyors/sdk.',
1469
- 'A new idempotency key is generated when omitted; reuse an explicit key to replay the same save.',
2078
+ 'Reuse an explicit idempotency key to replay the same save.',
1470
2079
  'Generated references are plans and never create executed roast history.',
1471
2080
  ],
1472
2081
  examples: [
@@ -1482,24 +2091,26 @@ const commandGroups = [
1482
2091
  {
1483
2092
  name: 'reference_profile_id',
1484
2093
  cliToken: 'profile-id',
1485
- description: 'saved generated reference profile UUID',
2094
+ description: 'UUID of a saved generated reference profile',
1486
2095
  required: true,
1487
2096
  idType: 'reference_profile_id',
1488
2097
  },
1489
2098
  {
1490
2099
  name: 'reference_revision_id',
1491
2100
  cliToken: 'revision-id',
1492
- description: 'saved generated revision UUID',
2101
+ description: 'UUID of the saved generated revision',
1493
2102
  required: true,
1494
2103
  idType: 'reference_revision_id',
1495
2104
  },
1496
2105
  ],
1497
2106
  options: [
1498
- { flags: '--output <file>', requiredInFlagMode: true },
1499
- { flags: '--force', description: 'Overwrite the destination file if it exists' },
2107
+ {
2108
+ flags: '--output <file>',
2109
+ description: 'Required. Path to write the .alog file to',
2110
+ },
2111
+ { flags: '--force', description: 'Overwrite the output file if it already exists' },
1500
2112
  ],
1501
2113
  notes: [
1502
- 'Backed by GET /v1/reference-profiles/{id}/revisions/{revisionId}/export via @purveyors/sdk.',
1503
2114
  'Only a saved generated revision can be exported; output is a local file plus a JSON receipt.',
1504
2115
  'The CLI does not claim verified Artisan 4.2 playback compatibility.',
1505
2116
  ],
@@ -1515,7 +2126,7 @@ const workflows = [
1515
2126
  title: 'Catalog to inventory',
1516
2127
  commands: [
1517
2128
  'purvey catalog search --origin "Ethiopia" --stocked --pretty',
1518
- 'purvey inventory add --catalog-id 128 --qty 10 --cost 8.50',
2129
+ 'purvey inventory add --catalog-id 128 --qty 10 --cost 85.00',
1519
2130
  ],
1520
2131
  },
1521
2132
  {
@@ -1526,6 +2137,14 @@ const workflows = [
1526
2137
  "purvey catalog similar 1182 --threshold 0.85 --stocked-only --json | jq '.data.groups'",
1527
2138
  ],
1528
2139
  },
2140
+ {
2141
+ title: 'Check prices and market moves',
2142
+ commands: [
2143
+ 'purvey market signals --summary --pretty',
2144
+ 'purvey market stats --origin "Colombia" --window 30d --json',
2145
+ 'purvey price-index history --window-days 90 --json',
2146
+ ],
2147
+ },
1529
2148
  {
1530
2149
  title: 'Import a roast from Artisan',
1531
2150
  commands: [
@@ -1573,13 +2192,15 @@ const errorPatterns = [
1573
2192
  exitCodes: [EXIT_CODES.INVALID_ARGUMENT, EXIT_CODES.NOT_FOUND],
1574
2193
  guidance: [
1575
2194
  'Verify whether the command wants catalog_id, inventory_id, roast_id, sale_id, reference_profile_id, or reference_revision_id.',
1576
- 'See the ID MAP section.',
2195
+ 'See the ID map.',
1577
2196
  ],
1578
2197
  },
1579
2198
  {
1580
2199
  title: 'Missing required args in write commands',
1581
2200
  exitCodes: [EXIT_CODES.INVALID_ARGUMENT],
1582
- guidance: ['Pass the required flags or use --form.'],
2201
+ guidance: [
2202
+ 'Pass the required positional arguments and flags shown by `--help`; `--form` is an interactive terminal alternative.',
2203
+ ],
1583
2204
  },
1584
2205
  {
1585
2206
  title: 'Parser mistakes like unknown options or commands',
@@ -1600,14 +2221,30 @@ const errorPatterns = [
1600
2221
  guidance: ['`roast watch` forbids using --auto-match together with --coffee-id.'],
1601
2222
  },
1602
2223
  {
1603
- title: 'Pagination only returning the first 20 results',
2224
+ title: 'Pagination only returning the first page',
1604
2225
  exitCodes: [],
1605
2226
  guidance: [
1606
- 'All list commands default to --limit 20.',
1607
- 'Use --offset to page, for example `--limit 20 --offset 40` returns items 41-60.',
2227
+ `Only these commands page with --offset (default --limit): ${paginatedCommands(commandGroups).join(', ')}.`,
2228
+ 'For example `--limit 20 --offset 40` returns items 41-60.',
1608
2229
  ],
1609
2230
  },
1610
2231
  ];
2232
+ /** Commands that take both --limit and --offset, so pagination guidance cannot name one that lacks them. */
2233
+ function paginatedCommands(groups) {
2234
+ const paged = [];
2235
+ for (const group of groups) {
2236
+ const commands = [...(group.command ? [group.command] : []), ...(group.subcommands ?? [])];
2237
+ for (const command of commands) {
2238
+ const options = command.options ?? [];
2239
+ const limit = options.find((option) => option.flags.startsWith('--limit '));
2240
+ if (limit && options.some((option) => option.flags.startsWith('--offset '))) {
2241
+ const name = command === group.command ? group.name : `${group.name} ${command.name}`;
2242
+ paged.push(limit.defaultValue === undefined ? name : `${name} ${limit.defaultValue}`);
2243
+ }
2244
+ }
2245
+ }
2246
+ return paged;
2247
+ }
1611
2248
  export function getCliManifest() {
1612
2249
  return {
1613
2250
  schemaVersion: '1',
@@ -1645,7 +2282,7 @@ export function getCliManifest() {
1645
2282
  'Use --json to request compact JSON explicitly.',
1646
2283
  'Use --pretty for indented JSON.',
1647
2284
  'Use --csv for array-shaped results that support CSV output.',
1648
- 'Set PURVEYORS_API_KEY or PARCHMENT_API_KEY when intentionally using API-key backed catalog proof reads.',
2285
+ 'PURVEYORS_API_KEY or PARCHMENT_API_KEY, when set, is used instead of the key stored by `purvey auth login`.',
1649
2286
  '`purvey context` prints dense human-readable operator reference text unless --json or --pretty is passed.',
1650
2287
  '`purvey manifest` is the preferred machine-readable contract and always emits it on stdout.',
1651
2288
  '`purvey context --json` stays available for compatibility parity with existing context-based callers.',
@@ -1696,9 +2333,15 @@ function renderRoles(roleContracts, groups) {
1696
2333
  'Commands that talk to purveyors.io generally require authentication unless listed above as public, local-only, or mixed-access teaser slices.',
1697
2334
  ...roleContracts.map((role) => `${role.role.padEnd(7, ' ')} ${role.description}`),
1698
2335
  '',
1699
- 'Both roles are granted on sign-in through purveyors.io.',
2336
+ 'viewer comes with any purveyors.io sign-in; member requires a membership (https://purveyors.io/account).',
1700
2337
  ];
1701
2338
  }
2339
+ /** Device-approval steps for `purvey auth login --headless`, shared by context text and the agent skill. */
2340
+ export const HEADLESS_LOGIN_STEPS = [
2341
+ 'CLI prints a purveyors.io approval URL',
2342
+ 'User opens it in any browser, signs in, and approves access',
2343
+ 'CLI completes automatically; nothing is pasted back',
2344
+ ];
1702
2345
  function renderAuthSection() {
1703
2346
  return [
1704
2347
  'AUTH',
@@ -1709,9 +2352,7 @@ function renderAuthSection() {
1709
2352
  '',
1710
2353
  'Headless login:',
1711
2354
  ' purvey auth login --headless',
1712
- ' 1. CLI prints a purveyors.io approval URL',
1713
- ' 2. User opens it in any browser, signs in, and approves access',
1714
- ' 3. CLI completes automatically; nothing is pasted back',
2355
+ ...HEADLESS_LOGIN_STEPS.map((step, index) => ` ${index + 1}. ${step}`),
1715
2356
  '',
1716
2357
  'Status:',
1717
2358
  ' purvey auth status',