agentmall 0.1.12 → 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.
Files changed (2) hide show
  1. package/dist/cli.js +365 -183
  2. 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
- 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();
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
- 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`
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
- console.log(` Price: \x1B[32m${formatCents(product.price)}\x1B[0m`);
435
+ kv("Price", formatCents(product.price), { color: c.green });
317
436
  }
318
437
  if (product.variants?.length) {
319
- console.log();
438
+ gap();
320
439
  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)}`);
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,124 +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
- console.log();
339
- console.log(" \x1B[1m\u{1F4B8} Checkout Estimate\x1B[0m");
458
+ section("Checkout Estimate");
340
459
  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`
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
- console.log(` Price: \x1B[32m${formatCents(linePrice)}\x1B[0m`);
465
+ kv("Price", formatCents(linePrice), { color: c.green });
346
466
  }
347
- console.log(` Quantity: ${quantityClamped}`);
467
+ kv("Quantity", `${quantityClamped}`);
348
468
  if (quantityClamped > 1) {
349
- console.log(` Unit price: ${formatCents(unitPriceCents)}`);
469
+ kv("Unit price", formatCents(unitPriceCents), { dim: true });
350
470
  }
351
471
  if (variantSelections?.length) {
352
472
  for (const variant of variantSelections) {
353
- console.log(` ${variant.label}: ${variant.value}`);
473
+ kv(variant.label, variant.value);
354
474
  }
355
475
  }
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)}`
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`
361
481
  );
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
- console.log(` \x1B[33m\u26A0 Price from cache \u2014 may not reflect current listing\x1B[0m`);
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
- console.log();
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
- console.log(` ${qty}x ${url}`);
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
- console.log(` ${v.label}: ${v.value}`);
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
- 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();
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
+ );
396
514
  }
397
515
  function displayOrderResult(order) {
398
- console.log();
516
+ gap();
517
+ const lines = [];
399
518
  if (order.status === "failed") {
400
- console.log(` \x1B[31m\u274C Order failed during submission.\x1B[0m`);
519
+ lines.push(`${c.red}\u2716 Order failed during submission${c.reset}`);
401
520
  } else if (order.deduplicated) {
402
- console.log(` \x1B[33m\u267B\uFE0F Reused existing order.\x1B[0m`);
521
+ lines.push(`${c.yellow}\u267B Reused existing order${c.reset}`);
403
522
  } else {
404
- console.log(` \x1B[32m\u2705 Order placed!\x1B[0m`);
523
+ lines.push(`${c.green}\u2714 Order placed${c.reset}`);
405
524
  }
406
- console.log(` ID: \x1B[1m${order.id}\x1B[0m`);
407
- console.log(` Status: ${order.status}`);
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)}`);
408
528
  if (order.buyerToken) {
409
- console.log(" \u{1F510} Saved local access token for status and refund checks.");
529
+ lines.push("");
530
+ lines.push(`${c.dim}\u{1F510} Saved buyer token for status checks${c.reset}`);
410
531
  }
411
- console.log();
412
- console.log(
413
- order.status === "failed" ? " Check status for failure details and refund progress:" : " Check status in 5-10 minutes:"
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}`
414
535
  );
415
- console.log(` npx agentmall status ${order.id}`);
416
- console.log();
536
+ lines.push(`${c.cyan}npx agentmall status ${order.id}${c.reset}`);
537
+ box(lines);
538
+ gap();
417
539
  }
418
540
  function displayStatus(purchase) {
419
- console.log();
420
- console.log(` \x1B[1m\u{1F4E6} Order ${purchase.id}\x1B[0m`);
421
- console.log(` Status: ${purchase.status}`);
541
+ section(`Order ${purchase.id}`);
542
+ kv("Status", statusBadge(purchase.status));
422
543
  if (purchase.items?.length) {
544
+ gap();
423
545
  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}`);
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
+ );
426
550
  }
427
551
  }
552
+ gap();
428
553
  if (purchase.finalTotal) {
429
- console.log(` Final total: ${formatCents(purchase.finalTotal)}`);
554
+ kv("Final total", formatCents(purchase.finalTotal));
430
555
  }
431
556
  if (purchase.failureReason) {
432
- console.log(
433
- ` \x1B[31mFailure: ${FAILURE_MESSAGES[purchase.failureReason] ?? purchase.failureReason}\x1B[0m`
557
+ kv(
558
+ "Failure",
559
+ `${c.red}${FAILURE_MESSAGES[purchase.failureReason] ?? purchase.failureReason}${c.reset}`
434
560
  );
435
561
  }
436
562
  if (purchase.deliveryMethod) {
437
- console.log(` Shipping: ${purchase.deliveryMethod}`);
563
+ kv("Shipping", purchase.deliveryMethod);
438
564
  }
439
- console.log(` Created: ${new Date(purchase.createdAt).toLocaleString()}`);
440
- console.log();
565
+ kv("Created", new Date(purchase.createdAt).toLocaleString(), { dim: true });
566
+ gap();
441
567
  }
442
568
  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}`);
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);
448
573
  if (refund2.txHash) {
449
- console.log(` TX: ${refund2.txHash}`);
574
+ kv("TX", refund2.txHash, { dim: true });
450
575
  }
451
- console.log(` Created: ${new Date(refund2.createdAt).toLocaleString()}`);
576
+ kv("Created", new Date(refund2.createdAt).toLocaleString(), { dim: true });
452
577
  if (refund2.processedAt) {
453
- console.log(` Processed: ${new Date(refund2.processedAt).toLocaleString()}`);
578
+ kv("Processed", new Date(refund2.processedAt).toLocaleString(), {
579
+ dim: true
580
+ });
454
581
  }
455
- console.log();
582
+ gap();
456
583
  }
457
584
 
458
585
  // src/cli/prompts.ts
@@ -673,7 +800,11 @@ async function runInteractiveCommand(command2, args2) {
673
800
  }
674
801
  async function readTempoWhoami() {
675
802
  try {
676
- const result = await runCommandCapture("tempo", ["wallet", "-j", "whoami"]);
803
+ const result = await runCommandCapture("tempo", [
804
+ "wallet",
805
+ "-j",
806
+ "whoami"
807
+ ]);
677
808
  if (result.code !== 0) return null;
678
809
  return JSON.parse(result.stdout);
679
810
  } catch {
@@ -686,17 +817,18 @@ async function ensureTempoInstalled() {
686
817
  if (result.code === 0) return;
687
818
  } catch {
688
819
  }
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");
820
+ errorMsg(
821
+ "Tempo CLI required",
822
+ `Install it first:
823
+ ${c.cyan}curl -fsSL https://tempo.xyz/install | bash${c.reset}`
824
+ );
693
825
  process.exit(1);
694
826
  }
695
827
  async function ensureTempoReady() {
696
828
  await ensureTempoInstalled();
697
829
  const whoami = await readTempoWhoami();
698
830
  if (whoami?.ready) return whoami;
699
- console.log(" Tempo wallet login required...");
831
+ muted("Tempo wallet login required...");
700
832
  const loginCode = await runInteractiveCommand("tempo", ["wallet", "login"]);
701
833
  if (loginCode !== 0) {
702
834
  throw new Error("Tempo wallet login failed");
@@ -711,30 +843,6 @@ function formatUsdAmount(cents) {
711
843
  function sleep(ms) {
712
844
  return new Promise((resolve) => setTimeout(resolve, ms));
713
845
  }
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
846
  function getSelectedVariantUnitPriceCents(selectedVariants) {
739
847
  const highestVariantPrice = selectedVariants?.reduce((highest, variant) => {
740
848
  const variantPriceCents = typeof variant.price === "number" ? Math.round(variant.price * 100) : 0;
@@ -748,12 +856,14 @@ async function ensureSufficientBalance(requiredCents, wallet) {
748
856
  if (available >= required) {
749
857
  return wallet;
750
858
  }
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.`
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}`
755
862
  );
756
- console.log(` Shortfall: ${formatUsdcNumber(required - available)} USDC`);
863
+ kv("Shortfall", `${formatUsdcNumber(required - available)} USDC`, {
864
+ color: c.yellow
865
+ });
866
+ gap();
757
867
  const fundNow = await promptConfirm("Open Tempo funding flow now?");
758
868
  if (!fundNow) {
759
869
  throw new Error(
@@ -768,13 +878,13 @@ async function ensureSufficientBalance(requiredCents, wallet) {
768
878
  const refreshed = await ensureTempoReady();
769
879
  available = parseTempoBalance(refreshed.balance?.available);
770
880
  if (available >= required) {
771
- console.log(
772
- ` Funding complete. New balance: ${formatUsdcNumber(available)} ${refreshed.balance?.symbol ?? "USDC"}`
881
+ line(
882
+ `${c.green}\u2714${c.reset} Funding complete \u2014 ${c.bold}${formatUsdcNumber(available)} ${refreshed.balance?.symbol ?? "USDC"}${c.reset}`
773
883
  );
774
884
  return refreshed;
775
885
  }
776
886
  if (attempt < 5) {
777
- console.log(" Waiting for updated wallet balance...");
887
+ muted("Waiting for updated wallet balance...");
778
888
  await sleep(3e3);
779
889
  }
780
890
  }
@@ -814,27 +924,28 @@ async function createPurchaseWithTempo(body) {
814
924
  try {
815
925
  return JSON.parse(result.stdout);
816
926
  } catch {
817
- throw new Error(`Unexpected response from Tempo request: ${result.stdout.trim() || "(empty)"}`);
927
+ throw new Error(
928
+ `Unexpected response from Tempo request: ${result.stdout.trim() || "(empty)"}`
929
+ );
818
930
  }
819
931
  }
820
932
  async function buy(urlArg) {
821
933
  try {
822
- console.log();
823
- console.log(" \x1B[1m\u{1F6D2} agentmall\x1B[0m \u2014 Buy anything with a URL");
824
- console.log();
934
+ banner();
935
+ gap();
825
936
  const url = urlArg ?? await promptProductUrl();
826
937
  const client = new AgentMall();
827
- const product = await withSpinner(
828
- "Looking up product...",
938
+ const product = await spin(
939
+ "Looking up product",
829
940
  () => client.products.lookup(url)
830
941
  );
831
942
  displayProduct(product);
832
943
  const quantity = await promptQuantity();
833
944
  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
945
+ const variantSelections = selectedVariants?.map(
946
+ (variant) => variant.selection
837
947
  );
948
+ const selectedVariantUnitPriceCents = getSelectedVariantUnitPriceCents(selectedVariants);
838
949
  const suggestedMaxBudget = calculateSuggestedBudget(
839
950
  product,
840
951
  quantity,
@@ -851,6 +962,7 @@ async function buy(urlArg) {
851
962
  variantSelections,
852
963
  selectedVariantUnitPriceCents
853
964
  );
965
+ gap();
854
966
  const address = await promptAddress();
855
967
  const maxBudget = await promptBudget(suggestedMaxBudget, minimumBudget);
856
968
  const { input: input2 } = await import("@inquirer/prompts");
@@ -859,66 +971,82 @@ async function buy(urlArg) {
859
971
  validate: (value) => EMAIL_PATTERN.test(value.trim()) ? true : "A valid email is required"
860
972
  });
861
973
  const body = {
862
- items: [{
863
- product_url: url,
864
- quantity,
865
- ...variantSelections?.length ? { variant: variantSelections } : {}
866
- }],
974
+ items: [
975
+ {
976
+ product_url: url,
977
+ quantity,
978
+ ...variantSelections?.length ? { variant: variantSelections } : {}
979
+ }
980
+ ],
867
981
  delivery_address: address,
868
982
  max_budget: maxBudget,
869
983
  buyer_email: buyerEmail
870
984
  };
871
985
  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();
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();
878
1001
  const proceed = await promptConfirm("Place order?");
879
1002
  if (!proceed) {
880
- console.log(" Cancelled.");
1003
+ muted("Cancelled.");
881
1004
  return;
882
1005
  }
883
1006
  const totalChargeCents = maxBudget + SERVICE_FEE_CENTS;
884
- const wallet = await ensureSufficientBalance(
885
- totalChargeCents,
886
- await ensureTempoReady()
887
- );
1007
+ const wallet = await spin("Connecting wallet", () => ensureTempoReady());
1008
+ await ensureSufficientBalance(totalChargeCents, wallet);
888
1009
  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}`);
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}`);
891
1014
  }
892
- console.log(" Placing order...");
893
- const order = await createPurchaseWithTempo(body);
1015
+ const order = await spin(
1016
+ "Placing order",
1017
+ () => createPurchaseWithTempo(body)
1018
+ );
894
1019
  if (order.buyerToken) {
895
1020
  await saveBuyerToken(order.id, order.buyerToken);
896
1021
  }
897
1022
  displayOrderResult(order);
898
1023
  } catch (error) {
899
- console.log();
1024
+ gap();
900
1025
  if (error instanceof ValidationError) {
901
- console.error(` \x1B[31mValidation error:\x1B[0m ${error.message}`);
1026
+ errorMsg("Validation error", error.message);
902
1027
  process.exitCode = 1;
903
1028
  return;
904
1029
  }
905
1030
  if (error instanceof ConflictError) {
906
- console.error(` \x1B[31mConflict:\x1B[0m ${error.message}`);
1031
+ errorMsg("Conflict", error.message);
907
1032
  process.exitCode = 1;
908
1033
  return;
909
1034
  }
910
1035
  if (error instanceof RateLimitError) {
911
- console.error(` \x1B[31mRate limited:\x1B[0m retry in about ${Math.ceil(error.retryAfterMs / 1e3)}s`);
1036
+ errorMsg(
1037
+ "Rate limited",
1038
+ `Retry in about ${Math.ceil(error.retryAfterMs / 1e3)}s`
1039
+ );
912
1040
  process.exitCode = 1;
913
1041
  return;
914
1042
  }
915
1043
  if (error instanceof AgentMallError) {
916
- console.error(` \x1B[31mRequest failed:\x1B[0m ${error.message}`);
1044
+ errorMsg("Request failed", error.message);
917
1045
  process.exitCode = 1;
918
1046
  return;
919
1047
  }
920
1048
  if (error instanceof Error) {
921
- console.error(` \x1B[31mError:\x1B[0m ${error.message}`);
1049
+ errorMsg("Error", error.message);
922
1050
  process.exitCode = 1;
923
1051
  return;
924
1052
  }
@@ -926,51 +1054,79 @@ async function buy(urlArg) {
926
1054
  }
927
1055
  }
928
1056
  async function status(purchaseId) {
1057
+ banner();
929
1058
  const apiSecret = process.env.AGENTMALL_API_SECRET;
930
1059
  const buyerToken = apiSecret ? null : await getBuyerToken(purchaseId);
931
1060
  const client = apiSecret ? new AgentMall({ apiSecret }) : new AgentMall();
932
1061
  if (!apiSecret && !buyerToken) {
933
- throw new Error("No saved buyer token found for this order on this machine.");
1062
+ throw new Error(
1063
+ "No saved buyer token found for this order on this machine."
1064
+ );
934
1065
  }
935
- const result = await client.purchases.get(
936
- purchaseId,
937
- buyerToken ? { buyerToken } : void 0
1066
+ const result = await spin(
1067
+ "Fetching order",
1068
+ () => client.purchases.get(purchaseId, buyerToken ? { buyerToken } : void 0)
938
1069
  );
939
1070
  displayStatus(result);
940
1071
  }
941
1072
  async function refund(purchaseId) {
1073
+ banner();
942
1074
  const apiSecret = process.env.AGENTMALL_API_SECRET;
943
1075
  const buyerToken = apiSecret ? null : await getBuyerToken(purchaseId);
944
1076
  const client = apiSecret ? new AgentMall({ apiSecret }) : new AgentMall();
945
1077
  if (!apiSecret && !buyerToken) {
946
- throw new Error("No saved buyer token found for this order on this machine.");
1078
+ throw new Error(
1079
+ "No saved buyer token found for this order on this machine."
1080
+ );
947
1081
  }
948
- const result = await client.refunds.getByPurchase(
949
- purchaseId,
950
- buyerToken ? { buyerToken } : void 0
1082
+ const result = await spin(
1083
+ "Fetching refund",
1084
+ () => client.refunds.getByPurchase(
1085
+ purchaseId,
1086
+ buyerToken ? { buyerToken } : void 0
1087
+ )
951
1088
  );
952
1089
  displayRefund(result);
953
1090
  }
954
1091
  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();
1092
+ banner();
1093
+ gap();
1094
+ const wallet = await spin(
1095
+ "Checking Tempo wallet",
1096
+ () => ensureTempoReady()
1097
+ );
1098
+ section("Wallet");
960
1099
  if (wallet.wallet) {
961
- console.log(` Wallet ready: ${wallet.wallet}`);
1100
+ kv("Address", wallet.wallet);
962
1101
  }
963
1102
  if (wallet.balance?.available) {
964
- console.log(` Available balance: ${wallet.balance.available} ${wallet.balance?.symbol ?? "USDC"}`);
1103
+ kv(
1104
+ "Balance",
1105
+ `${wallet.balance.available} ${wallet.balance?.symbol ?? "USDC"}`,
1106
+ { color: c.green }
1107
+ );
965
1108
  }
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();
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();
971
1120
  }
972
1121
 
973
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
+ };
974
1130
  var args = process.argv.slice(2);
975
1131
  var command = args[0];
976
1132
  async function main() {
@@ -1009,7 +1165,7 @@ async function main() {
1009
1165
  } catch (err) {
1010
1166
  if (err instanceof Error) {
1011
1167
  console.error(`
1012
- \x1B[31mError: ${err.message}\x1B[0m
1168
+ ${c2.dim}\u2716${c2.reset} ${err.message}
1013
1169
  `);
1014
1170
  } else {
1015
1171
  console.error(err);
@@ -1018,16 +1174,42 @@ async function main() {
1018
1174
  }
1019
1175
  }
1020
1176
  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
- `);
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();
1032
1214
  }
1033
1215
  main();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentmall",
3
- "version": "0.1.12",
3
+ "version": "0.1.13",
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",