@purveyors/cli 0.33.2 → 0.35.0

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 (87) hide show
  1. package/README.md +176 -13
  2. package/dist/commands/auth.d.ts.map +1 -1
  3. package/dist/commands/auth.js +21 -0
  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 +40 -13
  7. package/dist/commands/catalog.js.map +1 -1
  8. package/dist/commands/context.d.ts.map +1 -1
  9. package/dist/commands/context.js +2 -0
  10. package/dist/commands/context.js.map +1 -1
  11. package/dist/commands/inventory.d.ts.map +1 -1
  12. package/dist/commands/inventory.js +34 -11
  13. package/dist/commands/inventory.js.map +1 -1
  14. package/dist/commands/manifest.d.ts.map +1 -1
  15. package/dist/commands/manifest.js +2 -0
  16. package/dist/commands/manifest.js.map +1 -1
  17. package/dist/commands/market.d.ts.map +1 -1
  18. package/dist/commands/market.js +40 -2
  19. package/dist/commands/market.js.map +1 -1
  20. package/dist/commands/price-index.d.ts +4 -2
  21. package/dist/commands/price-index.d.ts.map +1 -1
  22. package/dist/commands/price-index.js +148 -5
  23. package/dist/commands/price-index.js.map +1 -1
  24. package/dist/commands/reference-profile.d.ts +4 -0
  25. package/dist/commands/reference-profile.d.ts.map +1 -0
  26. package/dist/commands/reference-profile.js +200 -0
  27. package/dist/commands/reference-profile.js.map +1 -0
  28. package/dist/commands/roast.d.ts.map +1 -1
  29. package/dist/commands/roast.js +42 -1
  30. package/dist/commands/roast.js.map +1 -1
  31. package/dist/lib/catalog.d.ts +26 -18
  32. package/dist/lib/catalog.d.ts.map +1 -1
  33. package/dist/lib/catalog.js +38 -46
  34. package/dist/lib/catalog.js.map +1 -1
  35. package/dist/lib/cherry.d.ts +2 -16
  36. package/dist/lib/cherry.d.ts.map +1 -1
  37. package/dist/lib/cherry.js +19 -5
  38. package/dist/lib/cherry.js.map +1 -1
  39. package/dist/lib/interactive/watch.d.ts +2 -3
  40. package/dist/lib/interactive/watch.d.ts.map +1 -1
  41. package/dist/lib/interactive/watch.js +4 -26
  42. package/dist/lib/interactive/watch.js.map +1 -1
  43. package/dist/lib/inventory.d.ts +8 -1
  44. package/dist/lib/inventory.d.ts.map +1 -1
  45. package/dist/lib/inventory.js +19 -5
  46. package/dist/lib/inventory.js.map +1 -1
  47. package/dist/lib/manifest.d.ts +13 -0
  48. package/dist/lib/manifest.d.ts.map +1 -1
  49. package/dist/lib/manifest.js +498 -18
  50. package/dist/lib/manifest.js.map +1 -1
  51. package/dist/lib/market.d.ts.map +1 -1
  52. package/dist/lib/numeric-contracts.d.ts +8 -0
  53. package/dist/lib/numeric-contracts.d.ts.map +1 -1
  54. package/dist/lib/numeric-contracts.js +2 -0
  55. package/dist/lib/numeric-contracts.js.map +1 -1
  56. package/dist/lib/parchment.d.ts +10 -1
  57. package/dist/lib/parchment.d.ts.map +1 -1
  58. package/dist/lib/parchment.js +11 -1
  59. package/dist/lib/parchment.js.map +1 -1
  60. package/dist/lib/reference-profiles.d.ts +182 -0
  61. package/dist/lib/reference-profiles.d.ts.map +1 -0
  62. package/dist/lib/reference-profiles.js +236 -0
  63. package/dist/lib/reference-profiles.js.map +1 -0
  64. package/dist/lib/roast.d.ts +36 -2
  65. package/dist/lib/roast.d.ts.map +1 -1
  66. package/dist/lib/roast.js +23 -0
  67. package/dist/lib/roast.js.map +1 -1
  68. package/dist/program.d.ts.map +1 -1
  69. package/dist/program.js +23 -0
  70. package/dist/program.js.map +1 -1
  71. package/package.json +2 -2
  72. package/dist/lib/artisan/parser.d.ts +0 -19
  73. package/dist/lib/artisan/parser.d.ts.map +0 -1
  74. package/dist/lib/artisan/parser.js +0 -376
  75. package/dist/lib/artisan/parser.js.map +0 -1
  76. package/dist/lib/artisan/temperature.d.ts +0 -52
  77. package/dist/lib/artisan/temperature.d.ts.map +0 -1
  78. package/dist/lib/artisan/temperature.js +0 -101
  79. package/dist/lib/artisan/temperature.js.map +0 -1
  80. package/dist/lib/artisan/types.d.ts +0 -195
  81. package/dist/lib/artisan/types.d.ts.map +0 -1
  82. package/dist/lib/artisan/types.js +0 -35
  83. package/dist/lib/artisan/types.js.map +0 -1
  84. package/dist/lib/artisan/validator.d.ts +0 -14
  85. package/dist/lib/artisan/validator.d.ts.map +0 -1
  86. package/dist/lib/artisan/validator.js +0 -228
  87. package/dist/lib/artisan/validator.js.map +0 -1
@@ -84,13 +84,39 @@ const idTypes = [
84
84
  {
85
85
  name: 'roast_id',
86
86
  source: 'roast_data row',
87
- usedBy: ['roast get/delete', 'roast list --roast-id', 'sales record --roast-id'],
87
+ usedBy: [
88
+ 'roast get/chart/delete',
89
+ 'roast list --roast-id',
90
+ 'sales record --roast-id',
91
+ 'reference-profile compare roast:<roast-id>',
92
+ ],
88
93
  },
89
94
  {
90
95
  name: 'sale_id',
91
96
  source: 'coffee_sales row',
92
97
  usedBy: ['sales update/delete'],
93
98
  },
99
+ {
100
+ name: 'reference_profile_id',
101
+ source: 'owner-scoped Studio reference profile',
102
+ usedBy: [
103
+ 'reference-profile get',
104
+ 'reference-profile chart',
105
+ 'reference-profile preview/save',
106
+ 'reference-profile export',
107
+ 'reference-profile compare profile:<uuid>',
108
+ ],
109
+ },
110
+ {
111
+ name: 'reference_revision_id',
112
+ source: 'immutable revision belonging to a reference profile',
113
+ usedBy: [
114
+ 'reference-profile chart',
115
+ 'reference-profile preview/save',
116
+ 'reference-profile export',
117
+ 'reference-profile compare revision:<uuid>',
118
+ ],
119
+ },
94
120
  ];
95
121
  const commandGroups = [
96
122
  {
@@ -102,6 +128,7 @@ const commandGroups = [
102
128
  name: 'login',
103
129
  summary: 'Log in to purveyors.io',
104
130
  auth: 'none',
131
+ sdkMethods: ['cliAuth.create', 'cliAuth.exchange'],
105
132
  options: [
106
133
  { flags: '--headless', description: 'Print approval URL without opening a browser' },
107
134
  ],
@@ -111,6 +138,7 @@ const commandGroups = [
111
138
  name: 'status',
112
139
  summary: 'Show current login status and role',
113
140
  auth: 'none',
141
+ sdkMethods: ['me'],
114
142
  options: [{ flags: '--pretty' }, { flags: '--csv' }],
115
143
  examples: [
116
144
  'purvey auth status',
@@ -118,6 +146,21 @@ const commandGroups = [
118
146
  'purvey auth status --json',
119
147
  ],
120
148
  },
149
+ {
150
+ name: 'whoami',
151
+ summary: 'Show the canonical identity, plan, scopes, and capabilities for the active credential',
152
+ auth: 'none',
153
+ sdkMethods: ['me'],
154
+ notes: [
155
+ 'Prints the GET /v1/me response unchanged, including capabilities.profileStudio.',
156
+ 'Uses PARCHMENT_API_KEY or PURVEYORS_API_KEY when set, otherwise the stored login key.',
157
+ 'Without a credential it prints the anonymous principal (authenticated: false).',
158
+ ],
159
+ examples: [
160
+ 'purvey auth whoami --pretty',
161
+ "purvey auth whoami | jq '.capabilities.profileStudio'",
162
+ ],
163
+ },
121
164
  {
122
165
  name: 'logout',
123
166
  summary: 'Clear stored credentials',
@@ -135,6 +178,7 @@ const commandGroups = [
135
178
  name: 'search',
136
179
  summary: 'Search coffees by origin, process, price, and catalog metadata; structured process filters require member access',
137
180
  auth: 'viewer',
181
+ sdkMethods: ['catalog.list'],
138
182
  options: [
139
183
  { flags: '--origin <origin>' },
140
184
  { flags: '--process <method>' },
@@ -149,6 +193,18 @@ const commandGroups = [
149
193
  { flags: '--ids <n,n,...>' },
150
194
  { flags: '--variety <text>' },
151
195
  { flags: '--stocked-days <n>' },
196
+ {
197
+ flags: '--supplier <name>',
198
+ description: 'Canonical /v1/catalog supplier filter (partial source-name match)',
199
+ },
200
+ {
201
+ flags: '--drying-method <method>',
202
+ description: 'Canonical /v1/catalog dryingMethod filter; Parchment owns matching',
203
+ },
204
+ {
205
+ flags: '--flavor <keywords>',
206
+ description: 'Comma-separated canonical /v1/catalog flavorKeywords; a row matches any keyword',
207
+ },
152
208
  { flags: '--stocked' },
153
209
  { flags: '--sort <field>' },
154
210
  { flags: '--offset <n>', defaultValue: 0 },
@@ -183,6 +239,7 @@ const commandGroups = [
183
239
  'purvey catalog search --process-additive "hops" --processing-confidence-min 0.8 --pretty',
184
240
  'purvey catalog search --variety "gesha" --stocked --pretty',
185
241
  'purvey catalog search --stocked-days 30 --pretty',
242
+ 'purvey catalog search --supplier "Royal" --flavor "blueberry,jasmine" --stocked --pretty',
186
243
  'purvey catalog search --origin "Ethiopia" --include-proof --json',
187
244
  ],
188
245
  },
@@ -190,6 +247,7 @@ const commandGroups = [
190
247
  name: 'get',
191
248
  summary: 'Get details for a specific coffee by ID',
192
249
  auth: 'viewer',
250
+ sdkMethods: ['catalog.list'],
193
251
  arguments: [
194
252
  {
195
253
  name: 'catalog_id',
@@ -217,17 +275,19 @@ const commandGroups = [
217
275
  name: 'stats',
218
276
  summary: 'Aggregate statistics for the catalog',
219
277
  auth: 'viewer',
278
+ sdkMethods: ['catalog.stats'],
220
279
  examples: ['purvey catalog stats --pretty'],
221
280
  },
222
281
  {
223
282
  name: 'facets',
224
- summary: 'List distinct catalog facet values with counts',
283
+ summary: 'List counted catalog facet values for filter discovery',
225
284
  auth: 'viewer',
285
+ sdkMethods: ['catalog.facets'],
226
286
  arguments: [
227
287
  {
228
288
  name: 'field',
229
- description: 'facet field: supplier, country, processing_base_method, fermentation_type, drying_method, grade, wholesale',
230
- required: true,
289
+ description: 'optional facet field: supplier, country, processing_base_method, fermentation_type, drying_method, grade, wholesale',
290
+ required: false,
231
291
  },
232
292
  ],
233
293
  options: [
@@ -235,24 +295,30 @@ const commandGroups = [
235
295
  flags: '--all',
236
296
  description: 'Use all visible catalog rows instead of the default stocked-only scope.',
237
297
  },
238
- { flags: '--limit <n>', defaultValue: 60 },
239
298
  ],
240
299
  notes: [
241
- 'Counts values from catalog rows visible to the current client.',
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.',
242
303
  'Defaults to currently stocked catalog rows; use --all for all visible rows.',
243
- 'Output metadata reports stocked_only/scope, rows_examined, and truncation for deterministic agent use.',
244
304
  ],
245
305
  examples: [
246
306
  'purvey catalog facets supplier --pretty',
247
- 'purvey catalog facets country --limit 25 --json',
307
+ 'purvey catalog facets country --all --json',
308
+ 'purvey catalog facets --pretty',
248
309
  ],
249
310
  },
250
311
  {
251
312
  name: 'rank',
252
313
  summary: 'Rank catalog candidates by deterministic objective',
253
314
  auth: 'viewer',
315
+ sdkMethods: ['catalog.rank'],
254
316
  options: [
255
317
  { flags: '--objective <objective>', defaultValue: 'premium' },
318
+ {
319
+ flags: '--supplier <name>',
320
+ description: 'Canonical /v1/catalog/rank supplier filter',
321
+ },
256
322
  { flags: '--country <country>' },
257
323
  { flags: '--process <method>' },
258
324
  { flags: '--stocked' },
@@ -277,12 +343,14 @@ const commandGroups = [
277
343
  examples: [
278
344
  'purvey catalog rank --objective premium --stocked --pretty',
279
345
  'purvey catalog rank --objective value --country Ethiopia --price-max 12 --json',
346
+ 'purvey catalog rank --objective premium --supplier "Royal Coffee" --limit 5 --json',
280
347
  ],
281
348
  },
282
349
  {
283
350
  name: 'rank-premium',
284
351
  summary: 'Rank premium catalog candidates by Purveyor Score',
285
352
  auth: 'viewer',
353
+ sdkMethods: ['catalog.rankPremium'],
286
354
  options: [
287
355
  { flags: '--origin <origin>' },
288
356
  { flags: '--process <method>' },
@@ -307,6 +375,7 @@ const commandGroups = [
307
375
  name: 'supplier-list',
308
376
  summary: 'List supplier aggregates from catalog rows',
309
377
  auth: 'viewer',
378
+ sdkMethods: ['catalog.suppliers'],
310
379
  options: [
311
380
  { flags: '--country <country>' },
312
381
  { flags: '--stocked' },
@@ -325,6 +394,7 @@ const commandGroups = [
325
394
  name: 'supplier-detail',
326
395
  summary: 'Show aggregate detail for a supplier query',
327
396
  auth: 'viewer',
397
+ sdkMethods: ['catalog.supplierDetail'],
328
398
  arguments: [
329
399
  {
330
400
  name: 'supplier',
@@ -349,6 +419,7 @@ const commandGroups = [
349
419
  name: 'supplier-rank',
350
420
  summary: 'Rank suppliers by average Purveyor Score and stocked coverage',
351
421
  auth: 'viewer',
422
+ sdkMethods: ['catalog.supplierRank'],
352
423
  options: [
353
424
  { flags: '--country <country>' },
354
425
  { flags: '--stocked' },
@@ -376,6 +447,7 @@ const commandGroups = [
376
447
  name: 'similar',
377
448
  summary: 'Fetch beta canonical /v1/catalog/{id}/similar groups for likely same-lot candidates and similar recommendations',
378
449
  auth: 'member',
450
+ sdkMethods: ['catalog.similar'],
379
451
  arguments: [
380
452
  {
381
453
  name: 'catalog_id',
@@ -422,6 +494,7 @@ const commandGroups = [
422
494
  name: 'list',
423
495
  summary: 'List your green coffee inventory with catalog details',
424
496
  auth: 'member',
497
+ sdkMethods: ['inventory.list'],
425
498
  options: [
426
499
  { flags: '--stocked' },
427
500
  { flags: '--catalog-id <id>' },
@@ -445,6 +518,7 @@ const commandGroups = [
445
518
  name: 'get',
446
519
  summary: 'Get a single inventory item',
447
520
  auth: 'member',
521
+ sdkMethods: ['inventory.list'],
448
522
  arguments: [
449
523
  {
450
524
  name: 'inventory_id',
@@ -460,8 +534,17 @@ const commandGroups = [
460
534
  name: 'add',
461
535
  summary: 'Add a bean to your inventory',
462
536
  auth: 'member',
537
+ sdkMethods: ['inventory.create'],
538
+ confirmedActionEquivalents: ['add_bean_to_inventory'],
463
539
  options: [
464
- { flags: '--catalog-id <id>', requiredInFlagMode: true },
540
+ {
541
+ flags: '--catalog-id <id>',
542
+ description: 'Catalog lot to add; exactly one of --catalog-id or --manual-name',
543
+ },
544
+ {
545
+ flags: '--manual-name <name>',
546
+ description: 'Coffee name for a lot that is not in the catalog',
547
+ },
465
548
  { flags: '--qty <lbs>', requiredInFlagMode: true },
466
549
  { flags: '--cost <dollars>' },
467
550
  { flags: '--tax-ship <dollars>' },
@@ -469,12 +552,21 @@ const commandGroups = [
469
552
  { flags: '--purchase-date <YYYY-MM-DD>' },
470
553
  { flags: '--form' },
471
554
  ],
472
- examples: ['purvey inventory add --catalog-id 128 --qty 10 --cost 8.50 --pretty'],
555
+ notes: [
556
+ '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.',
558
+ ],
559
+ examples: [
560
+ 'purvey inventory add --catalog-id 128 --qty 10 --cost 8.50 --pretty',
561
+ 'purvey inventory add --manual-name "Farm-gate Ethiopia lot 7" --qty 12 --cost 96 --pretty',
562
+ ],
473
563
  },
474
564
  {
475
565
  name: 'update',
476
566
  summary: 'Update an inventory item',
477
567
  auth: 'member',
568
+ sdkMethods: ['inventory.update'],
569
+ confirmedActionEquivalents: ['update_bean'],
478
570
  arguments: [
479
571
  {
480
572
  name: 'inventory_id',
@@ -490,12 +582,14 @@ const commandGroups = [
490
582
  { flags: '--tax-ship <dollars>' },
491
583
  { flags: '--notes <text>' },
492
584
  { flags: '--stocked <true|false>' },
585
+ { flags: '--rank <n>', description: 'Owner-assigned integer rank for this lot' },
493
586
  ],
494
587
  },
495
588
  {
496
589
  name: 'delete',
497
590
  summary: 'Delete an inventory item',
498
591
  auth: 'member',
592
+ sdkMethods: ['inventory.delete'],
499
593
  arguments: [
500
594
  {
501
595
  name: 'inventory_id',
@@ -518,6 +612,7 @@ const commandGroups = [
518
612
  name: 'list',
519
613
  summary: 'List your roast profiles, sorted by date',
520
614
  auth: 'member',
615
+ sdkMethods: ['roasts.list'],
521
616
  options: [
522
617
  { flags: '--coffee-id <id>' },
523
618
  { flags: '--roast-id <id>' },
@@ -542,6 +637,7 @@ const commandGroups = [
542
637
  name: 'get',
543
638
  summary: 'Get a single roast profile',
544
639
  auth: 'member',
640
+ sdkMethods: ['roasts.get'],
545
641
  arguments: [
546
642
  {
547
643
  name: 'roast_id',
@@ -553,10 +649,43 @@ const commandGroups = [
553
649
  ],
554
650
  options: [{ flags: '--include-temps' }, { flags: '--include-events' }],
555
651
  },
652
+ {
653
+ name: 'chart',
654
+ summary: 'Fetch the sampled chart model for one roast (series, events, revision)',
655
+ auth: 'member',
656
+ sdkMethods: ['roasts.chartData'],
657
+ arguments: [
658
+ {
659
+ name: 'roast_id',
660
+ cliToken: 'id',
661
+ description: 'roast_data.roast_id',
662
+ required: true,
663
+ idType: 'roast_id',
664
+ },
665
+ ],
666
+ options: [
667
+ {
668
+ flags: '--target-points <n>',
669
+ description: 'Approximate samples per series, 50-1000; Parchment defaults to 400',
670
+ minimum: 50,
671
+ maximum: 1000,
672
+ },
673
+ ],
674
+ notes: [
675
+ "Returns Parchment's canonical chart-data envelope unchanged: sampled series, events, and metadata.",
676
+ 'data.metadata.revision identifies the immutable chart revision used by reference-profile compare.',
677
+ ],
678
+ examples: [
679
+ 'purvey roast chart 123 --pretty',
680
+ "purvey roast chart 123 --target-points 120 | jq '.data.metadata.revision'",
681
+ ],
682
+ },
556
683
  {
557
684
  name: 'create',
558
685
  summary: 'Create a new roast profile',
559
686
  auth: 'member',
687
+ sdkMethods: ['roasts.create'],
688
+ confirmedActionEquivalents: ['create_roast_session'],
560
689
  options: [
561
690
  { flags: '--coffee-id <id>', requiredInFlagMode: true },
562
691
  { flags: '--batch-name <name>' },
@@ -564,6 +693,8 @@ const commandGroups = [
564
693
  { flags: '--oz-out <oz>' },
565
694
  { flags: '--roast-date <YYYY-MM-DD>' },
566
695
  { flags: '--notes <text>' },
696
+ { flags: '--targets <text>' },
697
+ { flags: '--roaster-type <text>' },
567
698
  { flags: '--form' },
568
699
  ],
569
700
  },
@@ -571,6 +702,8 @@ const commandGroups = [
571
702
  name: 'update',
572
703
  summary: 'Update a roast profile',
573
704
  auth: 'member',
705
+ sdkMethods: ['roasts.update'],
706
+ confirmedActionEquivalents: ['update_roast_notes'],
574
707
  arguments: [
575
708
  {
576
709
  name: 'roast_id',
@@ -591,6 +724,7 @@ const commandGroups = [
591
724
  name: 'delete',
592
725
  summary: 'Delete a roast profile',
593
726
  auth: 'member',
727
+ sdkMethods: ['roasts.delete'],
594
728
  arguments: [
595
729
  {
596
730
  name: 'roast_id',
@@ -606,6 +740,7 @@ const commandGroups = [
606
740
  name: 'import',
607
741
  summary: 'Import an Artisan .alog roast file',
608
742
  auth: 'member',
743
+ sdkMethods: ['roasts.import'],
609
744
  arguments: [{ name: 'file', description: 'Path to Artisan .alog file', required: false }],
610
745
  options: [
611
746
  { flags: '--coffee-id <id>', requiredInFlagMode: true },
@@ -620,6 +755,7 @@ const commandGroups = [
620
755
  name: 'watch',
621
756
  summary: 'Watch a directory for new Artisan .alog files',
622
757
  auth: 'member',
758
+ sdkMethods: ['roasts.import', 'inventory.list', 'roasts.classify'],
623
759
  arguments: [{ name: 'directory', description: 'Directory to watch', required: false }],
624
760
  options: [
625
761
  { flags: '--coffee-id <inventory_id>' },
@@ -650,6 +786,7 @@ const commandGroups = [
650
786
  name: 'list',
651
787
  summary: 'List your sales, sorted by sell date',
652
788
  auth: 'member',
789
+ sdkMethods: ['sales.list'],
653
790
  options: [
654
791
  { flags: '--coffee-id <id>' },
655
792
  { flags: '--date-start <YYYY-MM-DD>' },
@@ -668,6 +805,8 @@ const commandGroups = [
668
805
  name: 'record',
669
806
  summary: 'Record a new sale',
670
807
  auth: 'member',
808
+ sdkMethods: ['sales.create', 'roasts.list', 'roasts.get'],
809
+ confirmedActionEquivalents: ['record_sale'],
671
810
  options: [
672
811
  { flags: '--roast-id <id>' },
673
812
  { flags: '--coffee-id <id>' },
@@ -689,6 +828,7 @@ const commandGroups = [
689
828
  name: 'update',
690
829
  summary: 'Update a sale record',
691
830
  auth: 'member',
831
+ sdkMethods: ['sales.update'],
692
832
  arguments: [
693
833
  {
694
834
  name: 'sale_id',
@@ -709,6 +849,7 @@ const commandGroups = [
709
849
  name: 'delete',
710
850
  summary: 'Delete a sale record',
711
851
  auth: 'member',
852
+ sdkMethods: ['sales.delete'],
712
853
  arguments: [
713
854
  {
714
855
  name: 'sale_id',
@@ -731,6 +872,7 @@ const commandGroups = [
731
872
  name: 'get',
732
873
  summary: 'Get tasting notes for a coffee',
733
874
  auth: 'member',
875
+ sdkMethods: ['tasting.get'],
734
876
  arguments: [
735
877
  {
736
878
  name: 'catalog_id',
@@ -746,6 +888,7 @@ const commandGroups = [
746
888
  name: 'rate',
747
889
  summary: 'Rate a coffee bean with cupping scores',
748
890
  auth: 'member',
891
+ sdkMethods: ['tasting.rate'],
749
892
  arguments: [
750
893
  {
751
894
  name: 'inventory_id',
@@ -861,13 +1004,14 @@ const commandGroups = [
861
1004
  },
862
1005
  {
863
1006
  name: 'market',
864
- summary: 'Market Index decision surface: value signals, movement stats, and metadata trends via the canonical API',
1007
+ summary: 'Market Index decision surface: value signals, movement stats, metadata trends, overview, and evidence via the canonical API',
865
1008
  auth: 'mixed',
866
1009
  subcommands: [
867
1010
  {
868
1011
  name: 'signals',
869
1012
  summary: 'Actionable market value signals; unfiltered summary is a public teaser',
870
1013
  auth: 'none',
1014
+ sdkMethods: ['market.signals'],
871
1015
  options: [
872
1016
  { flags: '--summary' },
873
1017
  {
@@ -901,6 +1045,7 @@ const commandGroups = [
901
1045
  name: 'stats',
902
1046
  summary: 'Price movement-significance stats; unfiltered retail slice is public',
903
1047
  auth: 'none',
1048
+ sdkMethods: ['priceIndex.stats'],
904
1049
  options: [
905
1050
  { flags: '--origin <origin>' },
906
1051
  { flags: '--process <method>' },
@@ -921,6 +1066,7 @@ const commandGroups = [
921
1066
  name: 'metadata',
922
1067
  summary: 'Metadata-trend index; process/retail/month slice is public',
923
1068
  auth: 'none',
1069
+ sdkMethods: ['market.metadataIndex'],
924
1070
  options: [
925
1071
  { flags: '--dimension <process|disclosure|score>' },
926
1072
  { flags: '--origin <origin>' },
@@ -939,16 +1085,40 @@ const commandGroups = [
939
1085
  'purvey market metadata --dimension score --origin "Ethiopia" --grain month --json',
940
1086
  ],
941
1087
  },
1088
+ {
1089
+ name: 'overview',
1090
+ summary: 'Aggregate-only green-coffee market overview over the public catalog',
1091
+ auth: 'viewer',
1092
+ sdkMethods: ['market.overview'],
1093
+ 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.',
1097
+ ],
1098
+ examples: ['purvey market overview --pretty'],
1099
+ },
1100
+ {
1101
+ name: 'evidence',
1102
+ summary: 'Named arrivals, delistings, comparable lots, and supplier health and price ranges',
1103
+ auth: 'member',
1104
+ 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
+ ],
1109
+ examples: ['purvey market evidence --json'],
1110
+ },
942
1111
  ],
943
1112
  },
944
1113
  {
945
1114
  name: 'price-index',
946
- summary: 'Parchment Price Index aggregate snapshots; requires price-index (PPI) access',
947
- auth: 'member',
1115
+ summary: 'Parchment Price Index snapshots, matched 30-day comparisons, and chart history via the canonical API',
1116
+ auth: 'mixed',
948
1117
  command: {
949
1118
  name: 'price-index',
950
1119
  summary: 'Fetch Parchment Price Index aggregate snapshots from the canonical API',
951
1120
  auth: 'member',
1121
+ sdkMethods: ['priceIndex.list'],
952
1122
  options: [
953
1123
  { flags: '--origin <origin>' },
954
1124
  { flags: '--process <method>' },
@@ -976,6 +1146,75 @@ const commandGroups = [
976
1146
  'purvey price-index --from 2026-01-01 --to 2026-06-30 --pretty',
977
1147
  ],
978
1148
  },
1149
+ subcommands: [
1150
+ {
1151
+ name: 'comparisons',
1152
+ summary: 'Available exact 30-day matched price comparisons with significance',
1153
+ auth: 'member',
1154
+ sdkMethods: ['priceIndex.comparisons'],
1155
+ options: [{ flags: '--wholesale <true|false|all>' }],
1156
+ 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.',
1161
+ ],
1162
+ examples: [
1163
+ 'purvey price-index comparisons --pretty',
1164
+ 'purvey price-index comparisons --wholesale all --json',
1165
+ ],
1166
+ },
1167
+ {
1168
+ name: 'comparison',
1169
+ summary: 'Matched-listing price comparison for one origin between two exact dates',
1170
+ auth: 'member',
1171
+ sdkMethods: ['priceIndex.comparison'],
1172
+ 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>' },
1177
+ ],
1178
+ notes: [
1179
+ 'Backed by the canonical API GET /v1/price-index/comparison via @purveyors/sdk.',
1180
+ 'Requires Parchment Intelligence access, enforced server-side.',
1181
+ 'status insufficient_fresh_coverage returns a null changePercent, never zero.',
1182
+ ],
1183
+ examples: [
1184
+ 'purvey price-index comparison --origin "Ethiopia" --from 2026-08-31 --to 2026-09-30 --pretty',
1185
+ ],
1186
+ },
1187
+ {
1188
+ name: 'history',
1189
+ summary: 'Tier-one price-index chart history; windows up to 90 days are public',
1190
+ auth: 'none',
1191
+ sdkMethods: ['priceIndex.history'],
1192
+ options: [
1193
+ {
1194
+ flags: '--window-days <n>',
1195
+ description: `Trailing window in days, ${CLI_NUMERIC_BOUNDS.priceIndexHistoryWindowDays.minimum}-${CLI_NUMERIC_BOUNDS.priceIndexHistoryWindowDays.maximum}`,
1196
+ minimum: CLI_NUMERIC_BOUNDS.priceIndexHistoryWindowDays.minimum,
1197
+ maximum: CLI_NUMERIC_BOUNDS.priceIndexHistoryWindowDays.maximum,
1198
+ },
1199
+ { flags: '--page <n>' },
1200
+ {
1201
+ flags: '--limit <n>',
1202
+ description: `Results per page, ${CLI_NUMERIC_BOUNDS.priceIndexHistoryLimit.minimum}-${CLI_NUMERIC_BOUNDS.priceIndexHistoryLimit.maximum}`,
1203
+ minimum: CLI_NUMERIC_BOUNDS.priceIndexHistoryLimit.minimum,
1204
+ maximum: CLI_NUMERIC_BOUNDS.priceIndexHistoryLimit.maximum,
1205
+ },
1206
+ { flags: '--order <asc|desc>' },
1207
+ ],
1208
+ 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.',
1211
+ ],
1212
+ examples: [
1213
+ 'purvey price-index history --pretty',
1214
+ 'purvey price-index history --window-days 365 --limit 500 --json',
1215
+ ],
1216
+ },
1217
+ ],
979
1218
  },
980
1219
  {
981
1220
  name: 'procurement',
@@ -986,6 +1225,7 @@ const commandGroups = [
986
1225
  name: 'list',
987
1226
  summary: 'List your saved sourcing briefs',
988
1227
  auth: 'member',
1228
+ sdkMethods: ['procurement.briefs.list'],
989
1229
  notes: [
990
1230
  'Backed by the canonical API GET /v1/procurement/briefs via @purveyors/sdk.',
991
1231
  'Brief creation is a write handled by the Phase 2 write build-out (PADR-0016), not this read surface.',
@@ -996,6 +1236,7 @@ const commandGroups = [
996
1236
  name: 'get',
997
1237
  summary: 'Get a single saved sourcing brief by id',
998
1238
  auth: 'member',
1239
+ sdkMethods: ['procurement.briefs.get'],
999
1240
  arguments: [
1000
1241
  {
1001
1242
  name: 'brief_id',
@@ -1011,6 +1252,7 @@ const commandGroups = [
1011
1252
  name: 'matches',
1012
1253
  summary: 'Run a saved brief against the catalog and page through matches',
1013
1254
  auth: 'member',
1255
+ sdkMethods: ['procurement.briefs.matches'],
1014
1256
  arguments: [
1015
1257
  {
1016
1258
  name: 'brief_id',
@@ -1039,6 +1281,234 @@ const commandGroups = [
1039
1281
  },
1040
1282
  ],
1041
1283
  },
1284
+ {
1285
+ name: 'reference-profile',
1286
+ summary: 'Compare, preview, save, and export Studio reference plans through the canonical API',
1287
+ auth: 'member',
1288
+ subcommands: [
1289
+ {
1290
+ name: 'list',
1291
+ summary: 'List your Studio reference profiles',
1292
+ auth: 'member',
1293
+ 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.',
1298
+ ],
1299
+ examples: ['purvey reference-profile list --pretty'],
1300
+ },
1301
+ {
1302
+ name: 'get',
1303
+ summary: 'Get one reference profile and its current revision',
1304
+ auth: 'member',
1305
+ sdkMethods: ['referenceProfiles.get'],
1306
+ arguments: [
1307
+ {
1308
+ name: 'reference_profile_id',
1309
+ cliToken: 'profile-id',
1310
+ description: 'owner-scoped reference profile UUID',
1311
+ required: true,
1312
+ idType: 'reference_profile_id',
1313
+ },
1314
+ ],
1315
+ notes: ['Use data.currentRevision.id for chart, preview, and save.'],
1316
+ examples: ['purvey reference-profile get 5ea1af6f-234c-43a9-9bf8-5678dd24f854 --pretty'],
1317
+ },
1318
+ {
1319
+ name: 'chart',
1320
+ summary: 'Get the typed Artisan chart for a reference revision',
1321
+ auth: 'member',
1322
+ sdkMethods: ['referenceProfiles.chart'],
1323
+ arguments: [
1324
+ {
1325
+ name: 'reference_profile_id',
1326
+ cliToken: 'profile-id',
1327
+ description: 'owner-scoped reference profile UUID',
1328
+ required: true,
1329
+ idType: 'reference_profile_id',
1330
+ },
1331
+ {
1332
+ name: 'reference_revision_id',
1333
+ cliToken: 'revision-id',
1334
+ description: 'immutable revision UUID belonging to the profile',
1335
+ required: true,
1336
+ idType: 'reference_revision_id',
1337
+ },
1338
+ ],
1339
+ notes: [
1340
+ 'Backed by GET /v1/reference-profiles/{id}/revisions/{revisionId}/chart via @purveyors/sdk.',
1341
+ ],
1342
+ examples: [
1343
+ 'purvey reference-profile chart 5ea1af6f-234c-43a9-9bf8-5678dd24f854 8d2c41e0-7b9a-4f3e-a6d1-2c9e5f07b3a4 --pretty',
1344
+ ],
1345
+ },
1346
+ {
1347
+ name: 'import',
1348
+ summary: 'Upload an Artisan file as a private Studio reference profile',
1349
+ auth: 'member',
1350
+ sdkMethods: ['referenceProfiles.import'],
1351
+ arguments: [
1352
+ {
1353
+ name: 'file',
1354
+ description: 'Path to an Artisan .alog or importer-supported JSON file',
1355
+ required: true,
1356
+ },
1357
+ ],
1358
+ options: [
1359
+ { flags: '--title <text>' },
1360
+ { flags: '--notes <text>' },
1361
+ { flags: '--idempotency-key <key>', description: 'Stable key for safe retries' },
1362
+ ],
1363
+ 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.',
1366
+ 'Import creates a reference profile, not executed roast history.',
1367
+ ],
1368
+ examples: [
1369
+ 'purvey reference-profile import ~/artisan/ethiopia.alog --title "Ethiopia baseline"',
1370
+ ],
1371
+ },
1372
+ {
1373
+ name: 'compare',
1374
+ summary: 'Compare two immutable roast or reference revisions with measured deltas',
1375
+ auth: 'member',
1376
+ sdkMethods: ['referenceProfiles.compare', 'referenceProfiles.get', 'roasts.chartData'],
1377
+ arguments: [
1378
+ {
1379
+ name: 'left',
1380
+ description: 'selector: revision:<uuid>, profile:<uuid>, roast:<roast-id>, or roast:<roast-id>@<chart-revision>',
1381
+ required: true,
1382
+ },
1383
+ {
1384
+ name: 'right',
1385
+ description: 'selector: revision:<uuid>, profile:<uuid>, roast:<roast-id>, or roast:<roast-id>@<chart-revision>',
1386
+ required: true,
1387
+ },
1388
+ ],
1389
+ options: [
1390
+ { flags: '--unit <F|C>', defaultValue: 'F' },
1391
+ {
1392
+ flags: '--target-points <n>',
1393
+ description: 'Samples per aligned series, 50-1000',
1394
+ defaultValue: 400,
1395
+ minimum: 50,
1396
+ maximum: 1000,
1397
+ },
1398
+ ],
1399
+ notes: [
1400
+ 'Backed by POST /v1/profile-comparisons via @purveyors/sdk; output is the canonical comparison envelope.',
1401
+ '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.',
1403
+ ],
1404
+ examples: [
1405
+ 'purvey reference-profile compare roast:123 profile:5ea1af6f-234c-43a9-9bf8-5678dd24f854 --pretty',
1406
+ 'purvey reference-profile compare roast:123 roast:124 --unit C --json',
1407
+ ],
1408
+ },
1409
+ {
1410
+ name: 'preview',
1411
+ summary: 'Preview bounded temperature adjustments without saving',
1412
+ auth: 'member',
1413
+ sdkMethods: ['referenceProfiles.preview'],
1414
+ arguments: [
1415
+ {
1416
+ name: 'reference_profile_id',
1417
+ cliToken: 'profile-id',
1418
+ description: 'owner-scoped reference profile UUID',
1419
+ required: true,
1420
+ idType: 'reference_profile_id',
1421
+ },
1422
+ {
1423
+ name: 'reference_revision_id',
1424
+ cliToken: 'revision-id',
1425
+ description: 'immutable parent revision UUID',
1426
+ required: true,
1427
+ idType: 'reference_revision_id',
1428
+ },
1429
+ ],
1430
+ options: [{ flags: '--request <file>', requiredInFlagMode: true }],
1431
+ notes: [
1432
+ 'Backed by POST /v1/reference-profiles/{id}/revisions/{revisionId}/preview via @purveyors/sdk.',
1433
+ '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
+ '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.',
1436
+ ],
1437
+ examples: [
1438
+ 'purvey reference-profile preview 5ea1af6f-234c-43a9-9bf8-5678dd24f854 8d2c41e0-7b9a-4f3e-a6d1-2c9e5f07b3a4 --request changes.json --pretty',
1439
+ ],
1440
+ },
1441
+ {
1442
+ name: 'save',
1443
+ summary: 'Save changes as a new immutable generated reference plan',
1444
+ auth: 'member',
1445
+ sdkMethods: ['referenceProfiles.generate'],
1446
+ confirmedActionEquivalents: ['create_generated_reference'],
1447
+ arguments: [
1448
+ {
1449
+ name: 'reference_profile_id',
1450
+ cliToken: 'profile-id',
1451
+ description: 'owner-scoped reference profile UUID',
1452
+ required: true,
1453
+ idType: 'reference_profile_id',
1454
+ },
1455
+ {
1456
+ name: 'reference_revision_id',
1457
+ cliToken: 'revision-id',
1458
+ description: 'immutable parent revision UUID',
1459
+ required: true,
1460
+ idType: 'reference_revision_id',
1461
+ },
1462
+ ],
1463
+ options: [
1464
+ { flags: '--request <file>', requiredInFlagMode: true },
1465
+ { flags: '--idempotency-key <key>', description: 'Stable key for safe retries' },
1466
+ ],
1467
+ 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.',
1470
+ 'Generated references are plans and never create executed roast history.',
1471
+ ],
1472
+ examples: [
1473
+ 'purvey reference-profile save 5ea1af6f-234c-43a9-9bf8-5678dd24f854 8d2c41e0-7b9a-4f3e-a6d1-2c9e5f07b3a4 --request changes.json --pretty',
1474
+ ],
1475
+ },
1476
+ {
1477
+ name: 'export',
1478
+ summary: 'Download a saved generated reference as an unsigned Artisan .alog plan',
1479
+ auth: 'member',
1480
+ sdkMethods: ['referenceProfiles.exportGenerated'],
1481
+ arguments: [
1482
+ {
1483
+ name: 'reference_profile_id',
1484
+ cliToken: 'profile-id',
1485
+ description: 'saved generated reference profile UUID',
1486
+ required: true,
1487
+ idType: 'reference_profile_id',
1488
+ },
1489
+ {
1490
+ name: 'reference_revision_id',
1491
+ cliToken: 'revision-id',
1492
+ description: 'saved generated revision UUID',
1493
+ required: true,
1494
+ idType: 'reference_revision_id',
1495
+ },
1496
+ ],
1497
+ options: [
1498
+ { flags: '--output <file>', requiredInFlagMode: true },
1499
+ { flags: '--force', description: 'Overwrite the destination file if it exists' },
1500
+ ],
1501
+ notes: [
1502
+ 'Backed by GET /v1/reference-profiles/{id}/revisions/{revisionId}/export via @purveyors/sdk.',
1503
+ 'Only a saved generated revision can be exported; output is a local file plus a JSON receipt.',
1504
+ 'The CLI does not claim verified Artisan 4.2 playback compatibility.',
1505
+ ],
1506
+ examples: [
1507
+ 'purvey reference-profile export 0b7e9f52-3c61-4d8a-9e24-6f1a8c3d5b90 c43a1d7e-95b2-4e06-8f7c-1d2b3a4e5f68 --output ~/artisan/next-batch.alog',
1508
+ ],
1509
+ },
1510
+ ],
1511
+ },
1042
1512
  ];
1043
1513
  const workflows = [
1044
1514
  {
@@ -1063,6 +1533,16 @@ const workflows = [
1063
1533
  'purvey roast import ~/artisan/ethiopia.alog --coffee-id 7 --pretty',
1064
1534
  ],
1065
1535
  },
1536
+ {
1537
+ title: 'Plan and export an Artisan reference',
1538
+ commands: [
1539
+ 'purvey reference-profile import ~/artisan/ethiopia.alog --title "Ethiopia baseline"',
1540
+ 'purvey reference-profile get 5ea1af6f-234c-43a9-9bf8-5678dd24f854 --pretty',
1541
+ 'purvey reference-profile preview 5ea1af6f-234c-43a9-9bf8-5678dd24f854 8d2c41e0-7b9a-4f3e-a6d1-2c9e5f07b3a4 --request changes.json --pretty',
1542
+ 'purvey reference-profile save 5ea1af6f-234c-43a9-9bf8-5678dd24f854 8d2c41e0-7b9a-4f3e-a6d1-2c9e5f07b3a4 --request changes.json --idempotency-key 3f6c2a1b-8e4d-4b7a-9c05-7e1f2d3a4b5c',
1543
+ 'purvey reference-profile export 0b7e9f52-3c61-4d8a-9e24-6f1a8c3d5b90 c43a1d7e-95b2-4e06-8f7c-1d2b3a4e5f68 --output ~/artisan/next-batch.alog',
1544
+ ],
1545
+ },
1066
1546
  {
1067
1547
  title: 'Watch a folder for new roasts',
1068
1548
  commands: [
@@ -1092,7 +1572,7 @@ const errorPatterns = [
1092
1572
  title: 'Wrong ID type',
1093
1573
  exitCodes: [EXIT_CODES.INVALID_ARGUMENT, EXIT_CODES.NOT_FOUND],
1094
1574
  guidance: [
1095
- 'Verify whether the command wants catalog_id, inventory_id, roast_id, or sale_id.',
1575
+ 'Verify whether the command wants catalog_id, inventory_id, roast_id, sale_id, reference_profile_id, or reference_revision_id.',
1096
1576
  'See the ID MAP section.',
1097
1577
  ],
1098
1578
  },
@@ -1272,7 +1752,7 @@ function renderIdMap(ids) {
1272
1752
  for (const id of ids) {
1273
1753
  lines.push(`${id.name.padEnd(14, ' ')} ${id.source}; used by: ${id.usedBy.join(', ')}`);
1274
1754
  }
1275
- lines.push('', 'Common ID mistake: tasting get takes catalog_id; tasting rate takes inventory_id.');
1755
+ lines.push('', 'Common ID mistakes: tasting get takes catalog_id; tasting rate takes inventory_id; reference-profile commands use profile and revision UUIDs, not roast IDs.');
1276
1756
  return lines;
1277
1757
  }
1278
1758
  function renderOptions(options = [], indent = ' ') {
@@ -1298,10 +1778,10 @@ function renderCommandGroups(groups) {
1298
1778
  for (const group of groups) {
1299
1779
  if (group.command) {
1300
1780
  lines.push(...renderCommandEntry(group.command, '', group.name));
1301
- lines.push('');
1302
- continue;
1303
1781
  }
1304
- lines.push(group.name);
1782
+ else {
1783
+ lines.push(group.name);
1784
+ }
1305
1785
  for (const subcommand of group.subcommands ?? []) {
1306
1786
  lines.push(...renderCommandEntry(subcommand));
1307
1787
  }