@purveyors/cli 0.36.0 → 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 (46) hide show
  1. package/README.md +8 -6
  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.map +1 -1
  32. package/dist/commands/skill.js +7 -7
  33. package/dist/commands/skill.js.map +1 -1
  34. package/dist/commands/tasting.d.ts.map +1 -1
  35. package/dist/commands/tasting.js +16 -16
  36. package/dist/commands/tasting.js.map +1 -1
  37. package/dist/lib/agent-skill.d.ts.map +1 -1
  38. package/dist/lib/agent-skill.js +1 -5
  39. package/dist/lib/agent-skill.js.map +1 -1
  40. package/dist/lib/manifest.d.ts.map +1 -1
  41. package/dist/lib/manifest.js +871 -347
  42. package/dist/lib/manifest.js.map +1 -1
  43. package/dist/program.d.ts.map +1 -1
  44. package/dist/program.js +63 -12
  45. package/dist/program.js.map +1 -1
  46. 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.',
@@ -1014,11 +1441,10 @@ const commandGroups = [
1014
1441
  options: [
1015
1442
  {
1016
1443
  flags: '--file <file>',
1017
- description: 'Skill file to print: SKILL.md, workflows.md, or all',
1444
+ description: 'Skill file to print: SKILL.md, workflows.md, or all; all needs --json or --pretty',
1018
1445
  defaultValue: 'SKILL.md',
1019
1446
  notes: [
1020
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',
1021
- 'all needs --json or --pretty',
1022
1448
  ],
1023
1449
  },
1024
1450
  {
@@ -1045,9 +1471,8 @@ const commandGroups = [
1045
1471
  options: [
1046
1472
  {
1047
1473
  flags: '--target <target>',
1048
- description: 'claude, agents, or agents-md',
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)",
1049
1475
  notes: [
1050
- 'required',
1051
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',
1052
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/',
1053
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',
@@ -1055,7 +1480,7 @@ const commandGroups = [
1055
1480
  },
1056
1481
  {
1057
1482
  flags: '--scope <scope>',
1058
- description: 'user (home directory) or project (current directory); defaults to user, or project for agents-md',
1483
+ description: 'user (your home directory) or project (the current directory); defaults to user, or project for agents-md',
1059
1484
  },
1060
1485
  {
1061
1486
  flags: '--force',
@@ -1091,7 +1516,7 @@ const commandGroups = [
1091
1516
  },
1092
1517
  {
1093
1518
  name: 'market',
1094
- 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',
1095
1520
  auth: 'mixed',
1096
1521
  subcommands: [
1097
1522
  {
@@ -1100,28 +1525,48 @@ const commandGroups = [
1100
1525
  auth: 'none',
1101
1526
  sdkMethods: ['market.signals'],
1102
1527
  options: [
1103
- { flags: '--summary' },
1528
+ {
1529
+ flags: '--summary',
1530
+ description: 'Return only signal counts; the one view that works without signing in or Parchment Intelligence',
1531
+ },
1104
1532
  {
1105
1533
  flags: '--type <type>',
1106
- 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',
1107
1558
  },
1108
- { flags: '--origin <origin>' },
1109
- { flags: '--process <method>' },
1110
- { flags: '--market <retail|wholesale|all>' },
1111
- { flags: '--min-discount <n>' },
1112
- { flags: '--min-score <n>' },
1113
- { flags: '--window <7d|30d>' },
1114
1559
  {
1115
1560
  flags: '--limit <n>',
1116
- description: `Results per page, ${CLI_NUMERIC_BOUNDS.marketSignalsLimit.minimum}-${CLI_NUMERIC_BOUNDS.marketSignalsLimit.maximum}`,
1561
+ description: 'Number of signals per page',
1562
+ defaultValue: 20,
1117
1563
  minimum: CLI_NUMERIC_BOUNDS.marketSignalsLimit.minimum,
1118
1564
  maximum: CLI_NUMERIC_BOUNDS.marketSignalsLimit.maximum,
1119
1565
  },
1120
1566
  ],
1121
1567
  notes: [
1122
- 'Backed by the canonical API GET /v1/market/signals via @purveyors/sdk against api.purveyors.io.',
1123
- '--summary is the only unauthenticated slice (counts only); any filter requires Parchment Intelligence access, enforced server-side (403 on denial).',
1124
- '--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.',
1125
1570
  ],
1126
1571
  examples: [
1127
1572
  'purvey market signals --summary --pretty',
@@ -1134,15 +1579,31 @@ const commandGroups = [
1134
1579
  auth: 'none',
1135
1580
  sdkMethods: ['priceIndex.stats'],
1136
1581
  options: [
1137
- { flags: '--origin <origin>' },
1138
- { flags: '--process <method>' },
1139
- { flags: '--market <retail|wholesale|all>' },
1140
- { flags: '--window <7d|30d>' },
1141
- { 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
+ },
1142
1604
  ],
1143
1605
  notes: [
1144
- 'Backed by the canonical API GET /v1/price-index/stats via @purveyors/sdk.',
1145
- '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.',
1146
1607
  ],
1147
1608
  examples: [
1148
1609
  'purvey market stats --pretty',
@@ -1155,17 +1616,28 @@ const commandGroups = [
1155
1616
  auth: 'none',
1156
1617
  sdkMethods: ['market.metadataIndex'],
1157
1618
  options: [
1158
- { flags: '--dimension <process|disclosure|score>' },
1159
- { flags: '--origin <origin>' },
1160
- { flags: '--market <retail|wholesale|all>' },
1161
- { flags: '--grain <week|month>' },
1162
- { flags: '--from <date>' },
1163
- { 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' },
1164
1637
  ],
1165
1638
  notes: [
1166
- 'Backed by the canonical API GET /v1/market/metadata-index via @purveyors/sdk.',
1167
- 'Public slice: dimension=process, no origin, market=retail, grain=month; anything else requires Intelligence access.',
1168
- '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.',
1169
1641
  ],
1170
1642
  examples: [
1171
1643
  'purvey market metadata --pretty',
@@ -1178,9 +1650,8 @@ const commandGroups = [
1178
1650
  auth: 'viewer',
1179
1651
  sdkMethods: ['market.overview'],
1180
1652
  notes: [
1181
- 'Backed by the canonical API GET /v1/market/overview via @purveyors/sdk.',
1182
- 'The API rejects anonymous requests; any signed-in session or API key with catalog read access receives the same public evidence.',
1183
- '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.',
1184
1655
  ],
1185
1656
  examples: ['purvey market overview --pretty'],
1186
1657
  },
@@ -1189,41 +1660,57 @@ const commandGroups = [
1189
1660
  summary: 'Named arrivals, delistings, comparable lots, and supplier health and price ranges',
1190
1661
  auth: 'member',
1191
1662
  sdkMethods: ['market.evidence'],
1192
- notes: [
1193
- 'Backed by the canonical API GET /v1/market/evidence via @purveyors/sdk.',
1194
- 'Requires Parchment Intelligence plus catalog read access, enforced server-side (401/403 on denial).',
1195
- ],
1663
+ notes: ['Requires Parchment Intelligence plus catalog read access (exit 3 on denial).'],
1196
1664
  examples: ['purvey market evidence --json'],
1197
1665
  },
1198
1666
  ],
1199
1667
  },
1200
1668
  {
1201
1669
  name: 'price-index',
1202
- 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',
1203
1671
  auth: 'mixed',
1204
1672
  command: {
1205
1673
  name: 'price-index',
1206
- summary: 'Fetch Parchment Price Index aggregate snapshots from the canonical API',
1674
+ summary: 'Fetch Parchment Price Index aggregate snapshots',
1207
1675
  auth: 'member',
1208
1676
  sdkMethods: ['priceIndex.list'],
1209
1677
  options: [
1210
- { flags: '--origin <origin>' },
1211
- { flags: '--process <method>' },
1212
- { flags: '--grade <grade>' },
1213
- { flags: '--from <date>' },
1214
- { flags: '--to <date>' },
1215
- { flags: '--wholesale <true|false>' },
1216
- { 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
+ },
1217
1704
  {
1218
1705
  flags: '--limit <n>',
1219
- description: `Results per page, ${CLI_NUMERIC_BOUNDS.priceIndexLimit.minimum}-${CLI_NUMERIC_BOUNDS.priceIndexLimit.maximum}`,
1706
+ description: 'Number of snapshots per page',
1707
+ defaultValue: 100,
1220
1708
  minimum: CLI_NUMERIC_BOUNDS.priceIndexLimit.minimum,
1221
1709
  maximum: CLI_NUMERIC_BOUNDS.priceIndexLimit.maximum,
1222
1710
  },
1223
1711
  ],
1224
1712
  notes: [
1225
- 'Backed by the canonical API GET /v1/price-index via @purveyors/sdk against api.purveyors.io.',
1226
- '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.',
1227
1714
  'PARCHMENT_API_KEY or PURVEYORS_API_KEY overrides the scoped API key stored by `purvey auth login`.',
1228
1715
  '--from and --to accept ISO dates (YYYY-MM-DD); --wholesale accepts "true" or "false".',
1229
1716
  ],
@@ -1239,12 +1726,17 @@ const commandGroups = [
1239
1726
  summary: 'Available exact 30-day matched price comparisons with significance',
1240
1727
  auth: 'member',
1241
1728
  sdkMethods: ['priceIndex.comparisons'],
1242
- 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
+ ],
1243
1736
  notes: [
1244
- 'Backed by the canonical API GET /v1/price-index/comparisons via @purveyors/sdk.',
1245
- 'Requires Parchment Intelligence access, enforced server-side.',
1246
- 'The API discovers origins with an exact 30-day matched comparison; an empty comparisons list means none qualify.',
1247
- '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.',
1248
1740
  ],
1249
1741
  examples: [
1250
1742
  'purvey price-index comparisons --pretty',
@@ -1257,14 +1749,23 @@ const commandGroups = [
1257
1749
  auth: 'member',
1258
1750
  sdkMethods: ['priceIndex.comparison'],
1259
1751
  options: [
1260
- { flags: '--origin <origin>', notes: ['required'] },
1261
- { flags: '--from <date>', notes: ['required; ISO date YYYY-MM-DD'] },
1262
- { flags: '--to <date>', notes: ['required; ISO date within 365 days of --from'] },
1263
- { 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
+ },
1264
1766
  ],
1265
1767
  notes: [
1266
- 'Backed by the canonical API GET /v1/price-index/comparison via @purveyors/sdk.',
1267
- 'Requires Parchment Intelligence access, enforced server-side.',
1768
+ 'Requires Parchment Intelligence access.',
1268
1769
  'status insufficient_fresh_coverage returns a null changePercent, never zero.',
1269
1770
  ],
1270
1771
  examples: [
@@ -1273,28 +1774,37 @@ const commandGroups = [
1273
1774
  },
1274
1775
  {
1275
1776
  name: 'history',
1276
- 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',
1277
1778
  auth: 'none',
1278
1779
  sdkMethods: ['priceIndex.history'],
1279
1780
  options: [
1280
1781
  {
1281
1782
  flags: '--window-days <n>',
1282
- 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,
1283
1785
  minimum: CLI_NUMERIC_BOUNDS.priceIndexHistoryWindowDays.minimum,
1284
1786
  maximum: CLI_NUMERIC_BOUNDS.priceIndexHistoryWindowDays.maximum,
1285
1787
  },
1286
- { flags: '--page <n>' },
1788
+ {
1789
+ flags: '--page <n>',
1790
+ description: 'Page number, starting at 1',
1791
+ defaultValue: 1,
1792
+ },
1287
1793
  {
1288
1794
  flags: '--limit <n>',
1289
- 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,
1290
1797
  minimum: CLI_NUMERIC_BOUNDS.priceIndexHistoryLimit.minimum,
1291
1798
  maximum: CLI_NUMERIC_BOUNDS.priceIndexHistoryLimit.maximum,
1292
1799
  },
1293
- { 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
+ },
1294
1805
  ],
1295
1806
  notes: [
1296
- 'Backed by the canonical API GET /v1/price-index/history via @purveyors/sdk.',
1297
- '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.',
1298
1808
  ],
1299
1809
  examples: [
1300
1810
  'purvey price-index history --pretty',
@@ -1305,7 +1815,7 @@ const commandGroups = [
1305
1815
  },
1306
1816
  {
1307
1817
  name: 'procurement',
1308
- 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',
1309
1819
  auth: 'member',
1310
1820
  subcommands: [
1311
1821
  {
@@ -1313,10 +1823,7 @@ const commandGroups = [
1313
1823
  summary: 'List your saved sourcing briefs',
1314
1824
  auth: 'member',
1315
1825
  sdkMethods: ['procurement.briefs.list'],
1316
- notes: [
1317
- 'Backed by the canonical API GET /v1/procurement/briefs via @purveyors/sdk.',
1318
- 'Brief creation is a write handled by the Phase 2 write build-out (PADR-0016), not this read surface.',
1319
- ],
1826
+ notes: ['The CLI reads saved briefs; it does not create them.'],
1320
1827
  examples: ['purvey procurement list --pretty'],
1321
1828
  },
1322
1829
  {
@@ -1332,7 +1839,6 @@ const commandGroups = [
1332
1839
  required: true,
1333
1840
  },
1334
1841
  ],
1335
- notes: ['Backed by the canonical API GET /v1/procurement/briefs/{id} via @purveyors/sdk.'],
1336
1842
  examples: ['purvey procurement get <brief-id> --pretty'],
1337
1843
  },
1338
1844
  {
@@ -1349,18 +1855,19 @@ const commandGroups = [
1349
1855
  },
1350
1856
  ],
1351
1857
  options: [
1352
- { flags: '--page <n>' },
1858
+ {
1859
+ flags: '--page <n>',
1860
+ description: 'Page number, starting at 1',
1861
+ defaultValue: 1,
1862
+ },
1353
1863
  {
1354
1864
  flags: '--limit <n>',
1355
- 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,
1356
1867
  minimum: CLI_NUMERIC_BOUNDS.procurementMatchesLimit.minimum,
1357
1868
  maximum: CLI_NUMERIC_BOUNDS.procurementMatchesLimit.maximum,
1358
1869
  },
1359
1870
  ],
1360
- notes: [
1361
- 'Backed by the canonical API GET /v1/procurement/briefs/{id}/matches via @purveyors/sdk.',
1362
- '--limit maxes at 100 (default 25); --page is 1-based (default 1).',
1363
- ],
1364
1871
  examples: [
1365
1872
  'purvey procurement matches <brief-id> --pretty',
1366
1873
  'purvey procurement matches <brief-id> --page 2 --limit 50 --json',
@@ -1370,7 +1877,7 @@ const commandGroups = [
1370
1877
  },
1371
1878
  {
1372
1879
  name: 'reference-profile',
1373
- 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',
1374
1881
  auth: 'member',
1375
1882
  subcommands: [
1376
1883
  {
@@ -1378,11 +1885,13 @@ const commandGroups = [
1378
1885
  summary: 'List your Studio reference profiles',
1379
1886
  auth: 'member',
1380
1887
  sdkMethods: ['referenceProfiles.list'],
1381
- options: [{ flags: '--include-archived' }],
1382
- notes: [
1383
- 'Backed by GET /v1/reference-profiles via @purveyors/sdk.',
1384
- '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
+ },
1385
1893
  ],
1894
+ notes: ['Requires a member credential and Studio access on your account.'],
1386
1895
  examples: ['purvey reference-profile list --pretty'],
1387
1896
  },
1388
1897
  {
@@ -1394,7 +1903,7 @@ const commandGroups = [
1394
1903
  {
1395
1904
  name: 'reference_profile_id',
1396
1905
  cliToken: 'profile-id',
1397
- description: 'owner-scoped reference profile UUID',
1906
+ description: 'Reference profile UUID',
1398
1907
  required: true,
1399
1908
  idType: 'reference_profile_id',
1400
1909
  },
@@ -1404,28 +1913,25 @@ const commandGroups = [
1404
1913
  },
1405
1914
  {
1406
1915
  name: 'chart',
1407
- summary: 'Get the typed Artisan chart for a reference revision',
1916
+ summary: 'Get the Artisan chart for a reference revision',
1408
1917
  auth: 'member',
1409
1918
  sdkMethods: ['referenceProfiles.chart'],
1410
1919
  arguments: [
1411
1920
  {
1412
1921
  name: 'reference_profile_id',
1413
1922
  cliToken: 'profile-id',
1414
- description: 'owner-scoped reference profile UUID',
1923
+ description: 'Reference profile UUID',
1415
1924
  required: true,
1416
1925
  idType: 'reference_profile_id',
1417
1926
  },
1418
1927
  {
1419
1928
  name: 'reference_revision_id',
1420
1929
  cliToken: 'revision-id',
1421
- description: 'immutable revision UUID belonging to the profile',
1930
+ description: 'Revision UUID belonging to the profile',
1422
1931
  required: true,
1423
1932
  idType: 'reference_revision_id',
1424
1933
  },
1425
1934
  ],
1426
- notes: [
1427
- 'Backed by GET /v1/reference-profiles/{id}/revisions/{revisionId}/chart via @purveyors/sdk.',
1428
- ],
1429
1935
  examples: [
1430
1936
  'purvey reference-profile chart 5ea1af6f-234c-43a9-9bf8-5678dd24f854 8d2c41e0-7b9a-4f3e-a6d1-2c9e5f07b3a4 --pretty',
1431
1937
  ],
@@ -1443,13 +1949,16 @@ const commandGroups = [
1443
1949
  },
1444
1950
  ],
1445
1951
  options: [
1446
- { flags: '--title <text>' },
1447
- { flags: '--notes <text>' },
1448
- { 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
+ },
1449
1958
  ],
1450
1959
  notes: [
1451
- 'Backed by POST /v1/reference-profiles/imports via @purveyors/sdk; parsing and private retention are server-owned.',
1452
- '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.',
1453
1962
  'Import creates a reference profile, not executed roast history.',
1454
1963
  ],
1455
1964
  examples: [
@@ -1474,19 +1983,23 @@ const commandGroups = [
1474
1983
  },
1475
1984
  ],
1476
1985
  options: [
1477
- { 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
+ },
1478
1991
  {
1479
1992
  flags: '--target-points <n>',
1480
- description: 'Samples per aligned series, 50-1000',
1993
+ description: 'Number of samples in each aligned temperature series',
1481
1994
  defaultValue: 400,
1482
1995
  minimum: 50,
1483
1996
  maximum: 1000,
1484
1997
  },
1485
1998
  ],
1486
1999
  notes: [
1487
- 'Backed by POST /v1/profile-comparisons via @purveyors/sdk; output is the canonical comparison envelope.',
2000
+ 'Output is the comparison result unchanged.',
1488
2001
  'profile:<uuid> resolves the profile currentRevisionId; roast:<roast-id> resolves data.metadata.revision from roast chart data.',
1489
- '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.',
1490
2003
  ],
1491
2004
  examples: [
1492
2005
  'purvey reference-profile compare roast:123 profile:5ea1af6f-234c-43a9-9bf8-5678dd24f854 --pretty',
@@ -1502,24 +2015,28 @@ const commandGroups = [
1502
2015
  {
1503
2016
  name: 'reference_profile_id',
1504
2017
  cliToken: 'profile-id',
1505
- description: 'owner-scoped reference profile UUID',
2018
+ description: 'Reference profile UUID',
1506
2019
  required: true,
1507
2020
  idType: 'reference_profile_id',
1508
2021
  },
1509
2022
  {
1510
2023
  name: 'reference_revision_id',
1511
2024
  cliToken: 'revision-id',
1512
- description: 'immutable parent revision UUID',
2025
+ description: 'Revision UUID to adjust; it is never changed',
1513
2026
  required: true,
1514
2027
  idType: 'reference_revision_id',
1515
2028
  },
1516
2029
  ],
1517
- 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
+ ],
1518
2036
  notes: [
1519
- 'Backed by POST /v1/reference-profiles/{id}/revisions/{revisionId}/preview via @purveyors/sdk.',
1520
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.',
1521
2038
  'At most 12 adjustments are accepted; intervals must be ordered and each nonzero delta is bounded to -20 through 20.',
1522
- '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.',
1523
2040
  ],
1524
2041
  examples: [
1525
2042
  'purvey reference-profile preview 5ea1af6f-234c-43a9-9bf8-5678dd24f854 8d2c41e0-7b9a-4f3e-a6d1-2c9e5f07b3a4 --request changes.json --pretty',
@@ -1535,25 +2052,30 @@ const commandGroups = [
1535
2052
  {
1536
2053
  name: 'reference_profile_id',
1537
2054
  cliToken: 'profile-id',
1538
- description: 'owner-scoped reference profile UUID',
2055
+ description: 'Reference profile UUID',
1539
2056
  required: true,
1540
2057
  idType: 'reference_profile_id',
1541
2058
  },
1542
2059
  {
1543
2060
  name: 'reference_revision_id',
1544
2061
  cliToken: 'revision-id',
1545
- description: 'immutable parent revision UUID',
2062
+ description: 'Revision UUID the new plan is based on; it is never changed',
1546
2063
  required: true,
1547
2064
  idType: 'reference_revision_id',
1548
2065
  },
1549
2066
  ],
1550
2067
  options: [
1551
- { flags: '--request <file>', requiredInFlagMode: true },
1552
- { 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
+ },
1553
2076
  ],
1554
2077
  notes: [
1555
- 'Backed by POST /v1/reference-profiles/{id}/revisions/{revisionId}/generated via @purveyors/sdk.',
1556
- '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.',
1557
2079
  'Generated references are plans and never create executed roast history.',
1558
2080
  ],
1559
2081
  examples: [
@@ -1569,24 +2091,26 @@ const commandGroups = [
1569
2091
  {
1570
2092
  name: 'reference_profile_id',
1571
2093
  cliToken: 'profile-id',
1572
- description: 'saved generated reference profile UUID',
2094
+ description: 'UUID of a saved generated reference profile',
1573
2095
  required: true,
1574
2096
  idType: 'reference_profile_id',
1575
2097
  },
1576
2098
  {
1577
2099
  name: 'reference_revision_id',
1578
2100
  cliToken: 'revision-id',
1579
- description: 'saved generated revision UUID',
2101
+ description: 'UUID of the saved generated revision',
1580
2102
  required: true,
1581
2103
  idType: 'reference_revision_id',
1582
2104
  },
1583
2105
  ],
1584
2106
  options: [
1585
- { flags: '--output <file>', requiredInFlagMode: true },
1586
- { 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' },
1587
2112
  ],
1588
2113
  notes: [
1589
- 'Backed by GET /v1/reference-profiles/{id}/revisions/{revisionId}/export via @purveyors/sdk.',
1590
2114
  'Only a saved generated revision can be exported; output is a local file plus a JSON receipt.',
1591
2115
  'The CLI does not claim verified Artisan 4.2 playback compatibility.',
1592
2116
  ],
@@ -1602,7 +2126,7 @@ const workflows = [
1602
2126
  title: 'Catalog to inventory',
1603
2127
  commands: [
1604
2128
  'purvey catalog search --origin "Ethiopia" --stocked --pretty',
1605
- 'purvey inventory add --catalog-id 128 --qty 10 --cost 8.50',
2129
+ 'purvey inventory add --catalog-id 128 --qty 10 --cost 85.00',
1606
2130
  ],
1607
2131
  },
1608
2132
  {
@@ -1758,7 +2282,7 @@ export function getCliManifest() {
1758
2282
  'Use --json to request compact JSON explicitly.',
1759
2283
  'Use --pretty for indented JSON.',
1760
2284
  'Use --csv for array-shaped results that support CSV output.',
1761
- '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`.',
1762
2286
  '`purvey context` prints dense human-readable operator reference text unless --json or --pretty is passed.',
1763
2287
  '`purvey manifest` is the preferred machine-readable contract and always emits it on stdout.',
1764
2288
  '`purvey context --json` stays available for compatibility parity with existing context-based callers.',