@purveyors/cli 0.9.6 → 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.
Files changed (63) hide show
  1. package/README.md +476 -159
  2. package/dist/commands/auth.d.ts.map +1 -1
  3. package/dist/commands/auth.js +4 -3
  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 +55 -31
  7. package/dist/commands/catalog.js.map +1 -1
  8. package/dist/commands/config.d.ts.map +1 -1
  9. package/dist/commands/config.js +61 -12
  10. package/dist/commands/config.js.map +1 -1
  11. package/dist/commands/context.d.ts.map +1 -1
  12. package/dist/commands/context.js +34 -234
  13. package/dist/commands/context.js.map +1 -1
  14. package/dist/commands/manifest.d.ts +3 -0
  15. package/dist/commands/manifest.d.ts.map +1 -0
  16. package/dist/commands/manifest.js +32 -0
  17. package/dist/commands/manifest.js.map +1 -0
  18. package/dist/commands/roast.d.ts.map +1 -1
  19. package/dist/commands/roast.js +105 -2
  20. package/dist/commands/roast.js.map +1 -1
  21. package/dist/commands/sales.d.ts.map +1 -1
  22. package/dist/commands/sales.js +87 -40
  23. package/dist/commands/sales.js.map +1 -1
  24. package/dist/index.js +6 -105
  25. package/dist/index.js.map +1 -1
  26. package/dist/lib/config.d.ts.map +1 -1
  27. package/dist/lib/config.js +8 -2
  28. package/dist/lib/config.js.map +1 -1
  29. package/dist/lib/errors.d.ts +13 -1
  30. package/dist/lib/errors.d.ts.map +1 -1
  31. package/dist/lib/errors.js +153 -10
  32. package/dist/lib/errors.js.map +1 -1
  33. package/dist/lib/index.d.ts +1 -0
  34. package/dist/lib/index.d.ts.map +1 -1
  35. package/dist/lib/index.js +1 -0
  36. package/dist/lib/index.js.map +1 -1
  37. package/dist/lib/interactive/watch.d.ts +37 -3
  38. package/dist/lib/interactive/watch.d.ts.map +1 -1
  39. package/dist/lib/interactive/watch.js +237 -81
  40. package/dist/lib/interactive/watch.js.map +1 -1
  41. package/dist/lib/manifest.d.ts +107 -0
  42. package/dist/lib/manifest.d.ts.map +1 -0
  43. package/dist/lib/manifest.js +977 -0
  44. package/dist/lib/manifest.js.map +1 -0
  45. package/dist/lib/output.d.ts +10 -0
  46. package/dist/lib/output.d.ts.map +1 -1
  47. package/dist/lib/output.js +33 -18
  48. package/dist/lib/output.js.map +1 -1
  49. package/dist/lib/roast.d.ts +2 -0
  50. package/dist/lib/roast.d.ts.map +1 -1
  51. package/dist/lib/roast.js +5 -2
  52. package/dist/lib/roast.js.map +1 -1
  53. package/dist/lib/sales.d.ts +15 -2
  54. package/dist/lib/sales.d.ts.map +1 -1
  55. package/dist/lib/sales.js +77 -15
  56. package/dist/lib/sales.js.map +1 -1
  57. package/dist/program.d.ts +4 -0
  58. package/dist/program.d.ts.map +1 -0
  59. package/dist/program.js +135 -0
  60. package/dist/program.js.map +1 -0
  61. package/dist/types/index.d.ts +4 -0
  62. package/dist/types/index.d.ts.map +1 -1
  63. package/package.json +7 -1
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 context` for the dense agent reference.
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,26 +35,101 @@ 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
28
- # 1. Authenticate
65
+ # 1. Authenticate before using catalog, inventory, roast, sales, or tasting commands
29
66
  purvey auth login
30
67
 
31
- # 2. Confirm the session
68
+ # For agents, CI, or remote machines, use headless flow:
69
+ # purvey auth login --headless
70
+
71
+ # 2. Confirm the session and role
32
72
  purvey auth status
33
73
 
34
- # 3. Search the catalog
74
+ # 3. Search the catalog (requires viewer role)
35
75
  purvey catalog search --origin "Ethiopia" --stocked --pretty
36
76
 
37
- # 4. Check inventory
77
+ # 4. Check inventory (requires member role)
38
78
  purvey inventory list --stocked --pretty
39
79
 
40
80
  # 5. Import a roast from Artisan
41
81
  purvey roast import ~/artisan/my-roast.alog --coffee-id 7 --pretty
82
+
83
+ # 6. Get the machine-readable CLI contract
84
+ purvey manifest
85
+
86
+ # 7. Get the dense readable CLI reference
87
+ purvey context
42
88
  ```
43
89
 
44
- ## 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
126
+
127
+ No pre-existing session is required for `auth`, `config`, `context`, or `manifest`.
128
+
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
45
133
 
46
134
  `purvey` uses Google OAuth through purveyors.io.
47
135
 
@@ -55,6 +143,9 @@ Headless login for agents, CI, and remote machines:
55
143
 
56
144
  ```bash
57
145
  purvey auth login --headless
146
+ # CLI prints a Google OAuth URL
147
+ # Open it in any browser and sign in
148
+ # Paste the full callback URL back into the terminal
58
149
  ```
59
150
 
60
151
  Status:
@@ -75,19 +166,20 @@ Credentials are stored at `~/.config/purvey/credentials.json`.
75
166
 
76
167
  ### Auth roles
77
168
 
78
- `catalog` commands require an authenticated viewer session. Sign in before using:
169
+ | Role | Access |
170
+ | -------- | ------------------------------------------------------------------- |
171
+ | `viewer` | `catalog search`, `catalog get`, `catalog stats`, `catalog similar` |
172
+ | `member` | All viewer commands, plus `inventory`, `roast`, `sales`, `tasting` |
79
173
 
80
- - `purvey catalog search`
81
- - `purvey catalog get <id>`
82
- - `purvey catalog stats`
83
- - `purvey catalog similar <id>`
174
+ `auth`, `config`, `context`, and `manifest` remain available without a pre-existing session.
175
+
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.
84
177
 
85
- `inventory`, `roast`, `sales`, and `tasting` commands require a member role.
178
+ ## Output contract and scripting
86
179
 
87
- ## Output and Scripting
180
+ Most commands write compact JSON to stdout by default. Use `--json` if you want to request that mode explicitly.
88
181
 
89
- Most commands write compact JSON to stdout by default.
90
- 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.
91
183
 
92
184
  Pretty JSON:
93
185
 
@@ -95,10 +187,11 @@ Pretty JSON:
95
187
  purvey inventory list --pretty
96
188
  ```
97
189
 
98
- CSV output for array results:
190
+ CSV output for array results on supporting commands:
99
191
 
100
192
  ```bash
101
193
  purvey inventory list --csv > inventory.csv
194
+ purvey roast list --csv > roasts.csv
102
195
  purvey sales list --csv > sales.csv
103
196
  ```
104
197
 
@@ -110,22 +203,82 @@ purvey roast list --limit 5 | jq '.[].roast_id'
110
203
  purvey auth status 2>/dev/null | jq -r '.email'
111
204
  ```
112
205
 
113
- Operational messages go to stderr, so stdout stays script-friendly.
206
+ Operational messages go to stderr so stdout stays script-friendly.
207
+
208
+ Fatal errors also stay on stderr, but the payload format depends on mode:
209
+
210
+ - interactive terminal with no explicit output flag: human-readable text
211
+ - `--json`, `--pretty`, or `--csv`: JSON error envelope on stderr
212
+ - piped or redirected with no explicit flag: compact JSON error envelope on stderr
213
+
214
+ That contract also applies to parser-level mistakes such as unknown options, unknown commands, and missing required arguments.
215
+
216
+ The JSON error envelope includes:
217
+
218
+ ```json
219
+ { "error": true, "code": "INVALID_ARGUMENT", "exitCode": 2, "message": "..." }
220
+ ```
114
221
 
115
222
  ### Output caveats worth knowing
116
223
 
117
- - `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.
118
- - `--json` is an explicit alias for the default compact JSON mode, and it forces JSON even in an interactive terminal.
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.
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.
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.
228
+ - `--csv` affects successful stdout output only; fatal errors still use JSON on stderr.
229
+
230
+ ## Exit codes
231
+
232
+ All `purvey` commands exit with a numeric code your scripts can check with `$?`.
119
233
 
120
- ## Command Reference
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 |
243
+
244
+ Scripting pattern:
245
+
246
+ ```bash
247
+ purvey catalog search --origin "Ethiopia" --stocked --json
248
+ if [ $? -eq 3 ]; then
249
+ purvey auth login --headless
250
+ fi
251
+ ```
252
+
253
+ ## Command reference
121
254
 
122
255
  ### auth
123
256
 
124
257
  - `purvey auth login`
125
258
  - `purvey auth login --headless`
126
259
  - `purvey auth status`
260
+ - `purvey auth status --json`
261
+ - `purvey auth status --pretty`
262
+ - `purvey auth status --csv`
127
263
  - `purvey auth logout`
128
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
+
129
282
  ### catalog
130
283
 
131
284
  - `purvey catalog search`
@@ -135,27 +288,27 @@ Operational messages go to stderr, so stdout stays script-friendly.
135
288
 
136
289
  `catalog search` filters:
137
290
 
138
- - `--origin <text>` -- filter by origin (country, continent, or region)
139
- - `--process <method>` -- filter by processing method (e.g. natural, washed)
140
- - `--price-min <n>` -- minimum price per lb (USD)
141
- - `--price-max <n>` -- maximum price per lb (USD)
142
- - `--flavor <keywords>` -- flavor keywords, comma-separated
143
- - `--name <text>` -- filter by coffee name (partial match, case-insensitive)
144
- - `--supplier <name>` -- filter by supplier/source name (partial match, case-insensitive)
145
- - `--ids <n,n,...>` -- fetch specific catalog IDs (comma-separated, ignores limit)
146
- - `--stocked` -- only show currently stocked coffees
147
- - `--variety <text>` -- filter by coffee variety/cultivar (partial match)
148
- - `--drying-method <text>` -- filter by drying method (partial match)
149
- - `--stocked-days <n>` -- only show coffees stocked within N days
150
- - `--sort <price|price-desc|name|origin|newest>` -- sort results
151
- - `--offset <n>` -- skip N results for pagination
152
- - `--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`
153
306
 
154
307
  `catalog similar <id>` options:
155
308
 
156
- - `--threshold <0-1>` -- minimum similarity score (default: 0.70)
157
- - `--limit <n>` -- max results (default: 10)
158
- - `--stocked-only` -- only show currently stocked beans
309
+ - `--threshold <0-1>`; default `0.70`
310
+ - `--limit <n>`; default `10`
311
+ - `--stocked-only`
159
312
 
160
313
  Examples:
161
314
 
@@ -165,54 +318,73 @@ purvey catalog search --supplier "Royal Coffee" --stocked --pretty
165
318
  purvey catalog search --ids "1182,1183,1200"
166
319
  purvey catalog search --stocked --sort price --offset 10 --limit 10
167
320
  purvey catalog similar 1182 --threshold 0.85 --stocked-only --pretty
168
- purvey catalog similar 1182 --json | jq '.[0]'
321
+ purvey catalog stats --pretty
322
+ purvey catalog get 1182 --pretty
169
323
  ```
170
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
+
171
331
  ### inventory
172
332
 
173
333
  - `purvey inventory list`
174
334
  - `purvey inventory get <id>`
175
335
  - `purvey inventory add`
176
336
  - `purvey inventory update <id>`
177
- - `purvey inventory delete <id>` (use `--force` to cascade delete dependent roasts and sales)
337
+ - `purvey inventory delete <id>`
178
338
 
179
- `inventory list` options:
339
+ `inventory list` filters:
180
340
 
181
- - `--stocked` -- only show currently stocked beans
182
- - `--catalog-id <id>` -- filter by catalog ID
183
- - `--purchase-date-start <YYYY-MM-DD>` -- only show purchases on or after this date
184
- - `--purchase-date-end <YYYY-MM-DD>` -- only show purchases on or before this date
185
- - `--origin <country>` -- filter by country of origin (partial match)
186
- - `--limit <n>` -- maximum results (default: 20)
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`
187
348
 
188
349
  `inventory add` flags:
189
350
 
190
- - `--catalog-id <id>` -- [REQUIRED] coffee_catalog.catalog_id
191
- - `--qty <lbs>` -- [REQUIRED] quantity in pounds
192
- - `--cost <dollars>` -- bean cost in dollars
193
- - `--tax-ship <dollars>` -- tax and shipping cost in dollars
194
- - `--notes <text>` -- notes for this inventory item
195
- - `--purchase-date <YYYY-MM-DD>` -- purchase date (defaults to today)
196
- - `--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`
197
358
 
198
359
  `inventory update <id>` flags:
199
360
 
200
- - `--qty <lbs>` -- updated quantity in pounds
201
- - `--cost <dollars>` -- updated bean cost
202
- - `--tax-ship <dollars>` -- updated tax/shipping cost
203
- - `--notes <text>` -- updated notes
204
- - `--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
205
371
 
206
372
  Examples:
207
373
 
208
374
  ```bash
209
375
  purvey inventory list --stocked --pretty
210
376
  purvey inventory add --catalog-id 128 --qty 10 --cost 8.50
211
- purvey inventory add --catalog-id 42 --qty 5 --cost 6.25 --tax-ship 4.00
377
+ purvey inventory add --form
212
378
  purvey inventory update 7 --stocked false
213
379
  purvey inventory delete 7 --yes
214
380
  ```
215
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
+
216
388
  ### roast
217
389
 
218
390
  - `purvey roast list`
@@ -225,67 +397,78 @@ purvey inventory delete 7 --yes
225
397
 
226
398
  `roast list` filters:
227
399
 
228
- - `--coffee-id <id>` -- filter by inventory item ID (green_coffee_inv.id)
229
- - `--roast-id <id>` -- filter by exact roast profile ID
230
- - `--batch-name <text>` -- filter by batch name (partial match, case-insensitive)
231
- - `--coffee-name <text>` -- filter by bean name (partial match, case-insensitive)
232
- - `--date-start <YYYY-MM-DD>` -- only show roasts on or after this date
233
- - `--date-end <YYYY-MM-DD>` -- only show roasts on or before this date
234
- - `--stocked` -- only show roasts for currently stocked beans
235
- - `--catalog-id <id>` -- filter by coffee_catalog ID
236
- - `--limit <n>` -- maximum results (default: 20)
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`
237
410
 
238
411
  `roast get <id>` options:
239
412
 
240
- - `--include-temps` -- include temperature curve data
241
- - `--include-events` -- include roast event markers
413
+ - `--include-temps`
414
+ - `--include-events`
242
415
 
243
416
  `roast create` flags:
244
417
 
245
- - `--coffee-id <id>` -- [REQUIRED] green_coffee_inv ID
246
- - `--batch-name <name>` -- batch name (defaults to coffee name + today's date)
247
- - `--oz-in <oz>` -- green weight in ounces
248
- - `--oz-out <oz>` -- roasted weight in ounces
249
- - `--roast-date <YYYY-MM-DD>` -- roast date (defaults to today)
250
- - `--notes <text>` -- roast notes
251
- - `--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`
252
425
 
253
- `roast update <id>` fields:
426
+ `roast update <id>` flags:
254
427
 
255
- - `--notes <text>` -- updated roast notes
256
- - `--oz-out <oz>` -- updated roasted weight (triggers weight loss recalculation)
257
- - `--batch-name <name>` -- updated batch name
258
- - `--targets <text>` -- updated roast targets
428
+ - `--batch-name <name>`
429
+ - `--oz-out <oz>`
430
+ - `--notes <text>`
431
+ - `--targets <text>`
259
432
 
260
433
  `roast import [file]` flags:
261
434
 
262
- - `--coffee-id <id>` -- [REQUIRED] green_coffee_inv ID
263
- - `--batch-name <name>` -- batch name (auto-generated if omitted)
264
- - `--oz-in <oz>` -- green weight (extracted from .alog if present, overridden here)
265
- - `--roast-notes <text>` -- additional roast notes
266
- - `--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`
267
441
 
268
442
  `roast watch [directory]` options:
269
443
 
270
- - `--coffee-id <id>` -- [REQUIRED unless --auto-match] inventory ID for all imports
271
- - `--batch-prefix <name>` -- batch name prefix for auto-named batches
272
- - `--prompt-each` -- prompt for bean selection on each new file
273
- - `--auto-match` -- auto-match beans per file (mutually exclusive with --coffee-id)
274
- - `--resume` -- resume a previous watch session
275
- - `--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`
276
454
 
277
455
  Examples:
278
456
 
279
457
  ```bash
280
458
  purvey roast list --catalog-id 128 --pretty
281
- purvey roast list --batch-name "Ethiopia Guji" --pretty
282
- purvey roast list --date-start 2026-03-01 --date-end 2026-03-31
459
+ purvey roast get 123 --include-temps --pretty
283
460
  purvey roast create --coffee-id 7 --batch-name "Ethiopia Guji Light" --oz-in 16
284
- purvey roast update 123 --targets "Aim for FC at 390F, 18% dev"
285
- 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"
286
462
  purvey roast watch ~/artisan/ --auto-match
287
463
  ```
288
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
+
289
472
  ### sales
290
473
 
291
474
  - `purvey sales list`
@@ -295,100 +478,167 @@ purvey roast watch ~/artisan/ --auto-match
295
478
 
296
479
  `sales list` filters:
297
480
 
298
- - `--roast-id <id>` -- filter by roast profile ID
299
- - `--date-start <YYYY-MM-DD>` -- only show sales on or after this date
300
- - `--date-end <YYYY-MM-DD>` -- only show sales on or before this date
301
- - `--buyer <name>` -- filter by buyer name (partial match, case-insensitive)
302
- - `--limit <n>` -- maximum results (default: 20)
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`
303
487
 
304
488
  `sales record` flags:
305
489
 
306
- - `--roast-id <id>` -- [REQUIRED] roast_data.roast_id
307
- - `--oz <amount>` -- [REQUIRED] ounces sold
308
- - `--price <dollars>` -- [REQUIRED] total sale price in dollars
309
- - `--buyer <name>` -- buyer name or identifier
310
- - `--sell-date <YYYY-MM-DD>` -- sale date (defaults to today)
311
- - `--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`
312
498
 
313
499
  `sales update <id>` flags:
314
500
 
315
- - `--oz <amount>` -- updated ounces sold
316
- - `--price <dollars>` -- updated sale price
317
- - `--buyer <name>` -- updated buyer name
318
- - `--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`
319
509
 
320
510
  Examples:
321
511
 
322
512
  ```bash
323
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
324
515
  purvey sales list --pretty
325
516
  purvey sales update 5 --price 24.00
326
517
  purvey sales delete 5 --yes
327
518
  ```
328
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
+
329
527
  ### tasting
330
528
 
331
- - `purvey tasting get <bean-id>`
332
- - `purvey tasting rate [bean-id]`
529
+ - `purvey tasting get <catalog-id>`
530
+ - `purvey tasting rate [inventory-id]`
531
+
532
+ ID distinction:
333
533
 
334
- `purvey tasting get <bean-id>` options:
534
+ - `tasting get` takes a `catalog_id`
535
+ - `tasting rate` takes an `inventory id`
335
536
 
336
- - `--filter <user|supplier|both>` -- which notes to show (default: both)
537
+ `purvey tasting get <catalog-id>` options:
337
538
 
338
- `purvey tasting rate [bean-id]` options:
539
+ - `--filter <user|supplier|both>`; default `both`
339
540
 
340
- - `--aroma <1-5>` -- [REQUIRED in flag mode]
341
- - `--body <1-5>` -- [REQUIRED in flag mode]
342
- - `--acidity <1-5>` -- [REQUIRED in flag mode]
343
- - `--sweetness <1-5>` -- [REQUIRED in flag mode]
344
- - `--aftertaste <1-5>` -- [REQUIRED in flag mode]
345
- - `--brew-method <method>` -- brew method used (e.g. pour_over, espresso)
346
- - `--notes <text>` -- additional tasting notes
347
- - `--form` -- interactive form mode
541
+ `purvey tasting rate [inventory-id]` options:
542
+
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`
348
551
 
349
552
  Examples:
350
553
 
351
554
  ```bash
352
555
  purvey tasting get 128 --filter both --pretty
353
556
  purvey tasting rate 7 --aroma 4 --body 3 --acidity 5 --sweetness 4 --aftertaste 4
354
- purvey tasting rate 42 --aroma 3 --body 3 --acidity 3 --sweetness 3 --aftertaste 3 --notes "Underextracted"
557
+ purvey tasting rate --form
355
558
  ```
356
559
 
560
+ Notes:
561
+
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.
565
+
357
566
  ### config
358
567
 
359
- - `purvey config list`
360
- - `purvey config get <key>`
361
- - `purvey config set <key> <value>`
362
- - `purvey config reset`
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`)
363
572
 
364
573
  Current config key:
365
574
 
366
- - `form-mode`: when set to `true`, write commands enter interactive mode when required args are missing
575
+ - `form-mode`: when `true`, write commands enter interactive mode when required args are missing
367
576
 
368
577
  Examples:
369
578
 
370
579
  ```bash
371
580
  purvey config set form-mode true
372
- purvey config get form-mode
581
+ purvey config get form-mode --json
582
+ purvey config reset --json
373
583
  ```
374
584
 
585
+ Notes:
586
+
587
+ - Config commands are local-only.
588
+ - Config file path: `~/.config/purvey/config.json`
589
+ - `--csv` is not supported.
590
+
375
591
  ### context
376
592
 
377
593
  - `purvey context`
594
+ - `purvey context --json`
595
+ - `purvey context --pretty`
596
+
597
+ Notes:
598
+
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.
604
+
605
+ ### manifest
606
+
607
+ - `purvey manifest`
608
+ - `purvey manifest --json`
609
+ - `purvey manifest --pretty`
610
+
611
+ Notes:
378
612
 
379
- Use this when an agent needs a compact, source-aware CLI reference.
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.
380
619
 
381
- ## Common Workflows
620
+ ### In-process manifest export
382
621
 
383
- ### Buy coffee, roast it, rate it, and record a sale
622
+ - `@purveyors/cli/manifest`
623
+
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.
625
+
626
+ ```ts
627
+ import { getCliManifest } from '@purveyors/cli/manifest';
628
+
629
+ const manifest = getCliManifest();
630
+ ```
631
+
632
+ ## Common workflows
633
+
634
+ ### Catalog to inventory to roast to sale
384
635
 
385
636
  ```bash
386
637
  purvey catalog search --origin "Ethiopia" --process "natural" --stocked --pretty
387
638
  purvey inventory add --catalog-id 128 --qty 10 --cost 8.50
388
- purvey inventory list --stocked --pretty
389
639
  purvey roast import ~/artisan/guji-light.alog --coffee-id 7 --pretty
390
640
  purvey tasting rate 7 --aroma 5 --body 3 --acidity 5 --sweetness 4 --aftertaste 4
391
- 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"
392
642
  ```
393
643
 
394
644
  ### Continuous Artisan watch mode
@@ -407,45 +657,97 @@ purvey roast list --csv > roasts.csv
407
657
  purvey sales list --csv > sales.csv
408
658
  ```
409
659
 
410
- ## 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
411
670
 
412
671
  Use the right ID for the right command.
413
672
 
414
- - `catalog_id`: coffee_catalog rows; used by `catalog get`, `inventory add --catalog-id`, `tasting get`, `roast list --catalog-id`
415
- - `inventory id`: green_coffee_inv rows; used by `inventory get/update/delete`, `roast --coffee-id`, `tasting rate`, `roast list --coffee-id`
416
- - `roast_id`: roast_data rows; used by `roast get/delete`, `sales --roast-id`, `roast list --roast-id`
417
- - `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`
418
678
 
419
- ## Environment Variables
679
+ ## Environment variables
420
680
 
421
681
  - `PURVEYORS_SUPABASE_URL`: override the Supabase project URL
422
682
  - `PURVEYORS_SUPABASE_ANON_KEY`: override the Supabase anon key
423
683
  - `PURVEYORS_BASE_URL`: override the Purveyors web base URL
424
684
  - `PURVEY_DEBUG`: enable verbose error output
425
685
 
426
- ## For AI Agents
686
+ ## For AI agents
427
687
 
428
- Start here:
688
+ Recommended bootstrap order:
429
689
 
430
690
  ```bash
691
+ purvey manifest
431
692
  purvey context
693
+ purvey auth login --headless
432
694
  ```
433
695
 
434
- Recommended flow:
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:
699
+
700
+ - stable command names
701
+ - structured stdout by default
702
+ - headless auth flow
703
+ - documented exit codes and role boundaries
704
+ - dedicated machine-readable manifest command
705
+ - dedicated dense human-readable reference command
706
+ - `purvey context --json` parity with `purvey manifest`
707
+ - `@purveyors/cli/manifest` export for in-process access
708
+ - `--offset` and `--limit` pagination across list commands
709
+
710
+ The scripting contract is simple: stdout carries successful payloads, stderr carries status and fatal errors, and exit codes stay stable.
711
+
712
+ ## Troubleshooting
713
+
714
+ **`Error: not logged in` or exit code 3**
435
715
 
436
716
  ```bash
717
+ purvey auth login
718
+ # or
437
719
  purvey auth login --headless
438
- purvey auth status 2>/dev/null | jq .
439
- purvey inventory list | jq '.[].id'
440
720
  ```
441
721
 
442
- The CLI is designed to be agent-friendly:
722
+ **Catalog commands fail after logging in**
443
723
 
444
- - stable command names
445
- - structured output on stdout
446
- - headless auth flow
447
- - copy-pasteable examples
448
- - a dedicated `context` command for onboarding
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.
725
+
726
+ **Wrong ID type passed to a command**
727
+
728
+ Use the [ID reference](#id-reference) section above. `catalog_id` and inventory IDs are different values.
729
+
730
+ **Pagination only shows the first page**
731
+
732
+ All list commands default to 20 results. Use `--limit` and `--offset`.
733
+
734
+ ```bash
735
+ purvey inventory list --limit 20 --offset 0
736
+ purvey inventory list --limit 20 --offset 20
737
+ purvey inventory list --limit 20 --offset 40
738
+ ```
739
+
740
+ **`inventory delete` fails with dependency conflict**
741
+
742
+ ```bash
743
+ purvey inventory delete 7 --force --yes
744
+ ```
745
+
746
+ **Enable verbose error output**
747
+
748
+ ```bash
749
+ PURVEY_DEBUG=1 purvey <command>
750
+ ```
449
751
 
450
752
  ## Development
451
753
 
@@ -454,18 +756,33 @@ git clone https://github.com/reedwhetstone/purveyors-cli
454
756
  cd purveyors-cli
455
757
  pnpm install
456
758
  npm run build
759
+ npm run verify:contract
760
+ npm run verify:dist
761
+ npm run verify:prepublish
457
762
  npm run check
458
763
  npm run lint
459
764
  npm test
460
765
  ```
461
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
+
462
769
  Key files:
463
770
 
464
- - `src/index.ts`: top-level CLI registration and global options
771
+ - `src/index.ts`: executable entrypoint
772
+ - `src/program.ts`: top-level CLI registration and global options
465
773
  - `src/commands/`: command definitions and help text
466
774
  - `src/lib/`: business logic and Supabase integration
467
- - `src/commands/context.ts`: dense agent reference
468
- - `AGENTS.md`: contributor guide
775
+ - `src/commands/context.ts`: dense human-readable agent reference
776
+ - `src/commands/manifest.ts`: machine-readable CLI manifest command
777
+ - `src/lib/manifest.ts`: shared manifest contract, package export list, command metadata, ID guidance, and context renderer
778
+ - `package.json`: package metadata and export surface, including `./manifest`
779
+ - `tests/dist-contract.test.ts`: compiled artifact parity guardrails
780
+ - `AGENTS.md`: canonical contributor guide
781
+
782
+ Live documentation:
783
+
784
+ - <https://purveyors.io/docs/cli/overview>
785
+ - <https://purveyors.io/docs/api/overview>
469
786
 
470
787
  ## License
471
788