@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.
Files changed (95) 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/config.d.ts +7 -0
  6. package/dist/commands/config.d.ts.map +1 -0
  7. package/dist/commands/config.js +82 -0
  8. package/dist/commands/config.js.map +1 -0
  9. package/dist/commands/inventory.d.ts +2 -25
  10. package/dist/commands/inventory.d.ts.map +1 -1
  11. package/dist/commands/inventory.js +109 -149
  12. package/dist/commands/inventory.js.map +1 -1
  13. package/dist/commands/roast.d.ts +2 -41
  14. package/dist/commands/roast.d.ts.map +1 -1
  15. package/dist/commands/roast.js +445 -126
  16. package/dist/commands/roast.js.map +1 -1
  17. package/dist/commands/sales.d.ts +2 -10
  18. package/dist/commands/sales.d.ts.map +1 -1
  19. package/dist/commands/sales.js +100 -112
  20. package/dist/commands/sales.js.map +1 -1
  21. package/dist/commands/tasting.d.ts +4 -38
  22. package/dist/commands/tasting.d.ts.map +1 -1
  23. package/dist/commands/tasting.js +93 -116
  24. package/dist/commands/tasting.js.map +1 -1
  25. package/dist/index.js +8 -0
  26. package/dist/index.js.map +1 -1
  27. package/dist/lib/ai.d.ts +36 -0
  28. package/dist/lib/ai.d.ts.map +1 -0
  29. package/dist/lib/ai.js +38 -0
  30. package/dist/lib/ai.js.map +1 -0
  31. package/dist/lib/artisan/db.d.ts +37 -0
  32. package/dist/lib/artisan/db.d.ts.map +1 -0
  33. package/dist/lib/artisan/db.js +51 -0
  34. package/dist/lib/artisan/db.js.map +1 -0
  35. package/dist/lib/artisan/import.d.ts +16 -0
  36. package/dist/lib/artisan/import.d.ts.map +1 -0
  37. package/dist/lib/artisan/import.js +447 -0
  38. package/dist/lib/artisan/import.js.map +1 -0
  39. package/dist/lib/artisan/index.d.ts +9 -0
  40. package/dist/lib/artisan/index.d.ts.map +1 -0
  41. package/dist/lib/artisan/index.js +7 -0
  42. package/dist/lib/artisan/index.js.map +1 -0
  43. package/dist/lib/artisan/parser.d.ts +19 -0
  44. package/dist/lib/artisan/parser.d.ts.map +1 -0
  45. package/dist/lib/artisan/parser.js +376 -0
  46. package/dist/lib/artisan/parser.js.map +1 -0
  47. package/dist/lib/artisan/temperature.d.ts +52 -0
  48. package/dist/lib/artisan/temperature.d.ts.map +1 -0
  49. package/dist/lib/artisan/temperature.js +101 -0
  50. package/dist/lib/artisan/temperature.js.map +1 -0
  51. package/dist/lib/artisan/types.d.ts +195 -0
  52. package/dist/lib/artisan/types.d.ts.map +1 -0
  53. package/dist/lib/artisan/types.js +35 -0
  54. package/dist/lib/artisan/types.js.map +1 -0
  55. package/dist/lib/artisan/validator.d.ts +14 -0
  56. package/dist/lib/artisan/validator.d.ts.map +1 -0
  57. package/dist/lib/artisan/validator.js +228 -0
  58. package/dist/lib/artisan/validator.js.map +1 -0
  59. package/dist/lib/catalog.d.ts +87 -0
  60. package/dist/lib/catalog.d.ts.map +1 -0
  61. package/dist/lib/catalog.js +111 -0
  62. package/dist/lib/catalog.js.map +1 -0
  63. package/dist/lib/config.d.ts +26 -0
  64. package/dist/lib/config.d.ts.map +1 -1
  65. package/dist/lib/config.js +59 -0
  66. package/dist/lib/config.js.map +1 -1
  67. package/dist/lib/index.d.ts +6 -0
  68. package/dist/lib/index.d.ts.map +1 -0
  69. package/dist/lib/index.js +11 -0
  70. package/dist/lib/index.js.map +1 -0
  71. package/dist/lib/interactive/forms.d.ts +33 -0
  72. package/dist/lib/interactive/forms.d.ts.map +1 -0
  73. package/dist/lib/interactive/forms.js +139 -0
  74. package/dist/lib/interactive/forms.js.map +1 -0
  75. package/dist/lib/interactive/watch.d.ts +66 -0
  76. package/dist/lib/interactive/watch.d.ts.map +1 -0
  77. package/dist/lib/interactive/watch.js +494 -0
  78. package/dist/lib/interactive/watch.js.map +1 -0
  79. package/dist/lib/inventory.d.ts +80 -0
  80. package/dist/lib/inventory.d.ts.map +1 -0
  81. package/dist/lib/inventory.js +205 -0
  82. package/dist/lib/inventory.js.map +1 -0
  83. package/dist/lib/roast.d.ts +127 -0
  84. package/dist/lib/roast.d.ts.map +1 -0
  85. package/dist/lib/roast.js +284 -0
  86. package/dist/lib/roast.js.map +1 -0
  87. package/dist/lib/sales.d.ts +53 -0
  88. package/dist/lib/sales.d.ts.map +1 -0
  89. package/dist/lib/sales.js +155 -0
  90. package/dist/lib/sales.js.map +1 -0
  91. package/dist/lib/tasting.d.ts +76 -0
  92. package/dist/lib/tasting.d.ts.map +1 -0
  93. package/dist/lib/tasting.js +136 -0
  94. package/dist/lib/tasting.js.map +1 -0
  95. 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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"}
@@ -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
@@ -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;AAM9D;;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;AAED,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,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"}
@@ -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
@@ -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;AAE9D,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;AAE5E;;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,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,CAAC"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,6 @@
1
+ export * from './catalog.js';
2
+ export * from './inventory.js';
3
+ export * from './roast.js';
4
+ export * from './sales.js';
5
+ export * from './tasting.js';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -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"}