@purveyors/cli 0.2.1 → 0.4.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/dist/commands/catalog.d.ts +4 -56
- package/dist/commands/catalog.d.ts.map +1 -1
- package/dist/commands/catalog.js +14 -81
- package/dist/commands/catalog.js.map +1 -1
- package/dist/commands/config.d.ts +7 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +82 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/inventory.d.ts +2 -25
- package/dist/commands/inventory.d.ts.map +1 -1
- package/dist/commands/inventory.js +109 -149
- package/dist/commands/inventory.js.map +1 -1
- package/dist/commands/roast.d.ts +2 -41
- package/dist/commands/roast.d.ts.map +1 -1
- package/dist/commands/roast.js +445 -126
- package/dist/commands/roast.js.map +1 -1
- package/dist/commands/sales.d.ts +2 -10
- package/dist/commands/sales.d.ts.map +1 -1
- package/dist/commands/sales.js +100 -112
- package/dist/commands/sales.js.map +1 -1
- package/dist/commands/tasting.d.ts +4 -38
- package/dist/commands/tasting.d.ts.map +1 -1
- package/dist/commands/tasting.js +93 -116
- package/dist/commands/tasting.js.map +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/ai.d.ts +36 -0
- package/dist/lib/ai.d.ts.map +1 -0
- package/dist/lib/ai.js +38 -0
- package/dist/lib/ai.js.map +1 -0
- package/dist/lib/artisan/db.d.ts +37 -0
- package/dist/lib/artisan/db.d.ts.map +1 -0
- package/dist/lib/artisan/db.js +51 -0
- package/dist/lib/artisan/db.js.map +1 -0
- package/dist/lib/artisan/import.d.ts +16 -0
- package/dist/lib/artisan/import.d.ts.map +1 -0
- package/dist/lib/artisan/import.js +447 -0
- package/dist/lib/artisan/import.js.map +1 -0
- package/dist/lib/artisan/index.d.ts +9 -0
- package/dist/lib/artisan/index.d.ts.map +1 -0
- package/dist/lib/artisan/index.js +7 -0
- package/dist/lib/artisan/index.js.map +1 -0
- package/dist/lib/artisan/parser.d.ts +19 -0
- package/dist/lib/artisan/parser.d.ts.map +1 -0
- package/dist/lib/artisan/parser.js +376 -0
- package/dist/lib/artisan/parser.js.map +1 -0
- package/dist/lib/artisan/temperature.d.ts +52 -0
- package/dist/lib/artisan/temperature.d.ts.map +1 -0
- package/dist/lib/artisan/temperature.js +101 -0
- package/dist/lib/artisan/temperature.js.map +1 -0
- package/dist/lib/artisan/types.d.ts +195 -0
- package/dist/lib/artisan/types.d.ts.map +1 -0
- package/dist/lib/artisan/types.js +35 -0
- package/dist/lib/artisan/types.js.map +1 -0
- package/dist/lib/artisan/validator.d.ts +14 -0
- package/dist/lib/artisan/validator.d.ts.map +1 -0
- package/dist/lib/artisan/validator.js +228 -0
- package/dist/lib/artisan/validator.js.map +1 -0
- package/dist/lib/catalog.d.ts +87 -0
- package/dist/lib/catalog.d.ts.map +1 -0
- package/dist/lib/catalog.js +111 -0
- package/dist/lib/catalog.js.map +1 -0
- package/dist/lib/config.d.ts +26 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +59 -0
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/index.d.ts +6 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/index.js +11 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/lib/interactive/forms.d.ts +33 -0
- package/dist/lib/interactive/forms.d.ts.map +1 -0
- package/dist/lib/interactive/forms.js +139 -0
- package/dist/lib/interactive/forms.js.map +1 -0
- package/dist/lib/interactive/watch.d.ts +66 -0
- package/dist/lib/interactive/watch.d.ts.map +1 -0
- package/dist/lib/interactive/watch.js +494 -0
- package/dist/lib/interactive/watch.js.map +1 -0
- package/dist/lib/inventory.d.ts +80 -0
- package/dist/lib/inventory.d.ts.map +1 -0
- package/dist/lib/inventory.js +205 -0
- package/dist/lib/inventory.js.map +1 -0
- package/dist/lib/roast.d.ts +127 -0
- package/dist/lib/roast.d.ts.map +1 -0
- package/dist/lib/roast.js +284 -0
- package/dist/lib/roast.js.map +1 -0
- package/dist/lib/sales.d.ts +53 -0
- package/dist/lib/sales.d.ts.map +1 -0
- package/dist/lib/sales.js +155 -0
- package/dist/lib/sales.js.map +1 -0
- package/dist/lib/tasting.d.ts +76 -0
- package/dist/lib/tasting.d.ts.map +1 -0
- package/dist/lib/tasting.js +136 -0
- package/dist/lib/tasting.js.map +1 -0
- package/package.json +15 -2
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
// ─── Zod schemas ──────────────────────────────────────────────────────────────
|
|
3
|
+
export const searchCatalogSchema = z.object({
|
|
4
|
+
origin: z.string().optional(),
|
|
5
|
+
process: z.string().optional(),
|
|
6
|
+
priceMin: z.number().optional(),
|
|
7
|
+
priceMax: z.number().optional(),
|
|
8
|
+
flavor: z.string().optional(),
|
|
9
|
+
stocked: z.boolean().optional(),
|
|
10
|
+
limit: z.number().int().min(1).default(10),
|
|
11
|
+
});
|
|
12
|
+
export const getCatalogSchema = z.object({
|
|
13
|
+
id: z.number().int().positive(),
|
|
14
|
+
});
|
|
15
|
+
export const getCatalogStatsSchema = z.object({});
|
|
16
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
17
|
+
/**
|
|
18
|
+
* Strip PostgREST special characters from user-supplied filter values.
|
|
19
|
+
* Prevents injection into .or() filter strings where values are interpolated directly.
|
|
20
|
+
* Removes: ( ) , . * % that have meaning in PostgREST filter syntax.
|
|
21
|
+
*/
|
|
22
|
+
export function sanitizeFilterValue(value) {
|
|
23
|
+
return value.replace(/[(),.*%]/g, '');
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Aggregate stats from an array of catalog items.
|
|
27
|
+
* Pure function — no I/O, safe to unit test.
|
|
28
|
+
*/
|
|
29
|
+
export function computeCatalogStats(items) {
|
|
30
|
+
const stocked = items.filter((i) => i.stocked === true).length;
|
|
31
|
+
const byOrigin = {};
|
|
32
|
+
for (const item of items) {
|
|
33
|
+
const key = item.country ?? item.continent ?? 'Unknown';
|
|
34
|
+
byOrigin[key] = (byOrigin[key] ?? 0) + 1;
|
|
35
|
+
}
|
|
36
|
+
const prices = items.map((i) => i.cost_lb).filter((p) => p !== null);
|
|
37
|
+
const avgPricePerLb = prices.length > 0
|
|
38
|
+
? Math.round((prices.reduce((a, b) => a + b, 0) / prices.length) * 100) / 100
|
|
39
|
+
: null;
|
|
40
|
+
const priceRange = {
|
|
41
|
+
min: prices.length > 0 ? Math.min(...prices) : null,
|
|
42
|
+
max: prices.length > 0 ? Math.max(...prices) : null,
|
|
43
|
+
};
|
|
44
|
+
return { total: items.length, stocked, byOrigin, avgPricePerLb, priceRange };
|
|
45
|
+
}
|
|
46
|
+
// ─── Pure lib functions ───────────────────────────────────────────────────────
|
|
47
|
+
/**
|
|
48
|
+
* Search the coffee catalog with optional filters.
|
|
49
|
+
*/
|
|
50
|
+
export async function searchCatalog(supabase, opts) {
|
|
51
|
+
const parsed = searchCatalogSchema.parse(opts);
|
|
52
|
+
let query = supabase.from('coffee_catalog').select('*');
|
|
53
|
+
if (parsed.origin) {
|
|
54
|
+
const o = sanitizeFilterValue(parsed.origin);
|
|
55
|
+
query = query.or(`country.ilike.%${o}%,continent.ilike.%${o}%,region.ilike.%${o}%`);
|
|
56
|
+
}
|
|
57
|
+
if (parsed.process) {
|
|
58
|
+
const p = sanitizeFilterValue(parsed.process);
|
|
59
|
+
query = query.ilike('processing', `%${p}%`);
|
|
60
|
+
}
|
|
61
|
+
if (parsed.priceMin !== undefined) {
|
|
62
|
+
query = query.gte('cost_lb', parsed.priceMin);
|
|
63
|
+
}
|
|
64
|
+
if (parsed.priceMax !== undefined) {
|
|
65
|
+
query = query.lte('cost_lb', parsed.priceMax);
|
|
66
|
+
}
|
|
67
|
+
if (parsed.flavor) {
|
|
68
|
+
const keywords = parsed.flavor
|
|
69
|
+
.split(',')
|
|
70
|
+
.map((k) => sanitizeFilterValue(k.trim()))
|
|
71
|
+
.filter(Boolean);
|
|
72
|
+
const flavorFilters = keywords
|
|
73
|
+
.flatMap((kw) => [
|
|
74
|
+
`description_short.ilike.%${kw}%`,
|
|
75
|
+
`description_long.ilike.%${kw}%`,
|
|
76
|
+
`cupping_notes.ilike.%${kw}%`,
|
|
77
|
+
`farm_notes.ilike.%${kw}%`,
|
|
78
|
+
])
|
|
79
|
+
.join(',');
|
|
80
|
+
query = query.or(flavorFilters);
|
|
81
|
+
}
|
|
82
|
+
if (parsed.stocked) {
|
|
83
|
+
query = query.eq('stocked', true);
|
|
84
|
+
}
|
|
85
|
+
const { data, error } = await query.limit(parsed.limit);
|
|
86
|
+
if (error)
|
|
87
|
+
throw error;
|
|
88
|
+
return (data ?? []);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Fetch a single catalog item by ID.
|
|
92
|
+
*/
|
|
93
|
+
export async function getCatalog(supabase, id) {
|
|
94
|
+
getCatalogSchema.parse({ id });
|
|
95
|
+
const { data, error } = await supabase.from('coffee_catalog').select('*').eq('id', id).single();
|
|
96
|
+
if (error)
|
|
97
|
+
throw error;
|
|
98
|
+
return data;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Fetch aggregate statistics for the coffee catalog.
|
|
102
|
+
*/
|
|
103
|
+
export async function getCatalogStats(supabase) {
|
|
104
|
+
const { data, error } = await supabase
|
|
105
|
+
.from('coffee_catalog')
|
|
106
|
+
.select('id, country, continent, cost_lb, stocked');
|
|
107
|
+
if (error)
|
|
108
|
+
throw error;
|
|
109
|
+
return computeCatalogStats((data ?? []));
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=catalog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalog.js","sourceRoot":"","sources":["../../src/lib/catalog.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/config.d.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import type { StoredCredentials } from '../types/index.js';
|
|
2
2
|
declare const CONFIG_DIR: string;
|
|
3
3
|
declare const CREDENTIALS_FILE: string;
|
|
4
|
+
export interface PurveyConfig {
|
|
5
|
+
'form-mode'?: boolean;
|
|
6
|
+
}
|
|
7
|
+
/** All valid config keys and their accepted value types. */
|
|
8
|
+
declare const CONFIG_KEYS: readonly ["form-mode"];
|
|
9
|
+
export type ConfigKey = (typeof CONFIG_KEYS)[number];
|
|
10
|
+
export declare function isValidConfigKey(key: string): key is ConfigKey;
|
|
4
11
|
/**
|
|
5
12
|
* Ensure the config directory exists with secure permissions.
|
|
6
13
|
*/
|
|
@@ -18,5 +25,24 @@ export declare function writeCredentials(creds: StoredCredentials): Promise<void
|
|
|
18
25
|
* Delete stored credentials (logout).
|
|
19
26
|
*/
|
|
20
27
|
export declare function deleteCredentials(): Promise<void>;
|
|
28
|
+
/**
|
|
29
|
+
* Read the purvey app config from ~/.config/purvey/config.json.
|
|
30
|
+
* Returns an empty config object if the file does not exist.
|
|
31
|
+
*/
|
|
32
|
+
export declare function readConfig(): Promise<PurveyConfig>;
|
|
33
|
+
/**
|
|
34
|
+
* Write the purvey app config to ~/.config/purvey/config.json.
|
|
35
|
+
*/
|
|
36
|
+
export declare function writeConfig(config: PurveyConfig): Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* Get a single config value by key.
|
|
39
|
+
* Returns undefined if the key is not set.
|
|
40
|
+
*/
|
|
41
|
+
export declare function getConfigValue(key: string): Promise<string | undefined>;
|
|
42
|
+
/**
|
|
43
|
+
* Set a single config value by key.
|
|
44
|
+
* Throws if the key is invalid or the value cannot be coerced to the expected type.
|
|
45
|
+
*/
|
|
46
|
+
export declare function setConfigValue(key: string, value: string): Promise<void>;
|
|
21
47
|
export { CONFIG_DIR, CREDENTIALS_FILE };
|
|
22
48
|
//# sourceMappingURL=config.d.ts.map
|
package/dist/lib/config.d.ts.map
CHANGED
|
@@ -1 +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,QAAuC,CAAC;AACxD,QAAA,MAAM,gBAAgB,QAAuC,CAAC;
|
|
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,QAAuC,CAAC;AACxD,QAAA,MAAM,gBAAgB,QAAuC,CAAC;AAS9D,MAAM,WAAW,YAAY;IAC3B,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED,4DAA4D;AAC5D,QAAA,MAAM,WAAW,wBAAyB,CAAC;AAC3C,MAAM,MAAM,SAAS,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC;AAErD,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,IAAI,SAAS,CAE9D;AAID;;GAEG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAErD;AAED;;;GAGG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAqBzE;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;AAID;;;GAGG;AACH,wBAAsB,UAAU,IAAI,OAAO,CAAC,YAAY,CAAC,CAQxD;AAED;;GAEG;AACH,wBAAsB,WAAW,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAGrE;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAM7E;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAe9E;AAED,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,CAAC"}
|
package/dist/lib/config.js
CHANGED
|
@@ -4,9 +4,16 @@ import { mkdir, readFile, writeFile, unlink, access } from 'fs/promises';
|
|
|
4
4
|
import { constants } from 'fs';
|
|
5
5
|
const CONFIG_DIR = join(homedir(), '.config', 'purvey');
|
|
6
6
|
const CREDENTIALS_FILE = join(CONFIG_DIR, 'credentials.json');
|
|
7
|
+
const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
|
|
7
8
|
// Legacy path from v0.1.x (was ~/.config/prvrs/)
|
|
8
9
|
const LEGACY_CONFIG_DIR = join(homedir(), '.config', 'prvrs');
|
|
9
10
|
const LEGACY_CREDENTIALS_FILE = join(LEGACY_CONFIG_DIR, 'credentials.json');
|
|
11
|
+
/** All valid config keys and their accepted value types. */
|
|
12
|
+
const CONFIG_KEYS = ['form-mode'];
|
|
13
|
+
export function isValidConfigKey(key) {
|
|
14
|
+
return CONFIG_KEYS.includes(key);
|
|
15
|
+
}
|
|
16
|
+
// ─── Credentials ─────────────────────────────────────────────────────────────
|
|
10
17
|
/**
|
|
11
18
|
* Ensure the config directory exists with secure permissions.
|
|
12
19
|
*/
|
|
@@ -59,5 +66,57 @@ export async function deleteCredentials() {
|
|
|
59
66
|
// Already gone — that's fine
|
|
60
67
|
}
|
|
61
68
|
}
|
|
69
|
+
// ─── App config read/write ────────────────────────────────────────────────────
|
|
70
|
+
/**
|
|
71
|
+
* Read the purvey app config from ~/.config/purvey/config.json.
|
|
72
|
+
* Returns an empty config object if the file does not exist.
|
|
73
|
+
*/
|
|
74
|
+
export async function readConfig() {
|
|
75
|
+
try {
|
|
76
|
+
await access(CONFIG_FILE, constants.R_OK);
|
|
77
|
+
const raw = await readFile(CONFIG_FILE, 'utf-8');
|
|
78
|
+
return JSON.parse(raw);
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return {};
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Write the purvey app config to ~/.config/purvey/config.json.
|
|
86
|
+
*/
|
|
87
|
+
export async function writeConfig(config) {
|
|
88
|
+
await ensureConfigDir();
|
|
89
|
+
await writeFile(CONFIG_FILE, JSON.stringify(config, null, 2) + '\n', { mode: 0o600 });
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Get a single config value by key.
|
|
93
|
+
* Returns undefined if the key is not set.
|
|
94
|
+
*/
|
|
95
|
+
export async function getConfigValue(key) {
|
|
96
|
+
if (!isValidConfigKey(key))
|
|
97
|
+
return undefined;
|
|
98
|
+
const config = await readConfig();
|
|
99
|
+
const value = config[key];
|
|
100
|
+
if (value === undefined)
|
|
101
|
+
return undefined;
|
|
102
|
+
return String(value);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Set a single config value by key.
|
|
106
|
+
* Throws if the key is invalid or the value cannot be coerced to the expected type.
|
|
107
|
+
*/
|
|
108
|
+
export async function setConfigValue(key, value) {
|
|
109
|
+
if (!isValidConfigKey(key)) {
|
|
110
|
+
throw new Error(`Unknown config key: "${key}". Valid keys: ${CONFIG_KEYS.join(', ')}.`);
|
|
111
|
+
}
|
|
112
|
+
const config = await readConfig();
|
|
113
|
+
if (key === 'form-mode') {
|
|
114
|
+
if (value !== 'true' && value !== 'false') {
|
|
115
|
+
throw new Error(`"form-mode" must be "true" or "false".`);
|
|
116
|
+
}
|
|
117
|
+
config['form-mode'] = value === 'true';
|
|
118
|
+
}
|
|
119
|
+
await writeConfig(config);
|
|
120
|
+
}
|
|
62
121
|
export { CONFIG_DIR, CREDENTIALS_FILE };
|
|
63
122
|
//# sourceMappingURL=config.js.map
|
package/dist/lib/config.js.map
CHANGED
|
@@ -1 +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,QAAQ,CAAC,CAAC;AACxD,MAAM,gBAAgB,GAAG,IAAI,CAAC,UAAU,EAAE,kBAAkB,CAAC,CAAC;
|
|
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,QAAQ,CAAC,CAAC;AACxD,MAAM,gBAAgB,GAAG,IAAI,CAAC,UAAU,EAAE,kBAAkB,CAAC,CAAC;AAC9D,MAAM,WAAW,GAAG,IAAI,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC;AAEpD,iDAAiD;AACjD,MAAM,iBAAiB,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;AAC9D,MAAM,uBAAuB,GAAG,IAAI,CAAC,iBAAiB,EAAE,kBAAkB,CAAC,CAAC;AAQ5E,4DAA4D;AAC5D,MAAM,WAAW,GAAG,CAAC,WAAW,CAAU,CAAC;AAG3C,MAAM,UAAU,gBAAgB,CAAC,GAAW;IAC1C,OAAQ,WAAiC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;AAC1D,CAAC;AAED,gFAAgF;AAEhF;;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;;;GAGG;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,uEAAuE;QACvE,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,uBAAuB,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC;YACtD,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,uBAAuB,EAAE,OAAO,CAAC,CAAC;YAC7D,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAsB,CAAC;YACnD,sBAAsB;YACtB,MAAM,gBAAgB,CAAC,KAAK,CAAC,CAAC;YAC9B,kCAAkC;YAClC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,CAAC;YAC3D,MAAM,UAAU,CAAC,uBAAuB,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;YAC1D,OAAO,KAAK,CAAC;QACf,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;IACH,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,iFAAiF;AAEjF;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU;IAC9B,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,WAAW,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC;QAC1C,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;QACjD,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAiB,CAAC;IACzC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,MAAoB;IACpD,MAAM,eAAe,EAAE,CAAC;IACxB,MAAM,SAAS,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;AACxF,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,GAAW;IAC9C,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC;QAAE,OAAO,SAAS,CAAC;IAC7C,MAAM,MAAM,GAAG,MAAM,UAAU,EAAE,CAAC;IAClC,MAAM,KAAK,GAAG,MAAM,CAAC,GAAgB,CAAC,CAAC;IACvC,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAC1C,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;AACvB,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,GAAW,EAAE,KAAa;IAC7D,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CAAC,wBAAwB,GAAG,kBAAkB,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC1F,CAAC;IAED,MAAM,MAAM,GAAG,MAAM,UAAU,EAAE,CAAC;IAElC,IAAI,GAAG,KAAK,WAAW,EAAE,CAAC;QACxB,IAAI,KAAK,KAAK,MAAM,IAAI,KAAK,KAAK,OAAO,EAAE,CAAC;YAC1C,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;QAC5D,CAAC;QACD,MAAM,CAAC,WAAW,CAAC,GAAG,KAAK,KAAK,MAAM,CAAC;IACzC,CAAC;IAED,MAAM,WAAW,CAAC,MAAM,CAAC,CAAC;AAC5B,CAAC;AAED,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/lib/index.ts"],"names":[],"mappings":"AAMA,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Barrel export — re-exports all lib modules for convenience.
|
|
2
|
+
// Usage: import { searchCatalog, listInventory } from '@purveyors/cli/lib'
|
|
3
|
+
// Or use individual subpath imports:
|
|
4
|
+
// import { searchCatalog } from '@purveyors/cli/catalog'
|
|
5
|
+
// import { listInventory } from '@purveyors/cli/inventory'
|
|
6
|
+
export * from './catalog.js';
|
|
7
|
+
export * from './inventory.js';
|
|
8
|
+
export * from './roast.js';
|
|
9
|
+
export * from './sales.js';
|
|
10
|
+
export * from './tasting.js';
|
|
11
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/lib/index.ts"],"names":[],"mappings":"AAAA,8DAA8D;AAC9D,2EAA2E;AAC3E,qCAAqC;AACrC,2DAA2D;AAC3D,6DAA6D;AAE7D,cAAc,cAAc,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive form helpers for `purvey` write commands.
|
|
3
|
+
* CLI-only — not exported via package.json subpaths.
|
|
4
|
+
* All functions use @clack/prompts for step-by-step TUI wizards.
|
|
5
|
+
*/
|
|
6
|
+
import type { SupabaseClient } from '@supabase/supabase-js';
|
|
7
|
+
/** Call after any clack result to abort cleanly on Ctrl+C / Escape. */
|
|
8
|
+
export declare function guardCancel(result: unknown): void;
|
|
9
|
+
/**
|
|
10
|
+
* Interactive bean picker — shows user's inventory beans and lets them select one.
|
|
11
|
+
* Returns the selected bean's ID and name.
|
|
12
|
+
*/
|
|
13
|
+
export declare function pickBean(supabase: SupabaseClient, userId: string): Promise<{
|
|
14
|
+
id: number;
|
|
15
|
+
name: string;
|
|
16
|
+
}>;
|
|
17
|
+
/**
|
|
18
|
+
* Interactive roast picker — shows user's roast profiles, lets them select one.
|
|
19
|
+
* Returns the selected roast's ID and batch name.
|
|
20
|
+
*/
|
|
21
|
+
export declare function pickRoast(supabase: SupabaseClient, userId: string): Promise<{
|
|
22
|
+
id: number;
|
|
23
|
+
batchName: string;
|
|
24
|
+
}>;
|
|
25
|
+
/**
|
|
26
|
+
* Interactive catalog search — user types search term, sees matching coffees.
|
|
27
|
+
* Returns selected catalog item ID and name.
|
|
28
|
+
*/
|
|
29
|
+
export declare function pickCatalogItem(supabase: SupabaseClient): Promise<{
|
|
30
|
+
id: number;
|
|
31
|
+
name: string;
|
|
32
|
+
}>;
|
|
33
|
+
//# sourceMappingURL=forms.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"forms.d.ts","sourceRoot":"","sources":["../../../src/lib/interactive/forms.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAI5D,uEAAuE;AACvE,wBAAgB,WAAW,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAKjD;AAID;;;GAGG;AACH,wBAAsB,QAAQ,CAC5B,QAAQ,EAAE,cAAc,EACxB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CA4CvC;AAED;;;GAGG;AACH,wBAAsB,SAAS,CAC7B,QAAQ,EAAE,cAAc,EACxB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,CAuC5C;AAED;;;GAGG;AACH,wBAAsB,eAAe,CACnC,QAAQ,EAAE,cAAc,GACvB,OAAO,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,CAiEvC"}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive form helpers for `purvey` write commands.
|
|
3
|
+
* CLI-only — not exported via package.json subpaths.
|
|
4
|
+
* All functions use @clack/prompts for step-by-step TUI wizards.
|
|
5
|
+
*/
|
|
6
|
+
import * as p from '@clack/prompts';
|
|
7
|
+
// ─── Cancel guard ─────────────────────────────────────────────────────────────
|
|
8
|
+
/** Call after any clack result to abort cleanly on Ctrl+C / Escape. */
|
|
9
|
+
export function guardCancel(result) {
|
|
10
|
+
if (p.isCancel(result)) {
|
|
11
|
+
p.cancel('Cancelled.');
|
|
12
|
+
process.exit(0);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
// ─── Pickers ──────────────────────────────────────────────────────────────────
|
|
16
|
+
/**
|
|
17
|
+
* Interactive bean picker — shows user's inventory beans and lets them select one.
|
|
18
|
+
* Returns the selected bean's ID and name.
|
|
19
|
+
*/
|
|
20
|
+
export async function pickBean(supabase, userId) {
|
|
21
|
+
const { data, error } = await supabase
|
|
22
|
+
.from('green_coffee_inv')
|
|
23
|
+
.select('id, coffee_catalog!catalog_id (name)')
|
|
24
|
+
.eq('user', userId)
|
|
25
|
+
.eq('stocked', true)
|
|
26
|
+
.order('id', { ascending: false })
|
|
27
|
+
.limit(50);
|
|
28
|
+
if (error)
|
|
29
|
+
throw error;
|
|
30
|
+
const rows = (data ?? []);
|
|
31
|
+
if (rows.length === 0) {
|
|
32
|
+
p.cancel('No stocked beans found in your inventory. Add some first.');
|
|
33
|
+
process.exit(0);
|
|
34
|
+
}
|
|
35
|
+
const options = rows.map((row) => {
|
|
36
|
+
const catalog = Array.isArray(row.coffee_catalog)
|
|
37
|
+
? (row.coffee_catalog[0] ?? null)
|
|
38
|
+
: row.coffee_catalog;
|
|
39
|
+
const name = catalog?.name ?? `Bean #${row.id}`;
|
|
40
|
+
return { value: String(row.id), label: `${name} (#${row.id})`, hint: name };
|
|
41
|
+
});
|
|
42
|
+
const selected = await p.select({
|
|
43
|
+
message: 'Select a bean from your inventory',
|
|
44
|
+
options,
|
|
45
|
+
});
|
|
46
|
+
guardCancel(selected);
|
|
47
|
+
const selectedId = parseInt(selected, 10);
|
|
48
|
+
const matchedRow = rows.find((r) => r.id === selectedId);
|
|
49
|
+
const catalog = Array.isArray(matchedRow?.coffee_catalog)
|
|
50
|
+
? (matchedRow?.coffee_catalog[0] ?? null)
|
|
51
|
+
: (matchedRow?.coffee_catalog ?? null);
|
|
52
|
+
const name = catalog?.name ?? `Bean #${selectedId}`;
|
|
53
|
+
return { id: selectedId, name };
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Interactive roast picker — shows user's roast profiles, lets them select one.
|
|
57
|
+
* Returns the selected roast's ID and batch name.
|
|
58
|
+
*/
|
|
59
|
+
export async function pickRoast(supabase, userId) {
|
|
60
|
+
const { data, error } = await supabase
|
|
61
|
+
.from('roast_profiles')
|
|
62
|
+
.select('roast_id, batch_name, roast_date')
|
|
63
|
+
.eq('user', userId)
|
|
64
|
+
.order('roast_date', { ascending: false })
|
|
65
|
+
.limit(50);
|
|
66
|
+
if (error)
|
|
67
|
+
throw error;
|
|
68
|
+
const rows = (data ?? []);
|
|
69
|
+
if (rows.length === 0) {
|
|
70
|
+
p.cancel('No roast profiles found. Create one first.');
|
|
71
|
+
process.exit(0);
|
|
72
|
+
}
|
|
73
|
+
const options = rows.map((row) => {
|
|
74
|
+
const label = row.batch_name ?? `Roast #${row.roast_id}`;
|
|
75
|
+
const hint = row.roast_date ?? undefined;
|
|
76
|
+
return { value: String(row.roast_id), label, hint };
|
|
77
|
+
});
|
|
78
|
+
const selected = await p.select({
|
|
79
|
+
message: 'Select a roast profile',
|
|
80
|
+
options,
|
|
81
|
+
});
|
|
82
|
+
guardCancel(selected);
|
|
83
|
+
const selectedId = parseInt(selected, 10);
|
|
84
|
+
const matchedRow = rows.find((r) => r.roast_id === selectedId);
|
|
85
|
+
const batchName = matchedRow?.batch_name ?? `Roast #${selectedId}`;
|
|
86
|
+
return { id: selectedId, batchName };
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Interactive catalog search — user types search term, sees matching coffees.
|
|
90
|
+
* Returns selected catalog item ID and name.
|
|
91
|
+
*/
|
|
92
|
+
export async function pickCatalogItem(supabase) {
|
|
93
|
+
const searchTerm = await p.text({
|
|
94
|
+
message: 'Search coffee catalog (origin, name, or flavor)',
|
|
95
|
+
placeholder: 'e.g. Ethiopia, natural, berry',
|
|
96
|
+
validate: (v) => {
|
|
97
|
+
if (!v || v.trim().length === 0)
|
|
98
|
+
return 'Please enter a search term.';
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
guardCancel(searchTerm);
|
|
102
|
+
const term = searchTerm.trim();
|
|
103
|
+
const safe = term.replace(/[(),.*%]/g, '');
|
|
104
|
+
const { data, error } = await supabase
|
|
105
|
+
.from('coffee_catalog')
|
|
106
|
+
.select('id, name, country, processing, cost_lb')
|
|
107
|
+
.or([
|
|
108
|
+
`name.ilike.%${safe}%`,
|
|
109
|
+
`country.ilike.%${safe}%`,
|
|
110
|
+
`continent.ilike.%${safe}%`,
|
|
111
|
+
`region.ilike.%${safe}%`,
|
|
112
|
+
`cupping_notes.ilike.%${safe}%`,
|
|
113
|
+
`description_short.ilike.%${safe}%`,
|
|
114
|
+
].join(','))
|
|
115
|
+
.limit(20);
|
|
116
|
+
if (error)
|
|
117
|
+
throw error;
|
|
118
|
+
const rows = (data ?? []);
|
|
119
|
+
if (rows.length === 0) {
|
|
120
|
+
p.cancel(`No coffees found matching "${term}". Try a different search.`);
|
|
121
|
+
process.exit(0);
|
|
122
|
+
}
|
|
123
|
+
const options = rows.map((row) => {
|
|
124
|
+
const label = row.name ?? `Catalog #${row.id}`;
|
|
125
|
+
const parts = [row.country, row.processing, row.cost_lb ? `$${row.cost_lb}/lb` : null].filter(Boolean);
|
|
126
|
+
const hint = parts.join(' · ') || undefined;
|
|
127
|
+
return { value: String(row.id), label, hint };
|
|
128
|
+
});
|
|
129
|
+
const selected = await p.select({
|
|
130
|
+
message: 'Select a coffee',
|
|
131
|
+
options,
|
|
132
|
+
});
|
|
133
|
+
guardCancel(selected);
|
|
134
|
+
const selectedId = parseInt(selected, 10);
|
|
135
|
+
const matchedRow = rows.find((r) => r.id === selectedId);
|
|
136
|
+
const name = matchedRow?.name ?? `Catalog #${selectedId}`;
|
|
137
|
+
return { id: selectedId, name };
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=forms.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"forms.js","sourceRoot":"","sources":["../../../src/lib/interactive/forms.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Watch module for `purvey roast watch`.
|
|
3
|
+
* CLI-only — not exported via package.json subpaths.
|
|
4
|
+
*
|
|
5
|
+
* Monitors a directory for new .alog files, imports them automatically,
|
|
6
|
+
* and shows a verification table when the session ends (Ctrl+C).
|
|
7
|
+
*/
|
|
8
|
+
import type { SupabaseClient } from '@supabase/supabase-js';
|
|
9
|
+
import type { MilestoneData, ProcessedRoastData } from '../artisan/types.js';
|
|
10
|
+
export interface WatchSession {
|
|
11
|
+
directory: string;
|
|
12
|
+
coffeeId: number;
|
|
13
|
+
coffeeName: string;
|
|
14
|
+
batchPrefix: string;
|
|
15
|
+
startedAt: string;
|
|
16
|
+
imports: ImportRecord[];
|
|
17
|
+
}
|
|
18
|
+
export interface ImportRecord {
|
|
19
|
+
fileName: string;
|
|
20
|
+
roastId: number | null;
|
|
21
|
+
batchName: string;
|
|
22
|
+
status: 'success' | 'failed' | 'needs-review';
|
|
23
|
+
error?: string;
|
|
24
|
+
milestones?: MilestoneData;
|
|
25
|
+
phases?: ProcessedRoastData['phases'];
|
|
26
|
+
importedAt: string;
|
|
27
|
+
aiMatch?: {
|
|
28
|
+
coffeeName: string;
|
|
29
|
+
confidence: number;
|
|
30
|
+
reasoning: string;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
interface StartWatchOpts {
|
|
34
|
+
coffeeId: number;
|
|
35
|
+
coffeeName: string;
|
|
36
|
+
batchPrefix: string;
|
|
37
|
+
promptEach?: boolean;
|
|
38
|
+
startSequence?: number;
|
|
39
|
+
autoMatch?: boolean;
|
|
40
|
+
}
|
|
41
|
+
/** Persist session state to disk after each import. */
|
|
42
|
+
export declare function saveWatchSession(session: WatchSession): Promise<void>;
|
|
43
|
+
/** Load a previously saved watch session. Returns null if none exists. */
|
|
44
|
+
export declare function loadWatchSession(): Promise<WatchSession | null>;
|
|
45
|
+
/**
|
|
46
|
+
* Generate a sequential batch name: "{prefix} #{n}".
|
|
47
|
+
* sequence is 1-based (first import = 1).
|
|
48
|
+
*/
|
|
49
|
+
export declare function generateBatchName(prefix: string, sequence: number): string;
|
|
50
|
+
/** Returns true if filename has a .alog extension (case-insensitive). */
|
|
51
|
+
export declare function isAlogFile(filename: string): boolean;
|
|
52
|
+
/** Render a padded verification table and print to stderr. */
|
|
53
|
+
export declare function printVerificationTable(session: WatchSession, autoMatch?: boolean): void;
|
|
54
|
+
/**
|
|
55
|
+
* Start watching a directory for new .alog files.
|
|
56
|
+
* Blocks until SIGINT (Ctrl+C), then prints a summary table and resolves.
|
|
57
|
+
*
|
|
58
|
+
* @param supabase Authenticated Supabase client
|
|
59
|
+
* @param userId Authenticated user ID
|
|
60
|
+
* @param directory Absolute or relative path to watch
|
|
61
|
+
* @param opts Watch options
|
|
62
|
+
* @returns The final WatchSession (all imports recorded)
|
|
63
|
+
*/
|
|
64
|
+
export declare function startWatch(supabase: SupabaseClient, userId: string, directory: string, opts: StartWatchOpts): Promise<WatchSession>;
|
|
65
|
+
export {};
|
|
66
|
+
//# sourceMappingURL=watch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"watch.d.ts","sourceRoot":"","sources":["../../../src/lib/interactive/watch.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAE5D,OAAO,KAAK,EAAE,aAAa,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAK7E,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,YAAY,EAAE,CAAC;CACzB;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,SAAS,GAAG,QAAQ,GAAG,cAAc,CAAC;IAC9C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,aAAa,CAAC;IAC3B,MAAM,CAAC,EAAE,kBAAkB,CAAC,QAAQ,CAAC,CAAC;IACtC,UAAU,EAAE,MAAM,CAAC;IAEnB,OAAO,CAAC,EAAE;QACR,UAAU,EAAE,MAAM,CAAC;QACnB,UAAU,EAAE,MAAM,CAAC;QACnB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;CACH;AAED,UAAU,cAAc;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAMD,uDAAuD;AACvD,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAG3E;AAED,0EAA0E;AAC1E,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAQrE;AAID;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAE1E;AAID,yEAAyE;AACzE,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAEpD;AAID,8DAA8D;AAC9D,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,YAAY,EAAE,SAAS,CAAC,EAAE,OAAO,GAAG,IAAI,CASvF;AA8JD;;;;;;;;;GASG;AACH,wBAAsB,UAAU,CAC9B,QAAQ,EAAE,cAAc,EACxB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE,cAAc,GACnB,OAAO,CAAC,YAAY,CAAC,CAiPvB"}
|