agentmall 0.1.2 → 0.1.5

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 CHANGED
@@ -1,5 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ // src/cli/index.ts
4
+ import { spawn } from "child_process";
5
+
3
6
  // src/constants.ts
4
7
  var BASE_URL = "https://api.agentmall.sh";
5
8
  var SERVICE_FEE_CENTS = 150;
@@ -65,7 +68,6 @@ function createIdempotencyKey() {
65
68
  var AgentMall = class {
66
69
  baseUrl;
67
70
  apiSecret;
68
- account;
69
71
  _fetch;
70
72
  products;
71
73
  purchases;
@@ -74,7 +76,6 @@ var AgentMall = class {
74
76
  constructor(config = {}) {
75
77
  this.baseUrl = (config.baseUrl ?? BASE_URL).replace(/\/$/, "");
76
78
  this.apiSecret = config.apiSecret;
77
- this.account = config.account;
78
79
  this._fetch = config.fetch ?? globalThis.fetch.bind(globalThis);
79
80
  this.products = new AgentMallProducts(this);
80
81
  this.purchases = new AgentMallPurchases(this);
@@ -82,8 +83,8 @@ var AgentMall = class {
82
83
  this.refunds = new AgentMallRefunds(this);
83
84
  }
84
85
  /** @internal */
85
- getAccount() {
86
- return this.account;
86
+ hasApiSecret() {
87
+ return Boolean(this.apiSecret);
87
88
  }
88
89
  /** @internal */
89
90
  async request(method, path, options) {
@@ -165,28 +166,19 @@ var AgentMallPurchases = class {
165
166
  }
166
167
  });
167
168
  }
168
- /** Get a single purchase by ID using either operator auth or the payer wallet. */
169
+ /** Get a single purchase by ID using either operator auth or a buyer token. */
169
170
  async get(id, options) {
170
- const account = options?.account ?? this.client.getAccount();
171
- if (account) {
172
- const challenge = await this.client.request(
173
- "POST",
174
- "/api/purchases/status/challenge",
175
- {
176
- body: { id }
177
- }
178
- );
179
- const signature = await account.signMessage({
180
- message: challenge.message
181
- });
171
+ if (options?.buyerToken) {
182
172
  return this.client.request("POST", "/api/purchases/status", {
183
173
  body: {
184
174
  id,
185
- challenge: challenge.challenge,
186
- signature
175
+ buyer_token: options.buyerToken
187
176
  }
188
177
  });
189
178
  }
179
+ if (!this.client.hasApiSecret()) {
180
+ throw new Error("buyerToken or apiSecret is required for this endpoint");
181
+ }
190
182
  return this.client.request("GET", "/api/purchases", {
191
183
  auth: true,
192
184
  params: { id }
@@ -234,6 +226,33 @@ var AgentMallRefunds = class {
234
226
  params: { id }
235
227
  });
236
228
  }
229
+ /** Get refund status for a purchase using either operator auth or a buyer token. */
230
+ async getByPurchase(purchaseId, options) {
231
+ if (options?.buyerToken) {
232
+ return this.client.request("POST", "/api/refunds/status", {
233
+ body: {
234
+ purchase_id: purchaseId,
235
+ buyer_token: options.buyerToken
236
+ }
237
+ });
238
+ }
239
+ if (!this.client.hasApiSecret()) {
240
+ throw new Error("buyerToken or apiSecret is required for this endpoint");
241
+ }
242
+ const result = await this.client.request(
243
+ "GET",
244
+ "/api/refunds",
245
+ {
246
+ auth: true,
247
+ params: { purchase_id: purchaseId }
248
+ }
249
+ );
250
+ const refund2 = result.refunds[0];
251
+ if (!refund2) {
252
+ throw new AgentMallError(404, { error: "Not found" });
253
+ }
254
+ return refund2;
255
+ }
237
256
  /** List refunds. Requires apiSecret. */
238
257
  async list(filters) {
239
258
  const result = await this.client.request(
@@ -245,45 +264,58 @@ var AgentMallRefunds = class {
245
264
  }
246
265
  };
247
266
 
248
- // src/purchase.ts
249
- async function purchase(config) {
250
- const { account, baseUrl, ...body } = config;
251
- let mppxClient;
252
- try {
253
- mppxClient = await import("mppx/client");
254
- } catch {
255
- throw new Error("mppx is required for purchases. Install it: npm install mppx");
256
- }
257
- const Mppx = mppxClient.Mppx;
258
- const tempo = mppxClient.tempo;
259
- if (!Mppx || !tempo) {
260
- throw new Error("mppx is required for purchases. Install it: npm install mppx");
261
- }
262
- const mppx = Mppx.create({
263
- methods: [tempo({ account })]
264
- });
265
- const client = new AgentMall({
266
- baseUrl: baseUrl ?? BASE_URL,
267
- fetch: mppx.fetch.bind(mppx)
268
- });
269
- return client.purchases.create(body);
270
- }
271
-
272
267
  // src/cli/display.ts
268
+ var FAILURE_MESSAGES = {
269
+ budget_exceeded: "Actual total exceeded your maximum budget.",
270
+ out_of_stock: "Item is out of stock.",
271
+ product_not_found: "Product is no longer available.",
272
+ product_unavailable: "Product cannot be purchased right now.",
273
+ variant_required: "A required size, color, or variant must be selected.",
274
+ variant_unavailable: "Selected variant is unavailable.",
275
+ quantity_unavailable: "Requested quantity is unavailable.",
276
+ quantity_limit: "Retailer quantity limit exceeded.",
277
+ invalid_url: "Product URL is invalid.",
278
+ invalid_address: "Shipping address could not be validated.",
279
+ shipping_unavailable: "Item cannot be shipped to this address.",
280
+ retailer_unavailable: "Retailer is temporarily unavailable.",
281
+ unsupported_retailer: "Retailer is not supported.",
282
+ validation_failed: "Order request failed validation.",
283
+ cancelled: "Order was cancelled."
284
+ };
273
285
  function formatCents(cents) {
274
286
  return `$${(cents / 100).toFixed(2)}`;
275
287
  }
276
- function displayProduct(product) {
288
+ function getBudgetBasePrice(product) {
289
+ return product.listPrice ?? product.price;
290
+ }
291
+ function calculateSuggestedBudget(product, quantity = 1) {
292
+ const quantityClamped = Math.max(1, quantity);
293
+ const basePriceCents = getBudgetBasePrice(product) * quantityClamped;
294
+ const variableBufferCents = Math.round(basePriceCents * 0.15);
295
+ return basePriceCents + Math.max(variableBufferCents, 800);
296
+ }
297
+ function calculateMinimumBudget(product, quantity = 1) {
298
+ return product.price * Math.max(1, quantity);
299
+ }
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;
277
305
  console.log();
278
306
  console.log(` \x1B[1m${product.title}\x1B[0m`);
279
307
  console.log(` ${product.retailer} \u2014 ${product.availability}`);
280
308
  console.log();
281
- if (product.listPrice && product.discountPercent) {
309
+ if (lineListPrice && product.discountPercent) {
282
310
  console.log(
283
- ` Price: \x1B[32m${formatCents(product.price)}\x1B[0m \x1B[2m\x1B[9m${formatCents(product.listPrice)}\x1B[0m \x1B[33m-${product.discountPercent}%\x1B[0m`
311
+ ` Price: \x1B[32m${formatCents(linePrice)}\x1B[0m \x1B[2m\x1B[9m${formatCents(lineListPrice)}\x1B[0m \x1B[33m-${product.discountPercent}%\x1B[0m`
284
312
  );
285
313
  } else {
286
- console.log(` Price: \x1B[32m${formatCents(product.price)}\x1B[0m`);
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)}`);
287
319
  }
288
320
  if (product.variants?.length) {
289
321
  console.log();
@@ -294,13 +326,19 @@ function displayProduct(product) {
294
326
  }
295
327
  console.log();
296
328
  console.log(
297
- ` Suggested budget: \x1B[1m${formatCents(product.suggestedMaxBudget)}\x1B[0m (includes 15% buffer for tax & shipping)`
329
+ ` Suggested budget: \x1B[1m${formatCents(suggestedBudget)}\x1B[0m (includes 15% buffer for tax and an $8 minimum shipping buffer)`
298
330
  );
299
331
  console.log(
300
332
  ` Service fee: ${formatCents(SERVICE_FEE_CENTS)}`
301
333
  );
302
334
  console.log(
303
- ` Total charge: \x1B[1m${formatCents(product.suggestedMaxBudget + SERVICE_FEE_CENTS)}\x1B[0m USDC`
335
+ ` Total charge: \x1B[1m${formatCents(suggestedBudget + SERVICE_FEE_CENTS)}\x1B[0m USDC`
336
+ );
337
+ console.log(
338
+ ` Any unused amount from your max budget is refunded automatically after checkout.`
339
+ );
340
+ console.log(
341
+ ` The buffer covers unforeseen tax, shipping, and retailer price changes at final checkout.`
304
342
  );
305
343
  if (product.cached) {
306
344
  console.log(` \x1B[33m\u26A0 Price from cache \u2014 may not reflect current listing\x1B[0m`);
@@ -340,6 +378,9 @@ function displayOrderResult(order) {
340
378
  }
341
379
  console.log(` ID: \x1B[1m${order.id}\x1B[0m`);
342
380
  console.log(` Status: ${order.status}`);
381
+ if (order.buyerToken) {
382
+ console.log(" Saved local access token for status and refund checks.");
383
+ }
343
384
  console.log();
344
385
  console.log(
345
386
  order.status === "failed" ? " Check status for failure details and refund progress:" : " Check status in 5-10 minutes:"
@@ -347,26 +388,43 @@ function displayOrderResult(order) {
347
388
  console.log(` npx agentmall status ${order.id}`);
348
389
  console.log();
349
390
  }
350
- function displayStatus(purchase2) {
391
+ function displayStatus(purchase) {
351
392
  console.log();
352
- console.log(` \x1B[1mOrder ${purchase2.id}\x1B[0m`);
353
- console.log(` Status: ${purchase2.status}`);
354
- if (purchase2.items?.length) {
355
- for (const item of purchase2.items) {
393
+ console.log(` \x1B[1mOrder ${purchase.id}\x1B[0m`);
394
+ console.log(` Status: ${purchase.status}`);
395
+ if (purchase.items?.length) {
396
+ for (const item of purchase.items) {
356
397
  const price = item.price ? ` \u2014 ${formatCents(item.price)}` : "";
357
398
  console.log(` ${item.quantity}x ${item.title ?? item.productRef}${price}`);
358
399
  }
359
400
  }
360
- if (purchase2.finalTotal) {
361
- console.log(` Final total: ${formatCents(purchase2.finalTotal)}`);
401
+ if (purchase.finalTotal) {
402
+ console.log(` Final total: ${formatCents(purchase.finalTotal)}`);
403
+ }
404
+ if (purchase.failureReason) {
405
+ console.log(
406
+ ` \x1B[31mFailure: ${FAILURE_MESSAGES[purchase.failureReason] ?? purchase.failureReason}\x1B[0m`
407
+ );
362
408
  }
363
- if (purchase2.failureReason) {
364
- console.log(` \x1B[31mFailure: ${purchase2.failureReason}\x1B[0m`);
409
+ if (purchase.deliveryMethod) {
410
+ console.log(` Shipping: ${purchase.deliveryMethod}`);
365
411
  }
366
- if (purchase2.deliveryMethod) {
367
- console.log(` Shipping: ${purchase2.deliveryMethod}`);
412
+ console.log(` Created: ${new Date(purchase.createdAt).toLocaleString()}`);
413
+ console.log();
414
+ }
415
+ function displayRefund(refund2) {
416
+ console.log();
417
+ console.log(` \x1B[1mRefund ${refund2.id}\x1B[0m`);
418
+ console.log(` Status: ${refund2.status}`);
419
+ console.log(` Amount: ${formatCents(refund2.amountCents)}`);
420
+ console.log(` Reason: ${refund2.reason}`);
421
+ if (refund2.txHash) {
422
+ console.log(` TX: ${refund2.txHash}`);
423
+ }
424
+ console.log(` Created: ${new Date(refund2.createdAt).toLocaleString()}`);
425
+ if (refund2.processedAt) {
426
+ console.log(` Processed: ${new Date(refund2.processedAt).toLocaleString()}`);
368
427
  }
369
- console.log(` Created: ${new Date(purchase2.createdAt).toLocaleString()}`);
370
428
  console.log();
371
429
  }
372
430
 
@@ -439,37 +497,198 @@ async function promptAddress() {
439
497
  country: "US"
440
498
  };
441
499
  }
500
+ async function promptQuantity() {
501
+ const value = await input({
502
+ message: "Quantity",
503
+ default: "1",
504
+ validate: (v) => {
505
+ const n = parseInt(v.trim(), 10);
506
+ if (!Number.isInteger(n) || n <= 0) {
507
+ return "Enter a whole number greater than 0";
508
+ }
509
+ return true;
510
+ }
511
+ });
512
+ return parseInt(value.trim(), 10);
513
+ }
442
514
  async function promptConfirm(message) {
443
515
  return confirm({ message });
444
516
  }
445
- async function promptBudget(suggested) {
517
+ function formatCents2(cents) {
518
+ return `$${(cents / 100).toFixed(2)}`;
519
+ }
520
+ function parseBudgetInput(value) {
521
+ const trimmed = value.trim();
522
+ if (!trimmed) return null;
523
+ if (/^\d+\.\d{1,2}$/.test(trimmed)) {
524
+ return Math.round(Number(trimmed) * 100);
525
+ }
526
+ if (/^\d+$/.test(trimmed)) {
527
+ return parseInt(trimmed, 10);
528
+ }
529
+ return null;
530
+ }
531
+ async function promptBudget(suggested, minimumCents) {
446
532
  const value = await input({
447
- message: `Max budget in cents (suggested: ${suggested})`,
533
+ message: `Max budget in cents (e.g. ${suggested} = ${formatCents2(suggested)})`,
448
534
  default: String(suggested),
449
535
  validate: (v) => {
450
- const n = parseInt(v, 10);
451
- if (isNaN(n) || n <= 0) return "Must be a positive integer";
536
+ const n = parseBudgetInput(v);
537
+ if (n === null || n <= 0) {
538
+ return "Enter cents like 1540 or dollars like 15.40";
539
+ }
540
+ if (n < minimumCents) {
541
+ return `Max budget must be at least the current product price ${formatCents2(minimumCents)}`;
542
+ }
452
543
  return true;
453
544
  }
454
545
  });
455
- return parseInt(value, 10);
546
+ const parsed = parseBudgetInput(value);
547
+ if (parsed === null) {
548
+ throw new Error("Invalid budget input");
549
+ }
550
+ return parsed;
551
+ }
552
+
553
+ // src/cli/tokenStore.ts
554
+ import { chmod, mkdir, readFile, writeFile } from "fs/promises";
555
+ import { homedir } from "os";
556
+ import { dirname, join } from "path";
557
+ var STORE_PATH = join(homedir(), ".agentmall", "tokens.json");
558
+ function emptyStore() {
559
+ return {
560
+ version: 1,
561
+ purchases: {}
562
+ };
563
+ }
564
+ async function readStore() {
565
+ try {
566
+ const raw = await readFile(STORE_PATH, "utf8");
567
+ const parsed = JSON.parse(raw);
568
+ return {
569
+ version: 1,
570
+ purchases: parsed.purchases ?? {}
571
+ };
572
+ } catch {
573
+ return emptyStore();
574
+ }
575
+ }
576
+ async function writeStore(store) {
577
+ await mkdir(dirname(STORE_PATH), { recursive: true });
578
+ await writeFile(STORE_PATH, JSON.stringify(store, null, 2), "utf8");
579
+ try {
580
+ await chmod(STORE_PATH, 384);
581
+ } catch {
582
+ }
583
+ }
584
+ async function saveBuyerToken(purchaseId, buyerToken) {
585
+ const store = await readStore();
586
+ store.purchases[purchaseId] = {
587
+ buyerToken,
588
+ savedAt: Date.now()
589
+ };
590
+ await writeStore(store);
591
+ }
592
+ async function getBuyerToken(purchaseId) {
593
+ const store = await readStore();
594
+ return store.purchases[purchaseId]?.buyerToken ?? null;
456
595
  }
457
596
 
458
597
  // src/cli/index.ts
459
598
  var EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
460
- async function resolveAccount() {
599
+ async function runCommandCapture(command2, args2) {
600
+ return await new Promise((resolve, reject) => {
601
+ const child = spawn(command2, args2, {
602
+ stdio: ["ignore", "pipe", "pipe"],
603
+ shell: process.platform === "win32"
604
+ });
605
+ let stdout = "";
606
+ let stderr = "";
607
+ child.stdout?.on("data", (chunk) => {
608
+ stdout += chunk.toString();
609
+ });
610
+ child.stderr?.on("data", (chunk) => {
611
+ stderr += chunk.toString();
612
+ });
613
+ child.on("error", reject);
614
+ child.on(
615
+ "exit",
616
+ (code) => resolve({
617
+ code: code ?? 1,
618
+ stdout,
619
+ stderr
620
+ })
621
+ );
622
+ });
623
+ }
624
+ async function runInteractiveCommand(command2, args2) {
625
+ return await new Promise((resolve, reject) => {
626
+ const child = spawn(command2, args2, {
627
+ stdio: "inherit",
628
+ shell: process.platform === "win32"
629
+ });
630
+ child.on("error", reject);
631
+ child.on("exit", (code) => resolve(code ?? 1));
632
+ });
633
+ }
634
+ async function readTempoWhoami() {
461
635
  try {
462
- const { resolveAccount: resolve } = await import("mppx/cli");
463
- return await resolve();
636
+ const result = await runCommandCapture("tempo", ["wallet", "-j", "whoami"]);
637
+ if (result.code !== 0) return null;
638
+ return JSON.parse(result.stdout);
464
639
  } catch {
465
- const key = process.env.MPPX_PRIVATE_KEY;
466
- if (!key) {
467
- console.error("\x1B[31mNo wallet found.\x1B[0m");
468
- console.error("Set MPPX_PRIVATE_KEY or run: npx mppx account create");
469
- process.exit(1);
470
- }
471
- const { privateKeyToAccount } = await import("viem/accounts");
472
- return privateKeyToAccount(key);
640
+ return null;
641
+ }
642
+ }
643
+ async function ensureTempoInstalled() {
644
+ try {
645
+ const result = await runCommandCapture("tempo", ["--version"]);
646
+ if (result.code === 0) return;
647
+ } catch {
648
+ }
649
+ console.error("\x1B[31mTempo CLI is required for purchases.\x1B[0m");
650
+ console.error("");
651
+ console.error("Install it first:");
652
+ console.error(" curl -fsSL https://tempo.xyz/install | bash");
653
+ process.exit(1);
654
+ }
655
+ async function ensureTempoReady() {
656
+ await ensureTempoInstalled();
657
+ const whoami = await readTempoWhoami();
658
+ if (whoami?.ready) return whoami;
659
+ console.log(" Tempo wallet login required...");
660
+ const loginCode = await runInteractiveCommand("tempo", ["wallet", "login"]);
661
+ if (loginCode !== 0) {
662
+ throw new Error("Tempo wallet login failed");
663
+ }
664
+ const afterLogin = await readTempoWhoami();
665
+ if (afterLogin?.ready) return afterLogin;
666
+ throw new Error("Tempo wallet is not ready after login");
667
+ }
668
+ function formatUsdAmount(cents) {
669
+ return (cents / 100).toFixed(2);
670
+ }
671
+ async function createPurchaseWithTempo(body) {
672
+ const totalChargeCents = body.max_budget + SERVICE_FEE_CENTS;
673
+ const result = await runCommandCapture("tempo", [
674
+ "request",
675
+ "-s",
676
+ "--max-spend",
677
+ formatUsdAmount(totalChargeCents),
678
+ "-X",
679
+ "POST",
680
+ "--json",
681
+ JSON.stringify(body),
682
+ `${BASE_URL}/api/purchases`
683
+ ]);
684
+ if (result.code !== 0) {
685
+ const message = result.stderr.trim() || result.stdout.trim() || "Tempo request failed";
686
+ throw new Error(message);
687
+ }
688
+ try {
689
+ return JSON.parse(result.stdout);
690
+ } catch {
691
+ throw new Error(`Unexpected response from Tempo request: ${result.stdout.trim() || "(empty)"}`);
473
692
  }
474
693
  }
475
694
  async function buy(urlArg) {
@@ -477,15 +696,17 @@ async function buy(urlArg) {
477
696
  console.log();
478
697
  console.log(" \x1B[1magentmall\x1B[0m \u2014 Buy anything with a URL");
479
698
  console.log();
480
- const account = await resolveAccount();
481
699
  const url = urlArg ?? await promptProductUrl();
482
700
  const client = new AgentMall();
483
- console.log(" Fetching product info...");
701
+ console.log(" Looking up product...");
484
702
  const product = await client.products.lookup(url);
485
- displayProduct(product);
703
+ const quantity = await promptQuantity();
704
+ const suggestedMaxBudget = calculateSuggestedBudget(product, quantity);
705
+ const minimumBudget = calculateMinimumBudget(product, quantity);
706
+ displayProduct(product, quantity);
486
707
  const variantSelections = product.variants?.length ? await promptVariants(product.variants) : void 0;
487
708
  const address = await promptAddress();
488
- const maxBudget = await promptBudget(product.suggestedMaxBudget);
709
+ const maxBudget = await promptBudget(suggestedMaxBudget, minimumBudget);
489
710
  const { input: input2 } = await import("@inquirer/prompts");
490
711
  const buyerEmail = await input2({
491
712
  message: "Email for order updates",
@@ -494,7 +715,7 @@ async function buy(urlArg) {
494
715
  const body = {
495
716
  items: [{
496
717
  product_url: url,
497
- quantity: 1,
718
+ quantity,
498
719
  ...variantSelections?.length ? { variant: variantSelections } : {}
499
720
  }],
500
721
  delivery_address: address,
@@ -503,17 +724,24 @@ async function buy(urlArg) {
503
724
  };
504
725
  displayOrderSummary(body);
505
726
  console.log(` Total charge: \x1B[1m${formatCents(maxBudget + SERVICE_FEE_CENTS)}\x1B[0m USDC on Tempo`);
727
+ console.log(" Any unused amount from your max budget is refunded automatically after checkout.");
728
+ console.log(" The buffer covers unforeseen tax, shipping, and retailer price changes at final checkout.");
506
729
  console.log();
507
730
  const proceed = await promptConfirm("Place order?");
508
731
  if (!proceed) {
509
732
  console.log(" Cancelled.");
510
733
  return;
511
734
  }
735
+ const wallet = await ensureTempoReady();
736
+ if (wallet.wallet) {
737
+ const balance = wallet.balance?.available ? `, balance ${wallet.balance.available} ${wallet.balance?.symbol ?? "USDC"}` : "";
738
+ console.log(` Paying with Tempo wallet ${wallet.wallet}${balance}`);
739
+ }
512
740
  console.log(" Placing order...");
513
- const order = await purchase({
514
- ...body,
515
- account
516
- });
741
+ const order = await createPurchaseWithTempo(body);
742
+ if (order.buyerToken) {
743
+ await saveBuyerToken(order.id, order.buyerToken);
744
+ }
517
745
  displayOrderResult(order);
518
746
  } catch (error) {
519
747
  console.log();
@@ -542,10 +770,48 @@ async function buy(urlArg) {
542
770
  }
543
771
  async function status(purchaseId) {
544
772
  const apiSecret = process.env.AGENTMALL_API_SECRET;
545
- const client = apiSecret ? new AgentMall({ apiSecret }) : new AgentMall({ account: await resolveAccount() });
546
- const result = await client.purchases.get(purchaseId);
773
+ const buyerToken = apiSecret ? null : await getBuyerToken(purchaseId);
774
+ const client = apiSecret ? new AgentMall({ apiSecret }) : new AgentMall();
775
+ if (!apiSecret && !buyerToken) {
776
+ throw new Error("No saved buyer token found for this order on this machine.");
777
+ }
778
+ const result = await client.purchases.get(
779
+ purchaseId,
780
+ buyerToken ? { buyerToken } : void 0
781
+ );
547
782
  displayStatus(result);
548
783
  }
784
+ async function refund(purchaseId) {
785
+ const apiSecret = process.env.AGENTMALL_API_SECRET;
786
+ const buyerToken = apiSecret ? null : await getBuyerToken(purchaseId);
787
+ const client = apiSecret ? new AgentMall({ apiSecret }) : new AgentMall();
788
+ if (!apiSecret && !buyerToken) {
789
+ throw new Error("No saved buyer token found for this order on this machine.");
790
+ }
791
+ const result = await client.refunds.getByPurchase(
792
+ purchaseId,
793
+ buyerToken ? { buyerToken } : void 0
794
+ );
795
+ displayRefund(result);
796
+ }
797
+ async function onboard() {
798
+ console.log();
799
+ console.log(" \x1B[1magentmall onboarding\x1B[0m");
800
+ console.log();
801
+ const wallet = await ensureTempoReady();
802
+ console.log();
803
+ if (wallet.wallet) {
804
+ console.log(` Wallet ready: ${wallet.wallet}`);
805
+ }
806
+ if (wallet.balance?.available) {
807
+ console.log(` Available balance: ${wallet.balance.available} ${wallet.balance?.symbol ?? "USDC"}`);
808
+ }
809
+ console.log(" Next steps:");
810
+ console.log(" 1. Run \x1B[1mtempo wallet whoami\x1B[0m to review your wallet");
811
+ console.log(" 2. Add funds to your Tempo wallet if needed");
812
+ console.log(" 3. Retry your purchase with \x1B[1mnpx agentmall buy <url>\x1B[0m");
813
+ console.log();
814
+ }
549
815
 
550
816
  // src/cli.ts
551
817
  var args = process.argv.slice(2);
@@ -563,6 +829,17 @@ async function main() {
563
829
  }
564
830
  await status(args[1]);
565
831
  break;
832
+ case "refund":
833
+ if (!args[1]) {
834
+ console.error("Usage: agentmall refund <purchase_id>");
835
+ process.exit(1);
836
+ }
837
+ await refund(args[1]);
838
+ break;
839
+ case "onboard":
840
+ case "setup":
841
+ await onboard();
842
+ break;
566
843
  case "help":
567
844
  case "--help":
568
845
  case "-h":
@@ -588,14 +865,12 @@ function printHelp() {
588
865
  \x1B[1magentmall\x1B[0m \u2014 Buy anything with a URL
589
866
 
590
867
  Usage:
868
+ agentmall onboard Log in to Tempo and show wallet funding steps
591
869
  agentmall [url] Buy a product (interactive)
592
870
  agentmall buy <url> Buy a product
593
- agentmall status <id> Check order status with the payer wallet
871
+ agentmall status <id> Check order status using the saved buyer token
872
+ agentmall refund <id> Check refund status using the saved buyer token
594
873
  agentmall help Show this help
595
-
596
- Environment:
597
- MPPX_PRIVATE_KEY Wallet private key for payments and buyer status checks
598
- AGENTMALL_API_SECRET Operator secret for internal admin read endpoints
599
874
  `);
600
875
  }
601
876
  main();
package/dist/index.cjs CHANGED
@@ -108,7 +108,6 @@ function createIdempotencyKey() {
108
108
  var AgentMall = class {
109
109
  baseUrl;
110
110
  apiSecret;
111
- account;
112
111
  _fetch;
113
112
  products;
114
113
  purchases;
@@ -117,7 +116,6 @@ var AgentMall = class {
117
116
  constructor(config = {}) {
118
117
  this.baseUrl = (config.baseUrl ?? BASE_URL).replace(/\/$/, "");
119
118
  this.apiSecret = config.apiSecret;
120
- this.account = config.account;
121
119
  this._fetch = config.fetch ?? globalThis.fetch.bind(globalThis);
122
120
  this.products = new AgentMallProducts(this);
123
121
  this.purchases = new AgentMallPurchases(this);
@@ -125,8 +123,8 @@ var AgentMall = class {
125
123
  this.refunds = new AgentMallRefunds(this);
126
124
  }
127
125
  /** @internal */
128
- getAccount() {
129
- return this.account;
126
+ hasApiSecret() {
127
+ return Boolean(this.apiSecret);
130
128
  }
131
129
  /** @internal */
132
130
  async request(method, path, options) {
@@ -208,28 +206,19 @@ var AgentMallPurchases = class {
208
206
  }
209
207
  });
210
208
  }
211
- /** Get a single purchase by ID using either operator auth or the payer wallet. */
209
+ /** Get a single purchase by ID using either operator auth or a buyer token. */
212
210
  async get(id, options) {
213
- const account = options?.account ?? this.client.getAccount();
214
- if (account) {
215
- const challenge = await this.client.request(
216
- "POST",
217
- "/api/purchases/status/challenge",
218
- {
219
- body: { id }
220
- }
221
- );
222
- const signature = await account.signMessage({
223
- message: challenge.message
224
- });
211
+ if (options?.buyerToken) {
225
212
  return this.client.request("POST", "/api/purchases/status", {
226
213
  body: {
227
214
  id,
228
- challenge: challenge.challenge,
229
- signature
215
+ buyer_token: options.buyerToken
230
216
  }
231
217
  });
232
218
  }
219
+ if (!this.client.hasApiSecret()) {
220
+ throw new Error("buyerToken or apiSecret is required for this endpoint");
221
+ }
233
222
  return this.client.request("GET", "/api/purchases", {
234
223
  auth: true,
235
224
  params: { id }
@@ -277,6 +266,33 @@ var AgentMallRefunds = class {
277
266
  params: { id }
278
267
  });
279
268
  }
269
+ /** Get refund status for a purchase using either operator auth or a buyer token. */
270
+ async getByPurchase(purchaseId, options) {
271
+ if (options?.buyerToken) {
272
+ return this.client.request("POST", "/api/refunds/status", {
273
+ body: {
274
+ purchase_id: purchaseId,
275
+ buyer_token: options.buyerToken
276
+ }
277
+ });
278
+ }
279
+ if (!this.client.hasApiSecret()) {
280
+ throw new Error("buyerToken or apiSecret is required for this endpoint");
281
+ }
282
+ const result = await this.client.request(
283
+ "GET",
284
+ "/api/refunds",
285
+ {
286
+ auth: true,
287
+ params: { purchase_id: purchaseId }
288
+ }
289
+ );
290
+ const refund = result.refunds[0];
291
+ if (!refund) {
292
+ throw new AgentMallError(404, { error: "Not found" });
293
+ }
294
+ return refund;
295
+ }
280
296
  /** List refunds. Requires apiSecret. */
281
297
  async list(filters) {
282
298
  const result = await this.client.request(
package/dist/index.d.cts CHANGED
@@ -3,8 +3,6 @@ type AgentMallConfig = {
3
3
  baseUrl?: string;
4
4
  /** Operator API secret for admin read endpoints (Bearer auth). */
5
5
  apiSecret?: string;
6
- /** Wallet account used for buyer-authenticated purchase status reads. */
7
- account?: WalletAccount;
8
6
  /** Custom fetch implementation (e.g. mppx-wrapped fetch for automatic 402 handling). */
9
7
  fetch?: typeof globalThis.fetch;
10
8
  };
@@ -33,7 +31,7 @@ type ProductLookup = {
33
31
  availability: string;
34
32
  imageUrl?: string;
35
33
  retailer: string;
36
- /** Suggested max_budget in cents (list price + 15% buffer). */
34
+ /** Suggested max_budget in cents (list price + a buffer, with a minimum $8 buffer). */
37
35
  suggestedMaxBudget: number;
38
36
  variants?: ProductVariant[];
39
37
  /** True if served from cache. Price may not reflect current listing. */
@@ -72,12 +70,14 @@ type CreatePurchaseResponse = {
72
70
  id: string;
73
71
  status: PurchaseLifecycleStatus;
74
72
  deduplicated?: boolean;
73
+ buyerToken?: string;
75
74
  };
76
75
  type PurchaseStatusChallenge = {
77
76
  challenge: string;
78
77
  message: string;
79
78
  expiresAt: number;
80
79
  };
80
+ type RefundStatusChallenge = PurchaseStatusChallenge;
81
81
  type PurchaseItem = {
82
82
  productRef: string;
83
83
  quantity: number;
@@ -144,7 +144,6 @@ type RefundFilters = {
144
144
  declare class AgentMall {
145
145
  private baseUrl;
146
146
  private apiSecret?;
147
- private account?;
148
147
  private _fetch;
149
148
  products: AgentMallProducts;
150
149
  purchases: AgentMallPurchases;
@@ -152,7 +151,7 @@ declare class AgentMall {
152
151
  refunds: AgentMallRefunds;
153
152
  constructor(config?: AgentMallConfig);
154
153
  /** @internal */
155
- getAccount(): WalletAccount | undefined;
154
+ hasApiSecret(): boolean;
156
155
  /** @internal */
157
156
  request<T>(method: string, path: string, options?: {
158
157
  body?: unknown;
@@ -175,9 +174,9 @@ declare class AgentMallPurchases {
175
174
  * Use mppx-wrapped fetch to handle payment automatically, or catch PaymentRequiredError.
176
175
  */
177
176
  create(body: CreatePurchaseRequest): Promise<CreatePurchaseResponse>;
178
- /** Get a single purchase by ID using either operator auth or the payer wallet. */
177
+ /** Get a single purchase by ID using either operator auth or a buyer token. */
179
178
  get(id: string, options?: {
180
- account?: WalletAccount;
179
+ buyerToken?: string;
181
180
  }): Promise<Purchase>;
182
181
  /** List purchases. Requires apiSecret. */
183
182
  list(filters?: PurchaseFilters): Promise<Purchase[]>;
@@ -195,6 +194,10 @@ declare class AgentMallRefunds {
195
194
  constructor(client: AgentMall);
196
195
  /** Get a single refund by public ID. Requires apiSecret. */
197
196
  get(id: string): Promise<Refund>;
197
+ /** Get refund status for a purchase using either operator auth or a buyer token. */
198
+ getByPurchase(purchaseId: string, options?: {
199
+ buyerToken?: string;
200
+ }): Promise<Refund>;
198
201
  /** List refunds. Requires apiSecret. */
199
202
  list(filters?: RefundFilters): Promise<Refund[]>;
200
203
  }
@@ -248,4 +251,4 @@ declare const BASE_URL = "https://api.agentmall.sh";
248
251
  declare const SERVICE_FEE_CENTS = 150;
249
252
  declare const SUPPORTED_RETAILERS: readonly ["amazon.com", "walmart.com", "target.com", "bestbuy.com", "homedepot.com", "ebay.com", "lowes.com", "wayfair.com", "acehardware.com", "1800flowers.com", "pokemoncenter.com"];
250
253
 
251
- export { AgentMall, type AgentMallConfig, AgentMallError, BASE_URL, ConflictError, type CreatePurchaseRequest, type CreatePurchaseResponse, type DeliveryAddress, type Payment, type PaymentFilters, PaymentRequiredError, type ProductLookup, type ProductVariant, type Purchase, type PurchaseFilters, type PurchaseItem, type PurchaseItemInput, type PurchaseLifecycleStatus, type PurchaseStatusChallenge, RateLimitError, type Refund, type RefundFilters, SERVICE_FEE_CENTS, SUPPORTED_RETAILERS, ValidationError, type VariantSelection, type WalletAccount, purchase };
254
+ export { AgentMall, type AgentMallConfig, AgentMallError, BASE_URL, ConflictError, type CreatePurchaseRequest, type CreatePurchaseResponse, type DeliveryAddress, type Payment, type PaymentFilters, PaymentRequiredError, type ProductLookup, type ProductVariant, type Purchase, type PurchaseFilters, type PurchaseItem, type PurchaseItemInput, type PurchaseLifecycleStatus, type PurchaseStatusChallenge, RateLimitError, type Refund, type RefundFilters, type RefundStatusChallenge, SERVICE_FEE_CENTS, SUPPORTED_RETAILERS, ValidationError, type VariantSelection, type WalletAccount, purchase };
package/dist/index.d.ts CHANGED
@@ -3,8 +3,6 @@ type AgentMallConfig = {
3
3
  baseUrl?: string;
4
4
  /** Operator API secret for admin read endpoints (Bearer auth). */
5
5
  apiSecret?: string;
6
- /** Wallet account used for buyer-authenticated purchase status reads. */
7
- account?: WalletAccount;
8
6
  /** Custom fetch implementation (e.g. mppx-wrapped fetch for automatic 402 handling). */
9
7
  fetch?: typeof globalThis.fetch;
10
8
  };
@@ -33,7 +31,7 @@ type ProductLookup = {
33
31
  availability: string;
34
32
  imageUrl?: string;
35
33
  retailer: string;
36
- /** Suggested max_budget in cents (list price + 15% buffer). */
34
+ /** Suggested max_budget in cents (list price + a buffer, with a minimum $8 buffer). */
37
35
  suggestedMaxBudget: number;
38
36
  variants?: ProductVariant[];
39
37
  /** True if served from cache. Price may not reflect current listing. */
@@ -72,12 +70,14 @@ type CreatePurchaseResponse = {
72
70
  id: string;
73
71
  status: PurchaseLifecycleStatus;
74
72
  deduplicated?: boolean;
73
+ buyerToken?: string;
75
74
  };
76
75
  type PurchaseStatusChallenge = {
77
76
  challenge: string;
78
77
  message: string;
79
78
  expiresAt: number;
80
79
  };
80
+ type RefundStatusChallenge = PurchaseStatusChallenge;
81
81
  type PurchaseItem = {
82
82
  productRef: string;
83
83
  quantity: number;
@@ -144,7 +144,6 @@ type RefundFilters = {
144
144
  declare class AgentMall {
145
145
  private baseUrl;
146
146
  private apiSecret?;
147
- private account?;
148
147
  private _fetch;
149
148
  products: AgentMallProducts;
150
149
  purchases: AgentMallPurchases;
@@ -152,7 +151,7 @@ declare class AgentMall {
152
151
  refunds: AgentMallRefunds;
153
152
  constructor(config?: AgentMallConfig);
154
153
  /** @internal */
155
- getAccount(): WalletAccount | undefined;
154
+ hasApiSecret(): boolean;
156
155
  /** @internal */
157
156
  request<T>(method: string, path: string, options?: {
158
157
  body?: unknown;
@@ -175,9 +174,9 @@ declare class AgentMallPurchases {
175
174
  * Use mppx-wrapped fetch to handle payment automatically, or catch PaymentRequiredError.
176
175
  */
177
176
  create(body: CreatePurchaseRequest): Promise<CreatePurchaseResponse>;
178
- /** Get a single purchase by ID using either operator auth or the payer wallet. */
177
+ /** Get a single purchase by ID using either operator auth or a buyer token. */
179
178
  get(id: string, options?: {
180
- account?: WalletAccount;
179
+ buyerToken?: string;
181
180
  }): Promise<Purchase>;
182
181
  /** List purchases. Requires apiSecret. */
183
182
  list(filters?: PurchaseFilters): Promise<Purchase[]>;
@@ -195,6 +194,10 @@ declare class AgentMallRefunds {
195
194
  constructor(client: AgentMall);
196
195
  /** Get a single refund by public ID. Requires apiSecret. */
197
196
  get(id: string): Promise<Refund>;
197
+ /** Get refund status for a purchase using either operator auth or a buyer token. */
198
+ getByPurchase(purchaseId: string, options?: {
199
+ buyerToken?: string;
200
+ }): Promise<Refund>;
198
201
  /** List refunds. Requires apiSecret. */
199
202
  list(filters?: RefundFilters): Promise<Refund[]>;
200
203
  }
@@ -248,4 +251,4 @@ declare const BASE_URL = "https://api.agentmall.sh";
248
251
  declare const SERVICE_FEE_CENTS = 150;
249
252
  declare const SUPPORTED_RETAILERS: readonly ["amazon.com", "walmart.com", "target.com", "bestbuy.com", "homedepot.com", "ebay.com", "lowes.com", "wayfair.com", "acehardware.com", "1800flowers.com", "pokemoncenter.com"];
250
253
 
251
- export { AgentMall, type AgentMallConfig, AgentMallError, BASE_URL, ConflictError, type CreatePurchaseRequest, type CreatePurchaseResponse, type DeliveryAddress, type Payment, type PaymentFilters, PaymentRequiredError, type ProductLookup, type ProductVariant, type Purchase, type PurchaseFilters, type PurchaseItem, type PurchaseItemInput, type PurchaseLifecycleStatus, type PurchaseStatusChallenge, RateLimitError, type Refund, type RefundFilters, SERVICE_FEE_CENTS, SUPPORTED_RETAILERS, ValidationError, type VariantSelection, type WalletAccount, purchase };
254
+ export { AgentMall, type AgentMallConfig, AgentMallError, BASE_URL, ConflictError, type CreatePurchaseRequest, type CreatePurchaseResponse, type DeliveryAddress, type Payment, type PaymentFilters, PaymentRequiredError, type ProductLookup, type ProductVariant, type Purchase, type PurchaseFilters, type PurchaseItem, type PurchaseItemInput, type PurchaseLifecycleStatus, type PurchaseStatusChallenge, RateLimitError, type Refund, type RefundFilters, type RefundStatusChallenge, SERVICE_FEE_CENTS, SUPPORTED_RETAILERS, ValidationError, type VariantSelection, type WalletAccount, purchase };
package/dist/index.js CHANGED
@@ -63,7 +63,6 @@ function createIdempotencyKey() {
63
63
  var AgentMall = class {
64
64
  baseUrl;
65
65
  apiSecret;
66
- account;
67
66
  _fetch;
68
67
  products;
69
68
  purchases;
@@ -72,7 +71,6 @@ var AgentMall = class {
72
71
  constructor(config = {}) {
73
72
  this.baseUrl = (config.baseUrl ?? BASE_URL).replace(/\/$/, "");
74
73
  this.apiSecret = config.apiSecret;
75
- this.account = config.account;
76
74
  this._fetch = config.fetch ?? globalThis.fetch.bind(globalThis);
77
75
  this.products = new AgentMallProducts(this);
78
76
  this.purchases = new AgentMallPurchases(this);
@@ -80,8 +78,8 @@ var AgentMall = class {
80
78
  this.refunds = new AgentMallRefunds(this);
81
79
  }
82
80
  /** @internal */
83
- getAccount() {
84
- return this.account;
81
+ hasApiSecret() {
82
+ return Boolean(this.apiSecret);
85
83
  }
86
84
  /** @internal */
87
85
  async request(method, path, options) {
@@ -163,28 +161,19 @@ var AgentMallPurchases = class {
163
161
  }
164
162
  });
165
163
  }
166
- /** Get a single purchase by ID using either operator auth or the payer wallet. */
164
+ /** Get a single purchase by ID using either operator auth or a buyer token. */
167
165
  async get(id, options) {
168
- const account = options?.account ?? this.client.getAccount();
169
- if (account) {
170
- const challenge = await this.client.request(
171
- "POST",
172
- "/api/purchases/status/challenge",
173
- {
174
- body: { id }
175
- }
176
- );
177
- const signature = await account.signMessage({
178
- message: challenge.message
179
- });
166
+ if (options?.buyerToken) {
180
167
  return this.client.request("POST", "/api/purchases/status", {
181
168
  body: {
182
169
  id,
183
- challenge: challenge.challenge,
184
- signature
170
+ buyer_token: options.buyerToken
185
171
  }
186
172
  });
187
173
  }
174
+ if (!this.client.hasApiSecret()) {
175
+ throw new Error("buyerToken or apiSecret is required for this endpoint");
176
+ }
188
177
  return this.client.request("GET", "/api/purchases", {
189
178
  auth: true,
190
179
  params: { id }
@@ -232,6 +221,33 @@ var AgentMallRefunds = class {
232
221
  params: { id }
233
222
  });
234
223
  }
224
+ /** Get refund status for a purchase using either operator auth or a buyer token. */
225
+ async getByPurchase(purchaseId, options) {
226
+ if (options?.buyerToken) {
227
+ return this.client.request("POST", "/api/refunds/status", {
228
+ body: {
229
+ purchase_id: purchaseId,
230
+ buyer_token: options.buyerToken
231
+ }
232
+ });
233
+ }
234
+ if (!this.client.hasApiSecret()) {
235
+ throw new Error("buyerToken or apiSecret is required for this endpoint");
236
+ }
237
+ const result = await this.client.request(
238
+ "GET",
239
+ "/api/refunds",
240
+ {
241
+ auth: true,
242
+ params: { purchase_id: purchaseId }
243
+ }
244
+ );
245
+ const refund = result.refunds[0];
246
+ if (!refund) {
247
+ throw new AgentMallError(404, { error: "Not found" });
248
+ }
249
+ return refund;
250
+ }
235
251
  /** List refunds. Requires apiSecret. */
236
252
  async list(filters) {
237
253
  const result = await this.client.request(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentmall",
3
- "version": "0.1.2",
3
+ "version": "0.1.5",
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",