@purveyors/cli 0.2.1 → 0.3.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 (73) hide show
  1. package/dist/commands/catalog.d.ts +4 -56
  2. package/dist/commands/catalog.d.ts.map +1 -1
  3. package/dist/commands/catalog.js +14 -81
  4. package/dist/commands/catalog.js.map +1 -1
  5. package/dist/commands/inventory.d.ts +2 -25
  6. package/dist/commands/inventory.d.ts.map +1 -1
  7. package/dist/commands/inventory.js +47 -147
  8. package/dist/commands/inventory.js.map +1 -1
  9. package/dist/commands/roast.d.ts +2 -41
  10. package/dist/commands/roast.d.ts.map +1 -1
  11. package/dist/commands/roast.js +138 -125
  12. package/dist/commands/roast.js.map +1 -1
  13. package/dist/commands/sales.d.ts +2 -10
  14. package/dist/commands/sales.d.ts.map +1 -1
  15. package/dist/commands/sales.js +30 -109
  16. package/dist/commands/sales.js.map +1 -1
  17. package/dist/commands/tasting.d.ts +4 -38
  18. package/dist/commands/tasting.d.ts.map +1 -1
  19. package/dist/commands/tasting.js +18 -108
  20. package/dist/commands/tasting.js.map +1 -1
  21. package/dist/lib/artisan/db.d.ts +37 -0
  22. package/dist/lib/artisan/db.d.ts.map +1 -0
  23. package/dist/lib/artisan/db.js +51 -0
  24. package/dist/lib/artisan/db.js.map +1 -0
  25. package/dist/lib/artisan/import.d.ts +16 -0
  26. package/dist/lib/artisan/import.d.ts.map +1 -0
  27. package/dist/lib/artisan/import.js +447 -0
  28. package/dist/lib/artisan/import.js.map +1 -0
  29. package/dist/lib/artisan/index.d.ts +9 -0
  30. package/dist/lib/artisan/index.d.ts.map +1 -0
  31. package/dist/lib/artisan/index.js +7 -0
  32. package/dist/lib/artisan/index.js.map +1 -0
  33. package/dist/lib/artisan/parser.d.ts +19 -0
  34. package/dist/lib/artisan/parser.d.ts.map +1 -0
  35. package/dist/lib/artisan/parser.js +376 -0
  36. package/dist/lib/artisan/parser.js.map +1 -0
  37. package/dist/lib/artisan/temperature.d.ts +52 -0
  38. package/dist/lib/artisan/temperature.d.ts.map +1 -0
  39. package/dist/lib/artisan/temperature.js +101 -0
  40. package/dist/lib/artisan/temperature.js.map +1 -0
  41. package/dist/lib/artisan/types.d.ts +195 -0
  42. package/dist/lib/artisan/types.d.ts.map +1 -0
  43. package/dist/lib/artisan/types.js +35 -0
  44. package/dist/lib/artisan/types.js.map +1 -0
  45. package/dist/lib/artisan/validator.d.ts +14 -0
  46. package/dist/lib/artisan/validator.d.ts.map +1 -0
  47. package/dist/lib/artisan/validator.js +228 -0
  48. package/dist/lib/artisan/validator.js.map +1 -0
  49. package/dist/lib/catalog.d.ts +87 -0
  50. package/dist/lib/catalog.d.ts.map +1 -0
  51. package/dist/lib/catalog.js +111 -0
  52. package/dist/lib/catalog.js.map +1 -0
  53. package/dist/lib/index.d.ts +6 -0
  54. package/dist/lib/index.d.ts.map +1 -0
  55. package/dist/lib/index.js +11 -0
  56. package/dist/lib/index.js.map +1 -0
  57. package/dist/lib/inventory.d.ts +80 -0
  58. package/dist/lib/inventory.d.ts.map +1 -0
  59. package/dist/lib/inventory.js +205 -0
  60. package/dist/lib/inventory.js.map +1 -0
  61. package/dist/lib/roast.d.ts +127 -0
  62. package/dist/lib/roast.d.ts.map +1 -0
  63. package/dist/lib/roast.js +284 -0
  64. package/dist/lib/roast.js.map +1 -0
  65. package/dist/lib/sales.d.ts +53 -0
  66. package/dist/lib/sales.d.ts.map +1 -0
  67. package/dist/lib/sales.js +155 -0
  68. package/dist/lib/sales.js.map +1 -0
  69. package/dist/lib/tasting.d.ts +76 -0
  70. package/dist/lib/tasting.d.ts.map +1 -0
  71. package/dist/lib/tasting.js +136 -0
  72. package/dist/lib/tasting.js.map +1 -0
  73. package/package.json +13 -2
@@ -0,0 +1,136 @@
1
+ import { z } from 'zod';
2
+ import { AuthError, PrvrsError } from './errors.js';
3
+ // ─── Zod schemas ──────────────────────────────────────────────────────────────
4
+ export const tastingFilterSchema = z.enum(['user', 'supplier', 'both']);
5
+ export const getTastingNotesSchema = z.object({
6
+ id: z.number().int().positive(),
7
+ filter: tastingFilterSchema.default('both'),
8
+ });
9
+ export const cuppingScoreSchema = z.number().int().min(1).max(5);
10
+ export const rateCoffeeSchema = z.object({
11
+ aroma: cuppingScoreSchema,
12
+ body: cuppingScoreSchema,
13
+ acidity: cuppingScoreSchema,
14
+ sweetness: cuppingScoreSchema,
15
+ aftertaste: cuppingScoreSchema,
16
+ brewMethod: z.string().optional(),
17
+ notes: z.string().optional(),
18
+ });
19
+ // ─── Validation helpers ───────────────────────────────────────────────────────
20
+ /** Validate a cupping score is an integer in [1, 5]. */
21
+ export function isValidCuppingScore(value) {
22
+ return Number.isInteger(value) && value >= 1 && value <= 5;
23
+ }
24
+ /** Parse and validate a cupping score flag value (for CLI use). */
25
+ export function parseCuppingScore(raw, flag) {
26
+ const n = parseInt(raw, 10);
27
+ if (isNaN(n) || !isValidCuppingScore(n)) {
28
+ throw new PrvrsError('INVALID_ARGUMENT', `--${flag} must be an integer between 1 and 5 (got "${raw}").`);
29
+ }
30
+ return n;
31
+ }
32
+ // ─── Pure lib functions ───────────────────────────────────────────────────────
33
+ /**
34
+ * Get tasting notes for a bean by coffee_catalog ID.
35
+ * Combines supplier notes from coffee_catalog with user notes from green_coffee_inv.
36
+ */
37
+ export async function getTastingNotes(supabase, userId, id, filter = 'both') {
38
+ getTastingNotesSchema.parse({ id, filter });
39
+ const result = {
40
+ beanId: id,
41
+ filter,
42
+ supplier: null,
43
+ user: null,
44
+ };
45
+ // ── Supplier notes (from coffee_catalog) ─────────────────────────
46
+ if (filter === 'supplier' || filter === 'both') {
47
+ const { data: catalogRow, error: catalogError } = await supabase
48
+ .from('coffee_catalog')
49
+ .select('id, name, processing, region, source, cupping_notes, ai_tasting_notes, ai_description')
50
+ .eq('id', id)
51
+ .single();
52
+ if (catalogError && catalogError.code !== 'PGRST116') {
53
+ throw catalogError;
54
+ }
55
+ if (catalogRow) {
56
+ result.supplier = {
57
+ source: 'supplier',
58
+ catalogId: catalogRow.id,
59
+ name: catalogRow.name ?? null,
60
+ processing: catalogRow.processing ?? null,
61
+ region: catalogRow.region ?? null,
62
+ cupping_notes: catalogRow.cupping_notes ?? null,
63
+ ai_tasting_notes: catalogRow.ai_tasting_notes ?? null,
64
+ ai_description: catalogRow.ai_description ?? null,
65
+ };
66
+ }
67
+ }
68
+ // ── User notes (from green_coffee_inv) ───────────────────────────
69
+ if (filter === 'user' || filter === 'both') {
70
+ const { data: invRows, error: invError } = await supabase
71
+ .from('green_coffee_inv')
72
+ .select('id, catalog_id, cupping_notes, notes')
73
+ .eq('catalog_id', id)
74
+ .eq('user', userId)
75
+ .order('id', { ascending: false })
76
+ .limit(1);
77
+ if (invError)
78
+ throw invError;
79
+ if (invRows && invRows.length > 0) {
80
+ const row = invRows[0];
81
+ result.user = {
82
+ source: 'user',
83
+ inventoryId: row.id,
84
+ catalogId: row.catalog_id ?? null,
85
+ cupping_notes: row.cupping_notes ?? null,
86
+ notes: row.notes ?? null,
87
+ };
88
+ }
89
+ }
90
+ return result;
91
+ }
92
+ /**
93
+ * Rate a coffee by storing cupping scores on the inventory item (must belong to userId).
94
+ */
95
+ export async function rateCoffee(supabase, userId, id, input) {
96
+ const parsed = rateCoffeeSchema.parse(input);
97
+ // Verify ownership of the inventory item
98
+ const { data: existing, error: fetchError } = await supabase
99
+ .from('green_coffee_inv')
100
+ .select('id, catalog_id')
101
+ .eq('id', id)
102
+ .eq('user', userId)
103
+ .single();
104
+ if (fetchError || !existing) {
105
+ throw new AuthError(`Inventory item ${id} not found or does not belong to you.`);
106
+ }
107
+ const cupping = {
108
+ aroma: parsed.aroma,
109
+ body: parsed.body,
110
+ acidity: parsed.acidity,
111
+ sweetness: parsed.sweetness,
112
+ aftertaste: parsed.aftertaste,
113
+ rated_at: new Date().toISOString(),
114
+ };
115
+ if (parsed.brewMethod !== undefined)
116
+ cupping.brew_method = parsed.brewMethod;
117
+ if (parsed.notes !== undefined)
118
+ cupping.notes = parsed.notes;
119
+ const { error: updateError } = await supabase
120
+ .from('green_coffee_inv')
121
+ .update({ cupping_notes: cupping })
122
+ .eq('id', id)
123
+ .eq('user', userId);
124
+ if (updateError)
125
+ throw updateError;
126
+ // Re-fetch the updated row
127
+ const { data, error } = await supabase
128
+ .from('green_coffee_inv')
129
+ .select('id, catalog_id, cupping_notes, notes, last_updated')
130
+ .eq('id', id)
131
+ .single();
132
+ if (error)
133
+ throw error;
134
+ return data;
135
+ }
136
+ //# sourceMappingURL=tasting.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tasting.js","sourceRoot":"","sources":["../../src/lib/tasting.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,11 +1,21 @@
1
1
  {
2
2
  "name": "@purveyors/cli",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "description": "The official CLI for purveyors.io — coffee intelligence from your terminal",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "purvey": "./dist/index.js"
8
8
  },
9
+ "exports": {
10
+ ".": "./dist/index.js",
11
+ "./catalog": "./dist/lib/catalog.js",
12
+ "./inventory": "./dist/lib/inventory.js",
13
+ "./roast": "./dist/lib/roast.js",
14
+ "./sales": "./dist/lib/sales.js",
15
+ "./tasting": "./dist/lib/tasting.js",
16
+ "./lib": "./dist/lib/index.js",
17
+ "./artisan": "./dist/lib/artisan/index.js"
18
+ },
9
19
  "files": [
10
20
  "dist",
11
21
  "README.md",
@@ -42,7 +52,8 @@
42
52
  "@supabase/supabase-js": "^2.49.1",
43
53
  "chalk": "^5.4.1",
44
54
  "commander": "^13.1.0",
45
- "ora": "^8.2.0"
55
+ "ora": "^8.2.0",
56
+ "zod": "^4.3.6"
46
57
  },
47
58
  "devDependencies": {
48
59
  "@eslint/eslintrc": "^3.3.0",