@purveyors/cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/LICENSE.md +53 -0
  2. package/README.md +175 -0
  3. package/dist/commands/auth.d.ts +6 -0
  4. package/dist/commands/auth.d.ts.map +1 -0
  5. package/dist/commands/auth.js +193 -0
  6. package/dist/commands/auth.js.map +1 -0
  7. package/dist/commands/catalog.d.ts +63 -0
  8. package/dist/commands/catalog.d.ts.map +1 -0
  9. package/dist/commands/catalog.js +132 -0
  10. package/dist/commands/catalog.js.map +1 -0
  11. package/dist/commands/inventory.d.ts +32 -0
  12. package/dist/commands/inventory.d.ts.map +1 -0
  13. package/dist/commands/inventory.js +287 -0
  14. package/dist/commands/inventory.js.map +1 -0
  15. package/dist/commands/roast.d.ts +48 -0
  16. package/dist/commands/roast.d.ts.map +1 -0
  17. package/dist/commands/roast.js +225 -0
  18. package/dist/commands/roast.js.map +1 -0
  19. package/dist/commands/sales.d.ts +17 -0
  20. package/dist/commands/sales.d.ts.map +1 -0
  21. package/dist/commands/sales.js +226 -0
  22. package/dist/commands/sales.js.map +1 -0
  23. package/dist/commands/tasting.d.ts +46 -0
  24. package/dist/commands/tasting.d.ts.map +1 -0
  25. package/dist/commands/tasting.js +174 -0
  26. package/dist/commands/tasting.js.map +1 -0
  27. package/dist/index.d.ts +3 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +68 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/lib/config.d.ts +21 -0
  32. package/dist/lib/config.d.ts.map +1 -0
  33. package/dist/lib/config.js +45 -0
  34. package/dist/lib/config.js.map +1 -0
  35. package/dist/lib/errors.d.ts +20 -0
  36. package/dist/lib/errors.d.ts.map +1 -0
  37. package/dist/lib/errors.js +58 -0
  38. package/dist/lib/errors.js.map +1 -0
  39. package/dist/lib/output.d.ts +22 -0
  40. package/dist/lib/output.d.ts.map +1 -0
  41. package/dist/lib/output.js +86 -0
  42. package/dist/lib/output.js.map +1 -0
  43. package/dist/lib/prompts.d.ts +11 -0
  44. package/dist/lib/prompts.d.ts.map +1 -0
  45. package/dist/lib/prompts.js +22 -0
  46. package/dist/lib/prompts.js.map +1 -0
  47. package/dist/lib/supabase.d.ts +22 -0
  48. package/dist/lib/supabase.d.ts.map +1 -0
  49. package/dist/lib/supabase.js +87 -0
  50. package/dist/lib/supabase.js.map +1 -0
  51. package/dist/types/database.types.d.ts +13 -0
  52. package/dist/types/database.types.d.ts.map +1 -0
  53. package/dist/types/database.types.js +8 -0
  54. package/dist/types/database.types.js.map +1 -0
  55. package/dist/types/index.d.ts +28 -0
  56. package/dist/types/index.d.ts.map +1 -0
  57. package/dist/types/index.js +5 -0
  58. package/dist/types/index.js.map +1 -0
  59. package/package.json +58 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sales.d.ts","sourceRoot":"","sources":["../../src/commands/sales.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AASpC,MAAM,WAAW,IAAI;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;CACtB;AAQD;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CA0R3C"}
@@ -0,0 +1,226 @@
1
+ import { Command } from 'commander';
2
+ import { createAuthenticatedClient } from '../lib/supabase.js';
3
+ import { outputData, info, success } from '../lib/output.js';
4
+ import { withErrorHandling, AuthError, PrvrsError } from '../lib/errors.js';
5
+ import { confirm, todayIso } from '../lib/prompts.js';
6
+ // ─── Shared select columns ────────────────────────────────────────────────────
7
+ const SALE_SELECT = 'id, roast_id, oz_sold, sale_price, buyer, sell_date, user, last_updated';
8
+ // ─── Command builder ──────────────────────────────────────────────────────────
9
+ /**
10
+ * `prvrs sales` — Record and manage coffee sales.
11
+ * Requires authentication.
12
+ */
13
+ export function buildSalesCommand() {
14
+ const sales = new Command('sales').description('Record and manage coffee sales');
15
+ // ── sales list ────────────────────────────────────────────────────────────
16
+ sales
17
+ .command('list')
18
+ .description('List your sales, sorted by sell date (newest first)')
19
+ .option('--limit <n>', 'Maximum results to return', '20')
20
+ .action(withErrorHandling(async (opts, cmd) => {
21
+ const globalOpts = cmd.optsWithGlobals();
22
+ const supabase = await createAuthenticatedClient();
23
+ const { data: { user }, } = await supabase.auth.getUser();
24
+ if (!user) {
25
+ throw new AuthError('Not logged in. Run `prvrs auth login` first.');
26
+ }
27
+ const limit = Math.max(1, parseInt(opts.limit, 10));
28
+ // Join with roast_profiles to surface coffee name
29
+ const { data, error } = await supabase
30
+ .from('sales')
31
+ .select(`${SALE_SELECT}, roast_profiles!roast_id (batch_name, coffee_name)`)
32
+ .eq('user', user.id)
33
+ .order('sell_date', { ascending: false })
34
+ .limit(limit);
35
+ if (error)
36
+ throw error;
37
+ if (!data || data.length === 0) {
38
+ info('No sales found.');
39
+ return;
40
+ }
41
+ outputData(data, globalOpts);
42
+ }));
43
+ // ── sales record ──────────────────────────────────────────────────────────
44
+ sales
45
+ .command('record')
46
+ .description('Record a new sale')
47
+ .requiredOption('--roast-id <id>', 'Roast profile ID')
48
+ .requiredOption('--oz <amount>', 'Ounces sold')
49
+ .requiredOption('--price <dollars>', 'Sale price in dollars')
50
+ .option('--buyer <name>', 'Buyer name or identifier')
51
+ .option('--sell-date <YYYY-MM-DD>', 'Sale date (defaults to today)')
52
+ .action(withErrorHandling(async (opts, cmd) => {
53
+ const globalOpts = cmd.optsWithGlobals();
54
+ const supabase = await createAuthenticatedClient();
55
+ const { data: { user }, } = await supabase.auth.getUser();
56
+ if (!user) {
57
+ throw new AuthError('Not logged in. Run `prvrs auth login` first.');
58
+ }
59
+ const roastId = parseInt(opts.roastId, 10);
60
+ if (isNaN(roastId)) {
61
+ throw new PrvrsError('INVALID_ARGUMENT', `Invalid --roast-id: "${opts.roastId}".`);
62
+ }
63
+ const oz = parseFloat(opts.oz);
64
+ if (isNaN(oz) || oz <= 0) {
65
+ throw new PrvrsError('INVALID_ARGUMENT', `Invalid --oz: "${opts.oz}". Must be a positive number.`);
66
+ }
67
+ const price = parseFloat(opts.price);
68
+ if (isNaN(price) || price < 0) {
69
+ throw new PrvrsError('INVALID_ARGUMENT', `Invalid --price: "${opts.price}". Must be a non-negative number.`);
70
+ }
71
+ // Verify the roast profile belongs to the user
72
+ const { data: roastExists, error: roastError } = await supabase
73
+ .from('roast_profiles')
74
+ .select('roast_id')
75
+ .eq('roast_id', roastId)
76
+ .eq('user', user.id)
77
+ .single();
78
+ if (roastError || !roastExists) {
79
+ throw new AuthError(`Roast profile ${roastId} not found or does not belong to you.`);
80
+ }
81
+ const insertPayload = {
82
+ user: user.id,
83
+ roast_id: roastId,
84
+ oz_sold: oz,
85
+ sale_price: price,
86
+ sell_date: opts.sellDate ?? todayIso(),
87
+ };
88
+ if (opts.buyer !== undefined) {
89
+ insertPayload.buyer = opts.buyer;
90
+ }
91
+ const { data: inserted, error: insertError } = await supabase
92
+ .from('sales')
93
+ .insert(insertPayload)
94
+ .select('id')
95
+ .single();
96
+ if (insertError)
97
+ throw insertError;
98
+ // Re-fetch the full row with roast join
99
+ const { data, error } = await supabase
100
+ .from('sales')
101
+ .select(`${SALE_SELECT}, roast_profiles!roast_id (batch_name, coffee_name)`)
102
+ .eq('id', inserted.id)
103
+ .single();
104
+ if (error)
105
+ throw error;
106
+ success(`Sale ${inserted.id} recorded.`);
107
+ outputData(data, globalOpts);
108
+ }));
109
+ // ── sales update <id> ─────────────────────────────────────────────────────
110
+ sales
111
+ .command('update <id>')
112
+ .description('Update an existing sale (must be yours)')
113
+ .option('--oz <amount>', 'Updated ounces sold')
114
+ .option('--price <dollars>', 'Updated sale price')
115
+ .option('--buyer <name>', 'Updated buyer name')
116
+ .option('--sell-date <YYYY-MM-DD>', 'Updated sale date')
117
+ .action(withErrorHandling(async (id, opts, cmd) => {
118
+ const globalOpts = cmd.optsWithGlobals();
119
+ const supabase = await createAuthenticatedClient();
120
+ const { data: { user }, } = await supabase.auth.getUser();
121
+ if (!user) {
122
+ throw new AuthError('Not logged in. Run `prvrs auth login` first.');
123
+ }
124
+ const saleId = parseInt(id, 10);
125
+ if (isNaN(saleId)) {
126
+ throw new PrvrsError('INVALID_ARGUMENT', `Invalid sale ID: "${id}".`);
127
+ }
128
+ // Verify ownership
129
+ const { data: existing, error: fetchError } = await supabase
130
+ .from('sales')
131
+ .select('id')
132
+ .eq('id', saleId)
133
+ .eq('user', user.id)
134
+ .single();
135
+ if (fetchError || !existing) {
136
+ throw new AuthError(`Sale ${id} not found or does not belong to you.`);
137
+ }
138
+ // Build partial update
139
+ const updates = {};
140
+ if (opts.oz !== undefined) {
141
+ const oz = parseFloat(opts.oz);
142
+ if (isNaN(oz) || oz <= 0)
143
+ throw new PrvrsError('INVALID_ARGUMENT', `Invalid --oz: "${opts.oz}".`);
144
+ updates.oz_sold = oz;
145
+ }
146
+ if (opts.price !== undefined) {
147
+ const price = parseFloat(opts.price);
148
+ if (isNaN(price) || price < 0)
149
+ throw new PrvrsError('INVALID_ARGUMENT', `Invalid --price: "${opts.price}".`);
150
+ updates.sale_price = price;
151
+ }
152
+ if (opts.buyer !== undefined) {
153
+ updates.buyer = opts.buyer;
154
+ }
155
+ if (opts.sellDate !== undefined) {
156
+ updates.sell_date = opts.sellDate;
157
+ }
158
+ if (Object.keys(updates).length === 0) {
159
+ throw new PrvrsError('INVALID_ARGUMENT', 'No update fields provided. Pass at least one of: --oz, --price, --buyer, --sell-date.');
160
+ }
161
+ const { error: updateError } = await supabase
162
+ .from('sales')
163
+ .update(updates)
164
+ .eq('id', saleId)
165
+ .eq('user', user.id);
166
+ if (updateError)
167
+ throw updateError;
168
+ // Re-fetch the updated row
169
+ const { data, error } = await supabase
170
+ .from('sales')
171
+ .select(`${SALE_SELECT}, roast_profiles!roast_id (batch_name, coffee_name)`)
172
+ .eq('id', saleId)
173
+ .single();
174
+ if (error)
175
+ throw error;
176
+ success(`Sale ${saleId} updated.`);
177
+ outputData(data, globalOpts);
178
+ }));
179
+ // ── sales delete <id> ─────────────────────────────────────────────────────
180
+ sales
181
+ .command('delete <id>')
182
+ .description('Delete a sale (must be yours)')
183
+ .option('-y, --yes', 'Skip confirmation prompt')
184
+ .action(withErrorHandling(async (id, opts, cmd) => {
185
+ void cmd; // global opts not needed for delete
186
+ const supabase = await createAuthenticatedClient();
187
+ const { data: { user }, } = await supabase.auth.getUser();
188
+ if (!user) {
189
+ throw new AuthError('Not logged in. Run `prvrs auth login` first.');
190
+ }
191
+ const saleId = parseInt(id, 10);
192
+ if (isNaN(saleId)) {
193
+ throw new PrvrsError('INVALID_ARGUMENT', `Invalid sale ID: "${id}".`);
194
+ }
195
+ // Verify ownership
196
+ const { data: existing, error: fetchError } = await supabase
197
+ .from('sales')
198
+ .select('id, sell_date, oz_sold')
199
+ .eq('id', saleId)
200
+ .eq('user', user.id)
201
+ .single();
202
+ if (fetchError || !existing) {
203
+ throw new AuthError(`Sale ${id} not found or does not belong to you.`);
204
+ }
205
+ if (!opts.yes) {
206
+ const label = existing.sell_date
207
+ ? `from ${existing.sell_date} (${existing.oz_sold} oz)`
208
+ : `#${saleId}`;
209
+ const ok = await confirm(`Delete sale ${label}?`);
210
+ if (!ok) {
211
+ info('Aborted.');
212
+ return;
213
+ }
214
+ }
215
+ const { error: deleteError } = await supabase
216
+ .from('sales')
217
+ .delete()
218
+ .eq('id', saleId)
219
+ .eq('user', user.id);
220
+ if (deleteError)
221
+ throw deleteError;
222
+ success(`Sale ${saleId} deleted.`);
223
+ }));
224
+ return sales;
225
+ }
226
+ //# sourceMappingURL=sales.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sales.js","sourceRoot":"","sources":["../../src/commands/sales.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ import { Command } from 'commander';
2
+ export type TastingFilter = 'user' | 'supplier' | 'both';
3
+ export interface SupplierTastingNotes {
4
+ source: 'supplier';
5
+ catalogId: number;
6
+ name: string | null;
7
+ processing: string | null;
8
+ region: string | null;
9
+ cupping_notes: string | null;
10
+ ai_tasting_notes: unknown | null;
11
+ ai_description: string | null;
12
+ }
13
+ export interface UserTastingNotes {
14
+ source: 'user';
15
+ inventoryId: number;
16
+ catalogId: number | null;
17
+ cupping_notes: string | null;
18
+ notes: string | null;
19
+ }
20
+ export interface TastingResult {
21
+ beanId: number;
22
+ filter: TastingFilter;
23
+ supplier: SupplierTastingNotes | null;
24
+ user: UserTastingNotes | null;
25
+ }
26
+ export interface CuppingNotes {
27
+ aroma?: number;
28
+ body?: number;
29
+ acidity?: number;
30
+ sweetness?: number;
31
+ aftertaste?: number;
32
+ brew_method?: string;
33
+ notes?: string;
34
+ rated_at?: string;
35
+ }
36
+ /** Validate a cupping score is an integer in [1, 5]. */
37
+ export declare function isValidCuppingScore(value: number): boolean;
38
+ /** Parse and validate a cupping score flag value. */
39
+ export declare function parseCuppingScore(raw: string, flag: string): number;
40
+ /**
41
+ * `prvrs tasting` — View and record tasting notes for a bean.
42
+ * Combines supplier notes from coffee_catalog with user notes from green_coffee_inv.
43
+ * Requires authentication.
44
+ */
45
+ export declare function buildTastingCommand(): Command;
46
+ //# sourceMappingURL=tasting.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tasting.d.ts","sourceRoot":"","sources":["../../src/commands/tasting.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAQpC,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,UAAU,GAAG,MAAM,CAAC;AAEzD,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,UAAU,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,gBAAgB,EAAE,OAAO,GAAG,IAAI,CAAC;IACjC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B;AAED,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,aAAa,CAAC;IACtB,QAAQ,EAAE,oBAAoB,GAAG,IAAI,CAAC;IACtC,IAAI,EAAE,gBAAgB,GAAG,IAAI,CAAC;CAC/B;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAID,wDAAwD;AACxD,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAE1D;AAED,qDAAqD;AACrD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CASnE;AAID;;;;GAIG;AACH,wBAAgB,mBAAmB,IAAI,OAAO,CAyM7C"}
@@ -0,0 +1,174 @@
1
+ import { Command } from 'commander';
2
+ import { createAuthenticatedClient } from '../lib/supabase.js';
3
+ import { outputData, info, success } from '../lib/output.js';
4
+ import { withErrorHandling, AuthError, PrvrsError } from '../lib/errors.js';
5
+ // ─── Validation helpers ───────────────────────────────────────────────────────
6
+ /** Validate a cupping score is an integer in [1, 5]. */
7
+ export function isValidCuppingScore(value) {
8
+ return Number.isInteger(value) && value >= 1 && value <= 5;
9
+ }
10
+ /** Parse and validate a cupping score flag value. */
11
+ export function parseCuppingScore(raw, flag) {
12
+ const n = parseInt(raw, 10);
13
+ if (isNaN(n) || !isValidCuppingScore(n)) {
14
+ throw new PrvrsError('INVALID_ARGUMENT', `--${flag} must be an integer between 1 and 5 (got "${raw}").`);
15
+ }
16
+ return n;
17
+ }
18
+ // ─── Command builder ──────────────────────────────────────────────────────────
19
+ /**
20
+ * `prvrs tasting` — View and record tasting notes for a bean.
21
+ * Combines supplier notes from coffee_catalog with user notes from green_coffee_inv.
22
+ * Requires authentication.
23
+ */
24
+ export function buildTastingCommand() {
25
+ const tasting = new Command('tasting').description('View and record tasting notes for a coffee bean');
26
+ // ── tasting get <bean-id> ─────────────────────────────────────────────────
27
+ tasting
28
+ .command('get <bean-id>')
29
+ .description('Retrieve tasting notes for a bean (by coffee_catalog ID). Combines supplier and user notes.')
30
+ .option('--filter <type>', 'Which notes to show: user, supplier, or both (default: both)', 'both')
31
+ .action(withErrorHandling(async (beanId, opts, cmd) => {
32
+ const globalOpts = cmd.optsWithGlobals();
33
+ const filter = opts.filter;
34
+ if (!['user', 'supplier', 'both'].includes(filter)) {
35
+ throw new PrvrsError('INVALID_OPTION', `Invalid --filter value "${filter}". Use: user, supplier, or both.`);
36
+ }
37
+ const catalogId = parseInt(beanId, 10);
38
+ if (isNaN(catalogId)) {
39
+ throw new PrvrsError('INVALID_ARGUMENT', `Invalid bean ID: "${beanId}".`);
40
+ }
41
+ const supabase = await createAuthenticatedClient();
42
+ const { data: { user }, } = await supabase.auth.getUser();
43
+ if (!user) {
44
+ throw new AuthError('Not logged in. Run `prvrs auth login` first.');
45
+ }
46
+ const result = {
47
+ beanId: catalogId,
48
+ filter,
49
+ supplier: null,
50
+ user: null,
51
+ };
52
+ // ── Supplier notes (from coffee_catalog) ─────────────────────────
53
+ if (filter === 'supplier' || filter === 'both') {
54
+ const { data: catalogRow, error: catalogError } = await supabase
55
+ .from('coffee_catalog')
56
+ .select('id, name, processing, region, source, cupping_notes, ai_tasting_notes, ai_description')
57
+ .eq('id', catalogId)
58
+ .single();
59
+ if (catalogError && catalogError.code !== 'PGRST116') {
60
+ throw catalogError;
61
+ }
62
+ if (catalogRow) {
63
+ result.supplier = {
64
+ source: 'supplier',
65
+ catalogId: catalogRow.id,
66
+ name: catalogRow.name ?? null,
67
+ processing: catalogRow.processing ?? null,
68
+ region: catalogRow.region ?? null,
69
+ cupping_notes: catalogRow.cupping_notes ?? null,
70
+ ai_tasting_notes: catalogRow.ai_tasting_notes ?? null,
71
+ ai_description: catalogRow.ai_description ?? null,
72
+ };
73
+ }
74
+ }
75
+ // ── User notes (from green_coffee_inv) ───────────────────────────
76
+ if (filter === 'user' || filter === 'both') {
77
+ // A user might have multiple inventory items from the same catalog entry
78
+ const { data: invRows, error: invError } = await supabase
79
+ .from('green_coffee_inv')
80
+ .select('id, catalog_id, cupping_notes, notes')
81
+ .eq('catalog_id', catalogId)
82
+ .eq('user', user.id)
83
+ .order('id', { ascending: false })
84
+ .limit(1);
85
+ if (invError)
86
+ throw invError;
87
+ if (invRows && invRows.length > 0) {
88
+ const row = invRows[0];
89
+ result.user = {
90
+ source: 'user',
91
+ inventoryId: row.id,
92
+ catalogId: row.catalog_id ?? null,
93
+ cupping_notes: row.cupping_notes ?? null,
94
+ notes: row.notes ?? null,
95
+ };
96
+ }
97
+ }
98
+ // If nothing found at all, let the user know
99
+ if (result.supplier === null && result.user === null) {
100
+ info(`No tasting notes found for bean ID ${catalogId} (filter: ${filter}).`);
101
+ return;
102
+ }
103
+ outputData(result, globalOpts);
104
+ }));
105
+ // ── tasting rate <bean-id> ────────────────────────────────────────────────
106
+ tasting
107
+ .command('rate <bean-id>')
108
+ .description('Rate a bean from your inventory using cupping scores (updates green_coffee_inv)')
109
+ .requiredOption('--aroma <1-5>', 'Aroma score (1-5)')
110
+ .requiredOption('--body <1-5>', 'Body score (1-5)')
111
+ .requiredOption('--acidity <1-5>', 'Acidity score (1-5)')
112
+ .requiredOption('--sweetness <1-5>', 'Sweetness score (1-5)')
113
+ .requiredOption('--aftertaste <1-5>', 'Aftertaste score (1-5)')
114
+ .option('--brew-method <method>', 'Brew method (e.g. pour_over, french_press, espresso)')
115
+ .option('--notes <text>', 'Additional tasting notes')
116
+ .action(withErrorHandling(async (beanId, opts, cmd) => {
117
+ const globalOpts = cmd.optsWithGlobals();
118
+ const inventoryId = parseInt(beanId, 10);
119
+ if (isNaN(inventoryId)) {
120
+ throw new PrvrsError('INVALID_ARGUMENT', `Invalid bean ID: "${beanId}". Pass a green_coffee_inv ID.`);
121
+ }
122
+ // Parse and validate all scores
123
+ const cupping = {
124
+ aroma: parseCuppingScore(opts.aroma, 'aroma'),
125
+ body: parseCuppingScore(opts.body, 'body'),
126
+ acidity: parseCuppingScore(opts.acidity, 'acidity'),
127
+ sweetness: parseCuppingScore(opts.sweetness, 'sweetness'),
128
+ aftertaste: parseCuppingScore(opts.aftertaste, 'aftertaste'),
129
+ rated_at: new Date().toISOString(),
130
+ };
131
+ if (opts.brewMethod !== undefined) {
132
+ cupping.brew_method = opts.brewMethod;
133
+ }
134
+ if (opts.notes !== undefined) {
135
+ cupping.notes = opts.notes;
136
+ }
137
+ const supabase = await createAuthenticatedClient();
138
+ const { data: { user }, } = await supabase.auth.getUser();
139
+ if (!user) {
140
+ throw new AuthError('Not logged in. Run `prvrs auth login` first.');
141
+ }
142
+ // Verify ownership of the inventory item
143
+ const { data: existing, error: fetchError } = await supabase
144
+ .from('green_coffee_inv')
145
+ .select('id, catalog_id')
146
+ .eq('id', inventoryId)
147
+ .eq('user', user.id)
148
+ .single();
149
+ if (fetchError || !existing) {
150
+ throw new AuthError(`Inventory item ${inventoryId} not found or does not belong to you.`);
151
+ }
152
+ // Update the cupping_notes JSONB field
153
+ const { error: updateError } = await supabase
154
+ .from('green_coffee_inv')
155
+ // Pass object directly — Supabase JS serializes JSONB columns correctly
156
+ .update({ cupping_notes: cupping })
157
+ .eq('id', inventoryId)
158
+ .eq('user', user.id);
159
+ if (updateError)
160
+ throw updateError;
161
+ // Re-fetch the updated row
162
+ const { data, error } = await supabase
163
+ .from('green_coffee_inv')
164
+ .select('id, catalog_id, cupping_notes, notes, last_updated')
165
+ .eq('id', inventoryId)
166
+ .single();
167
+ if (error)
168
+ throw error;
169
+ success(`Cupping notes saved for inventory item ${inventoryId}.`);
170
+ outputData(data, globalOpts);
171
+ }));
172
+ return tasting;
173
+ }
174
+ //# sourceMappingURL=tasting.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tasting.js","sourceRoot":"","sources":["../../src/commands/tasting.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/index.js ADDED
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import { buildAuthCommand } from './commands/auth.js';
4
+ import { buildCatalogCommand } from './commands/catalog.js';
5
+ import { buildInventoryCommand } from './commands/inventory.js';
6
+ import { buildRoastCommand } from './commands/roast.js';
7
+ import { buildSalesCommand } from './commands/sales.js';
8
+ import { buildTastingCommand } from './commands/tasting.js';
9
+ import { readFileSync } from 'fs';
10
+ import { fileURLToPath } from 'url';
11
+ import { dirname, join } from 'path';
12
+ const __filename = fileURLToPath(import.meta.url);
13
+ const __dirname = dirname(__filename);
14
+ // Read version from package.json at runtime
15
+ let version = '0.0.1';
16
+ try {
17
+ const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf-8'));
18
+ version = pkg.version ?? version;
19
+ }
20
+ catch {
21
+ // Fallback to hardcoded version
22
+ }
23
+ const program = new Command();
24
+ program
25
+ .name('prvrs')
26
+ .description('The official CLI for purveyors.io — coffee intelligence from your terminal')
27
+ .version(version, '-v, --version', 'Print version')
28
+ .option('--pretty', 'Pretty-print JSON output with colors')
29
+ .option('--csv', 'Output results as CSV (useful for piping to spreadsheets)')
30
+ .addHelpText('after', `
31
+ Examples:
32
+ $ prvrs auth login # Authenticate via Google
33
+ $ prvrs auth status # Check login state
34
+ $ prvrs catalog search --origin Ethiopia --stocked
35
+ $ prvrs catalog get 42
36
+ $ prvrs catalog stats
37
+ $ prvrs inventory list --stocked
38
+ $ prvrs inventory get 7
39
+ $ prvrs inventory add --catalog-id 42 --qty 5 --cost 28.50
40
+ $ prvrs inventory update 7 --stocked true
41
+ $ prvrs inventory delete 7
42
+ $ prvrs roast list --limit 5
43
+ $ prvrs roast get 123 --include-temps
44
+ $ prvrs roast create --coffee-id 7 --batch-name "Ethiopia Guji" --oz-in 16
45
+ $ prvrs roast delete 123
46
+ $ prvrs sales list
47
+ $ prvrs sales record --roast-id 123 --oz 12 --price 22.00
48
+ $ prvrs sales update 5 --price 24.00
49
+ $ prvrs sales delete 5
50
+ $ prvrs tasting get 42 --filter both
51
+ $ prvrs tasting rate 7 --aroma 4 --body 3 --acidity 5 --sweetness 4 --aftertaste 4
52
+ $ prvrs --help # Show this help
53
+
54
+ Docs: https://purveyors.io/docs/cli
55
+ `);
56
+ // Register subcommands
57
+ program.addCommand(buildAuthCommand());
58
+ program.addCommand(buildCatalogCommand());
59
+ program.addCommand(buildInventoryCommand());
60
+ program.addCommand(buildRoastCommand());
61
+ program.addCommand(buildSalesCommand());
62
+ program.addCommand(buildTastingCommand());
63
+ // Parse and dispatch
64
+ program.parseAsync(process.argv).catch((err) => {
65
+ console.error(err instanceof Error ? err.message : String(err));
66
+ process.exit(1);
67
+ });
68
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAChE,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,MAAM,IAAI,CAAC;AAClC,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AACpC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAErC,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAClD,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;AAEtC,4CAA4C;AAC5C,IAAI,OAAO,GAAG,OAAO,CAAC;AACtB,IAAI,CAAC;IACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,cAAc,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC;IACrF,OAAO,GAAG,GAAG,CAAC,OAAO,IAAI,OAAO,CAAC;AACnC,CAAC;AAAC,MAAM,CAAC;IACP,gCAAgC;AAClC,CAAC;AAED,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,OAAO,CAAC;KACb,WAAW,CAAC,4EAA4E,CAAC;KACzF,OAAO,CAAC,OAAO,EAAE,eAAe,EAAE,eAAe,CAAC;KAClD,MAAM,CAAC,UAAU,EAAE,sCAAsC,CAAC;KAC1D,MAAM,CAAC,OAAO,EAAE,2DAA2D,CAAC;KAC5E,WAAW,CACV,OAAO,EACP;;;;;;;;;;;;;;;;;;;;;;;;;CAyBH,CACE,CAAC;AAEJ,uBAAuB;AACvB,OAAO,CAAC,UAAU,CAAC,gBAAgB,EAAE,CAAC,CAAC;AACvC,OAAO,CAAC,UAAU,CAAC,mBAAmB,EAAE,CAAC,CAAC;AAC1C,OAAO,CAAC,UAAU,CAAC,qBAAqB,EAAE,CAAC,CAAC;AAC5C,OAAO,CAAC,UAAU,CAAC,iBAAiB,EAAE,CAAC,CAAC;AACxC,OAAO,CAAC,UAAU,CAAC,iBAAiB,EAAE,CAAC,CAAC;AACxC,OAAO,CAAC,UAAU,CAAC,mBAAmB,EAAE,CAAC,CAAC;AAE1C,qBAAqB;AACrB,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;IACtD,OAAO,CAAC,KAAK,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;IAChE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,21 @@
1
+ import type { StoredCredentials } from '../types/index.js';
2
+ declare const CONFIG_DIR: string;
3
+ declare const CREDENTIALS_FILE: string;
4
+ /**
5
+ * Ensure the config directory exists with secure permissions.
6
+ */
7
+ export declare function ensureConfigDir(): Promise<void>;
8
+ /**
9
+ * Read stored credentials from disk. Returns null if not found.
10
+ */
11
+ export declare function readCredentials(): Promise<StoredCredentials | null>;
12
+ /**
13
+ * Write credentials to disk with restrictive permissions (owner read-only).
14
+ */
15
+ export declare function writeCredentials(creds: StoredCredentials): Promise<void>;
16
+ /**
17
+ * Delete stored credentials (logout).
18
+ */
19
+ export declare function deleteCredentials(): Promise<void>;
20
+ export { CONFIG_DIR, CREDENTIALS_FILE };
21
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAE3D,QAAA,MAAM,UAAU,QAAsC,CAAC;AACvD,QAAA,MAAM,gBAAgB,QAAuC,CAAC;AAE9D;;GAEG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAErD;AAED;;GAEG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAQzE;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAG9E;AAED;;GAEG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CAMvD;AAED,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,CAAC"}
@@ -0,0 +1,45 @@
1
+ import { homedir } from 'os';
2
+ import { join } from 'path';
3
+ import { mkdir, readFile, writeFile, unlink, access } from 'fs/promises';
4
+ import { constants } from 'fs';
5
+ const CONFIG_DIR = join(homedir(), '.config', 'prvrs');
6
+ const CREDENTIALS_FILE = join(CONFIG_DIR, 'credentials.json');
7
+ /**
8
+ * Ensure the config directory exists with secure permissions.
9
+ */
10
+ export async function ensureConfigDir() {
11
+ await mkdir(CONFIG_DIR, { recursive: true, mode: 0o700 });
12
+ }
13
+ /**
14
+ * Read stored credentials from disk. Returns null if not found.
15
+ */
16
+ export async function readCredentials() {
17
+ try {
18
+ await access(CREDENTIALS_FILE, constants.R_OK);
19
+ const raw = await readFile(CREDENTIALS_FILE, 'utf-8');
20
+ return JSON.parse(raw);
21
+ }
22
+ catch {
23
+ return null;
24
+ }
25
+ }
26
+ /**
27
+ * Write credentials to disk with restrictive permissions (owner read-only).
28
+ */
29
+ export async function writeCredentials(creds) {
30
+ await ensureConfigDir();
31
+ await writeFile(CREDENTIALS_FILE, JSON.stringify(creds, null, 2), { mode: 0o600 });
32
+ }
33
+ /**
34
+ * Delete stored credentials (logout).
35
+ */
36
+ export async function deleteCredentials() {
37
+ try {
38
+ await unlink(CREDENTIALS_FILE);
39
+ }
40
+ catch {
41
+ // Already gone — that's fine
42
+ }
43
+ }
44
+ export { CONFIG_DIR, CREDENTIALS_FILE };
45
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAC7B,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACzE,OAAO,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAG/B,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;AACvD,MAAM,gBAAgB,GAAG,IAAI,CAAC,UAAU,EAAE,kBAAkB,CAAC,CAAC;AAE9D;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe;IACnC,MAAM,KAAK,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;AAC5D,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe;IACnC,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,gBAAgB,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC;QAC/C,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,gBAAgB,EAAE,OAAO,CAAC,CAAC;QACtD,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAsB,CAAC;IAC9C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,KAAwB;IAC7D,MAAM,eAAe,EAAE,CAAC;IACxB,MAAM,SAAS,CAAC,gBAAgB,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;AACrF,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB;IACrC,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,gBAAgB,CAAC,CAAC;IACjC,CAAC;IAAC,MAAM,CAAC;QACP,6BAA6B;IAC/B,CAAC;AACH,CAAC;AAED,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,CAAC"}