agentmall 0.1.7 → 0.1.9

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 +126 -60
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -288,34 +288,32 @@ function formatCents(cents) {
288
288
  function getBudgetBasePrice(product) {
289
289
  return product.listPrice ?? product.price;
290
290
  }
291
- function calculateSuggestedBudget(product, quantity = 1) {
291
+ function calculateSuggestedBudget(product, quantity = 1, variantUnitPriceCents) {
292
292
  const quantityClamped = Math.max(1, quantity);
293
- const basePriceCents = getBudgetBasePrice(product) * quantityClamped;
293
+ const baseUnitPriceCents = Math.max(
294
+ getBudgetBasePrice(product),
295
+ variantUnitPriceCents ?? 0
296
+ );
297
+ const basePriceCents = baseUnitPriceCents * quantityClamped;
294
298
  const variableBufferCents = Math.round(basePriceCents * 0.15);
295
299
  return basePriceCents + Math.max(variableBufferCents, 800);
296
300
  }
297
- function calculateMinimumBudget(product, quantity = 1) {
298
- return product.price * Math.max(1, quantity);
301
+ function calculateMinimumBudget(product, quantity = 1, variantUnitPriceCents) {
302
+ const unitPriceCents = Math.max(product.price, variantUnitPriceCents ?? 0);
303
+ return unitPriceCents * Math.max(1, quantity);
299
304
  }
300
- function displayProduct(product, quantity = 1) {
301
- const quantityClamped = Math.max(1, quantity);
302
- const suggestedBudget = calculateSuggestedBudget(product, quantityClamped);
303
- const linePrice = product.price * quantityClamped;
304
- const lineListPrice = typeof product.listPrice === "number" ? product.listPrice * quantityClamped : void 0;
305
+ function displayProduct(product) {
305
306
  console.log();
307
+ console.log(" \x1B[1m\u{1F4E6} Product\x1B[0m");
306
308
  console.log(` \x1B[1m${product.title}\x1B[0m`);
307
309
  console.log(` ${product.retailer} \u2014 ${product.availability}`);
308
310
  console.log();
309
- if (lineListPrice && product.discountPercent) {
311
+ if (product.listPrice && product.discountPercent) {
310
312
  console.log(
311
- ` Price: \x1B[32m${formatCents(linePrice)}\x1B[0m \x1B[2m\x1B[9m${formatCents(lineListPrice)}\x1B[0m \x1B[33m-${product.discountPercent}%\x1B[0m`
313
+ ` Price: \x1B[32m${formatCents(product.price)}\x1B[0m \x1B[2m\x1B[9m${formatCents(product.listPrice)}\x1B[0m \x1B[33m-${product.discountPercent}%\x1B[0m`
312
314
  );
313
315
  } else {
314
- console.log(` Price: \x1B[32m${formatCents(linePrice)}\x1B[0m`);
315
- }
316
- if (quantityClamped > 1) {
317
- console.log(` Quantity: ${quantityClamped}`);
318
- console.log(` Unit price: ${formatCents(product.price)}`);
316
+ console.log(` Price: \x1B[32m${formatCents(product.price)}\x1B[0m`);
319
317
  }
320
318
  if (product.variants?.length) {
321
319
  console.log();
@@ -325,6 +323,36 @@ function displayProduct(product, quantity = 1) {
325
323
  }
326
324
  }
327
325
  console.log();
326
+ }
327
+ function displayCheckoutEstimate(product, quantity = 1, variantSelections, variantUnitPriceCents) {
328
+ const quantityClamped = Math.max(1, quantity);
329
+ const unitPriceCents = Math.max(product.price, variantUnitPriceCents ?? 0);
330
+ const suggestedBudget = calculateSuggestedBudget(
331
+ product,
332
+ quantityClamped,
333
+ variantUnitPriceCents
334
+ );
335
+ const linePrice = unitPriceCents * quantityClamped;
336
+ const listBaseUnitPrice = typeof product.listPrice === "number" ? Math.max(product.listPrice, variantUnitPriceCents ?? 0) : void 0;
337
+ const lineListPrice = typeof listBaseUnitPrice === "number" ? listBaseUnitPrice * quantityClamped : void 0;
338
+ console.log();
339
+ console.log(" \x1B[1m\u{1F4B8} Checkout Estimate\x1B[0m");
340
+ 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`
343
+ );
344
+ } else {
345
+ console.log(` Price: \x1B[32m${formatCents(linePrice)}\x1B[0m`);
346
+ }
347
+ console.log(` Quantity: ${quantityClamped}`);
348
+ if (quantityClamped > 1) {
349
+ console.log(` Unit price: ${formatCents(unitPriceCents)}`);
350
+ }
351
+ if (variantSelections?.length) {
352
+ for (const variant of variantSelections) {
353
+ console.log(` ${variant.label}: ${variant.value}`);
354
+ }
355
+ }
328
356
  console.log(
329
357
  ` Suggested budget: \x1B[1m${formatCents(suggestedBudget)}\x1B[0m (includes 15% buffer for tax and an $8 minimum shipping buffer)`
330
358
  );
@@ -347,7 +375,7 @@ function displayProduct(product, quantity = 1) {
347
375
  }
348
376
  function displayOrderSummary(body) {
349
377
  console.log();
350
- console.log(" \x1B[1mOrder Summary\x1B[0m");
378
+ console.log(" \x1B[1m\u{1F9FE} Order Summary\x1B[0m");
351
379
  for (const item of body.items) {
352
380
  const url = item.product_url;
353
381
  const qty = item.quantity ?? 1;
@@ -360,6 +388,7 @@ function displayOrderSummary(body) {
360
388
  }
361
389
  const addr = body.delivery_address;
362
390
  console.log();
391
+ console.log(" \x1B[1m\u{1F4CD} Shipping\x1B[0m");
363
392
  console.log(` Ship to: ${addr.first_name} ${addr.last_name}`);
364
393
  console.log(` ${addr.address_line1}${addr.address_line2 ? `, ${addr.address_line2}` : ""}`);
365
394
  console.log(` ${addr.city}, ${addr.state ?? ""} ${addr.postal_code} ${addr.country}`);
@@ -370,16 +399,16 @@ function displayOrderSummary(body) {
370
399
  function displayOrderResult(order) {
371
400
  console.log();
372
401
  if (order.status === "failed") {
373
- console.log(` \x1B[31m\u2717\x1B[0m Order failed during submission.`);
402
+ console.log(` \x1B[31m\u274C Order failed during submission.\x1B[0m`);
374
403
  } else if (order.deduplicated) {
375
- console.log(` \x1B[33m\u21BA\x1B[0m Reused existing order.`);
404
+ console.log(` \x1B[33m\u267B\uFE0F Reused existing order.\x1B[0m`);
376
405
  } else {
377
- console.log(` \x1B[32m\u2713\x1B[0m Order placed!`);
406
+ console.log(` \x1B[32m\u2705 Order placed!\x1B[0m`);
378
407
  }
379
408
  console.log(` ID: \x1B[1m${order.id}\x1B[0m`);
380
409
  console.log(` Status: ${order.status}`);
381
410
  if (order.buyerToken) {
382
- console.log(" Saved local access token for status and refund checks.");
411
+ console.log(" \u{1F510} Saved local access token for status and refund checks.");
383
412
  }
384
413
  console.log();
385
414
  console.log(
@@ -390,7 +419,7 @@ function displayOrderResult(order) {
390
419
  }
391
420
  function displayStatus(purchase) {
392
421
  console.log();
393
- console.log(` \x1B[1mOrder ${purchase.id}\x1B[0m`);
422
+ console.log(` \x1B[1m\u{1F4E6} Order ${purchase.id}\x1B[0m`);
394
423
  console.log(` Status: ${purchase.status}`);
395
424
  if (purchase.items?.length) {
396
425
  for (const item of purchase.items) {
@@ -414,7 +443,7 @@ function displayStatus(purchase) {
414
443
  }
415
444
  function displayRefund(refund2) {
416
445
  console.log();
417
- console.log(` \x1B[1mRefund ${refund2.id}\x1B[0m`);
446
+ console.log(` \x1B[1m\u{1F4B8} Refund ${refund2.id}\x1B[0m`);
418
447
  console.log(` Status: ${refund2.status}`);
419
448
  console.log(` Amount: ${formatCents(refund2.amountCents)}`);
420
449
  console.log(` Reason: ${refund2.reason}`);
@@ -466,7 +495,12 @@ async function promptVariants(variants) {
466
495
  value: o.value
467
496
  }))
468
497
  });
469
- selections.push({ label, value });
498
+ const selected = options.find((o) => o.value === value);
499
+ selections.push({
500
+ selection: { label, value },
501
+ ...selected?.price !== void 0 ? { price: selected.price } : {},
502
+ ...selected?.url ? { url: selected.url } : {}
503
+ });
470
504
  }
471
505
  return selections;
472
506
  }
@@ -514,36 +548,33 @@ async function promptQuantity() {
514
548
  async function promptConfirm(message) {
515
549
  return confirm({ message });
516
550
  }
517
- function formatCents2(cents) {
518
- return `$${(cents / 100).toFixed(2)}`;
551
+ function formatUsd(cents) {
552
+ return (cents / 100).toFixed(2);
519
553
  }
520
- function parseBudgetInput(value) {
521
- const trimmed = value.trim();
554
+ function parseBudgetUsdInput(value) {
555
+ const trimmed = value.trim().replace(/[$,\s]/g, "");
522
556
  if (!trimmed) return null;
523
- if (/^\d+\.\d{1,2}$/.test(trimmed)) {
557
+ if (/^\d+(\.\d{0,2})?$/.test(trimmed)) {
524
558
  return Math.round(Number(trimmed) * 100);
525
559
  }
526
- if (/^\d+$/.test(trimmed)) {
527
- return parseInt(trimmed, 10);
528
- }
529
560
  return null;
530
561
  }
531
562
  async function promptBudget(suggested, minimumCents) {
532
563
  const value = await input({
533
- message: `Max budget in cents (e.g. ${suggested} = ${formatCents2(suggested)})`,
534
- default: String(suggested),
564
+ message: `Max budget in USD`,
565
+ default: formatUsd(suggested),
535
566
  validate: (v) => {
536
- const n = parseBudgetInput(v);
567
+ const n = parseBudgetUsdInput(v);
537
568
  if (n === null || n <= 0) {
538
- return "Enter cents like 1540 or dollars like 15.40";
569
+ return "Enter a USD amount like 48 or 48.17";
539
570
  }
540
571
  if (n < minimumCents) {
541
- return `Max budget must be at least the current product price ${formatCents2(minimumCents)}`;
572
+ return `Max budget must be at least $${formatUsd(minimumCents)}`;
542
573
  }
543
574
  return true;
544
575
  }
545
576
  });
546
- const parsed = parseBudgetInput(value);
577
+ const parsed = parseBudgetUsdInput(value);
547
578
  if (parsed === null) {
548
579
  throw new Error("Invalid budget input");
549
580
  }
@@ -679,6 +710,16 @@ async function ensureTempoReady() {
679
710
  function formatUsdAmount(cents) {
680
711
  return (cents / 100).toFixed(2);
681
712
  }
713
+ function sleep(ms) {
714
+ return new Promise((resolve) => setTimeout(resolve, ms));
715
+ }
716
+ function getSelectedVariantUnitPriceCents(selectedVariants) {
717
+ const highestVariantPrice = selectedVariants?.reduce((highest, variant) => {
718
+ const variantPriceCents = typeof variant.price === "number" ? Math.round(variant.price * 100) : 0;
719
+ return Math.max(highest, variantPriceCents);
720
+ }, 0) ?? 0;
721
+ return highestVariantPrice > 0 ? highestVariantPrice : void 0;
722
+ }
682
723
  async function ensureSufficientBalance(requiredCents, wallet) {
683
724
  const required = Number.parseFloat(formatUsdAmount(requiredCents));
684
725
  let available = parseTempoBalance(wallet.balance?.available);
@@ -686,6 +727,7 @@ async function ensureSufficientBalance(requiredCents, wallet) {
686
727
  return wallet;
687
728
  }
688
729
  console.log();
730
+ console.log(" \x1B[1m\u{1F4B3} Balance Check\x1B[0m");
689
731
  console.log(
690
732
  ` Tempo wallet balance is too low for this order: have ${formatUsdcNumber(available)} USDC, need ${formatUsdAmount(requiredCents)} USDC.`
691
733
  );
@@ -700,17 +742,23 @@ async function ensureSufficientBalance(requiredCents, wallet) {
700
742
  if (fundCode !== 0) {
701
743
  throw new Error("Tempo wallet funding was cancelled.");
702
744
  }
703
- const refreshed = await ensureTempoReady();
704
- available = parseTempoBalance(refreshed.balance?.available);
705
- if (available < required) {
706
- throw new Error(
707
- `Still not enough USDC after funding: have ${formatUsdcNumber(available)}, need ${formatUsdAmount(requiredCents)}.`
708
- );
745
+ for (let attempt = 0; attempt < 6; attempt += 1) {
746
+ const refreshed = await ensureTempoReady();
747
+ available = parseTempoBalance(refreshed.balance?.available);
748
+ if (available >= required) {
749
+ console.log(
750
+ ` Funding complete. New balance: ${formatUsdcNumber(available)} ${refreshed.balance?.symbol ?? "USDC"}`
751
+ );
752
+ return refreshed;
753
+ }
754
+ if (attempt < 5) {
755
+ console.log(" Waiting for updated wallet balance...");
756
+ await sleep(3e3);
757
+ }
709
758
  }
710
- console.log(
711
- ` Funding complete. New balance: ${formatUsdcNumber(available)} ${refreshed.balance?.symbol ?? "USDC"}`
759
+ throw new Error(
760
+ `Still not enough USDC after funding: have ${formatUsdcNumber(available)}, need ${formatUsdAmount(requiredCents)}.`
712
761
  );
713
- return refreshed;
714
762
  }
715
763
  async function createPurchaseWithTempo(body) {
716
764
  const totalChargeCents = body.max_budget + SERVICE_FEE_CENTS;
@@ -750,17 +798,35 @@ async function createPurchaseWithTempo(body) {
750
798
  async function buy(urlArg) {
751
799
  try {
752
800
  console.log();
753
- console.log(" \x1B[1magentmall\x1B[0m \u2014 Buy anything with a URL");
801
+ console.log(" \x1B[1m\u{1F6D2} agentmall\x1B[0m \u2014 Buy anything with a URL");
754
802
  console.log();
755
803
  const url = urlArg ?? await promptProductUrl();
756
804
  const client = new AgentMall();
757
805
  console.log(" Looking up product...");
758
806
  const product = await client.products.lookup(url);
807
+ displayProduct(product);
759
808
  const quantity = await promptQuantity();
760
- const suggestedMaxBudget = calculateSuggestedBudget(product, quantity);
761
- const minimumBudget = calculateMinimumBudget(product, quantity);
762
- displayProduct(product, quantity);
763
- const variantSelections = product.variants?.length ? await promptVariants(product.variants) : void 0;
809
+ 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
813
+ );
814
+ const suggestedMaxBudget = calculateSuggestedBudget(
815
+ product,
816
+ quantity,
817
+ selectedVariantUnitPriceCents
818
+ );
819
+ const minimumBudget = calculateMinimumBudget(
820
+ product,
821
+ quantity,
822
+ selectedVariantUnitPriceCents
823
+ );
824
+ displayCheckoutEstimate(
825
+ product,
826
+ quantity,
827
+ variantSelections,
828
+ selectedVariantUnitPriceCents
829
+ );
764
830
  const address = await promptAddress();
765
831
  const maxBudget = await promptBudget(suggestedMaxBudget, minimumBudget);
766
832
  const { input: input2 } = await import("@inquirer/prompts");
@@ -861,7 +927,7 @@ async function refund(purchaseId) {
861
927
  }
862
928
  async function onboard() {
863
929
  console.log();
864
- console.log(" \x1B[1magentmall onboarding\x1B[0m");
930
+ console.log(" \x1B[1m\u{1F511} agentmall onboarding\x1B[0m");
865
931
  console.log();
866
932
  const wallet = await ensureTempoReady();
867
933
  console.log();
@@ -927,15 +993,15 @@ async function main() {
927
993
  }
928
994
  function printHelp() {
929
995
  console.log(`
930
- \x1B[1magentmall\x1B[0m \u2014 Buy anything with a URL
996
+ \x1B[1m\u{1F6D2} agentmall\x1B[0m \u2014 Buy anything with a URL
931
997
 
932
998
  Usage:
933
- agentmall onboard Log in to Tempo and show wallet funding steps
934
- agentmall [url] Buy a product (interactive)
935
- agentmall buy <url> Buy a product
936
- agentmall status <id> Check order status using the saved buyer token
937
- agentmall refund <id> Check refund status using the saved buyer token
938
- agentmall help Show this help
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
939
1005
  `);
940
1006
  }
941
1007
  main();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentmall",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
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",