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