@purveyors/cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/LICENSE.md +53 -0
  2. package/README.md +175 -0
  3. package/dist/commands/auth.d.ts +6 -0
  4. package/dist/commands/auth.d.ts.map +1 -0
  5. package/dist/commands/auth.js +193 -0
  6. package/dist/commands/auth.js.map +1 -0
  7. package/dist/commands/catalog.d.ts +63 -0
  8. package/dist/commands/catalog.d.ts.map +1 -0
  9. package/dist/commands/catalog.js +132 -0
  10. package/dist/commands/catalog.js.map +1 -0
  11. package/dist/commands/inventory.d.ts +32 -0
  12. package/dist/commands/inventory.d.ts.map +1 -0
  13. package/dist/commands/inventory.js +287 -0
  14. package/dist/commands/inventory.js.map +1 -0
  15. package/dist/commands/roast.d.ts +48 -0
  16. package/dist/commands/roast.d.ts.map +1 -0
  17. package/dist/commands/roast.js +225 -0
  18. package/dist/commands/roast.js.map +1 -0
  19. package/dist/commands/sales.d.ts +17 -0
  20. package/dist/commands/sales.d.ts.map +1 -0
  21. package/dist/commands/sales.js +226 -0
  22. package/dist/commands/sales.js.map +1 -0
  23. package/dist/commands/tasting.d.ts +46 -0
  24. package/dist/commands/tasting.d.ts.map +1 -0
  25. package/dist/commands/tasting.js +174 -0
  26. package/dist/commands/tasting.js.map +1 -0
  27. package/dist/index.d.ts +3 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +68 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/lib/config.d.ts +21 -0
  32. package/dist/lib/config.d.ts.map +1 -0
  33. package/dist/lib/config.js +45 -0
  34. package/dist/lib/config.js.map +1 -0
  35. package/dist/lib/errors.d.ts +20 -0
  36. package/dist/lib/errors.d.ts.map +1 -0
  37. package/dist/lib/errors.js +58 -0
  38. package/dist/lib/errors.js.map +1 -0
  39. package/dist/lib/output.d.ts +22 -0
  40. package/dist/lib/output.d.ts.map +1 -0
  41. package/dist/lib/output.js +86 -0
  42. package/dist/lib/output.js.map +1 -0
  43. package/dist/lib/prompts.d.ts +11 -0
  44. package/dist/lib/prompts.d.ts.map +1 -0
  45. package/dist/lib/prompts.js +22 -0
  46. package/dist/lib/prompts.js.map +1 -0
  47. package/dist/lib/supabase.d.ts +22 -0
  48. package/dist/lib/supabase.d.ts.map +1 -0
  49. package/dist/lib/supabase.js +87 -0
  50. package/dist/lib/supabase.js.map +1 -0
  51. package/dist/types/database.types.d.ts +13 -0
  52. package/dist/types/database.types.d.ts.map +1 -0
  53. package/dist/types/database.types.js +8 -0
  54. package/dist/types/database.types.js.map +1 -0
  55. package/dist/types/index.d.ts +28 -0
  56. package/dist/types/index.d.ts.map +1 -0
  57. package/dist/types/index.js +5 -0
  58. package/dist/types/index.js.map +1 -0
  59. package/package.json +58 -0
package/LICENSE.md ADDED
@@ -0,0 +1,53 @@
1
+ # Sustainable Use License
2
+
3
+ Copyright (c) 2026 Reed Whetstone / purveyors.io
4
+
5
+ ## Acceptance
6
+
7
+ By using this software, you agree to all of the terms and conditions below.
8
+
9
+ ## Copyright License
10
+
11
+ The licensor grants you a non-exclusive, royalty-free, worldwide, non-sublicensable, non-transferable license to use, copy, distribute, make available, and prepare derivative works of the software, in each case subject to the limitations and conditions below.
12
+
13
+ ## Limitations
14
+
15
+ **You may not** provide the software to third parties as a hosted or managed service, where the service provides users with access to any substantial set of the features or functionality of the software.
16
+
17
+ **You may not** move, change, disable, or circumvent the license key functionality in the software, and you may not remove or obscure any functionality in the software that is protected by the license key.
18
+
19
+ **You may not** alter, remove, or obscure any licensing, copyright, or other notices of the licensor in the software. Any use of the licensor's trademarks is subject to applicable law.
20
+
21
+ ## Patents
22
+
23
+ The licensor grants you a license, under any patent claims the licensor can license, or becomes able to license, to make, have made, use, sell, offer for sale, import and have imported the software, in each case subject to the limitations and conditions in this license. This license does not cover any patent claims that you cause to be infringed by modifications or additions to the software. If you or your company make any written claim that the software infringes or contributes to infringement of any patent, your patent license for the software granted under these terms ends immediately.
24
+
25
+ ## Notices
26
+
27
+ You must ensure that anyone who gets a copy of any part of the software from you also gets a copy of these terms.
28
+
29
+ If you modify the software, you must include in any modified copies of the software prominent notices stating that you have modified the software.
30
+
31
+ ## No Other Rights
32
+
33
+ These terms do not imply any licenses other than those expressly granted in these terms.
34
+
35
+ ## Termination
36
+
37
+ If you use the software in violation of these terms, such use is not licensed, and your licenses will automatically terminate. If the licensor provides you with a notice of your violation, and you cease all violation of this license no later than 30 days after you receive that notice, your licenses will be reinstated retroactively. However, if you violate these terms after such reinstatement, any additional violation of these terms will cause your licenses to terminate automatically and permanently.
38
+
39
+ ## No Liability
40
+
41
+ _As far as the law allows, the software comes as is, without any warranty or condition, and the licensor will not be liable to you for any damages arising out of these terms or the use or nature of the software, under any kind of legal claim._
42
+
43
+ ## Definitions
44
+
45
+ The _licensor_ is the entity offering these terms, and the _software_ is the software the licensor makes available under these terms, including any portion of it.
46
+
47
+ _You_ refers both to you as an individual and to any legal entity you represent when using this software.
48
+
49
+ _Your company_ is any legal entity, sole proprietorship, or other kind of organization that you work for, plus all organizations that have control over, are under the control of, or are under common control with that organization. _Control_ means ownership of substantially all the assets of an entity, or the power to direct its management and legal commitments.
50
+
51
+ _Your licenses_ are all the licenses granted to you for the software under these terms.
52
+
53
+ _Use_ means anything you do with the software requiring one of your licenses.
package/README.md ADDED
@@ -0,0 +1,175 @@
1
+ # prvrs — The Purveyors CLI
2
+
3
+ > Coffee intelligence from your terminal.
4
+
5
+ `prvrs` is the official command-line interface for [purveyors.io](https://purveyors.io). It gives coffee professionals direct terminal access to the Purveyors platform: search green coffee availability, track pricing tiers, monitor inventory, and pipe data into spreadsheets, scripts, or dashboards.
6
+
7
+ ---
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ npm install -g @purveyors/cli
13
+ ```
14
+
15
+ Requires **Node.js >= 20**.
16
+
17
+ Verify:
18
+
19
+ ```bash
20
+ prvrs --version
21
+ ```
22
+
23
+ ---
24
+
25
+ ## Quick Start
26
+
27
+ ```bash
28
+ # 1. Authenticate
29
+ prvrs auth login
30
+
31
+ # 2. Confirm your session
32
+ prvrs auth status
33
+
34
+ # 3. Explore commands
35
+ prvrs --help
36
+ ```
37
+
38
+ ---
39
+
40
+ ## Authentication
41
+
42
+ `prvrs` authenticates via Google OAuth, using the same account as your purveyors.io web session.
43
+
44
+ ### Login
45
+
46
+ ```bash
47
+ prvrs auth login
48
+ ```
49
+
50
+ Opens your browser. Complete Google sign-in, then return to the terminal. Credentials are stored at `~/.config/prvrs/credentials.json` (owner-readable only, mode 0600).
51
+
52
+ ### Status
53
+
54
+ ```bash
55
+ prvrs auth status
56
+ ```
57
+
58
+ ```
59
+ ✔ Logged in as you@example.com
60
+ ℹ Role: authenticated
61
+ ℹ Token expires: 2026-03-16T08:00:00.000Z
62
+ ```
63
+
64
+ ### Logout
65
+
66
+ ```bash
67
+ prvrs auth logout
68
+ ```
69
+
70
+ Clears stored credentials from disk.
71
+
72
+ ---
73
+
74
+ ## Commands
75
+
76
+ ### `prvrs auth`
77
+
78
+ | Command | Description |
79
+ | ------------------- | --------------------------------------- |
80
+ | `prvrs auth login` | Log in via Google OAuth (opens browser) |
81
+ | `prvrs auth status` | Show current authentication state |
82
+ | `prvrs auth logout` | Clear stored credentials |
83
+
84
+ ---
85
+
86
+ ## Output Formats
87
+
88
+ All `prvrs` commands default to **compact JSON** — one line, no colors, machine-readable. This makes `prvrs` pipeable into `jq`, `csvkit`, or any script.
89
+
90
+ ### Default (compact JSON)
91
+
92
+ ```bash
93
+ prvrs auth status
94
+ # → {"authenticated":true,"email":"you@example.com","role":"authenticated","tokenExpires":"2026-03-16T08:00:00.000Z"}
95
+ ```
96
+
97
+ ### Pretty JSON (`--pretty`)
98
+
99
+ ```bash
100
+ prvrs auth status --pretty
101
+ # → indented, colorized JSON
102
+ ```
103
+
104
+ ### CSV (`--csv`)
105
+
106
+ ```bash
107
+ prvrs coffee list --csv > coffees.csv
108
+ ```
109
+
110
+ ### Piping with jq
111
+
112
+ ```bash
113
+ prvrs auth status | jq .email
114
+ # → "you@example.com"
115
+ ```
116
+
117
+ User feedback messages (spinners, success/error) go to **stderr**, so they never pollute your stdout pipe.
118
+
119
+ ---
120
+
121
+ ## Environment Variables
122
+
123
+ | Variable | Description |
124
+ | ----------------------------- | ---------------------------------------------------------- |
125
+ | `PURVEYORS_SUPABASE_URL` | Override the Supabase project URL (useful for dev/staging) |
126
+ | `PURVEYORS_SUPABASE_ANON_KEY` | Override the Supabase anon key |
127
+ | `PRVRS_DEBUG` | Set to any value to enable verbose error output |
128
+
129
+ ---
130
+
131
+ ## Development
132
+
133
+ ```bash
134
+ git clone https://github.com/reedwhetstone/purveyors-cli
135
+ cd purveyors-cli
136
+ pnpm install
137
+ ```
138
+
139
+ Create a `.env` file:
140
+
141
+ ```
142
+ PURVEYORS_SUPABASE_URL=https://your-project.supabase.co
143
+ PURVEYORS_SUPABASE_ANON_KEY=your-anon-key
144
+ ```
145
+
146
+ Run locally:
147
+
148
+ ```bash
149
+ pnpm dev -- auth status
150
+ pnpm dev -- --help
151
+ ```
152
+
153
+ Build:
154
+
155
+ ```bash
156
+ pnpm build
157
+ ```
158
+
159
+ Lint + type check + test:
160
+
161
+ ```bash
162
+ pnpm lint
163
+ pnpm check
164
+ pnpm test
165
+ ```
166
+
167
+ See [AGENTS.md](./AGENTS.md) for the full contributor guide including architecture, code conventions, and PR requirements.
168
+
169
+ ---
170
+
171
+ ## License
172
+
173
+ Sustainable Use License. See [LICENSE.md](./LICENSE.md).
174
+
175
+ Copyright © 2026 Reed Whetstone / purveyors.io
@@ -0,0 +1,6 @@
1
+ import { Command } from 'commander';
2
+ /**
3
+ * Build and return the `auth` command subtree.
4
+ */
5
+ export declare function buildAuthCommand(): Command;
6
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/commands/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA+NpC;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,OAAO,CAe1C"}
@@ -0,0 +1,193 @@
1
+ import { Command } from 'commander';
2
+ import { createServer } from 'http';
3
+ import { createAnonClient, validateSession } from '../lib/supabase.js';
4
+ import { writeCredentials, deleteCredentials } from '../lib/config.js';
5
+ import { outputData, success, info, warn } from '../lib/output.js';
6
+ import { withErrorHandling, AuthError } from '../lib/errors.js';
7
+ import chalk from 'chalk';
8
+ import ora from 'ora';
9
+ const DEFAULT_CALLBACK_HOST = 'localhost';
10
+ const CALLBACK_PATH = '/auth/callback';
11
+ /**
12
+ * Start a one-shot local HTTP server to receive the Supabase OAuth callback.
13
+ *
14
+ * Supabase delivers tokens in the URL fragment (#access_token=...) which is
15
+ * client-side only. We serve a minimal HTML page that extracts the fragment
16
+ * values and POSTs them back to the local server, then resolves the promise.
17
+ */
18
+ function startCallbackServer() {
19
+ return new Promise((resolve, reject) => {
20
+ let tokenResolve;
21
+ let tokenReject;
22
+ const tokenPromise = new Promise((res, rej) => {
23
+ tokenResolve = res;
24
+ tokenReject = rej;
25
+ });
26
+ const server = createServer((req, res) => {
27
+ const url = new URL(req.url ?? '/', `http://${DEFAULT_CALLBACK_HOST}`);
28
+ if (url.pathname === CALLBACK_PATH) {
29
+ res.writeHead(200, { 'Content-Type': 'text/html' });
30
+ res.end(`<!DOCTYPE html>
31
+ <html>
32
+ <head><title>Authenticating...</title></head>
33
+ <body>
34
+ <p>Completing authentication, please wait...</p>
35
+ <script>
36
+ const hash = window.location.hash.substring(1);
37
+ const params = new URLSearchParams(hash);
38
+ fetch('/auth/token', {
39
+ method: 'POST',
40
+ headers: { 'Content-Type': 'application/json' },
41
+ body: JSON.stringify({
42
+ access_token: params.get('access_token'),
43
+ refresh_token: params.get('refresh_token'),
44
+ expires_in: params.get('expires_in'),
45
+ })
46
+ }).then(() => {
47
+ document.body.innerHTML =
48
+ '<h2 style="font-family:sans-serif;color:#2d6a4f;">✔ Authenticated! You can close this tab.</h2>';
49
+ });
50
+ </script>
51
+ </body>
52
+ </html>`);
53
+ return;
54
+ }
55
+ if (url.pathname === '/auth/token' && req.method === 'POST') {
56
+ let body = '';
57
+ req.on('data', (chunk) => (body += chunk));
58
+ req.on('end', () => {
59
+ res.writeHead(200);
60
+ res.end('ok');
61
+ server.close();
62
+ try {
63
+ const { access_token, refresh_token, expires_in } = JSON.parse(body);
64
+ if (!access_token || !refresh_token) {
65
+ tokenReject(new AuthError('OAuth callback did not include tokens. Try again.'));
66
+ return;
67
+ }
68
+ tokenResolve({
69
+ accessToken: access_token,
70
+ refreshToken: refresh_token,
71
+ expiresIn: parseInt(expires_in ?? '3600', 10),
72
+ });
73
+ }
74
+ catch {
75
+ tokenReject(new AuthError('Failed to parse OAuth callback response.'));
76
+ }
77
+ });
78
+ return;
79
+ }
80
+ res.writeHead(404);
81
+ res.end();
82
+ });
83
+ server.listen(0, DEFAULT_CALLBACK_HOST, () => {
84
+ const addr = server.address();
85
+ if (!addr || typeof addr === 'string') {
86
+ reject(new AuthError('Failed to start callback server.'));
87
+ return;
88
+ }
89
+ resolve({ port: addr.port, tokenPromise });
90
+ });
91
+ server.on('error', (err) => reject(err));
92
+ });
93
+ }
94
+ /**
95
+ * `prvrs auth login`
96
+ * Opens a browser for Google OAuth via Supabase, captures the callback token.
97
+ */
98
+ const loginAction = withErrorHandling(async () => {
99
+ const { port, tokenPromise } = await startCallbackServer();
100
+ const redirectTo = `http://${DEFAULT_CALLBACK_HOST}:${port}${CALLBACK_PATH}`;
101
+ const supabase = createAnonClient();
102
+ const { data, error } = await supabase.auth.signInWithOAuth({
103
+ provider: 'google',
104
+ options: { redirectTo },
105
+ });
106
+ if (error || !data.url) {
107
+ throw new AuthError('Failed to generate OAuth URL.', error);
108
+ }
109
+ console.log(chalk.bold('\n Opening browser for authentication...'));
110
+ console.log(chalk.dim(` If the browser does not open, visit:\n ${data.url}\n`));
111
+ // Open browser cross-platform
112
+ const { spawn } = await import('child_process');
113
+ const openCmd = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'start' : 'xdg-open';
114
+ spawn(openCmd, [data.url], { detached: true, stdio: 'ignore' }).unref();
115
+ const spinner = ora('Waiting for authentication...').start();
116
+ const { accessToken, refreshToken, expiresIn } = await tokenPromise;
117
+ spinner.succeed('Authentication received');
118
+ // Fetch user info to confirm identity
119
+ const client = createAnonClient();
120
+ await client.auth.setSession({ access_token: accessToken, refresh_token: refreshToken });
121
+ const { data: { user }, } = await client.auth.getUser();
122
+ const creds = {
123
+ accessToken,
124
+ refreshToken,
125
+ expiresAt: Date.now() + expiresIn * 1000,
126
+ user: {
127
+ id: user?.id ?? 'unknown',
128
+ email: user?.email ?? 'unknown',
129
+ role: user?.role,
130
+ },
131
+ };
132
+ await writeCredentials(creds);
133
+ success(`Logged in as ${chalk.bold(creds.user.email)}`);
134
+ });
135
+ /**
136
+ * `prvrs auth status`
137
+ * Shows current login state and token info.
138
+ */
139
+ const statusAction = withErrorHandling(async (_, cmd) => {
140
+ const opts = cmd.optsWithGlobals();
141
+ const spinner = ora('Checking authentication status...').start();
142
+ const session = await validateSession();
143
+ spinner.stop();
144
+ if (!session) {
145
+ const result = {
146
+ authenticated: false,
147
+ message: 'Not logged in. Run `prvrs auth login` to authenticate.',
148
+ };
149
+ if (!opts.pretty && !opts.csv) {
150
+ warn(result.message);
151
+ process.exit(1);
152
+ }
153
+ outputData(result, opts);
154
+ process.exit(1);
155
+ }
156
+ const result = {
157
+ authenticated: true,
158
+ email: session.email,
159
+ role: session.role ?? 'authenticated',
160
+ tokenExpires: new Date(session.expiresAt).toISOString(),
161
+ };
162
+ if (!opts.pretty && !opts.csv) {
163
+ success(`Logged in as ${chalk.bold(session.email)}`);
164
+ info(`Role: ${result.role}`);
165
+ info(`Token expires: ${result.tokenExpires}`);
166
+ return;
167
+ }
168
+ outputData(result, opts);
169
+ });
170
+ /**
171
+ * `prvrs auth logout`
172
+ * Clears stored credentials from disk.
173
+ */
174
+ const logoutAction = withErrorHandling(async () => {
175
+ await deleteCredentials();
176
+ success('Logged out successfully.');
177
+ });
178
+ /**
179
+ * Build and return the `auth` command subtree.
180
+ */
181
+ export function buildAuthCommand() {
182
+ const auth = new Command('auth').description('Manage authentication with purveyors.io');
183
+ auth.command('login').description('Log in via Google OAuth (opens browser)').action(loginAction);
184
+ auth
185
+ .command('status')
186
+ .description('Show current authentication status')
187
+ .option('--pretty', 'Pretty-print JSON output')
188
+ .option('--csv', 'Output as CSV')
189
+ .action(statusAction);
190
+ auth.command('logout').description('Clear stored credentials').action(logoutAction);
191
+ return auth;
192
+ }
193
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/commands/auth.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,63 @@
1
+ import { Command } from 'commander';
2
+ /**
3
+ * Strip PostgREST special characters from user-supplied filter values.
4
+ * Prevents injection into .or() filter strings where values are interpolated directly.
5
+ * Removes: ( ) , . * % that have meaning in PostgREST filter syntax.
6
+ */
7
+ export declare function sanitizeFilterValue(value: string): string;
8
+ export interface CatalogItem {
9
+ id: number;
10
+ name: string | null;
11
+ source: string | null;
12
+ continent: string | null;
13
+ country: string | null;
14
+ region: string | null;
15
+ processing: string | null;
16
+ drying_method: string | null;
17
+ cultivar_detail: string | null;
18
+ grade: string | null;
19
+ appearance: string | null;
20
+ type: string | null;
21
+ description_short: string | null;
22
+ description_long: string | null;
23
+ farm_notes: string | null;
24
+ cupping_notes: string | null;
25
+ ai_description: string | null;
26
+ roast_recs: string | null;
27
+ cost_lb: number | null;
28
+ lot_size: string | null;
29
+ bag_size: string | null;
30
+ score_value: number | null;
31
+ stocked: boolean | null;
32
+ stocked_date: string | null;
33
+ unstocked_date: string | null;
34
+ arrival_date: string | null;
35
+ last_updated: string | null;
36
+ public_coffee: boolean | null;
37
+ wholesale: boolean | null;
38
+ price_tiers: Array<{
39
+ min_lbs: number;
40
+ price: number;
41
+ }> | null;
42
+ }
43
+ export interface CatalogStats {
44
+ total: number;
45
+ stocked: number;
46
+ byOrigin: Record<string, number>;
47
+ avgPricePerLb: number | null;
48
+ priceRange: {
49
+ min: number | null;
50
+ max: number | null;
51
+ };
52
+ }
53
+ /**
54
+ * Aggregate stats from an array of catalog items.
55
+ * Pure function — no I/O, safe to unit test.
56
+ */
57
+ export declare function computeCatalogStats(items: CatalogItem[]): CatalogStats;
58
+ /**
59
+ * `prvrs catalog` — Browse the public coffee catalog.
60
+ * The coffee_catalog table is publicly readable; no auth required.
61
+ */
62
+ export declare function buildCatalogCommand(): Command;
63
+ //# sourceMappingURL=catalog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../../src/commands/catalog.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAQpC;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEzD;AAID,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;IACxB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,aAAa,EAAE,OAAO,GAAG,IAAI,CAAC;IAC9B,SAAS,EAAE,OAAO,GAAG,IAAI,CAAC;IAC1B,WAAW,EAAE,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,GAAG,IAAI,CAAC;CAC/D;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,UAAU,EAAE;QAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC;CACxD;AAID;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,YAAY,CAoBtE;AAID;;;GAGG;AACH,wBAAgB,mBAAmB,IAAI,OAAO,CAgH7C"}
@@ -0,0 +1,132 @@
1
+ import { Command } from 'commander';
2
+ import { createAnonClient } from '../lib/supabase.js';
3
+ import { outputData, info } from '../lib/output.js';
4
+ import { withErrorHandling } from '../lib/errors.js';
5
+ // ─── Helpers ──────────────────────────────────────────────────────────────────
6
+ /**
7
+ * Strip PostgREST special characters from user-supplied filter values.
8
+ * Prevents injection into .or() filter strings where values are interpolated directly.
9
+ * Removes: ( ) , . * % that have meaning in PostgREST filter syntax.
10
+ */
11
+ export function sanitizeFilterValue(value) {
12
+ return value.replace(/[(),.*]/g, '');
13
+ }
14
+ // ─── Pure logic (exported for unit testing) ───────────────────────────────────
15
+ /**
16
+ * Aggregate stats from an array of catalog items.
17
+ * Pure function — no I/O, safe to unit test.
18
+ */
19
+ export function computeCatalogStats(items) {
20
+ const stocked = items.filter((i) => i.stocked === true).length;
21
+ const byOrigin = {};
22
+ for (const item of items) {
23
+ const key = item.country ?? item.continent ?? 'Unknown';
24
+ byOrigin[key] = (byOrigin[key] ?? 0) + 1;
25
+ }
26
+ const prices = items.map((i) => i.cost_lb).filter((p) => p !== null);
27
+ const avgPricePerLb = prices.length > 0
28
+ ? Math.round((prices.reduce((a, b) => a + b, 0) / prices.length) * 100) / 100
29
+ : null;
30
+ const priceRange = {
31
+ min: prices.length > 0 ? Math.min(...prices) : null,
32
+ max: prices.length > 0 ? Math.max(...prices) : null,
33
+ };
34
+ return { total: items.length, stocked, byOrigin, avgPricePerLb, priceRange };
35
+ }
36
+ // ─── Command builder ──────────────────────────────────────────────────────────
37
+ /**
38
+ * `prvrs catalog` — Browse the public coffee catalog.
39
+ * The coffee_catalog table is publicly readable; no auth required.
40
+ */
41
+ export function buildCatalogCommand() {
42
+ const catalog = new Command('catalog').description('Browse the public coffee catalog');
43
+ // ── catalog search ────────────────────────────────────────────────────────
44
+ catalog
45
+ .command('search')
46
+ .description('Search coffees by origin, process, price, or flavor')
47
+ .option('--origin <origin>', 'Filter by origin (country, continent, or region)')
48
+ .option('--process <method>', 'Filter by processing method (e.g. natural, washed)')
49
+ .option('--price-min <n>', 'Minimum price per lb (USD)')
50
+ .option('--price-max <n>', 'Maximum price per lb (USD)')
51
+ .option('--flavor <keywords>', 'Flavor keywords, comma-separated (e.g. "berry,chocolate")')
52
+ .option('--stocked', 'Only show currently stocked coffees')
53
+ .option('--limit <n>', 'Maximum results to return', '10')
54
+ .action(withErrorHandling(async (opts, cmd) => {
55
+ const globalOpts = cmd.optsWithGlobals();
56
+ const supabase = createAnonClient();
57
+ let query = supabase.from('coffee_catalog').select('*');
58
+ if (opts.origin) {
59
+ const o = sanitizeFilterValue(opts.origin);
60
+ query = query.or(`country.ilike.%${o}%,continent.ilike.%${o}%,region.ilike.%${o}%`);
61
+ }
62
+ if (opts.process) {
63
+ query = query.ilike('processing', `%${opts.process}%`);
64
+ }
65
+ if (opts.priceMin !== undefined) {
66
+ query = query.gte('cost_lb', parseFloat(opts.priceMin));
67
+ }
68
+ if (opts.priceMax !== undefined) {
69
+ query = query.lte('cost_lb', parseFloat(opts.priceMax));
70
+ }
71
+ if (opts.flavor) {
72
+ const keywords = opts.flavor
73
+ .split(',')
74
+ .map((k) => sanitizeFilterValue(k.trim()))
75
+ .filter(Boolean);
76
+ const flavorFilters = keywords
77
+ .flatMap((kw) => [
78
+ `description_short.ilike.%${kw}%`,
79
+ `description_long.ilike.%${kw}%`,
80
+ `cupping_notes.ilike.%${kw}%`,
81
+ `farm_notes.ilike.%${kw}%`,
82
+ ])
83
+ .join(',');
84
+ query = query.or(flavorFilters);
85
+ }
86
+ if (opts.stocked) {
87
+ query = query.eq('stocked', true);
88
+ }
89
+ const limit = Math.max(1, parseInt(opts.limit, 10));
90
+ const { data, error } = await query.limit(limit);
91
+ if (error)
92
+ throw error;
93
+ if (!data || data.length === 0) {
94
+ info('No coffees found matching your criteria.');
95
+ return;
96
+ }
97
+ outputData(data, globalOpts);
98
+ }));
99
+ // ── catalog get <id> ──────────────────────────────────────────────────────
100
+ catalog
101
+ .command('get <id>')
102
+ .description('Fetch a single coffee by ID')
103
+ .action(withErrorHandling(async (id, _opts, cmd) => {
104
+ const globalOpts = cmd.optsWithGlobals();
105
+ const supabase = createAnonClient();
106
+ const { data, error } = await supabase
107
+ .from('coffee_catalog')
108
+ .select('*')
109
+ .eq('id', parseInt(id, 10))
110
+ .single();
111
+ if (error)
112
+ throw error;
113
+ outputData(data, globalOpts);
114
+ }));
115
+ // ── catalog stats ─────────────────────────────────────────────────────────
116
+ catalog
117
+ .command('stats')
118
+ .description('Aggregate statistics for the coffee catalog')
119
+ .action(withErrorHandling(async (_opts, cmd) => {
120
+ const globalOpts = cmd.optsWithGlobals();
121
+ const supabase = createAnonClient();
122
+ const { data, error } = await supabase
123
+ .from('coffee_catalog')
124
+ .select('id, country, continent, cost_lb, stocked');
125
+ if (error)
126
+ throw error;
127
+ const stats = computeCatalogStats((data ?? []));
128
+ outputData(stats, globalOpts);
129
+ }));
130
+ return catalog;
131
+ }
132
+ //# sourceMappingURL=catalog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog.js","sourceRoot":"","sources":["../../src/commands/catalog.ts"],"names":[],"mappings":"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"}