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.
Files changed (2) hide show
  1. package/dist/cli.js +367 -159
  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,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
- 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)`
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
- console.log(
360
- ` Service fee: ${formatCents(SERVICE_FEE_CENTS)}`
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
- 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();
396
- console.log(` Max budget: \x1B[1m${formatCents(body.max_budget)}\x1B[0m + ${formatCents(SERVICE_FEE_CENTS)} fee`);
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
- console.log();
516
+ gap();
517
+ const lines = [];
401
518
  if (order.status === "failed") {
402
- console.log(` \x1B[31m\u274C Order failed during submission.\x1B[0m`);
519
+ lines.push(`${c.red}\u2716 Order failed during submission${c.reset}`);
403
520
  } else if (order.deduplicated) {
404
- console.log(` \x1B[33m\u267B\uFE0F Reused existing order.\x1B[0m`);
521
+ lines.push(`${c.yellow}\u267B Reused existing order${c.reset}`);
405
522
  } else {
406
- console.log(` \x1B[32m\u2705 Order placed!\x1B[0m`);
523
+ lines.push(`${c.green}\u2714 Order placed${c.reset}`);
407
524
  }
408
- console.log(` ID: \x1B[1m${order.id}\x1B[0m`);
409
- 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)}`);
410
528
  if (order.buyerToken) {
411
- 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}`);
412
531
  }
413
- console.log();
414
- console.log(
415
- 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}`
416
535
  );
417
- console.log(` npx agentmall status ${order.id}`);
418
- console.log();
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
- console.log();
422
- console.log(` \x1B[1m\u{1F4E6} Order ${purchase.id}\x1B[0m`);
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
- 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
+ );
428
550
  }
429
551
  }
552
+ gap();
430
553
  if (purchase.finalTotal) {
431
- console.log(` Final total: ${formatCents(purchase.finalTotal)}`);
554
+ kv("Final total", formatCents(purchase.finalTotal));
432
555
  }
433
556
  if (purchase.failureReason) {
434
- console.log(
435
- ` \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}`
436
560
  );
437
561
  }
438
562
  if (purchase.deliveryMethod) {
439
- console.log(` Shipping: ${purchase.deliveryMethod}`);
563
+ kv("Shipping", purchase.deliveryMethod);
440
564
  }
441
- console.log(` Created: ${new Date(purchase.createdAt).toLocaleString()}`);
442
- console.log();
565
+ kv("Created", new Date(purchase.createdAt).toLocaleString(), { dim: true });
566
+ gap();
443
567
  }
444
568
  function displayRefund(refund2) {
445
- console.log();
446
- console.log(` \x1B[1m\u{1F4B8} Refund ${refund2.id}\x1B[0m`);
447
- console.log(` Status: ${refund2.status}`);
448
- console.log(` Amount: ${formatCents(refund2.amountCents)}`);
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
- console.log(` TX: ${refund2.txHash}`);
574
+ kv("TX", refund2.txHash, { dim: true });
452
575
  }
453
- console.log(` Created: ${new Date(refund2.createdAt).toLocaleString()}`);
576
+ kv("Created", new Date(refund2.createdAt).toLocaleString(), { dim: true });
454
577
  if (refund2.processedAt) {
455
- console.log(` Processed: ${new Date(refund2.processedAt).toLocaleString()}`);
578
+ kv("Processed", new Date(refund2.processedAt).toLocaleString(), {
579
+ dim: true
580
+ });
456
581
  }
457
- console.log();
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", ["wallet", "-j", "whoami"]);
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
- console.error("\x1B[31mTempo CLI is required for purchases.\x1B[0m");
692
- console.error("");
693
- console.error("Install it first:");
694
- 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
+ );
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
- console.log(" Tempo wallet login required...");
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
- console.log();
730
- console.log(" \x1B[1m\u{1F4B3} Balance Check\x1B[0m");
731
- console.log(
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
- console.log(` Shortfall: ${formatUsdcNumber(required - available)} USDC`);
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
- console.log(
750
- ` 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}`
751
883
  );
752
884
  return refreshed;
753
885
  }
754
886
  if (attempt < 5) {
755
- console.log(" Waiting for updated wallet balance...");
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(`Unexpected response from Tempo request: ${result.stdout.trim() || "(empty)"}`);
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
- console.log();
801
- console.log(" \x1B[1m\u{1F6D2} agentmall\x1B[0m \u2014 Buy anything with a URL");
802
- console.log();
934
+ banner();
935
+ gap();
803
936
  const url = urlArg ?? await promptProductUrl();
804
937
  const client = new AgentMall();
805
- console.log(" Looking up product...");
806
- const product = await client.products.lookup(url);
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((variant) => variant.selection);
811
- const selectedVariantUnitPriceCents = getSelectedVariantUnitPriceCents(
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
- product_url: url,
840
- quantity,
841
- ...variantSelections?.length ? { variant: variantSelections } : {}
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
- console.log(` Total charge: \x1B[1m${formatCents(maxBudget + SERVICE_FEE_CENTS)}\x1B[0m USDC on Tempo`);
849
- console.log(" Any unused amount from your max budget is refunded automatically after checkout.");
850
- console.log(" The buffer covers unforeseen tax, shipping, and retailer price changes at final checkout.");
851
- 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();
852
1001
  const proceed = await promptConfirm("Place order?");
853
1002
  if (!proceed) {
854
- console.log(" Cancelled.");
1003
+ muted("Cancelled.");
855
1004
  return;
856
1005
  }
857
1006
  const totalChargeCents = maxBudget + SERVICE_FEE_CENTS;
858
- const wallet = await ensureSufficientBalance(
859
- totalChargeCents,
860
- await ensureTempoReady()
861
- );
1007
+ const wallet = await spin("Connecting wallet", () => ensureTempoReady());
1008
+ await ensureSufficientBalance(totalChargeCents, wallet);
862
1009
  if (wallet.wallet) {
863
- const balance = wallet.balance?.available ? `, balance ${wallet.balance.available} ${wallet.balance?.symbol ?? "USDC"}` : "";
864
- 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}`);
865
1014
  }
866
- console.log(" Placing order...");
867
- const order = await createPurchaseWithTempo(body);
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
- console.log();
1024
+ gap();
874
1025
  if (error instanceof ValidationError) {
875
- console.error(` \x1B[31mValidation error:\x1B[0m ${error.message}`);
1026
+ errorMsg("Validation error", error.message);
876
1027
  process.exitCode = 1;
877
1028
  return;
878
1029
  }
879
1030
  if (error instanceof ConflictError) {
880
- console.error(` \x1B[31mConflict:\x1B[0m ${error.message}`);
1031
+ errorMsg("Conflict", error.message);
881
1032
  process.exitCode = 1;
882
1033
  return;
883
1034
  }
884
1035
  if (error instanceof RateLimitError) {
885
- 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
+ );
886
1040
  process.exitCode = 1;
887
1041
  return;
888
1042
  }
889
1043
  if (error instanceof AgentMallError) {
890
- console.error(` \x1B[31mRequest failed:\x1B[0m ${error.message}`);
1044
+ errorMsg("Request failed", error.message);
891
1045
  process.exitCode = 1;
892
1046
  return;
893
1047
  }
894
1048
  if (error instanceof Error) {
895
- console.error(` \x1B[31mError:\x1B[0m ${error.message}`);
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("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
+ );
908
1065
  }
909
- const result = await client.purchases.get(
910
- purchaseId,
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("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
+ );
921
1081
  }
922
- const result = await client.refunds.getByPurchase(
923
- purchaseId,
924
- buyerToken ? { buyerToken } : void 0
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
- console.log();
930
- console.log(" \x1B[1m\u{1F511} agentmall onboarding\x1B[0m");
931
- console.log();
932
- const wallet = await ensureTempoReady();
933
- console.log();
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
- console.log(` Wallet ready: ${wallet.wallet}`);
1100
+ kv("Address", wallet.wallet);
936
1101
  }
937
1102
  if (wallet.balance?.available) {
938
- 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
+ );
939
1108
  }
940
- console.log(" Next steps:");
941
- console.log(" 1. Run \x1B[1mtempo wallet whoami\x1B[0m to review your wallet");
942
- console.log(" 2. Add funds to your Tempo wallet if needed");
943
- console.log(" 3. Retry your purchase with \x1B[1mnpx agentmall buy <url>\x1B[0m");
944
- 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();
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
- \x1B[31mError: ${err.message}\x1B[0m
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
- console.log(`
996
- \x1B[1m\u{1F6D2} agentmall\x1B[0m \u2014 Buy anything with a URL
997
-
998
- Usage:
999
- \u{1F511} agentmall onboard Log in to Tempo and show wallet funding steps
1000
- \u{1F6CD}\uFE0F agentmall [url] Buy a product (interactive)
1001
- \u{1F6CD}\uFE0F agentmall buy <url> Buy a product
1002
- \u{1F4E6} agentmall status <id> Check order status using the saved buyer token
1003
- \u{1F4B8} agentmall refund <id> Check refund status using the saved buyer token
1004
- \u2753 agentmall help Show this help
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();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentmall",
3
- "version": "0.1.11",
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",