clishop 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,3878 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { Command } from "commander";
5
+ import chalk15 from "chalk";
6
+
7
+ // src/commands/auth.ts
8
+ import chalk from "chalk";
9
+ import ora from "ora";
10
+ import inquirer from "inquirer";
11
+
12
+ // src/auth.ts
13
+ import keytar from "keytar";
14
+ import axios from "axios";
15
+
16
+ // src/config.ts
17
+ import Conf from "conf";
18
+ var DEFAULT_AGENT = {
19
+ name: "default",
20
+ requireConfirmation: true,
21
+ maxOrderAmount: 500,
22
+ allowedCategories: [],
23
+ blockedCategories: []
24
+ };
25
+ var config = new Conf({
26
+ projectName: "clishop",
27
+ defaults: {
28
+ activeAgent: "default",
29
+ agents: {
30
+ default: DEFAULT_AGENT
31
+ },
32
+ apiBaseUrl: "https://clishop-backend.vercel.app/api",
33
+ outputFormat: "human",
34
+ setupCompleted: false
35
+ }
36
+ });
37
+ function getConfig() {
38
+ return config;
39
+ }
40
+ function getActiveAgent() {
41
+ const cfg = config.store;
42
+ const override = process.env.__CLISHOP_AGENT_OVERRIDE;
43
+ if (override && cfg.agents[override]) {
44
+ return cfg.agents[override];
45
+ }
46
+ return cfg.agents[cfg.activeAgent] || cfg.agents["default"];
47
+ }
48
+ function getAgent(name) {
49
+ return config.store.agents[name];
50
+ }
51
+ function setActiveAgent(name) {
52
+ if (!config.store.agents[name]) {
53
+ throw new Error(`Agent "${name}" does not exist. Create it first with: clishop agent create ${name}`);
54
+ }
55
+ config.set("activeAgent", name);
56
+ }
57
+ function createAgent(name, opts = {}) {
58
+ if (config.store.agents[name]) {
59
+ throw new Error(`Agent "${name}" already exists.`);
60
+ }
61
+ const agent = {
62
+ name,
63
+ requireConfirmation: true,
64
+ maxOrderAmount: 500,
65
+ allowedCategories: [],
66
+ blockedCategories: [],
67
+ ...opts
68
+ };
69
+ config.set(`agents.${name}`, agent);
70
+ return agent;
71
+ }
72
+ function updateAgent(name, opts) {
73
+ const existing = config.store.agents[name];
74
+ if (!existing) {
75
+ throw new Error(`Agent "${name}" does not exist.`);
76
+ }
77
+ const updated = { ...existing, ...opts, name };
78
+ config.set(`agents.${name}`, updated);
79
+ return updated;
80
+ }
81
+ function deleteAgent(name) {
82
+ if (name === "default") {
83
+ throw new Error('Cannot delete the "default" agent.');
84
+ }
85
+ if (!config.store.agents[name]) {
86
+ throw new Error(`Agent "${name}" does not exist.`);
87
+ }
88
+ const agents = { ...config.store.agents };
89
+ delete agents[name];
90
+ config.set("agents", agents);
91
+ if (config.store.activeAgent === name) {
92
+ config.set("activeAgent", "default");
93
+ }
94
+ }
95
+ function listAgents() {
96
+ return Object.values(config.store.agents);
97
+ }
98
+
99
+ // src/auth.ts
100
+ var SERVICE_NAME = "clishop";
101
+ var ACCOUNT_TOKEN = "auth-token";
102
+ var ACCOUNT_REFRESH = "refresh-token";
103
+ var ACCOUNT_USER = "user-info";
104
+ async function storeToken(token) {
105
+ await keytar.setPassword(SERVICE_NAME, ACCOUNT_TOKEN, token);
106
+ }
107
+ async function storeRefreshToken(token) {
108
+ await keytar.setPassword(SERVICE_NAME, ACCOUNT_REFRESH, token);
109
+ }
110
+ async function storeUserInfo(user) {
111
+ await keytar.setPassword(SERVICE_NAME, ACCOUNT_USER, JSON.stringify(user));
112
+ }
113
+ async function getToken() {
114
+ return keytar.getPassword(SERVICE_NAME, ACCOUNT_TOKEN);
115
+ }
116
+ async function getRefreshToken() {
117
+ return keytar.getPassword(SERVICE_NAME, ACCOUNT_REFRESH);
118
+ }
119
+ async function getUserInfo() {
120
+ const raw = await keytar.getPassword(SERVICE_NAME, ACCOUNT_USER);
121
+ if (!raw) return null;
122
+ try {
123
+ return JSON.parse(raw);
124
+ } catch {
125
+ return null;
126
+ }
127
+ }
128
+ async function clearAuth() {
129
+ await keytar.deletePassword(SERVICE_NAME, ACCOUNT_TOKEN);
130
+ await keytar.deletePassword(SERVICE_NAME, ACCOUNT_REFRESH);
131
+ await keytar.deletePassword(SERVICE_NAME, ACCOUNT_USER);
132
+ }
133
+ async function isLoggedIn() {
134
+ const token = await getToken();
135
+ return !!token;
136
+ }
137
+ async function login(email, password) {
138
+ const config2 = getConfig();
139
+ const baseUrl = config2.get("apiBaseUrl");
140
+ const res = await axios.post(`${baseUrl}/auth/login`, { email, password });
141
+ const { token, refreshToken, user } = res.data;
142
+ await storeToken(token);
143
+ if (refreshToken) await storeRefreshToken(refreshToken);
144
+ await storeUserInfo(user);
145
+ return user;
146
+ }
147
+ async function register(email, password, name) {
148
+ const config2 = getConfig();
149
+ const baseUrl = config2.get("apiBaseUrl");
150
+ const res = await axios.post(`${baseUrl}/auth/register`, { email, password, name });
151
+ const { token, refreshToken, user } = res.data;
152
+ await storeToken(token);
153
+ if (refreshToken) await storeRefreshToken(refreshToken);
154
+ await storeUserInfo(user);
155
+ return user;
156
+ }
157
+ async function logout() {
158
+ const config2 = getConfig();
159
+ const baseUrl = config2.get("apiBaseUrl");
160
+ const token = await getToken();
161
+ if (token) {
162
+ try {
163
+ await axios.post(`${baseUrl}/auth/logout`, {}, {
164
+ headers: { Authorization: `Bearer ${token}` }
165
+ });
166
+ } catch {
167
+ }
168
+ }
169
+ await clearAuth();
170
+ }
171
+
172
+ // src/commands/auth.ts
173
+ function registerAuthCommands(program2) {
174
+ program2.command("login").description("Log in to your CLISHOP account").option("-e, --email <email>", "Email address").option("-p, --password <password>", "Password (omit for secure prompt)").action(async (opts) => {
175
+ try {
176
+ if (await isLoggedIn()) {
177
+ const user2 = await getUserInfo();
178
+ const { confirm } = await inquirer.prompt([
179
+ {
180
+ type: "confirm",
181
+ name: "confirm",
182
+ message: `You are already logged in as ${chalk.cyan(user2?.email || "unknown")}. Log in as a different user?`,
183
+ default: false
184
+ }
185
+ ]);
186
+ if (!confirm) return;
187
+ }
188
+ let email = opts.email;
189
+ let password = opts.password;
190
+ if (!email || !password) {
191
+ const answers = await inquirer.prompt([
192
+ ...!email ? [{ type: "input", name: "email", message: "Email:" }] : [],
193
+ ...!password ? [{ type: "password", name: "password", message: "Password:", mask: "*" }] : []
194
+ ]);
195
+ email = email || answers.email;
196
+ password = password || answers.password;
197
+ }
198
+ const spinner = ora("Logging in...").start();
199
+ const user = await login(email, password);
200
+ spinner.succeed(chalk.green(`Logged in as ${chalk.bold(user.name)} (${user.email})`));
201
+ } catch (error) {
202
+ const msg = error?.response?.data?.message || error.message;
203
+ console.error(chalk.red(`
204
+ \u2717 Login failed: ${msg}`));
205
+ process.exitCode = 1;
206
+ }
207
+ });
208
+ program2.command("register").description("Create a new CLISHOP account").action(async () => {
209
+ try {
210
+ const answers = await inquirer.prompt([
211
+ { type: "input", name: "name", message: "Full name:" },
212
+ { type: "input", name: "email", message: "Email:" },
213
+ {
214
+ type: "password",
215
+ name: "password",
216
+ message: "Password:",
217
+ mask: "*"
218
+ },
219
+ {
220
+ type: "password",
221
+ name: "confirmPassword",
222
+ message: "Confirm password:",
223
+ mask: "*"
224
+ }
225
+ ]);
226
+ if (answers.password !== answers.confirmPassword) {
227
+ console.error(chalk.red("\u2717 Passwords do not match."));
228
+ process.exitCode = 1;
229
+ return;
230
+ }
231
+ const spinner = ora("Creating account...").start();
232
+ const user = await register(answers.email, answers.password, answers.name);
233
+ spinner.succeed(chalk.green(`Account created! Welcome, ${chalk.bold(user.name)}.`));
234
+ } catch (error) {
235
+ const msg = error?.response?.data?.message || error.message;
236
+ console.error(chalk.red(`
237
+ \u2717 Registration failed: ${msg}`));
238
+ process.exitCode = 1;
239
+ }
240
+ });
241
+ program2.command("logout").description("Log out of your CLISHOP account").action(async () => {
242
+ const spinner = ora("Logging out...").start();
243
+ await logout();
244
+ spinner.succeed(chalk.green("Logged out."));
245
+ });
246
+ program2.command("whoami").description("Show the currently logged-in user").action(async () => {
247
+ if (!await isLoggedIn()) {
248
+ console.log(chalk.yellow("Not logged in. Run: clishop login"));
249
+ return;
250
+ }
251
+ const user = await getUserInfo();
252
+ if (user) {
253
+ console.log(chalk.cyan(` Name: ${user.name}`));
254
+ console.log(chalk.cyan(` Email: ${user.email}`));
255
+ console.log(chalk.cyan(` ID: ${user.id}`));
256
+ } else {
257
+ console.log(chalk.yellow("Logged in but user info unavailable."));
258
+ }
259
+ });
260
+ }
261
+
262
+ // src/commands/agent.ts
263
+ import chalk2 from "chalk";
264
+ import inquirer2 from "inquirer";
265
+ function printAgent(agent, isActive) {
266
+ const marker = isActive ? chalk2.green("\u25CF ") : " ";
267
+ console.log(`${marker}${chalk2.bold(agent.name)}`);
268
+ console.log(` Max order amount: ${agent.maxOrderAmount != null ? `$${agent.maxOrderAmount}` : chalk2.dim("none")}`);
269
+ console.log(` Require confirmation: ${agent.requireConfirmation ? chalk2.green("yes") : chalk2.yellow("no")}`);
270
+ console.log(` Allowed categories: ${agent.allowedCategories?.length ? agent.allowedCategories.join(", ") : chalk2.dim("all")}`);
271
+ console.log(` Blocked categories: ${agent.blockedCategories?.length ? agent.blockedCategories.join(", ") : chalk2.dim("none")}`);
272
+ console.log(` Default address: ${agent.defaultAddressId || chalk2.dim("not set")}`);
273
+ console.log(` Default payment: ${agent.defaultPaymentMethodId || chalk2.dim("not set")}`);
274
+ console.log();
275
+ }
276
+ function registerAgentCommands(program2) {
277
+ const agent = program2.command("agent").description("Manage agents (safety profiles for ordering)");
278
+ agent.command("list").alias("ls").description("List all agents").action(() => {
279
+ const agents = listAgents();
280
+ const active = getConfig().get("activeAgent");
281
+ console.log(chalk2.bold("\nAgents:\n"));
282
+ for (const a of agents) {
283
+ printAgent(a, a.name === active);
284
+ }
285
+ console.log(chalk2.dim(`Active agent marked with ${chalk2.green("\u25CF")}`));
286
+ });
287
+ agent.command("create <name>").description("Create a new agent").option("--max-amount <amount>", "Max order amount", parseFloat).option("--no-confirm", "Don't require confirmation before ordering").action(async (name, opts) => {
288
+ try {
289
+ const newAgent = createAgent(name, {
290
+ maxOrderAmount: opts.maxAmount,
291
+ requireConfirmation: opts.confirm !== false
292
+ });
293
+ console.log(chalk2.green(`
294
+ \u2713 Agent "${newAgent.name}" created.
295
+ `));
296
+ printAgent(newAgent, false);
297
+ } catch (error) {
298
+ console.error(chalk2.red(`
299
+ \u2717 ${error.message}`));
300
+ process.exitCode = 1;
301
+ }
302
+ });
303
+ agent.command("use <name>").description("Switch the active agent").action((name) => {
304
+ try {
305
+ setActiveAgent(name);
306
+ console.log(chalk2.green(`
307
+ \u2713 Active agent set to "${name}".`));
308
+ } catch (error) {
309
+ console.error(chalk2.red(`
310
+ \u2717 ${error.message}`));
311
+ process.exitCode = 1;
312
+ }
313
+ });
314
+ agent.command("show [name]").description("Show details of an agent (defaults to active)").action((name) => {
315
+ const agentName = name || getConfig().get("activeAgent");
316
+ const a = getAgent(agentName);
317
+ if (!a) {
318
+ console.error(chalk2.red(`
319
+ \u2717 Agent "${agentName}" not found.`));
320
+ process.exitCode = 1;
321
+ return;
322
+ }
323
+ console.log();
324
+ printAgent(a, agentName === getConfig().get("activeAgent"));
325
+ });
326
+ agent.command("update [name]").description("Update an agent's settings (interactive)").action(async (name) => {
327
+ const agentName = name || getConfig().get("activeAgent");
328
+ const existing = getAgent(agentName);
329
+ if (!existing) {
330
+ console.error(chalk2.red(`
331
+ \u2717 Agent "${agentName}" not found.`));
332
+ process.exitCode = 1;
333
+ return;
334
+ }
335
+ const answers = await inquirer2.prompt([
336
+ {
337
+ type: "number",
338
+ name: "maxOrderAmount",
339
+ message: "Max order amount ($):",
340
+ default: existing.maxOrderAmount
341
+ },
342
+ {
343
+ type: "confirm",
344
+ name: "requireConfirmation",
345
+ message: "Require confirmation before ordering?",
346
+ default: existing.requireConfirmation
347
+ },
348
+ {
349
+ type: "input",
350
+ name: "allowedCategories",
351
+ message: "Allowed categories (comma-separated, empty = all):",
352
+ default: existing.allowedCategories?.join(", ") || ""
353
+ },
354
+ {
355
+ type: "input",
356
+ name: "blockedCategories",
357
+ message: "Blocked categories (comma-separated, empty = none):",
358
+ default: existing.blockedCategories?.join(", ") || ""
359
+ }
360
+ ]);
361
+ const updated = updateAgent(agentName, {
362
+ maxOrderAmount: answers.maxOrderAmount,
363
+ requireConfirmation: answers.requireConfirmation,
364
+ allowedCategories: answers.allowedCategories ? answers.allowedCategories.split(",").map((s) => s.trim()).filter(Boolean) : [],
365
+ blockedCategories: answers.blockedCategories ? answers.blockedCategories.split(",").map((s) => s.trim()).filter(Boolean) : []
366
+ });
367
+ console.log(chalk2.green(`
368
+ \u2713 Agent "${agentName}" updated.
369
+ `));
370
+ printAgent(updated, agentName === getConfig().get("activeAgent"));
371
+ });
372
+ agent.command("delete <name>").alias("rm").description("Delete an agent").action(async (name) => {
373
+ try {
374
+ const { confirm } = await inquirer2.prompt([
375
+ {
376
+ type: "confirm",
377
+ name: "confirm",
378
+ message: `Delete agent "${name}"? This cannot be undone.`,
379
+ default: false
380
+ }
381
+ ]);
382
+ if (!confirm) return;
383
+ deleteAgent(name);
384
+ console.log(chalk2.green(`
385
+ \u2713 Agent "${name}" deleted.`));
386
+ } catch (error) {
387
+ console.error(chalk2.red(`
388
+ \u2717 ${error.message}`));
389
+ process.exitCode = 1;
390
+ }
391
+ });
392
+ }
393
+
394
+ // src/commands/address.ts
395
+ import chalk4 from "chalk";
396
+ import ora2 from "ora";
397
+ import inquirer3 from "inquirer";
398
+
399
+ // src/api.ts
400
+ import axios2 from "axios";
401
+ import chalk3 from "chalk";
402
+ var client = null;
403
+ var API_BASE_URL = process.env.CLISHOP_API_URL || "https://clishop-backend.vercel.app/api";
404
+ function getApiClient() {
405
+ if (client) return client;
406
+ const baseUrl = API_BASE_URL;
407
+ client = axios2.create({
408
+ baseURL: baseUrl,
409
+ timeout: 3e4,
410
+ headers: {
411
+ "Content-Type": "application/json"
412
+ }
413
+ });
414
+ client.interceptors.request.use(async (reqConfig) => {
415
+ const token = await getToken();
416
+ if (token) {
417
+ reqConfig.headers.Authorization = `Bearer ${token}`;
418
+ }
419
+ return reqConfig;
420
+ });
421
+ client.interceptors.response.use(
422
+ (res) => res,
423
+ async (error) => {
424
+ if (error.response?.status === 401) {
425
+ const refreshToken = await getRefreshToken();
426
+ if (refreshToken) {
427
+ try {
428
+ const res = await axios2.post(`${baseUrl}/auth/refresh`, { refreshToken });
429
+ await storeToken(res.data.token);
430
+ if (error.config) {
431
+ error.config.headers.Authorization = `Bearer ${res.data.token}`;
432
+ return axios2(error.config);
433
+ }
434
+ } catch {
435
+ }
436
+ }
437
+ console.error(chalk3.red("\n\u2717 Session expired. Please login again: clishop login\n"));
438
+ process.exit(1);
439
+ }
440
+ throw error;
441
+ }
442
+ );
443
+ return client;
444
+ }
445
+ function handleApiError(error) {
446
+ if (axios2.isAxiosError(error)) {
447
+ const data = error.response?.data;
448
+ const message = data?.message || data?.error || error.message;
449
+ const status = error.response?.status;
450
+ if (status === 422 && data?.errors) {
451
+ console.error(chalk3.red("\n\u2717 Validation errors:"));
452
+ for (const [field, msgs] of Object.entries(data.errors)) {
453
+ console.error(chalk3.red(` ${field}: ${msgs.join(", ")}`));
454
+ }
455
+ } else if (status === 404) {
456
+ console.error(chalk3.red(`
457
+ \u2717 Not found: ${message}`));
458
+ } else {
459
+ console.error(chalk3.red(`
460
+ \u2717 API error (${status || "network"}): ${message}`));
461
+ }
462
+ } else if (error instanceof Error) {
463
+ console.error(chalk3.red(`
464
+ \u2717 ${error.message}`));
465
+ } else {
466
+ console.error(chalk3.red("\n\u2717 An unexpected error occurred."));
467
+ }
468
+ process.exit(1);
469
+ }
470
+
471
+ // src/commands/address.ts
472
+ function registerAddressCommands(program2) {
473
+ const address = program2.command("address").description("Manage shipping addresses (scoped to the active agent)");
474
+ address.command("list").alias("ls").description("List all addresses for the active agent").action(async () => {
475
+ try {
476
+ const agent = getActiveAgent();
477
+ const spinner = ora2("Fetching addresses...").start();
478
+ const api = getApiClient();
479
+ const res = await api.get("/addresses", {
480
+ params: { agent: agent.name }
481
+ });
482
+ spinner.stop();
483
+ const addresses = res.data.addresses;
484
+ if (addresses.length === 0) {
485
+ console.log(chalk4.yellow("\nNo addresses found. Add one with: clishop address add\n"));
486
+ return;
487
+ }
488
+ console.log(chalk4.bold(`
489
+ Addresses for agent "${agent.name}":
490
+ `));
491
+ for (const addr of addresses) {
492
+ const isDefault = addr.id === agent.defaultAddressId;
493
+ const marker = isDefault ? chalk4.green("\u25CF ") : " ";
494
+ console.log(`${marker}${chalk4.bold(addr.label)} ${chalk4.dim(`(${addr.id})`)}`);
495
+ if (addr.recipientName) console.log(` ${addr.recipientName}`);
496
+ if (addr.companyName) console.log(` ${chalk4.cyan(addr.companyName)}`);
497
+ console.log(` ${addr.line1}`);
498
+ if (addr.line2) console.log(` ${addr.line2}`);
499
+ console.log(` ${addr.city}${addr.region ? `, ${addr.region}` : ""} ${addr.postalCode}`);
500
+ console.log(` ${addr.country}`);
501
+ if (addr.recipientPhone) console.log(` ${chalk4.dim("Phone:")} ${addr.recipientPhone}`);
502
+ if (addr.vatNumber) console.log(` ${chalk4.dim("VAT:")} ${addr.vatNumber}`);
503
+ if (addr.taxId) console.log(` ${chalk4.dim("Tax ID:")} ${addr.taxId}`);
504
+ if (addr.instructions) console.log(` ${chalk4.dim("Instructions:")} ${addr.instructions}`);
505
+ console.log();
506
+ }
507
+ } catch (error) {
508
+ handleApiError(error);
509
+ }
510
+ });
511
+ address.command("add").description("Add a new address").action(async () => {
512
+ try {
513
+ const agent = getActiveAgent();
514
+ const answers = await inquirer3.prompt([
515
+ { type: "input", name: "label", message: "Label (e.g. Home, Office):" },
516
+ { type: "input", name: "recipientName", message: "Recipient name (optional):" },
517
+ { type: "input", name: "recipientPhone", message: "Recipient phone (optional):" },
518
+ {
519
+ type: "input",
520
+ name: "line1",
521
+ message: "Street name and number:",
522
+ validate: (v) => v.trim() ? true : "Required"
523
+ },
524
+ { type: "input", name: "line2", message: "Apartment, suite, floor, etc. (optional):" },
525
+ {
526
+ type: "input",
527
+ name: "postalCode",
528
+ message: "Postal / ZIP code:",
529
+ validate: (v) => v.trim() ? true : "Required"
530
+ },
531
+ {
532
+ type: "input",
533
+ name: "city",
534
+ message: "City:",
535
+ validate: (v) => v.trim() ? true : "Required"
536
+ },
537
+ { type: "input", name: "region", message: "State / Province / Region (optional):" },
538
+ {
539
+ type: "input",
540
+ name: "country",
541
+ message: "Country:",
542
+ validate: (v) => v.trim() ? true : "Required"
543
+ },
544
+ { type: "input", name: "instructions", message: "Delivery instructions (optional):" },
545
+ {
546
+ type: "confirm",
547
+ name: "isCompany",
548
+ message: "Is this a company/business address?",
549
+ default: false
550
+ }
551
+ ]);
552
+ let companyAnswers = { companyName: "", vatNumber: "", taxId: "" };
553
+ if (answers.isCompany) {
554
+ companyAnswers = await inquirer3.prompt([
555
+ {
556
+ type: "input",
557
+ name: "companyName",
558
+ message: "Company name:",
559
+ validate: (v) => v.trim() ? true : "Required for company addresses"
560
+ },
561
+ { type: "input", name: "vatNumber", message: "VAT number (optional):" },
562
+ { type: "input", name: "taxId", message: "Tax ID / EIN (optional):" }
563
+ ]);
564
+ }
565
+ const { setDefault } = await inquirer3.prompt([
566
+ {
567
+ type: "confirm",
568
+ name: "setDefault",
569
+ message: "Set as default address for this agent?",
570
+ default: true
571
+ }
572
+ ]);
573
+ const spinner = ora2("Saving address...").start();
574
+ const api = getApiClient();
575
+ const res = await api.post("/addresses", {
576
+ agent: agent.name,
577
+ label: answers.label,
578
+ recipientName: answers.recipientName || void 0,
579
+ recipientPhone: answers.recipientPhone || void 0,
580
+ companyName: companyAnswers.companyName || void 0,
581
+ vatNumber: companyAnswers.vatNumber || void 0,
582
+ taxId: companyAnswers.taxId || void 0,
583
+ line1: answers.line1,
584
+ line2: answers.line2 || void 0,
585
+ city: answers.city,
586
+ region: answers.region || void 0,
587
+ postalCode: answers.postalCode,
588
+ country: answers.country,
589
+ instructions: answers.instructions || void 0
590
+ });
591
+ if (setDefault) {
592
+ updateAgent(agent.name, { defaultAddressId: res.data.address.id });
593
+ }
594
+ spinner.succeed(chalk4.green(`Address "${answers.label}" added.`));
595
+ } catch (error) {
596
+ handleApiError(error);
597
+ }
598
+ });
599
+ address.command("remove <id>").alias("rm").description("Remove an address by ID").action(async (id) => {
600
+ try {
601
+ const { confirm } = await inquirer3.prompt([
602
+ {
603
+ type: "confirm",
604
+ name: "confirm",
605
+ message: `Delete address ${id}?`,
606
+ default: false
607
+ }
608
+ ]);
609
+ if (!confirm) return;
610
+ const spinner = ora2("Removing address...").start();
611
+ const api = getApiClient();
612
+ await api.delete(`/addresses/${id}`);
613
+ spinner.succeed(chalk4.green("Address removed."));
614
+ const agent = getActiveAgent();
615
+ if (agent.defaultAddressId === id) {
616
+ updateAgent(agent.name, { defaultAddressId: void 0 });
617
+ }
618
+ } catch (error) {
619
+ handleApiError(error);
620
+ }
621
+ });
622
+ address.command("set-default <id>").description("Set the default address for the active agent").action((id) => {
623
+ const agent = getActiveAgent();
624
+ updateAgent(agent.name, { defaultAddressId: id });
625
+ console.log(chalk4.green(`
626
+ \u2713 Default address for agent "${agent.name}" set to ${id}.`));
627
+ });
628
+ }
629
+
630
+ // src/commands/payment.ts
631
+ import chalk5 from "chalk";
632
+ import ora3 from "ora";
633
+ function registerPaymentCommands(program2) {
634
+ const payment = program2.command("payment").description("Manage payment methods (scoped to the active agent)");
635
+ payment.command("list").alias("ls").description("List payment methods for the active agent").action(async () => {
636
+ try {
637
+ const agent = getActiveAgent();
638
+ const spinner = ora3("Fetching payment methods...").start();
639
+ const api = getApiClient();
640
+ const res = await api.get("/payment-methods", {
641
+ params: { agent: agent.name }
642
+ });
643
+ spinner.stop();
644
+ const methods = res.data.paymentMethods;
645
+ if (methods.length === 0) {
646
+ console.log(chalk5.yellow("\nNo payment methods found. Add one with: clishop payment add\n"));
647
+ return;
648
+ }
649
+ console.log(chalk5.bold(`
650
+ Payment methods for agent "${agent.name}":
651
+ `));
652
+ for (const pm of methods) {
653
+ const isDefault = pm.id === agent.defaultPaymentMethodId;
654
+ const marker = isDefault ? chalk5.green("\u25CF ") : " ";
655
+ const last4 = pm.last4 ? ` \u2022\u2022\u2022\u2022 ${pm.last4}` : "";
656
+ console.log(`${marker}${chalk5.bold(pm.label)}${last4} ${chalk5.dim(`[${pm.type}]`)} ${chalk5.dim(`(${pm.id})`)}`);
657
+ }
658
+ console.log();
659
+ } catch (error) {
660
+ handleApiError(error);
661
+ }
662
+ });
663
+ payment.command("add").description("Add a payment method (opens browser for secure entry)").action(async () => {
664
+ try {
665
+ const agent = getActiveAgent();
666
+ const spinner = ora3("Requesting secure payment setup link...").start();
667
+ const api = getApiClient();
668
+ const res = await api.post("/payment-methods/setup", {
669
+ agent: agent.name
670
+ });
671
+ spinner.stop();
672
+ const { setupUrl } = res.data;
673
+ console.log(chalk5.bold("\nTo add a payment method securely, open this link in your browser:\n"));
674
+ console.log(chalk5.cyan.underline(` ${setupUrl}
675
+ `));
676
+ console.log(chalk5.dim("The CLI never collects raw card details. Payment is set up via the secure web portal."));
677
+ console.log(chalk5.dim("Once completed, run 'clishop payment list' to see your new method.\n"));
678
+ } catch (error) {
679
+ handleApiError(error);
680
+ }
681
+ });
682
+ payment.command("remove <id>").alias("rm").description("Remove a payment method").action(async (id) => {
683
+ try {
684
+ const spinner = ora3("Removing payment method...").start();
685
+ const api = getApiClient();
686
+ await api.delete(`/payment-methods/${id}`);
687
+ spinner.succeed(chalk5.green("Payment method removed."));
688
+ const agent = getActiveAgent();
689
+ if (agent.defaultPaymentMethodId === id) {
690
+ updateAgent(agent.name, { defaultPaymentMethodId: void 0 });
691
+ }
692
+ } catch (error) {
693
+ handleApiError(error);
694
+ }
695
+ });
696
+ payment.command("set-default <id>").description("Set the default payment method for the active agent").action((id) => {
697
+ const agent = getActiveAgent();
698
+ updateAgent(agent.name, { defaultPaymentMethodId: id });
699
+ console.log(chalk5.green(`
700
+ \u2713 Default payment for agent "${agent.name}" set to ${id}.`));
701
+ });
702
+ }
703
+
704
+ // src/commands/search.ts
705
+ import chalk6 from "chalk";
706
+ import ora4 from "ora";
707
+ import inquirer4 from "inquirer";
708
+ function formatPrice(cents, currency) {
709
+ return new Intl.NumberFormat("en-US", {
710
+ style: "currency",
711
+ currency
712
+ }).format(cents / 100);
713
+ }
714
+ var rateCache = null;
715
+ async function fetchRates(baseCurrency) {
716
+ if (rateCache && rateCache.base === baseCurrency && Date.now() - rateCache.fetchedAt < 36e5) {
717
+ return rateCache.rates;
718
+ }
719
+ try {
720
+ const res = await fetch(`https://open.er-api.com/v6/latest/${baseCurrency}`);
721
+ const data = await res.json();
722
+ if (data.rates) {
723
+ rateCache = { base: baseCurrency, rates: data.rates, fetchedAt: Date.now() };
724
+ return data.rates;
725
+ }
726
+ } catch {
727
+ }
728
+ return {};
729
+ }
730
+ function convertPrice(cents, fromCurrency, toCurrency, rates) {
731
+ if (fromCurrency === toCurrency) return null;
732
+ const rate = rates[toCurrency];
733
+ if (!rate) return null;
734
+ return Math.round(cents * rate);
735
+ }
736
+ function formatConverted(cents, fromCurrency, toCurrency, rates) {
737
+ const converted = convertPrice(cents, fromCurrency, toCurrency, rates);
738
+ if (converted == null) return "";
739
+ return chalk6.dim(` (~${formatPrice(converted, toCurrency)})`);
740
+ }
741
+ function renderStars(rating) {
742
+ const full = Math.floor(rating);
743
+ const half = rating - full >= 0.5 ? 1 : 0;
744
+ const empty = 5 - full - half;
745
+ return "\u2605".repeat(full) + (half ? "\xBD" : "") + "\u2606".repeat(empty);
746
+ }
747
+ function deliveryLabel(days) {
748
+ if (days <= 0) return "Same-day";
749
+ if (days === 1) return "Next-day";
750
+ if (days === 2) return "2-day";
751
+ return `${days}-day`;
752
+ }
753
+ function estimatedArrival(days) {
754
+ const d = /* @__PURE__ */ new Date();
755
+ d.setDate(d.getDate() + days);
756
+ return d.toLocaleDateString("en-US", { month: "short", day: "numeric" });
757
+ }
758
+ function scoreOutOf10(rating, maxScale = 5) {
759
+ return (rating / maxScale * 10).toFixed(1);
760
+ }
761
+ function renderFreeFormInfo(data, indent = 0) {
762
+ const pad = " ".repeat(indent);
763
+ if (data == null) return;
764
+ if (typeof data === "string") {
765
+ if (data.length > 100) {
766
+ const words = data.split(/\s+/);
767
+ let line = "";
768
+ for (const word of words) {
769
+ if (line.length + word.length + 1 > 90) {
770
+ console.log(`${pad}${chalk6.dim(line)}`);
771
+ line = word;
772
+ } else {
773
+ line = line ? `${line} ${word}` : word;
774
+ }
775
+ }
776
+ if (line) console.log(`${pad}${chalk6.dim(line)}`);
777
+ } else {
778
+ console.log(`${pad}${data}`);
779
+ }
780
+ return;
781
+ }
782
+ if (typeof data === "number" || typeof data === "boolean") {
783
+ console.log(`${pad}${data}`);
784
+ return;
785
+ }
786
+ if (Array.isArray(data)) {
787
+ for (const item of data) {
788
+ if (typeof item === "string") {
789
+ console.log(`${pad}${chalk6.dim("\u2022")} ${item}`);
790
+ } else if (typeof item === "object" && item !== null) {
791
+ renderFreeFormInfo(item, indent + 2);
792
+ console.log();
793
+ } else {
794
+ console.log(`${pad}${chalk6.dim("\u2022")} ${String(item)}`);
795
+ }
796
+ }
797
+ return;
798
+ }
799
+ if (typeof data === "object") {
800
+ for (const [key, value] of Object.entries(data)) {
801
+ if (key === "product_id" || key === "error" || key === "available") continue;
802
+ const label = key.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
803
+ if (value == null) continue;
804
+ if (typeof value === "string") {
805
+ if (value.length > 80) {
806
+ console.log(`${pad}${chalk6.bold(label + ":")}`);
807
+ renderFreeFormInfo(value, indent + 4);
808
+ } else {
809
+ console.log(`${pad}${chalk6.bold(label + ":")} ${value}`);
810
+ }
811
+ } else if (typeof value === "number") {
812
+ console.log(`${pad}${chalk6.bold(label + ":")} ${value}`);
813
+ } else if (typeof value === "boolean") {
814
+ console.log(`${pad}${chalk6.bold(label + ":")} ${value ? chalk6.green("Yes") : chalk6.red("No")}`);
815
+ } else if (Array.isArray(value)) {
816
+ console.log(`${pad}${chalk6.bold(label + ":")}`);
817
+ renderFreeFormInfo(value, indent + 4);
818
+ } else if (typeof value === "object") {
819
+ console.log(`${pad}${chalk6.bold(label + ":")}`);
820
+ renderFreeFormInfo(value, indent + 4);
821
+ }
822
+ }
823
+ }
824
+ }
825
+ function renderProductInfo(result, index, totalResults, formatPriceFn) {
826
+ const num = index + 1;
827
+ const storeBadge = result.storeName ? chalk6.dim(` from ${result.storeName}`) : "";
828
+ console.log(
829
+ ` ${chalk6.dim(`[${num}]`)} ${chalk6.bold.cyan(result.info?.title || result.info?.product_id || result.productId)}${storeBadge}`
830
+ );
831
+ console.log(` ${chalk6.dim(`ID: ${result.productId}`)}`);
832
+ if (result.error) {
833
+ console.log(` ${chalk6.red(`Error: ${result.error}`)}`);
834
+ console.log();
835
+ return;
836
+ }
837
+ if (result.info?.product_url) {
838
+ console.log(` ${chalk6.blue.underline(result.info.product_url)}`);
839
+ }
840
+ console.log();
841
+ const info = result.info || {};
842
+ if (info.price) {
843
+ const priceStr = info.price.amount && info.price.currency ? formatPriceFn(Math.round(parseFloat(info.price.amount) * 100), info.price.currency) : `${info.price.amount || "N/A"}`;
844
+ let priceLine = ` ${chalk6.bold("Price:")} ${chalk6.bold.white(priceStr)}`;
845
+ if (info.list_price?.amount) {
846
+ const listStr = formatPriceFn(
847
+ Math.round(parseFloat(info.list_price.amount) * 100),
848
+ info.list_price.currency || info.price.currency
849
+ );
850
+ priceLine += chalk6.dim.strikethrough(` ${listStr}`);
851
+ }
852
+ console.log(priceLine);
853
+ }
854
+ if (info.pricing && !info.price) {
855
+ const priceStr = info.pricing.amount && info.pricing.currency ? formatPriceFn(Math.round(parseFloat(info.pricing.amount) * 100), info.pricing.currency) : `${info.pricing.amount || "N/A"}`;
856
+ let priceLine = ` ${chalk6.bold("Price:")} ${chalk6.bold.white(priceStr)}`;
857
+ if (info.pricing.compare_at) {
858
+ const listStr = formatPriceFn(
859
+ Math.round(parseFloat(info.pricing.compare_at) * 100),
860
+ info.pricing.currency
861
+ );
862
+ priceLine += chalk6.dim.strikethrough(` ${listStr}`);
863
+ }
864
+ console.log(priceLine);
865
+ }
866
+ if (info.rating) {
867
+ const ratingScore = typeof info.rating === "object" ? `${info.rating.score}/${info.rating.max}` : String(info.rating);
868
+ let ratingLine = ` ${chalk6.bold("Rating:")} ${chalk6.yellow(ratingScore)}`;
869
+ if (info.review_count) {
870
+ ratingLine += chalk6.dim(` (${info.review_count.toLocaleString()} reviews)`);
871
+ }
872
+ console.log(ratingLine);
873
+ }
874
+ if (info.brand) {
875
+ console.log(` ${chalk6.bold("Brand:")} ${info.brand}`);
876
+ }
877
+ if (info.marketplace) {
878
+ console.log(` ${chalk6.bold("Marketplace:")} ${info.marketplace.name || info.marketplace.domain || ""}`);
879
+ }
880
+ if (info.availability) {
881
+ if (typeof info.availability === "string") {
882
+ const isInStock = info.availability.toLowerCase().includes("in stock");
883
+ console.log(` ${chalk6.bold("Availability:")} ${isInStock ? chalk6.green(info.availability) : chalk6.yellow(info.availability)}`);
884
+ } else if (typeof info.availability === "object") {
885
+ const status = info.availability.in_stock ? chalk6.green("In Stock") : chalk6.red("Out of Stock");
886
+ let availLine = ` ${chalk6.bold("Availability:")} ${status}`;
887
+ if (info.availability.quantity != null) {
888
+ availLine += chalk6.dim(` (${info.availability.quantity} available)`);
889
+ }
890
+ console.log(availLine);
891
+ }
892
+ }
893
+ if (info.prime) {
894
+ console.log(` ${chalk6.bold("Prime:")} ${chalk6.blue("\u2713 Prime eligible")}`);
895
+ }
896
+ if (info.shipping && typeof info.shipping === "object") {
897
+ const parts = [];
898
+ if (info.shipping.free) parts.push(chalk6.green("Free Shipping"));
899
+ if (info.shipping.estimated_days) parts.push(`${info.shipping.estimated_days}-day delivery`);
900
+ if (info.shipping.price?.amount) parts.push(`${info.shipping.price.amount} ${info.shipping.price.currency || ""}`);
901
+ if (info.shipping.weight_kg) parts.push(`${info.shipping.weight_kg}kg`);
902
+ if (parts.length > 0) {
903
+ console.log(` ${chalk6.bold("Shipping:")} ${parts.join(" \xB7 ")}`);
904
+ }
905
+ }
906
+ if (info.delivery_info) {
907
+ console.log(` ${chalk6.bold("Delivery:")} ${info.delivery_info}`);
908
+ }
909
+ if (info.returns && typeof info.returns === "object") {
910
+ const parts = [];
911
+ if (info.returns.free) parts.push(chalk6.green("Free Returns"));
912
+ if (info.returns.window_days) parts.push(`${info.returns.window_days}-day window`);
913
+ if (info.returns.note) parts.push(info.returns.note);
914
+ if (parts.length > 0) {
915
+ console.log(` ${chalk6.bold("Returns:")} ${parts.join(" \xB7 ")}`);
916
+ }
917
+ }
918
+ if (info.checkout && typeof info.checkout === "object") {
919
+ const parts = [];
920
+ if (info.checkout.mode) parts.push(info.checkout.mode);
921
+ if (info.checkout.note) parts.push(info.checkout.note);
922
+ if (parts.length > 0) {
923
+ console.log(` ${chalk6.bold("Checkout:")} ${parts.join(" \u2014 ")}`);
924
+ }
925
+ }
926
+ if (info.sold_by) {
927
+ console.log(` ${chalk6.bold("Sold by:")} ${info.sold_by}`);
928
+ }
929
+ if (info.categories && Array.isArray(info.categories)) {
930
+ console.log(` ${chalk6.bold("Category:")} ${info.categories.join(" > ")}`);
931
+ }
932
+ console.log();
933
+ if (info.features && Array.isArray(info.features) && info.features.length > 0) {
934
+ console.log(` ${chalk6.bold("Key Features:")}`);
935
+ for (const feature of info.features) {
936
+ if (feature.length > 80) {
937
+ const words = feature.split(/\s+/);
938
+ let line = "";
939
+ let first = true;
940
+ for (const word of words) {
941
+ if (line.length + word.length + 1 > 76) {
942
+ if (first) {
943
+ console.log(` ${chalk6.dim("\u2022")} ${line}`);
944
+ first = false;
945
+ } else {
946
+ console.log(` ${line}`);
947
+ }
948
+ line = word;
949
+ } else {
950
+ line = line ? `${line} ${word}` : word;
951
+ }
952
+ }
953
+ if (line) {
954
+ if (first) {
955
+ console.log(` ${chalk6.dim("\u2022")} ${line}`);
956
+ } else {
957
+ console.log(` ${line}`);
958
+ }
959
+ }
960
+ } else {
961
+ console.log(` ${chalk6.dim("\u2022")} ${feature}`);
962
+ }
963
+ }
964
+ console.log();
965
+ }
966
+ if (info.description) {
967
+ console.log(` ${chalk6.bold("Description:")}`);
968
+ const words = info.description.split(/\s+/);
969
+ let line = "";
970
+ for (const word of words) {
971
+ if (line.length + word.length + 1 > 76) {
972
+ console.log(` ${chalk6.dim(line)}`);
973
+ line = word;
974
+ } else {
975
+ line = line ? `${line} ${word}` : word;
976
+ }
977
+ }
978
+ if (line) console.log(` ${chalk6.dim(line)}`);
979
+ console.log();
980
+ }
981
+ if (info.specifications && typeof info.specifications === "object") {
982
+ const specs = info.specifications;
983
+ const keys = Object.keys(specs);
984
+ if (keys.length > 0) {
985
+ console.log(` ${chalk6.bold("Specifications:")}`);
986
+ const maxKeyLen = Math.min(30, Math.max(...keys.map((k) => k.length)));
987
+ for (const [key, value] of Object.entries(specs)) {
988
+ const paddedKey = key.padEnd(maxKeyLen);
989
+ console.log(` ${chalk6.dim(paddedKey)} ${value}`);
990
+ }
991
+ console.log();
992
+ }
993
+ }
994
+ if (info.images && Array.isArray(info.images) && info.images.length > 0) {
995
+ console.log(` ${chalk6.bold("Images:")} ${chalk6.dim(`${info.images.length} available`)}`);
996
+ for (let j = 0; j < Math.min(3, info.images.length); j++) {
997
+ console.log(` ${chalk6.dim(`[${j + 1}]`)} ${chalk6.blue.underline(info.images[j])}`);
998
+ }
999
+ if (info.images.length > 3) {
1000
+ console.log(` ${chalk6.dim(`... and ${info.images.length - 3} more`)}`);
1001
+ }
1002
+ console.log();
1003
+ }
1004
+ if (info.about && Array.isArray(info.about) && info.about.length > 0) {
1005
+ console.log(` ${chalk6.bold("About This Item:")}`);
1006
+ for (const section of info.about) {
1007
+ const words = section.split(/\s+/);
1008
+ let line = "";
1009
+ for (const word of words) {
1010
+ if (line.length + word.length + 1 > 76) {
1011
+ console.log(` ${chalk6.dim(line)}`);
1012
+ line = word;
1013
+ } else {
1014
+ line = line ? `${line} ${word}` : word;
1015
+ }
1016
+ }
1017
+ if (line) console.log(` ${chalk6.dim(line)}`);
1018
+ console.log();
1019
+ }
1020
+ }
1021
+ if (info.seo && typeof info.seo === "object" && Object.keys(info.seo).length > 0) {
1022
+ if (info.seo.title || info.seo.description) {
1023
+ console.log(` ${chalk6.bold("SEO:")}`);
1024
+ if (info.seo.title) console.log(` Title: ${info.seo.title}`);
1025
+ if (info.seo.description) console.log(` Description: ${info.seo.description}`);
1026
+ console.log();
1027
+ }
1028
+ }
1029
+ const handledKeys = /* @__PURE__ */ new Set([
1030
+ "product_id",
1031
+ "title",
1032
+ "price",
1033
+ "list_price",
1034
+ "pricing",
1035
+ "rating",
1036
+ "review_count",
1037
+ "brand",
1038
+ "marketplace",
1039
+ "availability",
1040
+ "prime",
1041
+ "shipping",
1042
+ "delivery_info",
1043
+ "returns",
1044
+ "checkout",
1045
+ "sold_by",
1046
+ "categories",
1047
+ "features",
1048
+ "description",
1049
+ "specifications",
1050
+ "images",
1051
+ "about",
1052
+ "seo",
1053
+ "product_url",
1054
+ "asin",
1055
+ "error",
1056
+ "available",
1057
+ "note",
1058
+ "updated_at",
1059
+ "sku",
1060
+ "model",
1061
+ "gtin",
1062
+ "variant",
1063
+ "tags"
1064
+ ]);
1065
+ const extraKeys = Object.keys(info).filter((k) => !handledKeys.has(k));
1066
+ if (extraKeys.length > 0) {
1067
+ console.log(` ${chalk6.bold("Additional Information:")}`);
1068
+ for (const key of extraKeys) {
1069
+ const value = info[key];
1070
+ if (value == null) continue;
1071
+ const label = key.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
1072
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
1073
+ console.log(` ${chalk6.dim(label + ":")} ${value}`);
1074
+ } else if (Array.isArray(value)) {
1075
+ console.log(` ${chalk6.dim(label + ":")}`);
1076
+ renderFreeFormInfo(value, 10);
1077
+ } else if (typeof value === "object") {
1078
+ console.log(` ${chalk6.dim(label + ":")}`);
1079
+ renderFreeFormInfo(value, 10);
1080
+ }
1081
+ }
1082
+ console.log();
1083
+ }
1084
+ if (info.tags && Array.isArray(info.tags) && info.tags.length > 0) {
1085
+ console.log(` ${chalk6.bold("Tags:")} ${info.tags.map((t) => chalk6.dim(`#${t}`)).join(" ")}`);
1086
+ console.log();
1087
+ }
1088
+ if (info.note) {
1089
+ console.log(` ${chalk6.dim(`\u2139 ${info.note}`)}`);
1090
+ console.log();
1091
+ }
1092
+ if (index < totalResults - 1) {
1093
+ console.log(chalk6.dim(" " + "\u2500".repeat(60)));
1094
+ console.log();
1095
+ }
1096
+ }
1097
+ function registerSearchCommands(program2) {
1098
+ program2.command("search <query>").description("Search for products").option("-c, --category <category>", "Filter by category").option("--brand <brand>", "Filter by brand").option("--model <model>", "Filter by model name/number").option("--sku <sku>", "Filter by SKU").option("--gtin <gtin>", "Filter by GTIN (UPC/EAN/ISBN)").option("--variant <variant>", "Filter by variant (size/color/storage/etc.)").option("--min-price <price>", "Minimum price (cents)", parseFloat).option("--max-price <price>", "Maximum price (cents)", parseFloat).option("--max-shipping <price>", "Maximum shipping cost (cents)", parseInt).option("--max-total <price>", "Maximum landed total: item + shipping (cents)", parseInt).option("--free-shipping", "Only show items with free shipping").option("--ship-to <address>", "Saved address label or ID (resolves country/city/postal automatically)").option("--country <code>", "Delivery country (ISO 3166-1 alpha-2, e.g. US, BE, NL)").option("--city <city>", "Delivery city").option("--postal-code <code>", "Delivery postal/zip code").option("--region <region>", "Delivery state/province/region").option("--lat <latitude>", "Delivery latitude (for local/proximity search)", parseFloat).option("--lng <longitude>", "Delivery longitude (for local/proximity search)", parseFloat).option("--deliver-by <date>", "Need delivery by date (YYYY-MM-DD)").option("--max-delivery-days <days>", "Maximum delivery/transit days", parseInt).option("--in-stock", "Only show in-stock items").option("--exclude-backorder", "Exclude backordered items").option("--min-qty <qty>", "Minimum quantity available", parseInt).option("--free-returns", "Only show items with free returns").option("--min-return-window-days <days>", "Minimum return window in days", parseInt).option("--store <store>", "Limit to a store (ID, slug, or name)").option("--vendor <vendor>", "Filter by vendor name (alias for --store)").option("--trusted-only", "Only show products from verified stores").option("--min-store-rating <rating>", "Minimum store rating (0-5)", parseFloat).option("--checkout-mode <mode>", "Checkout mode: instant, handoff").option("--min-rating <rating>", "Minimum product rating (1-5)", parseFloat).option("-s, --sort <field>", "Sort by: price, total-cost, rating, relevance, newest, delivery", "relevance").option("--order <dir>", "Sort order: asc, desc", "desc").option("-p, --page <page>", "Page number", parseInt, 1).option("-n, --per-page <count>", "Results per page", parseInt, 10).option("--express", "Only show items with 2-day or faster delivery").option("-e, --extended-search", "Enable extended search: query darkstores when no local results found").option("--no-extended-search", "Disable automatic extended search when no local results found").option("--extended-timeout <seconds>", "Extended search timeout in seconds (default: 30, max: 60)", parseInt).option("--json", "Output raw JSON").option("--compact", "Compact one-line-per-result output").option("--detailed", "Show full product details inline").option("-i, --interactive", "After results, interactively select products to get more info from their store").action(async (query, opts) => {
1099
+ try {
1100
+ const spinner = ora4(`Searching for "${query}"...`).start();
1101
+ const api = getApiClient();
1102
+ let maxDeliveryDays = opts.maxDeliveryDays;
1103
+ if (!maxDeliveryDays && opts.deliverBy) {
1104
+ const target = new Date(opts.deliverBy);
1105
+ const now = /* @__PURE__ */ new Date();
1106
+ const diff = Math.ceil((target.getTime() - now.getTime()) / (1e3 * 60 * 60 * 24));
1107
+ if (diff > 0) maxDeliveryDays = diff;
1108
+ }
1109
+ let shipToCountry = opts.country || void 0;
1110
+ let shipToCity = opts.city || void 0;
1111
+ let shipToPostalCode = opts.postalCode || void 0;
1112
+ let shipToRegion = opts.region || void 0;
1113
+ let shipToLat = opts.lat || void 0;
1114
+ let shipToLng = opts.lng || void 0;
1115
+ if (opts.shipTo) {
1116
+ try {
1117
+ spinner.text = `Resolving address "${opts.shipTo}"...`;
1118
+ const addrRes = await api.get("/addresses");
1119
+ const addresses = addrRes.data.addresses || [];
1120
+ const match = addresses.find(
1121
+ (a) => a.id === opts.shipTo || a.label && a.label.toLowerCase() === opts.shipTo.toLowerCase()
1122
+ );
1123
+ if (match) {
1124
+ if (!shipToCountry) shipToCountry = match.country;
1125
+ if (!shipToCity) shipToCity = match.city;
1126
+ if (!shipToPostalCode) shipToPostalCode = match.postalCode;
1127
+ if (!shipToRegion) shipToRegion = match.region;
1128
+ } else {
1129
+ spinner.warn(`Address "${opts.shipTo}" not found \u2014 ignoring --ship-to`);
1130
+ spinner.start(`Searching for "${query}"...`);
1131
+ }
1132
+ } catch {
1133
+ }
1134
+ spinner.text = `Searching for "${query}"...`;
1135
+ }
1136
+ if (!shipToCountry && !shipToCity && !shipToPostalCode && !opts.shipTo) {
1137
+ const agent = getActiveAgent();
1138
+ if (agent.defaultAddressId) {
1139
+ try {
1140
+ spinner.text = `Resolving default address...`;
1141
+ const addrRes = await api.get("/addresses");
1142
+ const addresses = addrRes.data.addresses || [];
1143
+ const defaultAddr = addresses.find((a) => a.id === agent.defaultAddressId);
1144
+ if (defaultAddr) {
1145
+ shipToCountry = defaultAddr.country;
1146
+ shipToCity = defaultAddr.city;
1147
+ shipToPostalCode = defaultAddr.postalCode;
1148
+ shipToRegion = defaultAddr.region || void 0;
1149
+ spinner.text = `Searching for "${query}" (delivering to: ${[shipToCity, shipToCountry].filter(Boolean).join(", ")})...`;
1150
+ }
1151
+ } catch {
1152
+ }
1153
+ }
1154
+ }
1155
+ const forceExtended = opts.extendedSearch === true;
1156
+ const disableExtended = opts.extendedSearch === false;
1157
+ const extendedTimeout = opts.extendedTimeout ? Math.min(60, Math.max(5, opts.extendedTimeout)) : 30;
1158
+ if (opts.express && !maxDeliveryDays) {
1159
+ maxDeliveryDays = 2;
1160
+ }
1161
+ const searchParams = {
1162
+ q: query,
1163
+ // Product match
1164
+ category: opts.category,
1165
+ brand: opts.brand,
1166
+ model: opts.model,
1167
+ sku: opts.sku,
1168
+ gtin: opts.gtin,
1169
+ variant: opts.variant,
1170
+ // Cost
1171
+ minPrice: opts.minPrice,
1172
+ maxPrice: opts.maxPrice,
1173
+ maxShipping: opts.maxShipping,
1174
+ maxTotal: opts.maxTotal,
1175
+ freeShipping: opts.freeShipping || void 0,
1176
+ // Delivery location
1177
+ shipTo: opts.shipTo || void 0,
1178
+ country: shipToCountry,
1179
+ city: shipToCity,
1180
+ postalCode: shipToPostalCode,
1181
+ region: shipToRegion,
1182
+ lat: shipToLat,
1183
+ lng: shipToLng,
1184
+ maxDeliveryDays,
1185
+ // Availability
1186
+ inStock: opts.inStock || void 0,
1187
+ excludeBackorder: opts.excludeBackorder || void 0,
1188
+ minQty: opts.minQty,
1189
+ // Returns
1190
+ freeReturns: opts.freeReturns || void 0,
1191
+ minReturnDays: opts.minReturnWindowDays,
1192
+ // Trust
1193
+ store: opts.store,
1194
+ vendor: opts.vendor,
1195
+ trustedOnly: opts.trustedOnly || void 0,
1196
+ minStoreRating: opts.minStoreRating,
1197
+ checkoutMode: opts.checkoutMode,
1198
+ // Rating / sorting / pagination
1199
+ minRating: opts.minRating,
1200
+ sort: opts.sort === "total-cost" ? "price" : opts.sort,
1201
+ // backend doesn't know total-cost yet
1202
+ order: opts.order,
1203
+ page: opts.page,
1204
+ pageSize: opts.perPage
1205
+ };
1206
+ if (forceExtended) {
1207
+ searchParams.extendedSearch = true;
1208
+ searchParams.extendedTimeout = extendedTimeout;
1209
+ }
1210
+ const httpTimeout = forceExtended ? (extendedTimeout + 5) * 1e3 : 15e3;
1211
+ if (forceExtended) {
1212
+ const locationParts = [shipToCountry, shipToCity, shipToPostalCode].filter(Boolean);
1213
+ const locationLabel = locationParts.length > 0 ? locationParts.join(", ") : "global";
1214
+ spinner.text = `Searching for "${query}" (extended search: ${extendedTimeout}s timeout, deliver to: ${locationLabel})...`;
1215
+ }
1216
+ let res = await api.get("/products/search", {
1217
+ params: searchParams,
1218
+ timeout: httpTimeout
1219
+ });
1220
+ const regularResult = res.data;
1221
+ const regularExtended = res.data.extended;
1222
+ if (regularResult.products.length === 0 && (!regularExtended || regularExtended.total === 0) && !forceExtended && !disableExtended) {
1223
+ spinner.text = `No local results for "${query}". Starting extended search across all stores (${extendedTimeout}s timeout)...`;
1224
+ try {
1225
+ res = await api.get("/products/search", {
1226
+ params: {
1227
+ ...searchParams,
1228
+ extendedSearch: true,
1229
+ extendedTimeout
1230
+ },
1231
+ timeout: (extendedTimeout + 5) * 1e3
1232
+ });
1233
+ } catch (extErr) {
1234
+ spinner.warn(`Extended search failed \u2014 showing regular results only.`);
1235
+ }
1236
+ }
1237
+ spinner.stop();
1238
+ const result = res.data;
1239
+ if (opts.json) {
1240
+ console.log(JSON.stringify(result, null, 2));
1241
+ return;
1242
+ }
1243
+ const extended = res.data.extended;
1244
+ const suggestAdvertise = res.data.suggestAdvertise;
1245
+ const didExtendedSearch = forceExtended || res.data.extended != null;
1246
+ if (result.products.length === 0 && (!extended || extended.total === 0)) {
1247
+ if (didExtendedSearch) {
1248
+ console.log(chalk6.yellow(`
1249
+ No results found for "${query}" (searched local catalog + all vendor stores).`));
1250
+ } else {
1251
+ console.log(chalk6.yellow(`
1252
+ No results found for "${query}".`));
1253
+ }
1254
+ if (!didExtendedSearch && disableExtended) {
1255
+ console.log(
1256
+ chalk6.dim("\n \u{1F50D} Tip: ") + chalk6.white("Extended search was disabled. Enable it to query vendor stores in real-time:") + chalk6.dim(`
1257
+ Run: `) + chalk6.cyan(`clishop search "${query}" --extended-search`) + chalk6.dim("\n")
1258
+ );
1259
+ }
1260
+ console.log(
1261
+ chalk6.dim(" \u{1F4A1} Tip: ") + chalk6.white("Can't find what you need? Advertise your request and let vendors come to you!") + chalk6.dim(`
1262
+ Run: `) + chalk6.cyan(`clishop advertise create`) + chalk6.dim(` or `) + chalk6.cyan(`clishop advertise quick "${query}"`) + chalk6.dim("\n")
1263
+ );
1264
+ return;
1265
+ }
1266
+ const COUNTRY_CURRENCY = {
1267
+ US: "USD",
1268
+ CA: "CAD",
1269
+ GB: "GBP",
1270
+ AU: "AUD",
1271
+ NZ: "NZD",
1272
+ EU: "EUR",
1273
+ DE: "EUR",
1274
+ FR: "EUR",
1275
+ IT: "EUR",
1276
+ ES: "EUR",
1277
+ NL: "EUR",
1278
+ BE: "EUR",
1279
+ AT: "EUR",
1280
+ IE: "EUR",
1281
+ PT: "EUR",
1282
+ FI: "EUR",
1283
+ GR: "EUR",
1284
+ LU: "EUR",
1285
+ SK: "EUR",
1286
+ SI: "EUR",
1287
+ EE: "EUR",
1288
+ LV: "EUR",
1289
+ LT: "EUR",
1290
+ SE: "SEK",
1291
+ NO: "NOK",
1292
+ DK: "DKK",
1293
+ PL: "PLN",
1294
+ CZ: "CZK",
1295
+ CH: "CHF",
1296
+ HU: "HUF",
1297
+ RO: "RON",
1298
+ BG: "BGN",
1299
+ HR: "EUR",
1300
+ JP: "JPY",
1301
+ CN: "CNY",
1302
+ KR: "KRW",
1303
+ IN: "INR",
1304
+ SG: "SGD",
1305
+ TH: "THB",
1306
+ AE: "AED",
1307
+ IL: "ILS",
1308
+ TR: "TRY",
1309
+ ZA: "ZAR",
1310
+ BR: "BRL",
1311
+ MX: "MXN",
1312
+ AR: "ARS",
1313
+ CO: "COP",
1314
+ CL: "CLP"
1315
+ };
1316
+ const userCurrency = shipToCountry ? COUNTRY_CURRENCY[shipToCountry.toUpperCase()] || "EUR" : "EUR";
1317
+ let exchangeRates = {};
1318
+ try {
1319
+ exchangeRates = await fetchRates(userCurrency);
1320
+ } catch {
1321
+ }
1322
+ const allProducts = [];
1323
+ for (const p of result.products) {
1324
+ allProducts.push({
1325
+ ...p,
1326
+ vendor: p.vendor || p.storeName || "Unknown",
1327
+ isExtended: false
1328
+ });
1329
+ }
1330
+ if (extended?.products) {
1331
+ for (const ep of extended.products) {
1332
+ allProducts.push({
1333
+ name: ep.name,
1334
+ priceInCents: ep.priceInCents,
1335
+ currency: ep.currency,
1336
+ freeShipping: ep.freeShipping,
1337
+ shippingPriceInCents: ep.shippingPriceInCents,
1338
+ shippingDays: ep.shippingDays,
1339
+ vendor: ep.storeName || "Unknown",
1340
+ storeRating: ep.storeRating ?? null,
1341
+ storeVerified: ep.storeVerified ?? false,
1342
+ brand: ep.brand,
1343
+ variant: ep.variant,
1344
+ variantLabel: ep.variantLabel,
1345
+ description: ep.description,
1346
+ id: ep.id,
1347
+ isExtended: true
1348
+ });
1349
+ }
1350
+ }
1351
+ if (opts.sort === "total-cost") {
1352
+ allProducts.sort((a, b) => {
1353
+ const aCost = a.priceInCents + (a.freeShipping ? 0 : a.shippingPriceInCents ?? 0);
1354
+ const bCost = b.priceInCents + (b.freeShipping ? 0 : b.shippingPriceInCents ?? 0);
1355
+ return opts.order === "desc" ? bCost - aCost : aCost - bCost;
1356
+ });
1357
+ }
1358
+ if (allProducts.length === 0) {
1359
+ } else {
1360
+ const totalCount = result.total + (extended?.total || 0);
1361
+ if (result.products.length > 0 && extended?.total > 0) {
1362
+ console.log(chalk6.bold(`
1363
+ Results for "${query}" \u2014 ${result.total} local + ${extended.total} from stores
1364
+ `));
1365
+ } else if (extended?.total > 0) {
1366
+ console.log(chalk6.bold(`
1367
+ Extended search for "${query}" \u2014 ${extended.total} result(s) from ${extended.storesResponded} store(s)
1368
+ `));
1369
+ } else {
1370
+ console.log(chalk6.bold(`
1371
+ Results for "${query}" \u2014 ${result.total} found (page ${result.page})
1372
+ `));
1373
+ }
1374
+ if (allProducts.length >= 2) {
1375
+ const withTotal = allProducts.map((p) => ({
1376
+ ...p,
1377
+ totalCost: p.priceInCents + (p.freeShipping ? 0 : p.shippingPriceInCents ?? 0)
1378
+ }));
1379
+ const cheapest = withTotal.reduce((a, b) => a.totalCost < b.totalCost ? a : b);
1380
+ const fastest = allProducts.filter((p) => p.shippingDays != null).sort((a, b) => (a.shippingDays ?? 99) - (b.shippingDays ?? 99))[0];
1381
+ const bestRated = allProducts.filter((p) => (p.rating ?? 0) > 0).sort((a, b) => (b.rating ?? 0) - (a.rating ?? 0))[0];
1382
+ const parts = [];
1383
+ parts.push(`${chalk6.green("Best price:")} ${formatPrice(cheapest.totalCost, cheapest.currency)} at ${cheapest.vendor}`);
1384
+ if (fastest?.shippingDays != null) {
1385
+ parts.push(`${chalk6.blue("Fastest:")} ${deliveryLabel(fastest.shippingDays)} at ${fastest.vendor}`);
1386
+ }
1387
+ if (bestRated?.rating) {
1388
+ parts.push(`${chalk6.yellow("Top rated:")} ${scoreOutOf10(bestRated.rating)}/10 at ${bestRated.vendor}`);
1389
+ }
1390
+ console.log(` ${chalk6.dim("\u250C")} ${parts.join(chalk6.dim(" \u2502 "))}`);
1391
+ const prices = withTotal.map((p) => p.totalCost);
1392
+ const minP = Math.min(...prices);
1393
+ const maxP = Math.max(...prices);
1394
+ const avgP = Math.round(prices.reduce((a, b) => a + b, 0) / prices.length);
1395
+ const curr = allProducts[0].currency;
1396
+ console.log(` ${chalk6.dim("\u2514")} ${chalk6.dim(`Price range: ${formatPrice(minP, curr)} \u2013 ${formatPrice(maxP, curr)} \xB7 Average: ${formatPrice(avgP, curr)}`)}`);
1397
+ console.log();
1398
+ }
1399
+ for (let i = 0; i < allProducts.length; i++) {
1400
+ const p = allProducts[i];
1401
+ const num = i + 1;
1402
+ const itemPrice = p.priceInCents;
1403
+ const shippingPrice = p.freeShipping ? 0 : p.shippingPriceInCents ?? 0;
1404
+ const totalCost = itemPrice + shippingPrice;
1405
+ const badges = [];
1406
+ if (i === 0) badges.push(chalk6.bgGreen.black(" BEST MATCH "));
1407
+ const allCosts = allProducts.map((x) => x.priceInCents + (x.freeShipping ? 0 : x.shippingPriceInCents ?? 0));
1408
+ if (totalCost === Math.min(...allCosts) && i !== 0) badges.push(chalk6.bgYellow.black(" BEST VALUE "));
1409
+ const converted = formatConverted(totalCost, p.currency, userCurrency, exchangeRates);
1410
+ if (opts.compact) {
1411
+ const priceStr = formatPrice(totalCost, p.currency) + converted;
1412
+ const store = p.vendor;
1413
+ const delivery = p.shippingDays != null ? `Arrives ${estimatedArrival(p.shippingDays)}` : "";
1414
+ console.log(
1415
+ ` ${chalk6.dim(`[${num}]`)} ${chalk6.cyan(p.name.length > 60 ? p.name.slice(0, 57) + "..." : p.name)} ${chalk6.bold.white(priceStr)} ${chalk6.dim(store)}${delivery ? " " + chalk6.dim(delivery) : ""}` + (badges.length ? " " + badges.join(" ") : "")
1416
+ );
1417
+ continue;
1418
+ }
1419
+ console.log(` ${chalk6.dim(`[${num}]`)} ${chalk6.bold.cyan(p.name)}${badges.length ? " " + badges.join(" ") : ""}`);
1420
+ let priceLine = ` ${chalk6.bold.white(formatPrice(itemPrice, p.currency))}`;
1421
+ if (p.freeShipping) {
1422
+ priceLine += chalk6.green(" Free Shipping");
1423
+ } else if (p.shippingPriceInCents != null && (p.shippingPriceInCents ?? 0) > 0) {
1424
+ priceLine += chalk6.dim(` + ${formatPrice(shippingPrice, p.currency)} shipping`);
1425
+ priceLine += chalk6.bold(` = ${formatPrice(totalCost, p.currency)}`);
1426
+ }
1427
+ priceLine += converted;
1428
+ if (p.shippingDays != null) {
1429
+ priceLine += chalk6.blue(` Arrives ${estimatedArrival(p.shippingDays)}`);
1430
+ }
1431
+ console.log(priceLine);
1432
+ const meta = [];
1433
+ const storeBadge = p.storeVerified ? chalk6.green(" \u2713") : "";
1434
+ const storeScore = p.storeRating != null ? chalk6.dim(` ${p.storeRating.toFixed(1)}/10`) : chalk6.dim(" (no store rating)");
1435
+ meta.push(`${p.vendor}${storeBadge}${storeScore}`);
1436
+ if (p.brand) meta.push(p.brand);
1437
+ if (p.rating && p.rating > 0) {
1438
+ meta.push(chalk6.yellow(`${scoreOutOf10(p.rating)}/10`) + (p.reviewCount ? chalk6.dim(` (${p.reviewCount})`) : ""));
1439
+ }
1440
+ if (p.isExtended) meta.push(chalk6.magenta("via extended search"));
1441
+ console.log(` ${chalk6.dim(meta.join(" \xB7 "))}`);
1442
+ if (p.id) console.log(` ${chalk6.dim(`ID: ${p.id}`)}`);
1443
+ ;
1444
+ if (opts.detailed) {
1445
+ if (p.category) console.log(` ${chalk6.dim(`Category: ${p.category}`)}`);
1446
+ if (p.variant || p.variantLabel) console.log(` ${chalk6.dim(`Variant: ${p.variant || p.variantLabel}`)}`);
1447
+ if (p.description) {
1448
+ console.log(` ${chalk6.dim(p.description.length > 200 ? p.description.slice(0, 200) + "..." : p.description)}`);
1449
+ }
1450
+ } else {
1451
+ if (p.description) {
1452
+ console.log(` ${chalk6.dim(p.description.length > 80 ? p.description.slice(0, 80) + "..." : p.description)}`);
1453
+ }
1454
+ }
1455
+ console.log();
1456
+ }
1457
+ }
1458
+ const hasExtendedProducts = extended?.products?.length > 0;
1459
+ if (opts.interactive && allProducts.length > 0) {
1460
+ const selectableProducts = allProducts.map((p, idx) => ({ product: p, index: idx })).filter((item) => item.product.id);
1461
+ if (selectableProducts.length > 0) {
1462
+ console.log(chalk6.dim(" \u2500".repeat(30)));
1463
+ console.log();
1464
+ const choices = selectableProducts.map((item) => {
1465
+ const p = item.product;
1466
+ const priceStr = formatPrice(p.priceInCents, p.currency);
1467
+ const storeStr = p.vendor || "Unknown";
1468
+ const extLabel = p.isExtended ? chalk6.magenta(" [store]") : chalk6.dim(" [local]");
1469
+ return {
1470
+ name: `${chalk6.cyan(p.name.length > 50 ? p.name.slice(0, 47) + "..." : p.name)} ${chalk6.bold(priceStr)} ${chalk6.dim(storeStr)}${extLabel}`,
1471
+ value: item.product.id,
1472
+ short: p.name.length > 40 ? p.name.slice(0, 37) + "..." : p.name
1473
+ };
1474
+ });
1475
+ const { selectedIds } = await inquirer4.prompt([
1476
+ {
1477
+ type: "checkbox",
1478
+ name: "selectedIds",
1479
+ message: "Select product(s) to request more information from their store:",
1480
+ choices,
1481
+ pageSize: 15
1482
+ }
1483
+ ]);
1484
+ if (selectedIds && selectedIds.length > 0) {
1485
+ const extendedIds = selectedIds.filter(
1486
+ (id) => allProducts.find((p) => p.id === id && p.isExtended)
1487
+ );
1488
+ const localIds = selectedIds.filter(
1489
+ (id) => allProducts.find((p) => p.id === id && !p.isExtended)
1490
+ );
1491
+ if (extendedIds.length > 0) {
1492
+ const infoSpinner = ora4(`Requesting detailed info for ${extendedIds.length} product(s) from their stores...`).start();
1493
+ try {
1494
+ const infoRes = await api.post("/products/info", {
1495
+ productIds: extendedIds
1496
+ }, {
1497
+ timeout: 3e4
1498
+ });
1499
+ infoSpinner.stop();
1500
+ const { results: infoResults } = infoRes.data;
1501
+ if (infoResults && infoResults.length > 0) {
1502
+ console.log(chalk6.bold(`
1503
+ Store Information \u2014 ${infoResults.length} result(s)
1504
+ `));
1505
+ for (let i = 0; i < infoResults.length; i++) {
1506
+ const infoResult = infoResults[i];
1507
+ renderProductInfo(infoResult, i, infoResults.length, formatPrice);
1508
+ }
1509
+ } else {
1510
+ console.log(chalk6.yellow("\n No additional information returned from the stores."));
1511
+ }
1512
+ } catch (infoErr) {
1513
+ infoSpinner.stop();
1514
+ console.error(chalk6.red("\n Failed to fetch product info from stores."));
1515
+ }
1516
+ }
1517
+ if (localIds.length > 0) {
1518
+ for (const localId of localIds) {
1519
+ const detailSpinner = ora4(`Fetching details for ${localId}...`).start();
1520
+ try {
1521
+ const detailRes = await api.get(`/products/${localId}`);
1522
+ detailSpinner.stop();
1523
+ const p = detailRes.data.product;
1524
+ if (p) {
1525
+ console.log();
1526
+ console.log(` ${chalk6.bold.cyan(p.name)}`);
1527
+ console.log(` ${chalk6.dim(`ID: ${p.id}`)}`);
1528
+ if (p.brand) console.log(` ${chalk6.bold("Brand:")} ${p.brand}`);
1529
+ if (p.model) console.log(` ${chalk6.bold("Model:")} ${p.model}`);
1530
+ if (p.sku) console.log(` ${chalk6.bold("SKU:")} ${p.sku}`);
1531
+ console.log(` ${chalk6.bold("Price:")} ${chalk6.bold.white(formatPrice(p.priceInCents, p.currency))}`);
1532
+ const status = p.inStock ? chalk6.green("In Stock") : chalk6.red("Out of Stock");
1533
+ console.log(` ${chalk6.bold("Availability:")} ${status}${p.stockQuantity != null ? chalk6.dim(` (${p.stockQuantity} available)`) : ""}`);
1534
+ if (p.freeShipping) console.log(` ${chalk6.bold("Shipping:")} ${chalk6.green("Free")}`);
1535
+ else if (p.shippingPriceInCents != null) console.log(` ${chalk6.bold("Shipping:")} ${formatPrice(p.shippingPriceInCents, p.currency)}`);
1536
+ if (p.shippingDays != null) console.log(` ${chalk6.bold("Delivery:")} ${deliveryLabel(p.shippingDays)}`);
1537
+ if (p.freeReturns) console.log(` ${chalk6.bold("Returns:")} ${chalk6.green("Free Returns")}${p.returnWindowDays ? ` \xB7 ${p.returnWindowDays}-day window` : ""}`);
1538
+ if (p.description) {
1539
+ console.log(` ${chalk6.bold("Description:")}`);
1540
+ const words = p.description.split(/\s+/);
1541
+ let line = "";
1542
+ for (const word of words) {
1543
+ if (line.length + word.length + 1 > 76) {
1544
+ console.log(` ${chalk6.dim(line)}`);
1545
+ line = word;
1546
+ } else {
1547
+ line = line ? `${line} ${word}` : word;
1548
+ }
1549
+ }
1550
+ if (line) console.log(` ${chalk6.dim(line)}`);
1551
+ }
1552
+ console.log();
1553
+ }
1554
+ } catch {
1555
+ detailSpinner.stop();
1556
+ }
1557
+ }
1558
+ }
1559
+ console.log();
1560
+ }
1561
+ }
1562
+ } else if (hasExtendedProducts && !opts.interactive) {
1563
+ const sampleIds = extended.products.slice(0, 2).map((ep) => ep.id).join(" ");
1564
+ console.log(
1565
+ chalk6.dim(" \u2139\uFE0F Tip: ") + chalk6.white("Want more details? Request info from the store:") + chalk6.dim(`
1566
+ Run: `) + chalk6.cyan(`clishop info ${sampleIds}`) + chalk6.dim(` or use `) + chalk6.cyan(`--interactive`) + chalk6.dim(` to select interactively`) + chalk6.dim("\n")
1567
+ );
1568
+ }
1569
+ const totalPages = Math.ceil(result.total / result.pageSize);
1570
+ if (totalPages > 1) {
1571
+ console.log(chalk6.dim(` Page ${result.page} of ${totalPages}. Use --page to navigate.
1572
+ `));
1573
+ }
1574
+ const showAdvertiseTip = result.page >= totalPages || suggestAdvertise;
1575
+ if (showAdvertiseTip) {
1576
+ if (!didExtendedSearch && disableExtended && result.products.length > 0) {
1577
+ console.log(
1578
+ chalk6.dim(" \u{1F50D} Tip: ") + chalk6.white("Want more results? Extended search was disabled. Enable it:") + chalk6.dim(`
1579
+ Run: `) + chalk6.cyan(`clishop search "${query}" --extended-search`) + chalk6.dim("\n")
1580
+ );
1581
+ }
1582
+ console.log(
1583
+ chalk6.dim(" \u{1F4A1} Tip: ") + chalk6.white("Didn't find the right match? Advertise your request for vendors to bid on.") + chalk6.dim(`
1584
+ Run: `) + chalk6.cyan(`clishop advertise create`) + chalk6.dim(` or `) + chalk6.cyan(`clishop advertise quick "${query}"`) + chalk6.dim("\n")
1585
+ );
1586
+ }
1587
+ } catch (error) {
1588
+ handleApiError(error);
1589
+ }
1590
+ });
1591
+ program2.command("product <id>").description("View detailed product information").option("--json", "Output raw JSON").action(async (id, opts) => {
1592
+ try {
1593
+ const spinner = ora4("Fetching product details...").start();
1594
+ const api = getApiClient();
1595
+ const res = await api.get(`/products/${id}`);
1596
+ spinner.stop();
1597
+ const p = res.data.product;
1598
+ if (opts.json) {
1599
+ console.log(JSON.stringify(p, null, 2));
1600
+ return;
1601
+ }
1602
+ const storeBadge = p.storeVerified ? chalk6.green(" \u2713 Verified") : "";
1603
+ console.log();
1604
+ console.log(chalk6.bold.cyan(` ${p.name}`));
1605
+ console.log(chalk6.dim(` ID: ${p.id}`));
1606
+ if (p.brand) console.log(chalk6.dim(` Brand: ${p.brand}`));
1607
+ if (p.model) console.log(chalk6.dim(` Model: ${p.model}`));
1608
+ if (p.variant) console.log(chalk6.dim(` Variant: ${p.variant}`));
1609
+ if (p.sku) console.log(chalk6.dim(` SKU: ${p.sku}`));
1610
+ if (p.gtin) console.log(chalk6.dim(` GTIN: ${p.gtin}`));
1611
+ console.log();
1612
+ console.log(` Price: ${chalk6.bold(formatPrice(p.priceInCents, p.currency))}`);
1613
+ if (p.freeShipping) {
1614
+ console.log(` Shipping: ${chalk6.green("Free")}`);
1615
+ } else if (p.shippingPriceInCents != null) {
1616
+ console.log(` Shipping: ${formatPrice(p.shippingPriceInCents, p.currency)}`);
1617
+ }
1618
+ if (p.shippingPriceInCents != null || p.freeShipping) {
1619
+ const total = p.priceInCents + (p.freeShipping ? 0 : p.shippingPriceInCents ?? 0);
1620
+ console.log(` Total: ${chalk6.bold(formatPrice(total, p.currency))}`);
1621
+ }
1622
+ const status = p.inStock ? chalk6.green("In Stock") : p.backorder ? chalk6.yellow("Backorder") : chalk6.red("Out of Stock");
1623
+ console.log(` Status: ${status}${p.stockQuantity != null ? chalk6.dim(` (${p.stockQuantity} available)`) : ""}`);
1624
+ if (p.shippingDays != null) console.log(` Delivery: ${deliveryLabel(p.shippingDays)}`);
1625
+ console.log(` Rating: ${chalk6.yellow(renderStars(p.rating))} ${chalk6.dim(`(${p.reviewCount} reviews)`)}`);
1626
+ console.log(` Category: ${p.category}`);
1627
+ console.log(` Store: ${p.vendor}${storeBadge}${p.storeRating != null ? chalk6.dim(` (${p.storeRating.toFixed(1)} store rating)`) : ""}`);
1628
+ const returnParts = [];
1629
+ if (p.freeReturns) returnParts.push(chalk6.green("Free Returns"));
1630
+ if (p.returnWindowDays) returnParts.push(`${p.returnWindowDays}-day return window`);
1631
+ if (returnParts.length) console.log(` Returns: ${returnParts.join(" \xB7 ")}`);
1632
+ if (p.checkoutMode && p.checkoutMode !== "instant") {
1633
+ console.log(` Checkout: ${chalk6.yellow(p.checkoutMode)}`);
1634
+ }
1635
+ console.log();
1636
+ console.log(` ${p.description}`);
1637
+ console.log();
1638
+ } catch (error) {
1639
+ handleApiError(error);
1640
+ }
1641
+ });
1642
+ program2.command("info <ids...>").description("Request detailed information about search result products from their stores").option("--json", "Output raw JSON").action(async (ids, opts) => {
1643
+ try {
1644
+ if (ids.length === 0) {
1645
+ console.error(chalk6.red("\n\u2717 Please provide one or more product IDs."));
1646
+ console.log(chalk6.dim(" Usage: clishop info <product-id> [product-id...]"));
1647
+ console.log(chalk6.dim(" Example: clishop info ep_abc123 ep_def456\n"));
1648
+ process.exit(1);
1649
+ }
1650
+ if (ids.length > 20) {
1651
+ console.error(chalk6.red("\n\u2717 Maximum 20 products per request."));
1652
+ process.exit(1);
1653
+ }
1654
+ const spinner = ora4(`Requesting detailed info for ${ids.length} product(s)...`).start();
1655
+ const api = getApiClient();
1656
+ const res = await api.post("/products/info", {
1657
+ productIds: ids
1658
+ }, {
1659
+ timeout: 3e4
1660
+ });
1661
+ spinner.stop();
1662
+ const { results, total } = res.data;
1663
+ if (opts.json) {
1664
+ console.log(JSON.stringify(res.data, null, 2));
1665
+ return;
1666
+ }
1667
+ if (!results || results.length === 0) {
1668
+ console.log(chalk6.yellow("\nNo information returned for the requested products."));
1669
+ console.log(chalk6.dim(" Make sure you're using product IDs from extended search results (ep_...)."));
1670
+ console.log(chalk6.dim(" Product IDs are shown after each search result.\n"));
1671
+ return;
1672
+ }
1673
+ console.log(chalk6.bold(`
1674
+ Product Information \u2014 ${total} result(s)
1675
+ `));
1676
+ for (let i = 0; i < results.length; i++) {
1677
+ renderProductInfo(results[i], i, results.length, formatPrice);
1678
+ }
1679
+ } catch (error) {
1680
+ handleApiError(error);
1681
+ }
1682
+ });
1683
+ }
1684
+
1685
+ // src/commands/order.ts
1686
+ import chalk7 from "chalk";
1687
+ import ora5 from "ora";
1688
+ import inquirer5 from "inquirer";
1689
+ function formatPrice2(cents, currency) {
1690
+ return new Intl.NumberFormat("en-US", { style: "currency", currency }).format(cents / 100);
1691
+ }
1692
+ var STATUS_COLORS = {
1693
+ pending: chalk7.yellow,
1694
+ confirmed: chalk7.blue,
1695
+ processing: chalk7.cyan,
1696
+ shipped: chalk7.magenta,
1697
+ delivered: chalk7.green,
1698
+ cancelled: chalk7.red
1699
+ };
1700
+ function registerOrderCommands(program2) {
1701
+ const order = program2.command("order").description("Place and manage orders");
1702
+ program2.command("buy <productId>").description("Quick-buy a product").option("-q, --quantity <qty>", "Quantity", parseInt, 1).option("--address <id>", "Shipping address ID (uses agent default if omitted)").option("--payment <id>", "Payment method ID (uses agent default if omitted)").option("-y, --yes", "Skip confirmation prompt").action(async (productId, opts) => {
1703
+ try {
1704
+ const agent = getActiveAgent();
1705
+ const addressId = opts.address || agent.defaultAddressId;
1706
+ const paymentId = opts.payment || agent.defaultPaymentMethodId;
1707
+ if (!addressId) {
1708
+ console.error(chalk7.red("\n\u2717 No address set. Add one with: clishop address add"));
1709
+ process.exitCode = 1;
1710
+ return;
1711
+ }
1712
+ if (!paymentId) {
1713
+ console.error(chalk7.red("\n\u2717 No payment method set. Add one with: clishop payment add"));
1714
+ process.exitCode = 1;
1715
+ return;
1716
+ }
1717
+ const api = getApiClient();
1718
+ const prodSpinner = ora5("Fetching product info...").start();
1719
+ let product;
1720
+ let isExtended = false;
1721
+ try {
1722
+ const prodRes = await api.get(`/products/${productId}`);
1723
+ product = prodRes.data.product;
1724
+ } catch (err) {
1725
+ if (err?.response?.status === 404) {
1726
+ try {
1727
+ const extRes = await api.get(`/products/extended/${productId}`);
1728
+ product = extRes.data.product;
1729
+ isExtended = true;
1730
+ } catch {
1731
+ prodSpinner.stop();
1732
+ console.error(chalk7.red(`
1733
+ \u2717 Product ${productId} not found.`));
1734
+ process.exitCode = 1;
1735
+ return;
1736
+ }
1737
+ } else {
1738
+ prodSpinner.stop();
1739
+ throw err;
1740
+ }
1741
+ }
1742
+ prodSpinner.stop();
1743
+ const totalCents = product.priceInCents * opts.quantity;
1744
+ if (agent.maxOrderAmount && totalCents / 100 > agent.maxOrderAmount) {
1745
+ console.error(
1746
+ chalk7.red(
1747
+ `
1748
+ \u2717 Order total (${formatPrice2(totalCents, product.currency)}) exceeds agent "${agent.name}" limit of $${agent.maxOrderAmount}.`
1749
+ )
1750
+ );
1751
+ process.exitCode = 1;
1752
+ return;
1753
+ }
1754
+ if (agent.blockedCategories?.includes(product.category)) {
1755
+ console.error(chalk7.red(`
1756
+ \u2717 Category "${product.category}" is blocked for agent "${agent.name}".`));
1757
+ process.exitCode = 1;
1758
+ return;
1759
+ }
1760
+ if (agent.allowedCategories?.length && !agent.allowedCategories.includes(product.category)) {
1761
+ console.error(chalk7.red(`
1762
+ \u2717 Category "${product.category}" is not in the allowed list for agent "${agent.name}".`));
1763
+ process.exitCode = 1;
1764
+ return;
1765
+ }
1766
+ if (agent.requireConfirmation && !opts.yes) {
1767
+ console.log(chalk7.bold("\n Order Summary:"));
1768
+ console.log(` Product: ${product.name}`);
1769
+ console.log(` Store: ${product.vendor || product.storeName || "\u2014"}`);
1770
+ console.log(` Quantity: ${opts.quantity}`);
1771
+ console.log(` Total: ${chalk7.bold(formatPrice2(totalCents, product.currency))}`);
1772
+ console.log(` Agent: ${agent.name}`);
1773
+ console.log();
1774
+ const { confirm } = await inquirer5.prompt([
1775
+ {
1776
+ type: "confirm",
1777
+ name: "confirm",
1778
+ message: "Place this order?",
1779
+ default: false
1780
+ }
1781
+ ]);
1782
+ if (!confirm) {
1783
+ console.log(chalk7.yellow("Order cancelled."));
1784
+ return;
1785
+ }
1786
+ }
1787
+ const spinner = ora5("Placing order...").start();
1788
+ const res = await api.post("/orders", {
1789
+ agent: agent.name,
1790
+ items: [{ productId, quantity: opts.quantity }],
1791
+ shippingAddressId: addressId,
1792
+ paymentMethodId: paymentId
1793
+ });
1794
+ spinner.succeed(chalk7.green(`Order placed! Order ID: ${chalk7.bold(res.data.order.id)}`));
1795
+ } catch (error) {
1796
+ handleApiError(error);
1797
+ }
1798
+ });
1799
+ order.command("list").alias("ls").description("List your orders").option("--status <status>", "Filter by status").option("-p, --page <page>", "Page number", parseInt, 1).option("--json", "Output raw JSON").action(async (opts) => {
1800
+ try {
1801
+ const spinner = ora5("Fetching orders...").start();
1802
+ const api = getApiClient();
1803
+ const res = await api.get("/orders", {
1804
+ params: {
1805
+ status: opts.status,
1806
+ page: opts.page
1807
+ }
1808
+ });
1809
+ spinner.stop();
1810
+ const orders = res.data.orders;
1811
+ if (opts.json) {
1812
+ console.log(JSON.stringify(orders, null, 2));
1813
+ return;
1814
+ }
1815
+ if (orders.length === 0) {
1816
+ console.log(chalk7.yellow("\nNo orders found.\n"));
1817
+ return;
1818
+ }
1819
+ console.log(chalk7.bold("\nYour Orders:\n"));
1820
+ for (const o of orders) {
1821
+ const statusColor = STATUS_COLORS[o.status] || chalk7.white;
1822
+ const date = new Date(o.createdAt).toLocaleDateString();
1823
+ console.log(
1824
+ ` ${chalk7.bold(o.id)} ${statusColor(o.status.toUpperCase().padEnd(12))} ${formatPrice2(o.totalAmountInCents, o.currency)} ${chalk7.dim(date)} ${chalk7.dim(`agent: ${o.agent}`)}`
1825
+ );
1826
+ for (const item of o.items) {
1827
+ console.log(chalk7.dim(` \xB7 ${item.productName} \xD7 ${item.quantity}`));
1828
+ }
1829
+ console.log();
1830
+ }
1831
+ } catch (error) {
1832
+ handleApiError(error);
1833
+ }
1834
+ });
1835
+ order.command("show <id>").description("Show order details").option("--json", "Output raw JSON").action(async (id, opts) => {
1836
+ try {
1837
+ const spinner = ora5("Fetching order...").start();
1838
+ const api = getApiClient();
1839
+ const res = await api.get(`/orders/${id}`);
1840
+ spinner.stop();
1841
+ const o = res.data.order;
1842
+ if (opts.json) {
1843
+ console.log(JSON.stringify(o, null, 2));
1844
+ return;
1845
+ }
1846
+ const statusColor = STATUS_COLORS[o.status] || chalk7.white;
1847
+ console.log();
1848
+ console.log(chalk7.bold(` Order ${o.id}`));
1849
+ console.log(` Status: ${statusColor(o.status.toUpperCase())}`);
1850
+ console.log(` Total: ${chalk7.bold(formatPrice2(o.totalAmountInCents, o.currency))}`);
1851
+ if (o.storeName) console.log(` Store: ${o.storeName}`);
1852
+ console.log(` Agent: ${o.agent}`);
1853
+ if (o.paymentLabel) console.log(` Payment: ${o.paymentLabel}`);
1854
+ console.log(` Placed: ${new Date(o.createdAt).toLocaleString()}`);
1855
+ console.log(` Updated: ${new Date(o.updatedAt).toLocaleString()}`);
1856
+ if (o.externalOrderId) {
1857
+ console.log(` eBay Ref: ${chalk7.dim(o.externalOrderId)}`);
1858
+ }
1859
+ if (o.shipments?.length) {
1860
+ for (const s of o.shipments) {
1861
+ console.log(` Tracking: ${s.trackingNumber || "pending"} ${s.carrier ? `(${s.carrier})` : ""}`);
1862
+ if (s.trackingUrl) console.log(` Track: ${chalk7.cyan.underline(s.trackingUrl)}`);
1863
+ }
1864
+ }
1865
+ console.log(chalk7.bold("\n Items:"));
1866
+ for (const item of o.items) {
1867
+ console.log(` \xB7 ${item.productName} \xD7 ${item.quantity} ${formatPrice2(item.totalPriceInCents, o.currency)}`);
1868
+ }
1869
+ if (o.externalOrderId && !o.externalOrderId.startsWith("pend_")) {
1870
+ try {
1871
+ const trackRes = await api.get(`/orders/${id}/tracking`);
1872
+ const { tracking } = trackRes.data;
1873
+ if (tracking) {
1874
+ const ebayStatus = tracking.status || "UNKNOWN";
1875
+ const STATUS_MAP = {
1876
+ PENDING_AVAILABILITY: "Pending",
1877
+ PENDING_PAYMENT: "Awaiting payment",
1878
+ PAYMENT_PROCESSING: "Payment processing",
1879
+ FULFILLMENT_IN_PROGRESS: "Being packed",
1880
+ FULFILLED: "Fulfilled",
1881
+ CANCELLED: "Cancelled"
1882
+ };
1883
+ console.log(chalk7.bold("\n eBay Status:"));
1884
+ console.log(` ${chalk7.cyan(STATUS_MAP[ebayStatus] || ebayStatus)}`);
1885
+ for (const li of tracking.line_items || []) {
1886
+ if (li.tracking_number) {
1887
+ console.log(` Tracking: ${chalk7.bold(li.tracking_number)}${li.carrier ? ` (${li.carrier})` : ""}`);
1888
+ }
1889
+ if (li.tracking_url) {
1890
+ console.log(` Track: ${chalk7.cyan.underline(li.tracking_url)}`);
1891
+ }
1892
+ if (li.estimated_delivery) {
1893
+ const eta = new Date(li.estimated_delivery).toLocaleDateString();
1894
+ console.log(` ETA: ${eta}`);
1895
+ }
1896
+ }
1897
+ } else if (trackRes.data.message) {
1898
+ console.log(chalk7.dim(`
1899
+ Vendor: ${trackRes.data.message}`));
1900
+ }
1901
+ } catch {
1902
+ }
1903
+ }
1904
+ console.log();
1905
+ } catch (error) {
1906
+ handleApiError(error);
1907
+ }
1908
+ });
1909
+ order.command("cancel <id>").description("Cancel an order").action(async (id) => {
1910
+ try {
1911
+ const { confirm } = await inquirer5.prompt([
1912
+ {
1913
+ type: "confirm",
1914
+ name: "confirm",
1915
+ message: `Cancel order ${id}? This may not be reversible.`,
1916
+ default: false
1917
+ }
1918
+ ]);
1919
+ if (!confirm) return;
1920
+ const spinner = ora5("Cancelling order...").start();
1921
+ const api = getApiClient();
1922
+ await api.post(`/orders/${id}/cancel`);
1923
+ spinner.succeed(chalk7.green("Order cancelled."));
1924
+ } catch (error) {
1925
+ handleApiError(error);
1926
+ }
1927
+ });
1928
+ }
1929
+
1930
+ // src/commands/review.ts
1931
+ import chalk8 from "chalk";
1932
+ import ora6 from "ora";
1933
+ import inquirer6 from "inquirer";
1934
+ function renderStars2(rating) {
1935
+ const filled = Math.round(rating);
1936
+ const empty = 10 - filled;
1937
+ return chalk8.yellow("\u2605".repeat(filled) + "\u2606".repeat(empty));
1938
+ }
1939
+ function renderRating(rating) {
1940
+ if (rating === 0) return chalk8.dim("No ratings yet");
1941
+ const color = rating >= 8 ? chalk8.green : rating >= 5 ? chalk8.yellow : chalk8.red;
1942
+ return `${renderStars2(rating)} ${color(`${rating.toFixed(1)}/10`)}`;
1943
+ }
1944
+ var RATING_CHOICES = [
1945
+ { name: "10 \u2605\u2605\u2605\u2605\u2605\u2605\u2605\u2605\u2605\u2605 Perfect", value: 10 },
1946
+ { name: " 9 \u2605\u2605\u2605\u2605\u2605\u2605\u2605\u2605\u2605\u2606 Excellent", value: 9 },
1947
+ { name: " 8 \u2605\u2605\u2605\u2605\u2605\u2605\u2605\u2605\u2606\u2606 Great", value: 8 },
1948
+ { name: " 7 \u2605\u2605\u2605\u2605\u2605\u2605\u2605\u2606\u2606\u2606 Good", value: 7 },
1949
+ { name: " 6 \u2605\u2605\u2605\u2605\u2605\u2605\u2606\u2606\u2606\u2606 Above Average", value: 6 },
1950
+ { name: " 5 \u2605\u2605\u2605\u2605\u2605\u2606\u2606\u2606\u2606\u2606 Average", value: 5 },
1951
+ { name: " 4 \u2605\u2605\u2605\u2605\u2606\u2606\u2606\u2606\u2606\u2606 Below Average", value: 4 },
1952
+ { name: " 3 \u2605\u2605\u2605\u2606\u2606\u2606\u2606\u2606\u2606\u2606 Poor", value: 3 },
1953
+ { name: " 2 \u2605\u2605\u2606\u2606\u2606\u2606\u2606\u2606\u2606\u2606 Bad", value: 2 },
1954
+ { name: " 1 \u2605\u2606\u2606\u2606\u2606\u2606\u2606\u2606\u2606\u2606 Terrible", value: 1 }
1955
+ ];
1956
+ function registerReviewCommands(program2) {
1957
+ const review = program2.command("review").description("Manage product & store reviews");
1958
+ review.command("order <orderId>").description("Review items and store from an order").action(async (orderId) => {
1959
+ try {
1960
+ const api = getApiClient();
1961
+ const spinner = ora6("Fetching order details...").start();
1962
+ let reviewable;
1963
+ try {
1964
+ const res = await api.get(`/orders/${orderId}/reviewable`);
1965
+ reviewable = res.data;
1966
+ } catch (err) {
1967
+ spinner.stop();
1968
+ handleApiError(err);
1969
+ return;
1970
+ }
1971
+ spinner.stop();
1972
+ const unreviewedItems = reviewable.items.filter((i) => !i.alreadyReviewed);
1973
+ const storeAlreadyReviewed = reviewable.store.alreadyReviewed;
1974
+ if (unreviewedItems.length === 0 && storeAlreadyReviewed) {
1975
+ console.log(chalk8.yellow("\nYou've already reviewed everything in this order.\n"));
1976
+ return;
1977
+ }
1978
+ console.log(chalk8.bold(`
1979
+ Review Order ${chalk8.cyan(orderId)}`));
1980
+ console.log(chalk8.dim(` Store: ${reviewable.store.name}
1981
+ `));
1982
+ const itemReviews = [];
1983
+ let storeReview = void 0;
1984
+ for (const item of unreviewedItems) {
1985
+ console.log(chalk8.bold(` Product: ${item.productName}`));
1986
+ const { wantReview } = await inquirer6.prompt([
1987
+ {
1988
+ type: "confirm",
1989
+ name: "wantReview",
1990
+ message: `Review "${item.productName}"?`,
1991
+ default: true
1992
+ }
1993
+ ]);
1994
+ if (!wantReview) continue;
1995
+ const answers = await inquirer6.prompt([
1996
+ {
1997
+ type: "select",
1998
+ name: "rating",
1999
+ message: "Rating (1-10):",
2000
+ choices: RATING_CHOICES
2001
+ },
2002
+ {
2003
+ type: "input",
2004
+ name: "title",
2005
+ message: "Review title:",
2006
+ validate: (v) => v.trim().length > 0 || "Title is required"
2007
+ },
2008
+ {
2009
+ type: "input",
2010
+ name: "body",
2011
+ message: "Review body:",
2012
+ validate: (v) => v.trim().length > 0 || "Body is required"
2013
+ }
2014
+ ]);
2015
+ itemReviews.push({
2016
+ productId: item.productId,
2017
+ rating: answers.rating,
2018
+ title: answers.title.trim(),
2019
+ body: answers.body.trim()
2020
+ });
2021
+ console.log(chalk8.dim(` \u2713 ${item.productName}: ${answers.rating}/10
2022
+ `));
2023
+ }
2024
+ if (!storeAlreadyReviewed) {
2025
+ console.log(chalk8.bold(` Store: ${reviewable.store.name}`));
2026
+ const { wantStoreReview } = await inquirer6.prompt([
2027
+ {
2028
+ type: "confirm",
2029
+ name: "wantStoreReview",
2030
+ message: `Review store "${reviewable.store.name}"?`,
2031
+ default: true
2032
+ }
2033
+ ]);
2034
+ if (wantStoreReview) {
2035
+ const storeAnswers = await inquirer6.prompt([
2036
+ {
2037
+ type: "select",
2038
+ name: "rating",
2039
+ message: "Store rating (1-10):",
2040
+ choices: RATING_CHOICES
2041
+ },
2042
+ {
2043
+ type: "input",
2044
+ name: "title",
2045
+ message: "Store review title:",
2046
+ validate: (v) => v.trim().length > 0 || "Title is required"
2047
+ },
2048
+ {
2049
+ type: "input",
2050
+ name: "body",
2051
+ message: "Store review body:",
2052
+ validate: (v) => v.trim().length > 0 || "Body is required"
2053
+ }
2054
+ ]);
2055
+ storeReview = {
2056
+ rating: storeAnswers.rating,
2057
+ title: storeAnswers.title.trim(),
2058
+ body: storeAnswers.body.trim()
2059
+ };
2060
+ }
2061
+ }
2062
+ if (itemReviews.length === 0 && !storeReview) {
2063
+ console.log(chalk8.yellow("\nNo reviews submitted.\n"));
2064
+ return;
2065
+ }
2066
+ const submitSpinner = ora6("Submitting reviews...").start();
2067
+ try {
2068
+ const res = await api.post(`/orders/${orderId}/reviews`, {
2069
+ itemReviews,
2070
+ storeReview
2071
+ });
2072
+ submitSpinner.succeed(chalk8.green("Reviews submitted!"));
2073
+ const data = res.data;
2074
+ if (data.productReviews?.length > 0) {
2075
+ console.log(chalk8.bold("\n Product Reviews:"));
2076
+ for (const r of data.productReviews) {
2077
+ console.log(` ${renderStars2(r.rating)} ${chalk8.bold(r.title)}`);
2078
+ }
2079
+ }
2080
+ if (data.storeReview && !data.storeReview.skipped) {
2081
+ console.log(chalk8.bold("\n Store Review:"));
2082
+ console.log(` ${renderStars2(data.storeReview.rating)} ${chalk8.bold(data.storeReview.title)}`);
2083
+ }
2084
+ console.log();
2085
+ } catch (err) {
2086
+ submitSpinner.stop();
2087
+ handleApiError(err);
2088
+ }
2089
+ } catch (error) {
2090
+ handleApiError(error);
2091
+ }
2092
+ });
2093
+ review.command("add <productId>").description("Write a review for a product").option("--order <orderId>", "Associate with an order").action(async (productId, opts) => {
2094
+ try {
2095
+ const answers = await inquirer6.prompt([
2096
+ {
2097
+ type: "select",
2098
+ name: "rating",
2099
+ message: "Rating (1-10):",
2100
+ choices: RATING_CHOICES
2101
+ },
2102
+ { type: "input", name: "title", message: "Review title:", validate: (v) => v.trim().length > 0 || "Required" },
2103
+ {
2104
+ type: "editor",
2105
+ name: "body",
2106
+ message: "Review body (opens your editor):"
2107
+ }
2108
+ ]);
2109
+ const spinner = ora6("Submitting review...").start();
2110
+ const api = getApiClient();
2111
+ const res = await api.post(`/products/${productId}/reviews`, {
2112
+ rating: answers.rating,
2113
+ title: answers.title.trim(),
2114
+ body: answers.body.trim(),
2115
+ orderId: opts.order || void 0
2116
+ });
2117
+ spinner.succeed(chalk8.green("Review submitted!"));
2118
+ console.log(`
2119
+ ${renderRating(answers.rating)} ${chalk8.bold(answers.title)}`);
2120
+ console.log(chalk8.dim(` Review ID: ${res.data.review.id}
2121
+ `));
2122
+ } catch (error) {
2123
+ handleApiError(error);
2124
+ }
2125
+ });
2126
+ review.command("store <storeId>").description("Write a review for a store").option("--order <orderId>", "Associate with an order").action(async (storeId, opts) => {
2127
+ try {
2128
+ const answers = await inquirer6.prompt([
2129
+ {
2130
+ type: "select",
2131
+ name: "rating",
2132
+ message: "Store rating (1-10):",
2133
+ choices: RATING_CHOICES
2134
+ },
2135
+ { type: "input", name: "title", message: "Review title:", validate: (v) => v.trim().length > 0 || "Required" },
2136
+ {
2137
+ type: "editor",
2138
+ name: "body",
2139
+ message: "Review body (opens your editor):"
2140
+ }
2141
+ ]);
2142
+ const spinner = ora6("Submitting store review...").start();
2143
+ const api = getApiClient();
2144
+ const res = await api.post(`/stores/${storeId}/reviews`, {
2145
+ rating: answers.rating,
2146
+ title: answers.title.trim(),
2147
+ body: answers.body.trim(),
2148
+ orderId: opts.order || void 0
2149
+ });
2150
+ spinner.succeed(chalk8.green("Store review submitted!"));
2151
+ console.log(`
2152
+ ${renderRating(answers.rating)} ${chalk8.bold(answers.title)}`);
2153
+ console.log(chalk8.dim(` Review ID: ${res.data.review.id}
2154
+ `));
2155
+ } catch (error) {
2156
+ handleApiError(error);
2157
+ }
2158
+ });
2159
+ review.command("list").alias("ls").description("List your reviews").option("--json", "Output raw JSON").action(async (opts) => {
2160
+ try {
2161
+ const spinner = ora6("Fetching reviews...").start();
2162
+ const api = getApiClient();
2163
+ const res = await api.get("/reviews/mine");
2164
+ spinner.stop();
2165
+ const { productReviews, storeReviews } = res.data;
2166
+ if (opts.json) {
2167
+ console.log(JSON.stringify(res.data, null, 2));
2168
+ return;
2169
+ }
2170
+ if (productReviews.length === 0 && storeReviews.length === 0) {
2171
+ console.log(chalk8.yellow("\nYou haven't written any reviews yet.\n"));
2172
+ return;
2173
+ }
2174
+ if (productReviews.length > 0) {
2175
+ console.log(chalk8.bold("\nProduct Reviews:\n"));
2176
+ for (const r of productReviews) {
2177
+ const date = new Date(r.createdAt).toLocaleDateString();
2178
+ console.log(` ${renderStars2(r.rating)} ${chalk8.bold(r.title)}`);
2179
+ console.log(` ${chalk8.dim(`on ${r.productName}`)} ${chalk8.dim(date)}`);
2180
+ console.log(` ${r.body.length > 150 ? r.body.slice(0, 150) + "..." : r.body}`);
2181
+ console.log();
2182
+ }
2183
+ }
2184
+ if (storeReviews.length > 0) {
2185
+ console.log(chalk8.bold("Store Reviews:\n"));
2186
+ for (const r of storeReviews) {
2187
+ const date = new Date(r.createdAt).toLocaleDateString();
2188
+ console.log(` ${renderStars2(r.rating)} ${chalk8.bold(r.title)}`);
2189
+ console.log(` ${chalk8.dim(`store: ${r.storeName}`)} ${chalk8.dim(date)}`);
2190
+ console.log(` ${r.body.length > 150 ? r.body.slice(0, 150) + "..." : r.body}`);
2191
+ console.log();
2192
+ }
2193
+ }
2194
+ } catch (error) {
2195
+ handleApiError(error);
2196
+ }
2197
+ });
2198
+ review.command("rating <id>").description("View rating details for a product or store").option("--store", "View store rating instead of product").action(async (id, opts) => {
2199
+ try {
2200
+ const spinner = ora6("Fetching rating...").start();
2201
+ const api = getApiClient();
2202
+ const endpoint = opts.store ? `/stores/${id}/rating` : `/products/${id}/rating`;
2203
+ const res = await api.get(endpoint);
2204
+ spinner.stop();
2205
+ const { rating } = res.data;
2206
+ const entity = opts.store ? res.data.store : res.data.product;
2207
+ console.log();
2208
+ console.log(chalk8.bold(` ${entity.name}`));
2209
+ console.log(` ${renderRating(rating.displayRating)}`);
2210
+ console.log();
2211
+ console.log(` Reviews: ${rating.reviewCount}`);
2212
+ console.log(` Total Orders: ${rating.totalOrders}`);
2213
+ console.log(` Bayesian Avg: ${rating.bayesianAverage.toFixed(2)}`);
2214
+ console.log(` Effective Cap: ${rating.effectiveCeiling.toFixed(1)}`);
2215
+ if (rating.isCapped) {
2216
+ console.log(chalk8.yellow(` \u26A0 Rating is capped \u2014 needs more orders to unlock higher rating`));
2217
+ if (rating.totalOrders < 100) {
2218
+ console.log(chalk8.dim(` ${100 - rating.totalOrders} more orders needed to unlock 8.0+ rating`));
2219
+ } else if (rating.totalOrders < 1e3) {
2220
+ console.log(chalk8.dim(` ${1e3 - rating.totalOrders} more orders needed to unlock 9.0+ rating`));
2221
+ }
2222
+ }
2223
+ console.log();
2224
+ } catch (error) {
2225
+ handleApiError(error);
2226
+ }
2227
+ });
2228
+ review.command("delete <reviewId>").alias("rm").description("Delete one of your reviews").option("--store", "Delete a store review").action(async (reviewId, opts) => {
2229
+ try {
2230
+ const { confirm } = await inquirer6.prompt([
2231
+ {
2232
+ type: "confirm",
2233
+ name: "confirm",
2234
+ message: `Delete review ${reviewId}?`,
2235
+ default: false
2236
+ }
2237
+ ]);
2238
+ if (!confirm) return;
2239
+ const spinner = ora6("Deleting review...").start();
2240
+ const api = getApiClient();
2241
+ const endpoint = opts.store ? `/store-reviews/${reviewId}` : `/reviews/${reviewId}`;
2242
+ await api.delete(endpoint);
2243
+ spinner.succeed(chalk8.green("Review deleted."));
2244
+ } catch (error) {
2245
+ handleApiError(error);
2246
+ }
2247
+ });
2248
+ }
2249
+
2250
+ // src/commands/config.ts
2251
+ import chalk9 from "chalk";
2252
+ function registerConfigCommands(program2) {
2253
+ const config2 = program2.command("config").description("View and manage CLI configuration");
2254
+ config2.command("show").description("Show current configuration").action(() => {
2255
+ const cfg = getConfig();
2256
+ console.log(chalk9.bold("\nCLISHOP Configuration:\n"));
2257
+ console.log(` Active agent: ${chalk9.cyan(cfg.get("activeAgent"))}`);
2258
+ console.log(` Output format: ${chalk9.cyan(cfg.get("outputFormat"))}`);
2259
+ console.log(` Config path: ${chalk9.dim(cfg.path)}`);
2260
+ console.log();
2261
+ });
2262
+ config2.command("set-output <format>").description("Set output format: human or json").action((format) => {
2263
+ if (format !== "human" && format !== "json") {
2264
+ console.error(chalk9.red('\u2717 Format must be "human" or "json".'));
2265
+ process.exitCode = 1;
2266
+ return;
2267
+ }
2268
+ const cfg = getConfig();
2269
+ cfg.set("outputFormat", format);
2270
+ console.log(chalk9.green(`
2271
+ \u2713 Output format set to "${format}".`));
2272
+ });
2273
+ config2.command("reset").description("Reset all configuration to defaults").action(() => {
2274
+ const cfg = getConfig();
2275
+ cfg.clear();
2276
+ console.log(chalk9.green("\n\u2713 Configuration reset to defaults."));
2277
+ });
2278
+ config2.command("path").description("Show the config file path").action(() => {
2279
+ const cfg = getConfig();
2280
+ console.log(cfg.path);
2281
+ });
2282
+ }
2283
+
2284
+ // src/commands/store.ts
2285
+ import chalk10 from "chalk";
2286
+ import ora7 from "ora";
2287
+ function formatPrice3(cents, currency) {
2288
+ return new Intl.NumberFormat("en-US", {
2289
+ style: "currency",
2290
+ currency
2291
+ }).format(cents / 100);
2292
+ }
2293
+ function renderStars3(rating) {
2294
+ const full = Math.floor(rating);
2295
+ const half = rating - full >= 0.5 ? 1 : 0;
2296
+ const empty = 5 - full - half;
2297
+ return "\u2605".repeat(full) + (half ? "\xBD" : "") + "\u2606".repeat(empty);
2298
+ }
2299
+ function deliveryLabel2(days) {
2300
+ if (days <= 0) return "Same-day";
2301
+ if (days === 1) return "Next-day";
2302
+ if (days === 2) return "2-day";
2303
+ return `${days}-day`;
2304
+ }
2305
+ function registerStoreCommands(program2) {
2306
+ const store = program2.command("store").description("Browse stores and their catalogs");
2307
+ store.command("list").alias("ls").description("List available stores").option("-q, --query <query>", "Search stores by name").option("--verified", "Only show verified stores").option("--min-rating <rating>", "Minimum store rating (0-5)", parseFloat).option("--country <country>", "Filter by country").option("-s, --sort <field>", "Sort by: name, rating, newest, products", "name").option("--order <dir>", "Sort order: asc, desc", "asc").option("-p, --page <page>", "Page number", parseInt, 1).option("-n, --per-page <count>", "Results per page", parseInt, 20).option("--json", "Output raw JSON").action(async (opts) => {
2308
+ try {
2309
+ const spinner = ora7("Fetching stores...").start();
2310
+ const api = getApiClient();
2311
+ const res = await api.get("/stores", {
2312
+ params: {
2313
+ q: opts.query,
2314
+ verified: opts.verified || void 0,
2315
+ minRating: opts.minRating,
2316
+ country: opts.country,
2317
+ sort: opts.sort,
2318
+ order: opts.order,
2319
+ page: opts.page,
2320
+ pageSize: opts.perPage
2321
+ }
2322
+ });
2323
+ spinner.stop();
2324
+ const { stores, total, page, pageSize } = res.data;
2325
+ if (opts.json) {
2326
+ console.log(JSON.stringify(res.data, null, 2));
2327
+ return;
2328
+ }
2329
+ if (stores.length === 0) {
2330
+ console.log(chalk10.yellow("\nNo stores found.\n"));
2331
+ return;
2332
+ }
2333
+ console.log(chalk10.bold(`
2334
+ Stores \u2014 ${total} found (page ${page})
2335
+ `));
2336
+ for (const s of stores) {
2337
+ const badge = s.verified ? chalk10.green(" \u2713") : "";
2338
+ const rating = s.rating != null ? chalk10.yellow(renderStars3(s.rating)) + chalk10.dim(` (${s.rating.toFixed(1)})`) : chalk10.dim("No rating");
2339
+ const products = s.productCount != null ? chalk10.dim(`${s.productCount} products`) : "";
2340
+ const country = s.country ? chalk10.dim(` ${s.country}`) : "";
2341
+ console.log(` ${chalk10.bold.cyan(s.name)}${badge} ${chalk10.dim(`(${s.slug})`)}`);
2342
+ console.log(` ${rating} ${products}${country}`);
2343
+ if (s.description) {
2344
+ const desc = s.description.length > 100 ? s.description.slice(0, 100) + "..." : s.description;
2345
+ console.log(` ${chalk10.dim(desc)}`);
2346
+ }
2347
+ console.log();
2348
+ }
2349
+ const totalPages = Math.ceil(total / pageSize);
2350
+ if (totalPages > 1) {
2351
+ console.log(chalk10.dim(` Page ${page} of ${totalPages}. Use --page to navigate.
2352
+ `));
2353
+ }
2354
+ } catch (error) {
2355
+ handleApiError(error);
2356
+ }
2357
+ });
2358
+ store.command("info <store>").description("View store details (by name, slug, or ID)").option("--json", "Output raw JSON").action(async (storeId, opts) => {
2359
+ try {
2360
+ const spinner = ora7("Fetching store info...").start();
2361
+ const api = getApiClient();
2362
+ const res = await api.get(`/stores/${encodeURIComponent(storeId)}`);
2363
+ spinner.stop();
2364
+ const s = res.data.store;
2365
+ if (opts.json) {
2366
+ console.log(JSON.stringify(s, null, 2));
2367
+ return;
2368
+ }
2369
+ const badge = s.verified ? chalk10.green(" \u2713 Verified") : chalk10.dim(" Unverified");
2370
+ console.log();
2371
+ console.log(chalk10.bold.cyan(` ${s.name}`) + badge);
2372
+ console.log(chalk10.dim(` ID: ${s.id} | Slug: ${s.slug}`));
2373
+ console.log();
2374
+ if (s.description) console.log(` ${s.description}`);
2375
+ console.log();
2376
+ if (s.rating != null) {
2377
+ console.log(` Rating: ${chalk10.yellow(renderStars3(s.rating))} ${chalk10.dim(`(${s.rating.toFixed(1)})`)}`);
2378
+ }
2379
+ if (s.productCount != null) console.log(` Products: ${s.productCount}`);
2380
+ if (s.country) console.log(` Country: ${s.country}`);
2381
+ console.log(` Currency: ${s.currency}`);
2382
+ if (s.domain) console.log(` Website: ${chalk10.cyan.underline(s.domain)}`);
2383
+ if (s.contactEmail) console.log(` Contact: ${s.contactEmail}`);
2384
+ console.log();
2385
+ console.log(chalk10.dim(` Browse catalog: clishop store catalog ${s.slug}`));
2386
+ console.log(chalk10.dim(` Search products: clishop search "<query>" --store ${s.slug}`));
2387
+ console.log();
2388
+ } catch (error) {
2389
+ handleApiError(error);
2390
+ }
2391
+ });
2392
+ store.command("catalog <store>").description("Browse a store's product catalog (by name, slug, or ID)").option("-q, --query <query>", "Search within the store's products").option("-c, --category <category>", "Filter by category").option("--min-price <price>", "Minimum price (cents)", parseFloat).option("--max-price <price>", "Maximum price (cents)", parseFloat).option("--min-rating <rating>", "Minimum product rating (1-5)", parseFloat).option("--in-stock", "Only show in-stock items").option("--free-shipping", "Only show items with free shipping").option("-s, --sort <field>", "Sort by: price, rating, newest, name", "newest").option("--order <dir>", "Sort order: asc, desc", "desc").option("-p, --page <page>", "Page number", parseInt, 1).option("-n, --per-page <count>", "Results per page", parseInt, 20).option("--json", "Output raw JSON").action(async (storeId, opts) => {
2393
+ try {
2394
+ const spinner = ora7(`Fetching catalog for "${storeId}"...`).start();
2395
+ const api = getApiClient();
2396
+ const res = await api.get(`/stores/${encodeURIComponent(storeId)}/catalog`, {
2397
+ params: {
2398
+ q: opts.query,
2399
+ category: opts.category,
2400
+ minPrice: opts.minPrice,
2401
+ maxPrice: opts.maxPrice,
2402
+ minRating: opts.minRating,
2403
+ inStock: opts.inStock || void 0,
2404
+ freeShipping: opts.freeShipping || void 0,
2405
+ sort: opts.sort,
2406
+ order: opts.order,
2407
+ page: opts.page,
2408
+ pageSize: opts.perPage
2409
+ }
2410
+ });
2411
+ spinner.stop();
2412
+ const { store: storeInfo, products, total, page, pageSize } = res.data;
2413
+ if (opts.json) {
2414
+ console.log(JSON.stringify(res.data, null, 2));
2415
+ return;
2416
+ }
2417
+ const badge = storeInfo.verified ? chalk10.green(" \u2713") : "";
2418
+ const storeRating = storeInfo.rating != null ? chalk10.dim(` (${storeInfo.rating.toFixed(1)} \u2605)`) : "";
2419
+ console.log(
2420
+ chalk10.bold(`
2421
+ ${storeInfo.name}${badge}${storeRating} \u2014 ${total} products (page ${page})
2422
+ `)
2423
+ );
2424
+ if (products.length === 0) {
2425
+ console.log(chalk10.yellow(" No products found matching your filters.\n"));
2426
+ return;
2427
+ }
2428
+ for (const p of products) {
2429
+ const stock = p.inStock ? chalk10.green("In Stock") : p.backorder ? chalk10.yellow("Backorder") : chalk10.red("Out of Stock");
2430
+ const price = chalk10.bold.white(formatPrice3(p.priceInCents, p.currency));
2431
+ const stars = chalk10.yellow(renderStars3(p.rating));
2432
+ const shippingInfo = p.freeShipping ? chalk10.green("Free Shipping") : p.shippingPriceInCents != null ? chalk10.dim(`+${formatPrice3(p.shippingPriceInCents, p.currency)} shipping`) : "";
2433
+ const deliveryInfo = p.shippingDays != null ? chalk10.dim(`(${deliveryLabel2(p.shippingDays)})`) : "";
2434
+ console.log(` ${chalk10.bold.cyan(p.name)} ${chalk10.dim(`(${p.id})`)}`);
2435
+ console.log(
2436
+ ` ${price} ${stock} ${stars} ${chalk10.dim(`(${p.reviewCount} reviews)`)}` + (shippingInfo ? ` ${shippingInfo}` : "") + (deliveryInfo ? ` ${deliveryInfo}` : "")
2437
+ );
2438
+ const meta = [];
2439
+ if (p.category) meta.push(p.category);
2440
+ if (p.brand) meta.push(p.brand);
2441
+ if (p.variant) meta.push(p.variant);
2442
+ if (meta.length) console.log(` ${chalk10.dim(meta.join(" \xB7 "))}`);
2443
+ const returnInfo = [];
2444
+ if (p.freeReturns) returnInfo.push("Free Returns");
2445
+ if (p.returnWindowDays) returnInfo.push(`${p.returnWindowDays}d return window`);
2446
+ if (returnInfo.length) console.log(` ${chalk10.dim(returnInfo.join(" \xB7 "))}`);
2447
+ const desc = p.description.length > 120 ? p.description.slice(0, 120) + "..." : p.description;
2448
+ console.log(` ${desc}`);
2449
+ console.log();
2450
+ }
2451
+ const totalPages = Math.ceil(total / pageSize);
2452
+ if (totalPages > 1) {
2453
+ console.log(chalk10.dim(` Page ${page} of ${totalPages}. Use --page to navigate.
2454
+ `));
2455
+ }
2456
+ } catch (error) {
2457
+ handleApiError(error);
2458
+ }
2459
+ });
2460
+ }
2461
+
2462
+ // src/commands/status.ts
2463
+ import chalk11 from "chalk";
2464
+ import ora8 from "ora";
2465
+ function registerStatusCommand(program2) {
2466
+ program2.command("status").description("Show full account overview \u2014 user, agents, addresses, payment methods").option("--json", "Output raw JSON").action(async (opts) => {
2467
+ try {
2468
+ if (!await isLoggedIn()) {
2469
+ console.log(chalk11.yellow("\nNot logged in. Run: clishop login\n"));
2470
+ return;
2471
+ }
2472
+ const spinner = ora8("Fetching account overview...").start();
2473
+ const api = getApiClient();
2474
+ const cfg = getConfig();
2475
+ const activeAgentName = cfg.get("activeAgent") || "default";
2476
+ const [userInfo, agentsRes] = await Promise.all([
2477
+ getUserInfo(),
2478
+ api.get("/agents")
2479
+ ]);
2480
+ const agents = agentsRes.data.agents || [];
2481
+ const agentDetails = await Promise.all(
2482
+ agents.map(async (agent) => {
2483
+ const [addressesRes, paymentsRes] = await Promise.all([
2484
+ api.get("/addresses", { params: { agent: agent.name } }),
2485
+ api.get("/payment-methods", { params: { agent: agent.name } })
2486
+ ]);
2487
+ return {
2488
+ ...agent,
2489
+ addresses: addressesRes.data.addresses || [],
2490
+ paymentMethods: paymentsRes.data.paymentMethods || []
2491
+ };
2492
+ })
2493
+ );
2494
+ spinner.stop();
2495
+ if (opts.json) {
2496
+ console.log(JSON.stringify({
2497
+ user: userInfo,
2498
+ activeAgent: activeAgentName,
2499
+ agents: agentDetails
2500
+ }, null, 2));
2501
+ return;
2502
+ }
2503
+ console.log(chalk11.bold.cyan("\n\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550"));
2504
+ console.log(chalk11.bold.cyan(" CLISHOP Account Overview"));
2505
+ console.log(chalk11.bold.cyan("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n"));
2506
+ console.log(chalk11.bold(" \u{1F464} User"));
2507
+ console.log(` Name: ${chalk11.white(userInfo?.name || "\u2014")}`);
2508
+ console.log(` Email: ${chalk11.white(userInfo?.email || "\u2014")}`);
2509
+ console.log(` ID: ${chalk11.dim(userInfo?.id || "\u2014")}`);
2510
+ console.log();
2511
+ console.log(chalk11.bold(` \u{1F916} Agents (${agents.length})`));
2512
+ if (agentDetails.length === 0) {
2513
+ console.log(chalk11.yellow(" No agents found."));
2514
+ }
2515
+ for (const agent of agentDetails) {
2516
+ const isActive = agent.name === activeAgentName;
2517
+ const marker = isActive ? chalk11.green("\u25CF ") : " ";
2518
+ const activeLabel = isActive ? chalk11.green(" (active)") : "";
2519
+ const limit = agent.maxOrderAmountInCents ? `$${(agent.maxOrderAmountInCents / 100).toFixed(2)}` : "No limit";
2520
+ const confirm = agent.requireConfirmation ? "Yes" : "No";
2521
+ console.log();
2522
+ console.log(` ${marker}${chalk11.bold.white(agent.name)}${activeLabel}`);
2523
+ console.log(` ID: ${chalk11.dim(agent.id)}`);
2524
+ console.log(` Max order: ${chalk11.yellow(limit)}`);
2525
+ console.log(` Require confirm: ${confirm}`);
2526
+ console.log();
2527
+ console.log(chalk11.bold(` \u{1F4CD} Addresses (${agent.addresses.length})`));
2528
+ if (agent.addresses.length === 0) {
2529
+ console.log(chalk11.dim(" None"));
2530
+ } else {
2531
+ for (const addr of agent.addresses) {
2532
+ const isDefault = addr.id === agent.defaultAddressId;
2533
+ const defaultBadge = isDefault ? chalk11.green(" \u2605 default") : "";
2534
+ console.log(` \u2022 ${chalk11.white(addr.label)}${defaultBadge}`);
2535
+ console.log(chalk11.dim(` ${addr.line1}, ${addr.city}, ${addr.postalCode} ${addr.country}`));
2536
+ }
2537
+ }
2538
+ console.log();
2539
+ console.log(chalk11.bold(` \u{1F4B3} Payment Methods (${agent.paymentMethods.length})`));
2540
+ if (agent.paymentMethods.length === 0) {
2541
+ console.log(chalk11.dim(" None \u2014 run: clishop payment add"));
2542
+ } else {
2543
+ for (const pm of agent.paymentMethods) {
2544
+ const isDefault = pm.id === agent.defaultPaymentMethodId;
2545
+ const defaultBadge = isDefault ? chalk11.green(" \u2605 default") : "";
2546
+ const last4 = pm.last4 ? ` \u2022\u2022\u2022\u2022 ${pm.last4}` : "";
2547
+ console.log(` \u2022 ${chalk11.white(pm.label)}${last4}${defaultBadge}`);
2548
+ console.log(chalk11.dim(` ${pm.type} via ${pm.provider || "unknown"} (${pm.id})`));
2549
+ }
2550
+ }
2551
+ }
2552
+ console.log();
2553
+ console.log(chalk11.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
2554
+ console.log(chalk11.dim(" Tip: Use --agent <name> to run commands as a specific agent"));
2555
+ console.log(chalk11.dim(" Tip: Use 'clishop agent switch <name>' to change active agent"));
2556
+ console.log();
2557
+ } catch (error) {
2558
+ handleApiError(error);
2559
+ }
2560
+ });
2561
+ }
2562
+
2563
+ // src/commands/setup.ts
2564
+ import chalk12 from "chalk";
2565
+ import ora9 from "ora";
2566
+ import inquirer7 from "inquirer";
2567
+ function divider(color = chalk12.cyan) {
2568
+ console.log(" " + color("\u2500".repeat(48)));
2569
+ }
2570
+ function stepHeader(step, total, title) {
2571
+ console.log();
2572
+ divider();
2573
+ console.log();
2574
+ console.log(
2575
+ chalk12.bold.white(` STEP ${step} of ${total}`) + chalk12.dim(" \xB7 ") + chalk12.bold(title)
2576
+ );
2577
+ console.log();
2578
+ }
2579
+ function formatPrice4(cents, currency = "USD") {
2580
+ return new Intl.NumberFormat("en-US", {
2581
+ style: "currency",
2582
+ currency
2583
+ }).format(cents / 100);
2584
+ }
2585
+ function registerSetupCommand(program2) {
2586
+ program2.command("setup").description(
2587
+ "First-time setup wizard \u2014 account, agent, address, payment, first search"
2588
+ ).action(async () => {
2589
+ await runSetupWizard();
2590
+ });
2591
+ }
2592
+ async function runSetupWizard() {
2593
+ const config2 = getConfig();
2594
+ console.log();
2595
+ divider(chalk12.cyan);
2596
+ console.log();
2597
+ console.log(chalk12.bold.cyan(" W E L C O M E T O C L I S H O P"));
2598
+ console.log(chalk12.dim(" Order anything from your terminal."));
2599
+ console.log();
2600
+ divider(chalk12.cyan);
2601
+ console.log();
2602
+ console.log(
2603
+ chalk12.dim(" This wizard will guide you through the initial setup.")
2604
+ );
2605
+ console.log(
2606
+ chalk12.dim(" It only takes a minute. You can re-run it anytime with:")
2607
+ );
2608
+ console.log(chalk12.dim(" ") + chalk12.white("clishop setup"));
2609
+ stepHeader(1, 5, "Account");
2610
+ let loggedIn = await isLoggedIn();
2611
+ if (loggedIn) {
2612
+ const user = await getUserInfo();
2613
+ console.log(
2614
+ chalk12.green(
2615
+ ` \u2713 Already logged in as ${chalk12.bold(user?.name || user?.email || "unknown")}`
2616
+ )
2617
+ );
2618
+ console.log();
2619
+ const { continueAs } = await inquirer7.prompt([
2620
+ {
2621
+ type: "confirm",
2622
+ name: "continueAs",
2623
+ message: `Continue as ${user?.email}?`,
2624
+ default: true
2625
+ }
2626
+ ]);
2627
+ if (!continueAs) {
2628
+ loggedIn = false;
2629
+ }
2630
+ }
2631
+ if (!loggedIn) {
2632
+ const { authChoice } = await inquirer7.prompt([
2633
+ {
2634
+ type: "select",
2635
+ name: "authChoice",
2636
+ message: "Do you have a CLISHOP account?",
2637
+ choices: [
2638
+ { name: "No \u2014 create a new account", value: "register" },
2639
+ { name: "Yes \u2014 log in to existing account", value: "login" }
2640
+ ]
2641
+ }
2642
+ ]);
2643
+ if (authChoice === "register") {
2644
+ const answers = await inquirer7.prompt([
2645
+ { type: "input", name: "name", message: "Your name:" },
2646
+ { type: "input", name: "email", message: "Email:" },
2647
+ {
2648
+ type: "password",
2649
+ name: "password",
2650
+ message: "Password:",
2651
+ mask: "*"
2652
+ },
2653
+ {
2654
+ type: "password",
2655
+ name: "confirmPassword",
2656
+ message: "Confirm password:",
2657
+ mask: "*"
2658
+ }
2659
+ ]);
2660
+ if (answers.password !== answers.confirmPassword) {
2661
+ console.error(chalk12.red("\n \u2717 Passwords do not match."));
2662
+ console.log(
2663
+ chalk12.dim(" Run ") + chalk12.white("clishop setup") + chalk12.dim(" to try again.\n")
2664
+ );
2665
+ process.exitCode = 1;
2666
+ return;
2667
+ }
2668
+ const spinner = ora9("Creating your account...").start();
2669
+ try {
2670
+ const user = await register(
2671
+ answers.email,
2672
+ answers.password,
2673
+ answers.name
2674
+ );
2675
+ spinner.succeed(
2676
+ chalk12.green(
2677
+ `Account created! Welcome, ${chalk12.bold(user.name)}.`
2678
+ )
2679
+ );
2680
+ } catch (error) {
2681
+ spinner.fail(
2682
+ chalk12.red(
2683
+ `Registration failed: ${error?.response?.data?.message || error.message}`
2684
+ )
2685
+ );
2686
+ console.log();
2687
+ console.log(
2688
+ chalk12.dim(
2689
+ " Make sure the backend is running at: " + chalk12.white(config2.get("apiBaseUrl"))
2690
+ )
2691
+ );
2692
+ console.log(
2693
+ chalk12.dim(" Then run ") + chalk12.white("clishop setup") + chalk12.dim(" again.\n")
2694
+ );
2695
+ process.exitCode = 1;
2696
+ return;
2697
+ }
2698
+ } else {
2699
+ const answers = await inquirer7.prompt([
2700
+ { type: "input", name: "email", message: "Email:" },
2701
+ {
2702
+ type: "password",
2703
+ name: "password",
2704
+ message: "Password:",
2705
+ mask: "*"
2706
+ }
2707
+ ]);
2708
+ const spinner = ora9("Logging in...").start();
2709
+ try {
2710
+ const user = await login(answers.email, answers.password);
2711
+ spinner.succeed(
2712
+ chalk12.green(`Logged in as ${chalk12.bold(user.name)}.`)
2713
+ );
2714
+ } catch (error) {
2715
+ spinner.fail(
2716
+ chalk12.red(
2717
+ `Login failed: ${error?.response?.data?.message || error.message}`
2718
+ )
2719
+ );
2720
+ console.log();
2721
+ console.log(
2722
+ chalk12.dim(
2723
+ " Make sure the backend is running at: " + chalk12.white(config2.get("apiBaseUrl"))
2724
+ )
2725
+ );
2726
+ console.log(
2727
+ chalk12.dim(" Then run ") + chalk12.white("clishop setup") + chalk12.dim(" again.\n")
2728
+ );
2729
+ process.exitCode = 1;
2730
+ return;
2731
+ }
2732
+ }
2733
+ }
2734
+ stepHeader(2, 5, "Agent (optional)");
2735
+ const activeAgent = getActiveAgent();
2736
+ console.log(
2737
+ chalk12.dim(
2738
+ ` A default agent is ready ($${activeAgent.maxOrderAmount} limit, confirmation on).`
2739
+ )
2740
+ );
2741
+ console.log(
2742
+ chalk12.dim(" Agents control spending limits and category restrictions.")
2743
+ );
2744
+ console.log();
2745
+ const { agentChoice } = await inquirer7.prompt([
2746
+ {
2747
+ type: "confirm",
2748
+ name: "agentChoice",
2749
+ message: "Configure a custom agent?",
2750
+ default: false
2751
+ }
2752
+ ]);
2753
+ if (agentChoice) {
2754
+ const answers = await inquirer7.prompt([
2755
+ {
2756
+ type: "input",
2757
+ name: "name",
2758
+ message: "Agent name:",
2759
+ validate: (v) => v.trim() ? true : "Name is required"
2760
+ },
2761
+ {
2762
+ type: "number",
2763
+ name: "maxOrderAmount",
2764
+ message: "Max order amount ($):",
2765
+ default: 500
2766
+ },
2767
+ {
2768
+ type: "confirm",
2769
+ name: "requireConfirmation",
2770
+ message: "Require confirmation before ordering?",
2771
+ default: true
2772
+ }
2773
+ ]);
2774
+ try {
2775
+ const agent = createAgent(answers.name.trim(), {
2776
+ maxOrderAmount: answers.maxOrderAmount,
2777
+ requireConfirmation: answers.requireConfirmation
2778
+ });
2779
+ setActiveAgent(agent.name);
2780
+ console.log(
2781
+ chalk12.green(
2782
+ `
2783
+ \u2713 Agent "${chalk12.bold(agent.name)}" created and set as active.`
2784
+ )
2785
+ );
2786
+ } catch (error) {
2787
+ console.error(chalk12.red(`
2788
+ \u2717 ${error.message}`));
2789
+ console.log(chalk12.dim(" Continuing with the default agent."));
2790
+ }
2791
+ } else {
2792
+ console.log(chalk12.green(" \u2713 Using default agent."));
2793
+ }
2794
+ stepHeader(3, 5, "Shipping Address");
2795
+ console.log(
2796
+ chalk12.dim(
2797
+ " Add an address so products can be delivered to you."
2798
+ )
2799
+ );
2800
+ console.log();
2801
+ const { addAddress } = await inquirer7.prompt([
2802
+ {
2803
+ type: "confirm",
2804
+ name: "addAddress",
2805
+ message: "Add a shipping address now?",
2806
+ default: true
2807
+ }
2808
+ ]);
2809
+ let addressCity = "";
2810
+ if (addAddress) {
2811
+ const addr = await inquirer7.prompt([
2812
+ {
2813
+ type: "input",
2814
+ name: "label",
2815
+ message: "Label (e.g. Home, Office):",
2816
+ default: "Home"
2817
+ },
2818
+ {
2819
+ type: "input",
2820
+ name: "recipientName",
2821
+ message: "Recipient name (optional):"
2822
+ },
2823
+ {
2824
+ type: "input",
2825
+ name: "recipientPhone",
2826
+ message: "Recipient phone (optional):"
2827
+ },
2828
+ {
2829
+ type: "input",
2830
+ name: "line1",
2831
+ message: "Street name and number:",
2832
+ validate: (v) => v.trim() ? true : "Required"
2833
+ },
2834
+ {
2835
+ type: "input",
2836
+ name: "line2",
2837
+ message: "Apartment, suite, floor, etc. (optional):"
2838
+ },
2839
+ {
2840
+ type: "input",
2841
+ name: "postalCode",
2842
+ message: "Postal / ZIP code:",
2843
+ validate: (v) => v.trim() ? true : "Required"
2844
+ },
2845
+ {
2846
+ type: "input",
2847
+ name: "city",
2848
+ message: "City:",
2849
+ validate: (v) => v.trim() ? true : "Required"
2850
+ },
2851
+ {
2852
+ type: "input",
2853
+ name: "region",
2854
+ message: "State / Province / Region (optional):"
2855
+ },
2856
+ {
2857
+ type: "input",
2858
+ name: "country",
2859
+ message: "Country:",
2860
+ validate: (v) => v.trim() ? true : "Required"
2861
+ },
2862
+ {
2863
+ type: "input",
2864
+ name: "instructions",
2865
+ message: "Delivery instructions (optional):"
2866
+ },
2867
+ {
2868
+ type: "confirm",
2869
+ name: "isCompany",
2870
+ message: "Is this a company/business address?",
2871
+ default: false
2872
+ }
2873
+ ]);
2874
+ let companyInfo = { companyName: "", vatNumber: "", taxId: "" };
2875
+ if (addr.isCompany) {
2876
+ companyInfo = await inquirer7.prompt([
2877
+ {
2878
+ type: "input",
2879
+ name: "companyName",
2880
+ message: "Company name:",
2881
+ validate: (v) => v.trim() ? true : "Required for company addresses"
2882
+ },
2883
+ {
2884
+ type: "input",
2885
+ name: "vatNumber",
2886
+ message: "VAT number (optional):"
2887
+ },
2888
+ {
2889
+ type: "input",
2890
+ name: "taxId",
2891
+ message: "Tax ID / EIN (optional):"
2892
+ }
2893
+ ]);
2894
+ }
2895
+ addressCity = addr.city;
2896
+ const spinner = ora9("Saving address...").start();
2897
+ try {
2898
+ const api = getApiClient();
2899
+ const agent = getActiveAgent();
2900
+ const res = await api.post("/addresses", {
2901
+ agent: agent.name,
2902
+ label: addr.label,
2903
+ recipientName: addr.recipientName || void 0,
2904
+ recipientPhone: addr.recipientPhone || void 0,
2905
+ companyName: companyInfo.companyName || void 0,
2906
+ vatNumber: companyInfo.vatNumber || void 0,
2907
+ taxId: companyInfo.taxId || void 0,
2908
+ line1: addr.line1,
2909
+ line2: addr.line2 || void 0,
2910
+ city: addr.city,
2911
+ region: addr.region || void 0,
2912
+ postalCode: addr.postalCode,
2913
+ country: addr.country,
2914
+ instructions: addr.instructions || void 0
2915
+ });
2916
+ updateAgent(agent.name, { defaultAddressId: res.data.address.id });
2917
+ spinner.succeed(
2918
+ chalk12.green(
2919
+ `Address "${addr.label}" saved and set as default.`
2920
+ )
2921
+ );
2922
+ } catch (error) {
2923
+ spinner.fail(
2924
+ chalk12.red(
2925
+ `Failed to save address: ${error?.response?.data?.message || error.message}`
2926
+ )
2927
+ );
2928
+ console.log(
2929
+ chalk12.dim(
2930
+ " You can add an address later with: " + chalk12.white("clishop address add")
2931
+ )
2932
+ );
2933
+ }
2934
+ } else {
2935
+ console.log(
2936
+ chalk12.dim(
2937
+ "\n You can add one later with: " + chalk12.white("clishop address add")
2938
+ )
2939
+ );
2940
+ }
2941
+ stepHeader(4, 5, "Payment Method");
2942
+ console.log(
2943
+ chalk12.dim(
2944
+ " For security, payment details are entered through a secure web"
2945
+ )
2946
+ );
2947
+ console.log(
2948
+ chalk12.dim(" page. The CLI never sees your card number.")
2949
+ );
2950
+ console.log();
2951
+ const { addPayment } = await inquirer7.prompt([
2952
+ {
2953
+ type: "confirm",
2954
+ name: "addPayment",
2955
+ message: "Set up a payment method now?",
2956
+ default: true
2957
+ }
2958
+ ]);
2959
+ if (addPayment) {
2960
+ const spinner = ora9("Requesting secure payment setup link...").start();
2961
+ try {
2962
+ const api = getApiClient();
2963
+ const agent = getActiveAgent();
2964
+ const res = await api.post("/payment-methods/setup", {
2965
+ agent: agent.name
2966
+ });
2967
+ spinner.stop();
2968
+ const { setupUrl } = res.data;
2969
+ console.log();
2970
+ console.log(
2971
+ chalk12.bold(
2972
+ " Open this link in your browser to add a payment method:"
2973
+ )
2974
+ );
2975
+ console.log();
2976
+ console.log(" " + chalk12.cyan.underline(setupUrl));
2977
+ console.log();
2978
+ console.log(
2979
+ chalk12.dim(
2980
+ " Once done, verify with: " + chalk12.white("clishop payment list")
2981
+ )
2982
+ );
2983
+ } catch (error) {
2984
+ spinner.fail(
2985
+ chalk12.red(
2986
+ `Could not get setup link: ${error?.response?.data?.message || error.message}`
2987
+ )
2988
+ );
2989
+ console.log(
2990
+ chalk12.dim(
2991
+ " You can set up payment later with: " + chalk12.white("clishop payment add")
2992
+ )
2993
+ );
2994
+ }
2995
+ } else {
2996
+ console.log(
2997
+ chalk12.dim(
2998
+ "\n You can set one up later with: " + chalk12.white("clishop payment add")
2999
+ )
3000
+ );
3001
+ }
3002
+ stepHeader(5, 5, "Your First Search");
3003
+ if (addressCity) {
3004
+ console.log(
3005
+ chalk12.dim(
3006
+ ` Let's find something! Products can be shipped to ${chalk12.white(addressCity)}.`
3007
+ )
3008
+ );
3009
+ } else {
3010
+ console.log(chalk12.dim(" Let's find something to order!"));
3011
+ }
3012
+ console.log();
3013
+ const { searchQuery } = await inquirer7.prompt([
3014
+ {
3015
+ type: "input",
3016
+ name: "searchQuery",
3017
+ message: "Search for a product (or press Enter to skip):",
3018
+ default: "headphones"
3019
+ }
3020
+ ]);
3021
+ if (searchQuery.trim()) {
3022
+ const spinner = ora9(`Searching for "${searchQuery}"...`).start();
3023
+ try {
3024
+ const api = getApiClient();
3025
+ const res = await api.get("/products/search", {
3026
+ params: { q: searchQuery, page: 1, pageSize: 5 }
3027
+ });
3028
+ spinner.stop();
3029
+ const result = res.data;
3030
+ if (result.products.length === 0) {
3031
+ console.log(
3032
+ chalk12.yellow(
3033
+ `
3034
+ No results for "${searchQuery}". Try other terms later!`
3035
+ )
3036
+ );
3037
+ } else {
3038
+ console.log(
3039
+ chalk12.bold(
3040
+ `
3041
+ Found ${result.total} result${result.total !== 1 ? "s" : ""} for "${searchQuery}":
3042
+ `
3043
+ )
3044
+ );
3045
+ for (const p of result.products) {
3046
+ const price = formatPrice4(p.priceInCents, p.currency || "USD");
3047
+ const stock = p.inStock ? chalk12.green("In Stock") : chalk12.red("Out of Stock");
3048
+ console.log(
3049
+ ` ${chalk12.bold.cyan(p.name)} ${chalk12.bold.white(price)} ${stock}`
3050
+ );
3051
+ console.log(chalk12.dim(` ID: ${p.id}`));
3052
+ console.log(
3053
+ chalk12.dim(
3054
+ ` ${p.description.length > 100 ? p.description.slice(0, 100) + "..." : p.description}`
3055
+ )
3056
+ );
3057
+ console.log();
3058
+ }
3059
+ console.log(
3060
+ chalk12.dim(" Buy a product with: ") + chalk12.white("clishop buy <product-id>")
3061
+ );
3062
+ }
3063
+ } catch (error) {
3064
+ spinner.fail(
3065
+ chalk12.red(
3066
+ `Search failed: ${error?.response?.data?.message || error.message}`
3067
+ )
3068
+ );
3069
+ }
3070
+ }
3071
+ config2.set("setupCompleted", true);
3072
+ console.log();
3073
+ divider(chalk12.green);
3074
+ console.log();
3075
+ console.log(chalk12.bold.green(" \u2713 You're all set!"));
3076
+ console.log();
3077
+ console.log(chalk12.dim(" Here are some commands to get you started:"));
3078
+ console.log();
3079
+ console.log(
3080
+ chalk12.white(" clishop search <query> ") + chalk12.dim("Search for products")
3081
+ );
3082
+ console.log(
3083
+ chalk12.white(" clishop buy <id> ") + chalk12.dim("Quick-buy a product")
3084
+ );
3085
+ console.log(
3086
+ chalk12.white(" clishop order list ") + chalk12.dim("View your orders")
3087
+ );
3088
+ console.log(
3089
+ chalk12.white(" clishop agent list ") + chalk12.dim("Manage your agents")
3090
+ );
3091
+ console.log(
3092
+ chalk12.white(" clishop --help ") + chalk12.dim("See all commands")
3093
+ );
3094
+ console.log();
3095
+ divider(chalk12.green);
3096
+ console.log();
3097
+ }
3098
+
3099
+ // src/commands/advertise.ts
3100
+ import chalk13 from "chalk";
3101
+ import ora10 from "ora";
3102
+ import inquirer8 from "inquirer";
3103
+ function formatPrice5(cents, currency) {
3104
+ return new Intl.NumberFormat("en-US", { style: "currency", currency }).format(cents / 100);
3105
+ }
3106
+ var STATUS_COLORS2 = {
3107
+ open: chalk13.green,
3108
+ closed: chalk13.dim,
3109
+ accepted: chalk13.blue,
3110
+ cancelled: chalk13.red,
3111
+ expired: chalk13.yellow
3112
+ };
3113
+ var BID_STATUS_COLORS = {
3114
+ pending: chalk13.yellow,
3115
+ accepted: chalk13.green,
3116
+ rejected: chalk13.red,
3117
+ withdrawn: chalk13.dim
3118
+ };
3119
+ function registerAdvertiseCommands(program2) {
3120
+ const advertise = program2.command("advertise").description("Advertise a request for vendors to bid on (when you can't find what you need)");
3121
+ advertise.command("create").alias("new").description("Create a new advertised request").action(async () => {
3122
+ try {
3123
+ const agent = getActiveAgent();
3124
+ console.log(chalk13.bold("\n \u{1F4E2} Advertise a Request\n"));
3125
+ console.log(chalk13.dim(" Can't find what you need? Describe it and vendors will bid to fulfill it.\n"));
3126
+ const answers = await inquirer8.prompt([
3127
+ {
3128
+ type: "input",
3129
+ name: "title",
3130
+ message: "What are you looking for? (product name / title):",
3131
+ validate: (v) => v.trim() ? true : "Required"
3132
+ },
3133
+ {
3134
+ type: "input",
3135
+ name: "description",
3136
+ message: "Describe what you need in detail (optional):"
3137
+ },
3138
+ {
3139
+ type: "input",
3140
+ name: "sku",
3141
+ message: "Specific SKU (optional):"
3142
+ },
3143
+ {
3144
+ type: "input",
3145
+ name: "brand",
3146
+ message: "Preferred brand (optional):"
3147
+ },
3148
+ {
3149
+ type: "input",
3150
+ name: "company",
3151
+ message: "Preferred company / manufacturer (optional):"
3152
+ },
3153
+ {
3154
+ type: "input",
3155
+ name: "features",
3156
+ message: "Desired features (optional):"
3157
+ },
3158
+ {
3159
+ type: "number",
3160
+ name: "quantity",
3161
+ message: "Quantity needed:",
3162
+ default: 1
3163
+ },
3164
+ {
3165
+ type: "confirm",
3166
+ name: "recurring",
3167
+ message: "Is this a recurring order?",
3168
+ default: false
3169
+ }
3170
+ ]);
3171
+ let recurringNote;
3172
+ if (answers.recurring) {
3173
+ const recAnswer = await inquirer8.prompt([
3174
+ {
3175
+ type: "input",
3176
+ name: "recurringNote",
3177
+ message: "How often? (e.g. weekly, monthly, every 2 weeks):"
3178
+ }
3179
+ ]);
3180
+ recurringNote = recAnswer.recurringNote || void 0;
3181
+ }
3182
+ const priceAnswers = await inquirer8.prompt([
3183
+ {
3184
+ type: "input",
3185
+ name: "bidPrice",
3186
+ message: "Max bid price you're willing to pay (e.g. 49.99, or leave empty):"
3187
+ }
3188
+ ]);
3189
+ let currency = "USD";
3190
+ if (priceAnswers.bidPrice && parseFloat(priceAnswers.bidPrice) > 0) {
3191
+ const currencyAnswer = await inquirer8.prompt([
3192
+ {
3193
+ type: "list",
3194
+ name: "currency",
3195
+ message: "Currency:",
3196
+ choices: [
3197
+ { name: "USD ($)", value: "USD" },
3198
+ { name: "EUR (\u20AC)", value: "EUR" },
3199
+ { name: "GBP (\xA3)", value: "GBP" },
3200
+ { name: "CAD (C$)", value: "CAD" },
3201
+ { name: "AUD (A$)", value: "AUD" },
3202
+ { name: "JPY (\xA5)", value: "JPY" },
3203
+ { name: "CHF (Fr)", value: "CHF" },
3204
+ { name: "CNY (\xA5)", value: "CNY" },
3205
+ { name: "INR (\u20B9)", value: "INR" },
3206
+ { name: "Other (enter code)", value: "OTHER" }
3207
+ ],
3208
+ default: "USD"
3209
+ }
3210
+ ]);
3211
+ if (currencyAnswer.currency === "OTHER") {
3212
+ const customCurrency = await inquirer8.prompt([
3213
+ {
3214
+ type: "input",
3215
+ name: "code",
3216
+ message: "Enter 3-letter currency code (e.g. SEK, NZD, MXN):",
3217
+ validate: (v) => /^[A-Z]{3}$/i.test(v.trim()) || "Enter a valid 3-letter code"
3218
+ }
3219
+ ]);
3220
+ currency = customCurrency.code.toUpperCase().trim();
3221
+ } else {
3222
+ currency = currencyAnswer.currency;
3223
+ }
3224
+ }
3225
+ const speedAnswer = await inquirer8.prompt([
3226
+ {
3227
+ type: "input",
3228
+ name: "speedDays",
3229
+ message: "Desired delivery speed in days (optional):"
3230
+ }
3231
+ ]);
3232
+ const returnAnswers = await inquirer8.prompt([
3233
+ {
3234
+ type: "confirm",
3235
+ name: "freeReturns",
3236
+ message: "Require free returns?",
3237
+ default: false
3238
+ },
3239
+ {
3240
+ type: "input",
3241
+ name: "minReturnDays",
3242
+ message: "Minimum return window in days (optional, e.g. 30):"
3243
+ }
3244
+ ]);
3245
+ const api = getApiClient();
3246
+ let addressId;
3247
+ const addrSpinner = ora10("Fetching your addresses...").start();
3248
+ try {
3249
+ const addrRes = await api.get("/addresses", { params: { agent: agent.name } });
3250
+ addrSpinner.stop();
3251
+ const addresses = addrRes.data.addresses;
3252
+ if (addresses.length > 0) {
3253
+ const addrMap = /* @__PURE__ */ new Map();
3254
+ const addrChoices = [];
3255
+ for (const a of addresses) {
3256
+ const displayName = `${a.label} \u2014 ${a.line1}`;
3257
+ addrMap.set(displayName, a.id);
3258
+ addrChoices.push({ name: displayName, value: displayName });
3259
+ }
3260
+ const skipOption = "Skip \u2014 don't set a delivery address";
3261
+ addrChoices.push({ name: chalk13.dim(skipOption), value: skipOption });
3262
+ const defaultAddr = addresses.find((a) => a.id === agent.defaultAddressId);
3263
+ const defaultDisplay = defaultAddr ? `${defaultAddr.label} \u2014 ${defaultAddr.line1}` : "";
3264
+ const { selectedAddress } = await inquirer8.prompt([
3265
+ {
3266
+ type: "list",
3267
+ name: "selectedAddress",
3268
+ message: "Delivery location:",
3269
+ choices: addrChoices,
3270
+ default: defaultDisplay
3271
+ }
3272
+ ]);
3273
+ addressId = addrMap.get(selectedAddress) || void 0;
3274
+ } else {
3275
+ addrSpinner.stop();
3276
+ console.log(chalk13.dim(" No addresses found. You can add one later with: clishop address add"));
3277
+ }
3278
+ } catch {
3279
+ addrSpinner.stop();
3280
+ console.log(chalk13.dim(" Could not fetch addresses. Skipping delivery location."));
3281
+ }
3282
+ let paymentMethods;
3283
+ const paySpinner = ora10("Fetching your payment methods...").start();
3284
+ try {
3285
+ const payRes = await api.get("/payment-methods", { params: { agent: agent.name } });
3286
+ paySpinner.stop();
3287
+ const payments = payRes.data.paymentMethods;
3288
+ if (payments.length > 0) {
3289
+ const payChoices = [
3290
+ { name: chalk13.green("Accept all payment methods"), value: "__ALL__" },
3291
+ ...payments.map((p) => ({
3292
+ name: `${p.label}${p.brand ? ` (${p.brand})` : ""}`,
3293
+ value: p.id,
3294
+ checked: true
3295
+ // Default: all user's payment methods selected
3296
+ }))
3297
+ ];
3298
+ const { selectedPayments } = await inquirer8.prompt([
3299
+ {
3300
+ type: "checkbox",
3301
+ name: "selectedPayments",
3302
+ message: "Accepted payment methods:",
3303
+ choices: payChoices
3304
+ }
3305
+ ]);
3306
+ if (selectedPayments.includes("__ALL__")) {
3307
+ paymentMethods = "all";
3308
+ } else if (selectedPayments.length > 0) {
3309
+ paymentMethods = JSON.stringify(selectedPayments);
3310
+ }
3311
+ } else {
3312
+ console.log(chalk13.dim(" No payment methods found. You can add one later with: clishop payment add"));
3313
+ }
3314
+ } catch {
3315
+ paySpinner.stop();
3316
+ console.log(chalk13.dim(" Could not fetch payment methods. Skipping."));
3317
+ }
3318
+ const body = {
3319
+ title: answers.title,
3320
+ description: answers.description || void 0,
3321
+ sku: answers.sku || void 0,
3322
+ brand: answers.brand || void 0,
3323
+ company: answers.company || void 0,
3324
+ features: answers.features || void 0,
3325
+ quantity: answers.quantity || 1,
3326
+ recurring: answers.recurring,
3327
+ recurringNote,
3328
+ currency,
3329
+ paymentMethods,
3330
+ addressId
3331
+ };
3332
+ if (priceAnswers.bidPrice) {
3333
+ const price = parseFloat(priceAnswers.bidPrice);
3334
+ if (!isNaN(price) && price > 0) {
3335
+ body.bidPriceInCents = Math.round(price * 100);
3336
+ }
3337
+ }
3338
+ if (speedAnswer.speedDays) {
3339
+ const days = parseInt(speedAnswer.speedDays, 10);
3340
+ if (!isNaN(days) && days > 0) {
3341
+ body.speedDays = days;
3342
+ }
3343
+ }
3344
+ if (returnAnswers.freeReturns) {
3345
+ body.freeReturns = true;
3346
+ }
3347
+ if (returnAnswers.minReturnDays) {
3348
+ const days = parseInt(returnAnswers.minReturnDays, 10);
3349
+ if (!isNaN(days) && days > 0) {
3350
+ body.minReturnDays = days;
3351
+ }
3352
+ }
3353
+ const spinner = ora10("Publishing your request...").start();
3354
+ const res = await api.post("/advertise", body);
3355
+ spinner.succeed(chalk13.green(`Request published! ID: ${chalk13.bold(res.data.advertise.id)}`));
3356
+ console.log(chalk13.dim("\n Vendors can now see your request and submit bids."));
3357
+ console.log(chalk13.dim(` Check bids with: clishop advertise show ${res.data.advertise.id}
3358
+ `));
3359
+ } catch (error) {
3360
+ handleApiError(error);
3361
+ }
3362
+ });
3363
+ advertise.command("quick <title>").description("Quickly advertise a request with flags").option("-d, --description <desc>", "Detailed description").option("--sku <sku>", "Specific SKU").option("--brand <brand>", "Preferred brand").option("--company <company>", "Preferred company").option("--features <features>", "Desired features").option("-q, --quantity <qty>", "Quantity", parseInt, 1).option("--recurring", "Recurring order").option("--recurring-note <note>", "Recurrence frequency").option("--bid-price <price>", "Max bid price", parseFloat).option("--currency <code>", "Currency code (default: USD)").option("--speed <days>", "Desired delivery days", parseInt).option("--free-returns", "Require free returns").option("--min-return-days <days>", "Minimum return window in days", parseInt).option("--payment-methods <methods>", 'Payment methods: "all" or comma-separated IDs').option("--address <id>", "Address ID for delivery").action(async (title, opts) => {
3364
+ try {
3365
+ let paymentMethods;
3366
+ if (opts.paymentMethods) {
3367
+ if (opts.paymentMethods.toLowerCase() === "all") {
3368
+ paymentMethods = "all";
3369
+ } else {
3370
+ const ids = opts.paymentMethods.split(",").map((id) => id.trim()).filter(Boolean);
3371
+ if (ids.length > 0) {
3372
+ paymentMethods = JSON.stringify(ids);
3373
+ }
3374
+ }
3375
+ }
3376
+ const body = {
3377
+ title,
3378
+ description: opts.description,
3379
+ sku: opts.sku,
3380
+ brand: opts.brand,
3381
+ company: opts.company,
3382
+ features: opts.features,
3383
+ quantity: opts.quantity,
3384
+ recurring: opts.recurring || false,
3385
+ recurringNote: opts.recurringNote,
3386
+ currency: opts.currency?.toUpperCase() || "USD",
3387
+ paymentMethods,
3388
+ addressId: opts.address
3389
+ };
3390
+ if (opts.bidPrice) {
3391
+ body.bidPriceInCents = Math.round(opts.bidPrice * 100);
3392
+ }
3393
+ if (opts.speed) {
3394
+ body.speedDays = opts.speed;
3395
+ }
3396
+ if (opts.freeReturns) {
3397
+ body.freeReturns = true;
3398
+ }
3399
+ if (opts.minReturnDays) {
3400
+ body.minReturnDays = opts.minReturnDays;
3401
+ }
3402
+ const spinner = ora10("Publishing your request...").start();
3403
+ const api = getApiClient();
3404
+ const res = await api.post("/advertise", body);
3405
+ spinner.succeed(chalk13.green(`Request published! ID: ${chalk13.bold(res.data.advertise.id)}`));
3406
+ } catch (error) {
3407
+ handleApiError(error);
3408
+ }
3409
+ });
3410
+ advertise.command("list").alias("ls").description("List your advertised requests").option("--status <status>", "Filter by status (open, closed, accepted, cancelled, expired)").option("-p, --page <page>", "Page number", parseInt, 1).option("--json", "Output raw JSON").action(async (opts) => {
3411
+ try {
3412
+ const spinner = ora10("Fetching your requests...").start();
3413
+ const api = getApiClient();
3414
+ const res = await api.get("/advertise", {
3415
+ params: { status: opts.status, page: opts.page }
3416
+ });
3417
+ spinner.stop();
3418
+ const ads = res.data.advertises;
3419
+ if (opts.json) {
3420
+ console.log(JSON.stringify(res.data, null, 2));
3421
+ return;
3422
+ }
3423
+ if (ads.length === 0) {
3424
+ console.log(chalk13.yellow("\nNo advertised requests found.\n"));
3425
+ console.log(chalk13.dim(" Create one with: clishop advertise create\n"));
3426
+ return;
3427
+ }
3428
+ console.log(chalk13.bold("\n\u{1F4E2} Your Advertised Requests:\n"));
3429
+ for (const ad of ads) {
3430
+ const statusColor = STATUS_COLORS2[ad.status] || chalk13.white;
3431
+ const date = new Date(ad.createdAt).toLocaleDateString();
3432
+ const bidCount = ad.bids?.length || 0;
3433
+ const bidInfo = bidCount > 0 ? chalk13.cyan(` (${bidCount} bid${bidCount > 1 ? "s" : ""})`) : chalk13.dim(" (no bids yet)");
3434
+ console.log(
3435
+ ` ${chalk13.bold(ad.id)} ${statusColor(ad.status.toUpperCase().padEnd(10))} ${chalk13.bold(ad.title)}${bidInfo} ${chalk13.dim(date)}`
3436
+ );
3437
+ const meta = [];
3438
+ if (ad.quantity > 1) meta.push(`qty: ${ad.quantity}`);
3439
+ if (ad.brand) meta.push(ad.brand);
3440
+ if (ad.bidPriceInCents) meta.push(`max: ${formatPrice5(ad.bidPriceInCents, ad.currency)}`);
3441
+ if (ad.speedDays) meta.push(`${ad.speedDays}-day delivery`);
3442
+ if (ad.freeReturns) meta.push("free returns");
3443
+ if (ad.minReturnDays) meta.push(`${ad.minReturnDays}d return min`);
3444
+ if (ad.recurring) meta.push("recurring");
3445
+ if (ad.address) meta.push(`\u2192 ${ad.address.label}`);
3446
+ if (meta.length) {
3447
+ console.log(` ${chalk13.dim(meta.join(" \xB7 "))}`);
3448
+ }
3449
+ console.log();
3450
+ }
3451
+ const totalPages = Math.ceil(res.data.total / res.data.pageSize);
3452
+ if (totalPages > 1) {
3453
+ console.log(chalk13.dim(` Page ${res.data.page} of ${totalPages}. Use --page to navigate.
3454
+ `));
3455
+ }
3456
+ } catch (error) {
3457
+ handleApiError(error);
3458
+ }
3459
+ });
3460
+ advertise.command("show <id>").description("View an advertised request and its bids").option("--json", "Output raw JSON").action(async (id, opts) => {
3461
+ try {
3462
+ const spinner = ora10("Fetching request...").start();
3463
+ const api = getApiClient();
3464
+ const res = await api.get(`/advertise/${id}`);
3465
+ spinner.stop();
3466
+ const ad = res.data.advertise;
3467
+ if (opts.json) {
3468
+ console.log(JSON.stringify(ad, null, 2));
3469
+ return;
3470
+ }
3471
+ const statusColor = STATUS_COLORS2[ad.status] || chalk13.white;
3472
+ console.log();
3473
+ console.log(chalk13.bold.cyan(` \u{1F4E2} ${ad.title}`));
3474
+ console.log(` ID: ${chalk13.dim(ad.id)}`);
3475
+ console.log(` Status: ${statusColor(ad.status.toUpperCase())}`);
3476
+ if (ad.description) console.log(` Details: ${ad.description}`);
3477
+ if (ad.sku) console.log(` SKU: ${ad.sku}`);
3478
+ if (ad.brand) console.log(` Brand: ${ad.brand}`);
3479
+ if (ad.company) console.log(` Company: ${ad.company}`);
3480
+ if (ad.features) console.log(` Features: ${ad.features}`);
3481
+ console.log(` Quantity: ${ad.quantity}`);
3482
+ if (ad.bidPriceInCents) console.log(` Max Bid: ${chalk13.bold(formatPrice5(ad.bidPriceInCents, ad.currency))}`);
3483
+ if (ad.speedDays) console.log(` Speed: ${ad.speedDays}-day delivery`);
3484
+ const returnReqs = [];
3485
+ if (ad.freeReturns) returnReqs.push(chalk13.green("Free Returns required"));
3486
+ if (ad.minReturnDays) returnReqs.push(`${ad.minReturnDays}-day return window min`);
3487
+ if (returnReqs.length) console.log(` Returns: ${returnReqs.join(" \xB7 ")}`);
3488
+ if (ad.recurring) console.log(` Recurring: Yes${ad.recurringNote ? ` (${ad.recurringNote})` : ""}`);
3489
+ if (ad.address) {
3490
+ const a = ad.address;
3491
+ console.log(` Deliver to: ${a.label} \u2014 ${a.line1 || ""}${a.city ? `, ${a.city}` : ""}${a.region ? `, ${a.region}` : ""} ${a.postalCode || ""}, ${a.country || ""}`);
3492
+ }
3493
+ if (ad.paymentMethods) {
3494
+ if (ad.paymentMethods === "all") {
3495
+ console.log(` Payment: ${chalk13.green("All methods accepted")}`);
3496
+ } else {
3497
+ try {
3498
+ const ids = JSON.parse(ad.paymentMethods);
3499
+ console.log(` Payment: ${ids.length} method${ids.length > 1 ? "s" : ""} configured`);
3500
+ } catch {
3501
+ console.log(` Payment: ${ad.paymentMethods}`);
3502
+ }
3503
+ }
3504
+ }
3505
+ if (ad.expiresAt) console.log(` Expires: ${new Date(ad.expiresAt).toLocaleString()}`);
3506
+ console.log(` Created: ${new Date(ad.createdAt).toLocaleString()}`);
3507
+ const bids = ad.bids || [];
3508
+ if (bids.length === 0) {
3509
+ console.log(chalk13.dim("\n No bids yet. Vendors will be able to see your request and submit bids."));
3510
+ } else {
3511
+ console.log(chalk13.bold(`
3512
+ Bids (${bids.length}):
3513
+ `));
3514
+ for (const bid of bids) {
3515
+ const bidStatusColor = BID_STATUS_COLORS[bid.status] || chalk13.white;
3516
+ const storeBadge = bid.store?.verified ? chalk13.green(" \u2713") : "";
3517
+ const storeRating = bid.store?.rating != null ? chalk13.dim(` (${bid.store.rating.toFixed(1)}\u2605)`) : "";
3518
+ console.log(` ${chalk13.bold(bid.id)} ${bidStatusColor(bid.status.toUpperCase().padEnd(10))} ${chalk13.bold(formatPrice5(bid.priceInCents, bid.currency))}`);
3519
+ console.log(` Store: ${bid.store?.name || bid.storeId}${storeBadge}${storeRating}`);
3520
+ if (bid.shippingDays != null) console.log(` Delivery: ${bid.shippingDays}-day`);
3521
+ const bidReturns = [];
3522
+ if (bid.freeReturns) bidReturns.push(chalk13.green("Free Returns"));
3523
+ if (bid.returnWindowDays) bidReturns.push(`${bid.returnWindowDays}-day return window`);
3524
+ if (bidReturns.length) console.log(` Returns: ${bidReturns.join(" \xB7 ")}`);
3525
+ if (bid.note) console.log(` Note: ${bid.note}`);
3526
+ if (bid.product) console.log(` Product: ${bid.product.name} (${formatPrice5(bid.product.priceInCents, bid.product.currency)})`);
3527
+ console.log(` Date: ${new Date(bid.createdAt).toLocaleString()}`);
3528
+ console.log();
3529
+ }
3530
+ if (ad.status === "open") {
3531
+ const pendingBids = bids.filter((b) => b.status === "pending");
3532
+ if (pendingBids.length > 0) {
3533
+ console.log(chalk13.dim(` Accept a bid: clishop advertise accept ${ad.id} <bidId>`));
3534
+ console.log(chalk13.dim(` Reject a bid: clishop advertise reject ${ad.id} <bidId>
3535
+ `));
3536
+ }
3537
+ }
3538
+ }
3539
+ console.log();
3540
+ } catch (error) {
3541
+ handleApiError(error);
3542
+ }
3543
+ });
3544
+ advertise.command("accept <advertiseId> <bidId>").description("Accept a vendor's bid on your request").action(async (advertiseId, bidId) => {
3545
+ try {
3546
+ const api = getApiClient();
3547
+ const detailSpinner = ora10("Fetching bid details...").start();
3548
+ const detailRes = await api.get(`/advertise/${advertiseId}`);
3549
+ detailSpinner.stop();
3550
+ const ad = detailRes.data.advertise;
3551
+ const bid = ad.bids?.find((b) => b.id === bidId);
3552
+ if (!bid) {
3553
+ console.error(chalk13.red(`
3554
+ \u2717 Bid ${bidId} not found on request ${advertiseId}.`));
3555
+ process.exitCode = 1;
3556
+ return;
3557
+ }
3558
+ console.log(chalk13.bold("\n Accept this bid?\n"));
3559
+ console.log(` Request: ${ad.title}`);
3560
+ console.log(` Store: ${bid.store?.name || bid.storeId}${bid.store?.verified ? chalk13.green(" \u2713") : ""}`);
3561
+ console.log(` Price: ${chalk13.bold(formatPrice5(bid.priceInCents, bid.currency))}`);
3562
+ if (bid.shippingDays != null) console.log(` Delivery: ${bid.shippingDays}-day`);
3563
+ if (bid.note) console.log(` Note: ${bid.note}`);
3564
+ console.log();
3565
+ const { confirm } = await inquirer8.prompt([
3566
+ {
3567
+ type: "confirm",
3568
+ name: "confirm",
3569
+ message: "Accept this bid? (All other bids will be rejected)",
3570
+ default: false
3571
+ }
3572
+ ]);
3573
+ if (!confirm) {
3574
+ console.log(chalk13.yellow("Cancelled."));
3575
+ return;
3576
+ }
3577
+ const spinner = ora10("Accepting bid...").start();
3578
+ await api.post(`/advertise/${advertiseId}/bids/${bidId}/accept`);
3579
+ spinner.succeed(chalk13.green("Bid accepted! The vendor will now fulfill your request."));
3580
+ } catch (error) {
3581
+ handleApiError(error);
3582
+ }
3583
+ });
3584
+ advertise.command("reject <advertiseId> <bidId>").description("Reject a vendor's bid").action(async (advertiseId, bidId) => {
3585
+ try {
3586
+ const { confirm } = await inquirer8.prompt([
3587
+ {
3588
+ type: "confirm",
3589
+ name: "confirm",
3590
+ message: `Reject bid ${bidId}?`,
3591
+ default: false
3592
+ }
3593
+ ]);
3594
+ if (!confirm) return;
3595
+ const spinner = ora10("Rejecting bid...").start();
3596
+ const api = getApiClient();
3597
+ await api.post(`/advertise/${advertiseId}/bids/${bidId}/reject`);
3598
+ spinner.succeed(chalk13.green("Bid rejected."));
3599
+ } catch (error) {
3600
+ handleApiError(error);
3601
+ }
3602
+ });
3603
+ advertise.command("cancel <id>").description("Cancel an advertised request").action(async (id) => {
3604
+ try {
3605
+ const { confirm } = await inquirer8.prompt([
3606
+ {
3607
+ type: "confirm",
3608
+ name: "confirm",
3609
+ message: `Cancel advertised request ${id}?`,
3610
+ default: false
3611
+ }
3612
+ ]);
3613
+ if (!confirm) return;
3614
+ const spinner = ora10("Cancelling request...").start();
3615
+ const api = getApiClient();
3616
+ await api.post(`/advertise/${id}/cancel`);
3617
+ spinner.succeed(chalk13.green("Request cancelled."));
3618
+ } catch (error) {
3619
+ handleApiError(error);
3620
+ }
3621
+ });
3622
+ }
3623
+
3624
+ // src/commands/support.ts
3625
+ import chalk14 from "chalk";
3626
+ import ora11 from "ora";
3627
+ import inquirer9 from "inquirer";
3628
+ var CATEGORY_CHOICES = [
3629
+ { name: "General question", value: "general" },
3630
+ { name: "Damaged item", value: "damaged" },
3631
+ { name: "Missing item", value: "missing" },
3632
+ { name: "Wrong item received", value: "wrong_item" },
3633
+ { name: "Refund request", value: "refund" },
3634
+ { name: "Shipping issue", value: "shipping" },
3635
+ { name: "Other", value: "other" }
3636
+ ];
3637
+ var PRIORITY_CHOICES = [
3638
+ { name: "Low", value: "low" },
3639
+ { name: "Normal", value: "normal" },
3640
+ { name: "High", value: "high" },
3641
+ { name: "Urgent", value: "urgent" }
3642
+ ];
3643
+ var STATUS_COLORS3 = {
3644
+ open: chalk14.green,
3645
+ in_progress: chalk14.cyan,
3646
+ awaiting_customer: chalk14.yellow,
3647
+ awaiting_store: chalk14.blue,
3648
+ resolved: chalk14.gray,
3649
+ closed: chalk14.dim
3650
+ };
3651
+ var PRIORITY_COLORS = {
3652
+ low: chalk14.dim,
3653
+ normal: chalk14.white,
3654
+ high: chalk14.yellow,
3655
+ urgent: chalk14.red
3656
+ };
3657
+ function registerSupportCommands(program2) {
3658
+ const support = program2.command("support").description("Manage support tickets for orders");
3659
+ support.command("create <orderId>").alias("new").description("Create a support ticket for an order").action(async (orderId) => {
3660
+ try {
3661
+ const answers = await inquirer9.prompt([
3662
+ {
3663
+ type: "select",
3664
+ name: "category",
3665
+ message: "What is this about?",
3666
+ choices: CATEGORY_CHOICES
3667
+ },
3668
+ {
3669
+ type: "select",
3670
+ name: "priority",
3671
+ message: "Priority:",
3672
+ choices: PRIORITY_CHOICES,
3673
+ default: "normal"
3674
+ },
3675
+ {
3676
+ type: "input",
3677
+ name: "subject",
3678
+ message: "Subject (short description):",
3679
+ validate: (v) => v.trim().length > 0 || "Subject is required"
3680
+ },
3681
+ {
3682
+ type: "editor",
3683
+ name: "message",
3684
+ message: "Describe the issue in detail (opens editor):"
3685
+ }
3686
+ ]);
3687
+ if (!answers.message || !answers.message.trim()) {
3688
+ console.error(chalk14.red("\n\u2717 Message is required.\n"));
3689
+ return;
3690
+ }
3691
+ const spinner = ora11("Creating support ticket...").start();
3692
+ const api = getApiClient();
3693
+ const res = await api.post("/support", {
3694
+ orderId,
3695
+ subject: answers.subject.trim(),
3696
+ category: answers.category,
3697
+ priority: answers.priority,
3698
+ message: answers.message.trim()
3699
+ });
3700
+ spinner.succeed(chalk14.green("Support ticket created!"));
3701
+ const t = res.data.ticket;
3702
+ console.log();
3703
+ console.log(` ${chalk14.bold("Ticket ID:")} ${chalk14.cyan(t.id)}`);
3704
+ console.log(` ${chalk14.bold("Subject:")} ${t.subject}`);
3705
+ console.log(` ${chalk14.bold("Store:")} ${t.storeName}`);
3706
+ console.log(` ${chalk14.bold("Category:")} ${t.category}`);
3707
+ console.log(` ${chalk14.bold("Status:")} ${(STATUS_COLORS3[t.status] || chalk14.white)(t.status)}`);
3708
+ console.log();
3709
+ console.log(chalk14.dim(" The store will be notified. You can follow up with:"));
3710
+ console.log(chalk14.dim(` clishop support show ${t.id}`));
3711
+ console.log();
3712
+ } catch (error) {
3713
+ handleApiError(error);
3714
+ }
3715
+ });
3716
+ support.command("list").alias("ls").description("List your support tickets").option("--status <status>", "Filter by status").option("--json", "Output raw JSON").action(async (opts) => {
3717
+ try {
3718
+ const spinner = ora11("Fetching tickets...").start();
3719
+ const api = getApiClient();
3720
+ const res = await api.get("/support", {
3721
+ params: { status: opts.status }
3722
+ });
3723
+ spinner.stop();
3724
+ const tickets = res.data.tickets;
3725
+ if (opts.json) {
3726
+ console.log(JSON.stringify(tickets, null, 2));
3727
+ return;
3728
+ }
3729
+ if (tickets.length === 0) {
3730
+ console.log(chalk14.yellow("\nNo support tickets found.\n"));
3731
+ return;
3732
+ }
3733
+ console.log(chalk14.bold("\nYour Support Tickets:\n"));
3734
+ for (const t of tickets) {
3735
+ const statusColor = STATUS_COLORS3[t.status] || chalk14.white;
3736
+ const priorityColor = PRIORITY_COLORS[t.priority] || chalk14.white;
3737
+ const date = new Date(t.createdAt).toLocaleDateString();
3738
+ console.log(
3739
+ ` ${chalk14.bold(t.id)} ${statusColor(t.status.toUpperCase().padEnd(20))} ${priorityColor(t.priority.padEnd(8))} ${chalk14.dim(date)}`
3740
+ );
3741
+ console.log(` ${chalk14.bold(t.subject)}`);
3742
+ console.log(` ${chalk14.dim(`Store: ${t.storeName} \xB7 Order: ${t.orderId} \xB7 ${t.category}`)}`);
3743
+ if (t.lastMessage) {
3744
+ const sender = t.lastMessage.senderType === "store" ? chalk14.blue("Store") : t.lastMessage.senderType === "system" ? chalk14.gray("System") : chalk14.green("You");
3745
+ console.log(` ${chalk14.dim("Last:")} ${sender}: ${chalk14.dim(t.lastMessage.body)}`);
3746
+ }
3747
+ console.log();
3748
+ }
3749
+ } catch (error) {
3750
+ handleApiError(error);
3751
+ }
3752
+ });
3753
+ support.command("show <ticketId>").description("View a support ticket and its messages").option("--json", "Output raw JSON").action(async (ticketId, opts) => {
3754
+ try {
3755
+ const spinner = ora11("Fetching ticket...").start();
3756
+ const api = getApiClient();
3757
+ const res = await api.get(`/support/${ticketId}`);
3758
+ spinner.stop();
3759
+ const t = res.data.ticket;
3760
+ if (opts.json) {
3761
+ console.log(JSON.stringify(t, null, 2));
3762
+ return;
3763
+ }
3764
+ const statusColor = STATUS_COLORS3[t.status] || chalk14.white;
3765
+ console.log();
3766
+ console.log(chalk14.bold(` Ticket ${chalk14.cyan(t.id)}`));
3767
+ console.log(` Subject: ${chalk14.bold(t.subject)}`);
3768
+ console.log(` Status: ${statusColor(t.status.toUpperCase())}`);
3769
+ console.log(` Category: ${t.category}`);
3770
+ console.log(` Priority: ${(PRIORITY_COLORS[t.priority] || chalk14.white)(t.priority)}`);
3771
+ console.log(` Store: ${t.storeName}`);
3772
+ console.log(` Order: ${t.orderId}`);
3773
+ console.log(` Created: ${new Date(t.createdAt).toLocaleString()}`);
3774
+ if (t.resolvedAt) console.log(` Resolved: ${new Date(t.resolvedAt).toLocaleString()}`);
3775
+ console.log(chalk14.bold("\n Messages:\n"));
3776
+ for (const m of t.messages) {
3777
+ const time = new Date(m.createdAt).toLocaleString();
3778
+ let sender;
3779
+ if (m.senderType === "customer") sender = chalk14.green.bold("You");
3780
+ else if (m.senderType === "store") sender = chalk14.blue.bold("Store");
3781
+ else sender = chalk14.gray.bold("System");
3782
+ console.log(` ${sender} ${chalk14.dim(time)}`);
3783
+ console.log(` ${m.body}`);
3784
+ console.log();
3785
+ }
3786
+ } catch (error) {
3787
+ handleApiError(error);
3788
+ }
3789
+ });
3790
+ support.command("reply <ticketId>").description("Reply to a support ticket").action(async (ticketId) => {
3791
+ try {
3792
+ const { message } = await inquirer9.prompt([
3793
+ {
3794
+ type: "editor",
3795
+ name: "message",
3796
+ message: "Your reply (opens editor):"
3797
+ }
3798
+ ]);
3799
+ if (!message || !message.trim()) {
3800
+ console.error(chalk14.red("\n\u2717 Message is required.\n"));
3801
+ return;
3802
+ }
3803
+ const spinner = ora11("Sending reply...").start();
3804
+ const api = getApiClient();
3805
+ const res = await api.post(`/support/${ticketId}/reply`, {
3806
+ message: message.trim()
3807
+ });
3808
+ spinner.succeed(chalk14.green("Reply sent!"));
3809
+ console.log(chalk14.dim(` Ticket status: ${res.data.ticketStatus}
3810
+ `));
3811
+ } catch (error) {
3812
+ handleApiError(error);
3813
+ }
3814
+ });
3815
+ support.command("close <ticketId>").description("Close a resolved ticket").action(async (ticketId) => {
3816
+ try {
3817
+ const { confirm } = await inquirer9.prompt([
3818
+ {
3819
+ type: "confirm",
3820
+ name: "confirm",
3821
+ message: "Close this ticket?",
3822
+ default: false
3823
+ }
3824
+ ]);
3825
+ if (!confirm) return;
3826
+ const spinner = ora11("Closing ticket...").start();
3827
+ const api = getApiClient();
3828
+ await api.patch(`/support/${ticketId}/status`, { status: "closed" });
3829
+ spinner.succeed(chalk14.green("Ticket closed."));
3830
+ } catch (error) {
3831
+ handleApiError(error);
3832
+ }
3833
+ });
3834
+ }
3835
+
3836
+ // src/index.ts
3837
+ var program = new Command();
3838
+ program.name("clishop").version("0.1.0").description(
3839
+ chalk15.bold("CLISHOP") + ' \u2014 Order anything from your terminal.\n\n Use agents to set safety limits, addresses, and payment methods.\n The "default" agent is used when no agent is specified.'
3840
+ ).option("--agent <name>", "Use a specific agent for this command").hook("preAction", (thisCommand) => {
3841
+ const agentOpt = thisCommand.opts().agent;
3842
+ if (agentOpt) {
3843
+ const config2 = getConfig();
3844
+ if (!config2.store.agents[agentOpt]) {
3845
+ console.error(chalk15.red(`\u2717 Agent "${agentOpt}" does not exist.`));
3846
+ process.exit(1);
3847
+ }
3848
+ process.env.__CLISHOP_AGENT_OVERRIDE = agentOpt;
3849
+ }
3850
+ });
3851
+ registerAuthCommands(program);
3852
+ registerAgentCommands(program);
3853
+ registerAddressCommands(program);
3854
+ registerPaymentCommands(program);
3855
+ registerSearchCommands(program);
3856
+ registerOrderCommands(program);
3857
+ registerReviewCommands(program);
3858
+ registerConfigCommands(program);
3859
+ registerStoreCommands(program);
3860
+ registerStatusCommand(program);
3861
+ registerSetupCommand(program);
3862
+ registerAdvertiseCommands(program);
3863
+ registerSupportCommands(program);
3864
+ async function main() {
3865
+ const hasSubcommand = process.argv.length > 2;
3866
+ if (!hasSubcommand) {
3867
+ const config2 = getConfig();
3868
+ if (!config2.get("setupCompleted")) {
3869
+ await runSetupWizard();
3870
+ return;
3871
+ }
3872
+ }
3873
+ await program.parseAsync(process.argv);
3874
+ }
3875
+ main().catch((err) => {
3876
+ console.error(chalk15.red(err.message));
3877
+ process.exit(1);
3878
+ });