@purveyors/cli 0.8.3 → 0.9.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.
@@ -1,294 +1,224 @@
1
1
  import { Command } from 'commander';
2
- // ─── Agent Context Reference ──────────────────────────────────────────────────
3
- //
4
- // This text is optimized for token efficiency: dense, structured, no fluff.
5
- // An agent reading this once should be able to use every CLI command correctly.
2
+ // This text is intentionally dense. An agent should be able to read it once,
3
+ // then use the CLI correctly without hunting through help output.
6
4
  const CONTEXT_TEXT = `
7
- PURVEY CLI Agent Reference v0.7
8
- ==================================
9
- CLI for purveyors.io coffee marketplace. Node >=20 required.
10
- Credentials: ~/.config/purvey/credentials.json (auto-refreshed, do not edit)
5
+ PURVEY CLI - Agent Reference
6
+ ============================
7
+ Official CLI for purveyors.io. Node.js 20+.
8
+ Credentials file: ~/.config/purvey/credentials.json
9
+ Config file: ~/.config/purvey/config.json
11
10
 
12
11
  ROLES
13
12
  -----
14
- viewer catalog commands only (no login required for catalog search/get/stats)
15
- member — all commands; required for inventory, roast, sales, tasting
13
+ viewer valid authenticated session; required for catalog commands
14
+ member required for inventory, roast, sales, and tasting commands
16
15
 
17
- AUTHENTICATION
18
- --------------
19
- Browser login (interactive):
16
+ AUTH
17
+ ----
18
+ Interactive login:
20
19
  purvey auth login
21
20
 
22
- Headless login (agents/CI — no browser):
21
+ Headless login:
23
22
  purvey auth login --headless
24
- prints Google OAuth URL
25
- user opens URL, signs in, copies callback URL
26
- paste callback URL back at prompt
27
- Token auto-refreshes; no action needed between sessions.
23
+ 1. CLI prints a Google OAuth URL
24
+ 2. User opens it in any browser and signs in
25
+ 3. User pastes the full callback URL back into the terminal
28
26
 
29
- Check status:
30
- purvey auth status # human-readable in terminal; compact JSON when piped
31
- purvey auth status --pretty # indented/colorized JSON (human reading)
32
- purvey auth status | jq '.email' # compact JSON on stdout
27
+ Status:
28
+ purvey auth status
29
+ purvey auth status --pretty
33
30
 
34
31
  Logout:
35
32
  purvey auth logout
36
33
 
37
- OUTPUT FORMATS
34
+ OUTPUT
35
+ ------
36
+ Most commands emit compact JSON to stdout by default.
37
+ Use --json to request compact JSON explicitly.
38
+ Use --pretty for indented JSON.
39
+ Use --csv for array-shaped results that support CSV output.
40
+ User feedback goes to stderr.
41
+
42
+ Important exception:
43
+ - auth status prints human-readable output in an interactive TTY unless --json, --pretty, or --csv is passed; otherwise it emits structured output
44
+
45
+ ID MAP
46
+ ------
47
+ catalog_id coffee_catalog row; used by catalog get, inventory add --catalog-id, tasting get, roast list --catalog-id
48
+ inventory_id green_coffee_inv row; used by inventory get/update/delete, roast --coffee-id, roast list --coffee-id, tasting rate
49
+ roast_id roast_data row; used by roast get/delete, roast list --roast-id, sales --roast-id
50
+ sale_id coffee_sales row; used by sales update/delete
51
+
52
+ COMMANDS
53
+ --------
54
+ auth
55
+ login [--headless]
56
+ status
57
+ logout
58
+
59
+ catalog
60
+ search [options]
61
+ --origin <text>
62
+ --process <method>
63
+ --price-min <n>
64
+ --price-max <n>
65
+ --flavor <keywords>
66
+ --name <text>
67
+ --supplier <name>
68
+ --ids <n,n,...>
69
+ --variety <text>
70
+ --drying-method <text>
71
+ --stocked-days <n>
72
+ --stocked
73
+ --sort <price|price-desc|name|origin|newest>
74
+ --offset <n>
75
+ --limit <n>
76
+ get <catalog_id>
77
+ stats
78
+ similar <catalog_id>
79
+ --threshold <0-1> default 0.70
80
+ --limit <n> default 10
81
+ --stocked-only
82
+
83
+ inventory (member)
84
+ list [--stocked] [--limit <n>]
85
+ get <inventory_id>
86
+ add
87
+ --catalog-id <id> required in flag mode
88
+ --qty <lbs> required in flag mode
89
+ --cost <dollars>
90
+ --tax-ship <dollars>
91
+ --notes <text>
92
+ --purchase-date <YYYY-MM-DD>
93
+ --form
94
+ update <inventory_id>
95
+ --qty <lbs>
96
+ --cost <dollars>
97
+ --tax-ship <dollars>
98
+ --notes <text>
99
+ --stocked <true|false>
100
+ delete <inventory_id> [--yes]
101
+
102
+ roast (member)
103
+ list
104
+ --coffee-id <inventory_id>
105
+ --roast-id <roast_id>
106
+ --batch-name <text>
107
+ --date-start <YYYY-MM-DD>
108
+ --date-end <YYYY-MM-DD>
109
+ --stocked
110
+ --catalog-id <catalog_id>
111
+ --limit <n>
112
+ get <roast_id>
113
+ --include-temps
114
+ --include-events
115
+ create
116
+ --coffee-id <inventory_id> required in flag mode
117
+ --batch-name <name>
118
+ --oz-in <oz>
119
+ --oz-out <oz>
120
+ --roast-date <YYYY-MM-DD>
121
+ --notes <text>
122
+ --form
123
+ update <roast_id>
124
+ --notes <text>
125
+ --oz-out <oz>
126
+ --batch-name <name>
127
+ --targets <text>
128
+ delete <roast_id> [--yes]
129
+ import [file]
130
+ --coffee-id <inventory_id> required in flag mode
131
+ --batch-name <name>
132
+ --oz-in <oz>
133
+ --roast-notes <text>
134
+ --form
135
+ watch [directory]
136
+ --coffee-id <inventory_id> required unless --auto-match
137
+ --batch-prefix <name>
138
+ --prompt-each
139
+ --auto-match mutually exclusive with --coffee-id
140
+ --resume
141
+ --form
142
+
143
+ sales (member)
144
+ list [--limit <n>]
145
+ record
146
+ --roast-id <roast_id> required in flag mode
147
+ --oz <amount> required in flag mode
148
+ --price <dollars> required in flag mode
149
+ --buyer <name>
150
+ --sell-date <YYYY-MM-DD>
151
+ --form
152
+ update <sale_id>
153
+ --oz <amount>
154
+ --price <dollars>
155
+ --buyer <name>
156
+ --sell-date <YYYY-MM-DD>
157
+ delete <sale_id> [--yes]
158
+
159
+ tasting (member)
160
+ get <catalog_id>
161
+ --filter <user|supplier|both> default both
162
+ rate [inventory_id]
163
+ --aroma <1-5> required in flag mode
164
+ --body <1-5> required in flag mode
165
+ --acidity <1-5> required in flag mode
166
+ --sweetness <1-5> required in flag mode
167
+ --aftertaste <1-5> required in flag mode
168
+ --brew-method <method>
169
+ --notes <text>
170
+ --form
171
+
172
+ config
173
+ list
174
+ get <key>
175
+ set <key> <value>
176
+ reset
177
+
178
+ Current config keys:
179
+ form-mode true|false
180
+
181
+ WORKFLOWS
182
+ ---------
183
+ Catalog -> inventory:
184
+ purvey catalog search --origin "Ethiopia" --stocked --pretty
185
+ purvey inventory add --catalog-id 128 --qty 10 --cost 8.50
186
+
187
+ Import a roast from Artisan:
188
+ purvey inventory list --stocked --pretty
189
+ purvey roast import ~/artisan/ethiopia.alog --coffee-id 7 --pretty
190
+
191
+ Watch a folder for new roasts:
192
+ purvey roast watch ~/artisan/ --coffee-id 7
193
+ purvey roast watch ~/artisan/ --auto-match
194
+ purvey roast watch --resume
195
+
196
+ Rate coffee and record a sale:
197
+ purvey tasting rate 7 --aroma 4 --body 3 --acidity 5 --sweetness 4 --aftertaste 4
198
+ purvey sales record --roast-id 123 --oz 12 --price 22.00 --buyer "Jane Smith"
199
+
200
+ ERROR PATTERNS
38
201
  --------------
39
- Default: compact single-line JSON (machine-readable, pipeable to jq)
40
- --pretty: indented + colorized JSON (human-readable)
41
- --csv: CSV rows (pipeable to spreadsheets)
42
- User feedback (spinners, success/error) → stderr only; stdout is always clean data.
43
-
44
- DATA MODEL
45
- ----------
46
- coffee_catalog (catalog_id)
47
- └─ green_coffee_inv (id, user, catalog_id) ← your inventory
48
- ├─ roast_data (roast_id, coffee_id) ← your roast profiles
49
- │ ├─ roast_temperatures ← temperature curve
50
- │ └─ roast_events ← FC, drop, etc.
51
- ├─ coffee_sales (id, roast_id) ← your sales
52
- └─ cupping notes stored on green_coffee_inv (aroma/body/acidity/sweetness/aftertaste)
53
-
54
- CATALOG COMMANDS (no auth required)
55
- -------------------------------------
56
- Search the public catalog:
57
- purvey catalog search [options]
58
- Options (all optional):
59
- --origin <text> country/region (e.g. "Ethiopia", "Colombia")
60
- --process <method> natural|washed|honey
61
- --price-min <n> min USD/lb (filters on price_per_lb)
62
- --price-max <n> max USD/lb (filters on price_per_lb)
63
- --flavor <keywords> comma-separated (e.g. "blueberry,citrus")
64
- --name <text> filter by coffee name (partial match, case-insensitive)
65
- --supplier <name> filter by supplier/source name (partial match, case-insensitive)
66
- --ids <n,n,...> fetch specific catalog IDs (comma-separated, ignores limit)
67
- --stocked only currently stocked
68
- --sort <field> price|price-desc|name|origin|newest
69
- --offset <n> skip N results for pagination (default: 0)
70
- --limit <n> max results (default: 10)
71
- Returns: array of catalog items with id, name, origin, process, flavor_notes, price_per_lb, cost_lb, stocked
72
-
73
- Get one catalog item by ID:
74
- purvey catalog get <catalog_id>
75
- Returns: single catalog item object
76
-
77
- Catalog statistics:
78
- purvey catalog stats
79
- Returns: aggregate stats (count, avg_price, origins, processes, etc.)
80
-
81
- INVENTORY COMMANDS (member role required)
82
- ------------------------------------------
83
- List your green coffee inventory:
84
- purvey inventory list [options]
85
- Options:
86
- --stocked only stocked beans
87
- --limit <n> max results (default: 20)
88
- Returns: array with id, catalog_id, qty, cost, stocked, purchase_date, plus joined catalog fields
89
-
90
- Get one inventory item:
91
- purvey inventory get <id>
92
- Returns: single inventory item
93
-
94
- Add bean to inventory (REQUIRED: --catalog-id, --qty):
95
- purvey inventory add --catalog-id <id> --qty <lbs> [options]
96
- Options:
97
- --catalog-id <id> [REQUIRED] coffee_catalog id
98
- --qty <lbs> [REQUIRED] pounds purchased
99
- --cost <dollars> cost per lb
100
- --tax-ship <dollars> tax + shipping cost
101
- --notes <text> notes
102
- --purchase-date <YYYY-MM-DD> defaults to today
103
- --form interactive wizard
104
- Returns: created inventory item
105
-
106
- Update inventory item:
107
- purvey inventory update <id> [options]
108
- Options (at least one required):
109
- --qty <lbs> updated quantity
110
- --cost <dollars> updated cost
111
- --tax-ship <dollars> updated tax/shipping
112
- --notes <text> updated notes
113
- --stocked <true|false> mark as stocked/unstocked
114
- Returns: updated inventory item
115
-
116
- Delete inventory item:
117
- purvey inventory delete <id> [-y]
118
- -y skips confirmation prompt
119
-
120
- ROAST COMMANDS (member role required)
121
- ---------------------------------------
122
- List roast profiles:
123
- purvey roast list [options]
124
- Options:
125
- --coffee-id <id> filter by inventory item id
126
- --limit <n> max results (default: 20)
127
- Returns: array of roast profiles with roast_id, batch_name, roast_date, oz_in, oz_out
128
-
129
- Get roast profile:
130
- purvey roast get <roast_id> [options]
131
- Options:
132
- --include-temps include temperature curve data
133
- --include-events include roast events (FC, drop, etc.)
134
- Returns: roast profile object
135
-
136
- Create roast profile (REQUIRED: --coffee-id):
137
- purvey roast create --coffee-id <id> [options]
138
- Options:
139
- --coffee-id <id> [REQUIRED] green_coffee_inv id
140
- --batch-name <name> defaults to "CoffeeName YYYY-MM-DD"
141
- --oz-in <oz> green weight in ounces
142
- --oz-out <oz> roasted weight in ounces
143
- --roast-date <YYYY-MM-DD> defaults to today
144
- --notes <text> roast notes
145
- --form interactive wizard
146
- Returns: created roast profile
147
-
148
- Delete roast profile:
149
- purvey roast delete <roast_id> [-y]
150
-
151
- Update roast profile:
152
- purvey roast update <roast_id> [options]
153
- Options:
154
- --notes <text> updated roast notes
155
- --oz-out <oz> updated roasted weight (recalculates weight_loss_percent)
156
- --batch-name <name> updated batch name
157
- --targets <text> updated roast targets (planning/goals)
158
- At least one flag required.
202
+ Not logged in:
203
+ run purvey auth login or purvey auth login --headless
159
204
 
160
- Import Artisan .alog file (REQUIRED: <file>, --coffee-id):
161
- purvey roast import <file.alog> --coffee-id <id> [options]
162
- Options:
163
- --coffee-id <id> [REQUIRED] green_coffee_inv id
164
- --batch-name <name> auto-generated if omitted
165
- --oz-in <oz> extracted from .alog if omitted
166
- --roast-notes <text> additional notes
167
- --form interactive wizard
168
- Returns: roast profile + milestone_events + control_events counts
169
-
170
- Watch directory for new .alog files (REQUIRED: <directory>):
171
- purvey roast watch <directory> [options]
172
- Options:
173
- --coffee-id <id> [REQUIRED unless --auto-match] green_coffee_inv id
174
- --batch-prefix <name> prefix for batch names
175
- --auto-match AI matches bean per file (requires member role; mutually exclusive with --coffee-id)
176
- --prompt-each prompt for bean on each new file
177
- --resume resume previous watch session
178
- --form interactive wizard
179
- Runs continuously (Ctrl+C to stop). Persists session for --resume.
180
-
181
- SALES COMMANDS (member role required)
182
- ---------------------------------------
183
- List sales:
184
- purvey sales list [--limit <n>]
185
- Returns: array with id, roast_id, oz, price, buyer, sell_date
186
-
187
- Record sale (REQUIRED: --roast-id, --oz, --price):
188
- purvey sales record --roast-id <id> --oz <amount> --price <dollars> [options]
189
- Options:
190
- --roast-id <id> [REQUIRED] roast_data id
191
- --oz <amount> [REQUIRED] ounces sold
192
- --price <dollars> [REQUIRED] sale price
193
- --buyer <name> buyer name/identifier
194
- --sell-date <YYYY-MM-DD> defaults to today
195
- --form interactive wizard
196
- Returns: created sale record
197
-
198
- Update sale:
199
- purvey sales update <id> [--oz <n>] [--price <n>] [--buyer <name>] [--sell-date <date>]
200
-
201
- Delete sale:
202
- purvey sales delete <id> [-y]
203
-
204
- TASTING COMMANDS (member role required)
205
- -----------------------------------------
206
- Get tasting notes for a catalog item:
207
- purvey tasting get <catalog_id> [--filter user|supplier|both]
208
- --filter both (default): returns {supplier: {...}, user: {...}}
209
- --filter user: only your cupping notes from green_coffee_inv
210
- --filter supplier: only the supplier's flavor notes from coffee_catalog
211
-
212
- Rate a bean (REQUIRED: <inventory_id>, all score flags):
213
- purvey tasting rate <inventory_id> --aroma <1-5> --body <1-5> --acidity <1-5> --sweetness <1-5> --aftertaste <1-5> [options]
214
- Options:
215
- --aroma <1-5> [REQUIRED in flag mode] integer 1-5
216
- --body <1-5> [REQUIRED in flag mode] integer 1-5
217
- --acidity <1-5> [REQUIRED in flag mode] integer 1-5
218
- --sweetness <1-5> [REQUIRED in flag mode] integer 1-5
219
- --aftertaste <1-5> [REQUIRED in flag mode] integer 1-5
220
- --brew-method pour_over|french_press|espresso|etc
221
- --notes <text> tasting notes
222
- --form interactive wizard
223
- Note: <inventory_id> is green_coffee_inv.id, NOT catalog_id.
224
- Scores are stored on the green_coffee_inv row.
225
-
226
- CONFIG COMMANDS
227
- ---------------
228
- purvey config list show all config
229
- purvey config get form-mode get a value
230
- purvey config set form-mode true|false set a value
231
- purvey config reset clear all config
232
-
233
- Config keys:
234
- form-mode true|false — when true, write commands auto-enter interactive
235
- wizard if required args are missing (default: false)
236
-
237
- COMMON WORKFLOWS
238
- ----------------
239
- 1. Import a roast and rate it:
240
- purvey inventory list --stocked --pretty # find your bean id
241
- purvey roast import roast.alog --coffee-id 42 # import
242
- purvey tasting rate 42 --aroma 4 --body 3 --acidity 5 --sweetness 4 --aftertaste 4
243
-
244
- 2. Full workflow: buy → roast → sell:
245
- purvey catalog search --origin "Ethiopia" --stocked --pretty
246
- purvey inventory add --catalog-id 128 --qty 10 --cost 8.50
247
- purvey roast create --coffee-id 7 --oz-in 16 --batch-name "Ethiopia Guji Light"
248
- purvey sales record --roast-id 123 --oz 12 --price 22.00 --buyer "Jane"
249
-
250
- 3. Watch directory and auto-import roasts:
251
- purvey roast watch ~/artisan/ --coffee-id 7
252
- # or with AI bean matching:
253
- purvey roast watch ~/artisan/ --auto-match
254
-
255
- 4. Find a coffee and check tasting notes:
256
- purvey catalog search --flavor "blueberry,floral" --pretty
257
- purvey tasting get 128 --filter both --pretty
258
-
259
- ERROR HANDLING
260
- --------------
261
- Exit 0: success
262
- Exit 1: error (auth failure, not found, validation error, API error)
263
- Errors go to stderr as: Error: <message>
205
+ Wrong ID type:
206
+ verify whether the command wants catalog_id, inventory_id, roast_id, or sale_id
264
207
 
265
- Common errors:
266
- "Not logged in" → run: purvey auth login [--headless]
267
- "Not found" → check the ID; must be yours (RLS enforced)
268
- "INVALID_ARGUMENT" → check flags; use --form for interactive mode
269
- "--auto-match and --coffee-id are mutually exclusive" → use one or the other
208
+ Missing required args in write commands:
209
+ pass the required flags or use --form
270
210
 
271
- ID TYPES (avoid confusion)
272
- ---------------------------
273
- catalog_id → coffee_catalog table; used with: catalog get, inventory add --catalog-id, tasting get
274
- inventory_id → green_coffee_inv table (your beans); used with: inventory get/update/delete, roast --coffee-id, tasting rate
275
- roast_id → roast_data table; used with: roast get/delete, sales --roast-id
276
- sale_id → coffee_sales table; used with: sales update/delete
211
+ Mutually exclusive watch flags:
212
+ roast watch forbids using --auto-match together with --coffee-id
277
213
  `.trim();
278
- // ─── Command builder ──────────────────────────────────────────────────────────
279
- /**
280
- * `purvey context`
281
- * Outputs a comprehensive CLI reference for AI agent onboarding.
282
- * Optimized for token efficiency — dense, structured, no fluff.
283
- */
284
214
  export function buildContextCommand() {
285
215
  return new Command('context')
286
216
  .description('Output full CLI reference for AI agent onboarding')
287
217
  .addHelpText('after', `
288
218
  Examples:
289
- purvey context # print full reference to stdout
290
- purvey context | head -50 # preview first section
291
- purvey context > cli-ref.txt # save to file
219
+ purvey context
220
+ purvey context | head -50
221
+ purvey context > cli-reference.txt
292
222
  `)
293
223
  .action(() => {
294
224
  console.log(CONTEXT_TEXT);
@@ -1 +1 @@
1
- {"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/commands/context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,iFAAiF;AACjF,EAAE;AACF,4EAA4E;AAC5E,gFAAgF;AAEhF,MAAM,YAAY,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+QpB,CAAC,IAAI,EAAE,CAAC;AAET,iFAAiF;AAEjF;;;;GAIG;AACH,MAAM,UAAU,mBAAmB;IACjC,OAAO,IAAI,OAAO,CAAC,SAAS,CAAC;SAC1B,WAAW,CAAC,mDAAmD,CAAC;SAChE,WAAW,CACV,OAAO,EACP;;;;;CAKL,CACI;SACA,MAAM,CAAC,GAAG,EAAE;QACX,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;IAC5B,CAAC,CAAC,CAAC;AACP,CAAC"}
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../../src/commands/context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,6EAA6E;AAC7E,kEAAkE;AAElE,MAAM,YAAY,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiNpB,CAAC,IAAI,EAAE,CAAC;AAET,MAAM,UAAU,mBAAmB;IACjC,OAAO,IAAI,OAAO,CAAC,SAAS,CAAC;SAC1B,WAAW,CAAC,mDAAmD,CAAC;SAChE,WAAW,CACV,OAAO,EACP;;;;;CAKL,CACI;SACA,MAAM,CAAC,GAAG,EAAE;QACX,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;IAC5B,CAAC,CAAC,CAAC;AACP,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"roast.d.ts","sourceRoot":"","sources":["../../src/commands/roast.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAgBpC,OAAO,KAAK,EACV,YAAY,EACZ,gBAAgB,EAChB,eAAe,EAEhB,MAAM,iBAAiB,CAAC;AAOzB,YAAY,EAAE,YAAY,EAAE,gBAAgB,EAAE,eAAe,EAAE,CAAC;AAIhE;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAmuB3C"}
1
+ {"version":3,"file":"roast.d.ts","sourceRoot":"","sources":["../../src/commands/roast.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAgBpC,OAAO,KAAK,EACV,YAAY,EACZ,gBAAgB,EAChB,eAAe,EAEhB,MAAM,iBAAiB,CAAC;AAOzB,YAAY,EAAE,YAAY,EAAE,gBAAgB,EAAE,eAAe,EAAE,CAAC;AAIhE;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAgyB3C"}
@@ -22,24 +22,70 @@ export function buildRoastCommand() {
22
22
  .command('list')
23
23
  .description('List your roast profiles, sorted by date (newest first)')
24
24
  .option('--coffee-id <id>', 'Filter by green_coffee_inv ID')
25
+ .option('--roast-id <id>', 'Filter by roast profile ID')
26
+ .option('--batch-name <text>', 'Filter by batch name (partial match, case-insensitive)')
27
+ .option('--date-start <YYYY-MM-DD>', 'Only show roasts on or after this date')
28
+ .option('--date-end <YYYY-MM-DD>', 'Only show roasts on or before this date')
29
+ .option('--stocked', 'Only show roasts for currently stocked beans')
30
+ .option('--catalog-id <id>', 'Filter by coffee_catalog ID')
25
31
  .option('--limit <n>', 'Maximum results to return', '20')
26
32
  .addHelpText('after', `
27
33
  Examples:
28
34
  purvey roast list --pretty
29
35
  purvey roast list --coffee-id 7 --pretty
36
+ purvey roast list --roast-id 123 --pretty
37
+ purvey roast list --batch-name "Ethiopia Guji" --pretty
38
+ purvey roast list --date-start 2026-03-01 --date-end 2026-03-31
39
+ purvey roast list --stocked --limit 10
40
+ purvey roast list --catalog-id 128 --pretty
30
41
  purvey roast list --limit 5 | jq '.[].roast_id'
31
42
  purvey roast list --csv > roasts.csv
32
43
 
33
44
  Notes:
34
45
  --coffee-id filters by inventory item (green_coffee_inv.id), not catalog_id.
46
+ --roast-id filters by the exact roast profile ID while preserving list output shape.
47
+ --catalog-id filters by coffee_catalog ID (cross-reference from catalog search).
48
+ --batch-name accepts partial matches (case-insensitive).
49
+ --date-start and --date-end accept YYYY-MM-DD format; use together for a range.
50
+ --stocked only returns roasts for beans currently marked as stocked in inventory.
35
51
  Returns roast_id, batch_name, roast_date, oz_in, oz_out, and bean details.
36
52
  Requires authentication (member role).
37
53
  `)
38
54
  .action(withErrorHandling(async (opts, cmd) => {
39
55
  const globalOpts = cmd.optsWithGlobals();
40
56
  const { supabase, userId } = await requireAuth('member');
57
+ // Parse --date-start and --date-end format
58
+ const dateStart = opts.dateStart;
59
+ const dateEnd = opts.dateEnd;
60
+ if (dateStart && !/^\d{4}-\d{2}-\d{2}$/.test(dateStart)) {
61
+ throw new PrvrsError('INVALID_ARGUMENT', `Invalid --date-start: "${dateStart}". Must be YYYY-MM-DD format.`);
62
+ }
63
+ if (dateEnd && !/^\d{4}-\d{2}-\d{2}$/.test(dateEnd)) {
64
+ throw new PrvrsError('INVALID_ARGUMENT', `Invalid --date-end: "${dateEnd}". Must be YYYY-MM-DD format.`);
65
+ }
66
+ // Parse exact-id filters
67
+ let roastId;
68
+ if (opts.roastId !== undefined) {
69
+ roastId = parseInt(opts.roastId, 10);
70
+ if (isNaN(roastId) || roastId <= 0) {
71
+ throw new PrvrsError('INVALID_ARGUMENT', `Invalid --roast-id: "${opts.roastId}". Must be a positive integer.`);
72
+ }
73
+ }
74
+ let catalogId;
75
+ if (opts.catalogId !== undefined) {
76
+ catalogId = parseInt(opts.catalogId, 10);
77
+ if (isNaN(catalogId) || catalogId <= 0) {
78
+ throw new PrvrsError('INVALID_ARGUMENT', `Invalid --catalog-id: "${opts.catalogId}". Must be a positive integer.`);
79
+ }
80
+ }
41
81
  const data = await listRoasts(supabase, userId, {
42
82
  coffee_id: opts.coffeeId !== undefined ? parseInt(opts.coffeeId, 10) : undefined,
83
+ roast_id: roastId,
84
+ batch_name: opts.batchName,
85
+ date_start: dateStart,
86
+ date_end: dateEnd,
87
+ stocked_only: opts.stocked === true ? true : undefined,
88
+ catalog_id: catalogId,
43
89
  limit: Math.max(1, parseInt(opts.limit, 10)),
44
90
  });
45
91
  if (data.length === 0) {