@mnemopay/sdk 1.0.1 → 1.2.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.
Files changed (80) hide show
  1. package/dist/adaptive.js +1 -1
  2. package/dist/adaptive.js.map +1 -1
  3. package/dist/anomaly.d.ts.map +1 -1
  4. package/dist/anomaly.js +11 -10
  5. package/dist/anomaly.js.map +1 -1
  6. package/dist/behavioral.js +1 -1
  7. package/dist/behavioral.js.map +1 -1
  8. package/dist/commerce/checkout/executor.d.ts +88 -0
  9. package/dist/commerce/checkout/executor.d.ts.map +1 -0
  10. package/dist/commerce/checkout/executor.js +168 -0
  11. package/dist/commerce/checkout/executor.js.map +1 -0
  12. package/dist/commerce/checkout/index.d.ts +10 -0
  13. package/dist/commerce/checkout/index.d.ts.map +1 -0
  14. package/dist/commerce/checkout/index.js +16 -0
  15. package/dist/commerce/checkout/index.js.map +1 -0
  16. package/dist/commerce/checkout/profile.d.ts +55 -0
  17. package/dist/commerce/checkout/profile.d.ts.map +1 -0
  18. package/dist/commerce/checkout/profile.js +69 -0
  19. package/dist/commerce/checkout/profile.js.map +1 -0
  20. package/dist/commerce/checkout/strategies/generic.d.ts +26 -0
  21. package/dist/commerce/checkout/strategies/generic.d.ts.map +1 -0
  22. package/dist/commerce/checkout/strategies/generic.js +279 -0
  23. package/dist/commerce/checkout/strategies/generic.js.map +1 -0
  24. package/dist/commerce/checkout/strategies/shopify.d.ts +39 -0
  25. package/dist/commerce/checkout/strategies/shopify.d.ts.map +1 -0
  26. package/dist/commerce/checkout/strategies/shopify.js +419 -0
  27. package/dist/commerce/checkout/strategies/shopify.js.map +1 -0
  28. package/dist/commerce/providers/firecrawl.d.ts +42 -0
  29. package/dist/commerce/providers/firecrawl.d.ts.map +1 -0
  30. package/dist/commerce/providers/firecrawl.js +233 -0
  31. package/dist/commerce/providers/firecrawl.js.map +1 -0
  32. package/dist/commerce/providers/index.d.ts +8 -0
  33. package/dist/commerce/providers/index.d.ts.map +1 -0
  34. package/dist/commerce/providers/index.js +11 -0
  35. package/dist/commerce/providers/index.js.map +1 -0
  36. package/dist/commerce/providers/shopify.d.ts +47 -0
  37. package/dist/commerce/providers/shopify.d.ts.map +1 -0
  38. package/dist/commerce/providers/shopify.js +242 -0
  39. package/dist/commerce/providers/shopify.js.map +1 -0
  40. package/dist/commerce.d.ts +3 -0
  41. package/dist/commerce.d.ts.map +1 -1
  42. package/dist/commerce.js +74 -42
  43. package/dist/commerce.js.map +1 -1
  44. package/dist/fico.d.ts.map +1 -1
  45. package/dist/fico.js +8 -3
  46. package/dist/fico.js.map +1 -1
  47. package/dist/fraud.d.ts +3 -1
  48. package/dist/fraud.d.ts.map +1 -1
  49. package/dist/fraud.js +71 -4
  50. package/dist/fraud.js.map +1 -1
  51. package/dist/identity.d.ts.map +1 -1
  52. package/dist/identity.js +39 -34
  53. package/dist/identity.js.map +1 -1
  54. package/dist/index.d.ts +62 -1
  55. package/dist/index.d.ts.map +1 -1
  56. package/dist/index.js +74 -6
  57. package/dist/index.js.map +1 -1
  58. package/dist/ledger.d.ts +21 -0
  59. package/dist/ledger.d.ts.map +1 -1
  60. package/dist/ledger.js +68 -0
  61. package/dist/ledger.js.map +1 -1
  62. package/dist/mcp/server.d.ts.map +1 -1
  63. package/dist/mcp/server.js +444 -1
  64. package/dist/mcp/server.js.map +1 -1
  65. package/dist/rails/index.d.ts +6 -0
  66. package/dist/rails/index.d.ts.map +1 -1
  67. package/dist/rails/index.js +60 -14
  68. package/dist/rails/index.js.map +1 -1
  69. package/dist/rails/paystack.d.ts +3 -0
  70. package/dist/rails/paystack.d.ts.map +1 -1
  71. package/dist/rails/paystack.js +18 -3
  72. package/dist/rails/paystack.js.map +1 -1
  73. package/dist/recall/engine.d.ts.map +1 -1
  74. package/dist/recall/engine.js +11 -2
  75. package/dist/recall/engine.js.map +1 -1
  76. package/dist/storage/sqlite.d.ts +12 -0
  77. package/dist/storage/sqlite.d.ts.map +1 -1
  78. package/dist/storage/sqlite.js +12 -3
  79. package/dist/storage/sqlite.js.map +1 -1
  80. package/package.json +25 -5
@@ -26,6 +26,8 @@ const index_js_1 = require("@modelcontextprotocol/sdk/server/index.js");
26
26
  const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
27
27
  const types_js_1 = require("@modelcontextprotocol/sdk/types.js");
28
28
  const index_js_2 = require("../index.js");
29
+ const index_js_3 = require("../rails/index.js");
30
+ const paystack_js_1 = require("../rails/paystack.js");
29
31
  // ─── Security: MCP-level rate limiter ────────────────────────────────────────
30
32
  // Separate from per-agent fraud rate limits — this guards the MCP server itself.
31
33
  const MCP_RATE_LIMIT = {
@@ -62,11 +64,46 @@ function createAgent() {
62
64
  debug: process.env.DEBUG === "true",
63
65
  });
64
66
  }
67
+ // ── Payment rail selection ────────────────────────────────────────────────
68
+ // Set MNEMOPAY_PAYMENT_RAIL to "stripe", "paystack", or "lightning".
69
+ // Defaults to MockRail when no rail/keys are configured (backwards compatible).
70
+ const railName = (process.env.MNEMOPAY_PAYMENT_RAIL || "mock").toLowerCase();
71
+ let paymentRail;
72
+ switch (railName) {
73
+ case "stripe": {
74
+ const key = process.env.STRIPE_SECRET_KEY;
75
+ if (!key)
76
+ throw new Error("STRIPE_SECRET_KEY required when MNEMOPAY_PAYMENT_RAIL=stripe");
77
+ const currency = process.env.STRIPE_CURRENCY || "usd";
78
+ paymentRail = new index_js_3.StripeRail(key, currency);
79
+ break;
80
+ }
81
+ case "paystack": {
82
+ const key = process.env.PAYSTACK_SECRET_KEY;
83
+ if (!key)
84
+ throw new Error("PAYSTACK_SECRET_KEY required when MNEMOPAY_PAYMENT_RAIL=paystack");
85
+ const currency = (process.env.PAYSTACK_CURRENCY || "NGN");
86
+ paymentRail = new paystack_js_1.PaystackRail(key, { currency });
87
+ break;
88
+ }
89
+ case "lightning": {
90
+ const url = process.env.LIGHTNING_LND_URL;
91
+ const macaroon = process.env.LIGHTNING_MACAROON;
92
+ if (!url || !macaroon)
93
+ throw new Error("LIGHTNING_LND_URL and LIGHTNING_MACAROON required when MNEMOPAY_PAYMENT_RAIL=lightning");
94
+ const btcPrice = Number(process.env.LIGHTNING_BTC_PRICE) || 60000;
95
+ paymentRail = new index_js_3.LightningRail(url, macaroon, btcPrice);
96
+ break;
97
+ }
98
+ default:
99
+ paymentRail = new index_js_3.MockRail();
100
+ }
65
101
  const recall = process.env.MNEMOPAY_RECALL || undefined;
66
102
  const agent = index_js_2.MnemoPay.quick(agentId, {
67
103
  debug: process.env.DEBUG === "true",
68
104
  recall,
69
105
  openaiApiKey: process.env.OPENAI_API_KEY,
106
+ paymentRail,
70
107
  });
71
108
  // Enable file persistence — always on by default.
72
109
  // Priority: MNEMOPAY_PERSIST_DIR env > Fly.io /data > ~/.mnemopay/data
@@ -338,6 +375,130 @@ const TOOLS = [
338
375
  },
339
376
  },
340
377
  },
378
+ // ── Approval / HITL tools ──────────────────────────────────────────────────
379
+ {
380
+ name: "shop_pending_approvals",
381
+ description: "List all purchases and charge requests waiting for your approval. " +
382
+ "Items expire after 10 minutes if not approved.",
383
+ inputSchema: { type: "object", properties: {} },
384
+ },
385
+ {
386
+ name: "shop_approve",
387
+ description: "Approve a pending purchase. Funds will be escrowed and purchase executed.",
388
+ inputSchema: {
389
+ type: "object",
390
+ properties: {
391
+ orderId: { type: "string", description: "Order ID from shop_pending_approvals" },
392
+ },
393
+ required: ["orderId"],
394
+ },
395
+ },
396
+ {
397
+ name: "shop_reject",
398
+ description: "Reject a pending purchase. Order will be cancelled, no money moves.",
399
+ inputSchema: {
400
+ type: "object",
401
+ properties: {
402
+ orderId: { type: "string", description: "Order ID to reject" },
403
+ },
404
+ required: ["orderId"],
405
+ },
406
+ },
407
+ {
408
+ name: "charge_request",
409
+ description: "Request a charge that requires user approval before executing. " +
410
+ "Unlike charge(), this queues the payment for review. " +
411
+ "Use charge_approve or charge_reject to finalize.",
412
+ inputSchema: {
413
+ type: "object",
414
+ properties: {
415
+ amount: { type: "number", description: "Amount in USD" },
416
+ reason: { type: "string", description: "Why the charge is needed" },
417
+ },
418
+ required: ["amount", "reason"],
419
+ },
420
+ },
421
+ {
422
+ name: "charge_approve",
423
+ description: "Approve a pending charge request. Executes the real charge.",
424
+ inputSchema: {
425
+ type: "object",
426
+ properties: {
427
+ requestId: { type: "string", description: "Request ID from charge_request" },
428
+ },
429
+ required: ["requestId"],
430
+ },
431
+ },
432
+ {
433
+ name: "charge_reject",
434
+ description: "Reject a pending charge request. No money moves.",
435
+ inputSchema: {
436
+ type: "object",
437
+ properties: {
438
+ requestId: { type: "string", description: "Request ID to reject" },
439
+ },
440
+ required: ["requestId"],
441
+ },
442
+ },
443
+ // ── Payment method management ─────────────────────────────────────────────
444
+ {
445
+ name: "payment_method_add",
446
+ description: "Create a Stripe customer and SetupIntent to collect a payment method. " +
447
+ "Returns a client_secret for Stripe.js to confirm. Only works with Stripe rail.",
448
+ inputSchema: {
449
+ type: "object",
450
+ properties: {
451
+ email: { type: "string", description: "Customer email" },
452
+ name: { type: "string", description: "Customer name (optional)" },
453
+ },
454
+ required: ["email"],
455
+ },
456
+ },
457
+ {
458
+ name: "payment_method_list",
459
+ description: "List saved payment methods for a Stripe customer.",
460
+ inputSchema: {
461
+ type: "object",
462
+ properties: {
463
+ customerId: { type: "string", description: "Stripe customer ID (cus_...)" },
464
+ },
465
+ required: ["customerId"],
466
+ },
467
+ },
468
+ {
469
+ name: "payment_method_remove",
470
+ description: "Detach a payment method from a Stripe customer.",
471
+ inputSchema: {
472
+ type: "object",
473
+ properties: {
474
+ paymentMethodId: { type: "string", description: "Payment method ID (pm_...)" },
475
+ },
476
+ required: ["paymentMethodId"],
477
+ },
478
+ },
479
+ // ── Receipts & Export ─────────────────────────────────────────────────────
480
+ {
481
+ name: "receipt_get",
482
+ description: "Get a formatted receipt for a transaction.",
483
+ inputSchema: {
484
+ type: "object",
485
+ properties: {
486
+ txId: { type: "string", description: "Transaction ID" },
487
+ },
488
+ required: ["txId"],
489
+ },
490
+ },
491
+ {
492
+ name: "history_export",
493
+ description: "Export full transaction history as JSON or CSV.",
494
+ inputSchema: {
495
+ type: "object",
496
+ properties: {
497
+ format: { type: "string", enum: ["json", "csv"], description: "Export format (default: json)" },
498
+ limit: { type: "number", description: "Max transactions (default: all)" },
499
+ },
500
+ },
501
+ },
341
502
  // ── Agent FICO ─────────────────────────────────────────────────────────────
342
503
  {
343
504
  name: "agent_fico_score",
@@ -398,6 +559,23 @@ const TOOLS = [
398
559
  },
399
560
  },
400
561
  },
562
+ // ── Checkout Executor ──────────────────────────────────────────────────────
563
+ {
564
+ name: "shop_checkout",
565
+ description: "Complete a purchase on a merchant website using browser automation. " +
566
+ "Navigates to the product URL, adds to cart, fills shipping/payment, " +
567
+ "and completes checkout. Requires MNEMOPAY_BUYER_* env vars for buyer profile. " +
568
+ "Supports Shopify natively; falls back to generic checkout for other sites.",
569
+ inputSchema: {
570
+ type: "object",
571
+ properties: {
572
+ productUrl: { type: "string", description: "Full URL of the product to purchase" },
573
+ headless: { type: "boolean", description: "Run browser in headless mode (default: true)" },
574
+ screenshotDir: { type: "string", description: "Directory to save debug screenshots" },
575
+ },
576
+ required: ["productUrl"],
577
+ },
578
+ },
401
579
  // ── Anomaly Detection ──────────────────────────────────────────────────────
402
580
  {
403
581
  name: "anomaly_check",
@@ -538,6 +716,35 @@ async function executeTool(agent, name, args) {
538
716
  if (order.status === "cancelled") {
539
717
  return `Order cancelled: ${order.failureReason}`;
540
718
  }
719
+ // If using a real provider (firecrawl/shopify), the purchase may need
720
+ // browser checkout to actually complete. Attempt it if buyer profile is configured.
721
+ const providerName = commerce["provider"]?.name;
722
+ if (product.url && (providerName === "firecrawl" || providerName === "shopify")) {
723
+ try {
724
+ const { CheckoutExecutor } = await import("../commerce/checkout/index.js");
725
+ const { loadProfileFromEnv } = await import("../commerce/checkout/profile.js");
726
+ const buyerProfile = loadProfileFromEnv();
727
+ if (buyerProfile) {
728
+ const executor = new CheckoutExecutor({ profile: buyerProfile, headless: true });
729
+ const checkoutResult = await executor.checkout(product.url);
730
+ if (checkoutResult.success) {
731
+ return JSON.stringify({
732
+ orderId: order.id,
733
+ product: order.product.title,
734
+ price: order.product.price,
735
+ status: "purchased",
736
+ escrowTxId: order.txId,
737
+ externalOrderId: checkoutResult.orderId,
738
+ confirmationUrl: checkoutResult.confirmationUrl,
739
+ totalCharged: checkoutResult.totalCharged,
740
+ message: "Purchase completed via browser checkout. Funds in escrow until delivery confirmed.",
741
+ remainingBudget: commerce.remainingBudget,
742
+ });
743
+ }
744
+ }
745
+ }
746
+ catch { /* checkout executor not available — return standard result */ }
747
+ }
541
748
  return JSON.stringify({
542
749
  orderId: order.id,
543
750
  product: order.product.title,
@@ -568,6 +775,167 @@ async function executeTool(agent, name, args) {
568
775
  (o.trackingUrl ? ` Track: ${o.trackingUrl}` : "")).join("\n");
569
776
  return `${list}\n\nSpent: $${summary.totalSpent.toFixed(2)} | Remaining: $${summary.remainingBudget.toFixed(2)} | Orders: ${summary.orderCount}`;
570
777
  }
778
+ // ── Approval / HITL handlers ───────────────────────────────────────────
779
+ case "shop_pending_approvals": {
780
+ const shopApprovals = Array.from(pendingApprovals.entries()).map(([id, entry]) => ({
781
+ orderId: id,
782
+ product: entry.order.product?.title,
783
+ price: entry.order.product?.price,
784
+ merchant: entry.order.product?.merchant,
785
+ waitingSince: new Date(entry.createdAt).toISOString(),
786
+ expiresIn: Math.max(0, Math.round((600_000 - (Date.now() - entry.createdAt)) / 1000)) + "s",
787
+ }));
788
+ const chargeRequests = Array.from(pendingChargeRequests.entries()).map(([id, entry]) => ({
789
+ requestId: id,
790
+ amount: entry.amount,
791
+ reason: entry.reason,
792
+ waitingSince: new Date(entry.createdAt).toISOString(),
793
+ expiresIn: Math.max(0, Math.round((600_000 - (Date.now() - entry.createdAt)) / 1000)) + "s",
794
+ }));
795
+ if (shopApprovals.length === 0 && chargeRequests.length === 0) {
796
+ return "No pending approvals.";
797
+ }
798
+ return JSON.stringify({ shopApprovals, chargeRequests }, null, 2);
799
+ }
800
+ case "shop_approve": {
801
+ const entry = pendingApprovals.get(args.orderId);
802
+ if (!entry)
803
+ throw new Error(`No pending approval for order ${args.orderId}`);
804
+ entry.resolve(true);
805
+ pendingApprovals.delete(args.orderId);
806
+ return JSON.stringify({ status: "approved", orderId: args.orderId, message: "Purchase approved. Escrow and purchase executing." });
807
+ }
808
+ case "shop_reject": {
809
+ const entry = pendingApprovals.get(args.orderId);
810
+ if (!entry)
811
+ throw new Error(`No pending approval for order ${args.orderId}`);
812
+ entry.resolve(false);
813
+ pendingApprovals.delete(args.orderId);
814
+ return JSON.stringify({ status: "rejected", orderId: args.orderId, message: "Purchase rejected. No money moved." });
815
+ }
816
+ case "charge_request": {
817
+ const requestId = `cr_${Date.now()}_${require("crypto").randomBytes(4).toString("hex")}`;
818
+ pendingChargeRequests.set(requestId, {
819
+ id: requestId,
820
+ amount: args.amount,
821
+ reason: args.reason,
822
+ context: args.context,
823
+ payOptions: args.payOptions,
824
+ createdAt: Date.now(),
825
+ });
826
+ return JSON.stringify({
827
+ requestId,
828
+ amount: args.amount,
829
+ reason: args.reason,
830
+ status: "pending_approval",
831
+ message: `Charge of $${args.amount.toFixed(2)} queued for approval. Use charge_approve("${requestId}") to execute.`,
832
+ expiresIn: "10 minutes",
833
+ });
834
+ }
835
+ case "charge_approve": {
836
+ const req = pendingChargeRequests.get(args.requestId);
837
+ if (!req)
838
+ throw new Error(`No pending charge request ${args.requestId}`);
839
+ pendingChargeRequests.delete(args.requestId);
840
+ // Execute the real charge
841
+ const tx = await agent.charge(req.amount, req.reason, req.context, req.payOptions);
842
+ return JSON.stringify({
843
+ status: "charged",
844
+ requestId: args.requestId,
845
+ txId: tx.id,
846
+ amount: tx.amount,
847
+ reason: req.reason,
848
+ rail: agent.paymentRail?.name,
849
+ message: "Charge executed. Funds held in escrow. Use settle() to finalize.",
850
+ });
851
+ }
852
+ case "charge_reject": {
853
+ const req = pendingChargeRequests.get(args.requestId);
854
+ if (!req)
855
+ throw new Error(`No pending charge request ${args.requestId}`);
856
+ pendingChargeRequests.delete(args.requestId);
857
+ return JSON.stringify({
858
+ status: "rejected",
859
+ requestId: args.requestId,
860
+ message: `Charge of $${req.amount.toFixed(2)} for "${req.reason}" rejected. No money moved.`,
861
+ });
862
+ }
863
+ // ── Payment method management ─────────────────────────────────────────
864
+ case "payment_method_add": {
865
+ const rail = agent.paymentRail;
866
+ if (!rail || rail.name !== "stripe")
867
+ throw new Error("payment_method_add requires Stripe rail. Set MNEMOPAY_PAYMENT_RAIL=stripe");
868
+ const customer = await rail.createCustomer(args.email, args.name);
869
+ const setup = await rail.createSetupIntent(customer.customerId);
870
+ return JSON.stringify({
871
+ customerId: customer.customerId,
872
+ setupIntentId: setup.setupIntentId,
873
+ clientSecret: setup.clientSecret,
874
+ message: "Use this clientSecret with Stripe.js to collect the card. Then pass customerId + paymentMethodId to charge().",
875
+ });
876
+ }
877
+ case "payment_method_list": {
878
+ const rail = agent.paymentRail;
879
+ if (!rail || rail.name !== "stripe")
880
+ throw new Error("payment_method_list requires Stripe rail");
881
+ const stripe = rail.stripe;
882
+ const methods = await stripe.paymentMethods.list({ customer: args.customerId, type: "card" });
883
+ const cards = methods.data.map((pm) => ({
884
+ id: pm.id,
885
+ brand: pm.card?.brand,
886
+ last4: pm.card?.last4,
887
+ expMonth: pm.card?.exp_month,
888
+ expYear: pm.card?.exp_year,
889
+ }));
890
+ if (cards.length === 0)
891
+ return "No saved payment methods.";
892
+ return JSON.stringify(cards, null, 2);
893
+ }
894
+ case "payment_method_remove": {
895
+ const rail = agent.paymentRail;
896
+ if (!rail || rail.name !== "stripe")
897
+ throw new Error("payment_method_remove requires Stripe rail");
898
+ const stripe = rail.stripe;
899
+ await stripe.paymentMethods.detach(args.paymentMethodId);
900
+ return JSON.stringify({ status: "removed", paymentMethodId: args.paymentMethodId });
901
+ }
902
+ // ── Receipts & Export ────────────────────────────────────────────────
903
+ case "receipt_get": {
904
+ const txHistory = await agent.history(10000);
905
+ const tx = txHistory.find((t) => t.id === args.txId);
906
+ if (!tx)
907
+ throw new Error(`Transaction ${args.txId} not found`);
908
+ const profile = await agent.profile();
909
+ const receipt = [
910
+ "═══════════════════════════════════════",
911
+ " MNEMOPAY RECEIPT ",
912
+ "═══════════════════════════════════════",
913
+ `Transaction ID: ${tx.id}`,
914
+ `Date: ${tx.createdAt}`,
915
+ `Agent: ${tx.agentId || profile.id}`,
916
+ `Amount: $${tx.amount.toFixed(2)}`,
917
+ `Status: ${tx.status}`,
918
+ `Reason: ${tx.reason || "N/A"}`,
919
+ tx.platformFee ? `Platform Fee: $${tx.platformFee.toFixed(2)}` : null,
920
+ tx.netAmount ? `Net Amount: $${tx.netAmount.toFixed(2)}` : null,
921
+ tx.externalId ? `External Ref: ${tx.externalId}` : null,
922
+ `Rail: ${agent.paymentRail?.name || "mock"}`,
923
+ "═══════════════════════════════════════",
924
+ ].filter(Boolean).join("\n");
925
+ return receipt;
926
+ }
927
+ case "history_export": {
928
+ const format = args.format || "json";
929
+ const limit = args.limit || 10000;
930
+ const txHistory = await agent.history(limit);
931
+ if (format === "csv") {
932
+ const railName = agent.paymentRail?.name || "mock";
933
+ const headers = "id,date,amount,status,reason,rail,externalId,platformFee,netAmount";
934
+ const rows = txHistory.map((tx) => [tx.id, tx.createdAt, tx.amount, tx.status, `"${(tx.reason || "").replace(/"/g, '""')}"`, railName, tx.externalId || "", tx.platformFee || "", tx.netAmount || ""].join(","));
935
+ return [headers, ...rows].join("\n");
936
+ }
937
+ return JSON.stringify(txHistory, null, 2);
938
+ }
571
939
  // ── Agent FICO ─────────────────────────────────────────────────────────
572
940
  case "agent_fico_score": {
573
941
  const fico = new index_js_2.AgentFICO();
@@ -643,6 +1011,32 @@ async function executeTool(agent, name, args) {
643
1011
  const result = _ewmaDetector.update(args.amount);
644
1012
  return JSON.stringify(result, null, 2);
645
1013
  }
1014
+ case "shop_checkout": {
1015
+ const { CheckoutExecutor } = await import("../commerce/checkout/index.js");
1016
+ const { loadProfileFromEnv } = await import("../commerce/checkout/profile.js");
1017
+ const profile = loadProfileFromEnv();
1018
+ if (!profile) {
1019
+ throw new Error("Buyer profile not configured. Set MNEMOPAY_BUYER_NAME, MNEMOPAY_BUYER_EMAIL, " +
1020
+ "MNEMOPAY_BUYER_ADDRESS_LINE1, MNEMOPAY_BUYER_ADDRESS_CITY, MNEMOPAY_BUYER_ADDRESS_STATE, " +
1021
+ "MNEMOPAY_BUYER_ADDRESS_ZIP, MNEMOPAY_BUYER_ADDRESS_COUNTRY env vars.");
1022
+ }
1023
+ const executor = new CheckoutExecutor({
1024
+ profile,
1025
+ headless: args.headless ?? true,
1026
+ screenshotDir: args.screenshotDir,
1027
+ });
1028
+ const result = await executor.checkout(args.productUrl);
1029
+ return JSON.stringify({
1030
+ success: result.success,
1031
+ orderId: result.orderId,
1032
+ totalCharged: result.totalCharged,
1033
+ confirmationUrl: result.confirmationUrl,
1034
+ steps: result.steps,
1035
+ elapsedMs: result.elapsedMs,
1036
+ failureReason: result.failureReason,
1037
+ screenshots: result.screenshots,
1038
+ }, null, 2);
1039
+ }
646
1040
  default:
647
1041
  throw new Error(`Unknown tool: ${name}`);
648
1042
  }
@@ -650,12 +1044,61 @@ async function executeTool(agent, name, args) {
650
1044
  // ── Module singletons ────────────────────────────────────────────────────────
651
1045
  const _merkleTree = new index_js_2.MerkleTree();
652
1046
  const _ewmaDetector = new index_js_2.EWMADetector(0.15, 2.5, 3.5, 10);
1047
+ const pendingApprovals = new Map();
1048
+ const pendingChargeRequests = new Map();
1049
+ // Auto-expire pending approvals after 10 minutes
1050
+ setInterval(() => {
1051
+ const now = Date.now();
1052
+ for (const [id, entry] of pendingApprovals) {
1053
+ if (now - entry.createdAt > 600_000) {
1054
+ entry.resolve(false); // auto-reject expired approvals
1055
+ pendingApprovals.delete(id);
1056
+ }
1057
+ }
1058
+ for (const [id, entry] of pendingChargeRequests) {
1059
+ if (now - entry.createdAt > 600_000) {
1060
+ pendingChargeRequests.delete(id);
1061
+ }
1062
+ }
1063
+ }, 60_000);
653
1064
  // ── Commerce singleton ──────────────────────────────────────────────────────
654
1065
  let _commerceEngine = null;
655
1066
  async function getCommerceEngine(agent) {
656
1067
  if (!_commerceEngine) {
657
1068
  const { CommerceEngine } = await import("../commerce.js");
658
- _commerceEngine = new CommerceEngine(agent);
1069
+ // ── Commerce provider selection ──────────────────────────────────────
1070
+ // Set MNEMOPAY_COMMERCE_PROVIDER to "firecrawl", "shopify", or "mock".
1071
+ const providerName = (process.env.MNEMOPAY_COMMERCE_PROVIDER || "mock").toLowerCase();
1072
+ let provider;
1073
+ switch (providerName) {
1074
+ case "firecrawl": {
1075
+ const { FirecrawlProvider } = await import("../commerce/providers/firecrawl.js");
1076
+ const apiKey = process.env.FIRECRAWL_API_KEY;
1077
+ if (!apiKey)
1078
+ throw new Error("FIRECRAWL_API_KEY required when MNEMOPAY_COMMERCE_PROVIDER=firecrawl");
1079
+ provider = new FirecrawlProvider({ apiKey });
1080
+ break;
1081
+ }
1082
+ case "shopify": {
1083
+ const { ShopifyProvider } = await import("../commerce/providers/shopify.js");
1084
+ const domain = process.env.SHOPIFY_STORE_DOMAIN;
1085
+ const token = process.env.SHOPIFY_STOREFRONT_TOKEN;
1086
+ if (!domain || !token)
1087
+ throw new Error("SHOPIFY_STORE_DOMAIN and SHOPIFY_STOREFRONT_TOKEN required when MNEMOPAY_COMMERCE_PROVIDER=shopify");
1088
+ provider = new ShopifyProvider({ storeDomain: domain, storefrontToken: token });
1089
+ break;
1090
+ }
1091
+ default:
1092
+ provider = undefined; // CommerceEngine uses MockCommerceProvider
1093
+ }
1094
+ _commerceEngine = new CommerceEngine(agent, provider);
1095
+ // ── Wire approval callback (HITL) ────────────────────────────────────
1096
+ _commerceEngine.onApprovalRequired(async (order) => {
1097
+ // Queue the order for user approval instead of auto-approving
1098
+ return new Promise((resolve) => {
1099
+ pendingApprovals.set(order.id, { order, resolve, createdAt: Date.now() });
1100
+ });
1101
+ });
659
1102
  }
660
1103
  return _commerceEngine;
661
1104
  }