agentmall 0.1.8 → 0.1.10

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 +106 -44
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -288,35 +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();
306
307
  console.log(" \x1B[1m\u{1F4E6} Product\x1B[0m");
307
308
  console.log(` \x1B[1m${product.title}\x1B[0m`);
308
309
  console.log(` ${product.retailer} \u2014 ${product.availability}`);
309
310
  console.log();
310
- if (lineListPrice && product.discountPercent) {
311
+ if (product.listPrice && product.discountPercent) {
311
312
  console.log(
312
- ` 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`
313
314
  );
314
315
  } else {
315
- console.log(` Price: \x1B[32m${formatCents(linePrice)}\x1B[0m`);
316
- }
317
- if (quantityClamped > 1) {
318
- console.log(` Quantity: ${quantityClamped}`);
319
- console.log(` Unit price: ${formatCents(product.price)}`);
316
+ console.log(` Price: \x1B[32m${formatCents(product.price)}\x1B[0m`);
320
317
  }
321
318
  if (product.variants?.length) {
322
319
  console.log();
@@ -326,7 +323,36 @@ function displayProduct(product, quantity = 1) {
326
323
  }
327
324
  }
328
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();
329
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
+ }
330
356
  console.log(
331
357
  ` Suggested budget: \x1B[1m${formatCents(suggestedBudget)}\x1B[0m (includes 15% buffer for tax and an $8 minimum shipping buffer)`
332
358
  );
@@ -469,7 +495,12 @@ async function promptVariants(variants) {
469
495
  value: o.value
470
496
  }))
471
497
  });
472
- 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
+ });
473
504
  }
474
505
  return selections;
475
506
  }
@@ -517,36 +548,33 @@ async function promptQuantity() {
517
548
  async function promptConfirm(message) {
518
549
  return confirm({ message });
519
550
  }
520
- function formatCents2(cents) {
521
- return `$${(cents / 100).toFixed(2)}`;
551
+ function formatUsd(cents) {
552
+ return (cents / 100).toFixed(2);
522
553
  }
523
- function parseBudgetInput(value) {
524
- const trimmed = value.trim();
554
+ function parseBudgetUsdInput(value) {
555
+ const trimmed = value.trim().replace(/[$,\s]/g, "");
525
556
  if (!trimmed) return null;
526
- if (/^\d+\.\d{1,2}$/.test(trimmed)) {
557
+ if (/^\d+(\.\d{0,2})?$/.test(trimmed)) {
527
558
  return Math.round(Number(trimmed) * 100);
528
559
  }
529
- if (/^\d+$/.test(trimmed)) {
530
- return parseInt(trimmed, 10);
531
- }
532
560
  return null;
533
561
  }
534
562
  async function promptBudget(suggested, minimumCents) {
535
563
  const value = await input({
536
- message: `Max budget in cents (e.g. ${suggested} = ${formatCents2(suggested)})`,
537
- default: String(suggested),
564
+ message: `Max budget in USD`,
565
+ default: formatUsd(suggested),
538
566
  validate: (v) => {
539
- const n = parseBudgetInput(v);
567
+ const n = parseBudgetUsdInput(v);
540
568
  if (n === null || n <= 0) {
541
- return "Enter cents like 1540 or dollars like 15.40";
569
+ return "Enter a USD amount like 48 or 48.17";
542
570
  }
543
571
  if (n < minimumCents) {
544
- return `Max budget must be at least the current product price ${formatCents2(minimumCents)}`;
572
+ return `Max budget must be at least $${formatUsd(minimumCents)}`;
545
573
  }
546
574
  return true;
547
575
  }
548
576
  });
549
- const parsed = parseBudgetInput(value);
577
+ const parsed = parseBudgetUsdInput(value);
550
578
  if (parsed === null) {
551
579
  throw new Error("Invalid budget input");
552
580
  }
@@ -682,6 +710,16 @@ async function ensureTempoReady() {
682
710
  function formatUsdAmount(cents) {
683
711
  return (cents / 100).toFixed(2);
684
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
+ }
685
723
  async function ensureSufficientBalance(requiredCents, wallet) {
686
724
  const required = Number.parseFloat(formatUsdAmount(requiredCents));
687
725
  let available = parseTempoBalance(wallet.balance?.available);
@@ -704,17 +742,23 @@ async function ensureSufficientBalance(requiredCents, wallet) {
704
742
  if (fundCode !== 0) {
705
743
  throw new Error("Tempo wallet funding was cancelled.");
706
744
  }
707
- const refreshed = await ensureTempoReady();
708
- available = parseTempoBalance(refreshed.balance?.available);
709
- if (available < required) {
710
- throw new Error(
711
- `Still not enough USDC after funding: have ${formatUsdcNumber(available)}, need ${formatUsdAmount(requiredCents)}.`
712
- );
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
+ }
713
758
  }
714
- console.log(
715
- ` 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)}.`
716
761
  );
717
- return refreshed;
718
762
  }
719
763
  async function createPurchaseWithTempo(body) {
720
764
  const totalChargeCents = body.max_budget + SERVICE_FEE_CENTS;
@@ -760,11 +804,29 @@ async function buy(urlArg) {
760
804
  const client = new AgentMall();
761
805
  console.log(" Looking up product...");
762
806
  const product = await client.products.lookup(url);
807
+ displayProduct(product);
763
808
  const quantity = await promptQuantity();
764
- const suggestedMaxBudget = calculateSuggestedBudget(product, quantity);
765
- const minimumBudget = calculateMinimumBudget(product, quantity);
766
- displayProduct(product, quantity);
767
- 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
+ );
768
830
  const address = await promptAddress();
769
831
  const maxBudget = await promptBudget(suggestedMaxBudget, minimumBudget);
770
832
  const { input: input2 } = await import("@inquirer/prompts");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentmall",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
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",