agentmall 0.1.11 → 0.1.13
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/cli.js +367 -159
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -264,6 +264,125 @@ var AgentMallRefunds = class {
|
|
|
264
264
|
}
|
|
265
265
|
};
|
|
266
266
|
|
|
267
|
+
// src/cli/ui.ts
|
|
268
|
+
var c = {
|
|
269
|
+
reset: "\x1B[0m",
|
|
270
|
+
bold: "\x1B[1m",
|
|
271
|
+
dim: "\x1B[2m",
|
|
272
|
+
italic: "\x1B[3m",
|
|
273
|
+
strike: "\x1B[9m",
|
|
274
|
+
red: "\x1B[31m",
|
|
275
|
+
green: "\x1B[32m",
|
|
276
|
+
yellow: "\x1B[33m",
|
|
277
|
+
blue: "\x1B[34m",
|
|
278
|
+
magenta: "\x1B[35m",
|
|
279
|
+
cyan: "\x1B[36m",
|
|
280
|
+
gray: "\x1B[90m"
|
|
281
|
+
};
|
|
282
|
+
var RULE_WIDTH = 48;
|
|
283
|
+
function banner() {
|
|
284
|
+
console.log();
|
|
285
|
+
console.log(
|
|
286
|
+
` ${c.bold}\u25CE agentmall${c.reset} ${c.dim}\u2014 buy anything with a URL${c.reset}`
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
function section(title) {
|
|
290
|
+
const pad = Math.max(2, RULE_WIDTH - title.length - 2);
|
|
291
|
+
console.log();
|
|
292
|
+
console.log(
|
|
293
|
+
` ${c.dim}\u2500\u2500\u2500${c.reset} ${c.bold}${title}${c.reset} ${c.dim}${"\u2500".repeat(pad)}${c.reset}`
|
|
294
|
+
);
|
|
295
|
+
}
|
|
296
|
+
function kv(key, value, opts) {
|
|
297
|
+
const k = `${c.gray}${key.padEnd(16)}${c.reset}`;
|
|
298
|
+
const color = opts?.color ?? "";
|
|
299
|
+
const reset = color ? c.reset : "";
|
|
300
|
+
const v = opts?.dim ? `${c.dim}${value}${c.reset}` : `${color}${value}${reset}`;
|
|
301
|
+
console.log(` ${k} ${v}`);
|
|
302
|
+
}
|
|
303
|
+
function line(text) {
|
|
304
|
+
console.log(` ${text}`);
|
|
305
|
+
}
|
|
306
|
+
function gap() {
|
|
307
|
+
console.log();
|
|
308
|
+
}
|
|
309
|
+
function muted(text) {
|
|
310
|
+
console.log(` ${c.dim}${text}${c.reset}`);
|
|
311
|
+
}
|
|
312
|
+
function hint(text) {
|
|
313
|
+
console.log(` ${c.dim}${text}${c.reset}`);
|
|
314
|
+
}
|
|
315
|
+
var STATUS_COLORS = {
|
|
316
|
+
payment_received: c.yellow,
|
|
317
|
+
submitted: c.blue,
|
|
318
|
+
pending: c.blue,
|
|
319
|
+
confirmed: c.green,
|
|
320
|
+
shipped: c.cyan,
|
|
321
|
+
delivered: c.green,
|
|
322
|
+
completed: c.green,
|
|
323
|
+
failed: c.red,
|
|
324
|
+
cancelled: c.red,
|
|
325
|
+
approved: c.yellow,
|
|
326
|
+
processing: c.blue,
|
|
327
|
+
processed: c.green,
|
|
328
|
+
in_stock: c.green,
|
|
329
|
+
out_of_stock: c.red,
|
|
330
|
+
low_stock: c.yellow
|
|
331
|
+
};
|
|
332
|
+
function statusBadge(status2) {
|
|
333
|
+
const color = STATUS_COLORS[status2] ?? c.gray;
|
|
334
|
+
return `${color}\u25CF${c.reset} ${status2}`;
|
|
335
|
+
}
|
|
336
|
+
var FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
337
|
+
async function spin(label, task) {
|
|
338
|
+
let frame = 0;
|
|
339
|
+
const render = (char, color, text) => {
|
|
340
|
+
process.stdout.write(`\r ${color}${char}${c.reset} ${text}\x1B[K`);
|
|
341
|
+
};
|
|
342
|
+
render(FRAMES[0], c.dim, `${c.dim}${label}${c.reset}`);
|
|
343
|
+
const interval = setInterval(() => {
|
|
344
|
+
frame = (frame + 1) % FRAMES.length;
|
|
345
|
+
render(FRAMES[frame], c.dim, `${c.dim}${label}${c.reset}`);
|
|
346
|
+
}, 80);
|
|
347
|
+
try {
|
|
348
|
+
const result = await task();
|
|
349
|
+
clearInterval(interval);
|
|
350
|
+
render("\u2714", c.green, label);
|
|
351
|
+
process.stdout.write("\n");
|
|
352
|
+
return result;
|
|
353
|
+
} catch (error) {
|
|
354
|
+
clearInterval(interval);
|
|
355
|
+
render("\u2716", c.red, label);
|
|
356
|
+
process.stdout.write("\n");
|
|
357
|
+
throw error;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
function stripAnsi(s) {
|
|
361
|
+
return s.replace(/\x1b\[[0-9;]*m/g, "");
|
|
362
|
+
}
|
|
363
|
+
function box(lines) {
|
|
364
|
+
const maxLen = Math.max(
|
|
365
|
+
...lines.map((l) => stripAnsi(l).length),
|
|
366
|
+
38
|
|
367
|
+
);
|
|
368
|
+
const w = maxLen + 2;
|
|
369
|
+
console.log(` ${c.dim}\u256D${"\u2500".repeat(w)}\u256E${c.reset}`);
|
|
370
|
+
for (const l of lines) {
|
|
371
|
+
const visible = stripAnsi(l).length;
|
|
372
|
+
const pad = maxLen - visible;
|
|
373
|
+
console.log(
|
|
374
|
+
` ${c.dim}\u2502${c.reset} ${l}${" ".repeat(Math.max(0, pad))} ${c.dim}\u2502${c.reset}`
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
console.log(` ${c.dim}\u2570${"\u2500".repeat(w)}\u256F${c.reset}`);
|
|
378
|
+
}
|
|
379
|
+
function errorMsg(label, message) {
|
|
380
|
+
console.log();
|
|
381
|
+
console.log(` ${c.red}\u2716 ${label}${c.reset}`);
|
|
382
|
+
console.log(` ${message}`);
|
|
383
|
+
console.log();
|
|
384
|
+
}
|
|
385
|
+
|
|
267
386
|
// src/cli/display.ts
|
|
268
387
|
var FAILURE_MESSAGES = {
|
|
269
388
|
budget_exceeded: "Actual total exceeded your maximum budget.",
|
|
@@ -303,26 +422,27 @@ function calculateMinimumBudget(product, quantity = 1, variantUnitPriceCents) {
|
|
|
303
422
|
return unitPriceCents * Math.max(1, quantity);
|
|
304
423
|
}
|
|
305
424
|
function displayProduct(product) {
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
console.log();
|
|
425
|
+
section("Product");
|
|
426
|
+
line(`${c.bold}${product.title}${c.reset}`);
|
|
427
|
+
line(`${c.dim}${product.retailer} \xB7 ${statusBadge(product.availability)}${c.reset}`);
|
|
428
|
+
gap();
|
|
311
429
|
if (product.listPrice && product.discountPercent) {
|
|
312
|
-
|
|
313
|
-
|
|
430
|
+
kv(
|
|
431
|
+
"Price",
|
|
432
|
+
`${c.green}${formatCents(product.price)}${c.reset} ${c.dim}${c.strike}${formatCents(product.listPrice)}${c.reset} ${c.yellow}-${product.discountPercent}%${c.reset}`
|
|
314
433
|
);
|
|
315
434
|
} else {
|
|
316
|
-
|
|
435
|
+
kv("Price", formatCents(product.price), { color: c.green });
|
|
317
436
|
}
|
|
318
437
|
if (product.variants?.length) {
|
|
319
|
-
|
|
438
|
+
gap();
|
|
320
439
|
for (const v of product.variants) {
|
|
321
|
-
const label = v.label ? `${v.label}
|
|
322
|
-
|
|
440
|
+
const label = v.label ? `${c.dim}${v.label}:${c.reset} ` : "";
|
|
441
|
+
line(
|
|
442
|
+
`${label}${c.bold}${v.value}${c.reset} ${c.dim}\u2014 $${v.price.toFixed(2)}${c.reset}`
|
|
443
|
+
);
|
|
323
444
|
}
|
|
324
445
|
}
|
|
325
|
-
console.log();
|
|
326
446
|
}
|
|
327
447
|
function displayCheckoutEstimate(product, quantity = 1, variantSelections, variantUnitPriceCents) {
|
|
328
448
|
const quantityClamped = Math.max(1, quantity);
|
|
@@ -335,126 +455,131 @@ function displayCheckoutEstimate(product, quantity = 1, variantSelections, varia
|
|
|
335
455
|
const linePrice = unitPriceCents * quantityClamped;
|
|
336
456
|
const listBaseUnitPrice = typeof product.listPrice === "number" ? Math.max(product.listPrice, variantUnitPriceCents ?? 0) : void 0;
|
|
337
457
|
const lineListPrice = typeof listBaseUnitPrice === "number" ? listBaseUnitPrice * quantityClamped : void 0;
|
|
338
|
-
|
|
339
|
-
console.log(" \x1B[1m\u{1F4B8} Checkout Estimate\x1B[0m");
|
|
458
|
+
section("Checkout Estimate");
|
|
340
459
|
if (lineListPrice && product.discountPercent) {
|
|
341
|
-
|
|
342
|
-
|
|
460
|
+
kv(
|
|
461
|
+
"Price",
|
|
462
|
+
`${c.green}${formatCents(linePrice)}${c.reset} ${c.dim}${c.strike}${formatCents(lineListPrice)}${c.reset} ${c.yellow}-${product.discountPercent}%${c.reset}`
|
|
343
463
|
);
|
|
344
464
|
} else {
|
|
345
|
-
|
|
465
|
+
kv("Price", formatCents(linePrice), { color: c.green });
|
|
346
466
|
}
|
|
347
|
-
|
|
467
|
+
kv("Quantity", `${quantityClamped}`);
|
|
348
468
|
if (quantityClamped > 1) {
|
|
349
|
-
|
|
469
|
+
kv("Unit price", formatCents(unitPriceCents), { dim: true });
|
|
350
470
|
}
|
|
351
471
|
if (variantSelections?.length) {
|
|
352
472
|
for (const variant of variantSelections) {
|
|
353
|
-
|
|
473
|
+
kv(variant.label, variant.value);
|
|
354
474
|
}
|
|
355
475
|
}
|
|
356
|
-
|
|
357
|
-
|
|
476
|
+
kv("Budget", `${c.bold}${formatCents(suggestedBudget)}${c.reset} ${c.dim}(suggested)${c.reset}`);
|
|
477
|
+
kv("Fee", formatCents(SERVICE_FEE_CENTS), { dim: true });
|
|
478
|
+
kv(
|
|
479
|
+
"Total",
|
|
480
|
+
`${c.bold}${formatCents(suggestedBudget + SERVICE_FEE_CENTS)}${c.reset} USDC`
|
|
358
481
|
);
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
console.log(
|
|
363
|
-
` Total charge: \x1B[1m${formatCents(suggestedBudget + SERVICE_FEE_CENTS)}\x1B[0m USDC`
|
|
364
|
-
);
|
|
365
|
-
console.log(
|
|
366
|
-
` Any unused amount from your max budget is refunded automatically after checkout.`
|
|
367
|
-
);
|
|
368
|
-
console.log(
|
|
369
|
-
` The buffer covers unforeseen tax, shipping, and retailer price changes at final checkout.`
|
|
482
|
+
gap();
|
|
483
|
+
hint(
|
|
484
|
+
"Budget includes a 15% tax/price buffer plus an $8 shipping buffer."
|
|
370
485
|
);
|
|
486
|
+
hint("Unused amount is refunded automatically after checkout.");
|
|
371
487
|
if (product.cached) {
|
|
372
|
-
|
|
488
|
+
gap();
|
|
489
|
+
line(
|
|
490
|
+
`${c.yellow}\u26A0${c.reset} ${c.yellow}Price from cache \u2014 may not reflect current listing${c.reset}`
|
|
491
|
+
);
|
|
373
492
|
}
|
|
374
|
-
console.log();
|
|
375
493
|
}
|
|
376
494
|
function displayOrderSummary(body) {
|
|
377
|
-
|
|
378
|
-
console.log(" \x1B[1m\u{1F9FE} Order Summary\x1B[0m");
|
|
495
|
+
section("Order Summary");
|
|
379
496
|
for (const item of body.items) {
|
|
380
|
-
const url = item.product_url;
|
|
381
497
|
const qty = item.quantity ?? 1;
|
|
382
|
-
|
|
498
|
+
line(`${c.dim}${qty}x${c.reset} ${item.product_url}`);
|
|
383
499
|
if (item.variant?.length) {
|
|
384
500
|
for (const v of item.variant) {
|
|
385
|
-
|
|
501
|
+
line(` ${c.dim}${v.label}:${c.reset} ${v.value}`);
|
|
386
502
|
}
|
|
387
503
|
}
|
|
388
504
|
}
|
|
505
|
+
section("Shipping");
|
|
389
506
|
const addr = body.delivery_address;
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
console.log();
|
|
507
|
+
line(`${addr.first_name} ${addr.last_name}`);
|
|
508
|
+
line(
|
|
509
|
+
`${addr.address_line1}${addr.address_line2 ? `, ${addr.address_line2}` : ""}`
|
|
510
|
+
);
|
|
511
|
+
line(
|
|
512
|
+
`${addr.city}, ${addr.state ?? ""} ${addr.postal_code} ${addr.country}`
|
|
513
|
+
);
|
|
398
514
|
}
|
|
399
515
|
function displayOrderResult(order) {
|
|
400
|
-
|
|
516
|
+
gap();
|
|
517
|
+
const lines = [];
|
|
401
518
|
if (order.status === "failed") {
|
|
402
|
-
|
|
519
|
+
lines.push(`${c.red}\u2716 Order failed during submission${c.reset}`);
|
|
403
520
|
} else if (order.deduplicated) {
|
|
404
|
-
|
|
521
|
+
lines.push(`${c.yellow}\u267B Reused existing order${c.reset}`);
|
|
405
522
|
} else {
|
|
406
|
-
|
|
523
|
+
lines.push(`${c.green}\u2714 Order placed${c.reset}`);
|
|
407
524
|
}
|
|
408
|
-
|
|
409
|
-
|
|
525
|
+
lines.push("");
|
|
526
|
+
lines.push(`${c.gray}ID${c.reset} ${c.bold}${order.id}${c.reset}`);
|
|
527
|
+
lines.push(`${c.gray}Status${c.reset} ${statusBadge(order.status)}`);
|
|
410
528
|
if (order.buyerToken) {
|
|
411
|
-
|
|
529
|
+
lines.push("");
|
|
530
|
+
lines.push(`${c.dim}\u{1F510} Saved buyer token for status checks${c.reset}`);
|
|
412
531
|
}
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
order.status === "failed" ?
|
|
532
|
+
lines.push("");
|
|
533
|
+
lines.push(
|
|
534
|
+
order.status === "failed" ? `${c.dim}Check failure details and refund progress:${c.reset}` : `${c.dim}Check status in 5-10 minutes:${c.reset}`
|
|
416
535
|
);
|
|
417
|
-
|
|
418
|
-
|
|
536
|
+
lines.push(`${c.cyan}npx agentmall status ${order.id}${c.reset}`);
|
|
537
|
+
box(lines);
|
|
538
|
+
gap();
|
|
419
539
|
}
|
|
420
540
|
function displayStatus(purchase) {
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
console.log(` Status: ${purchase.status}`);
|
|
541
|
+
section(`Order ${purchase.id}`);
|
|
542
|
+
kv("Status", statusBadge(purchase.status));
|
|
424
543
|
if (purchase.items?.length) {
|
|
544
|
+
gap();
|
|
425
545
|
for (const item of purchase.items) {
|
|
426
|
-
const price = item.price ? ` \u2014 ${formatCents(item.price)}` : "";
|
|
427
|
-
|
|
546
|
+
const price = item.price ? ` ${c.dim}\u2014 ${formatCents(item.price)}${c.reset}` : "";
|
|
547
|
+
line(
|
|
548
|
+
`${c.dim}${item.quantity}x${c.reset} ${item.title ?? item.productRef}${price}`
|
|
549
|
+
);
|
|
428
550
|
}
|
|
429
551
|
}
|
|
552
|
+
gap();
|
|
430
553
|
if (purchase.finalTotal) {
|
|
431
|
-
|
|
554
|
+
kv("Final total", formatCents(purchase.finalTotal));
|
|
432
555
|
}
|
|
433
556
|
if (purchase.failureReason) {
|
|
434
|
-
|
|
435
|
-
|
|
557
|
+
kv(
|
|
558
|
+
"Failure",
|
|
559
|
+
`${c.red}${FAILURE_MESSAGES[purchase.failureReason] ?? purchase.failureReason}${c.reset}`
|
|
436
560
|
);
|
|
437
561
|
}
|
|
438
562
|
if (purchase.deliveryMethod) {
|
|
439
|
-
|
|
563
|
+
kv("Shipping", purchase.deliveryMethod);
|
|
440
564
|
}
|
|
441
|
-
|
|
442
|
-
|
|
565
|
+
kv("Created", new Date(purchase.createdAt).toLocaleString(), { dim: true });
|
|
566
|
+
gap();
|
|
443
567
|
}
|
|
444
568
|
function displayRefund(refund2) {
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
console.log(` Reason: ${refund2.reason}`);
|
|
569
|
+
section(`Refund ${refund2.id}`);
|
|
570
|
+
kv("Status", statusBadge(refund2.status));
|
|
571
|
+
kv("Amount", formatCents(refund2.amountCents), { color: c.green });
|
|
572
|
+
kv("Reason", refund2.reason);
|
|
450
573
|
if (refund2.txHash) {
|
|
451
|
-
|
|
574
|
+
kv("TX", refund2.txHash, { dim: true });
|
|
452
575
|
}
|
|
453
|
-
|
|
576
|
+
kv("Created", new Date(refund2.createdAt).toLocaleString(), { dim: true });
|
|
454
577
|
if (refund2.processedAt) {
|
|
455
|
-
|
|
578
|
+
kv("Processed", new Date(refund2.processedAt).toLocaleString(), {
|
|
579
|
+
dim: true
|
|
580
|
+
});
|
|
456
581
|
}
|
|
457
|
-
|
|
582
|
+
gap();
|
|
458
583
|
}
|
|
459
584
|
|
|
460
585
|
// src/cli/prompts.ts
|
|
@@ -675,7 +800,11 @@ async function runInteractiveCommand(command2, args2) {
|
|
|
675
800
|
}
|
|
676
801
|
async function readTempoWhoami() {
|
|
677
802
|
try {
|
|
678
|
-
const result = await runCommandCapture("tempo", [
|
|
803
|
+
const result = await runCommandCapture("tempo", [
|
|
804
|
+
"wallet",
|
|
805
|
+
"-j",
|
|
806
|
+
"whoami"
|
|
807
|
+
]);
|
|
679
808
|
if (result.code !== 0) return null;
|
|
680
809
|
return JSON.parse(result.stdout);
|
|
681
810
|
} catch {
|
|
@@ -688,17 +817,18 @@ async function ensureTempoInstalled() {
|
|
|
688
817
|
if (result.code === 0) return;
|
|
689
818
|
} catch {
|
|
690
819
|
}
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
820
|
+
errorMsg(
|
|
821
|
+
"Tempo CLI required",
|
|
822
|
+
`Install it first:
|
|
823
|
+
${c.cyan}curl -fsSL https://tempo.xyz/install | bash${c.reset}`
|
|
824
|
+
);
|
|
695
825
|
process.exit(1);
|
|
696
826
|
}
|
|
697
827
|
async function ensureTempoReady() {
|
|
698
828
|
await ensureTempoInstalled();
|
|
699
829
|
const whoami = await readTempoWhoami();
|
|
700
830
|
if (whoami?.ready) return whoami;
|
|
701
|
-
|
|
831
|
+
muted("Tempo wallet login required...");
|
|
702
832
|
const loginCode = await runInteractiveCommand("tempo", ["wallet", "login"]);
|
|
703
833
|
if (loginCode !== 0) {
|
|
704
834
|
throw new Error("Tempo wallet login failed");
|
|
@@ -726,12 +856,14 @@ async function ensureSufficientBalance(requiredCents, wallet) {
|
|
|
726
856
|
if (available >= required) {
|
|
727
857
|
return wallet;
|
|
728
858
|
}
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
` Tempo wallet balance is too low for this order: have ${formatUsdcNumber(available)} USDC, need ${formatUsdAmount(requiredCents)} USDC.`
|
|
859
|
+
section("Balance Check");
|
|
860
|
+
line(
|
|
861
|
+
`Wallet balance too low: have ${c.bold}${formatUsdcNumber(available)} USDC${c.reset}, need ${c.bold}${formatUsdAmount(requiredCents)} USDC${c.reset}`
|
|
733
862
|
);
|
|
734
|
-
|
|
863
|
+
kv("Shortfall", `${formatUsdcNumber(required - available)} USDC`, {
|
|
864
|
+
color: c.yellow
|
|
865
|
+
});
|
|
866
|
+
gap();
|
|
735
867
|
const fundNow = await promptConfirm("Open Tempo funding flow now?");
|
|
736
868
|
if (!fundNow) {
|
|
737
869
|
throw new Error(
|
|
@@ -746,13 +878,13 @@ async function ensureSufficientBalance(requiredCents, wallet) {
|
|
|
746
878
|
const refreshed = await ensureTempoReady();
|
|
747
879
|
available = parseTempoBalance(refreshed.balance?.available);
|
|
748
880
|
if (available >= required) {
|
|
749
|
-
|
|
750
|
-
|
|
881
|
+
line(
|
|
882
|
+
`${c.green}\u2714${c.reset} Funding complete \u2014 ${c.bold}${formatUsdcNumber(available)} ${refreshed.balance?.symbol ?? "USDC"}${c.reset}`
|
|
751
883
|
);
|
|
752
884
|
return refreshed;
|
|
753
885
|
}
|
|
754
886
|
if (attempt < 5) {
|
|
755
|
-
|
|
887
|
+
muted("Waiting for updated wallet balance...");
|
|
756
888
|
await sleep(3e3);
|
|
757
889
|
}
|
|
758
890
|
}
|
|
@@ -792,25 +924,28 @@ async function createPurchaseWithTempo(body) {
|
|
|
792
924
|
try {
|
|
793
925
|
return JSON.parse(result.stdout);
|
|
794
926
|
} catch {
|
|
795
|
-
throw new Error(
|
|
927
|
+
throw new Error(
|
|
928
|
+
`Unexpected response from Tempo request: ${result.stdout.trim() || "(empty)"}`
|
|
929
|
+
);
|
|
796
930
|
}
|
|
797
931
|
}
|
|
798
932
|
async function buy(urlArg) {
|
|
799
933
|
try {
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
console.log();
|
|
934
|
+
banner();
|
|
935
|
+
gap();
|
|
803
936
|
const url = urlArg ?? await promptProductUrl();
|
|
804
937
|
const client = new AgentMall();
|
|
805
|
-
|
|
806
|
-
|
|
938
|
+
const product = await spin(
|
|
939
|
+
"Looking up product",
|
|
940
|
+
() => client.products.lookup(url)
|
|
941
|
+
);
|
|
807
942
|
displayProduct(product);
|
|
808
943
|
const quantity = await promptQuantity();
|
|
809
944
|
const selectedVariants = product.variants?.length ? await promptVariants(product.variants) : void 0;
|
|
810
|
-
const variantSelections = selectedVariants?.map(
|
|
811
|
-
|
|
812
|
-
selectedVariants
|
|
945
|
+
const variantSelections = selectedVariants?.map(
|
|
946
|
+
(variant) => variant.selection
|
|
813
947
|
);
|
|
948
|
+
const selectedVariantUnitPriceCents = getSelectedVariantUnitPriceCents(selectedVariants);
|
|
814
949
|
const suggestedMaxBudget = calculateSuggestedBudget(
|
|
815
950
|
product,
|
|
816
951
|
quantity,
|
|
@@ -827,6 +962,7 @@ async function buy(urlArg) {
|
|
|
827
962
|
variantSelections,
|
|
828
963
|
selectedVariantUnitPriceCents
|
|
829
964
|
);
|
|
965
|
+
gap();
|
|
830
966
|
const address = await promptAddress();
|
|
831
967
|
const maxBudget = await promptBudget(suggestedMaxBudget, minimumBudget);
|
|
832
968
|
const { input: input2 } = await import("@inquirer/prompts");
|
|
@@ -835,64 +971,82 @@ async function buy(urlArg) {
|
|
|
835
971
|
validate: (value) => EMAIL_PATTERN.test(value.trim()) ? true : "A valid email is required"
|
|
836
972
|
});
|
|
837
973
|
const body = {
|
|
838
|
-
items: [
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
974
|
+
items: [
|
|
975
|
+
{
|
|
976
|
+
product_url: url,
|
|
977
|
+
quantity,
|
|
978
|
+
...variantSelections?.length ? { variant: variantSelections } : {}
|
|
979
|
+
}
|
|
980
|
+
],
|
|
843
981
|
delivery_address: address,
|
|
844
982
|
max_budget: maxBudget,
|
|
845
983
|
buyer_email: buyerEmail
|
|
846
984
|
};
|
|
847
985
|
displayOrderSummary(body);
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
986
|
+
gap();
|
|
987
|
+
kv("Budget cap", `${c.bold}${formatCents(maxBudget)}${c.reset}`);
|
|
988
|
+
kv("Fee", formatCents(SERVICE_FEE_CENTS), { dim: true });
|
|
989
|
+
kv(
|
|
990
|
+
"Total charge",
|
|
991
|
+
`${c.bold}${formatCents(maxBudget + SERVICE_FEE_CENTS)}${c.reset} USDC on Tempo`
|
|
992
|
+
);
|
|
993
|
+
gap();
|
|
994
|
+
muted(
|
|
995
|
+
"Unused budget is refunded automatically after checkout."
|
|
996
|
+
);
|
|
997
|
+
muted(
|
|
998
|
+
"The buffer covers unforeseen tax, shipping, and retailer price changes."
|
|
999
|
+
);
|
|
1000
|
+
gap();
|
|
852
1001
|
const proceed = await promptConfirm("Place order?");
|
|
853
1002
|
if (!proceed) {
|
|
854
|
-
|
|
1003
|
+
muted("Cancelled.");
|
|
855
1004
|
return;
|
|
856
1005
|
}
|
|
857
1006
|
const totalChargeCents = maxBudget + SERVICE_FEE_CENTS;
|
|
858
|
-
const wallet = await
|
|
859
|
-
|
|
860
|
-
await ensureTempoReady()
|
|
861
|
-
);
|
|
1007
|
+
const wallet = await spin("Connecting wallet", () => ensureTempoReady());
|
|
1008
|
+
await ensureSufficientBalance(totalChargeCents, wallet);
|
|
862
1009
|
if (wallet.wallet) {
|
|
863
|
-
const
|
|
864
|
-
|
|
1010
|
+
const addr = wallet.wallet;
|
|
1011
|
+
const short = `${addr.slice(0, 6)}\u2026${addr.slice(-4)}`;
|
|
1012
|
+
const bal = wallet.balance?.available ? ` \xB7 ${wallet.balance.available} ${wallet.balance?.symbol ?? "USDC"}` : "";
|
|
1013
|
+
muted(`Wallet ${short}${bal}`);
|
|
865
1014
|
}
|
|
866
|
-
|
|
867
|
-
|
|
1015
|
+
const order = await spin(
|
|
1016
|
+
"Placing order",
|
|
1017
|
+
() => createPurchaseWithTempo(body)
|
|
1018
|
+
);
|
|
868
1019
|
if (order.buyerToken) {
|
|
869
1020
|
await saveBuyerToken(order.id, order.buyerToken);
|
|
870
1021
|
}
|
|
871
1022
|
displayOrderResult(order);
|
|
872
1023
|
} catch (error) {
|
|
873
|
-
|
|
1024
|
+
gap();
|
|
874
1025
|
if (error instanceof ValidationError) {
|
|
875
|
-
|
|
1026
|
+
errorMsg("Validation error", error.message);
|
|
876
1027
|
process.exitCode = 1;
|
|
877
1028
|
return;
|
|
878
1029
|
}
|
|
879
1030
|
if (error instanceof ConflictError) {
|
|
880
|
-
|
|
1031
|
+
errorMsg("Conflict", error.message);
|
|
881
1032
|
process.exitCode = 1;
|
|
882
1033
|
return;
|
|
883
1034
|
}
|
|
884
1035
|
if (error instanceof RateLimitError) {
|
|
885
|
-
|
|
1036
|
+
errorMsg(
|
|
1037
|
+
"Rate limited",
|
|
1038
|
+
`Retry in about ${Math.ceil(error.retryAfterMs / 1e3)}s`
|
|
1039
|
+
);
|
|
886
1040
|
process.exitCode = 1;
|
|
887
1041
|
return;
|
|
888
1042
|
}
|
|
889
1043
|
if (error instanceof AgentMallError) {
|
|
890
|
-
|
|
1044
|
+
errorMsg("Request failed", error.message);
|
|
891
1045
|
process.exitCode = 1;
|
|
892
1046
|
return;
|
|
893
1047
|
}
|
|
894
1048
|
if (error instanceof Error) {
|
|
895
|
-
|
|
1049
|
+
errorMsg("Error", error.message);
|
|
896
1050
|
process.exitCode = 1;
|
|
897
1051
|
return;
|
|
898
1052
|
}
|
|
@@ -900,51 +1054,79 @@ async function buy(urlArg) {
|
|
|
900
1054
|
}
|
|
901
1055
|
}
|
|
902
1056
|
async function status(purchaseId) {
|
|
1057
|
+
banner();
|
|
903
1058
|
const apiSecret = process.env.AGENTMALL_API_SECRET;
|
|
904
1059
|
const buyerToken = apiSecret ? null : await getBuyerToken(purchaseId);
|
|
905
1060
|
const client = apiSecret ? new AgentMall({ apiSecret }) : new AgentMall();
|
|
906
1061
|
if (!apiSecret && !buyerToken) {
|
|
907
|
-
throw new Error(
|
|
1062
|
+
throw new Error(
|
|
1063
|
+
"No saved buyer token found for this order on this machine."
|
|
1064
|
+
);
|
|
908
1065
|
}
|
|
909
|
-
const result = await
|
|
910
|
-
|
|
911
|
-
buyerToken ? { buyerToken } : void 0
|
|
1066
|
+
const result = await spin(
|
|
1067
|
+
"Fetching order",
|
|
1068
|
+
() => client.purchases.get(purchaseId, buyerToken ? { buyerToken } : void 0)
|
|
912
1069
|
);
|
|
913
1070
|
displayStatus(result);
|
|
914
1071
|
}
|
|
915
1072
|
async function refund(purchaseId) {
|
|
1073
|
+
banner();
|
|
916
1074
|
const apiSecret = process.env.AGENTMALL_API_SECRET;
|
|
917
1075
|
const buyerToken = apiSecret ? null : await getBuyerToken(purchaseId);
|
|
918
1076
|
const client = apiSecret ? new AgentMall({ apiSecret }) : new AgentMall();
|
|
919
1077
|
if (!apiSecret && !buyerToken) {
|
|
920
|
-
throw new Error(
|
|
1078
|
+
throw new Error(
|
|
1079
|
+
"No saved buyer token found for this order on this machine."
|
|
1080
|
+
);
|
|
921
1081
|
}
|
|
922
|
-
const result = await
|
|
923
|
-
|
|
924
|
-
|
|
1082
|
+
const result = await spin(
|
|
1083
|
+
"Fetching refund",
|
|
1084
|
+
() => client.refunds.getByPurchase(
|
|
1085
|
+
purchaseId,
|
|
1086
|
+
buyerToken ? { buyerToken } : void 0
|
|
1087
|
+
)
|
|
925
1088
|
);
|
|
926
1089
|
displayRefund(result);
|
|
927
1090
|
}
|
|
928
1091
|
async function onboard() {
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
1092
|
+
banner();
|
|
1093
|
+
gap();
|
|
1094
|
+
const wallet = await spin(
|
|
1095
|
+
"Checking Tempo wallet",
|
|
1096
|
+
() => ensureTempoReady()
|
|
1097
|
+
);
|
|
1098
|
+
section("Wallet");
|
|
934
1099
|
if (wallet.wallet) {
|
|
935
|
-
|
|
1100
|
+
kv("Address", wallet.wallet);
|
|
936
1101
|
}
|
|
937
1102
|
if (wallet.balance?.available) {
|
|
938
|
-
|
|
1103
|
+
kv(
|
|
1104
|
+
"Balance",
|
|
1105
|
+
`${wallet.balance.available} ${wallet.balance?.symbol ?? "USDC"}`,
|
|
1106
|
+
{ color: c.green }
|
|
1107
|
+
);
|
|
939
1108
|
}
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
1109
|
+
section("Next Steps");
|
|
1110
|
+
line(
|
|
1111
|
+
`${c.dim}1.${c.reset} Run ${c.bold}tempo wallet whoami${c.reset} to review your wallet`
|
|
1112
|
+
);
|
|
1113
|
+
line(
|
|
1114
|
+
`${c.dim}2.${c.reset} Add funds to your Tempo wallet if needed`
|
|
1115
|
+
);
|
|
1116
|
+
line(
|
|
1117
|
+
`${c.dim}3.${c.reset} Buy something: ${c.bold}npx agentmall buy <url>${c.reset}`
|
|
1118
|
+
);
|
|
1119
|
+
gap();
|
|
945
1120
|
}
|
|
946
1121
|
|
|
947
1122
|
// src/cli.ts
|
|
1123
|
+
var c2 = {
|
|
1124
|
+
reset: "\x1B[0m",
|
|
1125
|
+
bold: "\x1B[1m",
|
|
1126
|
+
dim: "\x1B[2m",
|
|
1127
|
+
cyan: "\x1B[36m",
|
|
1128
|
+
gray: "\x1B[90m"
|
|
1129
|
+
};
|
|
948
1130
|
var args = process.argv.slice(2);
|
|
949
1131
|
var command = args[0];
|
|
950
1132
|
async function main() {
|
|
@@ -983,7 +1165,7 @@ async function main() {
|
|
|
983
1165
|
} catch (err) {
|
|
984
1166
|
if (err instanceof Error) {
|
|
985
1167
|
console.error(`
|
|
986
|
-
\
|
|
1168
|
+
${c2.dim}\u2716${c2.reset} ${err.message}
|
|
987
1169
|
`);
|
|
988
1170
|
} else {
|
|
989
1171
|
console.error(err);
|
|
@@ -992,16 +1174,42 @@ async function main() {
|
|
|
992
1174
|
}
|
|
993
1175
|
}
|
|
994
1176
|
function printHelp() {
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
`);
|
|
1177
|
+
const W = 48;
|
|
1178
|
+
const rule = (title) => {
|
|
1179
|
+
const pad = Math.max(2, W - title.length - 2);
|
|
1180
|
+
return `${c2.dim}\u2500\u2500\u2500${c2.reset} ${c2.bold}${title}${c2.reset} ${c2.dim}${"\u2500".repeat(pad)}${c2.reset}`;
|
|
1181
|
+
};
|
|
1182
|
+
console.log();
|
|
1183
|
+
console.log(
|
|
1184
|
+
` ${c2.bold}\u25CE agentmall${c2.reset} ${c2.dim}\u2014 buy anything with a URL${c2.reset}`
|
|
1185
|
+
);
|
|
1186
|
+
console.log();
|
|
1187
|
+
console.log(` ${rule("Commands")}`);
|
|
1188
|
+
console.log(
|
|
1189
|
+
` ${c2.cyan}agentmall${c2.reset} [url] ${c2.dim}Interactive purchase${c2.reset}`
|
|
1190
|
+
);
|
|
1191
|
+
console.log(
|
|
1192
|
+
` ${c2.cyan}agentmall buy${c2.reset} <url> ${c2.dim}Buy a product${c2.reset}`
|
|
1193
|
+
);
|
|
1194
|
+
console.log(
|
|
1195
|
+
` ${c2.cyan}agentmall status${c2.reset} <id> ${c2.dim}Check order status${c2.reset}`
|
|
1196
|
+
);
|
|
1197
|
+
console.log(
|
|
1198
|
+
` ${c2.cyan}agentmall refund${c2.reset} <id> ${c2.dim}Check refund status${c2.reset}`
|
|
1199
|
+
);
|
|
1200
|
+
console.log(
|
|
1201
|
+
` ${c2.cyan}agentmall onboard${c2.reset} ${c2.dim}Set up Tempo wallet${c2.reset}`
|
|
1202
|
+
);
|
|
1203
|
+
console.log(
|
|
1204
|
+
` ${c2.cyan}agentmall help${c2.reset} ${c2.dim}Show this help${c2.reset}`
|
|
1205
|
+
);
|
|
1206
|
+
console.log();
|
|
1207
|
+
console.log(` ${rule("Examples")}`);
|
|
1208
|
+
console.log(
|
|
1209
|
+
` ${c2.dim}$${c2.reset} npx agentmall https://amazon.com/dp/B0DWTMJHCG`
|
|
1210
|
+
);
|
|
1211
|
+
console.log(` ${c2.dim}$${c2.reset} npx agentmall status pur_abc123`);
|
|
1212
|
+
console.log(` ${c2.dim}$${c2.reset} npx agentmall refund pur_abc123`);
|
|
1213
|
+
console.log();
|
|
1006
1214
|
}
|
|
1007
1215
|
main();
|