@useagentpay/sdk 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/index.cjs ADDED
@@ -0,0 +1,1046 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __esm = (fn, res) => function __init() {
9
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
+ };
11
+ var __export = (target, all) => {
12
+ for (var name in all)
13
+ __defProp(target, name, { get: all[name], enumerable: true });
14
+ };
15
+ var __copyProps = (to, from, except, desc) => {
16
+ if (from && typeof from === "object" || typeof from === "function") {
17
+ for (let key of __getOwnPropNames(from))
18
+ if (!__hasOwnProp.call(to, key) && key !== except)
19
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
20
+ }
21
+ return to;
22
+ };
23
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
24
+ // If the importer is in node compatibility mode or this is not an ESM
25
+ // file that has been converted to a CommonJS file using a Babel-
26
+ // compatible transform (i.e. "__esModule" has not been set), then set
27
+ // "default" to the CommonJS "module.exports" for node compatibility.
28
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
29
+ mod
30
+ ));
31
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
32
+
33
+ // ../../node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_tsx@4.21.0_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js
34
+ var init_cjs_shims = __esm({
35
+ "../../node_modules/.pnpm/tsup@8.5.1_jiti@2.6.1_postcss@8.5.6_tsx@4.21.0_typescript@5.9.3/node_modules/tsup/assets/cjs_shims.js"() {
36
+ "use strict";
37
+ }
38
+ });
39
+
40
+ // src/errors.ts
41
+ var NotSetupError, DecryptError, InsufficientBalanceError, ExceedsTxLimitError, NotApprovedError, InvalidMandateError, AlreadyExecutedError, CheckoutFailedError, TimeoutError;
42
+ var init_errors = __esm({
43
+ "src/errors.ts"() {
44
+ "use strict";
45
+ init_cjs_shims();
46
+ NotSetupError = class extends Error {
47
+ code = "NOT_SETUP";
48
+ constructor(message = "AgentPay has not been set up yet. Run `agentpay setup` first.") {
49
+ super(message);
50
+ this.name = "NotSetupError";
51
+ }
52
+ };
53
+ DecryptError = class extends Error {
54
+ code = "DECRYPT_FAILED";
55
+ constructor(message = "Failed to decrypt credentials. Wrong passphrase or corrupted file.") {
56
+ super(message);
57
+ this.name = "DecryptError";
58
+ }
59
+ };
60
+ InsufficientBalanceError = class extends Error {
61
+ code = "INSUFFICIENT_BALANCE";
62
+ constructor(amount, balance) {
63
+ const msg = amount !== void 0 && balance !== void 0 ? `Insufficient balance: requested $${amount.toFixed(2)} but only $${balance.toFixed(2)} available.` : "Insufficient balance for this transaction.";
64
+ super(msg);
65
+ this.name = "InsufficientBalanceError";
66
+ }
67
+ };
68
+ ExceedsTxLimitError = class extends Error {
69
+ code = "EXCEEDS_TX_LIMIT";
70
+ constructor(amount, limit) {
71
+ const msg = amount !== void 0 && limit !== void 0 ? `Amount $${amount.toFixed(2)} exceeds per-transaction limit of $${limit.toFixed(2)}.` : "Amount exceeds per-transaction limit.";
72
+ super(msg);
73
+ this.name = "ExceedsTxLimitError";
74
+ }
75
+ };
76
+ NotApprovedError = class extends Error {
77
+ code = "NOT_APPROVED";
78
+ constructor(txId) {
79
+ super(txId ? `Transaction ${txId} has not been approved.` : "Transaction has not been approved.");
80
+ this.name = "NotApprovedError";
81
+ }
82
+ };
83
+ InvalidMandateError = class extends Error {
84
+ code = "INVALID_MANDATE";
85
+ constructor(message = "Purchase mandate signature verification failed.") {
86
+ super(message);
87
+ this.name = "InvalidMandateError";
88
+ }
89
+ };
90
+ AlreadyExecutedError = class extends Error {
91
+ code = "ALREADY_EXECUTED";
92
+ constructor(txId) {
93
+ super(txId ? `Transaction ${txId} has already been executed.` : "Transaction has already been executed.");
94
+ this.name = "AlreadyExecutedError";
95
+ }
96
+ };
97
+ CheckoutFailedError = class extends Error {
98
+ code = "CHECKOUT_FAILED";
99
+ constructor(message = "Failed to complete checkout.") {
100
+ super(message);
101
+ this.name = "CheckoutFailedError";
102
+ }
103
+ };
104
+ TimeoutError = class extends Error {
105
+ code = "TIMEOUT";
106
+ constructor(message = "Operation timed out.") {
107
+ super(message);
108
+ this.name = "TimeoutError";
109
+ }
110
+ };
111
+ }
112
+ });
113
+
114
+ // src/transactions/poller.ts
115
+ var poller_exports = {};
116
+ __export(poller_exports, {
117
+ waitForApproval: () => waitForApproval
118
+ });
119
+ async function waitForApproval(txId, manager, options) {
120
+ const interval = options?.pollInterval ?? 2e3;
121
+ const timeout = options?.timeout ?? 3e5;
122
+ const deadline = Date.now() + timeout;
123
+ while (Date.now() < deadline) {
124
+ const tx = manager.get(txId);
125
+ if (!tx) throw new Error(`Transaction ${txId} not found.`);
126
+ if (tx.status === "approved") return { status: "approved" };
127
+ if (tx.status === "rejected") return { status: "rejected", reason: tx.rejectionReason };
128
+ await new Promise((resolve) => setTimeout(resolve, interval));
129
+ }
130
+ throw new TimeoutError(`Timed out waiting for approval of ${txId}`);
131
+ }
132
+ var init_poller = __esm({
133
+ "src/transactions/poller.ts"() {
134
+ "use strict";
135
+ init_cjs_shims();
136
+ init_errors();
137
+ }
138
+ });
139
+
140
+ // src/index.ts
141
+ var src_exports = {};
142
+ __export(src_exports, {
143
+ AgentPay: () => AgentPay,
144
+ AlreadyExecutedError: () => AlreadyExecutedError,
145
+ AuditLogger: () => AuditLogger,
146
+ BudgetManager: () => BudgetManager,
147
+ CheckoutFailedError: () => CheckoutFailedError,
148
+ DecryptError: () => DecryptError,
149
+ ExceedsTxLimitError: () => ExceedsTxLimitError,
150
+ InsufficientBalanceError: () => InsufficientBalanceError,
151
+ InvalidMandateError: () => InvalidMandateError,
152
+ NotApprovedError: () => NotApprovedError,
153
+ NotSetupError: () => NotSetupError,
154
+ PLACEHOLDER_MAP: () => PLACEHOLDER_MAP,
155
+ PurchaseExecutor: () => PurchaseExecutor,
156
+ TimeoutError: () => TimeoutError,
157
+ TransactionManager: () => TransactionManager,
158
+ VERSION: () => VERSION,
159
+ createMandate: () => createMandate,
160
+ credentialsToSwapMap: () => credentialsToSwapMap,
161
+ decrypt: () => decrypt,
162
+ encrypt: () => encrypt,
163
+ formatCurrency: () => formatCurrency,
164
+ formatStatus: () => formatStatus,
165
+ formatTable: () => formatTable,
166
+ formatTimestamp: () => formatTimestamp,
167
+ generateKeyPair: () => generateKeyPair,
168
+ generateTxId: () => generateTxId,
169
+ getAuditPath: () => getAuditPath,
170
+ getCredentialsPath: () => getCredentialsPath,
171
+ getHomePath: () => getHomePath,
172
+ getKeysPath: () => getKeysPath,
173
+ getPlaceholderVariables: () => getPlaceholderVariables,
174
+ getTransactionsPath: () => getTransactionsPath,
175
+ getWalletPath: () => getWalletPath,
176
+ loadPrivateKey: () => loadPrivateKey,
177
+ loadPublicKey: () => loadPublicKey,
178
+ loadVault: () => loadVault,
179
+ saveKeyPair: () => saveKeyPair,
180
+ saveVault: () => saveVault,
181
+ verifyMandate: () => verifyMandate,
182
+ waitForApproval: () => waitForApproval
183
+ });
184
+ module.exports = __toCommonJS(src_exports);
185
+ init_cjs_shims();
186
+ init_errors();
187
+
188
+ // src/utils/ids.ts
189
+ init_cjs_shims();
190
+ var import_node_crypto = require("crypto");
191
+ function generateTxId() {
192
+ return `tx_${(0, import_node_crypto.randomBytes)(4).toString("hex")}`;
193
+ }
194
+
195
+ // src/utils/display.ts
196
+ init_cjs_shims();
197
+ function formatCurrency(amount) {
198
+ return `$${amount.toFixed(2)}`;
199
+ }
200
+ function formatTimestamp(iso) {
201
+ const d = new Date(iso);
202
+ return d.toLocaleString("en-US", {
203
+ month: "short",
204
+ day: "numeric",
205
+ hour: "2-digit",
206
+ minute: "2-digit"
207
+ });
208
+ }
209
+ function formatTable(transactions) {
210
+ if (transactions.length === 0) return "No transactions.";
211
+ const header = "TX_ID MERCHANT AMOUNT DESCRIPTION";
212
+ const separator = "\u2500".repeat(header.length);
213
+ const rows = transactions.map((tx) => {
214
+ const id = tx.id.padEnd(14);
215
+ const merchant = tx.merchant.padEnd(16);
216
+ const amount = formatCurrency(tx.amount).padStart(9);
217
+ return `${id}${merchant}${amount} ${tx.description}`;
218
+ });
219
+ return [header, separator, ...rows].join("\n");
220
+ }
221
+ function formatStatus(data) {
222
+ const lines = [
223
+ "AgentPay Status",
224
+ "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
225
+ `Balance: ${formatCurrency(data.balance)} / ${formatCurrency(data.budget)}`,
226
+ `Per-tx limit: ${formatCurrency(data.limitPerTx)}`,
227
+ `Pending purchases: ${data.pending.length}`
228
+ ];
229
+ if (data.recent.length > 0) {
230
+ lines.push("", "Recent:");
231
+ for (const tx of data.recent) {
232
+ const status = `[${tx.status}]`.padEnd(12);
233
+ lines.push(` ${status} ${tx.id} ${tx.merchant.padEnd(12)} ${formatCurrency(tx.amount).padStart(8)} ${tx.description}`);
234
+ }
235
+ }
236
+ return lines.join("\n");
237
+ }
238
+
239
+ // src/utils/paths.ts
240
+ init_cjs_shims();
241
+ var import_node_os = require("os");
242
+ var import_node_path = require("path");
243
+ function getHomePath() {
244
+ return process.env.AGENTPAY_HOME || (0, import_node_path.join)((0, import_node_os.homedir)(), ".agentpay");
245
+ }
246
+ function getCredentialsPath() {
247
+ return (0, import_node_path.join)(getHomePath(), "credentials.enc");
248
+ }
249
+ function getKeysPath() {
250
+ return (0, import_node_path.join)(getHomePath(), "keys");
251
+ }
252
+ function getPublicKeyPath() {
253
+ return (0, import_node_path.join)(getKeysPath(), "public.pem");
254
+ }
255
+ function getPrivateKeyPath() {
256
+ return (0, import_node_path.join)(getKeysPath(), "private.pem");
257
+ }
258
+ function getWalletPath() {
259
+ return (0, import_node_path.join)(getHomePath(), "wallet.json");
260
+ }
261
+ function getTransactionsPath() {
262
+ return (0, import_node_path.join)(getHomePath(), "transactions.json");
263
+ }
264
+ function getAuditPath() {
265
+ return (0, import_node_path.join)(getHomePath(), "audit.log");
266
+ }
267
+
268
+ // src/vault/vault.ts
269
+ init_cjs_shims();
270
+ var import_node_crypto2 = require("crypto");
271
+ var import_node_fs = require("fs");
272
+ var import_node_path2 = require("path");
273
+ init_errors();
274
+ var ALGORITHM = "aes-256-gcm";
275
+ var PBKDF2_ITERATIONS = 1e5;
276
+ var PBKDF2_DIGEST = "sha512";
277
+ var KEY_LENGTH = 32;
278
+ var SALT_LENGTH = 32;
279
+ var IV_LENGTH = 16;
280
+ function deriveKey(passphrase, salt) {
281
+ return (0, import_node_crypto2.pbkdf2Sync)(passphrase, salt, PBKDF2_ITERATIONS, KEY_LENGTH, PBKDF2_DIGEST);
282
+ }
283
+ function encrypt(credentials, passphrase) {
284
+ const salt = (0, import_node_crypto2.randomBytes)(SALT_LENGTH);
285
+ const iv = (0, import_node_crypto2.randomBytes)(IV_LENGTH);
286
+ const key = deriveKey(passphrase, salt);
287
+ const cipher = (0, import_node_crypto2.createCipheriv)(ALGORITHM, key, iv);
288
+ const plaintext = JSON.stringify(credentials);
289
+ const encrypted = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
290
+ const authTag = cipher.getAuthTag();
291
+ return {
292
+ ciphertext: Buffer.concat([encrypted, authTag]).toString("base64"),
293
+ salt: salt.toString("base64"),
294
+ iv: iv.toString("base64")
295
+ };
296
+ }
297
+ function decrypt(vault, passphrase) {
298
+ try {
299
+ const salt = Buffer.from(vault.salt, "base64");
300
+ const iv = Buffer.from(vault.iv, "base64");
301
+ const data = Buffer.from(vault.ciphertext, "base64");
302
+ const key = deriveKey(passphrase, salt);
303
+ const authTag = data.subarray(data.length - 16);
304
+ const ciphertext = data.subarray(0, data.length - 16);
305
+ const decipher = (0, import_node_crypto2.createDecipheriv)(ALGORITHM, key, iv);
306
+ decipher.setAuthTag(authTag);
307
+ const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
308
+ return JSON.parse(decrypted.toString("utf8"));
309
+ } catch {
310
+ throw new DecryptError();
311
+ }
312
+ }
313
+ function saveVault(vault, path) {
314
+ const filePath = path ?? getCredentialsPath();
315
+ (0, import_node_fs.mkdirSync)((0, import_node_path2.dirname)(filePath), { recursive: true });
316
+ (0, import_node_fs.writeFileSync)(filePath, JSON.stringify(vault, null, 2), { mode: 384 });
317
+ }
318
+ function loadVault(path) {
319
+ const filePath = path ?? getCredentialsPath();
320
+ try {
321
+ const data = (0, import_node_fs.readFileSync)(filePath, "utf8");
322
+ return JSON.parse(data);
323
+ } catch {
324
+ throw new NotSetupError();
325
+ }
326
+ }
327
+
328
+ // src/auth/keypair.ts
329
+ init_cjs_shims();
330
+ var import_node_crypto3 = require("crypto");
331
+ var import_node_fs2 = require("fs");
332
+ var import_node_path3 = require("path");
333
+ function generateKeyPair(passphrase) {
334
+ const { publicKey, privateKey } = (0, import_node_crypto3.generateKeyPairSync)("ed25519", {
335
+ publicKeyEncoding: { type: "spki", format: "pem" },
336
+ privateKeyEncoding: {
337
+ type: "pkcs8",
338
+ format: "pem",
339
+ cipher: "aes-256-cbc",
340
+ passphrase
341
+ }
342
+ });
343
+ return { publicKey, privateKey };
344
+ }
345
+ function saveKeyPair(keys, publicPath, privatePath) {
346
+ const pubPath = publicPath ?? getPublicKeyPath();
347
+ const privPath = privatePath ?? getPrivateKeyPath();
348
+ (0, import_node_fs2.mkdirSync)((0, import_node_path3.dirname)(pubPath), { recursive: true });
349
+ (0, import_node_fs2.writeFileSync)(pubPath, keys.publicKey, { mode: 420 });
350
+ (0, import_node_fs2.writeFileSync)(privPath, keys.privateKey, { mode: 384 });
351
+ }
352
+ function loadPublicKey(path) {
353
+ return (0, import_node_fs2.readFileSync)(path ?? getPublicKeyPath(), "utf8");
354
+ }
355
+ function loadPrivateKey(path) {
356
+ return (0, import_node_fs2.readFileSync)(path ?? getPrivateKeyPath(), "utf8");
357
+ }
358
+
359
+ // src/auth/mandate.ts
360
+ init_cjs_shims();
361
+ var import_node_crypto4 = require("crypto");
362
+ function hashTransactionDetails(details) {
363
+ const canonical = JSON.stringify({
364
+ txId: details.txId,
365
+ merchant: details.merchant,
366
+ amount: details.amount,
367
+ description: details.description,
368
+ timestamp: details.timestamp
369
+ });
370
+ return (0, import_node_crypto4.createHash)("sha256").update(canonical).digest("hex");
371
+ }
372
+ function createMandate(txDetails, privateKeyPem, passphrase) {
373
+ const txHash = hashTransactionDetails(txDetails);
374
+ const data = Buffer.from(txHash);
375
+ const privateKey = (0, import_node_crypto4.createPrivateKey)({
376
+ key: privateKeyPem,
377
+ format: "pem",
378
+ type: "pkcs8",
379
+ passphrase
380
+ });
381
+ const signature = (0, import_node_crypto4.sign)(null, data, privateKey);
382
+ const publicKey = (0, import_node_crypto4.createPublicKey)(privateKey);
383
+ const publicKeyPem = publicKey.export({ type: "spki", format: "pem" });
384
+ return {
385
+ txId: txDetails.txId,
386
+ txHash,
387
+ signature: signature.toString("base64"),
388
+ publicKey: publicKeyPem,
389
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
390
+ };
391
+ }
392
+ function verifyMandate(mandate, txDetails) {
393
+ try {
394
+ const txHash = hashTransactionDetails(txDetails);
395
+ if (txHash !== mandate.txHash) return false;
396
+ const data = Buffer.from(txHash);
397
+ const signature = Buffer.from(mandate.signature, "base64");
398
+ const publicKey = (0, import_node_crypto4.createPublicKey)({
399
+ key: mandate.publicKey,
400
+ format: "pem",
401
+ type: "spki"
402
+ });
403
+ return (0, import_node_crypto4.verify)(null, data, publicKey, signature);
404
+ } catch {
405
+ return false;
406
+ }
407
+ }
408
+
409
+ // src/budget/budget.ts
410
+ init_cjs_shims();
411
+ var import_node_fs3 = require("fs");
412
+ var import_node_path4 = require("path");
413
+ init_errors();
414
+ var BudgetManager = class {
415
+ walletPath;
416
+ constructor(walletPath) {
417
+ this.walletPath = walletPath ?? getWalletPath();
418
+ }
419
+ getWallet() {
420
+ try {
421
+ const data = (0, import_node_fs3.readFileSync)(this.walletPath, "utf8");
422
+ return JSON.parse(data);
423
+ } catch {
424
+ throw new NotSetupError();
425
+ }
426
+ }
427
+ saveWallet(wallet) {
428
+ (0, import_node_fs3.mkdirSync)((0, import_node_path4.dirname)(this.walletPath), { recursive: true });
429
+ (0, import_node_fs3.writeFileSync)(this.walletPath, JSON.stringify(wallet, null, 2), { mode: 384 });
430
+ }
431
+ setBudget(amount) {
432
+ let wallet;
433
+ try {
434
+ wallet = this.getWallet();
435
+ } catch {
436
+ wallet = { budget: 0, balance: 0, limitPerTx: 0, spent: 0 };
437
+ }
438
+ wallet.budget = amount;
439
+ wallet.balance = amount - wallet.spent;
440
+ this.saveWallet(wallet);
441
+ }
442
+ setLimitPerTx(limit) {
443
+ const wallet = this.getWallet();
444
+ wallet.limitPerTx = limit;
445
+ this.saveWallet(wallet);
446
+ }
447
+ deductBalance(amount) {
448
+ const wallet = this.getWallet();
449
+ if (amount > wallet.balance) {
450
+ throw new InsufficientBalanceError(amount, wallet.balance);
451
+ }
452
+ wallet.balance -= amount;
453
+ wallet.spent += amount;
454
+ this.saveWallet(wallet);
455
+ }
456
+ checkProposal(amount) {
457
+ const wallet = this.getWallet();
458
+ if (amount > wallet.balance) {
459
+ throw new InsufficientBalanceError(amount, wallet.balance);
460
+ }
461
+ if (wallet.limitPerTx > 0 && amount > wallet.limitPerTx) {
462
+ throw new ExceedsTxLimitError(amount, wallet.limitPerTx);
463
+ }
464
+ }
465
+ addFunds(amount) {
466
+ const wallet = this.getWallet();
467
+ wallet.budget += amount;
468
+ wallet.balance += amount;
469
+ this.saveWallet(wallet);
470
+ return wallet;
471
+ }
472
+ getBalance() {
473
+ const wallet = this.getWallet();
474
+ return {
475
+ budget: wallet.budget,
476
+ balance: wallet.balance,
477
+ limitPerTx: wallet.limitPerTx,
478
+ spent: wallet.spent
479
+ };
480
+ }
481
+ initWallet(budget, limitPerTx = 0) {
482
+ const wallet = { budget, balance: budget, limitPerTx, spent: 0 };
483
+ this.saveWallet(wallet);
484
+ }
485
+ };
486
+
487
+ // src/transactions/manager.ts
488
+ init_cjs_shims();
489
+ var import_node_fs4 = require("fs");
490
+ var import_node_path5 = require("path");
491
+ var TransactionManager = class {
492
+ txPath;
493
+ constructor(txPath) {
494
+ this.txPath = txPath ?? getTransactionsPath();
495
+ }
496
+ loadAll() {
497
+ try {
498
+ const data = (0, import_node_fs4.readFileSync)(this.txPath, "utf8");
499
+ return JSON.parse(data);
500
+ } catch {
501
+ return [];
502
+ }
503
+ }
504
+ saveAll(transactions) {
505
+ (0, import_node_fs4.mkdirSync)((0, import_node_path5.dirname)(this.txPath), { recursive: true });
506
+ (0, import_node_fs4.writeFileSync)(this.txPath, JSON.stringify(transactions, null, 2), { mode: 384 });
507
+ }
508
+ propose(options) {
509
+ const transactions = this.loadAll();
510
+ const tx = {
511
+ id: generateTxId(),
512
+ status: "pending",
513
+ merchant: options.merchant,
514
+ amount: options.amount,
515
+ description: options.description,
516
+ url: options.url,
517
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
518
+ };
519
+ transactions.push(tx);
520
+ this.saveAll(transactions);
521
+ return tx;
522
+ }
523
+ get(txId) {
524
+ return this.loadAll().find((tx) => tx.id === txId);
525
+ }
526
+ approve(txId, mandate) {
527
+ const transactions = this.loadAll();
528
+ const tx = transactions.find((t) => t.id === txId);
529
+ if (!tx) throw new Error(`Transaction ${txId} not found.`);
530
+ if (tx.status !== "pending") throw new Error(`Cannot approve transaction in '${tx.status}' state.`);
531
+ tx.status = "approved";
532
+ tx.mandate = mandate;
533
+ this.saveAll(transactions);
534
+ }
535
+ reject(txId, reason) {
536
+ const transactions = this.loadAll();
537
+ const tx = transactions.find((t) => t.id === txId);
538
+ if (!tx) throw new Error(`Transaction ${txId} not found.`);
539
+ if (tx.status !== "pending") throw new Error(`Cannot reject transaction in '${tx.status}' state.`);
540
+ tx.status = "rejected";
541
+ tx.rejectionReason = reason;
542
+ this.saveAll(transactions);
543
+ }
544
+ markExecuting(txId) {
545
+ const transactions = this.loadAll();
546
+ const tx = transactions.find((t) => t.id === txId);
547
+ if (!tx) throw new Error(`Transaction ${txId} not found.`);
548
+ if (tx.status !== "approved") throw new Error(`Cannot execute transaction in '${tx.status}' state.`);
549
+ tx.status = "executing";
550
+ this.saveAll(transactions);
551
+ }
552
+ markCompleted(txId, receipt) {
553
+ const transactions = this.loadAll();
554
+ const tx = transactions.find((t) => t.id === txId);
555
+ if (!tx) throw new Error(`Transaction ${txId} not found.`);
556
+ if (tx.status !== "executing") throw new Error(`Cannot complete transaction in '${tx.status}' state.`);
557
+ tx.status = "completed";
558
+ tx.receipt = receipt;
559
+ this.saveAll(transactions);
560
+ }
561
+ markFailed(txId, error) {
562
+ const transactions = this.loadAll();
563
+ const tx = transactions.find((t) => t.id === txId);
564
+ if (!tx) throw new Error(`Transaction ${txId} not found.`);
565
+ if (tx.status !== "executing") throw new Error(`Cannot fail transaction in '${tx.status}' state.`);
566
+ tx.status = "failed";
567
+ tx.error = error;
568
+ this.saveAll(transactions);
569
+ }
570
+ list() {
571
+ return this.loadAll();
572
+ }
573
+ getPending() {
574
+ return this.loadAll().filter((tx) => tx.status === "pending");
575
+ }
576
+ getHistory() {
577
+ return this.loadAll().filter((tx) => tx.status !== "pending");
578
+ }
579
+ };
580
+
581
+ // src/index.ts
582
+ init_poller();
583
+
584
+ // src/audit/logger.ts
585
+ init_cjs_shims();
586
+ var import_node_fs5 = require("fs");
587
+ var import_node_path6 = require("path");
588
+ var AuditLogger = class {
589
+ logPath;
590
+ constructor(logPath) {
591
+ this.logPath = logPath ?? getAuditPath();
592
+ }
593
+ log(action, details) {
594
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
595
+ const detailsStr = JSON.stringify(details);
596
+ const entry = `${timestamp} ${action} ${detailsStr}
597
+ `;
598
+ (0, import_node_fs5.mkdirSync)((0, import_node_path6.dirname)(this.logPath), { recursive: true });
599
+ (0, import_node_fs5.appendFileSync)(this.logPath, entry, { mode: 384 });
600
+ }
601
+ getLog() {
602
+ try {
603
+ const data = (0, import_node_fs5.readFileSync)(this.logPath, "utf8");
604
+ return data.trim().split("\n").filter(Boolean);
605
+ } catch {
606
+ return [];
607
+ }
608
+ }
609
+ };
610
+
611
+ // src/executor/executor.ts
612
+ init_cjs_shims();
613
+ var import_stagehand = require("@browserbasehq/stagehand");
614
+ init_errors();
615
+
616
+ // src/executor/placeholder.ts
617
+ init_cjs_shims();
618
+ var PLACEHOLDER_MAP = {
619
+ card_number: "{{card_number}}",
620
+ cardholder_name: "{{cardholder_name}}",
621
+ card_expiry: "{{card_expiry}}",
622
+ card_cvv: "{{card_cvv}}",
623
+ billing_street: "{{billing_street}}",
624
+ billing_city: "{{billing_city}}",
625
+ billing_state: "{{billing_state}}",
626
+ billing_zip: "{{billing_zip}}",
627
+ billing_country: "{{billing_country}}",
628
+ shipping_street: "{{shipping_street}}",
629
+ shipping_city: "{{shipping_city}}",
630
+ shipping_state: "{{shipping_state}}",
631
+ shipping_zip: "{{shipping_zip}}",
632
+ shipping_country: "{{shipping_country}}",
633
+ email: "{{email}}",
634
+ phone: "{{phone}}"
635
+ };
636
+ function getPlaceholderVariables() {
637
+ return {
638
+ card_number: "%card_number%",
639
+ cardholder_name: "%cardholder_name%",
640
+ card_expiry: "%card_expiry%",
641
+ card_cvv: "%card_cvv%",
642
+ billing_street: "%billing_street%",
643
+ billing_city: "%billing_city%",
644
+ billing_state: "%billing_state%",
645
+ billing_zip: "%billing_zip%",
646
+ billing_country: "%billing_country%",
647
+ shipping_street: "%shipping_street%",
648
+ shipping_city: "%shipping_city%",
649
+ shipping_state: "%shipping_state%",
650
+ shipping_zip: "%shipping_zip%",
651
+ shipping_country: "%shipping_country%",
652
+ email: "%email%",
653
+ phone: "%phone%"
654
+ };
655
+ }
656
+ function credentialsToSwapMap(creds) {
657
+ return {
658
+ [PLACEHOLDER_MAP.card_number]: creds.card.number,
659
+ [PLACEHOLDER_MAP.cardholder_name]: creds.name,
660
+ [PLACEHOLDER_MAP.card_expiry]: creds.card.expiry,
661
+ [PLACEHOLDER_MAP.card_cvv]: creds.card.cvv,
662
+ [PLACEHOLDER_MAP.billing_street]: creds.billingAddress.street,
663
+ [PLACEHOLDER_MAP.billing_city]: creds.billingAddress.city,
664
+ [PLACEHOLDER_MAP.billing_state]: creds.billingAddress.state,
665
+ [PLACEHOLDER_MAP.billing_zip]: creds.billingAddress.zip,
666
+ [PLACEHOLDER_MAP.billing_country]: creds.billingAddress.country,
667
+ [PLACEHOLDER_MAP.shipping_street]: creds.shippingAddress.street,
668
+ [PLACEHOLDER_MAP.shipping_city]: creds.shippingAddress.city,
669
+ [PLACEHOLDER_MAP.shipping_state]: creds.shippingAddress.state,
670
+ [PLACEHOLDER_MAP.shipping_zip]: creds.shippingAddress.zip,
671
+ [PLACEHOLDER_MAP.shipping_country]: creds.shippingAddress.country,
672
+ [PLACEHOLDER_MAP.email]: creds.email,
673
+ [PLACEHOLDER_MAP.phone]: creds.phone
674
+ };
675
+ }
676
+
677
+ // src/executor/executor.ts
678
+ var PurchaseExecutor = class {
679
+ config;
680
+ stagehand = null;
681
+ constructor(config) {
682
+ this.config = {
683
+ browserbaseApiKey: config?.browserbaseApiKey ?? process.env.BROWSERBASE_API_KEY,
684
+ browserbaseProjectId: config?.browserbaseProjectId ?? process.env.BROWSERBASE_PROJECT_ID,
685
+ modelApiKey: config?.modelApiKey ?? process.env.ANTHROPIC_API_KEY
686
+ };
687
+ }
688
+ createStagehand() {
689
+ return new import_stagehand.Stagehand({
690
+ env: "BROWSERBASE",
691
+ apiKey: this.config.browserbaseApiKey,
692
+ projectId: this.config.browserbaseProjectId,
693
+ model: this.config.modelApiKey ? { modelName: "claude-3-7-sonnet-latest", apiKey: this.config.modelApiKey } : void 0,
694
+ browserbaseSessionCreateParams: {
695
+ browserSettings: {
696
+ recordSession: false
697
+ }
698
+ }
699
+ });
700
+ }
701
+ /**
702
+ * Phase 1: Open browser, navigate to URL, extract price and product info.
703
+ * Keeps the session alive for fillAndComplete().
704
+ */
705
+ async openAndDiscover(url, instructions) {
706
+ this.stagehand = this.createStagehand();
707
+ await this.stagehand.init();
708
+ const page = this.stagehand.context.activePage();
709
+ await page.goto(url);
710
+ const addToCartInstructions = instructions ? `On this product page: ${instructions}. Then add the item to the cart.` : "Add this product to the cart.";
711
+ await this.stagehand.act(addToCartInstructions);
712
+ const extracted = await this.stagehand.extract(
713
+ 'Extract the product name and the price (as a number without $ sign) from the page or cart. Return JSON: { "price": <number>, "productName": "<string>" }'
714
+ );
715
+ const price = parseFloat(extracted?.price ?? extracted?.extraction?.price ?? "0");
716
+ const productName = extracted?.productName ?? extracted?.extraction?.productName ?? "Unknown Product";
717
+ return { price, productName };
718
+ }
719
+ /**
720
+ * Phase 2: Proceed to checkout, fill forms, swap credentials, and submit.
721
+ * Must be called after openAndDiscover().
722
+ */
723
+ async fillAndComplete(credentials) {
724
+ if (!this.stagehand) {
725
+ throw new CheckoutFailedError("No active session. Call openAndDiscover() first.");
726
+ }
727
+ try {
728
+ await this.stagehand.act("Proceed to checkout from the cart.");
729
+ const variables = getPlaceholderVariables();
730
+ await this.stagehand.act(
731
+ `Fill in the checkout form with these values:
732
+ Name: ${variables.cardholder_name}
733
+ Card Number: ${variables.card_number}
734
+ Expiry: ${variables.card_expiry}
735
+ CVV: ${variables.card_cvv}
736
+ Email: ${variables.email}
737
+ Phone: ${variables.phone}
738
+ Billing Street: ${variables.billing_street}
739
+ Billing City: ${variables.billing_city}
740
+ Billing State: ${variables.billing_state}
741
+ Billing ZIP: ${variables.billing_zip}
742
+ Billing Country: ${variables.billing_country}
743
+ Shipping Street: ${variables.shipping_street}
744
+ Shipping City: ${variables.shipping_city}
745
+ Shipping State: ${variables.shipping_state}
746
+ Shipping ZIP: ${variables.shipping_zip}
747
+ Shipping Country: ${variables.shipping_country}`,
748
+ { variables }
749
+ );
750
+ const swapMap = credentialsToSwapMap(credentials);
751
+ const page = this.stagehand.context.activePage();
752
+ await page.evaluate((map) => {
753
+ const inputs = document.querySelectorAll("input, textarea, select");
754
+ for (const input of inputs) {
755
+ const el = input;
756
+ for (const [placeholder, value] of Object.entries(map)) {
757
+ if (el.value === placeholder) {
758
+ el.value = value;
759
+ el.dispatchEvent(new Event("input", { bubbles: true }));
760
+ el.dispatchEvent(new Event("change", { bubbles: true }));
761
+ }
762
+ }
763
+ }
764
+ const submitBtn = document.querySelector('button[type="submit"], input[type="submit"]');
765
+ if (submitBtn) submitBtn.click();
766
+ }, swapMap);
767
+ await page.waitForTimeout(5e3);
768
+ const result = await this.stagehand.extract(
769
+ "Extract the order confirmation number or ID from the page"
770
+ );
771
+ const confirmationId = result?.extraction;
772
+ if (!confirmationId || confirmationId === "null" || confirmationId === "UNKNOWN") {
773
+ throw new CheckoutFailedError("No order confirmation found. Checkout may not have completed.");
774
+ }
775
+ return {
776
+ success: true,
777
+ confirmationId
778
+ };
779
+ } catch (err) {
780
+ if (err instanceof CheckoutFailedError) throw err;
781
+ const message = err instanceof Error ? err.message : "Unknown checkout error";
782
+ throw new CheckoutFailedError(message);
783
+ }
784
+ }
785
+ /**
786
+ * Convenience: single-shot execute (navigate + checkout in one call).
787
+ * Used by AgentPay facade when amount is already known.
788
+ */
789
+ async execute(tx, credentials) {
790
+ this.stagehand = this.createStagehand();
791
+ try {
792
+ await this.stagehand.init();
793
+ const page = this.stagehand.context.activePage();
794
+ await page.goto(tx.url);
795
+ const variables = getPlaceholderVariables();
796
+ await this.stagehand.act(
797
+ `Find the product and proceed to checkout. Fill in the checkout form with these values:
798
+ Name: ${variables.cardholder_name}
799
+ Card Number: ${variables.card_number}
800
+ Expiry: ${variables.card_expiry}
801
+ CVV: ${variables.card_cvv}
802
+ Email: ${variables.email}
803
+ Phone: ${variables.phone}
804
+ Billing Street: ${variables.billing_street}
805
+ Billing City: ${variables.billing_city}
806
+ Billing State: ${variables.billing_state}
807
+ Billing ZIP: ${variables.billing_zip}
808
+ Billing Country: ${variables.billing_country}
809
+ Shipping Street: ${variables.shipping_street}
810
+ Shipping City: ${variables.shipping_city}
811
+ Shipping State: ${variables.shipping_state}
812
+ Shipping ZIP: ${variables.shipping_zip}
813
+ Shipping Country: ${variables.shipping_country}`,
814
+ { variables }
815
+ );
816
+ const swapMap = credentialsToSwapMap(credentials);
817
+ await page.evaluate((map) => {
818
+ const inputs = document.querySelectorAll("input, textarea, select");
819
+ for (const input of inputs) {
820
+ const el = input;
821
+ for (const [placeholder, value] of Object.entries(map)) {
822
+ if (el.value === placeholder) {
823
+ el.value = value;
824
+ el.dispatchEvent(new Event("input", { bubbles: true }));
825
+ el.dispatchEvent(new Event("change", { bubbles: true }));
826
+ }
827
+ }
828
+ }
829
+ const submitBtn = document.querySelector('button[type="submit"], input[type="submit"]');
830
+ if (submitBtn) submitBtn.click();
831
+ }, swapMap);
832
+ await page.waitForTimeout(5e3);
833
+ const result = await this.stagehand.extract(
834
+ "Extract the order confirmation number or ID from the page"
835
+ );
836
+ const confirmationId = result?.extraction;
837
+ if (!confirmationId || confirmationId === "null" || confirmationId === "UNKNOWN") {
838
+ throw new CheckoutFailedError("No order confirmation found. Checkout may not have completed.");
839
+ }
840
+ return {
841
+ success: true,
842
+ confirmationId
843
+ };
844
+ } catch (err) {
845
+ if (err instanceof CheckoutFailedError) throw err;
846
+ const message = err instanceof Error ? err.message : "Unknown checkout error";
847
+ throw new CheckoutFailedError(message);
848
+ } finally {
849
+ await this.close();
850
+ }
851
+ }
852
+ async close() {
853
+ try {
854
+ if (this.stagehand) {
855
+ await this.stagehand.close();
856
+ this.stagehand = null;
857
+ }
858
+ } catch {
859
+ }
860
+ }
861
+ };
862
+
863
+ // src/agentpay.ts
864
+ init_cjs_shims();
865
+ var import_node_path7 = require("path");
866
+ init_errors();
867
+ var AgentPay = class {
868
+ home;
869
+ passphrase;
870
+ budgetManager;
871
+ txManager;
872
+ auditLogger;
873
+ executor;
874
+ constructor(options) {
875
+ this.home = options?.home ?? getHomePath();
876
+ this.passphrase = options?.passphrase;
877
+ this.budgetManager = new BudgetManager((0, import_node_path7.join)(this.home, "wallet.json"));
878
+ this.txManager = new TransactionManager((0, import_node_path7.join)(this.home, "transactions.json"));
879
+ this.auditLogger = new AuditLogger((0, import_node_path7.join)(this.home, "audit.log"));
880
+ this.executor = new PurchaseExecutor(options?.executor);
881
+ }
882
+ get wallet() {
883
+ const bm = this.budgetManager;
884
+ return {
885
+ getBalance: () => bm.getBalance(),
886
+ getHistory: () => this.txManager.getHistory(),
887
+ getLimits: () => {
888
+ const w = bm.getBalance();
889
+ return { budget: w.budget, limitPerTx: w.limitPerTx, remaining: w.balance };
890
+ },
891
+ generateFundingQR: async (options) => {
892
+ const QRCode = await import("qrcode");
893
+ const params = new URLSearchParams();
894
+ if (options?.suggestedBudget) params.set("budget", String(options.suggestedBudget));
895
+ if (options?.message) params.set("msg", options.message);
896
+ const baseUrl = process.env.AGENTPAY_WEB_URL ?? "http://localhost:3000";
897
+ const url = `${baseUrl}/setup${params.toString() ? `?${params.toString()}` : ""}`;
898
+ const qrDataUrl = await QRCode.toDataURL(url);
899
+ return { url, qrDataUrl };
900
+ }
901
+ };
902
+ }
903
+ get transactions() {
904
+ return {
905
+ propose: (options) => {
906
+ this.budgetManager.checkProposal(options.amount);
907
+ const tx = this.txManager.propose(options);
908
+ this.auditLogger.log("PROPOSE", { txId: tx.id, merchant: tx.merchant, amount: tx.amount });
909
+ return tx;
910
+ },
911
+ get: (txId) => this.txManager.get(txId),
912
+ waitForApproval: async (txId, options) => {
913
+ const { waitForApproval: waitForApproval2 } = await Promise.resolve().then(() => (init_poller(), poller_exports));
914
+ return waitForApproval2(txId, this.txManager, options);
915
+ },
916
+ execute: async (txId) => {
917
+ const tx = this.txManager.get(txId);
918
+ if (!tx) throw new Error(`Transaction ${txId} not found.`);
919
+ if (tx.status === "completed" || tx.status === "failed") {
920
+ throw new AlreadyExecutedError(txId);
921
+ }
922
+ if (tx.status !== "approved") {
923
+ throw new NotApprovedError(txId);
924
+ }
925
+ if (!tx.mandate) {
926
+ throw new InvalidMandateError("No mandate found on approved transaction.");
927
+ }
928
+ const txDetails = {
929
+ txId: tx.id,
930
+ merchant: tx.merchant,
931
+ amount: tx.amount,
932
+ description: tx.description,
933
+ timestamp: tx.createdAt
934
+ };
935
+ if (!verifyMandate(tx.mandate, txDetails)) {
936
+ throw new InvalidMandateError();
937
+ }
938
+ this.budgetManager.checkProposal(tx.amount);
939
+ this.txManager.markExecuting(txId);
940
+ this.auditLogger.log("EXECUTE", { txId, browserbaseSessionStarted: true });
941
+ try {
942
+ if (!this.passphrase) {
943
+ throw new Error("Passphrase required for execution. Pass it to AgentPay constructor.");
944
+ }
945
+ const vaultPath = (0, import_node_path7.join)(this.home, "credentials.enc");
946
+ const vault = loadVault(vaultPath);
947
+ const credentials = decrypt(vault, this.passphrase);
948
+ const result = await this.executor.execute(tx, credentials);
949
+ const receipt = {
950
+ id: `rcpt_${txId.replace("tx_", "")}`,
951
+ merchant: tx.merchant,
952
+ amount: tx.amount,
953
+ confirmationId: result.confirmationId ?? "UNKNOWN",
954
+ completedAt: (/* @__PURE__ */ new Date()).toISOString()
955
+ };
956
+ this.txManager.markCompleted(txId, receipt);
957
+ this.budgetManager.deductBalance(tx.amount);
958
+ this.auditLogger.log("COMPLETE", { txId, confirmationId: receipt.confirmationId });
959
+ return receipt;
960
+ } catch (err) {
961
+ this.txManager.markFailed(txId, err instanceof Error ? err.message : "Unknown error");
962
+ this.auditLogger.log("FAILED", { txId, error: err instanceof Error ? err.message : "Unknown" });
963
+ throw err;
964
+ }
965
+ },
966
+ getReceipt: (txId) => {
967
+ const tx = this.txManager.get(txId);
968
+ return tx?.receipt;
969
+ }
970
+ };
971
+ }
972
+ get audit() {
973
+ return { getLog: () => this.auditLogger.getLog() };
974
+ }
975
+ status() {
976
+ try {
977
+ const wallet = this.budgetManager.getBalance();
978
+ const pending = this.txManager.getPending();
979
+ const recent = this.txManager.list().slice(-5);
980
+ return {
981
+ balance: wallet.balance,
982
+ budget: wallet.budget,
983
+ limitPerTx: wallet.limitPerTx,
984
+ pending,
985
+ recent,
986
+ isSetup: true
987
+ };
988
+ } catch {
989
+ return {
990
+ balance: 0,
991
+ budget: 0,
992
+ limitPerTx: 0,
993
+ pending: [],
994
+ recent: [],
995
+ isSetup: false
996
+ };
997
+ }
998
+ }
999
+ };
1000
+
1001
+ // src/index.ts
1002
+ var VERSION = "0.1.0";
1003
+ // Annotate the CommonJS export names for ESM import in node:
1004
+ 0 && (module.exports = {
1005
+ AgentPay,
1006
+ AlreadyExecutedError,
1007
+ AuditLogger,
1008
+ BudgetManager,
1009
+ CheckoutFailedError,
1010
+ DecryptError,
1011
+ ExceedsTxLimitError,
1012
+ InsufficientBalanceError,
1013
+ InvalidMandateError,
1014
+ NotApprovedError,
1015
+ NotSetupError,
1016
+ PLACEHOLDER_MAP,
1017
+ PurchaseExecutor,
1018
+ TimeoutError,
1019
+ TransactionManager,
1020
+ VERSION,
1021
+ createMandate,
1022
+ credentialsToSwapMap,
1023
+ decrypt,
1024
+ encrypt,
1025
+ formatCurrency,
1026
+ formatStatus,
1027
+ formatTable,
1028
+ formatTimestamp,
1029
+ generateKeyPair,
1030
+ generateTxId,
1031
+ getAuditPath,
1032
+ getCredentialsPath,
1033
+ getHomePath,
1034
+ getKeysPath,
1035
+ getPlaceholderVariables,
1036
+ getTransactionsPath,
1037
+ getWalletPath,
1038
+ loadPrivateKey,
1039
+ loadPublicKey,
1040
+ loadVault,
1041
+ saveKeyPair,
1042
+ saveVault,
1043
+ verifyMandate,
1044
+ waitForApproval
1045
+ });
1046
+ //# sourceMappingURL=index.cjs.map