@purveyors/cli 0.14.0 → 0.15.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.
package/README.md CHANGED
@@ -2,9 +2,22 @@
2
2
 
3
3
  Coffee intelligence from your terminal.
4
4
 
5
- `purvey` is the official CLI for [purveyors.io](https://purveyors.io). It gives coffee professionals and AI agents direct access to the Purveyors platform from the terminal: catalog search, inventory tracking, roast logging, sales records, tasting notes, and Artisan `.alog` import.
5
+ `purvey` is the official CLI for [purveyors.io](https://purveyors.io). It gives coffee professionals, developers, and AI agents direct access to the Purveyors platform from the terminal: catalog search, inventory tracking, roast logging, sales records, tasting notes, and Artisan `.alog` import.
6
6
 
7
- Run `purvey manifest` for the machine-readable contract, `purvey context` for the dense human-readable reference, or import `@purveyors/cli/manifest` in-process.
7
+ Use `purvey --help` for quick command discovery, `purvey context` for the dense human-readable operator reference, `purvey manifest` for the preferred machine-readable contract, or `@purveyors/cli/manifest` in-process.
8
+
9
+ ## At a glance
10
+
11
+ - Official binary: `purvey`
12
+ - Package: `@purveyors/cli`
13
+ - Runtime: Node.js 20+
14
+ - No pre-existing session required: `auth`, `config`, `context`, `manifest`
15
+ - Viewer role required: `catalog`
16
+ - Member role required: `inventory`, `roast`, `sales`, `tasting`
17
+ - Preferred machine-readable contract: `purvey manifest`
18
+ - Dense human-readable reference: `purvey context`
19
+ - Compatibility JSON alias: `purvey context --json`
20
+ - In-process machine contract: `@purveyors/cli/manifest`
8
21
 
9
22
  ## Installation
10
23
 
@@ -22,6 +35,30 @@ Verify the install:
22
35
  purvey --version
23
36
  ```
24
37
 
38
+ ## Documentation map
39
+
40
+ | Surface | Use it for |
41
+ | ------------------------------------------------ | --------------------------------------------------------- |
42
+ | <https://purveyors.io/docs/cli/overview> | Live CLI docs |
43
+ | <https://purveyors.io/docs/api/overview> | Live API docs |
44
+ | [AGENTS.md](./AGENTS.md) | Canonical contributor and agent guidance |
45
+ | [docs/CLI_STRATEGY.md](./docs/CLI_STRATEGY.md) | Historical architecture and shipped-surface retrospective |
46
+ | <https://github.com/reedwhetstone/purveyors-cli> | Repository, issues, and source |
47
+ | <https://www.npmjs.com/package/@purveyors/cli> | Package installation and release metadata |
48
+
49
+ Use the live docs on purveyors.io as the primary external reference. Use this README and `AGENTS.md` for repo-specific contributor detail.
50
+
51
+ ## Source-of-truth hierarchy
52
+
53
+ Use this hierarchy when references disagree:
54
+
55
+ 1. `src/program.ts`, `src/commands/*`, and `src/lib/manifest.ts` define the shipped command surface, help text, auth requirements, output modes, ID guidance, and manifest payload.
56
+ 2. `package.json` defines the package version, Node engine, binary entrypoint, scripts, and exported subpaths.
57
+ 3. `README.md`, `AGENTS.md`, and `docs/CLI_STRATEGY.md` explain the repo-specific contract for users, contributors, and agents.
58
+ 4. `https://purveyors.io/docs/cli/overview` and `https://purveyors.io/docs/api/overview` are the primary live product docs for external readers.
59
+
60
+ The CLI is an agent-first product surface. Treat the binary, exported functions, `purvey manifest`, `purvey context`, stdout/stderr behavior, and role-gated command boundaries as one contract.
61
+
25
62
  ## Quick Start
26
63
 
27
64
  ```bash
@@ -34,7 +71,7 @@ purvey auth login
34
71
  # 2. Confirm the session and role
35
72
  purvey auth status
36
73
 
37
- # 3. Search the catalog (requires viewer role, granted on sign-in)
74
+ # 3. Search the catalog (requires viewer role)
38
75
  purvey catalog search --origin "Ethiopia" --stocked --pretty
39
76
 
40
77
  # 4. Check inventory (requires member role)
@@ -43,18 +80,56 @@ purvey inventory list --stocked --pretty
43
80
  # 5. Import a roast from Artisan
44
81
  purvey roast import ~/artisan/my-roast.alog --coffee-id 7 --pretty
45
82
 
46
- # 6. (Agents/scripts) Get the machine-readable CLI contract in one command
83
+ # 6. Get the machine-readable CLI contract
47
84
  purvey manifest
48
85
 
49
- # 7. (Humans/agents) Get the dense readable CLI reference
86
+ # 7. Get the dense readable CLI reference
50
87
  purvey context
51
88
  ```
52
89
 
53
- ## Authentication
90
+ ## Reference surfaces
91
+
92
+ Use the right reference surface for the job:
93
+
94
+ - `purvey manifest` is the preferred stable machine-readable contract for agents, scripts, generated tooling, and parity checks.
95
+ - `purvey context` is the dense human-readable operator reference for reviewers and interactive use.
96
+ - `purvey context --json` and `purvey context --pretty` emit the same JSON payload as `purvey manifest`, but exist mainly for compatibility with tooling that already shells out to `context`.
97
+ - `@purveyors/cli/manifest` exposes the same contract in-process for Node.js and agent runtimes.
98
+
99
+ ## Package exports and shared product surface
100
+
101
+ The npm package is both a binary and a shared TypeScript product surface. `coffee-app` and agent runtimes import CLI business functions directly, so exported subpaths are part of the supported machine contract.
102
+
103
+ | Import path | Use it for |
104
+ | -------------------------- | ---------------------------------------------- |
105
+ | `@purveyors/cli` | CLI entrypoint package root |
106
+ | `@purveyors/cli/catalog` | Catalog search, lookup, stats, similar coffees |
107
+ | `@purveyors/cli/inventory` | Green coffee inventory operations |
108
+ | `@purveyors/cli/roast` | Roast profile operations |
109
+ | `@purveyors/cli/sales` | Sales record operations |
110
+ | `@purveyors/cli/tasting` | Tasting note and rating operations |
111
+ | `@purveyors/cli/lib` | Shared library helpers |
112
+ | `@purveyors/cli/manifest` | Stable machine-readable CLI manifest |
113
+ | `@purveyors/cli/artisan` | Artisan `.alog` parsing and import utilities |
114
+ | `@purveyors/cli/ai` | AI helper surface used by CLI workflows |
115
+
116
+ Shell integrations should usually start with `purvey manifest`. In-process agent and website integrations should import the smallest relevant subpath instead of shelling out when they are already running in Node.js.
117
+
118
+ Export discipline:
119
+
120
+ - Add or remove subpaths only when the package contract intentionally changes.
121
+ - Keep `package.json`, `README.md`, `AGENTS.md`, `docs/CLI_STRATEGY.md`, `src/lib/manifest.ts`, and dist parity checks aligned in the same PR.
122
+ - Prefer the narrowest import path for application and agent code. For example, use `@purveyors/cli/catalog` for catalog operations instead of importing the package root.
123
+ - Treat export-shape changes as product changes because coffee-app and agent runtimes import these functions directly.
124
+
125
+ ## Authentication and access model
54
126
 
55
127
  No pre-existing session is required for `auth`, `config`, `context`, or `manifest`.
56
128
 
57
- **All remote data commands require a valid authenticated session.** `catalog`, `inventory`, `roast`, `sales`, and `tasting` all require auth.
129
+ All remote data commands require a valid authenticated session:
130
+
131
+ - `catalog` requires the `viewer` role
132
+ - `inventory`, `roast`, `sales`, and `tasting` require the `member` role
58
133
 
59
134
  `purvey` uses Google OAuth through purveyors.io.
60
135
 
@@ -91,23 +166,20 @@ Credentials are stored at `~/.config/purvey/credentials.json`.
91
166
 
92
167
  ### Auth roles
93
168
 
94
- Two roles gate different command groups:
95
-
96
- | Role | Commands |
169
+ | Role | Access |
97
170
  | -------- | ------------------------------------------------------------------- |
98
171
  | `viewer` | `catalog search`, `catalog get`, `catalog stats`, `catalog similar` |
99
172
  | `member` | All viewer commands, plus `inventory`, `roast`, `sales`, `tasting` |
100
173
 
101
- Both roles are granted when you sign in through purveyors.io. The `viewer` role is the minimum required for catalog access; the `member` role is required for any personal data or write operations.
174
+ `auth`, `config`, `context`, and `manifest` remain available without a pre-existing session.
102
175
 
103
- `auth`, `config`, `context`, and `manifest` work without a pre-existing session. `config list/get/set/reset` are local-only commands, `context` is a local reference command, and `manifest` emits the machine-readable CLI contract.
176
+ Commands that require a higher role exit with code `3` on auth failure. That includes not being signed in, an expired session, or an insufficient role.
104
177
 
105
- Commands that require a higher role will exit with code `3` (auth error) if you are not signed in or your role is insufficient. Run `purvey auth status` to confirm your current role before scripting.
178
+ ## Output contract and scripting
106
179
 
107
- ## Output and Scripting
180
+ Most commands write compact JSON to stdout by default. Use `--json` if you want to request that mode explicitly.
108
181
 
109
- Most commands write compact JSON to stdout by default.
110
- Use `--json` if you want to request that mode explicitly.
182
+ Machine-contract rule of thumb: stdout is for successful payloads, stderr is for status or errors, and exit codes communicate the failure class. That rule is more important than making terminal output look conversational.
111
183
 
112
184
  Pretty JSON:
113
185
 
@@ -115,10 +187,11 @@ Pretty JSON:
115
187
  purvey inventory list --pretty
116
188
  ```
117
189
 
118
- CSV output for array results:
190
+ CSV output for array results on supporting commands:
119
191
 
120
192
  ```bash
121
193
  purvey inventory list --csv > inventory.csv
194
+ purvey roast list --csv > roasts.csv
122
195
  purvey sales list --csv > sales.csv
123
196
  ```
124
197
 
@@ -130,7 +203,7 @@ purvey roast list --limit 5 | jq '.[].roast_id'
130
203
  purvey auth status 2>/dev/null | jq -r '.email'
131
204
  ```
132
205
 
133
- Operational messages go to stderr, so stdout stays script-friendly.
206
+ Operational messages go to stderr so stdout stays script-friendly.
134
207
 
135
208
  Fatal errors also stay on stderr, but the payload format depends on mode:
136
209
 
@@ -138,7 +211,7 @@ Fatal errors also stay on stderr, but the payload format depends on mode:
138
211
  - `--json`, `--pretty`, or `--csv`: JSON error envelope on stderr
139
212
  - piped or redirected with no explicit flag: compact JSON error envelope on stderr
140
213
 
141
- That same contract applies to parser-level mistakes too, including unknown options, unknown commands, and missing required arguments.
214
+ That contract also applies to parser-level mistakes such as unknown options, unknown commands, and missing required arguments.
142
215
 
143
216
  The JSON error envelope includes:
144
217
 
@@ -148,24 +221,25 @@ The JSON error envelope includes:
148
221
 
149
222
  ### Output caveats worth knowing
150
223
 
151
- - `purvey auth status` prints human-readable output in an interactive terminal unless you pass `--json`, `--pretty`, or `--csv`. When piped or redirected, it emits JSON on stdout even when unauthenticated.
224
+ - `purvey auth status` prints human-readable output in an interactive terminal unless you pass `--json`, `--pretty`, or `--csv`. When piped or redirected, it emits structured JSON on stdout even when unauthenticated.
152
225
  - `purvey config list/get/set/reset` stay human-readable in an interactive terminal, but emit JSON on stdout when you pass `--json` or `--pretty`, or when stdout is non-interactive. `--csv` is not supported for config commands.
153
- - `--json` is an explicit alias for the default compact JSON mode, and it forces JSON even in an interactive terminal.
226
+ - `purvey context` defaults to dense human-readable reference text. `--json` and `--pretty` make it emit the same JSON manifest as `purvey manifest`.
227
+ - `purvey manifest` always emits the machine-readable contract on stdout. `--pretty` only changes formatting.
154
228
  - `--csv` affects successful stdout output only; fatal errors still use JSON on stderr.
155
229
 
156
- ## Exit Codes
230
+ ## Exit codes
157
231
 
158
232
  All `purvey` commands exit with a numeric code your scripts can check with `$?`.
159
233
 
160
- | Code | Meaning |
161
- | ---- | ----------------------------------------------------------------------------------------- |
162
- | `0` | Success |
163
- | `1` | Unexpected or unclassified error |
164
- | `2` | Invalid argument or bad input |
165
- | `3` | Auth error: not logged in, expired session, or insufficient role |
166
- | `4` | Not found |
167
- | `5` | Dependency conflict (for example, `inventory delete` without `--force` when roasts exist) |
168
- | `6` | Local config error |
234
+ | Code | Meaning |
235
+ | ---- | ---------------------------------------------------------------- |
236
+ | `0` | Success |
237
+ | `1` | Unexpected or unclassified error |
238
+ | `2` | Invalid argument or bad input |
239
+ | `3` | Auth error: not logged in, expired session, or insufficient role |
240
+ | `4` | Not found |
241
+ | `5` | Dependency conflict |
242
+ | `6` | Local config error |
169
243
 
170
244
  Scripting pattern:
171
245
 
@@ -176,15 +250,35 @@ if [ $? -eq 3 ]; then
176
250
  fi
177
251
  ```
178
252
 
179
- ## Command Reference
253
+ ## Command reference
180
254
 
181
255
  ### auth
182
256
 
183
257
  - `purvey auth login`
184
258
  - `purvey auth login --headless`
185
259
  - `purvey auth status`
260
+ - `purvey auth status --json`
261
+ - `purvey auth status --pretty`
262
+ - `purvey auth status --csv`
186
263
  - `purvey auth logout`
187
264
 
265
+ Examples:
266
+
267
+ ```bash
268
+ purvey auth login
269
+ purvey auth login --headless
270
+ purvey auth status --pretty
271
+ purvey auth status --csv
272
+ purvey auth logout
273
+ ```
274
+
275
+ Notes:
276
+
277
+ - `auth login` uses browser-based Google OAuth.
278
+ - `auth login --headless` prints an OAuth URL and accepts a pasted callback URL.
279
+ - `auth status --json` is the safest mode for scripts.
280
+ - `auth status --csv` is supported for spreadsheet-style checks, but JSON remains the better integration format.
281
+
188
282
  ### catalog
189
283
 
190
284
  - `purvey catalog search`
@@ -194,27 +288,27 @@ fi
194
288
 
195
289
  `catalog search` filters:
196
290
 
197
- - `--origin <text>` -- filter by origin (country, continent, or region)
198
- - `--process <method>` -- filter by processing method (e.g. natural, washed)
199
- - `--price-min <n>` -- minimum price per lb (USD)
200
- - `--price-max <n>` -- maximum price per lb (USD)
201
- - `--flavor <keywords>` -- flavor keywords, comma-separated
202
- - `--name <text>` -- filter by coffee name (partial match, case-insensitive)
203
- - `--supplier <name>` -- filter by supplier/source name (partial match, case-insensitive)
204
- - `--ids <n,n,...>` -- fetch specific catalog IDs (comma-separated, ignores limit)
205
- - `--stocked` -- only show currently stocked coffees
206
- - `--variety <text>` -- filter by coffee variety/cultivar (partial match)
207
- - `--drying-method <text>` -- filter by drying method (partial match)
208
- - `--stocked-days <n>` -- only show coffees stocked within N days
209
- - `--sort <price|price-desc|name|origin|newest>` -- sort results
210
- - `--offset <n>` -- skip N results for pagination
211
- - `--limit <n>` -- maximum results to return (default: 10)
291
+ - `--origin <text>`; origin, country, continent, or region
292
+ - `--process <method>`; processing method
293
+ - `--price-min <n>`; minimum USD/lb
294
+ - `--price-max <n>`; maximum USD/lb
295
+ - `--flavor <keywords>`; comma-separated flavor terms
296
+ - `--name <text>`; partial coffee name match
297
+ - `--supplier <name>`; partial supplier match
298
+ - `--ids <n,n,...>`; fetch specific catalog IDs, ignores limit and offset
299
+ - `--variety <text>`; partial cultivar match
300
+ - `--drying-method <text>`; partial drying method match
301
+ - `--stocked-days <n>`; stocked within N days
302
+ - `--stocked`; only currently stocked coffees
303
+ - `--sort <price|price-desc|name|origin|newest>`
304
+ - `--offset <n>`; pagination offset
305
+ - `--limit <n>`; default `10`
212
306
 
213
307
  `catalog similar <id>` options:
214
308
 
215
- - `--threshold <0-1>` -- minimum similarity score (default: 0.70)
216
- - `--limit <n>` -- max results (default: 10)
217
- - `--stocked-only` -- only show currently stocked beans
309
+ - `--threshold <0-1>`; default `0.70`
310
+ - `--limit <n>`; default `10`
311
+ - `--stocked-only`
218
312
 
219
313
  Examples:
220
314
 
@@ -224,57 +318,73 @@ purvey catalog search --supplier "Royal Coffee" --stocked --pretty
224
318
  purvey catalog search --ids "1182,1183,1200"
225
319
  purvey catalog search --stocked --sort price --offset 10 --limit 10
226
320
  purvey catalog similar 1182 --threshold 0.85 --stocked-only --pretty
227
- purvey catalog similar 1182 --json | jq '.[0]'
321
+ purvey catalog stats --pretty
322
+ purvey catalog get 1182 --pretty
228
323
  ```
229
324
 
325
+ Notes:
326
+
327
+ - Catalog commands require an authenticated `viewer` role.
328
+ - `catalog get` and `catalog similar` both take `coffee_catalog.catalog_id`.
329
+ - `catalog stats` returns aggregate catalog metrics, not your personal inventory metrics.
330
+
230
331
  ### inventory
231
332
 
232
333
  - `purvey inventory list`
233
334
  - `purvey inventory get <id>`
234
335
  - `purvey inventory add`
235
336
  - `purvey inventory update <id>`
236
- - `purvey inventory delete <id>` (use `--force` to cascade delete dependent roasts and sales)
337
+ - `purvey inventory delete <id>`
237
338
 
238
- `inventory list` options:
339
+ `inventory list` filters:
239
340
 
240
- - `--stocked` -- only show currently stocked beans
241
- - `--catalog-id <id>` -- filter by catalog ID
242
- - `--purchase-date-start <YYYY-MM-DD>` -- only show purchases on or after this date
243
- - `--purchase-date-end <YYYY-MM-DD>` -- only show purchases on or before this date
244
- - `--origin <country>` -- filter by country of origin (partial match)
245
- - `--limit <n>` -- maximum results (default: 20)
246
- - `--offset <n>` -- skip N results for pagination (default: 0)
341
+ - `--stocked`
342
+ - `--catalog-id <id>`
343
+ - `--purchase-date-start <YYYY-MM-DD>`
344
+ - `--purchase-date-end <YYYY-MM-DD>`
345
+ - `--origin <country>`
346
+ - `--limit <n>`; default `20`
347
+ - `--offset <n>`; default `0`
247
348
 
248
349
  `inventory add` flags:
249
350
 
250
- - `--catalog-id <id>` -- [REQUIRED] coffee_catalog.catalog_id
251
- - `--qty <lbs>` -- [REQUIRED] quantity in pounds
252
- - `--cost <dollars>` -- bean cost in dollars
253
- - `--tax-ship <dollars>` -- tax and shipping cost in dollars
254
- - `--notes <text>` -- notes for this inventory item
255
- - `--purchase-date <YYYY-MM-DD>` -- purchase date (defaults to today)
256
- - `--form` -- interactive form mode
351
+ - `--catalog-id <id>`; required in flag mode
352
+ - `--qty <lbs>`; required in flag mode
353
+ - `--cost <dollars>`
354
+ - `--tax-ship <dollars>`
355
+ - `--notes <text>`
356
+ - `--purchase-date <YYYY-MM-DD>`
357
+ - `--form`
257
358
 
258
359
  `inventory update <id>` flags:
259
360
 
260
- - `--qty <lbs>` -- updated quantity in pounds
261
- - `--cost <dollars>` -- updated bean cost
262
- - `--tax-ship <dollars>` -- updated tax/shipping cost
263
- - `--notes <text>` -- updated notes
264
- - `--stocked <true|false>` -- mark as stocked or not
361
+ - `--qty <lbs>`
362
+ - `--cost <dollars>`
363
+ - `--tax-ship <dollars>`
364
+ - `--notes <text>`
365
+ - `--stocked <true|false>`
366
+
367
+ `inventory delete <id>` options:
368
+
369
+ - `--force`; cascade dependent roasts and sales
370
+ - `--yes`; skip confirmation prompt
265
371
 
266
372
  Examples:
267
373
 
268
374
  ```bash
269
375
  purvey inventory list --stocked --pretty
270
- purvey inventory list --limit 20 --offset 20 # page 2
271
- purvey inventory list --csv > inventory.csv
272
376
  purvey inventory add --catalog-id 128 --qty 10 --cost 8.50
273
- purvey inventory add --catalog-id 42 --qty 5 --cost 6.25 --tax-ship 4.00
377
+ purvey inventory add --form
274
378
  purvey inventory update 7 --stocked false
275
379
  purvey inventory delete 7 --yes
276
380
  ```
277
381
 
382
+ Notes:
383
+
384
+ - Inventory commands require an authenticated `member` role.
385
+ - Inventory `id` is `green_coffee_inv.id`, not `catalog_id`.
386
+ - `inventory delete` may require `--force` if dependent roasts or sales exist.
387
+
278
388
  ### roast
279
389
 
280
390
  - `purvey roast list`
@@ -287,69 +397,78 @@ purvey inventory delete 7 --yes
287
397
 
288
398
  `roast list` filters:
289
399
 
290
- - `--coffee-id <id>` -- filter by inventory item ID (green_coffee_inv.id)
291
- - `--roast-id <id>` -- filter by exact roast profile ID
292
- - `--batch-name <text>` -- filter by batch name (partial match, case-insensitive)
293
- - `--coffee-name <text>` -- filter by bean name (partial match, case-insensitive)
294
- - `--date-start <YYYY-MM-DD>` -- only show roasts on or after this date
295
- - `--date-end <YYYY-MM-DD>` -- only show roasts on or before this date
296
- - `--stocked` -- only show roasts for currently stocked beans
297
- - `--catalog-id <id>` -- filter by coffee_catalog ID
298
- - `--limit <n>` -- maximum results (default: 20)
299
- - `--offset <n>` -- skip N results for pagination (default: 0)
400
+ - `--coffee-id <id>`; inventory item ID
401
+ - `--roast-id <id>`; exact roast profile ID
402
+ - `--batch-name <text>`
403
+ - `--coffee-name <text>`
404
+ - `--date-start <YYYY-MM-DD>`
405
+ - `--date-end <YYYY-MM-DD>`
406
+ - `--stocked`
407
+ - `--catalog-id <id>`
408
+ - `--limit <n>`; default `20`
409
+ - `--offset <n>`; default `0`
300
410
 
301
411
  `roast get <id>` options:
302
412
 
303
- - `--include-temps` -- include temperature curve data
304
- - `--include-events` -- include roast event markers
413
+ - `--include-temps`
414
+ - `--include-events`
305
415
 
306
416
  `roast create` flags:
307
417
 
308
- - `--coffee-id <id>` -- [REQUIRED] green_coffee_inv ID
309
- - `--batch-name <name>` -- batch name (defaults to coffee name + today's date)
310
- - `--oz-in <oz>` -- green weight in ounces
311
- - `--oz-out <oz>` -- roasted weight in ounces
312
- - `--roast-date <YYYY-MM-DD>` -- roast date (defaults to today)
313
- - `--notes <text>` -- roast notes
314
- - `--form` -- interactive form mode
418
+ - `--coffee-id <id>`; required in flag mode
419
+ - `--batch-name <name>`
420
+ - `--oz-in <oz>`
421
+ - `--oz-out <oz>`
422
+ - `--roast-date <YYYY-MM-DD>`
423
+ - `--notes <text>`
424
+ - `--form`
315
425
 
316
- `roast update <id>` fields:
426
+ `roast update <id>` flags:
317
427
 
318
- - `--notes <text>` -- updated roast notes
319
- - `--oz-out <oz>` -- updated roasted weight (triggers weight loss recalculation)
320
- - `--batch-name <name>` -- updated batch name
321
- - `--targets <text>` -- updated roast targets
428
+ - `--batch-name <name>`
429
+ - `--oz-out <oz>`
430
+ - `--notes <text>`
431
+ - `--targets <text>`
322
432
 
323
433
  `roast import [file]` flags:
324
434
 
325
- - `--coffee-id <id>` -- [REQUIRED] green_coffee_inv ID
326
- - `--batch-name <name>` -- batch name (auto-generated if omitted)
327
- - `--oz-in <oz>` -- green weight (extracted from .alog if present, overridden here)
328
- - `--roast-notes <text>` -- additional roast notes
329
- - `--form` -- interactive form mode
435
+ - `--coffee-id <id>`; required unless `--form`
436
+ - `--batch-name <name>`
437
+ - `--oz-in <oz>`
438
+ - `--roast-notes <text>`
439
+ - `--roast-targets <text>`
440
+ - `--form`
330
441
 
331
442
  `roast watch [directory]` options:
332
443
 
333
- - `--coffee-id <id>` -- [REQUIRED unless --auto-match] inventory ID for all imports
334
- - `--batch-prefix <name>` -- batch name prefix for auto-named batches
335
- - `--prompt-each` -- prompt for bean selection on each new file
336
- - `--auto-match` -- auto-match beans per file (mutually exclusive with --coffee-id)
337
- - `--resume` -- resume a previous watch session
338
- - `--form` -- interactive setup wizard
444
+ - `--coffee-id <id>`; required unless `--auto-match`
445
+ - `--batch-prefix <name>`
446
+ - `--prompt-each`
447
+ - `--auto-match`
448
+ - `--commit-mode <batch|individual>`
449
+ - `--oz-in <oz>`
450
+ - `--roast-notes <text>`
451
+ - `--roast-targets <text>`
452
+ - `--resume`
453
+ - `--form`
339
454
 
340
455
  Examples:
341
456
 
342
457
  ```bash
343
458
  purvey roast list --catalog-id 128 --pretty
344
- purvey roast list --batch-name "Ethiopia Guji" --pretty
345
- purvey roast list --date-start 2026-03-01 --date-end 2026-03-31
346
- purvey roast list --limit 20 --offset 20 # page 2
459
+ purvey roast get 123 --include-temps --pretty
347
460
  purvey roast create --coffee-id 7 --batch-name "Ethiopia Guji Light" --oz-in 16
348
- purvey roast update 123 --targets "Aim for FC at 390F, 18% dev"
349
- purvey roast import ~/artisan/ethiopia.alog --coffee-id 7
461
+ purvey roast import ~/artisan/ethiopia.alog --coffee-id 7 --roast-targets "Aim for 18% development"
350
462
  purvey roast watch ~/artisan/ --auto-match
351
463
  ```
352
464
 
465
+ Notes:
466
+
467
+ - Roast commands require an authenticated `member` role.
468
+ - `--coffee-id` uses inventory IDs.
469
+ - `roast watch --auto-match` is mutually exclusive with `--coffee-id`.
470
+ - `roast watch --commit-mode` defaults to `batch`.
471
+
353
472
  ### sales
354
473
 
355
474
  - `purvey sales list`
@@ -359,123 +478,150 @@ purvey roast watch ~/artisan/ --auto-match
359
478
 
360
479
  `sales list` filters:
361
480
 
362
- - `--roast-id <id>` -- filter by roast profile ID
363
- - `--date-start <YYYY-MM-DD>` -- only show sales on or after this date
364
- - `--date-end <YYYY-MM-DD>` -- only show sales on or before this date
365
- - `--buyer <name>` -- filter by buyer name (partial match, case-insensitive)
366
- - `--limit <n>` -- maximum results (default: 20)
367
- - `--offset <n>` -- skip N results for pagination (default: 0)
481
+ - `--roast-id <id>`
482
+ - `--date-start <YYYY-MM-DD>`
483
+ - `--date-end <YYYY-MM-DD>`
484
+ - `--buyer <name>`
485
+ - `--limit <n>`; default `20`
486
+ - `--offset <n>`; default `0`
368
487
 
369
488
  `sales record` flags:
370
489
 
371
- - `--roast-id <id>` -- [REQUIRED] roast_data.roast_id
372
- - `--oz <amount>` -- [REQUIRED] ounces sold
373
- - `--price <dollars>` -- [REQUIRED] total sale price in dollars
374
- - `--buyer <name>` -- buyer name or identifier
375
- - `--sell-date <YYYY-MM-DD>` -- sale date (defaults to today)
376
- - `--form` -- interactive form mode
490
+ - `--roast-id <id>`; exact selector mode
491
+ - `--coffee-id <id>`; resolved selector mode, requires `--batch-name`
492
+ - `--batch-name <name>`; resolved selector mode, requires `--coffee-id`
493
+ - `--oz <amount>`; required in flag mode
494
+ - `--price <dollars>`; required in flag mode
495
+ - `--buyer <name>`
496
+ - `--sell-date <YYYY-MM-DD>`
497
+ - `--form`
377
498
 
378
499
  `sales update <id>` flags:
379
500
 
380
- - `--oz <amount>` -- updated ounces sold
381
- - `--price <dollars>` -- updated sale price
382
- - `--buyer <name>` -- updated buyer name
383
- - `--sell-date <YYYY-MM-DD>` -- updated sale date
501
+ - `--oz <amount>`
502
+ - `--price <dollars>`
503
+ - `--buyer <name>`
504
+ - `--sell-date <YYYY-MM-DD>`
505
+
506
+ `sales delete <id>` options:
507
+
508
+ - `--yes`
384
509
 
385
510
  Examples:
386
511
 
387
512
  ```bash
388
513
  purvey sales record --roast-id 123 --oz 12 --price 22.00 --buyer "Jane Smith"
514
+ purvey sales record --coffee-id 7 --batch-name "Ethiopia Guji Light" --oz 8 --price 16.00
389
515
  purvey sales list --pretty
390
- purvey sales list --limit 20 --offset 20 # page 2
391
516
  purvey sales update 5 --price 24.00
392
517
  purvey sales delete 5 --yes
393
518
  ```
394
519
 
520
+ Notes:
521
+
522
+ - Sales commands require an authenticated `member` role.
523
+ - Use exactly one selector mode for `sales record`: exact `--roast-id`, or resolved `--coffee-id` plus `--batch-name`.
524
+ - If resolved mode matches multiple roasts, rerun with `--roast-id`.
525
+ - `--price` is total sale price, not per-ounce price.
526
+
395
527
  ### tasting
396
528
 
397
- - `purvey tasting get <catalog-id>` -- retrieve tasting notes (uses `coffee_catalog.catalog_id`)
398
- - `purvey tasting rate [inventory-id]` -- record cupping scores (uses `green_coffee_inv.id`)
529
+ - `purvey tasting get <catalog-id>`
530
+ - `purvey tasting rate [inventory-id]`
531
+
532
+ ID distinction:
399
533
 
400
- **ID distinction:** `tasting get` takes a `catalog_id`; `tasting rate` takes an `inventory id`. These are different numbers. Use `purvey catalog search` to find a `catalog_id` and `purvey inventory list` to find your `inventory id`.
534
+ - `tasting get` takes a `catalog_id`
535
+ - `tasting rate` takes an `inventory id`
401
536
 
402
537
  `purvey tasting get <catalog-id>` options:
403
538
 
404
- - `--filter <user|supplier|both>` -- which notes to show (default: both)
539
+ - `--filter <user|supplier|both>`; default `both`
405
540
 
406
541
  `purvey tasting rate [inventory-id]` options:
407
542
 
408
- - `--aroma <1-5>` -- [REQUIRED in flag mode]
409
- - `--body <1-5>` -- [REQUIRED in flag mode]
410
- - `--acidity <1-5>` -- [REQUIRED in flag mode]
411
- - `--sweetness <1-5>` -- [REQUIRED in flag mode]
412
- - `--aftertaste <1-5>` -- [REQUIRED in flag mode]
413
- - `--brew-method <method>` -- brew method used (e.g. pour_over, espresso)
414
- - `--notes <text>` -- additional tasting notes
415
- - `--form` -- interactive form mode
543
+ - `--aroma <1-5>`; required in flag mode
544
+ - `--body <1-5>`; required in flag mode
545
+ - `--acidity <1-5>`; required in flag mode
546
+ - `--sweetness <1-5>`; required in flag mode
547
+ - `--aftertaste <1-5>`; required in flag mode
548
+ - `--brew-method <method>`
549
+ - `--notes <text>`
550
+ - `--form`
416
551
 
417
552
  Examples:
418
553
 
419
554
  ```bash
420
- # catalog_id 128 = the bean in the catalog
421
555
  purvey tasting get 128 --filter both --pretty
422
- # inventory id 7 = your physical purchase from inventory
423
556
  purvey tasting rate 7 --aroma 4 --body 3 --acidity 5 --sweetness 4 --aftertaste 4
424
- purvey tasting rate 42 --aroma 3 --body 3 --acidity 3 --sweetness 3 --aftertaste 3 --notes "Underextracted"
557
+ purvey tasting rate --form
425
558
  ```
426
559
 
427
- ### config
560
+ Notes:
428
561
 
429
- - `purvey config list`
430
- - `purvey config get <key>`
431
- - `purvey config set <key> <value>`
432
- - `purvey config reset`
562
+ - Tasting commands require an authenticated `member` role.
563
+ - `tasting get` combines supplier notes with your own notes when available.
564
+ - `tasting rate` writes scores back to your inventory row.
433
565
 
434
- Current config key:
566
+ ### config
435
567
 
436
- - `form-mode`: when set to `true`, write commands enter interactive mode when required args are missing
568
+ - `purvey config list` (supports `--json`, `--pretty`)
569
+ - `purvey config get <key>` (supports `--json`, `--pretty`)
570
+ - `purvey config set <key> <value>` (supports `--json`, `--pretty`)
571
+ - `purvey config reset` (supports `--json`, `--pretty`)
437
572
 
438
- Output behavior:
573
+ Current config key:
439
574
 
440
- - Interactive terminals keep config commands human-readable by default
441
- - `--json` or `--pretty`, or any non-interactive stdout, emits JSON on stdout
442
- - `--csv` is not supported for config commands
575
+ - `form-mode`: when `true`, write commands enter interactive mode when required args are missing
443
576
 
444
577
  Examples:
445
578
 
446
579
  ```bash
447
580
  purvey config set form-mode true
448
- purvey config get form-mode
449
581
  purvey config get form-mode --json
450
582
  purvey config reset --json
451
583
  ```
452
584
 
585
+ Notes:
586
+
587
+ - Config commands are local-only.
588
+ - Config file path: `~/.config/purvey/config.json`
589
+ - `--csv` is not supported.
590
+
453
591
  ### context
454
592
 
455
593
  - `purvey context`
456
594
  - `purvey context --json`
457
595
  - `purvey context --pretty`
458
596
 
459
- `purvey context` defaults to dense, human-readable, source-aware CLI reference text.
597
+ Notes:
460
598
 
461
- Use `--json` or `--pretty` when you want the same machine-readable manifest that `purvey manifest` emits.
599
+ - Default output is the dense human-readable operator reference text.
600
+ - `--json` and `--pretty` emit the same machine-readable manifest as `purvey manifest`.
601
+ - Prefer `purvey manifest` for new machine integrations. Use `purvey context --json` when you need compatibility with an existing `context`-based workflow.
602
+ - Use `@purveyors/cli/manifest` when you need that same contract in-process from Node.js or an agent runtime.
603
+ - `--csv` is not supported.
462
604
 
463
605
  ### manifest
464
606
 
465
607
  - `purvey manifest`
608
+ - `purvey manifest --json`
466
609
  - `purvey manifest --pretty`
467
610
 
468
- Use this when an agent or script needs the machine-readable contract directly. `purvey manifest` emits compact JSON by default.
469
-
470
- `purvey manifest` and `purvey context --json` emit the same JSON manifest.
611
+ Notes:
471
612
 
472
- If you want indented JSON, add `--pretty` to either command.
613
+ - `purvey manifest` emits the preferred stable machine-readable CLI contract on stdout.
614
+ - `purvey manifest` and `purvey manifest --json` both emit compact JSON.
615
+ - `purvey manifest --pretty` emits indented JSON.
616
+ - `purvey manifest` and `purvey context --json` emit the same JSON payload.
617
+ - Use `purvey manifest` for new automation and treat `purvey context --json` as a compatibility alias.
618
+ - `--csv` is not supported.
473
619
 
474
- ### in-process manifest
620
+ ### In-process manifest export
475
621
 
476
622
  - `@purveyors/cli/manifest`
477
623
 
478
- Use this when you need the same contract from Node.js without shelling out to the CLI. This package subpath is exported via `./manifest` in `package.json`.
624
+ Use this when you need the same stable contract from Node.js without shelling out to the CLI. This package subpath is exported via `./manifest` in `package.json` and is part of the supported public machine surface for agents and scripts.
479
625
 
480
626
  ```ts
481
627
  import { getCliManifest } from '@purveyors/cli/manifest';
@@ -483,17 +629,16 @@ import { getCliManifest } from '@purveyors/cli/manifest';
483
629
  const manifest = getCliManifest();
484
630
  ```
485
631
 
486
- ## Common Workflows
632
+ ## Common workflows
487
633
 
488
- ### Buy coffee, roast it, rate it, and record a sale
634
+ ### Catalog to inventory to roast to sale
489
635
 
490
636
  ```bash
491
637
  purvey catalog search --origin "Ethiopia" --process "natural" --stocked --pretty
492
638
  purvey inventory add --catalog-id 128 --qty 10 --cost 8.50
493
- purvey inventory list --stocked --pretty
494
639
  purvey roast import ~/artisan/guji-light.alog --coffee-id 7 --pretty
495
640
  purvey tasting rate 7 --aroma 5 --body 3 --acidity 5 --sweetness 4 --aftertaste 4
496
- purvey sales record --roast-id 123 --oz 12 --price 22.00 --buyer "Jane Smith"
641
+ purvey sales record --coffee-id 7 --batch-name "Ethiopia Guji Light" --oz 12 --price 22.00 --buyer "Jane Smith"
497
642
  ```
498
643
 
499
644
  ### Continuous Artisan watch mode
@@ -512,53 +657,57 @@ purvey roast list --csv > roasts.csv
512
657
  purvey sales list --csv > sales.csv
513
658
  ```
514
659
 
515
- ## ID Reference
660
+ ### Bootstrap an agent or script
661
+
662
+ ```bash
663
+ purvey manifest
664
+ purvey context
665
+ purvey auth login --headless
666
+ purvey auth status 2>/dev/null | jq .
667
+ ```
668
+
669
+ ## ID reference
516
670
 
517
671
  Use the right ID for the right command.
518
672
 
519
- - `catalog_id`: coffee_catalog rows; used by `catalog get`, `inventory add --catalog-id`, `tasting get`, `roast list --catalog-id`
520
- - `inventory id`: green_coffee_inv rows; used by `inventory get/update/delete`, `roast --coffee-id`, `tasting rate`, `roast list --coffee-id`
521
- - `roast_id`: roast_data rows; used by `roast get/delete`, `sales --roast-id`, `roast list --roast-id`
522
- - `sale id`: coffee_sales rows; used by `sales update/delete`
673
+ - `catalog_id`: `coffee_catalog` rows; used by `catalog get`, `inventory add --catalog-id`, `tasting get`, `roast list --catalog-id`
674
+ - `inventory id`: `green_coffee_inv` rows; used by `inventory get/update/delete`, `roast --coffee-id`, `tasting rate`, `roast list --coffee-id`
675
+ - `roast_id`: `roast_data` rows; used by `roast get/delete`, `sales --roast-id`, `roast list --roast-id`
676
+ - `sales record` also supports resolving a roast from `inventory id` plus `--batch-name`; if that selector is ambiguous, use exact `roast_id`
677
+ - `sale id`: `coffee_sales` rows; used by `sales update/delete`
523
678
 
524
- ## Environment Variables
679
+ ## Environment variables
525
680
 
526
681
  - `PURVEYORS_SUPABASE_URL`: override the Supabase project URL
527
682
  - `PURVEYORS_SUPABASE_ANON_KEY`: override the Supabase anon key
528
683
  - `PURVEYORS_BASE_URL`: override the Purveyors web base URL
529
684
  - `PURVEY_DEBUG`: enable verbose error output
530
685
 
531
- ## For AI Agents
686
+ ## For AI agents
532
687
 
533
- Start here:
688
+ Recommended bootstrap order:
534
689
 
535
690
  ```bash
536
691
  purvey manifest
537
692
  purvey context
538
- ```
539
-
540
- Recommended flow:
541
-
542
- ```bash
543
693
  purvey auth login --headless
544
- purvey auth status 2>/dev/null | jq .
545
- purvey inventory list | jq '.[].id'
546
694
  ```
547
695
 
548
- The CLI is designed to be agent-friendly:
696
+ Use `purvey manifest` as the authoritative machine-readable entry point. Keep `purvey context` for dense operator context, or use `purvey context --json` only when you need compatibility with an existing wrapper.
697
+
698
+ Why this CLI works well for agents:
549
699
 
550
700
  - stable command names
551
- - structured output on stdout
701
+ - structured stdout by default
552
702
  - headless auth flow
553
- - copy-pasteable examples
554
- - a dedicated `manifest` command for machine-readable onboarding
555
- - a dedicated `context` command for dense human-readable onboarding
703
+ - documented exit codes and role boundaries
704
+ - dedicated machine-readable manifest command
705
+ - dedicated dense human-readable reference command
556
706
  - `purvey context --json` parity with `purvey manifest`
557
- - an `@purveyors/cli/manifest` export for in-process access
558
- - documented exit codes for programmatic error handling
559
- - `--offset` + `--limit` pagination on all list commands
707
+ - `@purveyors/cli/manifest` export for in-process access
708
+ - `--offset` and `--limit` pagination across list commands
560
709
 
561
- The scripting contract: stdout is always structured success data (JSON or CSV). stderr carries status/spinner output plus fatal errors. In structured or non-interactive modes, fatal errors are JSON envelopes on stderr; in interactive no-flag mode, they stay human-readable. Exit codes are stable and documented above. Never expect success data on stderr.
710
+ The scripting contract is simple: stdout carries successful payloads, stderr carries status and fatal errors, and exit codes stay stable.
562
711
 
563
712
  ## Troubleshooting
564
713
 
@@ -566,31 +715,29 @@ The scripting contract: stdout is always structured success data (JSON or CSV).
566
715
 
567
716
  ```bash
568
717
  purvey auth login
569
- # or for headless environments:
718
+ # or
570
719
  purvey auth login --headless
571
720
  ```
572
721
 
573
- **Catalog commands fail with auth error after logging in**
722
+ **Catalog commands fail after logging in**
574
723
 
575
- Run `purvey auth status` to confirm you have a `viewer` or `member` role. Both are granted automatically on sign-in. If auth status shows a stale session, run `purvey auth logout` then log in again.
724
+ Run `purvey auth status` to confirm you have a `viewer` or `member` role. If the session is stale, run `purvey auth logout` and log in again.
576
725
 
577
726
  **Wrong ID type passed to a command**
578
727
 
579
- The most common mistake is mixing up ID types. Check the [ID Reference](#id-reference) section above. Use `purvey catalog search` to find `catalog_id` values; use `purvey inventory list` to find `inventory id` values.
728
+ Use the [ID reference](#id-reference) section above. `catalog_id` and inventory IDs are different values.
580
729
 
581
- **Pagination: only seeing the first 20 results**
730
+ **Pagination only shows the first page**
582
731
 
583
- All list commands default to 20 results. Use `--limit` and `--offset` to page through larger sets:
732
+ All list commands default to 20 results. Use `--limit` and `--offset`.
584
733
 
585
734
  ```bash
586
- purvey inventory list --limit 20 --offset 0 # page 1
587
- purvey inventory list --limit 20 --offset 20 # page 2
588
- purvey inventory list --limit 20 --offset 40 # page 3
735
+ purvey inventory list --limit 20 --offset 0
736
+ purvey inventory list --limit 20 --offset 20
737
+ purvey inventory list --limit 20 --offset 40
589
738
  ```
590
739
 
591
- **`inventory delete` fails with dependency conflict (exit code 5)**
592
-
593
- The item has dependent roast profiles or sales records. Use `--force` to cascade delete:
740
+ **`inventory delete` fails with dependency conflict**
594
741
 
595
742
  ```bash
596
743
  purvey inventory delete 7 --force --yes
@@ -609,11 +756,16 @@ git clone https://github.com/reedwhetstone/purveyors-cli
609
756
  cd purveyors-cli
610
757
  pnpm install
611
758
  npm run build
759
+ npm run verify:contract
760
+ npm run verify:dist
761
+ npm run verify:prepublish
612
762
  npm run check
613
763
  npm run lint
614
764
  npm test
615
765
  ```
616
766
 
767
+ `npm run verify:prepublish` is the release guardrail. It rebuilds first, re-runs the targeted manifest and output-contract suites, checks the compiled `dist/` artifact, verifies the `npm pack --dry-run` publish surface, and smoke-tests `package.json`, `README.md`, `purvey manifest`, `purvey context --json`, and `@purveyors/cli/manifest`.
768
+
617
769
  Key files:
618
770
 
619
771
  - `src/index.ts`: executable entrypoint
@@ -622,12 +774,15 @@ Key files:
622
774
  - `src/lib/`: business logic and Supabase integration
623
775
  - `src/commands/context.ts`: dense human-readable agent reference
624
776
  - `src/commands/manifest.ts`: machine-readable CLI manifest command
625
- - `src/lib/manifest.ts`: shared manifest contract and renderer
777
+ - `src/lib/manifest.ts`: shared manifest contract, package export list, command metadata, ID guidance, and context renderer
626
778
  - `package.json`: package metadata and export surface, including `./manifest`
627
- - `AGENTS.md`: contributor guide
779
+ - `tests/dist-contract.test.ts`: compiled artifact parity guardrails
780
+ - `AGENTS.md`: canonical contributor guide
781
+
782
+ Live documentation:
628
783
 
629
- Live documentation: [purveyors.io/docs](https://purveyors.io/docs)
630
- Package: [npmjs.com/package/@purveyors/cli](https://www.npmjs.com/package/@purveyors/cli)
784
+ - <https://purveyors.io/docs/cli/overview>
785
+ - <https://purveyors.io/docs/api/overview>
631
786
 
632
787
  ## License
633
788