@tempoxyz/patron 0.0.21 → 0.0.22
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/bin.js +3 -3
- package/dist/chunks/{-NGLRKGBT.js → -PMO5HHO2.js} +127 -23
- package/dist/chunks/{chunk-UFBFF3R6.js → chunk-QKBTYUXG.js} +7 -2
- package/dist/chunks/{chunk-RDEPJ6XD.js → chunk-UWIBR62N.js} +3 -1
- package/dist/chunks/{hook-IWRRBFKG.js → hook-QEFJTAKR.js} +2 -2
- package/dist/chunks/{hook-process-FNSWFVFP.js → hook-process-D5ENJPRB.js} +2 -2
- package/dist/chunks/link-OU7W7IT6.js +226 -0
- package/dist/chunks/{wallet-WNPT6R4M.js → wallet-R3UWBXVC.js} +18 -1
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -4,14 +4,14 @@ const require = __patronCreateRequire(import.meta.url)
|
|
|
4
4
|
|
|
5
5
|
// src/bin.ts
|
|
6
6
|
if (process.argv[2] === "hook") {
|
|
7
|
-
const { runHookProcess } = await import("./chunks/hook-process-
|
|
7
|
+
const { runHookProcess } = await import("./chunks/hook-process-D5ENJPRB.js");
|
|
8
8
|
await runHookProcess().catch(() => void 0);
|
|
9
9
|
process.exit(0);
|
|
10
10
|
} else if (process.argv[2] === "pay") {
|
|
11
|
-
const { runPayProcess } = await import("./chunks/hook-process-
|
|
11
|
+
const { runPayProcess } = await import("./chunks/hook-process-D5ENJPRB.js");
|
|
12
12
|
await runPayProcess().catch(() => void 0);
|
|
13
13
|
process.exit(0);
|
|
14
14
|
} else {
|
|
15
|
-
const { run } = await import("./chunks/-
|
|
15
|
+
const { run } = await import("./chunks/-PMO5HHO2.js");
|
|
16
16
|
process.exitCode = await run(process.argv.slice(2));
|
|
17
17
|
}
|
|
@@ -2,6 +2,7 @@ import { createRequire as __patronCreateRequire } from 'node:module'
|
|
|
2
2
|
const require = __patronCreateRequire(import.meta.url)
|
|
3
3
|
import {
|
|
4
4
|
FileTokenStore,
|
|
5
|
+
LINK_TOKEN_STEP,
|
|
5
6
|
LeaderboardResponse,
|
|
6
7
|
PatronInfo,
|
|
7
8
|
STARTER_TOKENS,
|
|
@@ -28,7 +29,7 @@ import {
|
|
|
28
29
|
withPatronStatus,
|
|
29
30
|
withoutPatronHandlers,
|
|
30
31
|
writeJsonObject
|
|
31
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-QKBTYUXG.js";
|
|
32
33
|
import {
|
|
33
34
|
startRefill
|
|
34
35
|
} from "./chunk-HEQBEBKL.js";
|
|
@@ -57,12 +58,12 @@ import {
|
|
|
57
58
|
withLock,
|
|
58
59
|
writeConfig,
|
|
59
60
|
writePrivateFile
|
|
60
|
-
} from "./chunk-
|
|
61
|
+
} from "./chunk-UWIBR62N.js";
|
|
61
62
|
import "./chunk-GTMLTZRN.js";
|
|
62
63
|
|
|
63
64
|
// src/index.ts
|
|
64
65
|
import { readFileSync } from "node:fs";
|
|
65
|
-
import { rm as
|
|
66
|
+
import { rm as rm5 } from "node:fs/promises";
|
|
66
67
|
import { homedir as homedir2 } from "node:os";
|
|
67
68
|
|
|
68
69
|
// src/agents.ts
|
|
@@ -680,9 +681,19 @@ ${styleText(title, "bold", color)}
|
|
|
680
681
|
}
|
|
681
682
|
|
|
682
683
|
// src/purchase.ts
|
|
684
|
+
import { rm as rm4 } from "node:fs/promises";
|
|
685
|
+
var LINK_REFILL_BACKOFF_MS = 60 * 60 * 1e3;
|
|
686
|
+
async function linkRefillFailedAt(home) {
|
|
687
|
+
const value = await readJson(statePaths(home).linkRefill).catch(() => void 0);
|
|
688
|
+
return typeof value?.failedAt === "number" ? value.failedAt : void 0;
|
|
689
|
+
}
|
|
683
690
|
var REFILL_LOCK_STALE_MS = 5 * 60 * 1e3;
|
|
684
691
|
var defaultOpenWallet = async (settings, config, issuer, maxUnits) => {
|
|
685
|
-
|
|
692
|
+
if (config.wallet === "link") {
|
|
693
|
+
const { openLinkWallet } = await import("./wallet-R3UWBXVC.js");
|
|
694
|
+
return openLinkWallet(config, maxUnits);
|
|
695
|
+
}
|
|
696
|
+
const { openWallet } = await import("./wallet-R3UWBXVC.js");
|
|
686
697
|
return openWallet(settings, config.wallet, issuer, maxUnits);
|
|
687
698
|
};
|
|
688
699
|
async function buyWithinBudget(settings, config, wanted, openWallet = defaultOpenWallet) {
|
|
@@ -708,7 +719,7 @@ async function buyWithinBudget(settings, config, wanted, openWallet = defaultOpe
|
|
|
708
719
|
});
|
|
709
720
|
return tokens.length;
|
|
710
721
|
}
|
|
711
|
-
async function refill(base, openWallet = defaultOpenWallet) {
|
|
722
|
+
async function refill(base, openWallet = defaultOpenWallet, now = Date.now) {
|
|
712
723
|
const config = await readConfig(base.home);
|
|
713
724
|
if (!config || config.paused) return 0;
|
|
714
725
|
const settings = settingsFor(base, config);
|
|
@@ -719,7 +730,19 @@ async function refill(base, openWallet = defaultOpenWallet) {
|
|
|
719
730
|
if (!release) return 0;
|
|
720
731
|
try {
|
|
721
732
|
if (!needsRefill(await new FileTokenStore(settings.home).count())) return 0;
|
|
722
|
-
|
|
733
|
+
if (config.wallet !== "link")
|
|
734
|
+
return await buyWithinBudget(settings, config, PACK_SIZE, openWallet);
|
|
735
|
+
const failedAt = await linkRefillFailedAt(settings.home);
|
|
736
|
+
if (failedAt !== void 0 && now() - failedAt < LINK_REFILL_BACKOFF_MS) return 0;
|
|
737
|
+
const path = statePaths(settings.home).linkRefill;
|
|
738
|
+
try {
|
|
739
|
+
const bought = await buyWithinBudget(settings, config, PACK_SIZE, openWallet);
|
|
740
|
+
await rm4(path, { force: true });
|
|
741
|
+
return bought;
|
|
742
|
+
} catch (error) {
|
|
743
|
+
await writePrivateFile(path, JSON.stringify({ failedAt: now() }));
|
|
744
|
+
throw error;
|
|
745
|
+
}
|
|
723
746
|
} finally {
|
|
724
747
|
await release();
|
|
725
748
|
}
|
|
@@ -772,6 +795,18 @@ function formatStatus(view, color) {
|
|
|
772
795
|
color
|
|
773
796
|
)
|
|
774
797
|
];
|
|
798
|
+
if (view.linkRefillFailedAt !== void 0)
|
|
799
|
+
lines.push(
|
|
800
|
+
row(
|
|
801
|
+
"Refill",
|
|
802
|
+
styleText(
|
|
803
|
+
"last Link refill was not approved; approve the next one in the Link app (tries again after an hour)",
|
|
804
|
+
"yellow",
|
|
805
|
+
color
|
|
806
|
+
),
|
|
807
|
+
color
|
|
808
|
+
)
|
|
809
|
+
);
|
|
775
810
|
if (top.length > 0) lines.push(row("Top sites", top.join(" \xB7 "), color));
|
|
776
811
|
return lines.join("\n");
|
|
777
812
|
}
|
|
@@ -800,7 +835,8 @@ Usage: patron [command]
|
|
|
800
835
|
hook Run by agents after each web read (reads the event JSON)
|
|
801
836
|
|
|
802
837
|
Environment: PATRON_HOME, PATRON_GATEWAY_URL, PATRON_RELAY_URL, PATRON_NETWORK,
|
|
803
|
-
PATRON_PRIVATE_KEY (testnet automation), PATRON_BUDGET_USD (non-interactive setup)
|
|
838
|
+
PATRON_PRIVATE_KEY (testnet automation), PATRON_BUDGET_USD (non-interactive setup),
|
|
839
|
+
PATRON_WALLET=link (pay with Link through link-cli), PATRON_LINK_CLI (its path).`;
|
|
804
840
|
var UsageError = class extends Error {
|
|
805
841
|
};
|
|
806
842
|
function defaults() {
|
|
@@ -957,14 +993,12 @@ async function setup(deps) {
|
|
|
957
993
|
});
|
|
958
994
|
const budget = await askBudget(deps, DEFAULT_MONTHLY_BUDGET_USD);
|
|
959
995
|
const budgetUnits = BigInt(budgetTokens(budget)) * TOKEN_PRICE_UNITS;
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
);
|
|
964
|
-
const { signIn } = await import("./wallet-WNPT6R4M.js");
|
|
965
|
-
const account = await signIn(settings, { budgetUnits, issuer, output: deps.stdout });
|
|
966
|
-
done(deps, `Signed in as ${account.address}`);
|
|
996
|
+
const payWithLink = await askPayWithLink(deps);
|
|
997
|
+
if (payWithLink && budgetTokens(budget) < LINK_TOKEN_STEP)
|
|
998
|
+
throw new UsageError("Paying with Link needs a monthly budget of at least $0.01.");
|
|
999
|
+
const account = payWithLink ? await linkSignIn(deps) : await tempoSignIn(deps, budgetUnits, issuer);
|
|
967
1000
|
const config = {
|
|
1001
|
+
...account.linkPaymentMethodId ? { linkPaymentMethodId: account.linkPaymentMethodId } : {},
|
|
968
1002
|
address: account.address,
|
|
969
1003
|
agents: [],
|
|
970
1004
|
gatewayUrl: settings.gatewayUrl,
|
|
@@ -976,9 +1010,10 @@ async function setup(deps) {
|
|
|
976
1010
|
wallet: account.wallet
|
|
977
1011
|
};
|
|
978
1012
|
await writeConfig(settings.home, config);
|
|
979
|
-
const
|
|
1013
|
+
const starter = config.wallet === "link" ? PACK_SIZE : STARTER_TOKENS;
|
|
1014
|
+
const first = purchasableTokens(config, await readUsage(settings.home), starter);
|
|
980
1015
|
step(deps, `Buying ${first.toLocaleString("en-US")} seals (${tokensUsd(first)})`);
|
|
981
|
-
const bought = await buyWithinBudget(settings, config,
|
|
1016
|
+
const bought = await buyWithinBudget(settings, config, starter, deps.openWallet);
|
|
982
1017
|
done(deps, `${bought.toLocaleString("en-US")} seals ready`);
|
|
983
1018
|
deps.startRefill();
|
|
984
1019
|
await installStableCli(settings.home).catch(() => void 0);
|
|
@@ -1005,6 +1040,64 @@ async function setup(deps) {
|
|
|
1005
1040
|
styleText("\u2713 Patron on. Agents now pay the sites they read.", "green", deps.color)
|
|
1006
1041
|
);
|
|
1007
1042
|
}
|
|
1043
|
+
async function askPayWithLink(deps) {
|
|
1044
|
+
if (deps.settings.privateKey) return false;
|
|
1045
|
+
const preset = process.env.PATRON_WALLET;
|
|
1046
|
+
if (preset) return preset === "link";
|
|
1047
|
+
if (!deps.interactive) return false;
|
|
1048
|
+
const prompter = deps.prompter();
|
|
1049
|
+
try {
|
|
1050
|
+
const choice = await prompter.choose(
|
|
1051
|
+
"Pay with",
|
|
1052
|
+
["Tempo Wallet", "Link (your Link balance or a card, via link-cli)"],
|
|
1053
|
+
0
|
|
1054
|
+
);
|
|
1055
|
+
return choice === 1;
|
|
1056
|
+
} finally {
|
|
1057
|
+
prompter.close();
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
async function tempoSignIn(deps, budgetUnits, issuer) {
|
|
1061
|
+
step(
|
|
1062
|
+
deps,
|
|
1063
|
+
deps.settings.privateKey ? "Using PATRON_PRIVATE_KEY" : "Signing in with Tempo Wallet"
|
|
1064
|
+
);
|
|
1065
|
+
const { signIn } = await import("./wallet-R3UWBXVC.js");
|
|
1066
|
+
const account = await signIn(deps.settings, {
|
|
1067
|
+
budgetUnits,
|
|
1068
|
+
issuer,
|
|
1069
|
+
output: deps.stdout
|
|
1070
|
+
});
|
|
1071
|
+
done(deps, `Signed in as ${account.address}`);
|
|
1072
|
+
return account;
|
|
1073
|
+
}
|
|
1074
|
+
async function linkSignIn(deps) {
|
|
1075
|
+
step(deps, "Checking your Link wallet (link-cli)");
|
|
1076
|
+
const { linkBalance, linkCli, linkPaymentMethods } = await import("./link-OU7W7IT6.js");
|
|
1077
|
+
const cli = deps.linkCli ?? linkCli();
|
|
1078
|
+
const methods = await linkPaymentMethods(cli).catch((error) => {
|
|
1079
|
+
throw new Error(
|
|
1080
|
+
`${error.message}
|
|
1081
|
+
Patron pays with Link through Stripe's Link CLI: npm i -g @stripe/link-cli, then link-cli auth login.`
|
|
1082
|
+
);
|
|
1083
|
+
});
|
|
1084
|
+
const balance = linkBalance(methods);
|
|
1085
|
+
if (balance) {
|
|
1086
|
+
const available = balance.balance_details?.available_balance;
|
|
1087
|
+
done(
|
|
1088
|
+
deps,
|
|
1089
|
+
`Paying with your Link balance${available ? ` ($${(available.amount / 100).toFixed(2)} available)` : ""}`
|
|
1090
|
+
);
|
|
1091
|
+
return { address: "", linkPaymentMethodId: balance.id, wallet: "link" };
|
|
1092
|
+
}
|
|
1093
|
+
const fallback = methods.find((method) => method.is_default) ?? methods[0];
|
|
1094
|
+
if (!fallback)
|
|
1095
|
+
throw new Error(
|
|
1096
|
+
"Your Link wallet has no payment method. Add one at https://app.link.com."
|
|
1097
|
+
);
|
|
1098
|
+
done(deps, `Paying with Link: ${fallback.name}`);
|
|
1099
|
+
return { address: "", linkPaymentMethodId: fallback.id, wallet: "link" };
|
|
1100
|
+
}
|
|
1008
1101
|
async function askBudget(deps, fallback) {
|
|
1009
1102
|
const preset = process.env.PATRON_BUDGET_USD;
|
|
1010
1103
|
if (!deps.interactive) {
|
|
@@ -1027,14 +1120,21 @@ async function askBudget(deps, fallback) {
|
|
|
1027
1120
|
}
|
|
1028
1121
|
async function status(deps, menu) {
|
|
1029
1122
|
const { config, settings } = await loadSetup(deps);
|
|
1030
|
-
const [tokensLeft, usage, standing] = await Promise.all([
|
|
1123
|
+
const [tokensLeft, usage, standing, refillFailedAt] = await Promise.all([
|
|
1031
1124
|
new FileTokenStore(settings.home).count(),
|
|
1032
1125
|
readUsage(settings.home),
|
|
1033
|
-
fetchStanding(settings.gatewayUrl, config.address, config.name, deps.fetch).catch(
|
|
1126
|
+
config.address ? fetchStanding(settings.gatewayUrl, config.address, config.name, deps.fetch).catch(
|
|
1034
1127
|
() => void 0
|
|
1035
|
-
)
|
|
1128
|
+
) : void 0,
|
|
1129
|
+
config.wallet === "link" ? linkRefillFailedAt(settings.home) : void 0
|
|
1036
1130
|
]);
|
|
1037
|
-
say(
|
|
1131
|
+
say(
|
|
1132
|
+
deps,
|
|
1133
|
+
formatStatus(
|
|
1134
|
+
{ config, linkRefillFailedAt: refillFailedAt, standing, tokensLeft, usage },
|
|
1135
|
+
deps.color
|
|
1136
|
+
)
|
|
1137
|
+
);
|
|
1038
1138
|
if (!menu) return;
|
|
1039
1139
|
const prompter = deps.prompter();
|
|
1040
1140
|
let choice;
|
|
@@ -1076,7 +1176,7 @@ async function changeBudget(deps, input) {
|
|
|
1076
1176
|
} else budget = await askBudget(deps, config.monthlyBudgetUsd);
|
|
1077
1177
|
if (config.wallet === "tempo" && budget > config.monthlyBudgetUsd) {
|
|
1078
1178
|
step(deps, "Approve the new spending limit in Tempo Wallet");
|
|
1079
|
-
const { authorizeBudget } = await import("./wallet-
|
|
1179
|
+
const { authorizeBudget } = await import("./wallet-R3UWBXVC.js");
|
|
1080
1180
|
const issuer = await loadIssuer(settings.home, settings.gatewayUrl, {
|
|
1081
1181
|
fetch: deps.fetch
|
|
1082
1182
|
});
|
|
@@ -1185,10 +1285,14 @@ async function setName(deps, name) {
|
|
|
1185
1285
|
if (!name || !/^[\p{L}\p{N}_.-]{1,32}$/u.test(name))
|
|
1186
1286
|
throw new UsageError("Usage: patron name <handle> (1 to 32 letters, digits, _ . -)");
|
|
1187
1287
|
const { config, settings } = await loadSetup(deps);
|
|
1288
|
+
if (config.wallet === "link")
|
|
1289
|
+
throw new UsageError(
|
|
1290
|
+
"Leaderboard names belong to Tempo addresses; Link purchases are not ranked."
|
|
1291
|
+
);
|
|
1188
1292
|
const issuer = await loadIssuer(settings.home, settings.gatewayUrl, {
|
|
1189
1293
|
fetch: deps.fetch
|
|
1190
1294
|
});
|
|
1191
|
-
const open = deps.openWallet ?? (async (s, c, i, max) => (await import("./wallet-
|
|
1295
|
+
const open = deps.openWallet ?? (async (s, c, i, max) => (await import("./wallet-R3UWBXVC.js")).openWallet(s, c.wallet, i, max));
|
|
1192
1296
|
const wallet = await open(settings, config, issuer, 0n);
|
|
1193
1297
|
await setProfile(settings.gatewayUrl, wallet, name, deps.fetch);
|
|
1194
1298
|
await writeConfig(settings.home, { ...config, name });
|
|
@@ -1219,7 +1323,7 @@ async function uninstall(deps) {
|
|
|
1219
1323
|
}
|
|
1220
1324
|
}
|
|
1221
1325
|
if (purge) {
|
|
1222
|
-
await
|
|
1326
|
+
await rm5(deps.settings.home, { force: true, recursive: true });
|
|
1223
1327
|
done(deps, "Local data deleted.");
|
|
1224
1328
|
} else
|
|
1225
1329
|
say(
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
statePaths,
|
|
16
16
|
withLock,
|
|
17
17
|
writePrivateFile
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-UWIBR62N.js";
|
|
19
19
|
import {
|
|
20
20
|
__commonJS,
|
|
21
21
|
__export,
|
|
@@ -34839,6 +34839,7 @@ async function redeem(options) {
|
|
|
34839
34839
|
}
|
|
34840
34840
|
|
|
34841
34841
|
// src/budget.ts
|
|
34842
|
+
var LINK_TOKEN_STEP = 10;
|
|
34842
34843
|
var REFILL_THRESHOLD = 200;
|
|
34843
34844
|
var STARTER_TOKENS = 100;
|
|
34844
34845
|
function budgetTokens(monthlyBudgetUsd) {
|
|
@@ -34848,7 +34849,10 @@ function budgetTokens(monthlyBudgetUsd) {
|
|
|
34848
34849
|
}
|
|
34849
34850
|
function purchasableTokens(config2, usage, wanted) {
|
|
34850
34851
|
const left = budgetTokens(config2.monthlyBudgetUsd) - usage.boughtTokens;
|
|
34851
|
-
|
|
34852
|
+
const step = config2.wallet === "link" ? LINK_TOKEN_STEP : 1;
|
|
34853
|
+
const asked = Math.ceil(Math.floor(wanted) / step) * step;
|
|
34854
|
+
const count = Math.max(0, Math.min(asked, left, MAX_TOKENS_PER_PURCHASE));
|
|
34855
|
+
return count - count % step;
|
|
34852
34856
|
}
|
|
34853
34857
|
function refillSize(config2, usage) {
|
|
34854
34858
|
return purchasableTokens(config2, usage, PACK_SIZE);
|
|
@@ -35210,6 +35214,7 @@ export {
|
|
|
35210
35214
|
profileMessage,
|
|
35211
35215
|
buyTokens,
|
|
35212
35216
|
redeem,
|
|
35217
|
+
LINK_TOKEN_STEP,
|
|
35213
35218
|
STARTER_TOKENS,
|
|
35214
35219
|
budgetTokens,
|
|
35215
35220
|
purchasableTokens,
|
|
@@ -68,6 +68,7 @@ function statePaths(home) {
|
|
|
68
68
|
config: join(home, "config.json"),
|
|
69
69
|
issuer: join(home, "issuer.json"),
|
|
70
70
|
key: join(home, "key"),
|
|
71
|
+
linkRefill: join(home, "link-refill.json"),
|
|
71
72
|
refillLock: join(home, "refill.lock"),
|
|
72
73
|
stateLock: join(home, "state.lock"),
|
|
73
74
|
tokens: join(home, "tokens.enc"),
|
|
@@ -139,7 +140,8 @@ async function readConfig(home) {
|
|
|
139
140
|
paused: config.paused === true,
|
|
140
141
|
relayUrl: String(config.relayUrl ?? ""),
|
|
141
142
|
version: 1,
|
|
142
|
-
wallet: config.wallet === "key"
|
|
143
|
+
wallet: config.wallet === "key" || config.wallet === "link" ? config.wallet : "tempo",
|
|
144
|
+
...typeof config.linkPaymentMethodId === "string" ? { linkPaymentMethodId: config.linkPaymentMethodId } : {},
|
|
143
145
|
...typeof config.name === "string" ? { name: config.name } : {},
|
|
144
146
|
...typeof config.tokensPerPage === "number" ? { tokensPerPage: config.tokensPerPage } : {}
|
|
145
147
|
};
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
needsRefill,
|
|
16
16
|
redeem,
|
|
17
17
|
refillSize
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-QKBTYUXG.js";
|
|
19
19
|
import {
|
|
20
20
|
clampTokensPerPage,
|
|
21
21
|
settingsFor
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
updateUsage,
|
|
27
27
|
withLock,
|
|
28
28
|
writePrivateFile
|
|
29
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-UWIBR62N.js";
|
|
30
30
|
import "./chunk-GTMLTZRN.js";
|
|
31
31
|
|
|
32
32
|
// src/hook.ts
|
|
@@ -25,7 +25,7 @@ async function runHookProcess(argument = process.argv[3]) {
|
|
|
25
25
|
if (isCursorPayload(payload)) process.stdout.write("{}\n");
|
|
26
26
|
if (!turnEnd(payload) && hookDomains(payload).length === 0) return;
|
|
27
27
|
if (await startPayment(raw)) return;
|
|
28
|
-
const { runHook } = await import("./hook-
|
|
28
|
+
const { runHook } = await import("./hook-QEFJTAKR.js");
|
|
29
29
|
await runHook({
|
|
30
30
|
readInput: async () => raw,
|
|
31
31
|
settings: resolveSettings(),
|
|
@@ -33,7 +33,7 @@ async function runHookProcess(argument = process.argv[3]) {
|
|
|
33
33
|
});
|
|
34
34
|
}
|
|
35
35
|
async function runPayProcess() {
|
|
36
|
-
const { PAY_TIMEOUT_MS, runHook } = await import("./hook-
|
|
36
|
+
const { PAY_TIMEOUT_MS, runHook } = await import("./hook-QEFJTAKR.js");
|
|
37
37
|
setTimeout(() => process.exit(0), PAY_TIMEOUT_MS + 1e3).unref();
|
|
38
38
|
const raw = await readStdin(HOOK_TIMEOUT_MS);
|
|
39
39
|
await runHook(
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { createRequire as __patronCreateRequire } from 'node:module'
|
|
2
|
+
const require = __patronCreateRequire(import.meta.url)
|
|
3
|
+
import "./chunk-GTMLTZRN.js";
|
|
4
|
+
|
|
5
|
+
// src/link.ts
|
|
6
|
+
import { execFile } from "node:child_process";
|
|
7
|
+
import { Credential, Method } from "mppx";
|
|
8
|
+
import { Mppx } from "mppx/client";
|
|
9
|
+
import { Methods as StripeMethods } from "mppx/stripe";
|
|
10
|
+
function linkCliPath(env = process.env) {
|
|
11
|
+
return env.PATRON_LINK_CLI || "link-cli";
|
|
12
|
+
}
|
|
13
|
+
var LINK_CLI_TIMEOUT_MS = 3e4;
|
|
14
|
+
function quoteForCmd(arg) {
|
|
15
|
+
return `"${arg.replace(/"/g, '""').replace(/%/g, '"%"')}"`;
|
|
16
|
+
}
|
|
17
|
+
function linkCli(path = linkCliPath(), platform = process.platform) {
|
|
18
|
+
const windows = platform === "win32";
|
|
19
|
+
return (args, timeoutMs = LINK_CLI_TIMEOUT_MS) => new Promise((resolve, reject) => {
|
|
20
|
+
const argv = [...args, "--format", "json"];
|
|
21
|
+
execFile(
|
|
22
|
+
windows ? quoteForCmd(path) : path,
|
|
23
|
+
windows ? argv.map(quoteForCmd) : argv,
|
|
24
|
+
{
|
|
25
|
+
maxBuffer: 1024 * 1024,
|
|
26
|
+
timeout: timeoutMs,
|
|
27
|
+
...windows ? { shell: true, windowsVerbatimArguments: true } : {}
|
|
28
|
+
},
|
|
29
|
+
(error, stdout, stderr) => {
|
|
30
|
+
const parsed = parseLastJson(stdout);
|
|
31
|
+
if (error) {
|
|
32
|
+
const code = error.code;
|
|
33
|
+
if (code === "ENOENT")
|
|
34
|
+
return reject(
|
|
35
|
+
new Error(
|
|
36
|
+
"Link CLI not found. Install it with npm i -g @stripe/link-cli, then run link-cli auth login."
|
|
37
|
+
)
|
|
38
|
+
);
|
|
39
|
+
const message = errorMessage(parsed) ?? (stderr.trim() || error.message);
|
|
40
|
+
return reject(new Error(`Link CLI: ${message}`));
|
|
41
|
+
}
|
|
42
|
+
if (parsed === void 0)
|
|
43
|
+
return reject(new Error("Link CLI returned no JSON result."));
|
|
44
|
+
resolve(parsed);
|
|
45
|
+
}
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
async function linkPaymentMethods(cli) {
|
|
50
|
+
const result = await cli(["payment-methods", "list"]);
|
|
51
|
+
const list = Array.isArray(result) ? result : result?.payment_details ?? [];
|
|
52
|
+
if (!Array.isArray(list)) throw new Error("Link CLI listed no payment methods.");
|
|
53
|
+
return list.filter(
|
|
54
|
+
(item) => typeof item === "object" && item !== null && typeof item.id === "string"
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
function linkBalance(methods) {
|
|
58
|
+
return methods.find(
|
|
59
|
+
(method) => method.balance_details?.available_balance !== void 0 || /balance/i.test(method.type)
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
var WAITING = /* @__PURE__ */ new Set(["created", "pending_approval", "requires_action"]);
|
|
63
|
+
var PURCHASE_TIMEOUT_MS = 4 * 60 * 1e3;
|
|
64
|
+
async function createLinkSpt(options) {
|
|
65
|
+
const sleep = options.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
66
|
+
const usd = (options.amount / 100).toFixed(2);
|
|
67
|
+
const deadline = Date.now() + (options.timeoutMs ?? PURCHASE_TIMEOUT_MS);
|
|
68
|
+
const call = (args) => {
|
|
69
|
+
const left = deadline - Date.now();
|
|
70
|
+
if (left <= 0) throw new Error("Link did not approve the purchase in time.");
|
|
71
|
+
return options.cli(args, Math.min(LINK_CLI_TIMEOUT_MS, left));
|
|
72
|
+
};
|
|
73
|
+
const retrieve = async (id) => asSpendRequest(
|
|
74
|
+
await call(["spend-request", "retrieve", id, "--include", "shared_payment_token"])
|
|
75
|
+
);
|
|
76
|
+
const created = asSpendRequest(
|
|
77
|
+
await call([
|
|
78
|
+
"spend-request",
|
|
79
|
+
"create",
|
|
80
|
+
"--credential-type",
|
|
81
|
+
"shared_payment_token",
|
|
82
|
+
"--network-id",
|
|
83
|
+
options.networkId,
|
|
84
|
+
"--amount",
|
|
85
|
+
String(options.amount),
|
|
86
|
+
"--currency",
|
|
87
|
+
"usd",
|
|
88
|
+
"--context",
|
|
89
|
+
`Patron: buying $${usd} of seals (1,000 per dollar) that pay the websites your AI agents and browser read, $0.001 per page, privately. Started by npx @tempoxyz/patron on this computer.`,
|
|
90
|
+
"--idempotency-key",
|
|
91
|
+
options.idempotencyKey,
|
|
92
|
+
"--request-approval",
|
|
93
|
+
...options.paymentMethodId ? ["--payment-method-id", options.paymentMethodId] : []
|
|
94
|
+
])
|
|
95
|
+
);
|
|
96
|
+
let request = created;
|
|
97
|
+
try {
|
|
98
|
+
if (WAITING.has(request.status)) {
|
|
99
|
+
const action = request.status_details?.requires_action?.next_action;
|
|
100
|
+
const url = action?.action_url ?? request.approval_url;
|
|
101
|
+
options.output.write(
|
|
102
|
+
`
|
|
103
|
+
Approve $${usd} for Patron in the Link app${url ? `:
|
|
104
|
+
${url}` : "."}
|
|
105
|
+
|
|
106
|
+
`
|
|
107
|
+
);
|
|
108
|
+
while (WAITING.has(request.status)) {
|
|
109
|
+
assertResumable(request);
|
|
110
|
+
if (Date.now() > deadline)
|
|
111
|
+
throw new Error(`Link spend request ${request.id} was not approved in time.`);
|
|
112
|
+
await sleep(options.pollMs ?? 2e3);
|
|
113
|
+
request = await retrieve(request.id);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (request.status !== "approved")
|
|
117
|
+
throw new Error(`Link spend request ${request.id} ended ${request.status}.`);
|
|
118
|
+
} catch (error) {
|
|
119
|
+
if (WAITING.has(request.status))
|
|
120
|
+
await options.cli(["spend-request", "cancel", request.id], 5e3).catch(() => void 0);
|
|
121
|
+
throw error;
|
|
122
|
+
}
|
|
123
|
+
for (let attempt = 0; attempt < 8; attempt++) {
|
|
124
|
+
const spt = request.shared_payment_token?.id;
|
|
125
|
+
if (spt) return spt;
|
|
126
|
+
await sleep(attempt === 0 ? 0 : 1e3);
|
|
127
|
+
request = await retrieve(request.id);
|
|
128
|
+
}
|
|
129
|
+
throw new Error(
|
|
130
|
+
`Link approved spend request ${request.id} but issued no payment token.`
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
function assertResumable(request) {
|
|
134
|
+
if (request.status !== "requires_action") return;
|
|
135
|
+
const action = request.status_details?.requires_action?.next_action;
|
|
136
|
+
if (action?.resolution === "auto_resume") return;
|
|
137
|
+
throw new Error(
|
|
138
|
+
`Link needs you to act before it can pay${action?.display_message ? `: ${action.display_message}` : "."}${action?.action_url ? ` ${action.action_url}` : ""} Patron will ask again on the next purchase.`
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
async function paymentMethodFor(cli, preferred, cents) {
|
|
142
|
+
if (!preferred) return void 0;
|
|
143
|
+
const methods = await linkPaymentMethods(cli).catch(() => void 0);
|
|
144
|
+
if (!methods) return preferred;
|
|
145
|
+
const method = methods.find((item) => item.id === preferred);
|
|
146
|
+
if (!method) return void 0;
|
|
147
|
+
const available = method.balance_details?.available_balance;
|
|
148
|
+
if (available && (available.currency.toLowerCase() !== "usd" || available.amount < cents))
|
|
149
|
+
return void 0;
|
|
150
|
+
return preferred;
|
|
151
|
+
}
|
|
152
|
+
function asSpendRequest(value) {
|
|
153
|
+
const request = Array.isArray(value) ? value.at(-1) : value;
|
|
154
|
+
if (!request || typeof request.id !== "string" || typeof request.status !== "string")
|
|
155
|
+
throw new Error("Link CLI returned an unexpected spend request.");
|
|
156
|
+
return request;
|
|
157
|
+
}
|
|
158
|
+
function linkFetch(options) {
|
|
159
|
+
const charge = Method.toClient(StripeMethods.charge, {
|
|
160
|
+
async createCredential({ challenge }) {
|
|
161
|
+
const { amount, currency, methodDetails } = challenge.request;
|
|
162
|
+
const cents = Number(amount);
|
|
163
|
+
if (currency.toLowerCase() !== "usd")
|
|
164
|
+
throw new Error(`Patron asked for ${currency}; Link pays only USD.`);
|
|
165
|
+
if (!Number.isSafeInteger(cents) || cents < 1 || cents > options.maxCents)
|
|
166
|
+
throw new Error(
|
|
167
|
+
`Patron asked for ${amount} cents, more than this purchase allows.`
|
|
168
|
+
);
|
|
169
|
+
if (!methodDetails.networkId) throw new Error("Patron sent no Stripe network ID.");
|
|
170
|
+
const paymentMethodId = await paymentMethodFor(
|
|
171
|
+
options.cli,
|
|
172
|
+
options.paymentMethodId,
|
|
173
|
+
cents
|
|
174
|
+
);
|
|
175
|
+
const spt = await createLinkSpt({
|
|
176
|
+
amount: cents,
|
|
177
|
+
cli: options.cli,
|
|
178
|
+
idempotencyKey: `patron-${challenge.id}`,
|
|
179
|
+
networkId: methodDetails.networkId,
|
|
180
|
+
output: options.output,
|
|
181
|
+
...options.sleep ? { sleep: options.sleep } : {},
|
|
182
|
+
...paymentMethodId ? { paymentMethodId } : {}
|
|
183
|
+
});
|
|
184
|
+
return Credential.serialize({ challenge, payload: { spt } });
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
return Mppx.create({
|
|
188
|
+
...options.fetch ? { fetch: options.fetch } : {},
|
|
189
|
+
methods: [charge],
|
|
190
|
+
polyfill: false
|
|
191
|
+
}).fetch;
|
|
192
|
+
}
|
|
193
|
+
function parseLastJson(output) {
|
|
194
|
+
const text = output.trim();
|
|
195
|
+
if (!text) return void 0;
|
|
196
|
+
try {
|
|
197
|
+
return JSON.parse(text);
|
|
198
|
+
} catch {
|
|
199
|
+
const lines = text.split("\n").filter((line) => line.trim());
|
|
200
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
201
|
+
try {
|
|
202
|
+
return JSON.parse(lines.slice(i).join("\n"));
|
|
203
|
+
} catch {
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return void 0;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
function errorMessage(value) {
|
|
210
|
+
if (!value || typeof value !== "object") return void 0;
|
|
211
|
+
const record = value;
|
|
212
|
+
if (typeof record.message === "string") return record.message;
|
|
213
|
+
if (record.error && typeof record.error === "object") return errorMessage(record.error);
|
|
214
|
+
if (typeof record.error === "string") return record.error;
|
|
215
|
+
return void 0;
|
|
216
|
+
}
|
|
217
|
+
export {
|
|
218
|
+
createLinkSpt,
|
|
219
|
+
linkBalance,
|
|
220
|
+
linkCli,
|
|
221
|
+
linkCliPath,
|
|
222
|
+
linkFetch,
|
|
223
|
+
linkPaymentMethods,
|
|
224
|
+
paymentMethodFor,
|
|
225
|
+
quoteForCmd
|
|
226
|
+
};
|
|
@@ -2,7 +2,7 @@ import { createRequire as __patronCreateRequire } from 'node:module'
|
|
|
2
2
|
const require = __patronCreateRequire(import.meta.url)
|
|
3
3
|
import {
|
|
4
4
|
statePaths
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-UWIBR62N.js";
|
|
6
6
|
import "./chunk-GTMLTZRN.js";
|
|
7
7
|
|
|
8
8
|
// src/wallet.ts
|
|
@@ -118,6 +118,22 @@ async function openWallet(settings, wallet, issuer, maxUnits, output = process.s
|
|
|
118
118
|
signMessage: (message) => provider.request({ method: "personal_sign", params: [toHex(message), address] })
|
|
119
119
|
};
|
|
120
120
|
}
|
|
121
|
+
var UNITS_PER_CENT = 10000n;
|
|
122
|
+
async function openLinkWallet(config, maxUnits, output = process.stderr) {
|
|
123
|
+
const { linkCli, linkFetch } = await import("./link-OU7W7IT6.js");
|
|
124
|
+
return {
|
|
125
|
+
address: "0x0000000000000000000000000000000000000000",
|
|
126
|
+
fetch: linkFetch({
|
|
127
|
+
cli: linkCli(),
|
|
128
|
+
maxCents: Number(maxUnits / UNITS_PER_CENT),
|
|
129
|
+
output,
|
|
130
|
+
...config.linkPaymentMethodId ? { paymentMethodId: config.linkPaymentMethodId } : {}
|
|
131
|
+
}),
|
|
132
|
+
signMessage: async () => {
|
|
133
|
+
throw new Error("A Link wallet has no Tempo address to sign with.");
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
}
|
|
121
137
|
function budgetLimit(budgetUnits, currency) {
|
|
122
138
|
return {
|
|
123
139
|
limit: toHex(budgetUnits),
|
|
@@ -151,6 +167,7 @@ ${url}
|
|
|
151
167
|
}
|
|
152
168
|
export {
|
|
153
169
|
authorizeBudget,
|
|
170
|
+
openLinkWallet,
|
|
154
171
|
openWallet,
|
|
155
172
|
signIn
|
|
156
173
|
};
|