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.
- package/dist/cli.js +126 -60
- 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
|
|
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
|
-
|
|
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
|
|
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 (
|
|
311
|
+
if (product.listPrice && product.discountPercent) {
|
|
310
312
|
console.log(
|
|
311
|
-
` Price: \x1B[32m${formatCents(
|
|
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(
|
|
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[
|
|
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\
|
|
402
|
+
console.log(` \x1B[31m\u274C Order failed during submission.\x1B[0m`);
|
|
374
403
|
} else if (order.deduplicated) {
|
|
375
|
-
console.log(` \x1B[33m\
|
|
404
|
+
console.log(` \x1B[33m\u267B\uFE0F Reused existing order.\x1B[0m`);
|
|
376
405
|
} else {
|
|
377
|
-
console.log(` \x1B[32m\
|
|
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[
|
|
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[
|
|
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
|
-
|
|
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
|
|
518
|
-
return
|
|
551
|
+
function formatUsd(cents) {
|
|
552
|
+
return (cents / 100).toFixed(2);
|
|
519
553
|
}
|
|
520
|
-
function
|
|
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
|
|
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
|
|
534
|
-
default:
|
|
564
|
+
message: `Max budget in USD`,
|
|
565
|
+
default: formatUsd(suggested),
|
|
535
566
|
validate: (v) => {
|
|
536
|
-
const n =
|
|
567
|
+
const n = parseBudgetUsdInput(v);
|
|
537
568
|
if (n === null || n <= 0) {
|
|
538
|
-
return "Enter
|
|
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
|
|
572
|
+
return `Max budget must be at least $${formatUsd(minimumCents)}`;
|
|
542
573
|
}
|
|
543
574
|
return true;
|
|
544
575
|
}
|
|
545
576
|
});
|
|
546
|
-
const parsed =
|
|
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
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
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
|
-
|
|
711
|
-
`
|
|
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[
|
|
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
|
|
761
|
-
const
|
|
762
|
-
|
|
763
|
-
|
|
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[
|
|
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[
|
|
996
|
+
\x1B[1m\u{1F6D2} agentmall\x1B[0m \u2014 Buy anything with a URL
|
|
931
997
|
|
|
932
998
|
Usage:
|
|
933
|
-
agentmall onboard
|
|
934
|
-
agentmall [url]
|
|
935
|
-
agentmall buy <url>
|
|
936
|
-
agentmall status <id>
|
|
937
|
-
agentmall refund <id>
|
|
938
|
-
agentmall 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();
|