agentmall 0.1.12 → 0.1.14

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