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/.cursor/rules/commit-workflow.mdc +42 -0
- package/README.md +333 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3878 -0
- package/package.json +52 -0
- package/src/api.ts +89 -0
- package/src/auth.ts +117 -0
- package/src/commands/address.ts +213 -0
- package/src/commands/advertise.ts +702 -0
- package/src/commands/agent.ts +177 -0
- package/src/commands/auth.ts +122 -0
- package/src/commands/config.ts +56 -0
- package/src/commands/order.ts +334 -0
- package/src/commands/payment.ts +108 -0
- package/src/commands/review.ts +412 -0
- package/src/commands/search.ts +1319 -0
- package/src/commands/setup.ts +644 -0
- package/src/commands/status.ts +131 -0
- package/src/commands/store.ts +302 -0
- package/src/commands/support.ts +264 -0
- package/src/config.ts +127 -0
- package/src/index.ts +80 -0
- package/tsconfig.json +22 -0
|
@@ -0,0 +1,644 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import ora from "ora";
|
|
4
|
+
import inquirer from "inquirer";
|
|
5
|
+
import { login, register, isLoggedIn, getUserInfo } from "../auth.js";
|
|
6
|
+
import {
|
|
7
|
+
getConfig,
|
|
8
|
+
getActiveAgent,
|
|
9
|
+
createAgent,
|
|
10
|
+
updateAgent,
|
|
11
|
+
setActiveAgent,
|
|
12
|
+
} from "../config.js";
|
|
13
|
+
import { getApiClient } from "../api.js";
|
|
14
|
+
|
|
15
|
+
// ── Helpers ────────────────────────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
function divider(color: typeof chalk.cyan = chalk.cyan): void {
|
|
18
|
+
console.log(" " + color("─".repeat(48)));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function stepHeader(step: number, total: number, title: string): void {
|
|
22
|
+
console.log();
|
|
23
|
+
divider();
|
|
24
|
+
console.log();
|
|
25
|
+
console.log(
|
|
26
|
+
chalk.bold.white(` STEP ${step} of ${total}`) +
|
|
27
|
+
chalk.dim(" · ") +
|
|
28
|
+
chalk.bold(title)
|
|
29
|
+
);
|
|
30
|
+
console.log();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function formatPrice(cents: number, currency = "USD"): string {
|
|
34
|
+
return new Intl.NumberFormat("en-US", {
|
|
35
|
+
style: "currency",
|
|
36
|
+
currency,
|
|
37
|
+
}).format(cents / 100);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ── Command Registration ───────────────────────────────────────────────
|
|
41
|
+
|
|
42
|
+
export function registerSetupCommand(program: Command): void {
|
|
43
|
+
program
|
|
44
|
+
.command("setup")
|
|
45
|
+
.description(
|
|
46
|
+
"First-time setup wizard — account, agent, address, payment, first search"
|
|
47
|
+
)
|
|
48
|
+
.action(async () => {
|
|
49
|
+
await runSetupWizard();
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ── The Setup Wizard ───────────────────────────────────────────────────
|
|
54
|
+
|
|
55
|
+
export async function runSetupWizard(): Promise<void> {
|
|
56
|
+
const config = getConfig();
|
|
57
|
+
|
|
58
|
+
// ── Welcome Banner ─────────────────────────────────────────────────
|
|
59
|
+
|
|
60
|
+
console.log();
|
|
61
|
+
divider(chalk.cyan);
|
|
62
|
+
console.log();
|
|
63
|
+
console.log(chalk.bold.cyan(" W E L C O M E T O C L I S H O P"));
|
|
64
|
+
console.log(chalk.dim(" Order anything from your terminal."));
|
|
65
|
+
console.log();
|
|
66
|
+
divider(chalk.cyan);
|
|
67
|
+
console.log();
|
|
68
|
+
console.log(
|
|
69
|
+
chalk.dim(" This wizard will guide you through the initial setup.")
|
|
70
|
+
);
|
|
71
|
+
console.log(
|
|
72
|
+
chalk.dim(" It only takes a minute. You can re-run it anytime with:")
|
|
73
|
+
);
|
|
74
|
+
console.log(chalk.dim(" ") + chalk.white("clishop setup"));
|
|
75
|
+
|
|
76
|
+
// ════════════════════════════════════════════════════════════════════
|
|
77
|
+
// STEP 1 — Account
|
|
78
|
+
// ════════════════════════════════════════════════════════════════════
|
|
79
|
+
|
|
80
|
+
stepHeader(1, 5, "Account");
|
|
81
|
+
|
|
82
|
+
let loggedIn = await isLoggedIn();
|
|
83
|
+
|
|
84
|
+
if (loggedIn) {
|
|
85
|
+
const user = await getUserInfo();
|
|
86
|
+
console.log(
|
|
87
|
+
chalk.green(
|
|
88
|
+
` ✓ Already logged in as ${chalk.bold(user?.name || user?.email || "unknown")}`
|
|
89
|
+
)
|
|
90
|
+
);
|
|
91
|
+
console.log();
|
|
92
|
+
|
|
93
|
+
const { continueAs } = await inquirer.prompt([
|
|
94
|
+
{
|
|
95
|
+
type: "confirm",
|
|
96
|
+
name: "continueAs",
|
|
97
|
+
message: `Continue as ${user?.email}?`,
|
|
98
|
+
default: true,
|
|
99
|
+
},
|
|
100
|
+
]);
|
|
101
|
+
|
|
102
|
+
if (!continueAs) {
|
|
103
|
+
loggedIn = false;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (!loggedIn) {
|
|
108
|
+
const { authChoice } = await inquirer.prompt([
|
|
109
|
+
{
|
|
110
|
+
type: "select",
|
|
111
|
+
name: "authChoice",
|
|
112
|
+
message: "Do you have a CLISHOP account?",
|
|
113
|
+
choices: [
|
|
114
|
+
{ name: "No — create a new account", value: "register" },
|
|
115
|
+
{ name: "Yes — log in to existing account", value: "login" },
|
|
116
|
+
],
|
|
117
|
+
},
|
|
118
|
+
]);
|
|
119
|
+
|
|
120
|
+
if (authChoice === "register") {
|
|
121
|
+
// ── Registration ───────────────────────────────────────────────
|
|
122
|
+
const answers = await inquirer.prompt([
|
|
123
|
+
{ type: "input", name: "name", message: "Your name:" },
|
|
124
|
+
{ type: "input", name: "email", message: "Email:" },
|
|
125
|
+
{
|
|
126
|
+
type: "password",
|
|
127
|
+
name: "password",
|
|
128
|
+
message: "Password:",
|
|
129
|
+
mask: "*",
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
type: "password",
|
|
133
|
+
name: "confirmPassword",
|
|
134
|
+
message: "Confirm password:",
|
|
135
|
+
mask: "*",
|
|
136
|
+
},
|
|
137
|
+
]);
|
|
138
|
+
|
|
139
|
+
if (answers.password !== answers.confirmPassword) {
|
|
140
|
+
console.error(chalk.red("\n ✗ Passwords do not match."));
|
|
141
|
+
console.log(
|
|
142
|
+
chalk.dim(" Run ") +
|
|
143
|
+
chalk.white("clishop setup") +
|
|
144
|
+
chalk.dim(" to try again.\n")
|
|
145
|
+
);
|
|
146
|
+
process.exitCode = 1;
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const spinner = ora("Creating your account...").start();
|
|
151
|
+
try {
|
|
152
|
+
const user = await register(
|
|
153
|
+
answers.email,
|
|
154
|
+
answers.password,
|
|
155
|
+
answers.name
|
|
156
|
+
);
|
|
157
|
+
spinner.succeed(
|
|
158
|
+
chalk.green(
|
|
159
|
+
`Account created! Welcome, ${chalk.bold(user.name)}.`
|
|
160
|
+
)
|
|
161
|
+
);
|
|
162
|
+
} catch (error: any) {
|
|
163
|
+
spinner.fail(
|
|
164
|
+
chalk.red(
|
|
165
|
+
`Registration failed: ${error?.response?.data?.message || error.message}`
|
|
166
|
+
)
|
|
167
|
+
);
|
|
168
|
+
console.log();
|
|
169
|
+
console.log(
|
|
170
|
+
chalk.dim(
|
|
171
|
+
" Make sure the backend is running at: " +
|
|
172
|
+
chalk.white(config.get("apiBaseUrl"))
|
|
173
|
+
)
|
|
174
|
+
);
|
|
175
|
+
console.log(
|
|
176
|
+
chalk.dim(" Then run ") +
|
|
177
|
+
chalk.white("clishop setup") +
|
|
178
|
+
chalk.dim(" again.\n")
|
|
179
|
+
);
|
|
180
|
+
process.exitCode = 1;
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
} else {
|
|
184
|
+
// ── Login ──────────────────────────────────────────────────────
|
|
185
|
+
const answers = await inquirer.prompt([
|
|
186
|
+
{ type: "input", name: "email", message: "Email:" },
|
|
187
|
+
{
|
|
188
|
+
type: "password",
|
|
189
|
+
name: "password",
|
|
190
|
+
message: "Password:",
|
|
191
|
+
mask: "*",
|
|
192
|
+
},
|
|
193
|
+
]);
|
|
194
|
+
|
|
195
|
+
const spinner = ora("Logging in...").start();
|
|
196
|
+
try {
|
|
197
|
+
const user = await login(answers.email, answers.password);
|
|
198
|
+
spinner.succeed(
|
|
199
|
+
chalk.green(`Logged in as ${chalk.bold(user.name)}.`)
|
|
200
|
+
);
|
|
201
|
+
} catch (error: any) {
|
|
202
|
+
spinner.fail(
|
|
203
|
+
chalk.red(
|
|
204
|
+
`Login failed: ${error?.response?.data?.message || error.message}`
|
|
205
|
+
)
|
|
206
|
+
);
|
|
207
|
+
console.log();
|
|
208
|
+
console.log(
|
|
209
|
+
chalk.dim(
|
|
210
|
+
" Make sure the backend is running at: " +
|
|
211
|
+
chalk.white(config.get("apiBaseUrl"))
|
|
212
|
+
)
|
|
213
|
+
);
|
|
214
|
+
console.log(
|
|
215
|
+
chalk.dim(" Then run ") +
|
|
216
|
+
chalk.white("clishop setup") +
|
|
217
|
+
chalk.dim(" again.\n")
|
|
218
|
+
);
|
|
219
|
+
process.exitCode = 1;
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// ════════════════════════════════════════════════════════════════════
|
|
226
|
+
// STEP 2 — Agent
|
|
227
|
+
// ════════════════════════════════════════════════════════════════════
|
|
228
|
+
|
|
229
|
+
stepHeader(2, 5, "Agent (optional)");
|
|
230
|
+
|
|
231
|
+
const activeAgent = getActiveAgent();
|
|
232
|
+
|
|
233
|
+
console.log(
|
|
234
|
+
chalk.dim(
|
|
235
|
+
` A default agent is ready ($${activeAgent.maxOrderAmount} limit, confirmation on).`
|
|
236
|
+
)
|
|
237
|
+
);
|
|
238
|
+
console.log(
|
|
239
|
+
chalk.dim(" Agents control spending limits and category restrictions.")
|
|
240
|
+
);
|
|
241
|
+
console.log();
|
|
242
|
+
|
|
243
|
+
const { agentChoice } = await inquirer.prompt([
|
|
244
|
+
{
|
|
245
|
+
type: "confirm",
|
|
246
|
+
name: "agentChoice",
|
|
247
|
+
message: "Configure a custom agent?",
|
|
248
|
+
default: false,
|
|
249
|
+
},
|
|
250
|
+
]);
|
|
251
|
+
|
|
252
|
+
if (agentChoice) {
|
|
253
|
+
const answers = await inquirer.prompt([
|
|
254
|
+
{
|
|
255
|
+
type: "input",
|
|
256
|
+
name: "name",
|
|
257
|
+
message: "Agent name:",
|
|
258
|
+
validate: (v: string) => (v.trim() ? true : "Name is required"),
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
type: "number",
|
|
262
|
+
name: "maxOrderAmount",
|
|
263
|
+
message: "Max order amount ($):",
|
|
264
|
+
default: 500,
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
type: "confirm",
|
|
268
|
+
name: "requireConfirmation",
|
|
269
|
+
message: "Require confirmation before ordering?",
|
|
270
|
+
default: true,
|
|
271
|
+
},
|
|
272
|
+
]);
|
|
273
|
+
|
|
274
|
+
try {
|
|
275
|
+
const agent = createAgent(answers.name.trim(), {
|
|
276
|
+
maxOrderAmount: answers.maxOrderAmount,
|
|
277
|
+
requireConfirmation: answers.requireConfirmation,
|
|
278
|
+
});
|
|
279
|
+
setActiveAgent(agent.name);
|
|
280
|
+
console.log(
|
|
281
|
+
chalk.green(
|
|
282
|
+
`\n ✓ Agent "${chalk.bold(agent.name)}" created and set as active.`
|
|
283
|
+
)
|
|
284
|
+
);
|
|
285
|
+
} catch (error: any) {
|
|
286
|
+
console.error(chalk.red(`\n ✗ ${error.message}`));
|
|
287
|
+
console.log(chalk.dim(" Continuing with the default agent."));
|
|
288
|
+
}
|
|
289
|
+
} else {
|
|
290
|
+
console.log(chalk.green(" ✓ Using default agent."));
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// ════════════════════════════════════════════════════════════════════
|
|
294
|
+
// STEP 3 — Shipping Address
|
|
295
|
+
// ════════════════════════════════════════════════════════════════════
|
|
296
|
+
|
|
297
|
+
stepHeader(3, 5, "Shipping Address");
|
|
298
|
+
|
|
299
|
+
console.log(
|
|
300
|
+
chalk.dim(
|
|
301
|
+
" Add an address so products can be delivered to you."
|
|
302
|
+
)
|
|
303
|
+
);
|
|
304
|
+
console.log();
|
|
305
|
+
|
|
306
|
+
const { addAddress } = await inquirer.prompt([
|
|
307
|
+
{
|
|
308
|
+
type: "confirm",
|
|
309
|
+
name: "addAddress",
|
|
310
|
+
message: "Add a shipping address now?",
|
|
311
|
+
default: true,
|
|
312
|
+
},
|
|
313
|
+
]);
|
|
314
|
+
|
|
315
|
+
let addressCity = "";
|
|
316
|
+
|
|
317
|
+
if (addAddress) {
|
|
318
|
+
const addr = await inquirer.prompt([
|
|
319
|
+
{
|
|
320
|
+
type: "input",
|
|
321
|
+
name: "label",
|
|
322
|
+
message: "Label (e.g. Home, Office):",
|
|
323
|
+
default: "Home",
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
type: "input",
|
|
327
|
+
name: "recipientName",
|
|
328
|
+
message: "Recipient name (optional):",
|
|
329
|
+
},
|
|
330
|
+
{
|
|
331
|
+
type: "input",
|
|
332
|
+
name: "recipientPhone",
|
|
333
|
+
message: "Recipient phone (optional):",
|
|
334
|
+
},
|
|
335
|
+
{
|
|
336
|
+
type: "input",
|
|
337
|
+
name: "line1",
|
|
338
|
+
message: "Street name and number:",
|
|
339
|
+
validate: (v: string) => (v.trim() ? true : "Required"),
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
type: "input",
|
|
343
|
+
name: "line2",
|
|
344
|
+
message: "Apartment, suite, floor, etc. (optional):",
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
type: "input",
|
|
348
|
+
name: "postalCode",
|
|
349
|
+
message: "Postal / ZIP code:",
|
|
350
|
+
validate: (v: string) => (v.trim() ? true : "Required"),
|
|
351
|
+
},
|
|
352
|
+
{
|
|
353
|
+
type: "input",
|
|
354
|
+
name: "city",
|
|
355
|
+
message: "City:",
|
|
356
|
+
validate: (v: string) => (v.trim() ? true : "Required"),
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
type: "input",
|
|
360
|
+
name: "region",
|
|
361
|
+
message: "State / Province / Region (optional):",
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
type: "input",
|
|
365
|
+
name: "country",
|
|
366
|
+
message: "Country:",
|
|
367
|
+
validate: (v: string) => (v.trim() ? true : "Required"),
|
|
368
|
+
},
|
|
369
|
+
{
|
|
370
|
+
type: "input",
|
|
371
|
+
name: "instructions",
|
|
372
|
+
message: "Delivery instructions (optional):",
|
|
373
|
+
},
|
|
374
|
+
{
|
|
375
|
+
type: "confirm",
|
|
376
|
+
name: "isCompany",
|
|
377
|
+
message: "Is this a company/business address?",
|
|
378
|
+
default: false,
|
|
379
|
+
},
|
|
380
|
+
]);
|
|
381
|
+
|
|
382
|
+
let companyInfo = { companyName: "", vatNumber: "", taxId: "" };
|
|
383
|
+
if (addr.isCompany) {
|
|
384
|
+
companyInfo = await inquirer.prompt([
|
|
385
|
+
{
|
|
386
|
+
type: "input",
|
|
387
|
+
name: "companyName",
|
|
388
|
+
message: "Company name:",
|
|
389
|
+
validate: (v: string) => (v.trim() ? true : "Required for company addresses"),
|
|
390
|
+
},
|
|
391
|
+
{
|
|
392
|
+
type: "input",
|
|
393
|
+
name: "vatNumber",
|
|
394
|
+
message: "VAT number (optional):",
|
|
395
|
+
},
|
|
396
|
+
{
|
|
397
|
+
type: "input",
|
|
398
|
+
name: "taxId",
|
|
399
|
+
message: "Tax ID / EIN (optional):",
|
|
400
|
+
},
|
|
401
|
+
]);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
addressCity = addr.city;
|
|
405
|
+
|
|
406
|
+
const spinner = ora("Saving address...").start();
|
|
407
|
+
try {
|
|
408
|
+
const api = getApiClient();
|
|
409
|
+
const agent = getActiveAgent();
|
|
410
|
+
const res = await api.post("/addresses", {
|
|
411
|
+
agent: agent.name,
|
|
412
|
+
label: addr.label,
|
|
413
|
+
recipientName: addr.recipientName || undefined,
|
|
414
|
+
recipientPhone: addr.recipientPhone || undefined,
|
|
415
|
+
companyName: companyInfo.companyName || undefined,
|
|
416
|
+
vatNumber: companyInfo.vatNumber || undefined,
|
|
417
|
+
taxId: companyInfo.taxId || undefined,
|
|
418
|
+
line1: addr.line1,
|
|
419
|
+
line2: addr.line2 || undefined,
|
|
420
|
+
city: addr.city,
|
|
421
|
+
region: addr.region || undefined,
|
|
422
|
+
postalCode: addr.postalCode,
|
|
423
|
+
country: addr.country,
|
|
424
|
+
instructions: addr.instructions || undefined,
|
|
425
|
+
});
|
|
426
|
+
// Set as default address for this agent
|
|
427
|
+
updateAgent(agent.name, { defaultAddressId: res.data.address.id });
|
|
428
|
+
spinner.succeed(
|
|
429
|
+
chalk.green(
|
|
430
|
+
`Address "${addr.label}" saved and set as default.`
|
|
431
|
+
)
|
|
432
|
+
);
|
|
433
|
+
} catch (error: any) {
|
|
434
|
+
spinner.fail(
|
|
435
|
+
chalk.red(
|
|
436
|
+
`Failed to save address: ${error?.response?.data?.message || error.message}`
|
|
437
|
+
)
|
|
438
|
+
);
|
|
439
|
+
console.log(
|
|
440
|
+
chalk.dim(
|
|
441
|
+
" You can add an address later with: " +
|
|
442
|
+
chalk.white("clishop address add")
|
|
443
|
+
)
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
} else {
|
|
447
|
+
console.log(
|
|
448
|
+
chalk.dim(
|
|
449
|
+
"\n You can add one later with: " + chalk.white("clishop address add")
|
|
450
|
+
)
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// ════════════════════════════════════════════════════════════════════
|
|
455
|
+
// STEP 4 — Payment Method
|
|
456
|
+
// ════════════════════════════════════════════════════════════════════
|
|
457
|
+
|
|
458
|
+
stepHeader(4, 5, "Payment Method");
|
|
459
|
+
|
|
460
|
+
console.log(
|
|
461
|
+
chalk.dim(
|
|
462
|
+
" For security, payment details are entered through a secure web"
|
|
463
|
+
)
|
|
464
|
+
);
|
|
465
|
+
console.log(
|
|
466
|
+
chalk.dim(" page. The CLI never sees your card number.")
|
|
467
|
+
);
|
|
468
|
+
console.log();
|
|
469
|
+
|
|
470
|
+
const { addPayment } = await inquirer.prompt([
|
|
471
|
+
{
|
|
472
|
+
type: "confirm",
|
|
473
|
+
name: "addPayment",
|
|
474
|
+
message: "Set up a payment method now?",
|
|
475
|
+
default: true,
|
|
476
|
+
},
|
|
477
|
+
]);
|
|
478
|
+
|
|
479
|
+
if (addPayment) {
|
|
480
|
+
const spinner = ora("Requesting secure payment setup link...").start();
|
|
481
|
+
try {
|
|
482
|
+
const api = getApiClient();
|
|
483
|
+
const agent = getActiveAgent();
|
|
484
|
+
const res = await api.post("/payment-methods/setup", {
|
|
485
|
+
agent: agent.name,
|
|
486
|
+
});
|
|
487
|
+
spinner.stop();
|
|
488
|
+
const { setupUrl } = res.data;
|
|
489
|
+
|
|
490
|
+
console.log();
|
|
491
|
+
console.log(
|
|
492
|
+
chalk.bold(
|
|
493
|
+
" Open this link in your browser to add a payment method:"
|
|
494
|
+
)
|
|
495
|
+
);
|
|
496
|
+
console.log();
|
|
497
|
+
console.log(" " + chalk.cyan.underline(setupUrl));
|
|
498
|
+
console.log();
|
|
499
|
+
console.log(
|
|
500
|
+
chalk.dim(
|
|
501
|
+
' Once done, verify with: ' + chalk.white("clishop payment list")
|
|
502
|
+
)
|
|
503
|
+
);
|
|
504
|
+
} catch (error: any) {
|
|
505
|
+
spinner.fail(
|
|
506
|
+
chalk.red(
|
|
507
|
+
`Could not get setup link: ${error?.response?.data?.message || error.message}`
|
|
508
|
+
)
|
|
509
|
+
);
|
|
510
|
+
console.log(
|
|
511
|
+
chalk.dim(
|
|
512
|
+
" You can set up payment later with: " +
|
|
513
|
+
chalk.white("clishop payment add")
|
|
514
|
+
)
|
|
515
|
+
);
|
|
516
|
+
}
|
|
517
|
+
} else {
|
|
518
|
+
console.log(
|
|
519
|
+
chalk.dim(
|
|
520
|
+
"\n You can set one up later with: " +
|
|
521
|
+
chalk.white("clishop payment add")
|
|
522
|
+
)
|
|
523
|
+
);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// ════════════════════════════════════════════════════════════════════
|
|
527
|
+
// STEP 5 — First Search
|
|
528
|
+
// ════════════════════════════════════════════════════════════════════
|
|
529
|
+
|
|
530
|
+
stepHeader(5, 5, "Your First Search");
|
|
531
|
+
|
|
532
|
+
if (addressCity) {
|
|
533
|
+
console.log(
|
|
534
|
+
chalk.dim(
|
|
535
|
+
` Let's find something! Products can be shipped to ${chalk.white(addressCity)}.`
|
|
536
|
+
)
|
|
537
|
+
);
|
|
538
|
+
} else {
|
|
539
|
+
console.log(chalk.dim(" Let's find something to order!"));
|
|
540
|
+
}
|
|
541
|
+
console.log();
|
|
542
|
+
|
|
543
|
+
const { searchQuery } = await inquirer.prompt([
|
|
544
|
+
{
|
|
545
|
+
type: "input",
|
|
546
|
+
name: "searchQuery",
|
|
547
|
+
message: "Search for a product (or press Enter to skip):",
|
|
548
|
+
default: "headphones",
|
|
549
|
+
},
|
|
550
|
+
]);
|
|
551
|
+
|
|
552
|
+
if (searchQuery.trim()) {
|
|
553
|
+
const spinner = ora(`Searching for "${searchQuery}"...`).start();
|
|
554
|
+
try {
|
|
555
|
+
const api = getApiClient();
|
|
556
|
+
const res = await api.get("/products/search", {
|
|
557
|
+
params: { q: searchQuery, page: 1, pageSize: 5 },
|
|
558
|
+
});
|
|
559
|
+
spinner.stop();
|
|
560
|
+
|
|
561
|
+
const result = res.data;
|
|
562
|
+
|
|
563
|
+
if (result.products.length === 0) {
|
|
564
|
+
console.log(
|
|
565
|
+
chalk.yellow(
|
|
566
|
+
`\n No results for "${searchQuery}". Try other terms later!`
|
|
567
|
+
)
|
|
568
|
+
);
|
|
569
|
+
} else {
|
|
570
|
+
console.log(
|
|
571
|
+
chalk.bold(
|
|
572
|
+
`\n Found ${result.total} result${result.total !== 1 ? "s" : ""} for "${searchQuery}":\n`
|
|
573
|
+
)
|
|
574
|
+
);
|
|
575
|
+
|
|
576
|
+
for (const p of result.products) {
|
|
577
|
+
const price = formatPrice(p.priceInCents, p.currency || "USD");
|
|
578
|
+
const stock = p.inStock
|
|
579
|
+
? chalk.green("In Stock")
|
|
580
|
+
: chalk.red("Out of Stock");
|
|
581
|
+
|
|
582
|
+
console.log(
|
|
583
|
+
` ${chalk.bold.cyan(p.name)} ${chalk.bold.white(price)} ${stock}`
|
|
584
|
+
);
|
|
585
|
+
console.log(chalk.dim(` ID: ${p.id}`));
|
|
586
|
+
console.log(
|
|
587
|
+
chalk.dim(
|
|
588
|
+
` ${p.description.length > 100 ? p.description.slice(0, 100) + "..." : p.description}`
|
|
589
|
+
)
|
|
590
|
+
);
|
|
591
|
+
console.log();
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
console.log(
|
|
595
|
+
chalk.dim(" Buy a product with: ") +
|
|
596
|
+
chalk.white("clishop buy <product-id>")
|
|
597
|
+
);
|
|
598
|
+
}
|
|
599
|
+
} catch (error: any) {
|
|
600
|
+
spinner.fail(
|
|
601
|
+
chalk.red(
|
|
602
|
+
`Search failed: ${error?.response?.data?.message || error.message}`
|
|
603
|
+
)
|
|
604
|
+
);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// ════════════════════════════════════════════════════════════════════
|
|
609
|
+
// DONE
|
|
610
|
+
// ════════════════════════════════════════════════════════════════════
|
|
611
|
+
|
|
612
|
+
config.set("setupCompleted", true);
|
|
613
|
+
|
|
614
|
+
console.log();
|
|
615
|
+
divider(chalk.green);
|
|
616
|
+
console.log();
|
|
617
|
+
console.log(chalk.bold.green(" ✓ You're all set!"));
|
|
618
|
+
console.log();
|
|
619
|
+
console.log(chalk.dim(" Here are some commands to get you started:"));
|
|
620
|
+
console.log();
|
|
621
|
+
console.log(
|
|
622
|
+
chalk.white(" clishop search <query> ") +
|
|
623
|
+
chalk.dim("Search for products")
|
|
624
|
+
);
|
|
625
|
+
console.log(
|
|
626
|
+
chalk.white(" clishop buy <id> ") +
|
|
627
|
+
chalk.dim("Quick-buy a product")
|
|
628
|
+
);
|
|
629
|
+
console.log(
|
|
630
|
+
chalk.white(" clishop order list ") +
|
|
631
|
+
chalk.dim("View your orders")
|
|
632
|
+
);
|
|
633
|
+
console.log(
|
|
634
|
+
chalk.white(" clishop agent list ") +
|
|
635
|
+
chalk.dim("Manage your agents")
|
|
636
|
+
);
|
|
637
|
+
console.log(
|
|
638
|
+
chalk.white(" clishop --help ") +
|
|
639
|
+
chalk.dim("See all commands")
|
|
640
|
+
);
|
|
641
|
+
console.log();
|
|
642
|
+
divider(chalk.green);
|
|
643
|
+
console.log();
|
|
644
|
+
}
|