@useagentpay/sdk 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -541,7 +541,7 @@ __export(approval_server_exports, {
541
541
  });
542
542
  import { createServer } from "http";
543
543
  import { randomBytes as randomBytes3 } from "crypto";
544
- import { join as join2 } from "path";
544
+ import { join as join3 } from "path";
545
545
  function parseBody(req) {
546
546
  return new Promise((resolve, reject) => {
547
547
  const chunks = [];
@@ -627,7 +627,7 @@ function createApprovalServer(tx, tm, audit, options) {
627
627
  return;
628
628
  }
629
629
  try {
630
- const keyPath = options?.home ? join2(options.home, "keys", "private.pem") : void 0;
630
+ const keyPath = options?.home ? join3(options.home, "keys", "private.pem") : void 0;
631
631
  const privateKeyPem = loadPrivateKey(keyPath);
632
632
  const txDetails = {
633
633
  txId: tx.id,
@@ -743,6 +743,200 @@ var init_approval_server = __esm({
743
743
  }
744
744
  });
745
745
 
746
+ // src/tunnel/tunnel.ts
747
+ import { spawn } from "child_process";
748
+ async function openTunnel(port) {
749
+ return new Promise((resolve, reject) => {
750
+ let child;
751
+ try {
752
+ child = spawn("cloudflared", ["tunnel", "--url", `http://127.0.0.1:${port}`], {
753
+ stdio: ["ignore", "pipe", "pipe"]
754
+ });
755
+ } catch {
756
+ reject(new Error(
757
+ "cloudflared is required for mobile approval. Install it: https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/"
758
+ ));
759
+ return;
760
+ }
761
+ let resolved = false;
762
+ const timeout = setTimeout(() => {
763
+ if (!resolved) {
764
+ resolved = true;
765
+ child.kill();
766
+ reject(new Error("cloudflared tunnel timed out waiting for URL (15s). Is cloudflared installed?"));
767
+ }
768
+ }, 15e3);
769
+ const close = () => {
770
+ child.kill();
771
+ };
772
+ const chunks = [];
773
+ child.stderr?.on("data", (data) => {
774
+ const text = data.toString();
775
+ chunks.push(text);
776
+ const match = text.match(/https:\/\/[a-z0-9-]+\.trycloudflare\.com/);
777
+ if (match && !resolved) {
778
+ resolved = true;
779
+ clearTimeout(timeout);
780
+ resolve({ url: match[0], close });
781
+ }
782
+ });
783
+ child.on("error", (err) => {
784
+ if (!resolved) {
785
+ resolved = true;
786
+ clearTimeout(timeout);
787
+ reject(new Error(
788
+ `cloudflared failed to start: ${err.message}. Install it: https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/`
789
+ ));
790
+ }
791
+ });
792
+ child.on("exit", (code) => {
793
+ if (!resolved) {
794
+ resolved = true;
795
+ clearTimeout(timeout);
796
+ const output = chunks.join("");
797
+ reject(new Error(
798
+ `cloudflared exited with code ${code}. Output: ${output.slice(0, 500)}`
799
+ ));
800
+ }
801
+ });
802
+ });
803
+ }
804
+ var init_tunnel = __esm({
805
+ "src/tunnel/tunnel.ts"() {
806
+ "use strict";
807
+ init_esm_shims();
808
+ }
809
+ });
810
+
811
+ // src/notify/notify.ts
812
+ import { execFile } from "child_process";
813
+ async function sendNotification(payload, options) {
814
+ const results = [];
815
+ if (options.command) {
816
+ const cmd = options.command.replace(/\{\{url\}\}/g, payload.url);
817
+ try {
818
+ await runCommand(cmd);
819
+ results.push({ method: "command", success: true });
820
+ } catch (err) {
821
+ results.push({
822
+ method: "command",
823
+ success: false,
824
+ error: err instanceof Error ? err.message : "Command failed"
825
+ });
826
+ }
827
+ }
828
+ if (options.webhookUrl) {
829
+ try {
830
+ const res = await fetch(options.webhookUrl, {
831
+ method: "POST",
832
+ headers: { "Content-Type": "application/json" },
833
+ body: JSON.stringify(payload)
834
+ });
835
+ if (!res.ok) {
836
+ results.push({
837
+ method: "webhook",
838
+ success: false,
839
+ error: `Webhook returned ${res.status}`
840
+ });
841
+ } else {
842
+ results.push({ method: "webhook", success: true });
843
+ }
844
+ } catch (err) {
845
+ results.push({
846
+ method: "webhook",
847
+ success: false,
848
+ error: err instanceof Error ? err.message : "Webhook failed"
849
+ });
850
+ }
851
+ }
852
+ if (results.length === 0) {
853
+ results.push({
854
+ method: "none",
855
+ success: false,
856
+ error: "No notification method configured. Set AGENTPAY_NOTIFY_COMMAND or AGENTPAY_NOTIFY_WEBHOOK."
857
+ });
858
+ }
859
+ return results;
860
+ }
861
+ function runCommand(cmd) {
862
+ return new Promise((resolve, reject) => {
863
+ execFile("sh", ["-c", cmd], { timeout: 1e4 }, (err) => {
864
+ if (err) reject(err);
865
+ else resolve();
866
+ });
867
+ });
868
+ }
869
+ var init_notify = __esm({
870
+ "src/notify/notify.ts"() {
871
+ "use strict";
872
+ init_esm_shims();
873
+ }
874
+ });
875
+
876
+ // src/server/mobile-approval-server.ts
877
+ var mobile_approval_server_exports = {};
878
+ __export(mobile_approval_server_exports, {
879
+ requestMobileApproval: () => requestMobileApproval
880
+ });
881
+ async function requestMobileApproval(tx, tm, audit, options) {
882
+ const handle = createApprovalServer(tx, tm, audit, {
883
+ openBrowser: false,
884
+ home: options.home
885
+ });
886
+ await waitForPort(handle);
887
+ let tunnel;
888
+ try {
889
+ tunnel = await openTunnel(handle.port);
890
+ const approvalUrl = `${tunnel.url}/approve/${tx.id}?token=${handle.token}`;
891
+ const payload = {
892
+ url: approvalUrl,
893
+ txId: tx.id,
894
+ merchant: tx.merchant,
895
+ amount: tx.amount
896
+ };
897
+ const notifyResults = await sendNotification(payload, options.notify);
898
+ audit.log("MOBILE_APPROVAL_REQUESTED", {
899
+ txId: tx.id,
900
+ tunnelUrl: tunnel.url,
901
+ notifyResults
902
+ });
903
+ const result = await handle.promise;
904
+ return {
905
+ ...result,
906
+ approvalUrl,
907
+ notifyResults
908
+ };
909
+ } finally {
910
+ tunnel?.close();
911
+ }
912
+ }
913
+ function waitForPort(handle, timeoutMs = 5e3) {
914
+ return new Promise((resolve, reject) => {
915
+ const start = Date.now();
916
+ const check = () => {
917
+ if (handle.port > 0) {
918
+ resolve();
919
+ return;
920
+ }
921
+ if (Date.now() - start > timeoutMs) {
922
+ reject(new Error("Approval server failed to bind to a port"));
923
+ return;
924
+ }
925
+ setTimeout(check, 50);
926
+ };
927
+ check();
928
+ });
929
+ }
930
+ var init_mobile_approval_server = __esm({
931
+ "src/server/mobile-approval-server.ts"() {
932
+ "use strict";
933
+ init_esm_shims();
934
+ init_approval_server();
935
+ init_tunnel();
936
+ init_notify();
937
+ }
938
+ });
939
+
746
940
  // src/server/passphrase-html.ts
747
941
  function esc2(s) {
748
942
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
@@ -1270,10 +1464,46 @@ var BudgetManager = class {
1270
1464
  }
1271
1465
  };
1272
1466
 
1273
- // src/transactions/manager.ts
1467
+ // src/config/config.ts
1274
1468
  init_esm_shims();
1469
+ init_paths();
1275
1470
  import { readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4 } from "fs";
1276
- import { dirname as dirname4 } from "path";
1471
+ import { dirname as dirname4, join as join2 } from "path";
1472
+
1473
+ // src/config/types.ts
1474
+ init_esm_shims();
1475
+ var DEFAULT_CONFIG = {
1476
+ mobileMode: false
1477
+ };
1478
+
1479
+ // src/config/config.ts
1480
+ function getConfigPath(home) {
1481
+ return join2(home ?? getHomePath(), "config.json");
1482
+ }
1483
+ function loadConfig(home) {
1484
+ try {
1485
+ const data = readFileSync4(getConfigPath(home), "utf8");
1486
+ return { ...DEFAULT_CONFIG, ...JSON.parse(data) };
1487
+ } catch {
1488
+ return { ...DEFAULT_CONFIG };
1489
+ }
1490
+ }
1491
+ function saveConfig(config, home) {
1492
+ const path2 = getConfigPath(home);
1493
+ mkdirSync4(dirname4(path2), { recursive: true });
1494
+ writeFileSync4(path2, JSON.stringify(config, null, 2), { mode: 384 });
1495
+ }
1496
+ function setMobileMode(enabled, home) {
1497
+ const config = loadConfig(home);
1498
+ config.mobileMode = enabled;
1499
+ saveConfig(config, home);
1500
+ return config;
1501
+ }
1502
+
1503
+ // src/transactions/manager.ts
1504
+ init_esm_shims();
1505
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5 } from "fs";
1506
+ import { dirname as dirname5 } from "path";
1277
1507
  init_paths();
1278
1508
  var TransactionManager = class {
1279
1509
  txPath;
@@ -1282,15 +1512,15 @@ var TransactionManager = class {
1282
1512
  }
1283
1513
  loadAll() {
1284
1514
  try {
1285
- const data = readFileSync4(this.txPath, "utf8");
1515
+ const data = readFileSync5(this.txPath, "utf8");
1286
1516
  return JSON.parse(data);
1287
1517
  } catch {
1288
1518
  return [];
1289
1519
  }
1290
1520
  }
1291
1521
  saveAll(transactions) {
1292
- mkdirSync4(dirname4(this.txPath), { recursive: true });
1293
- writeFileSync4(this.txPath, JSON.stringify(transactions, null, 2), { mode: 384 });
1522
+ mkdirSync5(dirname5(this.txPath), { recursive: true });
1523
+ writeFileSync5(this.txPath, JSON.stringify(transactions, null, 2), { mode: 384 });
1294
1524
  }
1295
1525
  propose(options) {
1296
1526
  const transactions = this.loadAll();
@@ -1371,8 +1601,8 @@ init_poller();
1371
1601
  // src/audit/logger.ts
1372
1602
  init_esm_shims();
1373
1603
  init_paths();
1374
- import { appendFileSync, readFileSync as readFileSync5, mkdirSync as mkdirSync5 } from "fs";
1375
- import { dirname as dirname5 } from "path";
1604
+ import { appendFileSync, readFileSync as readFileSync6, mkdirSync as mkdirSync6 } from "fs";
1605
+ import { dirname as dirname6 } from "path";
1376
1606
  var AuditLogger = class {
1377
1607
  logPath;
1378
1608
  constructor(logPath) {
@@ -1383,12 +1613,12 @@ var AuditLogger = class {
1383
1613
  const detailsStr = JSON.stringify(details);
1384
1614
  const entry = `${timestamp} ${action} ${detailsStr}
1385
1615
  `;
1386
- mkdirSync5(dirname5(this.logPath), { recursive: true });
1616
+ mkdirSync6(dirname6(this.logPath), { recursive: true });
1387
1617
  appendFileSync(this.logPath, entry, { mode: 384 });
1388
1618
  }
1389
1619
  getLog() {
1390
1620
  try {
1391
- const data = readFileSync5(this.logPath, "utf8");
1621
+ const data = readFileSync6(this.logPath, "utf8");
1392
1622
  return data.trim().split("\n").filter(Boolean);
1393
1623
  } catch {
1394
1624
  return [];
@@ -1655,13 +1885,16 @@ var PurchaseExecutor = class {
1655
1885
 
1656
1886
  // src/index.ts
1657
1887
  init_approval_server();
1888
+ init_mobile_approval_server();
1889
+ init_tunnel();
1890
+ init_notify();
1658
1891
 
1659
1892
  // src/server/setup-server.ts
1660
1893
  init_esm_shims();
1661
1894
  import { createServer as createServer2 } from "http";
1662
1895
  import { randomBytes as randomBytes4 } from "crypto";
1663
- import { mkdirSync as mkdirSync6 } from "fs";
1664
- import { join as join3 } from "path";
1896
+ import { mkdirSync as mkdirSync7 } from "fs";
1897
+ import { join as join4 } from "path";
1665
1898
 
1666
1899
  // src/server/setup-html.ts
1667
1900
  init_esm_shims();
@@ -2139,7 +2372,7 @@ function createSetupServer(options) {
2139
2372
  return;
2140
2373
  }
2141
2374
  try {
2142
- mkdirSync6(home, { recursive: true });
2375
+ mkdirSync7(home, { recursive: true });
2143
2376
  const credentials = {
2144
2377
  card: { number: body.card.number, expiry: body.card.expiry, cvv: body.card.cvv },
2145
2378
  name: body.name,
@@ -2160,18 +2393,18 @@ function createSetupServer(options) {
2160
2393
  email: body.email,
2161
2394
  phone: body.phone
2162
2395
  };
2163
- const credPath = join3(home, "credentials.enc");
2396
+ const credPath = join4(home, "credentials.enc");
2164
2397
  const vault = encrypt(credentials, passphrase);
2165
2398
  saveVault(vault, credPath);
2166
- const keysDir = join3(home, "keys");
2167
- mkdirSync6(keysDir, { recursive: true });
2399
+ const keysDir = join4(home, "keys");
2400
+ mkdirSync7(keysDir, { recursive: true });
2168
2401
  const keys = generateKeyPair(passphrase);
2169
- saveKeyPair(keys, join3(keysDir, "public.pem"), join3(keysDir, "private.pem"));
2402
+ saveKeyPair(keys, join4(keysDir, "public.pem"), join4(keysDir, "private.pem"));
2170
2403
  const budget = typeof body.budget === "number" ? body.budget : 0;
2171
2404
  const limitPerTx = typeof body.limitPerTx === "number" ? body.limitPerTx : 0;
2172
- const bm = new BudgetManager(join3(home, "wallet.json"));
2405
+ const bm = new BudgetManager(join4(home, "wallet.json"));
2173
2406
  bm.initWallet(budget, limitPerTx);
2174
- const audit = new AuditLogger(join3(home, "audit.log"));
2407
+ const audit = new AuditLogger(join4(home, "audit.log"));
2175
2408
  audit.log("SETUP", { message: "credentials encrypted, keypair generated, wallet initialized", source: "browser-setup" });
2176
2409
  } catch (err) {
2177
2410
  const msg = err instanceof Error ? err.message : "Setup failed";
@@ -2749,7 +2982,7 @@ App.init();
2749
2982
 
2750
2983
  // src/server/routes.ts
2751
2984
  init_esm_shims();
2752
- import { existsSync as existsSync2, mkdirSync as mkdirSync7 } from "fs";
2985
+ import { existsSync as existsSync2, mkdirSync as mkdirSync8 } from "fs";
2753
2986
  init_keypair();
2754
2987
  init_paths();
2755
2988
  function handleGetStatus() {
@@ -2776,11 +3009,11 @@ function handlePostSetup(body) {
2776
3009
  }
2777
3010
  try {
2778
3011
  const home = getHomePath();
2779
- mkdirSync7(home, { recursive: true });
3012
+ mkdirSync8(home, { recursive: true });
2780
3013
  const vault = encrypt(body.credentials, body.passphrase);
2781
3014
  saveVault(vault, getCredentialsPath());
2782
3015
  const keys = generateKeyPair(body.passphrase);
2783
- mkdirSync7(getKeysPath(), { recursive: true });
3016
+ mkdirSync8(getKeysPath(), { recursive: true });
2784
3017
  saveKeyPair(keys);
2785
3018
  const bm = new BudgetManager();
2786
3019
  bm.initWallet(body.budget || 0, body.limitPerTx || 0);
@@ -2930,7 +3163,7 @@ async function promptPassphraseSafe(context) {
2930
3163
 
2931
3164
  // src/agentpay.ts
2932
3165
  init_esm_shims();
2933
- import { join as join4 } from "path";
3166
+ import { join as join5 } from "path";
2934
3167
  init_mandate();
2935
3168
  init_paths();
2936
3169
  init_errors();
@@ -2944,9 +3177,9 @@ var AgentPay = class {
2944
3177
  constructor(options) {
2945
3178
  this.home = options?.home ?? getHomePath();
2946
3179
  this.passphrase = options?.passphrase;
2947
- this.budgetManager = new BudgetManager(join4(this.home, "wallet.json"));
2948
- this.txManager = new TransactionManager(join4(this.home, "transactions.json"));
2949
- this.auditLogger = new AuditLogger(join4(this.home, "audit.log"));
3180
+ this.budgetManager = new BudgetManager(join5(this.home, "wallet.json"));
3181
+ this.txManager = new TransactionManager(join5(this.home, "transactions.json"));
3182
+ this.auditLogger = new AuditLogger(join5(this.home, "audit.log"));
2950
3183
  this.executor = new PurchaseExecutor(options?.executor);
2951
3184
  }
2952
3185
  get wallet() {
@@ -2960,6 +3193,13 @@ var AgentPay = class {
2960
3193
  }
2961
3194
  };
2962
3195
  }
3196
+ get config() {
3197
+ return {
3198
+ get: () => loadConfig(this.home),
3199
+ setMobileMode: (enabled) => setMobileMode(enabled, this.home),
3200
+ save: (config) => saveConfig(config, this.home)
3201
+ };
3202
+ }
2963
3203
  get transactions() {
2964
3204
  return {
2965
3205
  propose: (options) => {
@@ -2978,13 +3218,28 @@ var AgentPay = class {
2978
3218
  if (!tx) throw new Error(`Transaction ${txId} not found.`);
2979
3219
  if (tx.status !== "pending") throw new Error(`Transaction ${txId} is not pending.`);
2980
3220
  const { existsSync: existsSync3 } = await import("fs");
2981
- const keyPath = join4(this.home, "keys", "private.pem");
3221
+ const keyPath = join5(this.home, "keys", "private.pem");
2982
3222
  if (!existsSync3(keyPath)) {
2983
3223
  throw new Error('Private key not found. Run "agentpay setup" first.');
2984
3224
  }
2985
3225
  const { requestBrowserApproval: requestBrowserApproval2 } = await Promise.resolve().then(() => (init_approval_server(), approval_server_exports));
2986
3226
  return requestBrowserApproval2(tx, this.txManager, this.auditLogger, this.home);
2987
3227
  },
3228
+ requestMobileApproval: async (txId, notify) => {
3229
+ const tx = this.txManager.get(txId);
3230
+ if (!tx) throw new Error(`Transaction ${txId} not found.`);
3231
+ if (tx.status !== "pending") throw new Error(`Transaction ${txId} is not pending.`);
3232
+ const { existsSync: existsSync3 } = await import("fs");
3233
+ const keyPath = join5(this.home, "keys", "private.pem");
3234
+ if (!existsSync3(keyPath)) {
3235
+ throw new Error('Private key not found. Run "agentpay setup" first.');
3236
+ }
3237
+ const { requestMobileApproval: requestMobileApproval2 } = await Promise.resolve().then(() => (init_mobile_approval_server(), mobile_approval_server_exports));
3238
+ return requestMobileApproval2(tx, this.txManager, this.auditLogger, {
3239
+ notify,
3240
+ home: this.home
3241
+ });
3242
+ },
2988
3243
  execute: async (txId) => {
2989
3244
  const tx = this.txManager.get(txId);
2990
3245
  if (!tx) throw new Error(`Transaction ${txId} not found.`);
@@ -3014,7 +3269,7 @@ var AgentPay = class {
3014
3269
  if (!this.passphrase) {
3015
3270
  throw new Error("Passphrase required for execution. Pass it to AgentPay constructor.");
3016
3271
  }
3017
- const vaultPath = join4(this.home, "credentials.enc");
3272
+ const vaultPath = join5(this.home, "credentials.enc");
3018
3273
  const vault = loadVault(vaultPath);
3019
3274
  const credentials = decrypt(vault, this.passphrase);
3020
3275
  const result = await this.executor.execute(tx, credentials);
@@ -3045,6 +3300,7 @@ var AgentPay = class {
3045
3300
  return { getLog: () => this.auditLogger.getLog() };
3046
3301
  }
3047
3302
  status() {
3303
+ const cfg = loadConfig(this.home);
3048
3304
  try {
3049
3305
  const wallet = this.budgetManager.getBalance();
3050
3306
  const pending = this.txManager.getPending();
@@ -3055,7 +3311,8 @@ var AgentPay = class {
3055
3311
  limitPerTx: wallet.limitPerTx,
3056
3312
  pending,
3057
3313
  recent,
3058
- isSetup: true
3314
+ isSetup: true,
3315
+ mobileMode: cfg.mobileMode
3059
3316
  };
3060
3317
  } catch {
3061
3318
  return {
@@ -3064,14 +3321,15 @@ var AgentPay = class {
3064
3321
  limitPerTx: 0,
3065
3322
  pending: [],
3066
3323
  recent: [],
3067
- isSetup: false
3324
+ isSetup: false,
3325
+ mobileMode: cfg.mobileMode
3068
3326
  };
3069
3327
  }
3070
3328
  }
3071
3329
  };
3072
3330
 
3073
3331
  // src/index.ts
3074
- var VERSION = true ? "0.1.2" : "0.0.0";
3332
+ var VERSION = true ? "0.1.3" : "0.0.0";
3075
3333
  export {
3076
3334
  AgentPay,
3077
3335
  AlreadyExecutedError,
@@ -3106,18 +3364,24 @@ export {
3106
3364
  getPlaceholderVariables,
3107
3365
  getTransactionsPath,
3108
3366
  getWalletPath,
3367
+ loadConfig,
3109
3368
  loadPrivateKey,
3110
3369
  loadPublicKey,
3111
3370
  loadVault,
3112
3371
  openBrowser,
3372
+ openTunnel,
3113
3373
  promptConfirm,
3114
3374
  promptInput,
3115
3375
  promptPassphrase,
3116
3376
  promptPassphraseSafe,
3117
3377
  requestBrowserApproval,
3118
3378
  requestBrowserSetup,
3379
+ requestMobileApproval,
3380
+ saveConfig,
3119
3381
  saveKeyPair,
3120
3382
  saveVault,
3383
+ sendNotification,
3384
+ setMobileMode,
3121
3385
  startServer as startDashboardServer,
3122
3386
  verifyMandate,
3123
3387
  waitForApproval