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.
- package/dist/cli.js +365 -183
- 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
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
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
|
-
|
|
313
|
-
|
|
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
|
-
|
|
435
|
+
kv("Price", formatCents(product.price), { color: c.green });
|
|
317
436
|
}
|
|
318
437
|
if (product.variants?.length) {
|
|
319
|
-
|
|
438
|
+
gap();
|
|
320
439
|
for (const v of product.variants) {
|
|
321
|
-
const label = v.label ? `${v.label}
|
|
322
|
-
|
|
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
|
-
|
|
339
|
-
console.log(" \x1B[1m\u{1F4B8} Checkout Estimate\x1B[0m");
|
|
458
|
+
section("Checkout Estimate");
|
|
340
459
|
if (lineListPrice && product.discountPercent) {
|
|
341
|
-
|
|
342
|
-
|
|
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
|
-
|
|
465
|
+
kv("Price", formatCents(linePrice), { color: c.green });
|
|
346
466
|
}
|
|
347
|
-
|
|
467
|
+
kv("Quantity", `${quantityClamped}`);
|
|
348
468
|
if (quantityClamped > 1) {
|
|
349
|
-
|
|
469
|
+
kv("Unit price", formatCents(unitPriceCents), { dim: true });
|
|
350
470
|
}
|
|
351
471
|
if (variantSelections?.length) {
|
|
352
472
|
for (const variant of variantSelections) {
|
|
353
|
-
|
|
473
|
+
kv(variant.label, variant.value);
|
|
354
474
|
}
|
|
355
475
|
}
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
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
|
-
|
|
363
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
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
|
-
|
|
516
|
+
gap();
|
|
517
|
+
const lines = [];
|
|
399
518
|
if (order.status === "failed") {
|
|
400
|
-
|
|
519
|
+
lines.push(`${c.red}\u2716 Order failed during submission${c.reset}`);
|
|
401
520
|
} else if (order.deduplicated) {
|
|
402
|
-
|
|
521
|
+
lines.push(`${c.yellow}\u267B Reused existing order${c.reset}`);
|
|
403
522
|
} else {
|
|
404
|
-
|
|
523
|
+
lines.push(`${c.green}\u2714 Order placed${c.reset}`);
|
|
405
524
|
}
|
|
406
|
-
|
|
407
|
-
|
|
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
|
-
|
|
529
|
+
lines.push("");
|
|
530
|
+
lines.push(`${c.dim}\u{1F510} Saved buyer token for status checks${c.reset}`);
|
|
410
531
|
}
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
order.status === "failed" ?
|
|
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
|
-
|
|
416
|
-
|
|
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
|
-
|
|
420
|
-
|
|
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
|
-
|
|
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
|
-
|
|
554
|
+
kv("Final total", formatCents(purchase.finalTotal));
|
|
430
555
|
}
|
|
431
556
|
if (purchase.failureReason) {
|
|
432
|
-
|
|
433
|
-
|
|
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
|
-
|
|
563
|
+
kv("Shipping", purchase.deliveryMethod);
|
|
438
564
|
}
|
|
439
|
-
|
|
440
|
-
|
|
565
|
+
kv("Created", new Date(purchase.createdAt).toLocaleString(), { dim: true });
|
|
566
|
+
gap();
|
|
441
567
|
}
|
|
442
568
|
function displayRefund(refund2) {
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
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
|
-
|
|
574
|
+
kv("TX", refund2.txHash, { dim: true });
|
|
450
575
|
}
|
|
451
|
-
|
|
576
|
+
kv("Created", new Date(refund2.createdAt).toLocaleString(), { dim: true });
|
|
452
577
|
if (refund2.processedAt) {
|
|
453
|
-
|
|
578
|
+
kv("Processed", new Date(refund2.processedAt).toLocaleString(), {
|
|
579
|
+
dim: true
|
|
580
|
+
});
|
|
454
581
|
}
|
|
455
|
-
|
|
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", [
|
|
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
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
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
|
-
|
|
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
|
-
|
|
752
|
-
|
|
753
|
-
|
|
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
|
-
|
|
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
|
-
|
|
772
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
823
|
-
|
|
824
|
-
console.log();
|
|
934
|
+
banner();
|
|
935
|
+
gap();
|
|
825
936
|
const url = urlArg ?? await promptProductUrl();
|
|
826
937
|
const client = new AgentMall();
|
|
827
|
-
const product = await
|
|
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(
|
|
835
|
-
|
|
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
|
-
|
|
864
|
-
|
|
865
|
-
|
|
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
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
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
|
-
|
|
1003
|
+
muted("Cancelled.");
|
|
881
1004
|
return;
|
|
882
1005
|
}
|
|
883
1006
|
const totalChargeCents = maxBudget + SERVICE_FEE_CENTS;
|
|
884
|
-
const wallet = await
|
|
885
|
-
|
|
886
|
-
await ensureTempoReady()
|
|
887
|
-
);
|
|
1007
|
+
const wallet = await spin("Connecting wallet", () => ensureTempoReady());
|
|
1008
|
+
await ensureSufficientBalance(totalChargeCents, wallet);
|
|
888
1009
|
if (wallet.wallet) {
|
|
889
|
-
const
|
|
890
|
-
|
|
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
|
-
|
|
893
|
-
|
|
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
|
-
|
|
1024
|
+
gap();
|
|
900
1025
|
if (error instanceof ValidationError) {
|
|
901
|
-
|
|
1026
|
+
errorMsg("Validation error", error.message);
|
|
902
1027
|
process.exitCode = 1;
|
|
903
1028
|
return;
|
|
904
1029
|
}
|
|
905
1030
|
if (error instanceof ConflictError) {
|
|
906
|
-
|
|
1031
|
+
errorMsg("Conflict", error.message);
|
|
907
1032
|
process.exitCode = 1;
|
|
908
1033
|
return;
|
|
909
1034
|
}
|
|
910
1035
|
if (error instanceof RateLimitError) {
|
|
911
|
-
|
|
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
|
-
|
|
1044
|
+
errorMsg("Request failed", error.message);
|
|
917
1045
|
process.exitCode = 1;
|
|
918
1046
|
return;
|
|
919
1047
|
}
|
|
920
1048
|
if (error instanceof Error) {
|
|
921
|
-
|
|
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(
|
|
1062
|
+
throw new Error(
|
|
1063
|
+
"No saved buyer token found for this order on this machine."
|
|
1064
|
+
);
|
|
934
1065
|
}
|
|
935
|
-
const result = await
|
|
936
|
-
|
|
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(
|
|
1078
|
+
throw new Error(
|
|
1079
|
+
"No saved buyer token found for this order on this machine."
|
|
1080
|
+
);
|
|
947
1081
|
}
|
|
948
|
-
const result = await
|
|
949
|
-
|
|
950
|
-
|
|
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
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
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
|
-
|
|
1100
|
+
kv("Address", wallet.wallet);
|
|
962
1101
|
}
|
|
963
1102
|
if (wallet.balance?.available) {
|
|
964
|
-
|
|
1103
|
+
kv(
|
|
1104
|
+
"Balance",
|
|
1105
|
+
`${wallet.balance.available} ${wallet.balance?.symbol ?? "USDC"}`,
|
|
1106
|
+
{ color: c.green }
|
|
1107
|
+
);
|
|
965
1108
|
}
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
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
|
-
\
|
|
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
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
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();
|