agentmall 0.0.1 → 0.1.0

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 ADDED
@@ -0,0 +1,575 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/constants.ts
4
+ var BASE_URL = "https://api.agentmall.sh";
5
+ var SERVICE_FEE_CENTS = 150;
6
+ var SUPPORTED_RETAILERS = [
7
+ "amazon.com",
8
+ "walmart.com",
9
+ "target.com",
10
+ "bestbuy.com",
11
+ "homedepot.com",
12
+ "ebay.com",
13
+ "lowes.com",
14
+ "wayfair.com",
15
+ "acehardware.com",
16
+ "1800flowers.com",
17
+ "pokemoncenter.com"
18
+ ];
19
+
20
+ // src/errors.ts
21
+ var AgentMallError = class extends Error {
22
+ status;
23
+ body;
24
+ constructor(status2, body) {
25
+ const message = typeof body === "object" && body !== null && "error" in body ? body.error : `Request failed with status ${status2}`;
26
+ super(message);
27
+ this.name = "AgentMallError";
28
+ this.status = status2;
29
+ this.body = body;
30
+ }
31
+ };
32
+ var RateLimitError = class extends AgentMallError {
33
+ retryAfterMs;
34
+ constructor(body, retryAfterMs) {
35
+ super(429, body);
36
+ this.name = "RateLimitError";
37
+ this.retryAfterMs = retryAfterMs;
38
+ }
39
+ };
40
+ var PaymentRequiredError = class extends AgentMallError {
41
+ headers;
42
+ constructor(body, headers) {
43
+ super(402, body);
44
+ this.name = "PaymentRequiredError";
45
+ this.headers = headers;
46
+ }
47
+ };
48
+ var ValidationError = class extends AgentMallError {
49
+ constructor(body) {
50
+ super(400, body);
51
+ this.name = "ValidationError";
52
+ }
53
+ };
54
+ var ConflictError = class extends AgentMallError {
55
+ constructor(body) {
56
+ super(409, body);
57
+ this.name = "ConflictError";
58
+ }
59
+ };
60
+
61
+ // src/client.ts
62
+ function createIdempotencyKey() {
63
+ return globalThis.crypto?.randomUUID?.() ?? `agentmall_${Date.now()}_${Math.random().toString(16).slice(2)}`;
64
+ }
65
+ var AgentMall = class {
66
+ baseUrl;
67
+ apiSecret;
68
+ _fetch;
69
+ products;
70
+ purchases;
71
+ payments;
72
+ refunds;
73
+ constructor(config = {}) {
74
+ this.baseUrl = (config.baseUrl ?? BASE_URL).replace(/\/$/, "");
75
+ this.apiSecret = config.apiSecret;
76
+ this._fetch = config.fetch ?? globalThis.fetch.bind(globalThis);
77
+ this.products = new AgentMallProducts(this);
78
+ this.purchases = new AgentMallPurchases(this);
79
+ this.payments = new AgentMallPayments(this);
80
+ this.refunds = new AgentMallRefunds(this);
81
+ }
82
+ /** @internal */
83
+ async request(method, path, options) {
84
+ const url = new URL(`${this.baseUrl}${path}`);
85
+ if (options?.params) {
86
+ for (const [key, value] of Object.entries(options.params)) {
87
+ if (value !== void 0) url.searchParams.set(key, value);
88
+ }
89
+ }
90
+ const headers = { ...options?.headers ?? {} };
91
+ if (options?.auth) {
92
+ if (!this.apiSecret) {
93
+ throw new Error("apiSecret is required for this endpoint");
94
+ }
95
+ headers["Authorization"] = `Bearer ${this.apiSecret}`;
96
+ }
97
+ if (options?.body) {
98
+ headers["Content-Type"] = "application/json";
99
+ }
100
+ const response = await this._fetch(url.toString(), {
101
+ method,
102
+ headers,
103
+ body: options?.body ? JSON.stringify(options.body) : void 0
104
+ });
105
+ if (!response.ok) {
106
+ let body;
107
+ try {
108
+ body = await response.json();
109
+ } catch {
110
+ body = await response.text().catch(() => null);
111
+ }
112
+ if (response.status === 429) {
113
+ const retryAfter = response.headers.get("Retry-After");
114
+ const retryMs = retryAfter ? parseInt(retryAfter, 10) * 1e3 : 6e4;
115
+ throw new RateLimitError(body, retryMs);
116
+ }
117
+ if (response.status === 402) {
118
+ throw new PaymentRequiredError(body, response.headers);
119
+ }
120
+ if (response.status === 400) {
121
+ throw new ValidationError(body);
122
+ }
123
+ if (response.status === 409) {
124
+ throw new ConflictError(body);
125
+ }
126
+ throw new AgentMallError(response.status, body);
127
+ }
128
+ return await response.json();
129
+ }
130
+ };
131
+ var AgentMallProducts = class {
132
+ constructor(client) {
133
+ this.client = client;
134
+ }
135
+ /** Look up product info by URL. Returns price, availability, variants, and suggested max_budget. */
136
+ async lookup(url) {
137
+ return this.client.request("GET", "/api/products", {
138
+ params: { url }
139
+ });
140
+ }
141
+ };
142
+ var AgentMallPurchases = class {
143
+ constructor(client) {
144
+ this.client = client;
145
+ }
146
+ /**
147
+ * Create a purchase. The first call returns a 402 MPP payment challenge.
148
+ * Use mppx-wrapped fetch to handle payment automatically, or catch PaymentRequiredError.
149
+ */
150
+ async create(body) {
151
+ const idempotencyKey = body.idempotency_key ?? createIdempotencyKey();
152
+ return this.client.request("POST", "/api/purchases", {
153
+ body: {
154
+ ...body,
155
+ idempotency_key: idempotencyKey
156
+ },
157
+ headers: {
158
+ "X-Idempotency-Key": idempotencyKey
159
+ }
160
+ });
161
+ }
162
+ /** Get a single purchase by ID. Requires apiSecret. */
163
+ async get(id) {
164
+ return this.client.request("GET", "/api/purchases", {
165
+ auth: true,
166
+ params: { id }
167
+ });
168
+ }
169
+ /** List purchases. Requires apiSecret. */
170
+ async list(filters) {
171
+ const result = await this.client.request(
172
+ "GET",
173
+ "/api/purchases",
174
+ { auth: true, params: filters }
175
+ );
176
+ return result.purchases;
177
+ }
178
+ };
179
+ var AgentMallPayments = class {
180
+ constructor(client) {
181
+ this.client = client;
182
+ }
183
+ /** Get a single payment by ID. Requires apiSecret. */
184
+ async get(id) {
185
+ return this.client.request("GET", "/api/payments", {
186
+ auth: true,
187
+ params: { id }
188
+ });
189
+ }
190
+ /** List payments. Requires apiSecret. */
191
+ async list(filters) {
192
+ const result = await this.client.request(
193
+ "GET",
194
+ "/api/payments",
195
+ { auth: true, params: filters }
196
+ );
197
+ return result.payments;
198
+ }
199
+ };
200
+ var AgentMallRefunds = class {
201
+ constructor(client) {
202
+ this.client = client;
203
+ }
204
+ /** Get a single refund by public ID. Requires apiSecret. */
205
+ async get(id) {
206
+ return this.client.request("GET", "/api/refunds", {
207
+ auth: true,
208
+ params: { id }
209
+ });
210
+ }
211
+ /** List refunds. Requires apiSecret. */
212
+ async list(filters) {
213
+ const result = await this.client.request(
214
+ "GET",
215
+ "/api/refunds",
216
+ { auth: true, params: filters }
217
+ );
218
+ return result.refunds;
219
+ }
220
+ };
221
+
222
+ // src/purchase.ts
223
+ async function purchase(config) {
224
+ const { account, baseUrl, ...body } = config;
225
+ const mppxModule = await import("mppx");
226
+ const Mppx = mppxModule.Mppx ?? mppxModule.default?.Mppx;
227
+ const tempo = mppxModule.tempo ?? mppxModule.default?.tempo;
228
+ if (!Mppx || !tempo) {
229
+ throw new Error("mppx is required for purchases. Install it: npm install mppx");
230
+ }
231
+ const mppx = Mppx.create({
232
+ methods: [tempo({ account })],
233
+ polyfill: false
234
+ });
235
+ const client = new AgentMall({
236
+ baseUrl: baseUrl ?? BASE_URL,
237
+ fetch: mppx.fetch.bind(mppx)
238
+ });
239
+ return client.purchases.create(body);
240
+ }
241
+
242
+ // src/cli/display.ts
243
+ function formatCents(cents) {
244
+ return `$${(cents / 100).toFixed(2)}`;
245
+ }
246
+ function displayProduct(product) {
247
+ console.log();
248
+ console.log(` \x1B[1m${product.title}\x1B[0m`);
249
+ console.log(` ${product.retailer} \u2014 ${product.availability}`);
250
+ console.log();
251
+ if (product.listPrice && product.discountPercent) {
252
+ console.log(
253
+ ` Price: \x1B[32m${formatCents(product.price)}\x1B[0m \x1B[2m\x1B[9m${formatCents(product.listPrice)}\x1B[0m \x1B[33m-${product.discountPercent}%\x1B[0m`
254
+ );
255
+ } else {
256
+ console.log(` Price: \x1B[32m${formatCents(product.price)}\x1B[0m`);
257
+ }
258
+ if (product.variants?.length) {
259
+ console.log();
260
+ for (const v of product.variants) {
261
+ const label = v.label ? `${v.label}: ` : "";
262
+ console.log(` ${label}\x1B[1m${v.value}\x1B[0m \u2014 $${v.price.toFixed(2)}`);
263
+ }
264
+ }
265
+ console.log();
266
+ console.log(
267
+ ` Suggested budget: \x1B[1m${formatCents(product.suggestedMaxBudget)}\x1B[0m (includes 15% buffer for tax & shipping)`
268
+ );
269
+ console.log(
270
+ ` Service fee: ${formatCents(SERVICE_FEE_CENTS)}`
271
+ );
272
+ console.log(
273
+ ` Total charge: \x1B[1m${formatCents(product.suggestedMaxBudget + SERVICE_FEE_CENTS)}\x1B[0m USDC`
274
+ );
275
+ if (product.cached) {
276
+ console.log(` \x1B[33m\u26A0 Price from cache \u2014 may not reflect current listing\x1B[0m`);
277
+ }
278
+ console.log();
279
+ }
280
+ function displayOrderSummary(body) {
281
+ console.log();
282
+ console.log(" \x1B[1mOrder Summary\x1B[0m");
283
+ for (const item of body.items) {
284
+ const url = item.product_url;
285
+ const qty = item.quantity ?? 1;
286
+ console.log(` ${qty}x ${url}`);
287
+ if (item.variant?.length) {
288
+ for (const v of item.variant) {
289
+ console.log(` ${v.label}: ${v.value}`);
290
+ }
291
+ }
292
+ }
293
+ const addr = body.delivery_address;
294
+ console.log();
295
+ console.log(` Ship to: ${addr.first_name} ${addr.last_name}`);
296
+ console.log(` ${addr.address_line1}${addr.address_line2 ? `, ${addr.address_line2}` : ""}`);
297
+ console.log(` ${addr.city}, ${addr.state ?? ""} ${addr.postal_code} ${addr.country}`);
298
+ console.log();
299
+ console.log(` Max budget: \x1B[1m${formatCents(body.max_budget)}\x1B[0m + ${formatCents(SERVICE_FEE_CENTS)} fee`);
300
+ console.log();
301
+ }
302
+ function displayOrderResult(order) {
303
+ console.log();
304
+ if (order.status === "failed") {
305
+ console.log(` \x1B[31m\u2717\x1B[0m Order failed during submission.`);
306
+ } else if (order.deduplicated) {
307
+ console.log(` \x1B[33m\u21BA\x1B[0m Reused existing order.`);
308
+ } else {
309
+ console.log(` \x1B[32m\u2713\x1B[0m Order placed!`);
310
+ }
311
+ console.log(` ID: \x1B[1m${order.id}\x1B[0m`);
312
+ console.log(` Status: ${order.status}`);
313
+ console.log();
314
+ console.log(
315
+ order.status === "failed" ? " Check status for failure details and refund progress:" : " Check status in 5-10 minutes:"
316
+ );
317
+ console.log(` npx agentmall status ${order.id}`);
318
+ console.log();
319
+ }
320
+ function displayStatus(purchase2) {
321
+ console.log();
322
+ console.log(` \x1B[1mOrder ${purchase2.id}\x1B[0m`);
323
+ console.log(` Status: ${purchase2.status}`);
324
+ if (purchase2.items?.length) {
325
+ for (const item of purchase2.items) {
326
+ const price = item.price ? ` \u2014 ${formatCents(item.price)}` : "";
327
+ console.log(` ${item.quantity}x ${item.title ?? item.productRef}${price}`);
328
+ }
329
+ }
330
+ if (purchase2.finalTotal) {
331
+ console.log(` Final total: ${formatCents(purchase2.finalTotal)}`);
332
+ }
333
+ if (purchase2.failureReason) {
334
+ console.log(` \x1B[31mFailure: ${purchase2.failureReason}\x1B[0m`);
335
+ }
336
+ if (purchase2.deliveryMethod) {
337
+ console.log(` Shipping: ${purchase2.deliveryMethod}`);
338
+ }
339
+ console.log(` Created: ${new Date(purchase2.createdAt).toLocaleString()}`);
340
+ console.log();
341
+ }
342
+
343
+ // src/cli/prompts.ts
344
+ import { input, confirm, select } from "@inquirer/prompts";
345
+ async function promptProductUrl() {
346
+ return input({
347
+ message: "Product URL",
348
+ validate: (value) => {
349
+ try {
350
+ const url = new URL(value);
351
+ const hostname = url.hostname.replace(/^www\./, "");
352
+ const supported = SUPPORTED_RETAILERS.some(
353
+ (d) => hostname === d || hostname.endsWith(`.${d}`)
354
+ );
355
+ if (!supported) return `Unsupported retailer: ${hostname}`;
356
+ return true;
357
+ } catch {
358
+ return "Invalid URL";
359
+ }
360
+ }
361
+ });
362
+ }
363
+ async function promptVariants(variants) {
364
+ const groups = /* @__PURE__ */ new Map();
365
+ for (const v of variants) {
366
+ const label = v.label ?? "Option";
367
+ const group = groups.get(label) ?? [];
368
+ group.push(v);
369
+ groups.set(label, group);
370
+ }
371
+ const selections = [];
372
+ for (const [label, options] of groups) {
373
+ if (options.length <= 1) continue;
374
+ const value = await select({
375
+ message: label,
376
+ choices: options.map((o) => ({
377
+ name: `${o.value} \u2014 $${o.price.toFixed(2)}`,
378
+ value: o.value
379
+ }))
380
+ });
381
+ selections.push({ label, value });
382
+ }
383
+ return selections;
384
+ }
385
+ async function promptAddress() {
386
+ const first_name = await input({ message: "First name" });
387
+ const last_name = await input({ message: "Last name" });
388
+ const address_line1 = await input({ message: "Address line 1" });
389
+ const address_line2 = await input({ message: "Address line 2 (optional)", default: "" });
390
+ const city = await input({ message: "City" });
391
+ const state = await input({
392
+ message: "State (2-letter code)",
393
+ validate: (v) => v.length === 2 ? true : "Must be a 2-letter state code"
394
+ });
395
+ const postal_code = await input({ message: "ZIP code" });
396
+ const phone_number = await input({
397
+ message: "Phone (+1...)",
398
+ validate: (v) => v.startsWith("+1") ? true : "Include +1 country code"
399
+ });
400
+ return {
401
+ first_name,
402
+ last_name,
403
+ address_line1,
404
+ ...address_line2 ? { address_line2 } : {},
405
+ city,
406
+ state: state.toUpperCase(),
407
+ postal_code,
408
+ phone_number,
409
+ country: "US"
410
+ };
411
+ }
412
+ async function promptConfirm(message) {
413
+ return confirm({ message });
414
+ }
415
+ async function promptBudget(suggested) {
416
+ const value = await input({
417
+ message: `Max budget in cents (suggested: ${suggested})`,
418
+ default: String(suggested),
419
+ validate: (v) => {
420
+ const n = parseInt(v, 10);
421
+ if (isNaN(n) || n <= 0) return "Must be a positive integer";
422
+ return true;
423
+ }
424
+ });
425
+ return parseInt(value, 10);
426
+ }
427
+
428
+ // src/cli/index.ts
429
+ var EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
430
+ async function resolveAccount() {
431
+ try {
432
+ const { resolveAccount: resolve } = await import("mppx/cli");
433
+ return await resolve();
434
+ } catch {
435
+ const key = process.env.MPPX_PRIVATE_KEY;
436
+ if (!key) {
437
+ console.error("\x1B[31mNo wallet found.\x1B[0m");
438
+ console.error("Set MPPX_PRIVATE_KEY or run: npx mppx account create");
439
+ process.exit(1);
440
+ }
441
+ const { privateKeyToAccount } = await import("viem/accounts");
442
+ return privateKeyToAccount(key);
443
+ }
444
+ }
445
+ async function buy(urlArg) {
446
+ try {
447
+ console.log();
448
+ console.log(" \x1B[1magentmall\x1B[0m \u2014 Buy anything with a URL");
449
+ console.log();
450
+ const account = await resolveAccount();
451
+ const url = urlArg ?? await promptProductUrl();
452
+ const client = new AgentMall();
453
+ console.log(" Fetching product info...");
454
+ const product = await client.products.lookup(url);
455
+ displayProduct(product);
456
+ const variantSelections = product.variants?.length ? await promptVariants(product.variants) : void 0;
457
+ const address = await promptAddress();
458
+ const maxBudget = await promptBudget(product.suggestedMaxBudget);
459
+ const { input: input2 } = await import("@inquirer/prompts");
460
+ const buyerEmail = await input2({
461
+ message: "Email for order updates",
462
+ validate: (value) => EMAIL_PATTERN.test(value.trim()) ? true : "A valid email is required"
463
+ });
464
+ const body = {
465
+ items: [{
466
+ product_url: url,
467
+ quantity: 1,
468
+ ...variantSelections?.length ? { variant: variantSelections } : {}
469
+ }],
470
+ delivery_address: address,
471
+ max_budget: maxBudget,
472
+ buyer_email: buyerEmail
473
+ };
474
+ displayOrderSummary(body);
475
+ console.log(` Total charge: \x1B[1m${formatCents(maxBudget + SERVICE_FEE_CENTS)}\x1B[0m USDC on Tempo`);
476
+ console.log();
477
+ const proceed = await promptConfirm("Place order?");
478
+ if (!proceed) {
479
+ console.log(" Cancelled.");
480
+ return;
481
+ }
482
+ console.log(" Placing order...");
483
+ const order = await purchase({
484
+ ...body,
485
+ account
486
+ });
487
+ displayOrderResult(order);
488
+ } catch (error) {
489
+ console.log();
490
+ if (error instanceof ValidationError) {
491
+ console.error(` \x1B[31mValidation error:\x1B[0m ${error.message}`);
492
+ process.exitCode = 1;
493
+ return;
494
+ }
495
+ if (error instanceof ConflictError) {
496
+ console.error(` \x1B[31mConflict:\x1B[0m ${error.message}`);
497
+ process.exitCode = 1;
498
+ return;
499
+ }
500
+ if (error instanceof RateLimitError) {
501
+ console.error(` \x1B[31mRate limited:\x1B[0m retry in about ${Math.ceil(error.retryAfterMs / 1e3)}s`);
502
+ process.exitCode = 1;
503
+ return;
504
+ }
505
+ if (error instanceof AgentMallError) {
506
+ console.error(` \x1B[31mRequest failed:\x1B[0m ${error.message}`);
507
+ process.exitCode = 1;
508
+ return;
509
+ }
510
+ throw error;
511
+ }
512
+ }
513
+ async function status(purchaseId) {
514
+ const apiSecret = process.env.AGENTMALL_API_SECRET;
515
+ if (!apiSecret) {
516
+ console.error("\x1B[31mAGENTMALL_API_SECRET is required.\x1B[0m");
517
+ process.exit(1);
518
+ }
519
+ const client = new AgentMall({ apiSecret });
520
+ const result = await client.purchases.get(purchaseId);
521
+ displayStatus(result);
522
+ }
523
+
524
+ // src/cli.ts
525
+ var args = process.argv.slice(2);
526
+ var command = args[0];
527
+ async function main() {
528
+ try {
529
+ switch (command) {
530
+ case "buy":
531
+ await buy(args[1]);
532
+ break;
533
+ case "status":
534
+ if (!args[1]) {
535
+ console.error("Usage: agentmall status <purchase_id>");
536
+ process.exit(1);
537
+ }
538
+ await status(args[1]);
539
+ break;
540
+ case "help":
541
+ case "--help":
542
+ case "-h":
543
+ printHelp();
544
+ break;
545
+ default:
546
+ await buy(args[0]);
547
+ break;
548
+ }
549
+ } catch (err) {
550
+ if (err instanceof Error) {
551
+ console.error(`
552
+ \x1B[31mError: ${err.message}\x1B[0m
553
+ `);
554
+ } else {
555
+ console.error(err);
556
+ }
557
+ process.exit(1);
558
+ }
559
+ }
560
+ function printHelp() {
561
+ console.log(`
562
+ \x1B[1magentmall\x1B[0m \u2014 Buy anything with a URL
563
+
564
+ Usage:
565
+ agentmall [url] Buy a product (interactive)
566
+ agentmall buy <url> Buy a product
567
+ agentmall status <id> Check order status
568
+ agentmall help Show this help
569
+
570
+ Environment:
571
+ MPPX_PRIVATE_KEY Wallet private key for payments
572
+ AGENTMALL_API_SECRET Operator secret for read endpoints
573
+ `);
574
+ }
575
+ main();