@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.
- package/LICENSE.md +53 -0
- package/README.md +175 -0
- package/dist/commands/auth.d.ts +6 -0
- package/dist/commands/auth.d.ts.map +1 -0
- package/dist/commands/auth.js +193 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/catalog.d.ts +63 -0
- package/dist/commands/catalog.d.ts.map +1 -0
- package/dist/commands/catalog.js +132 -0
- package/dist/commands/catalog.js.map +1 -0
- package/dist/commands/inventory.d.ts +32 -0
- package/dist/commands/inventory.d.ts.map +1 -0
- package/dist/commands/inventory.js +287 -0
- package/dist/commands/inventory.js.map +1 -0
- package/dist/commands/roast.d.ts +48 -0
- package/dist/commands/roast.d.ts.map +1 -0
- package/dist/commands/roast.js +225 -0
- package/dist/commands/roast.js.map +1 -0
- package/dist/commands/sales.d.ts +17 -0
- package/dist/commands/sales.d.ts.map +1 -0
- package/dist/commands/sales.js +226 -0
- package/dist/commands/sales.js.map +1 -0
- package/dist/commands/tasting.d.ts +46 -0
- package/dist/commands/tasting.d.ts.map +1 -0
- package/dist/commands/tasting.js +174 -0
- package/dist/commands/tasting.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +68 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/config.d.ts +21 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +45 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/errors.d.ts +20 -0
- package/dist/lib/errors.d.ts.map +1 -0
- package/dist/lib/errors.js +58 -0
- package/dist/lib/errors.js.map +1 -0
- package/dist/lib/output.d.ts +22 -0
- package/dist/lib/output.d.ts.map +1 -0
- package/dist/lib/output.js +86 -0
- package/dist/lib/output.js.map +1 -0
- package/dist/lib/prompts.d.ts +11 -0
- package/dist/lib/prompts.d.ts.map +1 -0
- package/dist/lib/prompts.js +22 -0
- package/dist/lib/prompts.js.map +1 -0
- package/dist/lib/supabase.d.ts +22 -0
- package/dist/lib/supabase.d.ts.map +1 -0
- package/dist/lib/supabase.js +87 -0
- package/dist/lib/supabase.js.map +1 -0
- package/dist/types/database.types.d.ts +13 -0
- package/dist/types/database.types.d.ts.map +1 -0
- package/dist/types/database.types.js +8 -0
- package/dist/types/database.types.js.map +1 -0
- package/dist/types/index.d.ts +28 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +5 -0
- package/dist/types/index.js.map +1 -0
- 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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"}
|
package/dist/index.d.ts
ADDED
|
@@ -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"}
|