@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/cli.js ADDED
@@ -0,0 +1,2728 @@
1
+ #!/usr/bin/env node
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __esm = (fn, res) => function __init() {
5
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
+ };
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+
12
+ // ../../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/esm_shims.js
13
+ import path from "path";
14
+ import { fileURLToPath } from "url";
15
+ var init_esm_shims = __esm({
16
+ "../../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/esm_shims.js"() {
17
+ "use strict";
18
+ }
19
+ });
20
+
21
+ // src/errors.ts
22
+ var NotSetupError, DecryptError, InsufficientBalanceError, ExceedsTxLimitError, NotApprovedError, InvalidMandateError, AlreadyExecutedError, CheckoutFailedError, TimeoutError;
23
+ var init_errors = __esm({
24
+ "src/errors.ts"() {
25
+ "use strict";
26
+ init_esm_shims();
27
+ NotSetupError = class extends Error {
28
+ code = "NOT_SETUP";
29
+ constructor(message = "AgentPay has not been set up yet. Run `agentpay setup` first.") {
30
+ super(message);
31
+ this.name = "NotSetupError";
32
+ }
33
+ };
34
+ DecryptError = class extends Error {
35
+ code = "DECRYPT_FAILED";
36
+ constructor(message = "Failed to decrypt credentials. Wrong passphrase or corrupted file.") {
37
+ super(message);
38
+ this.name = "DecryptError";
39
+ }
40
+ };
41
+ InsufficientBalanceError = class extends Error {
42
+ code = "INSUFFICIENT_BALANCE";
43
+ constructor(amount, balance) {
44
+ 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.";
45
+ super(msg);
46
+ this.name = "InsufficientBalanceError";
47
+ }
48
+ };
49
+ ExceedsTxLimitError = class extends Error {
50
+ code = "EXCEEDS_TX_LIMIT";
51
+ constructor(amount, limit) {
52
+ 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.";
53
+ super(msg);
54
+ this.name = "ExceedsTxLimitError";
55
+ }
56
+ };
57
+ NotApprovedError = class extends Error {
58
+ code = "NOT_APPROVED";
59
+ constructor(txId) {
60
+ super(txId ? `Transaction ${txId} has not been approved.` : "Transaction has not been approved.");
61
+ this.name = "NotApprovedError";
62
+ }
63
+ };
64
+ InvalidMandateError = class extends Error {
65
+ code = "INVALID_MANDATE";
66
+ constructor(message = "Purchase mandate signature verification failed.") {
67
+ super(message);
68
+ this.name = "InvalidMandateError";
69
+ }
70
+ };
71
+ AlreadyExecutedError = class extends Error {
72
+ code = "ALREADY_EXECUTED";
73
+ constructor(txId) {
74
+ super(txId ? `Transaction ${txId} has already been executed.` : "Transaction has already been executed.");
75
+ this.name = "AlreadyExecutedError";
76
+ }
77
+ };
78
+ CheckoutFailedError = class extends Error {
79
+ code = "CHECKOUT_FAILED";
80
+ constructor(message = "Failed to complete checkout.") {
81
+ super(message);
82
+ this.name = "CheckoutFailedError";
83
+ }
84
+ };
85
+ TimeoutError = class extends Error {
86
+ code = "TIMEOUT";
87
+ constructor(message = "Operation timed out.") {
88
+ super(message);
89
+ this.name = "TimeoutError";
90
+ }
91
+ };
92
+ }
93
+ });
94
+
95
+ // src/utils/ids.ts
96
+ import { randomBytes } from "crypto";
97
+ function generateTxId() {
98
+ return `tx_${randomBytes(4).toString("hex")}`;
99
+ }
100
+ var init_ids = __esm({
101
+ "src/utils/ids.ts"() {
102
+ "use strict";
103
+ init_esm_shims();
104
+ }
105
+ });
106
+
107
+ // src/utils/display.ts
108
+ function formatCurrency(amount) {
109
+ return `$${amount.toFixed(2)}`;
110
+ }
111
+ function formatTimestamp(iso) {
112
+ const d = new Date(iso);
113
+ return d.toLocaleString("en-US", {
114
+ month: "short",
115
+ day: "numeric",
116
+ hour: "2-digit",
117
+ minute: "2-digit"
118
+ });
119
+ }
120
+ function formatStatus(data) {
121
+ const lines = [
122
+ "AgentPay Status",
123
+ "\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500",
124
+ `Balance: ${formatCurrency(data.balance)} / ${formatCurrency(data.budget)}`,
125
+ `Per-tx limit: ${formatCurrency(data.limitPerTx)}`,
126
+ `Pending purchases: ${data.pending.length}`
127
+ ];
128
+ if (data.recent.length > 0) {
129
+ lines.push("", "Recent:");
130
+ for (const tx of data.recent) {
131
+ const status = `[${tx.status}]`.padEnd(12);
132
+ lines.push(` ${status} ${tx.id} ${tx.merchant.padEnd(12)} ${formatCurrency(tx.amount).padStart(8)} ${tx.description}`);
133
+ }
134
+ }
135
+ return lines.join("\n");
136
+ }
137
+ var init_display = __esm({
138
+ "src/utils/display.ts"() {
139
+ "use strict";
140
+ init_esm_shims();
141
+ }
142
+ });
143
+
144
+ // src/utils/paths.ts
145
+ import { homedir } from "os";
146
+ import { join } from "path";
147
+ function getHomePath() {
148
+ return process.env.AGENTPAY_HOME || join(homedir(), ".agentpay");
149
+ }
150
+ function getCredentialsPath() {
151
+ return join(getHomePath(), "credentials.enc");
152
+ }
153
+ function getKeysPath() {
154
+ return join(getHomePath(), "keys");
155
+ }
156
+ function getPublicKeyPath() {
157
+ return join(getKeysPath(), "public.pem");
158
+ }
159
+ function getPrivateKeyPath() {
160
+ return join(getKeysPath(), "private.pem");
161
+ }
162
+ function getWalletPath() {
163
+ return join(getHomePath(), "wallet.json");
164
+ }
165
+ function getTransactionsPath() {
166
+ return join(getHomePath(), "transactions.json");
167
+ }
168
+ function getAuditPath() {
169
+ return join(getHomePath(), "audit.log");
170
+ }
171
+ var init_paths = __esm({
172
+ "src/utils/paths.ts"() {
173
+ "use strict";
174
+ init_esm_shims();
175
+ }
176
+ });
177
+
178
+ // src/vault/vault.ts
179
+ import { pbkdf2Sync, randomBytes as randomBytes2, createCipheriv, createDecipheriv } from "crypto";
180
+ import { readFileSync, writeFileSync, mkdirSync } from "fs";
181
+ import { dirname } from "path";
182
+ function deriveKey(passphrase, salt) {
183
+ return pbkdf2Sync(passphrase, salt, PBKDF2_ITERATIONS, KEY_LENGTH, PBKDF2_DIGEST);
184
+ }
185
+ function encrypt(credentials, passphrase) {
186
+ const salt = randomBytes2(SALT_LENGTH);
187
+ const iv = randomBytes2(IV_LENGTH);
188
+ const key = deriveKey(passphrase, salt);
189
+ const cipher = createCipheriv(ALGORITHM, key, iv);
190
+ const plaintext = JSON.stringify(credentials);
191
+ const encrypted = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
192
+ const authTag = cipher.getAuthTag();
193
+ return {
194
+ ciphertext: Buffer.concat([encrypted, authTag]).toString("base64"),
195
+ salt: salt.toString("base64"),
196
+ iv: iv.toString("base64")
197
+ };
198
+ }
199
+ function decrypt(vault, passphrase) {
200
+ try {
201
+ const salt = Buffer.from(vault.salt, "base64");
202
+ const iv = Buffer.from(vault.iv, "base64");
203
+ const data = Buffer.from(vault.ciphertext, "base64");
204
+ const key = deriveKey(passphrase, salt);
205
+ const authTag = data.subarray(data.length - 16);
206
+ const ciphertext = data.subarray(0, data.length - 16);
207
+ const decipher = createDecipheriv(ALGORITHM, key, iv);
208
+ decipher.setAuthTag(authTag);
209
+ const decrypted = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
210
+ return JSON.parse(decrypted.toString("utf8"));
211
+ } catch {
212
+ throw new DecryptError();
213
+ }
214
+ }
215
+ function saveVault(vault, path2) {
216
+ const filePath = path2 ?? getCredentialsPath();
217
+ mkdirSync(dirname(filePath), { recursive: true });
218
+ writeFileSync(filePath, JSON.stringify(vault, null, 2), { mode: 384 });
219
+ }
220
+ function loadVault(path2) {
221
+ const filePath = path2 ?? getCredentialsPath();
222
+ try {
223
+ const data = readFileSync(filePath, "utf8");
224
+ return JSON.parse(data);
225
+ } catch {
226
+ throw new NotSetupError();
227
+ }
228
+ }
229
+ var ALGORITHM, PBKDF2_ITERATIONS, PBKDF2_DIGEST, KEY_LENGTH, SALT_LENGTH, IV_LENGTH;
230
+ var init_vault = __esm({
231
+ "src/vault/vault.ts"() {
232
+ "use strict";
233
+ init_esm_shims();
234
+ init_errors();
235
+ init_paths();
236
+ ALGORITHM = "aes-256-gcm";
237
+ PBKDF2_ITERATIONS = 1e5;
238
+ PBKDF2_DIGEST = "sha512";
239
+ KEY_LENGTH = 32;
240
+ SALT_LENGTH = 32;
241
+ IV_LENGTH = 16;
242
+ }
243
+ });
244
+
245
+ // src/auth/keypair.ts
246
+ import { generateKeyPairSync } from "crypto";
247
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, mkdirSync as mkdirSync2 } from "fs";
248
+ import { dirname as dirname2 } from "path";
249
+ function generateKeyPair(passphrase) {
250
+ const { publicKey, privateKey } = generateKeyPairSync("ed25519", {
251
+ publicKeyEncoding: { type: "spki", format: "pem" },
252
+ privateKeyEncoding: {
253
+ type: "pkcs8",
254
+ format: "pem",
255
+ cipher: "aes-256-cbc",
256
+ passphrase
257
+ }
258
+ });
259
+ return { publicKey, privateKey };
260
+ }
261
+ function saveKeyPair(keys, publicPath, privatePath) {
262
+ const pubPath = publicPath ?? getPublicKeyPath();
263
+ const privPath = privatePath ?? getPrivateKeyPath();
264
+ mkdirSync2(dirname2(pubPath), { recursive: true });
265
+ writeFileSync2(pubPath, keys.publicKey, { mode: 420 });
266
+ writeFileSync2(privPath, keys.privateKey, { mode: 384 });
267
+ }
268
+ function loadPrivateKey(path2) {
269
+ return readFileSync2(path2 ?? getPrivateKeyPath(), "utf8");
270
+ }
271
+ var init_keypair = __esm({
272
+ "src/auth/keypair.ts"() {
273
+ "use strict";
274
+ init_esm_shims();
275
+ init_paths();
276
+ }
277
+ });
278
+
279
+ // src/auth/mandate.ts
280
+ import { createHash, createPrivateKey, createPublicKey as createPublicKey2, sign, verify } from "crypto";
281
+ function hashTransactionDetails(details) {
282
+ const canonical = JSON.stringify({
283
+ txId: details.txId,
284
+ merchant: details.merchant,
285
+ amount: details.amount,
286
+ description: details.description,
287
+ timestamp: details.timestamp
288
+ });
289
+ return createHash("sha256").update(canonical).digest("hex");
290
+ }
291
+ function createMandate(txDetails, privateKeyPem, passphrase) {
292
+ const txHash = hashTransactionDetails(txDetails);
293
+ const data = Buffer.from(txHash);
294
+ const privateKey = createPrivateKey({
295
+ key: privateKeyPem,
296
+ format: "pem",
297
+ type: "pkcs8",
298
+ passphrase
299
+ });
300
+ const signature = sign(null, data, privateKey);
301
+ const publicKey = createPublicKey2(privateKey);
302
+ const publicKeyPem = publicKey.export({ type: "spki", format: "pem" });
303
+ return {
304
+ txId: txDetails.txId,
305
+ txHash,
306
+ signature: signature.toString("base64"),
307
+ publicKey: publicKeyPem,
308
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
309
+ };
310
+ }
311
+ function verifyMandate(mandate, txDetails) {
312
+ try {
313
+ const txHash = hashTransactionDetails(txDetails);
314
+ if (txHash !== mandate.txHash) return false;
315
+ const data = Buffer.from(txHash);
316
+ const signature = Buffer.from(mandate.signature, "base64");
317
+ const publicKey = createPublicKey2({
318
+ key: mandate.publicKey,
319
+ format: "pem",
320
+ type: "spki"
321
+ });
322
+ return verify(null, data, publicKey, signature);
323
+ } catch {
324
+ return false;
325
+ }
326
+ }
327
+ var init_mandate = __esm({
328
+ "src/auth/mandate.ts"() {
329
+ "use strict";
330
+ init_esm_shims();
331
+ }
332
+ });
333
+
334
+ // src/budget/budget.ts
335
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
336
+ import { dirname as dirname3 } from "path";
337
+ var BudgetManager;
338
+ var init_budget = __esm({
339
+ "src/budget/budget.ts"() {
340
+ "use strict";
341
+ init_esm_shims();
342
+ init_errors();
343
+ init_paths();
344
+ BudgetManager = class {
345
+ walletPath;
346
+ constructor(walletPath) {
347
+ this.walletPath = walletPath ?? getWalletPath();
348
+ }
349
+ getWallet() {
350
+ try {
351
+ const data = readFileSync3(this.walletPath, "utf8");
352
+ return JSON.parse(data);
353
+ } catch {
354
+ throw new NotSetupError();
355
+ }
356
+ }
357
+ saveWallet(wallet) {
358
+ mkdirSync3(dirname3(this.walletPath), { recursive: true });
359
+ writeFileSync3(this.walletPath, JSON.stringify(wallet, null, 2), { mode: 384 });
360
+ }
361
+ setBudget(amount) {
362
+ let wallet;
363
+ try {
364
+ wallet = this.getWallet();
365
+ } catch {
366
+ wallet = { budget: 0, balance: 0, limitPerTx: 0, spent: 0 };
367
+ }
368
+ wallet.budget = amount;
369
+ wallet.balance = amount - wallet.spent;
370
+ this.saveWallet(wallet);
371
+ }
372
+ setLimitPerTx(limit) {
373
+ const wallet = this.getWallet();
374
+ wallet.limitPerTx = limit;
375
+ this.saveWallet(wallet);
376
+ }
377
+ deductBalance(amount) {
378
+ const wallet = this.getWallet();
379
+ if (amount > wallet.balance) {
380
+ throw new InsufficientBalanceError(amount, wallet.balance);
381
+ }
382
+ wallet.balance -= amount;
383
+ wallet.spent += amount;
384
+ this.saveWallet(wallet);
385
+ }
386
+ checkProposal(amount) {
387
+ const wallet = this.getWallet();
388
+ if (amount > wallet.balance) {
389
+ throw new InsufficientBalanceError(amount, wallet.balance);
390
+ }
391
+ if (wallet.limitPerTx > 0 && amount > wallet.limitPerTx) {
392
+ throw new ExceedsTxLimitError(amount, wallet.limitPerTx);
393
+ }
394
+ }
395
+ addFunds(amount) {
396
+ const wallet = this.getWallet();
397
+ wallet.budget += amount;
398
+ wallet.balance += amount;
399
+ this.saveWallet(wallet);
400
+ return wallet;
401
+ }
402
+ getBalance() {
403
+ const wallet = this.getWallet();
404
+ return {
405
+ budget: wallet.budget,
406
+ balance: wallet.balance,
407
+ limitPerTx: wallet.limitPerTx,
408
+ spent: wallet.spent
409
+ };
410
+ }
411
+ initWallet(budget, limitPerTx = 0) {
412
+ const wallet = { budget, balance: budget, limitPerTx, spent: 0 };
413
+ this.saveWallet(wallet);
414
+ }
415
+ };
416
+ }
417
+ });
418
+
419
+ // src/transactions/manager.ts
420
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4 } from "fs";
421
+ import { dirname as dirname4 } from "path";
422
+ var TransactionManager;
423
+ var init_manager = __esm({
424
+ "src/transactions/manager.ts"() {
425
+ "use strict";
426
+ init_esm_shims();
427
+ init_ids();
428
+ init_paths();
429
+ TransactionManager = class {
430
+ txPath;
431
+ constructor(txPath) {
432
+ this.txPath = txPath ?? getTransactionsPath();
433
+ }
434
+ loadAll() {
435
+ try {
436
+ const data = readFileSync4(this.txPath, "utf8");
437
+ return JSON.parse(data);
438
+ } catch {
439
+ return [];
440
+ }
441
+ }
442
+ saveAll(transactions) {
443
+ mkdirSync4(dirname4(this.txPath), { recursive: true });
444
+ writeFileSync4(this.txPath, JSON.stringify(transactions, null, 2), { mode: 384 });
445
+ }
446
+ propose(options) {
447
+ const transactions = this.loadAll();
448
+ const tx = {
449
+ id: generateTxId(),
450
+ status: "pending",
451
+ merchant: options.merchant,
452
+ amount: options.amount,
453
+ description: options.description,
454
+ url: options.url,
455
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
456
+ };
457
+ transactions.push(tx);
458
+ this.saveAll(transactions);
459
+ return tx;
460
+ }
461
+ get(txId) {
462
+ return this.loadAll().find((tx) => tx.id === txId);
463
+ }
464
+ approve(txId, mandate) {
465
+ const transactions = this.loadAll();
466
+ const tx = transactions.find((t) => t.id === txId);
467
+ if (!tx) throw new Error(`Transaction ${txId} not found.`);
468
+ if (tx.status !== "pending") throw new Error(`Cannot approve transaction in '${tx.status}' state.`);
469
+ tx.status = "approved";
470
+ tx.mandate = mandate;
471
+ this.saveAll(transactions);
472
+ }
473
+ reject(txId, reason) {
474
+ const transactions = this.loadAll();
475
+ const tx = transactions.find((t) => t.id === txId);
476
+ if (!tx) throw new Error(`Transaction ${txId} not found.`);
477
+ if (tx.status !== "pending") throw new Error(`Cannot reject transaction in '${tx.status}' state.`);
478
+ tx.status = "rejected";
479
+ tx.rejectionReason = reason;
480
+ this.saveAll(transactions);
481
+ }
482
+ markExecuting(txId) {
483
+ const transactions = this.loadAll();
484
+ const tx = transactions.find((t) => t.id === txId);
485
+ if (!tx) throw new Error(`Transaction ${txId} not found.`);
486
+ if (tx.status !== "approved") throw new Error(`Cannot execute transaction in '${tx.status}' state.`);
487
+ tx.status = "executing";
488
+ this.saveAll(transactions);
489
+ }
490
+ markCompleted(txId, receipt) {
491
+ const transactions = this.loadAll();
492
+ const tx = transactions.find((t) => t.id === txId);
493
+ if (!tx) throw new Error(`Transaction ${txId} not found.`);
494
+ if (tx.status !== "executing") throw new Error(`Cannot complete transaction in '${tx.status}' state.`);
495
+ tx.status = "completed";
496
+ tx.receipt = receipt;
497
+ this.saveAll(transactions);
498
+ }
499
+ markFailed(txId, error) {
500
+ const transactions = this.loadAll();
501
+ const tx = transactions.find((t) => t.id === txId);
502
+ if (!tx) throw new Error(`Transaction ${txId} not found.`);
503
+ if (tx.status !== "executing") throw new Error(`Cannot fail transaction in '${tx.status}' state.`);
504
+ tx.status = "failed";
505
+ tx.error = error;
506
+ this.saveAll(transactions);
507
+ }
508
+ list() {
509
+ return this.loadAll();
510
+ }
511
+ getPending() {
512
+ return this.loadAll().filter((tx) => tx.status === "pending");
513
+ }
514
+ getHistory() {
515
+ return this.loadAll().filter((tx) => tx.status !== "pending");
516
+ }
517
+ };
518
+ }
519
+ });
520
+
521
+ // src/transactions/poller.ts
522
+ var poller_exports = {};
523
+ __export(poller_exports, {
524
+ waitForApproval: () => waitForApproval
525
+ });
526
+ async function waitForApproval(txId, manager, options) {
527
+ const interval = options?.pollInterval ?? 2e3;
528
+ const timeout = options?.timeout ?? 3e5;
529
+ const deadline = Date.now() + timeout;
530
+ while (Date.now() < deadline) {
531
+ const tx = manager.get(txId);
532
+ if (!tx) throw new Error(`Transaction ${txId} not found.`);
533
+ if (tx.status === "approved") return { status: "approved" };
534
+ if (tx.status === "rejected") return { status: "rejected", reason: tx.rejectionReason };
535
+ await new Promise((resolve2) => setTimeout(resolve2, interval));
536
+ }
537
+ throw new TimeoutError(`Timed out waiting for approval of ${txId}`);
538
+ }
539
+ var init_poller = __esm({
540
+ "src/transactions/poller.ts"() {
541
+ "use strict";
542
+ init_esm_shims();
543
+ init_errors();
544
+ }
545
+ });
546
+
547
+ // src/audit/logger.ts
548
+ import { appendFileSync, readFileSync as readFileSync5, mkdirSync as mkdirSync5 } from "fs";
549
+ import { dirname as dirname5 } from "path";
550
+ var AuditLogger;
551
+ var init_logger = __esm({
552
+ "src/audit/logger.ts"() {
553
+ "use strict";
554
+ init_esm_shims();
555
+ init_paths();
556
+ AuditLogger = class {
557
+ logPath;
558
+ constructor(logPath) {
559
+ this.logPath = logPath ?? getAuditPath();
560
+ }
561
+ log(action, details) {
562
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
563
+ const detailsStr = JSON.stringify(details);
564
+ const entry = `${timestamp} ${action} ${detailsStr}
565
+ `;
566
+ mkdirSync5(dirname5(this.logPath), { recursive: true });
567
+ appendFileSync(this.logPath, entry, { mode: 384 });
568
+ }
569
+ getLog() {
570
+ try {
571
+ const data = readFileSync5(this.logPath, "utf8");
572
+ return data.trim().split("\n").filter(Boolean);
573
+ } catch {
574
+ return [];
575
+ }
576
+ }
577
+ };
578
+ }
579
+ });
580
+
581
+ // src/executor/placeholder.ts
582
+ function getPlaceholderVariables() {
583
+ return {
584
+ card_number: "%card_number%",
585
+ cardholder_name: "%cardholder_name%",
586
+ card_expiry: "%card_expiry%",
587
+ card_cvv: "%card_cvv%",
588
+ billing_street: "%billing_street%",
589
+ billing_city: "%billing_city%",
590
+ billing_state: "%billing_state%",
591
+ billing_zip: "%billing_zip%",
592
+ billing_country: "%billing_country%",
593
+ shipping_street: "%shipping_street%",
594
+ shipping_city: "%shipping_city%",
595
+ shipping_state: "%shipping_state%",
596
+ shipping_zip: "%shipping_zip%",
597
+ shipping_country: "%shipping_country%",
598
+ email: "%email%",
599
+ phone: "%phone%"
600
+ };
601
+ }
602
+ function credentialsToSwapMap(creds) {
603
+ return {
604
+ [PLACEHOLDER_MAP.card_number]: creds.card.number,
605
+ [PLACEHOLDER_MAP.cardholder_name]: creds.name,
606
+ [PLACEHOLDER_MAP.card_expiry]: creds.card.expiry,
607
+ [PLACEHOLDER_MAP.card_cvv]: creds.card.cvv,
608
+ [PLACEHOLDER_MAP.billing_street]: creds.billingAddress.street,
609
+ [PLACEHOLDER_MAP.billing_city]: creds.billingAddress.city,
610
+ [PLACEHOLDER_MAP.billing_state]: creds.billingAddress.state,
611
+ [PLACEHOLDER_MAP.billing_zip]: creds.billingAddress.zip,
612
+ [PLACEHOLDER_MAP.billing_country]: creds.billingAddress.country,
613
+ [PLACEHOLDER_MAP.shipping_street]: creds.shippingAddress.street,
614
+ [PLACEHOLDER_MAP.shipping_city]: creds.shippingAddress.city,
615
+ [PLACEHOLDER_MAP.shipping_state]: creds.shippingAddress.state,
616
+ [PLACEHOLDER_MAP.shipping_zip]: creds.shippingAddress.zip,
617
+ [PLACEHOLDER_MAP.shipping_country]: creds.shippingAddress.country,
618
+ [PLACEHOLDER_MAP.email]: creds.email,
619
+ [PLACEHOLDER_MAP.phone]: creds.phone
620
+ };
621
+ }
622
+ var PLACEHOLDER_MAP;
623
+ var init_placeholder = __esm({
624
+ "src/executor/placeholder.ts"() {
625
+ "use strict";
626
+ init_esm_shims();
627
+ PLACEHOLDER_MAP = {
628
+ card_number: "{{card_number}}",
629
+ cardholder_name: "{{cardholder_name}}",
630
+ card_expiry: "{{card_expiry}}",
631
+ card_cvv: "{{card_cvv}}",
632
+ billing_street: "{{billing_street}}",
633
+ billing_city: "{{billing_city}}",
634
+ billing_state: "{{billing_state}}",
635
+ billing_zip: "{{billing_zip}}",
636
+ billing_country: "{{billing_country}}",
637
+ shipping_street: "{{shipping_street}}",
638
+ shipping_city: "{{shipping_city}}",
639
+ shipping_state: "{{shipping_state}}",
640
+ shipping_zip: "{{shipping_zip}}",
641
+ shipping_country: "{{shipping_country}}",
642
+ email: "{{email}}",
643
+ phone: "{{phone}}"
644
+ };
645
+ }
646
+ });
647
+
648
+ // src/executor/executor.ts
649
+ import { Stagehand } from "@browserbasehq/stagehand";
650
+ var PurchaseExecutor;
651
+ var init_executor = __esm({
652
+ "src/executor/executor.ts"() {
653
+ "use strict";
654
+ init_esm_shims();
655
+ init_errors();
656
+ init_placeholder();
657
+ PurchaseExecutor = class {
658
+ config;
659
+ stagehand = null;
660
+ constructor(config) {
661
+ this.config = {
662
+ browserbaseApiKey: config?.browserbaseApiKey ?? process.env.BROWSERBASE_API_KEY,
663
+ browserbaseProjectId: config?.browserbaseProjectId ?? process.env.BROWSERBASE_PROJECT_ID,
664
+ modelApiKey: config?.modelApiKey ?? process.env.ANTHROPIC_API_KEY
665
+ };
666
+ }
667
+ createStagehand() {
668
+ return new Stagehand({
669
+ env: "BROWSERBASE",
670
+ apiKey: this.config.browserbaseApiKey,
671
+ projectId: this.config.browserbaseProjectId,
672
+ model: this.config.modelApiKey ? { modelName: "claude-3-7-sonnet-latest", apiKey: this.config.modelApiKey } : void 0,
673
+ browserbaseSessionCreateParams: {
674
+ browserSettings: {
675
+ recordSession: false
676
+ }
677
+ }
678
+ });
679
+ }
680
+ /**
681
+ * Phase 1: Open browser, navigate to URL, extract price and product info.
682
+ * Keeps the session alive for fillAndComplete().
683
+ */
684
+ async openAndDiscover(url, instructions) {
685
+ this.stagehand = this.createStagehand();
686
+ await this.stagehand.init();
687
+ const page = this.stagehand.context.activePage();
688
+ await page.goto(url);
689
+ const addToCartInstructions = instructions ? `On this product page: ${instructions}. Then add the item to the cart.` : "Add this product to the cart.";
690
+ await this.stagehand.act(addToCartInstructions);
691
+ const extracted = await this.stagehand.extract(
692
+ 'Extract the product name and the price (as a number without $ sign) from the page or cart. Return JSON: { "price": <number>, "productName": "<string>" }'
693
+ );
694
+ const price = parseFloat(extracted?.price ?? extracted?.extraction?.price ?? "0");
695
+ const productName = extracted?.productName ?? extracted?.extraction?.productName ?? "Unknown Product";
696
+ return { price, productName };
697
+ }
698
+ /**
699
+ * Phase 2: Proceed to checkout, fill forms, swap credentials, and submit.
700
+ * Must be called after openAndDiscover().
701
+ */
702
+ async fillAndComplete(credentials) {
703
+ if (!this.stagehand) {
704
+ throw new CheckoutFailedError("No active session. Call openAndDiscover() first.");
705
+ }
706
+ try {
707
+ await this.stagehand.act("Proceed to checkout from the cart.");
708
+ const variables = getPlaceholderVariables();
709
+ await this.stagehand.act(
710
+ `Fill in the checkout form with these values:
711
+ Name: ${variables.cardholder_name}
712
+ Card Number: ${variables.card_number}
713
+ Expiry: ${variables.card_expiry}
714
+ CVV: ${variables.card_cvv}
715
+ Email: ${variables.email}
716
+ Phone: ${variables.phone}
717
+ Billing Street: ${variables.billing_street}
718
+ Billing City: ${variables.billing_city}
719
+ Billing State: ${variables.billing_state}
720
+ Billing ZIP: ${variables.billing_zip}
721
+ Billing Country: ${variables.billing_country}
722
+ Shipping Street: ${variables.shipping_street}
723
+ Shipping City: ${variables.shipping_city}
724
+ Shipping State: ${variables.shipping_state}
725
+ Shipping ZIP: ${variables.shipping_zip}
726
+ Shipping Country: ${variables.shipping_country}`,
727
+ { variables }
728
+ );
729
+ const swapMap = credentialsToSwapMap(credentials);
730
+ const page = this.stagehand.context.activePage();
731
+ await page.evaluate((map) => {
732
+ const inputs = document.querySelectorAll("input, textarea, select");
733
+ for (const input of inputs) {
734
+ const el = input;
735
+ for (const [placeholder, value] of Object.entries(map)) {
736
+ if (el.value === placeholder) {
737
+ el.value = value;
738
+ el.dispatchEvent(new Event("input", { bubbles: true }));
739
+ el.dispatchEvent(new Event("change", { bubbles: true }));
740
+ }
741
+ }
742
+ }
743
+ const submitBtn = document.querySelector('button[type="submit"], input[type="submit"]');
744
+ if (submitBtn) submitBtn.click();
745
+ }, swapMap);
746
+ await page.waitForTimeout(5e3);
747
+ const result = await this.stagehand.extract(
748
+ "Extract the order confirmation number or ID from the page"
749
+ );
750
+ const confirmationId = result?.extraction;
751
+ if (!confirmationId || confirmationId === "null" || confirmationId === "UNKNOWN") {
752
+ throw new CheckoutFailedError("No order confirmation found. Checkout may not have completed.");
753
+ }
754
+ return {
755
+ success: true,
756
+ confirmationId
757
+ };
758
+ } catch (err) {
759
+ if (err instanceof CheckoutFailedError) throw err;
760
+ const message = err instanceof Error ? err.message : "Unknown checkout error";
761
+ throw new CheckoutFailedError(message);
762
+ }
763
+ }
764
+ /**
765
+ * Convenience: single-shot execute (navigate + checkout in one call).
766
+ * Used by AgentPay facade when amount is already known.
767
+ */
768
+ async execute(tx, credentials) {
769
+ this.stagehand = this.createStagehand();
770
+ try {
771
+ await this.stagehand.init();
772
+ const page = this.stagehand.context.activePage();
773
+ await page.goto(tx.url);
774
+ const variables = getPlaceholderVariables();
775
+ await this.stagehand.act(
776
+ `Find the product and proceed to checkout. Fill in the checkout form with these values:
777
+ Name: ${variables.cardholder_name}
778
+ Card Number: ${variables.card_number}
779
+ Expiry: ${variables.card_expiry}
780
+ CVV: ${variables.card_cvv}
781
+ Email: ${variables.email}
782
+ Phone: ${variables.phone}
783
+ Billing Street: ${variables.billing_street}
784
+ Billing City: ${variables.billing_city}
785
+ Billing State: ${variables.billing_state}
786
+ Billing ZIP: ${variables.billing_zip}
787
+ Billing Country: ${variables.billing_country}
788
+ Shipping Street: ${variables.shipping_street}
789
+ Shipping City: ${variables.shipping_city}
790
+ Shipping State: ${variables.shipping_state}
791
+ Shipping ZIP: ${variables.shipping_zip}
792
+ Shipping Country: ${variables.shipping_country}`,
793
+ { variables }
794
+ );
795
+ const swapMap = credentialsToSwapMap(credentials);
796
+ await page.evaluate((map) => {
797
+ const inputs = document.querySelectorAll("input, textarea, select");
798
+ for (const input of inputs) {
799
+ const el = input;
800
+ for (const [placeholder, value] of Object.entries(map)) {
801
+ if (el.value === placeholder) {
802
+ el.value = value;
803
+ el.dispatchEvent(new Event("input", { bubbles: true }));
804
+ el.dispatchEvent(new Event("change", { bubbles: true }));
805
+ }
806
+ }
807
+ }
808
+ const submitBtn = document.querySelector('button[type="submit"], input[type="submit"]');
809
+ if (submitBtn) submitBtn.click();
810
+ }, swapMap);
811
+ await page.waitForTimeout(5e3);
812
+ const result = await this.stagehand.extract(
813
+ "Extract the order confirmation number or ID from the page"
814
+ );
815
+ const confirmationId = result?.extraction;
816
+ if (!confirmationId || confirmationId === "null" || confirmationId === "UNKNOWN") {
817
+ throw new CheckoutFailedError("No order confirmation found. Checkout may not have completed.");
818
+ }
819
+ return {
820
+ success: true,
821
+ confirmationId
822
+ };
823
+ } catch (err) {
824
+ if (err instanceof CheckoutFailedError) throw err;
825
+ const message = err instanceof Error ? err.message : "Unknown checkout error";
826
+ throw new CheckoutFailedError(message);
827
+ } finally {
828
+ await this.close();
829
+ }
830
+ }
831
+ async close() {
832
+ try {
833
+ if (this.stagehand) {
834
+ await this.stagehand.close();
835
+ this.stagehand = null;
836
+ }
837
+ } catch {
838
+ }
839
+ }
840
+ };
841
+ }
842
+ });
843
+
844
+ // src/agentpay.ts
845
+ import { join as join2 } from "path";
846
+ var AgentPay;
847
+ var init_agentpay = __esm({
848
+ "src/agentpay.ts"() {
849
+ "use strict";
850
+ init_esm_shims();
851
+ init_budget();
852
+ init_manager();
853
+ init_logger();
854
+ init_executor();
855
+ init_mandate();
856
+ init_vault();
857
+ init_paths();
858
+ init_errors();
859
+ AgentPay = class {
860
+ home;
861
+ passphrase;
862
+ budgetManager;
863
+ txManager;
864
+ auditLogger;
865
+ executor;
866
+ constructor(options) {
867
+ this.home = options?.home ?? getHomePath();
868
+ this.passphrase = options?.passphrase;
869
+ this.budgetManager = new BudgetManager(join2(this.home, "wallet.json"));
870
+ this.txManager = new TransactionManager(join2(this.home, "transactions.json"));
871
+ this.auditLogger = new AuditLogger(join2(this.home, "audit.log"));
872
+ this.executor = new PurchaseExecutor(options?.executor);
873
+ }
874
+ get wallet() {
875
+ const bm = this.budgetManager;
876
+ return {
877
+ getBalance: () => bm.getBalance(),
878
+ getHistory: () => this.txManager.getHistory(),
879
+ getLimits: () => {
880
+ const w = bm.getBalance();
881
+ return { budget: w.budget, limitPerTx: w.limitPerTx, remaining: w.balance };
882
+ },
883
+ generateFundingQR: async (options) => {
884
+ const QRCode2 = await import("qrcode");
885
+ const params = new URLSearchParams();
886
+ if (options?.suggestedBudget) params.set("budget", String(options.suggestedBudget));
887
+ if (options?.message) params.set("msg", options.message);
888
+ const baseUrl = process.env.AGENTPAY_WEB_URL ?? "http://localhost:3000";
889
+ const url = `${baseUrl}/setup${params.toString() ? `?${params.toString()}` : ""}`;
890
+ const qrDataUrl = await QRCode2.toDataURL(url);
891
+ return { url, qrDataUrl };
892
+ }
893
+ };
894
+ }
895
+ get transactions() {
896
+ return {
897
+ propose: (options) => {
898
+ this.budgetManager.checkProposal(options.amount);
899
+ const tx = this.txManager.propose(options);
900
+ this.auditLogger.log("PROPOSE", { txId: tx.id, merchant: tx.merchant, amount: tx.amount });
901
+ return tx;
902
+ },
903
+ get: (txId) => this.txManager.get(txId),
904
+ waitForApproval: async (txId, options) => {
905
+ const { waitForApproval: waitForApproval2 } = await Promise.resolve().then(() => (init_poller(), poller_exports));
906
+ return waitForApproval2(txId, this.txManager, options);
907
+ },
908
+ execute: async (txId) => {
909
+ const tx = this.txManager.get(txId);
910
+ if (!tx) throw new Error(`Transaction ${txId} not found.`);
911
+ if (tx.status === "completed" || tx.status === "failed") {
912
+ throw new AlreadyExecutedError(txId);
913
+ }
914
+ if (tx.status !== "approved") {
915
+ throw new NotApprovedError(txId);
916
+ }
917
+ if (!tx.mandate) {
918
+ throw new InvalidMandateError("No mandate found on approved transaction.");
919
+ }
920
+ const txDetails = {
921
+ txId: tx.id,
922
+ merchant: tx.merchant,
923
+ amount: tx.amount,
924
+ description: tx.description,
925
+ timestamp: tx.createdAt
926
+ };
927
+ if (!verifyMandate(tx.mandate, txDetails)) {
928
+ throw new InvalidMandateError();
929
+ }
930
+ this.budgetManager.checkProposal(tx.amount);
931
+ this.txManager.markExecuting(txId);
932
+ this.auditLogger.log("EXECUTE", { txId, browserbaseSessionStarted: true });
933
+ try {
934
+ if (!this.passphrase) {
935
+ throw new Error("Passphrase required for execution. Pass it to AgentPay constructor.");
936
+ }
937
+ const vaultPath = join2(this.home, "credentials.enc");
938
+ const vault = loadVault(vaultPath);
939
+ const credentials = decrypt(vault, this.passphrase);
940
+ const result = await this.executor.execute(tx, credentials);
941
+ const receipt = {
942
+ id: `rcpt_${txId.replace("tx_", "")}`,
943
+ merchant: tx.merchant,
944
+ amount: tx.amount,
945
+ confirmationId: result.confirmationId ?? "UNKNOWN",
946
+ completedAt: (/* @__PURE__ */ new Date()).toISOString()
947
+ };
948
+ this.txManager.markCompleted(txId, receipt);
949
+ this.budgetManager.deductBalance(tx.amount);
950
+ this.auditLogger.log("COMPLETE", { txId, confirmationId: receipt.confirmationId });
951
+ return receipt;
952
+ } catch (err) {
953
+ this.txManager.markFailed(txId, err instanceof Error ? err.message : "Unknown error");
954
+ this.auditLogger.log("FAILED", { txId, error: err instanceof Error ? err.message : "Unknown" });
955
+ throw err;
956
+ }
957
+ },
958
+ getReceipt: (txId) => {
959
+ const tx = this.txManager.get(txId);
960
+ return tx?.receipt;
961
+ }
962
+ };
963
+ }
964
+ get audit() {
965
+ return { getLog: () => this.auditLogger.getLog() };
966
+ }
967
+ status() {
968
+ try {
969
+ const wallet = this.budgetManager.getBalance();
970
+ const pending = this.txManager.getPending();
971
+ const recent = this.txManager.list().slice(-5);
972
+ return {
973
+ balance: wallet.balance,
974
+ budget: wallet.budget,
975
+ limitPerTx: wallet.limitPerTx,
976
+ pending,
977
+ recent,
978
+ isSetup: true
979
+ };
980
+ } catch {
981
+ return {
982
+ balance: 0,
983
+ budget: 0,
984
+ limitPerTx: 0,
985
+ pending: [],
986
+ recent: [],
987
+ isSetup: false
988
+ };
989
+ }
990
+ }
991
+ };
992
+ }
993
+ });
994
+
995
+ // src/server/passphrase-html.ts
996
+ function esc(s) {
997
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
998
+ }
999
+ function formatCurrency2(n) {
1000
+ return "$" + n.toFixed(2);
1001
+ }
1002
+ function getPassphraseHtml(token, context) {
1003
+ const actionLabel = context?.action === "approve" ? "Approve Transaction" : "Approve Purchase";
1004
+ const buttonLabel = context?.action === "approve" ? "Unlock &amp; Approve" : "Unlock &amp; Approve";
1005
+ let contextHtml = "";
1006
+ if (context) {
1007
+ const lines = [];
1008
+ if (context.merchant) lines.push(`<div class="detail"><span class="detail-label">Merchant</span><span class="detail-value">${esc(context.merchant)}</span></div>`);
1009
+ if (context.amount !== void 0) lines.push(`<div class="detail"><span class="detail-label">Amount</span><span class="detail-value">${formatCurrency2(context.amount)}</span></div>`);
1010
+ if (context.description) lines.push(`<div class="detail"><span class="detail-label">Description</span><span class="detail-value">${esc(context.description)}</span></div>`);
1011
+ if (context.txId) lines.push(`<div class="detail"><span class="detail-label">Transaction</span><span class="detail-value" style="font-family:monospace;font-size:12px;">${esc(context.txId)}</span></div>`);
1012
+ if (lines.length > 0) {
1013
+ contextHtml = `<div class="card context-card">${lines.join("")}</div>`;
1014
+ }
1015
+ }
1016
+ return `<!DOCTYPE html>
1017
+ <html lang="en">
1018
+ <head>
1019
+ <meta charset="utf-8">
1020
+ <meta name="viewport" content="width=device-width, initial-scale=1">
1021
+ <title>AgentPay \u2014 Passphrase Required</title>
1022
+ <style>
1023
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
1024
+ body {
1025
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
1026
+ background: #f5f5f5;
1027
+ color: #111;
1028
+ min-height: 100vh;
1029
+ display: flex;
1030
+ justify-content: center;
1031
+ padding: 40px 16px;
1032
+ }
1033
+ .container { width: 100%; max-width: 420px; }
1034
+ h1 { font-size: 24px; font-weight: 700; margin-bottom: 4px; }
1035
+ .subtitle { color: #666; font-size: 14px; margin-bottom: 24px; }
1036
+ .card {
1037
+ background: #fff;
1038
+ border-radius: 8px;
1039
+ padding: 24px;
1040
+ margin-bottom: 16px;
1041
+ border: 1px solid #e0e0e0;
1042
+ }
1043
+ .context-card { padding: 16px 20px; }
1044
+ .detail {
1045
+ display: flex;
1046
+ justify-content: space-between;
1047
+ align-items: center;
1048
+ padding: 8px 0;
1049
+ border-bottom: 1px solid #f0f0f0;
1050
+ }
1051
+ .detail:last-child { border-bottom: none; }
1052
+ .detail-label { font-size: 13px; color: #666; }
1053
+ .detail-value { font-size: 14px; font-weight: 600; }
1054
+ label { display: block; font-size: 13px; font-weight: 500; color: #333; margin-bottom: 6px; }
1055
+ input[type="password"] {
1056
+ width: 100%;
1057
+ padding: 12px 14px;
1058
+ border: 1px solid #d0d0d0;
1059
+ border-radius: 8px;
1060
+ font-size: 15px;
1061
+ outline: none;
1062
+ transition: border-color 0.15s;
1063
+ }
1064
+ input[type="password"]:focus { border-color: #111; }
1065
+ button {
1066
+ width: 100%;
1067
+ padding: 12px;
1068
+ border: none;
1069
+ border-radius: 8px;
1070
+ font-size: 14px;
1071
+ font-weight: 600;
1072
+ cursor: pointer;
1073
+ transition: opacity 0.15s;
1074
+ margin-top: 16px;
1075
+ background: #111;
1076
+ color: #fff;
1077
+ }
1078
+ button:hover { opacity: 0.85; }
1079
+ button:disabled { opacity: 0.5; cursor: not-allowed; }
1080
+ .error { color: #c62828; font-size: 13px; margin-top: 10px; }
1081
+ .success-screen {
1082
+ text-align: center;
1083
+ padding: 40px 0;
1084
+ }
1085
+ .checkmark {
1086
+ font-size: 48px;
1087
+ margin-bottom: 16px;
1088
+ }
1089
+ .success-msg {
1090
+ font-size: 16px;
1091
+ font-weight: 600;
1092
+ margin-bottom: 8px;
1093
+ }
1094
+ .success-hint {
1095
+ font-size: 13px;
1096
+ color: #666;
1097
+ }
1098
+ .hidden { display: none; }
1099
+ </style>
1100
+ </head>
1101
+ <body>
1102
+ <div class="container">
1103
+ <div id="form-view">
1104
+ <h1>AgentPay</h1>
1105
+ <p class="subtitle">${esc(actionLabel)}</p>
1106
+ ${contextHtml}
1107
+ <div class="card">
1108
+ <label for="passphrase">Passphrase</label>
1109
+ <input type="password" id="passphrase" placeholder="Enter your passphrase" autofocus>
1110
+ <div id="error" class="error hidden"></div>
1111
+ <button id="submit">${buttonLabel}</button>
1112
+ </div>
1113
+ </div>
1114
+ <div id="success-view" class="hidden">
1115
+ <h1>AgentPay</h1>
1116
+ <p class="subtitle">${esc(actionLabel)}</p>
1117
+ <div class="card">
1118
+ <div class="success-screen">
1119
+ <div class="checkmark">&#10003;</div>
1120
+ <div class="success-msg">Passphrase received</div>
1121
+ <div class="success-hint">You can close this tab.</div>
1122
+ </div>
1123
+ </div>
1124
+ </div>
1125
+ </div>
1126
+ <script>
1127
+ (function() {
1128
+ var token = ${JSON.stringify(token)};
1129
+ var form = document.getElementById('form-view');
1130
+ var success = document.getElementById('success-view');
1131
+ var input = document.getElementById('passphrase');
1132
+ var btn = document.getElementById('submit');
1133
+ var errDiv = document.getElementById('error');
1134
+
1135
+ function submit() {
1136
+ var passphrase = input.value;
1137
+ if (!passphrase) {
1138
+ errDiv.textContent = 'Passphrase is required.';
1139
+ errDiv.classList.remove('hidden');
1140
+ return;
1141
+ }
1142
+ btn.disabled = true;
1143
+ btn.textContent = 'Submitting...';
1144
+ errDiv.classList.add('hidden');
1145
+
1146
+ fetch('/passphrase', {
1147
+ method: 'POST',
1148
+ headers: { 'Content-Type': 'application/json' },
1149
+ body: JSON.stringify({ token: token, passphrase: passphrase })
1150
+ })
1151
+ .then(function(res) { return res.json(); })
1152
+ .then(function(data) {
1153
+ if (data.error) {
1154
+ errDiv.textContent = data.error;
1155
+ errDiv.classList.remove('hidden');
1156
+ btn.disabled = false;
1157
+ btn.textContent = '${buttonLabel}';
1158
+ } else {
1159
+ form.classList.add('hidden');
1160
+ success.classList.remove('hidden');
1161
+ }
1162
+ })
1163
+ .catch(function() {
1164
+ errDiv.textContent = 'Failed to submit. Is the CLI still running?';
1165
+ errDiv.classList.remove('hidden');
1166
+ btn.disabled = false;
1167
+ btn.textContent = '${buttonLabel}';
1168
+ });
1169
+ }
1170
+
1171
+ btn.addEventListener('click', submit);
1172
+ input.addEventListener('keydown', function(e) {
1173
+ if (e.key === 'Enter') submit();
1174
+ });
1175
+ })();
1176
+ </script>
1177
+ </body>
1178
+ </html>`;
1179
+ }
1180
+ var init_passphrase_html = __esm({
1181
+ "src/server/passphrase-html.ts"() {
1182
+ "use strict";
1183
+ init_esm_shims();
1184
+ }
1185
+ });
1186
+
1187
+ // src/utils/open-browser.ts
1188
+ import { exec } from "child_process";
1189
+ import { platform } from "os";
1190
+ function openBrowser(url) {
1191
+ const plat = platform();
1192
+ let cmd;
1193
+ if (plat === "darwin") cmd = `open "${url}"`;
1194
+ else if (plat === "win32") cmd = `start "" "${url}"`;
1195
+ else cmd = `xdg-open "${url}"`;
1196
+ exec(cmd, (err) => {
1197
+ if (err) {
1198
+ console.log(`Open ${url} in your browser.`);
1199
+ }
1200
+ });
1201
+ }
1202
+ var init_open_browser = __esm({
1203
+ "src/utils/open-browser.ts"() {
1204
+ "use strict";
1205
+ init_esm_shims();
1206
+ }
1207
+ });
1208
+
1209
+ // src/server/passphrase-server.ts
1210
+ var passphrase_server_exports = {};
1211
+ __export(passphrase_server_exports, {
1212
+ collectPassphrase: () => collectPassphrase
1213
+ });
1214
+ import { createServer } from "http";
1215
+ import { randomBytes as randomBytes3 } from "crypto";
1216
+ function parseBody(req) {
1217
+ return new Promise((resolve2, reject) => {
1218
+ const chunks = [];
1219
+ let size = 0;
1220
+ req.on("data", (chunk) => {
1221
+ size += chunk.length;
1222
+ if (size > MAX_BODY) {
1223
+ req.destroy();
1224
+ reject(new Error("Request body too large"));
1225
+ return;
1226
+ }
1227
+ chunks.push(chunk);
1228
+ });
1229
+ req.on("end", () => {
1230
+ try {
1231
+ const text = Buffer.concat(chunks).toString("utf8");
1232
+ resolve2(text ? JSON.parse(text) : {});
1233
+ } catch {
1234
+ reject(new Error("Invalid JSON"));
1235
+ }
1236
+ });
1237
+ req.on("error", reject);
1238
+ });
1239
+ }
1240
+ function sendJson(res, status, body) {
1241
+ const json = JSON.stringify(body);
1242
+ res.writeHead(status, {
1243
+ "Content-Type": "application/json",
1244
+ "Content-Length": Buffer.byteLength(json)
1245
+ });
1246
+ res.end(json);
1247
+ }
1248
+ function sendHtml(res, html) {
1249
+ res.writeHead(200, {
1250
+ "Content-Type": "text/html; charset=utf-8",
1251
+ "Content-Length": Buffer.byteLength(html)
1252
+ });
1253
+ res.end(html);
1254
+ }
1255
+ function collectPassphrase(context) {
1256
+ return new Promise((resolve2, reject) => {
1257
+ const nonce = randomBytes3(32).toString("hex");
1258
+ let settled = false;
1259
+ const server = createServer(async (req, res) => {
1260
+ const url = new URL(req.url ?? "/", `http://${req.headers.host}`);
1261
+ const method = req.method ?? "GET";
1262
+ try {
1263
+ if (method === "GET" && url.pathname === "/passphrase") {
1264
+ const token = url.searchParams.get("token");
1265
+ if (token !== nonce) {
1266
+ sendHtml(res, "<h1>Invalid or expired link.</h1>");
1267
+ return;
1268
+ }
1269
+ sendHtml(res, getPassphraseHtml(nonce, context));
1270
+ } else if (method === "POST" && url.pathname === "/passphrase") {
1271
+ const body = await parseBody(req);
1272
+ if (body.token !== nonce) {
1273
+ sendJson(res, 403, { error: "Invalid token." });
1274
+ return;
1275
+ }
1276
+ const passphrase = body.passphrase;
1277
+ if (typeof passphrase !== "string" || !passphrase) {
1278
+ sendJson(res, 400, { error: "Passphrase is required." });
1279
+ return;
1280
+ }
1281
+ sendJson(res, 200, { ok: true });
1282
+ if (!settled) {
1283
+ settled = true;
1284
+ cleanup();
1285
+ resolve2(passphrase);
1286
+ }
1287
+ } else {
1288
+ sendJson(res, 404, { error: "Not found" });
1289
+ }
1290
+ } catch (err) {
1291
+ const message = err instanceof Error ? err.message : "Internal error";
1292
+ sendJson(res, 500, { error: message });
1293
+ }
1294
+ });
1295
+ const timer = setTimeout(() => {
1296
+ if (!settled) {
1297
+ settled = true;
1298
+ cleanup();
1299
+ reject(new TimeoutError("Passphrase entry timed out after 5 minutes."));
1300
+ }
1301
+ }, TIMEOUT_MS);
1302
+ function cleanup() {
1303
+ clearTimeout(timer);
1304
+ server.close();
1305
+ }
1306
+ server.on("error", (err) => {
1307
+ if (!settled) {
1308
+ settled = true;
1309
+ cleanup();
1310
+ reject(err);
1311
+ }
1312
+ });
1313
+ server.listen(0, "127.0.0.1", () => {
1314
+ const addr = server.address();
1315
+ if (!addr || typeof addr === "string") {
1316
+ if (!settled) {
1317
+ settled = true;
1318
+ cleanup();
1319
+ reject(new Error("Failed to bind server"));
1320
+ }
1321
+ return;
1322
+ }
1323
+ const url = `http://127.0.0.1:${addr.port}/passphrase?token=${nonce}`;
1324
+ console.log("Waiting for passphrase entry in browser...");
1325
+ openBrowser(url);
1326
+ });
1327
+ });
1328
+ }
1329
+ var TIMEOUT_MS, MAX_BODY;
1330
+ var init_passphrase_server = __esm({
1331
+ "src/server/passphrase-server.ts"() {
1332
+ "use strict";
1333
+ init_esm_shims();
1334
+ init_passphrase_html();
1335
+ init_open_browser();
1336
+ init_errors();
1337
+ TIMEOUT_MS = 5 * 60 * 1e3;
1338
+ MAX_BODY = 4096;
1339
+ }
1340
+ });
1341
+
1342
+ // src/utils/prompt.ts
1343
+ import { createInterface } from "readline";
1344
+ function createRl() {
1345
+ return createInterface({ input: process.stdin, output: process.stdout });
1346
+ }
1347
+ function promptInput(question) {
1348
+ return new Promise((resolve2) => {
1349
+ const rl = createRl();
1350
+ rl.question(question, (answer) => {
1351
+ rl.close();
1352
+ resolve2(answer.trim());
1353
+ });
1354
+ });
1355
+ }
1356
+ async function promptPassphrase(prompt = "Passphrase: ") {
1357
+ return promptInput(prompt);
1358
+ }
1359
+ function promptConfirm(question) {
1360
+ return new Promise((resolve2) => {
1361
+ const rl = createRl();
1362
+ rl.question(`${question} (y/N): `, (answer) => {
1363
+ rl.close();
1364
+ resolve2(answer.trim().toLowerCase() === "y" || answer.trim().toLowerCase() === "yes");
1365
+ });
1366
+ });
1367
+ }
1368
+ async function promptPassphraseSafe(context) {
1369
+ if (process.stdin.isTTY) {
1370
+ return promptPassphrase("Enter passphrase: ");
1371
+ }
1372
+ const { collectPassphrase: collectPassphrase2 } = await Promise.resolve().then(() => (init_passphrase_server(), passphrase_server_exports));
1373
+ return collectPassphrase2(context);
1374
+ }
1375
+ var init_prompt = __esm({
1376
+ "src/utils/prompt.ts"() {
1377
+ "use strict";
1378
+ init_esm_shims();
1379
+ }
1380
+ });
1381
+
1382
+ // src/commands/setup.ts
1383
+ var setup_exports = {};
1384
+ __export(setup_exports, {
1385
+ setupCommand: () => setupCommand
1386
+ });
1387
+ import { mkdirSync as mkdirSync6 } from "fs";
1388
+ async function setupCommand() {
1389
+ console.log("AgentPay Setup");
1390
+ console.log("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n");
1391
+ const home = getHomePath();
1392
+ mkdirSync6(home, { recursive: true });
1393
+ const passphrase = await promptPassphrase("Choose a passphrase: ");
1394
+ const confirm = await promptPassphrase("Confirm passphrase: ");
1395
+ if (passphrase !== confirm) {
1396
+ console.error("Passphrases do not match.");
1397
+ process.exit(1);
1398
+ }
1399
+ console.log("\nBilling Information");
1400
+ console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
1401
+ const cardNumber = await promptInput("Card number: ");
1402
+ const cardExpiry = await promptInput("Expiry (MM/YY): ");
1403
+ const cardCvv = await promptInput("CVV: ");
1404
+ console.log("\nPersonal Information");
1405
+ console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
1406
+ const name = await promptInput("Full name: ");
1407
+ const email = await promptInput("Email: ");
1408
+ const phone = await promptInput("Phone: ");
1409
+ console.log("\nBilling Address");
1410
+ console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
1411
+ const billingStreet = await promptInput("Street: ");
1412
+ const billingCity = await promptInput("City: ");
1413
+ const billingState = await promptInput("State: ");
1414
+ const billingZip = await promptInput("ZIP: ");
1415
+ const billingCountry = await promptInput("Country (e.g. US): ");
1416
+ console.log("\nShipping Address");
1417
+ console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
1418
+ const sameAsBilling = await promptInput("Same as billing? (y/N): ");
1419
+ let shippingStreet, shippingCity, shippingState, shippingZip, shippingCountry;
1420
+ if (sameAsBilling.toLowerCase() === "y" || sameAsBilling.toLowerCase() === "yes") {
1421
+ shippingStreet = billingStreet;
1422
+ shippingCity = billingCity;
1423
+ shippingState = billingState;
1424
+ shippingZip = billingZip;
1425
+ shippingCountry = billingCountry;
1426
+ } else {
1427
+ shippingStreet = await promptInput("Street: ");
1428
+ shippingCity = await promptInput("City: ");
1429
+ shippingState = await promptInput("State: ");
1430
+ shippingZip = await promptInput("ZIP: ");
1431
+ shippingCountry = await promptInput("Country (e.g. US): ");
1432
+ }
1433
+ const credentials = {
1434
+ card: { number: cardNumber, expiry: cardExpiry, cvv: cardCvv },
1435
+ name,
1436
+ billingAddress: { street: billingStreet, city: billingCity, state: billingState, zip: billingZip, country: billingCountry },
1437
+ shippingAddress: { street: shippingStreet, city: shippingCity, state: shippingState, zip: shippingZip, country: shippingCountry },
1438
+ email,
1439
+ phone
1440
+ };
1441
+ const vault = encrypt(credentials, passphrase);
1442
+ saveVault(vault, getCredentialsPath());
1443
+ console.log("\nCredentials encrypted and saved.");
1444
+ const keys = generateKeyPair(passphrase);
1445
+ mkdirSync6(getKeysPath(), { recursive: true });
1446
+ saveKeyPair(keys);
1447
+ console.log("Keypair generated.");
1448
+ const bm = new BudgetManager();
1449
+ bm.initWallet(0, 0);
1450
+ console.log("Wallet initialized.");
1451
+ const audit = new AuditLogger();
1452
+ audit.log("SETUP", { message: "credentials encrypted, keypair generated, wallet initialized" });
1453
+ console.log("\nSetup complete! Next steps:");
1454
+ console.log(" agentpay budget --set 200 Set your spending budget");
1455
+ console.log(" agentpay budget --limit-per-tx 50 Set per-transaction limit");
1456
+ }
1457
+ var init_setup = __esm({
1458
+ "src/commands/setup.ts"() {
1459
+ "use strict";
1460
+ init_esm_shims();
1461
+ init_prompt();
1462
+ init_vault();
1463
+ init_keypair();
1464
+ init_budget();
1465
+ init_logger();
1466
+ init_paths();
1467
+ }
1468
+ });
1469
+
1470
+ // src/commands/budget.ts
1471
+ var budget_exports = {};
1472
+ __export(budget_exports, {
1473
+ budgetCommand: () => budgetCommand
1474
+ });
1475
+ function budgetCommand(options) {
1476
+ const bm = new BudgetManager();
1477
+ const audit = new AuditLogger();
1478
+ if (options.set) {
1479
+ const amount = parseFloat(options.set);
1480
+ if (isNaN(amount) || amount < 0) {
1481
+ console.error("Invalid amount. Provide a positive number.");
1482
+ process.exit(1);
1483
+ }
1484
+ bm.setBudget(amount);
1485
+ audit.log("BUDGET_SET", { amount });
1486
+ console.log(`Budget set to ${formatCurrency(amount)}.`);
1487
+ }
1488
+ if (options.limitPerTx) {
1489
+ const limit = parseFloat(options.limitPerTx);
1490
+ if (isNaN(limit) || limit < 0) {
1491
+ console.error("Invalid limit. Provide a positive number.");
1492
+ process.exit(1);
1493
+ }
1494
+ bm.setLimitPerTx(limit);
1495
+ audit.log("LIMIT_SET", { limitPerTx: limit });
1496
+ console.log(`Per-transaction limit set to ${formatCurrency(limit)}.`);
1497
+ }
1498
+ if (!options.set && !options.limitPerTx) {
1499
+ const balance = bm.getBalance();
1500
+ console.log(`Budget: ${formatCurrency(balance.budget)}`);
1501
+ console.log(`Balance: ${formatCurrency(balance.balance)}`);
1502
+ console.log(`Spent: ${formatCurrency(balance.spent)}`);
1503
+ console.log(`Per-tx limit: ${balance.limitPerTx > 0 ? formatCurrency(balance.limitPerTx) : "None"}`);
1504
+ }
1505
+ }
1506
+ var init_budget2 = __esm({
1507
+ "src/commands/budget.ts"() {
1508
+ "use strict";
1509
+ init_esm_shims();
1510
+ init_budget();
1511
+ init_logger();
1512
+ init_display();
1513
+ }
1514
+ });
1515
+
1516
+ // src/commands/pending.ts
1517
+ var pending_exports = {};
1518
+ __export(pending_exports, {
1519
+ pendingCommand: () => pendingCommand
1520
+ });
1521
+ function pendingCommand() {
1522
+ const tm = new TransactionManager();
1523
+ const pending = tm.getPending();
1524
+ if (pending.length === 0) {
1525
+ console.log("No pending purchases.");
1526
+ return;
1527
+ }
1528
+ console.log("Pending Purchases:");
1529
+ console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
1530
+ console.log("TX_ID MERCHANT AMOUNT DESCRIPTION");
1531
+ for (const tx of pending) {
1532
+ const id = tx.id.padEnd(14);
1533
+ const merchant = tx.merchant.padEnd(16);
1534
+ const amount = formatCurrency(tx.amount).padStart(9);
1535
+ console.log(`${id}${merchant}${amount} ${tx.description}`);
1536
+ }
1537
+ console.log(`
1538
+ ${pending.length} pending purchase${pending.length === 1 ? "" : "s"}. Use 'agentpay approve <txId>' or 'agentpay reject <txId>'.`);
1539
+ }
1540
+ var init_pending = __esm({
1541
+ "src/commands/pending.ts"() {
1542
+ "use strict";
1543
+ init_esm_shims();
1544
+ init_manager();
1545
+ init_display();
1546
+ }
1547
+ });
1548
+
1549
+ // src/commands/approve.ts
1550
+ var approve_exports = {};
1551
+ __export(approve_exports, {
1552
+ approveCommand: () => approveCommand
1553
+ });
1554
+ async function approveCommand(txId) {
1555
+ const tm = new TransactionManager();
1556
+ const audit = new AuditLogger();
1557
+ const tx = tm.get(txId);
1558
+ if (!tx) {
1559
+ console.error(`Transaction ${txId} not found.`);
1560
+ process.exit(1);
1561
+ }
1562
+ if (tx.status !== "pending") {
1563
+ console.error(`Cannot approve transaction in '${tx.status}' state.`);
1564
+ process.exit(1);
1565
+ }
1566
+ console.log(`Approve purchase:`);
1567
+ console.log(` Merchant: ${tx.merchant}`);
1568
+ console.log(` Amount: ${formatCurrency(tx.amount)}`);
1569
+ console.log(` Description: ${tx.description}`);
1570
+ console.log();
1571
+ const passphrase = await promptPassphraseSafe({
1572
+ action: "approve",
1573
+ merchant: tx.merchant,
1574
+ amount: tx.amount,
1575
+ description: tx.description,
1576
+ txId
1577
+ });
1578
+ try {
1579
+ const privateKeyPem = loadPrivateKey();
1580
+ const txDetails = {
1581
+ txId: tx.id,
1582
+ merchant: tx.merchant,
1583
+ amount: tx.amount,
1584
+ description: tx.description,
1585
+ timestamp: tx.createdAt
1586
+ };
1587
+ const mandate = createMandate(txDetails, privateKeyPem, passphrase);
1588
+ tm.approve(txId, mandate);
1589
+ audit.log("APPROVE", { txId, mandateSigned: true });
1590
+ console.log(`
1591
+ Approved! Transaction ${txId} is now ready for execution.`);
1592
+ } catch (err) {
1593
+ console.error(`Failed to approve: ${err instanceof Error ? err.message : "Unknown error"}`);
1594
+ process.exit(1);
1595
+ }
1596
+ }
1597
+ var init_approve = __esm({
1598
+ "src/commands/approve.ts"() {
1599
+ "use strict";
1600
+ init_esm_shims();
1601
+ init_manager();
1602
+ init_keypair();
1603
+ init_mandate();
1604
+ init_logger();
1605
+ init_prompt();
1606
+ init_display();
1607
+ }
1608
+ });
1609
+
1610
+ // src/commands/reject.ts
1611
+ var reject_exports = {};
1612
+ __export(reject_exports, {
1613
+ rejectCommand: () => rejectCommand
1614
+ });
1615
+ function rejectCommand(txId, options) {
1616
+ const tm = new TransactionManager();
1617
+ const audit = new AuditLogger();
1618
+ const tx = tm.get(txId);
1619
+ if (!tx) {
1620
+ console.error(`Transaction ${txId} not found.`);
1621
+ process.exit(1);
1622
+ }
1623
+ if (tx.status !== "pending") {
1624
+ console.error(`Cannot reject transaction in '${tx.status}' state.`);
1625
+ process.exit(1);
1626
+ }
1627
+ tm.reject(txId, options.reason);
1628
+ audit.log("REJECT", { txId, reason: options.reason });
1629
+ console.log(`Rejected transaction ${txId}.${options.reason ? ` Reason: ${options.reason}` : ""}`);
1630
+ }
1631
+ var init_reject = __esm({
1632
+ "src/commands/reject.ts"() {
1633
+ "use strict";
1634
+ init_esm_shims();
1635
+ init_manager();
1636
+ init_logger();
1637
+ }
1638
+ });
1639
+
1640
+ // src/commands/status.ts
1641
+ var status_exports = {};
1642
+ __export(status_exports, {
1643
+ statusCommand: () => statusCommand
1644
+ });
1645
+ function statusCommand() {
1646
+ const ap = new AgentPay();
1647
+ const s = ap.status();
1648
+ if (!s.isSetup) {
1649
+ console.log("AgentPay is not set up. Run `agentpay setup` first.");
1650
+ return;
1651
+ }
1652
+ console.log(formatStatus({
1653
+ balance: s.balance,
1654
+ budget: s.budget,
1655
+ limitPerTx: s.limitPerTx,
1656
+ pending: s.pending,
1657
+ recent: s.recent
1658
+ }));
1659
+ }
1660
+ var init_status = __esm({
1661
+ "src/commands/status.ts"() {
1662
+ "use strict";
1663
+ init_esm_shims();
1664
+ init_agentpay();
1665
+ init_display();
1666
+ }
1667
+ });
1668
+
1669
+ // src/commands/history.ts
1670
+ var history_exports = {};
1671
+ __export(history_exports, {
1672
+ historyCommand: () => historyCommand
1673
+ });
1674
+ function historyCommand() {
1675
+ const tm = new TransactionManager();
1676
+ const history = tm.getHistory();
1677
+ if (history.length === 0) {
1678
+ console.log("No transaction history.");
1679
+ return;
1680
+ }
1681
+ console.log("Transaction History:");
1682
+ console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
1683
+ console.log("STATUS TX_ID MERCHANT AMOUNT DATE DESCRIPTION");
1684
+ for (const tx of history) {
1685
+ const status = `[${tx.status}]`.padEnd(13);
1686
+ const id = tx.id.padEnd(14);
1687
+ const merchant = tx.merchant.padEnd(16);
1688
+ const amount = formatCurrency(tx.amount).padStart(9);
1689
+ const date = formatTimestamp(tx.createdAt).padEnd(18);
1690
+ console.log(`${status}${id}${merchant}${amount} ${date}${tx.description}`);
1691
+ }
1692
+ }
1693
+ var init_history = __esm({
1694
+ "src/commands/history.ts"() {
1695
+ "use strict";
1696
+ init_esm_shims();
1697
+ init_manager();
1698
+ init_display();
1699
+ }
1700
+ });
1701
+
1702
+ // src/commands/qr.ts
1703
+ var qr_exports = {};
1704
+ __export(qr_exports, {
1705
+ qrCommand: () => qrCommand
1706
+ });
1707
+ import QRCode from "qrcode";
1708
+ async function qrCommand(options) {
1709
+ const params = new URLSearchParams();
1710
+ if (options.budget) params.set("budget", options.budget);
1711
+ if (options.message) params.set("msg", options.message);
1712
+ const baseUrl = process.env.AGENTPAY_WEB_URL ?? "http://localhost:3000";
1713
+ const url = `${baseUrl}/setup${params.toString() ? `?${params.toString()}` : ""}`;
1714
+ console.log("Scan this QR code to set up AgentPay:\n");
1715
+ const qrString = await QRCode.toString(url, { type: "terminal", small: true });
1716
+ console.log(qrString);
1717
+ console.log(`
1718
+ URL: ${url}`);
1719
+ }
1720
+ var init_qr = __esm({
1721
+ "src/commands/qr.ts"() {
1722
+ "use strict";
1723
+ init_esm_shims();
1724
+ }
1725
+ });
1726
+
1727
+ // src/commands/reset.ts
1728
+ var reset_exports = {};
1729
+ __export(reset_exports, {
1730
+ resetCommand: () => resetCommand
1731
+ });
1732
+ import { rmSync, existsSync } from "fs";
1733
+ async function resetCommand() {
1734
+ const home = getHomePath();
1735
+ if (!existsSync(home)) {
1736
+ console.log("Nothing to reset. AgentPay data directory does not exist.");
1737
+ return;
1738
+ }
1739
+ console.log(`This will permanently delete all AgentPay data at: ${home}`);
1740
+ console.log("This includes encrypted credentials, keys, wallet, transactions, and audit logs.");
1741
+ const answer = await promptInput("\nType YES to confirm: ");
1742
+ if (answer !== "YES") {
1743
+ console.log("Cancelled.");
1744
+ return;
1745
+ }
1746
+ rmSync(home, { recursive: true, force: true });
1747
+ console.log("All AgentPay data has been deleted.");
1748
+ }
1749
+ var init_reset = __esm({
1750
+ "src/commands/reset.ts"() {
1751
+ "use strict";
1752
+ init_esm_shims();
1753
+ init_prompt();
1754
+ init_paths();
1755
+ }
1756
+ });
1757
+
1758
+ // src/commands/buy.ts
1759
+ var buy_exports = {};
1760
+ __export(buy_exports, {
1761
+ buyCommand: () => buyCommand
1762
+ });
1763
+ import { readFileSync as readFileSync6 } from "fs";
1764
+ import { resolve } from "path";
1765
+ function loadEnv() {
1766
+ try {
1767
+ const envPath = resolve(process.cwd(), ".env");
1768
+ const content = readFileSync6(envPath, "utf8");
1769
+ for (const line of content.split("\n")) {
1770
+ const trimmed = line.trim();
1771
+ if (!trimmed || trimmed.startsWith("#")) continue;
1772
+ const eqIdx = trimmed.indexOf("=");
1773
+ if (eqIdx === -1) continue;
1774
+ const key = trimmed.slice(0, eqIdx).trim();
1775
+ const value = trimmed.slice(eqIdx + 1).trim();
1776
+ if (!process.env[key]) {
1777
+ process.env[key] = value;
1778
+ }
1779
+ }
1780
+ } catch {
1781
+ }
1782
+ }
1783
+ async function buyCommand(options) {
1784
+ loadEnv();
1785
+ console.log("AgentPay Purchase");
1786
+ console.log("\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n");
1787
+ const executor = new PurchaseExecutor();
1788
+ const bm = new BudgetManager();
1789
+ const tm = new TransactionManager();
1790
+ const audit = new AuditLogger();
1791
+ const isTTY = process.stdin.isTTY;
1792
+ let amount;
1793
+ let productName = options.description;
1794
+ if (options.amount) {
1795
+ amount = parseFloat(options.amount);
1796
+ if (isNaN(amount) || amount <= 0) {
1797
+ console.error("Invalid amount.");
1798
+ process.exit(1);
1799
+ }
1800
+ console.log(` Merchant: ${options.merchant}`);
1801
+ console.log(` Amount: ${formatCurrency(amount)}`);
1802
+ console.log(` Description: ${options.description}`);
1803
+ console.log(` URL: ${options.url}`);
1804
+ console.log();
1805
+ } else {
1806
+ const pickupHint = options.pickup ? "Select in-store pickup if available." : "";
1807
+ const instructions = [pickupHint, options.description].filter(Boolean).join(" ");
1808
+ console.log("Opening browser to discover price...\n");
1809
+ try {
1810
+ const discovered = await executor.openAndDiscover(options.url, instructions || void 0);
1811
+ amount = discovered.price;
1812
+ productName = discovered.productName || options.description;
1813
+ } catch (err) {
1814
+ console.error(`Failed to load product page: ${err instanceof Error ? err.message : "Unknown error"}`);
1815
+ await executor.close();
1816
+ process.exit(1);
1817
+ }
1818
+ if (amount <= 0) {
1819
+ console.error("Could not extract price from the page.");
1820
+ await executor.close();
1821
+ process.exit(1);
1822
+ }
1823
+ console.log(` Product: ${productName}`);
1824
+ console.log(` Merchant: ${options.merchant}`);
1825
+ console.log(` Amount: ${formatCurrency(amount)}`);
1826
+ console.log();
1827
+ }
1828
+ try {
1829
+ bm.checkProposal(amount);
1830
+ } catch (err) {
1831
+ console.error(err instanceof Error ? err.message : "Budget check failed.");
1832
+ await executor.close();
1833
+ process.exit(1);
1834
+ }
1835
+ let passphrase;
1836
+ if (isTTY) {
1837
+ const confirmed = await promptConfirm("Approve this purchase?");
1838
+ if (!confirmed) {
1839
+ console.log("Purchase cancelled.");
1840
+ await executor.close();
1841
+ process.exit(0);
1842
+ }
1843
+ passphrase = await promptPassphraseSafe();
1844
+ } else {
1845
+ passphrase = await promptPassphraseSafe({
1846
+ action: "buy",
1847
+ merchant: options.merchant,
1848
+ amount,
1849
+ description: productName
1850
+ });
1851
+ console.log("Passphrase received. Continuing...");
1852
+ }
1853
+ const vault = loadVault(getCredentialsPath());
1854
+ const credentials = decrypt(vault, passphrase);
1855
+ console.log("\nProposing transaction...");
1856
+ const tx = tm.propose({
1857
+ merchant: options.merchant,
1858
+ amount,
1859
+ description: productName,
1860
+ url: options.url
1861
+ });
1862
+ audit.log("PROPOSE", { txId: tx.id, merchant: tx.merchant, amount: tx.amount, source: "buy-command" });
1863
+ console.log(` Transaction ${tx.id} created.`);
1864
+ const privateKeyPem = loadPrivateKey();
1865
+ const mandate = createMandate(
1866
+ { txId: tx.id, merchant: tx.merchant, amount: tx.amount, description: tx.description, timestamp: tx.createdAt },
1867
+ privateKeyPem,
1868
+ passphrase
1869
+ );
1870
+ tm.approve(tx.id, mandate);
1871
+ audit.log("APPROVE", { txId: tx.id, source: "buy-command", mandateSigned: true });
1872
+ console.log(" Approved and signed.\n");
1873
+ console.log("Filling checkout...");
1874
+ tm.markExecuting(tx.id);
1875
+ audit.log("EXECUTE", { txId: tx.id, source: "buy-command" });
1876
+ try {
1877
+ let result;
1878
+ if (!options.amount) {
1879
+ result = await executor.fillAndComplete(credentials);
1880
+ } else {
1881
+ result = await executor.execute(tx, credentials);
1882
+ }
1883
+ const receipt = {
1884
+ id: `rcpt_${tx.id.replace("tx_", "")}`,
1885
+ merchant: tx.merchant,
1886
+ amount: tx.amount,
1887
+ confirmationId: result.confirmationId ?? "UNKNOWN",
1888
+ completedAt: (/* @__PURE__ */ new Date()).toISOString()
1889
+ };
1890
+ tm.markCompleted(tx.id, receipt);
1891
+ bm.deductBalance(tx.amount);
1892
+ audit.log("COMPLETE", { txId: tx.id, confirmationId: receipt.confirmationId, source: "buy-command" });
1893
+ console.log(`
1894
+ Purchase complete!`);
1895
+ console.log(` Confirmation: ${receipt.confirmationId}`);
1896
+ console.log(` Amount: ${formatCurrency(receipt.amount)}`);
1897
+ } catch (err) {
1898
+ tm.markFailed(tx.id, err instanceof Error ? err.message : "Unknown error");
1899
+ audit.log("FAILED", { txId: tx.id, error: err instanceof Error ? err.message : "Unknown", source: "buy-command" });
1900
+ console.error(`
1901
+ Checkout failed: ${err instanceof Error ? err.message : "Unknown error"}`);
1902
+ process.exit(1);
1903
+ } finally {
1904
+ await executor.close();
1905
+ }
1906
+ }
1907
+ var init_buy = __esm({
1908
+ "src/commands/buy.ts"() {
1909
+ "use strict";
1910
+ init_esm_shims();
1911
+ init_prompt();
1912
+ init_display();
1913
+ init_executor();
1914
+ init_budget();
1915
+ init_manager();
1916
+ init_logger();
1917
+ init_keypair();
1918
+ init_mandate();
1919
+ init_vault();
1920
+ init_paths();
1921
+ }
1922
+ });
1923
+
1924
+ // src/server/html.ts
1925
+ function getDashboardHtml() {
1926
+ return `<!DOCTYPE html>
1927
+ <html lang="en">
1928
+ <head>
1929
+ <meta charset="utf-8">
1930
+ <meta name="viewport" content="width=device-width, initial-scale=1">
1931
+ <title>AgentPay Dashboard</title>
1932
+ <style>
1933
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
1934
+ body {
1935
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
1936
+ background: #f5f5f5;
1937
+ color: #111;
1938
+ min-height: 100vh;
1939
+ display: flex;
1940
+ justify-content: center;
1941
+ padding: 40px 16px;
1942
+ }
1943
+ .container { width: 100%; max-width: 480px; }
1944
+ h1 { font-size: 24px; font-weight: 700; margin-bottom: 8px; }
1945
+ h2 { font-size: 18px; font-weight: 600; margin-bottom: 12px; }
1946
+ .subtitle { color: #666; font-size: 14px; margin-bottom: 32px; }
1947
+ .card {
1948
+ background: #fff;
1949
+ border-radius: 8px;
1950
+ padding: 24px;
1951
+ margin-bottom: 16px;
1952
+ border: 1px solid #e0e0e0;
1953
+ }
1954
+ label { display: block; font-size: 13px; font-weight: 500; color: #333; margin-bottom: 4px; }
1955
+ input, select {
1956
+ width: 100%;
1957
+ padding: 10px 12px;
1958
+ border: 1px solid #d0d0d0;
1959
+ border-radius: 8px;
1960
+ font-size: 14px;
1961
+ margin-bottom: 16px;
1962
+ outline: none;
1963
+ transition: border-color 0.15s;
1964
+ }
1965
+ input:focus { border-color: #111; }
1966
+ .row { display: flex; gap: 12px; }
1967
+ .row > div { flex: 1; }
1968
+ button {
1969
+ width: 100%;
1970
+ padding: 12px;
1971
+ border: none;
1972
+ border-radius: 8px;
1973
+ font-size: 14px;
1974
+ font-weight: 600;
1975
+ cursor: pointer;
1976
+ transition: opacity 0.15s;
1977
+ }
1978
+ button:hover { opacity: 0.85; }
1979
+ button:disabled { opacity: 0.5; cursor: not-allowed; }
1980
+ .btn-primary { background: #111; color: #fff; }
1981
+ .btn-secondary { background: #e0e0e0; color: #111; }
1982
+
1983
+ /* Progress bar for wizard */
1984
+ .progress { display: flex; gap: 6px; margin-bottom: 24px; }
1985
+ .progress .step {
1986
+ flex: 1; height: 4px; border-radius: 2px; background: #e0e0e0;
1987
+ transition: background 0.3s;
1988
+ }
1989
+ .progress .step.active { background: #111; }
1990
+
1991
+ /* Balance display */
1992
+ .balance-display {
1993
+ text-align: center;
1994
+ padding: 32px 0;
1995
+ }
1996
+ .balance-amount {
1997
+ font-size: 48px;
1998
+ font-weight: 700;
1999
+ letter-spacing: -1px;
2000
+ }
2001
+ .balance-label { font-size: 13px; color: #666; margin-top: 4px; }
2002
+
2003
+ /* Budget bar */
2004
+ .budget-bar-container { margin: 16px 0; }
2005
+ .budget-bar {
2006
+ height: 8px;
2007
+ background: #e0e0e0;
2008
+ border-radius: 4px;
2009
+ overflow: hidden;
2010
+ }
2011
+ .budget-bar-fill {
2012
+ height: 100%;
2013
+ background: #111;
2014
+ border-radius: 4px;
2015
+ transition: width 0.3s;
2016
+ }
2017
+ .budget-bar-labels {
2018
+ display: flex;
2019
+ justify-content: space-between;
2020
+ font-size: 12px;
2021
+ color: #666;
2022
+ margin-top: 4px;
2023
+ }
2024
+
2025
+ /* Stats grid */
2026
+ .stats { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-bottom: 16px; }
2027
+ .stat { text-align: center; padding: 12px; background: #f9f9f9; border-radius: 8px; }
2028
+ .stat-value { font-size: 18px; font-weight: 700; }
2029
+ .stat-label { font-size: 11px; color: #666; margin-top: 2px; }
2030
+
2031
+ /* Add funds inline */
2032
+ .add-funds-row { display: flex; gap: 8px; }
2033
+ .add-funds-row input { margin-bottom: 0; flex: 1; }
2034
+ .add-funds-row button { width: auto; padding: 10px 20px; }
2035
+
2036
+ /* Transactions */
2037
+ .tx-list { margin-top: 12px; }
2038
+ .tx-item {
2039
+ display: flex;
2040
+ justify-content: space-between;
2041
+ align-items: center;
2042
+ padding: 10px 0;
2043
+ border-bottom: 1px solid #f0f0f0;
2044
+ font-size: 13px;
2045
+ }
2046
+ .tx-item:last-child { border-bottom: none; }
2047
+ .tx-merchant { font-weight: 500; }
2048
+ .tx-amount { font-weight: 600; }
2049
+ .tx-status {
2050
+ font-size: 11px;
2051
+ padding: 2px 6px;
2052
+ border-radius: 4px;
2053
+ font-weight: 500;
2054
+ }
2055
+ .tx-status.completed { background: #e6f4ea; color: #1e7e34; }
2056
+ .tx-status.pending { background: #fff8e1; color: #f57f17; }
2057
+ .tx-status.failed { background: #fde8e8; color: #c62828; }
2058
+ .tx-status.rejected { background: #fde8e8; color: #c62828; }
2059
+ .tx-status.approved { background: #e3f2fd; color: #1565c0; }
2060
+ .tx-status.executing { background: #e3f2fd; color: #1565c0; }
2061
+
2062
+ .error { color: #c62828; font-size: 13px; margin-top: 8px; }
2063
+ .success { color: #1e7e34; font-size: 13px; margin-top: 8px; }
2064
+ .hidden { display: none; }
2065
+
2066
+ /* Checkbox row for same-as-billing */
2067
+ .checkbox-row {
2068
+ display: flex;
2069
+ align-items: center;
2070
+ gap: 8px;
2071
+ margin-bottom: 16px;
2072
+ }
2073
+ .checkbox-row input { width: auto; margin: 0; }
2074
+ .checkbox-row label { margin: 0; }
2075
+ </style>
2076
+ </head>
2077
+ <body>
2078
+ <div class="container" id="app">
2079
+ <div id="loading">Loading...</div>
2080
+ </div>
2081
+
2082
+ <script>
2083
+ const App = {
2084
+ state: { isSetup: false, wallet: null, recentTransactions: [], wizardStep: 1 },
2085
+
2086
+ async init() {
2087
+ try {
2088
+ const res = await fetch('/api/status');
2089
+ const data = await res.json();
2090
+ this.state.isSetup = data.isSetup;
2091
+ this.state.wallet = data.wallet;
2092
+ this.state.recentTransactions = data.recentTransactions || [];
2093
+ } catch (e) {
2094
+ console.error('Failed to load status', e);
2095
+ }
2096
+ this.render();
2097
+ },
2098
+
2099
+ render() {
2100
+ const app = document.getElementById('app');
2101
+ if (this.state.isSetup && this.state.wallet) {
2102
+ app.innerHTML = this.renderDashboard();
2103
+ this.bindDashboard();
2104
+ } else if (this.state.isSetup) {
2105
+ app.innerHTML = '<div class="card"><h2>Setup detected</h2><p>Wallet data could not be loaded. Try running <code>agentpay budget --set 100</code> from the CLI.</p></div>';
2106
+ } else {
2107
+ app.innerHTML = this.renderWizard();
2108
+ this.bindWizard();
2109
+ }
2110
+ },
2111
+
2112
+ fmt(n) {
2113
+ return '$' + Number(n).toFixed(2);
2114
+ },
2115
+
2116
+ renderDashboard() {
2117
+ const w = this.state.wallet;
2118
+ const pct = w.budget > 0 ? Math.min(100, (w.spent / w.budget) * 100) : 0;
2119
+ const txHtml = this.state.recentTransactions.length === 0
2120
+ ? '<p style="color:#666;font-size:13px;">No transactions yet.</p>'
2121
+ : this.state.recentTransactions.map(tx => \`
2122
+ <div class="tx-item">
2123
+ <div>
2124
+ <div class="tx-merchant">\${this.esc(tx.merchant)}</div>
2125
+ <div style="color:#999;font-size:11px;">\${new Date(tx.createdAt).toLocaleDateString()}</div>
2126
+ </div>
2127
+ <div style="text-align:right">
2128
+ <div class="tx-amount">\${this.fmt(tx.amount)}</div>
2129
+ <span class="tx-status \${tx.status}">\${tx.status}</span>
2130
+ </div>
2131
+ </div>\`).join('');
2132
+
2133
+ return \`
2134
+ <h1>AgentPay</h1>
2135
+ <p class="subtitle">Wallet Dashboard</p>
2136
+
2137
+ <div class="card">
2138
+ <div class="balance-display">
2139
+ <div class="balance-amount">\${this.fmt(w.balance)}</div>
2140
+ <div class="balance-label">Available Balance</div>
2141
+ </div>
2142
+ <div class="budget-bar-container">
2143
+ <div class="budget-bar"><div class="budget-bar-fill" style="width:\${pct.toFixed(1)}%"></div></div>
2144
+ <div class="budget-bar-labels">
2145
+ <span>\${this.fmt(w.spent)} spent</span>
2146
+ <span>\${this.fmt(w.budget)} budget</span>
2147
+ </div>
2148
+ </div>
2149
+ </div>
2150
+
2151
+ <div class="card">
2152
+ <div class="stats">
2153
+ <div class="stat"><div class="stat-value">\${this.fmt(w.budget)}</div><div class="stat-label">Total Budget</div></div>
2154
+ <div class="stat"><div class="stat-value">\${this.fmt(w.spent)}</div><div class="stat-label">Total Spent</div></div>
2155
+ <div class="stat"><div class="stat-value">\${this.fmt(w.balance)}</div><div class="stat-label">Remaining</div></div>
2156
+ <div class="stat"><div class="stat-value">\${w.limitPerTx > 0 ? this.fmt(w.limitPerTx) : 'None'}</div><div class="stat-label">Per-Tx Limit</div></div>
2157
+ </div>
2158
+ </div>
2159
+
2160
+ <div class="card">
2161
+ <h2>Add Funds</h2>
2162
+ <div class="add-funds-row">
2163
+ <input type="number" id="fundAmount" placeholder="Amount" min="0.01" step="0.01">
2164
+ <button class="btn-primary" id="fundBtn">Add</button>
2165
+ </div>
2166
+ <div id="fundMsg"></div>
2167
+ </div>
2168
+
2169
+ <div class="card">
2170
+ <h2>Recent Transactions</h2>
2171
+ <div class="tx-list">\${txHtml}</div>
2172
+ </div>
2173
+ \`;
2174
+ },
2175
+
2176
+ bindDashboard() {
2177
+ const btn = document.getElementById('fundBtn');
2178
+ const input = document.getElementById('fundAmount');
2179
+ const msg = document.getElementById('fundMsg');
2180
+
2181
+ btn.addEventListener('click', async () => {
2182
+ const amount = parseFloat(input.value);
2183
+ if (!amount || amount <= 0) {
2184
+ msg.innerHTML = '<p class="error">Enter a valid amount.</p>';
2185
+ return;
2186
+ }
2187
+ btn.disabled = true;
2188
+ btn.textContent = '...';
2189
+ try {
2190
+ const res = await fetch('/api/fund', {
2191
+ method: 'POST',
2192
+ headers: { 'Content-Type': 'application/json' },
2193
+ body: JSON.stringify({ amount }),
2194
+ });
2195
+ const data = await res.json();
2196
+ if (data.error) {
2197
+ msg.innerHTML = '<p class="error">' + this.esc(data.error) + '</p>';
2198
+ } else {
2199
+ this.state.wallet = data.wallet;
2200
+ input.value = '';
2201
+ this.render();
2202
+ }
2203
+ } catch (e) {
2204
+ msg.innerHTML = '<p class="error">Request failed.</p>';
2205
+ }
2206
+ btn.disabled = false;
2207
+ btn.textContent = 'Add';
2208
+ });
2209
+ },
2210
+
2211
+ renderWizard() {
2212
+ const step = this.state.wizardStep;
2213
+ const steps = [1, 2, 3, 4];
2214
+ const progressHtml = '<div class="progress">' + steps.map(s =>
2215
+ '<div class="step' + (s <= step ? ' active' : '') + '"></div>'
2216
+ ).join('') + '</div>';
2217
+
2218
+ const titles = ['Create Passphrase', 'Card Information', 'Personal Details', 'Budget & Limits'];
2219
+
2220
+ let fields = '';
2221
+ if (step === 1) {
2222
+ fields = \`
2223
+ <label for="w_pass">Passphrase</label>
2224
+ <input type="password" id="w_pass" placeholder="Choose a strong passphrase">
2225
+ <label for="w_pass2">Confirm Passphrase</label>
2226
+ <input type="password" id="w_pass2" placeholder="Confirm your passphrase">
2227
+ \`;
2228
+ } else if (step === 2) {
2229
+ fields = \`
2230
+ <label for="w_cardNum">Card Number</label>
2231
+ <input type="text" id="w_cardNum" placeholder="4242 4242 4242 4242">
2232
+ <div class="row">
2233
+ <div><label for="w_expiry">Expiry</label><input type="text" id="w_expiry" placeholder="MM/YY"></div>
2234
+ <div><label for="w_cvv">CVV</label><input type="text" id="w_cvv" placeholder="123"></div>
2235
+ </div>
2236
+ \`;
2237
+ } else if (step === 3) {
2238
+ fields = \`
2239
+ <label for="w_name">Full Name</label>
2240
+ <input type="text" id="w_name" placeholder="Jane Doe">
2241
+ <div class="row">
2242
+ <div><label for="w_email">Email</label><input type="email" id="w_email" placeholder="jane@example.com"></div>
2243
+ <div><label for="w_phone">Phone</label><input type="tel" id="w_phone" placeholder="+1 555 0123"></div>
2244
+ </div>
2245
+ <label for="w_street">Street Address</label>
2246
+ <input type="text" id="w_street" placeholder="123 Main St">
2247
+ <div class="row">
2248
+ <div><label for="w_city">City</label><input type="text" id="w_city" placeholder="San Francisco"></div>
2249
+ <div><label for="w_state">State</label><input type="text" id="w_state" placeholder="CA"></div>
2250
+ </div>
2251
+ <div class="row">
2252
+ <div><label for="w_zip">ZIP</label><input type="text" id="w_zip" placeholder="94102"></div>
2253
+ <div><label for="w_country">Country</label><input type="text" id="w_country" placeholder="US" value="US"></div>
2254
+ </div>
2255
+ \`;
2256
+ } else if (step === 4) {
2257
+ fields = \`
2258
+ <label for="w_budget">Initial Budget ($)</label>
2259
+ <input type="number" id="w_budget" placeholder="200" min="0" step="0.01">
2260
+ <label for="w_limit">Per-Transaction Limit ($)</label>
2261
+ <input type="number" id="w_limit" placeholder="50 (0 = no limit)" min="0" step="0.01">
2262
+ \`;
2263
+ }
2264
+
2265
+ return \`
2266
+ <h1>AgentPay</h1>
2267
+ <p class="subtitle">Step \${step} of 4 \u2014 \${titles[step - 1]}</p>
2268
+ \${progressHtml}
2269
+ <div class="card">
2270
+ \${fields}
2271
+ <div id="wizardError"></div>
2272
+ <div style="display:flex;gap:8px;margin-top:8px;">
2273
+ \${step > 1 ? '<button class="btn-secondary" id="wizBack">Back</button>' : ''}
2274
+ <button class="btn-primary" id="wizNext">\${step === 4 ? 'Complete Setup' : 'Continue'}</button>
2275
+ </div>
2276
+ </div>
2277
+ \`;
2278
+ },
2279
+
2280
+ // Wizard form data persisted across steps
2281
+ wizardData: {},
2282
+
2283
+ bindWizard() {
2284
+ const step = this.state.wizardStep;
2285
+ const errDiv = document.getElementById('wizardError');
2286
+ const nextBtn = document.getElementById('wizNext');
2287
+ const backBtn = document.getElementById('wizBack');
2288
+
2289
+ // Restore saved data into fields
2290
+ this.restoreWizardFields(step);
2291
+
2292
+ if (backBtn) {
2293
+ backBtn.addEventListener('click', () => {
2294
+ this.saveWizardFields(step);
2295
+ this.state.wizardStep--;
2296
+ this.render();
2297
+ });
2298
+ }
2299
+
2300
+ nextBtn.addEventListener('click', async () => {
2301
+ errDiv.innerHTML = '';
2302
+
2303
+ if (step === 1) {
2304
+ const pass = document.getElementById('w_pass').value;
2305
+ const pass2 = document.getElementById('w_pass2').value;
2306
+ if (!pass) { errDiv.innerHTML = '<p class="error">Passphrase is required.</p>'; return; }
2307
+ if (pass !== pass2) { errDiv.innerHTML = '<p class="error">Passphrases do not match.</p>'; return; }
2308
+ this.wizardData.passphrase = pass;
2309
+ this.state.wizardStep = 2;
2310
+ this.render();
2311
+ } else if (step === 2) {
2312
+ this.saveWizardFields(step);
2313
+ const d = this.wizardData;
2314
+ if (!d.cardNumber) { errDiv.innerHTML = '<p class="error">Card number is required.</p>'; return; }
2315
+ if (!d.expiry) { errDiv.innerHTML = '<p class="error">Expiry is required.</p>'; return; }
2316
+ if (!d.cvv) { errDiv.innerHTML = '<p class="error">CVV is required.</p>'; return; }
2317
+ this.state.wizardStep = 3;
2318
+ this.render();
2319
+ } else if (step === 3) {
2320
+ this.saveWizardFields(step);
2321
+ const d = this.wizardData;
2322
+ if (!d.name) { errDiv.innerHTML = '<p class="error">Full name is required.</p>'; return; }
2323
+ this.state.wizardStep = 4;
2324
+ this.render();
2325
+ } else if (step === 4) {
2326
+ this.saveWizardFields(step);
2327
+ const d = this.wizardData;
2328
+
2329
+ nextBtn.disabled = true;
2330
+ nextBtn.textContent = 'Setting up...';
2331
+
2332
+ try {
2333
+ const res = await fetch('/api/setup', {
2334
+ method: 'POST',
2335
+ headers: { 'Content-Type': 'application/json' },
2336
+ body: JSON.stringify({
2337
+ passphrase: d.passphrase,
2338
+ credentials: {
2339
+ card: { number: d.cardNumber, expiry: d.expiry, cvv: d.cvv },
2340
+ name: d.name,
2341
+ billingAddress: { street: d.street || '', city: d.city || '', state: d.state || '', zip: d.zip || '', country: d.country || 'US' },
2342
+ shippingAddress: { street: d.street || '', city: d.city || '', state: d.state || '', zip: d.zip || '', country: d.country || 'US' },
2343
+ email: d.email || '',
2344
+ phone: d.phone || '',
2345
+ },
2346
+ budget: parseFloat(d.budget) || 0,
2347
+ limitPerTx: parseFloat(d.limit) || 0,
2348
+ }),
2349
+ });
2350
+ const result = await res.json();
2351
+ if (result.error) {
2352
+ errDiv.innerHTML = '<p class="error">' + this.esc(result.error) + '</p>';
2353
+ nextBtn.disabled = false;
2354
+ nextBtn.textContent = 'Complete Setup';
2355
+ } else {
2356
+ this.state.isSetup = true;
2357
+ this.state.wallet = result.wallet;
2358
+ this.state.recentTransactions = [];
2359
+ this.wizardData = {};
2360
+ this.render();
2361
+ }
2362
+ } catch (e) {
2363
+ errDiv.innerHTML = '<p class="error">Setup request failed.</p>';
2364
+ nextBtn.disabled = false;
2365
+ nextBtn.textContent = 'Complete Setup';
2366
+ }
2367
+ }
2368
+ });
2369
+ },
2370
+
2371
+ saveWizardFields(step) {
2372
+ const val = (id) => { const el = document.getElementById(id); return el ? el.value : ''; };
2373
+ if (step === 2) {
2374
+ this.wizardData.cardNumber = val('w_cardNum');
2375
+ this.wizardData.expiry = val('w_expiry');
2376
+ this.wizardData.cvv = val('w_cvv');
2377
+ } else if (step === 3) {
2378
+ this.wizardData.name = val('w_name');
2379
+ this.wizardData.email = val('w_email');
2380
+ this.wizardData.phone = val('w_phone');
2381
+ this.wizardData.street = val('w_street');
2382
+ this.wizardData.city = val('w_city');
2383
+ this.wizardData.state = val('w_state');
2384
+ this.wizardData.zip = val('w_zip');
2385
+ this.wizardData.country = val('w_country');
2386
+ } else if (step === 4) {
2387
+ this.wizardData.budget = val('w_budget');
2388
+ this.wizardData.limit = val('w_limit');
2389
+ }
2390
+ },
2391
+
2392
+ restoreWizardFields(step) {
2393
+ const set = (id, val) => { const el = document.getElementById(id); if (el && val) el.value = val; };
2394
+ if (step === 2) {
2395
+ set('w_cardNum', this.wizardData.cardNumber);
2396
+ set('w_expiry', this.wizardData.expiry);
2397
+ set('w_cvv', this.wizardData.cvv);
2398
+ } else if (step === 3) {
2399
+ set('w_name', this.wizardData.name);
2400
+ set('w_email', this.wizardData.email);
2401
+ set('w_phone', this.wizardData.phone);
2402
+ set('w_street', this.wizardData.street);
2403
+ set('w_city', this.wizardData.city);
2404
+ set('w_state', this.wizardData.state);
2405
+ set('w_zip', this.wizardData.zip);
2406
+ set('w_country', this.wizardData.country);
2407
+ } else if (step === 4) {
2408
+ set('w_budget', this.wizardData.budget);
2409
+ set('w_limit', this.wizardData.limit);
2410
+ }
2411
+ },
2412
+
2413
+ esc(s) {
2414
+ const d = document.createElement('div');
2415
+ d.textContent = s;
2416
+ return d.innerHTML;
2417
+ },
2418
+ };
2419
+
2420
+ App.init();
2421
+ </script>
2422
+ </body>
2423
+ </html>`;
2424
+ }
2425
+ var init_html = __esm({
2426
+ "src/server/html.ts"() {
2427
+ "use strict";
2428
+ init_esm_shims();
2429
+ }
2430
+ });
2431
+
2432
+ // src/server/routes.ts
2433
+ import { existsSync as existsSync2, mkdirSync as mkdirSync7 } from "fs";
2434
+ function handleGetStatus() {
2435
+ const isSetup = existsSync2(getCredentialsPath());
2436
+ if (!isSetup) {
2437
+ return { status: 200, body: { isSetup: false } };
2438
+ }
2439
+ try {
2440
+ const bm = new BudgetManager();
2441
+ const wallet = bm.getBalance();
2442
+ const tm = new TransactionManager();
2443
+ const recent = tm.list().slice(-10).reverse();
2444
+ return {
2445
+ status: 200,
2446
+ body: { isSetup: true, wallet, recentTransactions: recent }
2447
+ };
2448
+ } catch {
2449
+ return { status: 200, body: { isSetup: true, wallet: null, recentTransactions: [] } };
2450
+ }
2451
+ }
2452
+ function handlePostSetup(body) {
2453
+ if (!body.passphrase || !body.credentials) {
2454
+ return { status: 400, body: { error: "Missing passphrase or credentials" } };
2455
+ }
2456
+ try {
2457
+ const home = getHomePath();
2458
+ mkdirSync7(home, { recursive: true });
2459
+ const vault = encrypt(body.credentials, body.passphrase);
2460
+ saveVault(vault, getCredentialsPath());
2461
+ const keys = generateKeyPair(body.passphrase);
2462
+ mkdirSync7(getKeysPath(), { recursive: true });
2463
+ saveKeyPair(keys);
2464
+ const bm = new BudgetManager();
2465
+ bm.initWallet(body.budget || 0, body.limitPerTx || 0);
2466
+ const audit = new AuditLogger();
2467
+ audit.log("SETUP", { source: "dashboard", message: "credentials encrypted, keypair generated, wallet initialized" });
2468
+ const wallet = bm.getBalance();
2469
+ return { status: 200, body: { success: true, wallet } };
2470
+ } catch (err) {
2471
+ const message = err instanceof Error ? err.message : "Setup failed";
2472
+ return { status: 500, body: { error: message } };
2473
+ }
2474
+ }
2475
+ function handlePostFund(body) {
2476
+ if (!body.amount || body.amount <= 0) {
2477
+ return { status: 400, body: { error: "Amount must be positive" } };
2478
+ }
2479
+ try {
2480
+ const bm = new BudgetManager();
2481
+ const wallet = bm.addFunds(body.amount);
2482
+ const audit = new AuditLogger();
2483
+ audit.log("ADD_FUNDS", { source: "dashboard", amount: body.amount });
2484
+ return { status: 200, body: { success: true, wallet } };
2485
+ } catch (err) {
2486
+ const message = err instanceof Error ? err.message : "Failed to add funds";
2487
+ return { status: 500, body: { error: message } };
2488
+ }
2489
+ }
2490
+ var init_routes = __esm({
2491
+ "src/server/routes.ts"() {
2492
+ "use strict";
2493
+ init_esm_shims();
2494
+ init_vault();
2495
+ init_keypair();
2496
+ init_budget();
2497
+ init_manager();
2498
+ init_logger();
2499
+ init_paths();
2500
+ }
2501
+ });
2502
+
2503
+ // src/server/index.ts
2504
+ import { createServer as createServer2 } from "http";
2505
+ function parseBody2(req) {
2506
+ return new Promise((resolve2, reject) => {
2507
+ const chunks = [];
2508
+ let size = 0;
2509
+ req.on("data", (chunk) => {
2510
+ size += chunk.length;
2511
+ if (size > MAX_BODY2) {
2512
+ req.destroy();
2513
+ reject(new Error("Request body too large"));
2514
+ return;
2515
+ }
2516
+ chunks.push(chunk);
2517
+ });
2518
+ req.on("end", () => {
2519
+ try {
2520
+ const text = Buffer.concat(chunks).toString("utf8");
2521
+ resolve2(text ? JSON.parse(text) : {});
2522
+ } catch {
2523
+ reject(new Error("Invalid JSON"));
2524
+ }
2525
+ });
2526
+ req.on("error", reject);
2527
+ });
2528
+ }
2529
+ function sendJson2(res, status, body) {
2530
+ const json = JSON.stringify(body);
2531
+ res.writeHead(status, {
2532
+ "Content-Type": "application/json",
2533
+ "Content-Length": Buffer.byteLength(json)
2534
+ });
2535
+ res.end(json);
2536
+ }
2537
+ function sendHtml2(res, html) {
2538
+ res.writeHead(200, {
2539
+ "Content-Type": "text/html; charset=utf-8",
2540
+ "Content-Length": Buffer.byteLength(html)
2541
+ });
2542
+ res.end(html);
2543
+ }
2544
+ function startServer(port) {
2545
+ return new Promise((resolve2, reject) => {
2546
+ const server = createServer2(async (req, res) => {
2547
+ const url = req.url ?? "/";
2548
+ const method = req.method ?? "GET";
2549
+ try {
2550
+ if (method === "GET" && url === "/api/status") {
2551
+ const result = handleGetStatus();
2552
+ sendJson2(res, result.status, result.body);
2553
+ } else if (method === "POST" && url === "/api/setup") {
2554
+ const body = await parseBody2(req);
2555
+ const result = handlePostSetup(body);
2556
+ sendJson2(res, result.status, result.body);
2557
+ } else if (method === "POST" && url === "/api/fund") {
2558
+ const body = await parseBody2(req);
2559
+ const result = handlePostFund(body);
2560
+ sendJson2(res, result.status, result.body);
2561
+ } else if (method === "GET" && (url === "/" || url === "/index.html")) {
2562
+ sendHtml2(res, getDashboardHtml());
2563
+ } else {
2564
+ sendJson2(res, 404, { error: "Not found" });
2565
+ }
2566
+ } catch (err) {
2567
+ const message = err instanceof Error ? err.message : "Internal error";
2568
+ sendJson2(res, 500, { error: message });
2569
+ }
2570
+ });
2571
+ server.on("error", (err) => {
2572
+ if (err.code === "EADDRINUSE") {
2573
+ reject(new Error(`Port ${port} is already in use. Try a different port with --port <number>.`));
2574
+ } else {
2575
+ reject(err);
2576
+ }
2577
+ });
2578
+ server.listen(port, "127.0.0.1", () => {
2579
+ resolve2(server);
2580
+ });
2581
+ });
2582
+ }
2583
+ var MAX_BODY2;
2584
+ var init_server = __esm({
2585
+ "src/server/index.ts"() {
2586
+ "use strict";
2587
+ init_esm_shims();
2588
+ init_html();
2589
+ init_routes();
2590
+ MAX_BODY2 = 1048576;
2591
+ }
2592
+ });
2593
+
2594
+ // src/commands/dashboard.ts
2595
+ var dashboard_exports = {};
2596
+ __export(dashboard_exports, {
2597
+ dashboardCommand: () => dashboardCommand
2598
+ });
2599
+ async function dashboardCommand(options) {
2600
+ const port = parseInt(options.port, 10) || 3141;
2601
+ const url = `http://127.0.0.1:${port}`;
2602
+ try {
2603
+ const server = await startServer(port);
2604
+ console.log(`AgentPay Dashboard running at ${url}`);
2605
+ console.log("Press Ctrl+C to stop.\n");
2606
+ openBrowser(url);
2607
+ const shutdown = () => {
2608
+ console.log("\nShutting down dashboard...");
2609
+ server.close(() => process.exit(0));
2610
+ setTimeout(() => process.exit(0), 3e3);
2611
+ };
2612
+ process.on("SIGINT", shutdown);
2613
+ process.on("SIGTERM", shutdown);
2614
+ } catch (err) {
2615
+ const message = err instanceof Error ? err.message : "Failed to start server";
2616
+ console.error(message);
2617
+ process.exit(1);
2618
+ }
2619
+ }
2620
+ var init_dashboard = __esm({
2621
+ "src/commands/dashboard.ts"() {
2622
+ "use strict";
2623
+ init_esm_shims();
2624
+ init_server();
2625
+ init_open_browser();
2626
+ }
2627
+ });
2628
+
2629
+ // src/commands/mcp.ts
2630
+ var mcp_exports = {};
2631
+ __export(mcp_exports, {
2632
+ mcpCommand: () => mcpCommand
2633
+ });
2634
+ async function mcpCommand(options) {
2635
+ try {
2636
+ const { startServer: startServer2 } = await import("@useagentpay/mcp-server");
2637
+ await startServer2({ http: options.http });
2638
+ } catch (err) {
2639
+ if (err instanceof Error && "code" in err && err.code === "ERR_MODULE_NOT_FOUND") {
2640
+ console.error("MCP server package not installed.");
2641
+ console.error("Run: pnpm add @useagentpay/mcp-server");
2642
+ process.exit(1);
2643
+ }
2644
+ throw err;
2645
+ }
2646
+ }
2647
+ var init_mcp = __esm({
2648
+ "src/commands/mcp.ts"() {
2649
+ "use strict";
2650
+ init_esm_shims();
2651
+ }
2652
+ });
2653
+
2654
+ // src/cli.ts
2655
+ init_esm_shims();
2656
+ import { Command } from "commander";
2657
+
2658
+ // src/index.ts
2659
+ init_esm_shims();
2660
+ init_errors();
2661
+ init_ids();
2662
+ init_display();
2663
+ init_paths();
2664
+ init_vault();
2665
+ init_keypair();
2666
+ init_mandate();
2667
+ init_budget();
2668
+ init_manager();
2669
+ init_poller();
2670
+ init_logger();
2671
+ init_executor();
2672
+ init_placeholder();
2673
+ init_agentpay();
2674
+ var VERSION = "0.1.0";
2675
+
2676
+ // src/cli.ts
2677
+ var program = new Command();
2678
+ program.name("agentpay").description("Local-first payments SDK for AI agents").version(VERSION);
2679
+ program.command("setup").description("Set up AgentPay with your billing credentials").action(async () => {
2680
+ const { setupCommand: setupCommand2 } = await Promise.resolve().then(() => (init_setup(), setup_exports));
2681
+ await setupCommand2();
2682
+ });
2683
+ program.command("budget").description("View or configure spending budget").option("--set <amount>", "Set total budget").option("--limit-per-tx <amount>", "Set per-transaction limit").action(async (options) => {
2684
+ const { budgetCommand: budgetCommand2 } = await Promise.resolve().then(() => (init_budget2(), budget_exports));
2685
+ budgetCommand2(options);
2686
+ });
2687
+ program.command("pending").description("List pending purchase proposals").action(async () => {
2688
+ const { pendingCommand: pendingCommand2 } = await Promise.resolve().then(() => (init_pending(), pending_exports));
2689
+ pendingCommand2();
2690
+ });
2691
+ program.command("approve <txId>").description("Approve a pending purchase").action(async (txId) => {
2692
+ const { approveCommand: approveCommand2 } = await Promise.resolve().then(() => (init_approve(), approve_exports));
2693
+ await approveCommand2(txId);
2694
+ });
2695
+ program.command("reject <txId>").description("Reject a pending purchase").option("--reason <reason>", "Reason for rejection").action(async (txId, options) => {
2696
+ const { rejectCommand: rejectCommand2 } = await Promise.resolve().then(() => (init_reject(), reject_exports));
2697
+ rejectCommand2(txId, options);
2698
+ });
2699
+ program.command("status").description("Show wallet status and recent transactions").action(async () => {
2700
+ const { statusCommand: statusCommand2 } = await Promise.resolve().then(() => (init_status(), status_exports));
2701
+ statusCommand2();
2702
+ });
2703
+ program.command("history").description("Show full transaction history").action(async () => {
2704
+ const { historyCommand: historyCommand2 } = await Promise.resolve().then(() => (init_history(), history_exports));
2705
+ historyCommand2();
2706
+ });
2707
+ program.command("qr").description("Display QR code for web-based setup").option("--budget <amount>", "Suggested budget amount").option("--message <msg>", "Message to display on setup page").action(async (options) => {
2708
+ const { qrCommand: qrCommand2 } = await Promise.resolve().then(() => (init_qr(), qr_exports));
2709
+ await qrCommand2(options);
2710
+ });
2711
+ program.command("reset").description("Delete all AgentPay data").action(async () => {
2712
+ const { resetCommand: resetCommand2 } = await Promise.resolve().then(() => (init_reset(), reset_exports));
2713
+ await resetCommand2();
2714
+ });
2715
+ program.command("buy").description("Propose, approve, and execute a purchase").requiredOption("--merchant <name>", "Merchant name").requiredOption("--description <desc>", "Purchase description").requiredOption("--url <url>", "Product/checkout URL").option("--amount <amount>", "Purchase amount (auto-detected from page if omitted)").option("--pickup", "Select in-store pickup").action(async (options) => {
2716
+ const { buyCommand: buyCommand2 } = await Promise.resolve().then(() => (init_buy(), buy_exports));
2717
+ await buyCommand2(options);
2718
+ });
2719
+ program.command("dashboard").description("Open the AgentPay dashboard in your browser").option("--port <port>", "Port for dashboard server", "3141").action(async (options) => {
2720
+ const { dashboardCommand: dashboardCommand2 } = await Promise.resolve().then(() => (init_dashboard(), dashboard_exports));
2721
+ await dashboardCommand2(options);
2722
+ });
2723
+ program.command("mcp").description("Start AgentPay MCP server (stdio transport)").option("--http", "Use HTTP transport instead of stdio").action(async (options) => {
2724
+ const { mcpCommand: mcpCommand2 } = await Promise.resolve().then(() => (init_mcp(), mcp_exports));
2725
+ await mcpCommand2(options);
2726
+ });
2727
+ program.parse();
2728
+ //# sourceMappingURL=cli.js.map