@tempoxyz/patron 0.0.20 → 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/README.md +3 -1
- package/dist/bin.js +3 -3
- package/dist/chunks/{-RNUFDXSV.js → -PMO5HHO2.js} +218 -42
- package/dist/chunks/{chunk-CV2WYKM7.js → chunk-HEQBEBKL.js} +14 -1
- 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-Y2W56T2X.js → hook-process-D5ENJPRB.js} +3 -3
- 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/README.md
CHANGED
|
@@ -50,7 +50,9 @@ and the site's domain through an Oblivious HTTP relay (RFC 9458): the relay sees
|
|
|
50
50
|
not the content; the gateway sees the content but not your IP.
|
|
51
51
|
|
|
52
52
|
Local state lives in `~/.patron`: settings, seals encrypted with AES-256-GCM under a key
|
|
53
|
-
file readable only by you,
|
|
53
|
+
file readable only by you, this month's per-site counts, which never leave the machine, and
|
|
54
|
+
a copy of the CLI in `~/.patron/cli` that agent hooks start with node, so a read never waits
|
|
55
|
+
on npx. Running `npx @tempoxyz/patron` updates the copy.
|
|
54
56
|
|
|
55
57
|
## Budget
|
|
56
58
|
|
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,10 +29,10 @@ 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
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-HEQBEBKL.js";
|
|
35
36
|
import {
|
|
36
37
|
DEFAULT_MONTHLY_BUDGET_USD,
|
|
37
38
|
MAX_TOKENS_PER_PAGE,
|
|
@@ -57,18 +58,19 @@ 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 { homedir } from "node:os";
|
|
66
|
+
import { rm as rm5 } from "node:fs/promises";
|
|
67
|
+
import { homedir as homedir2 } from "node:os";
|
|
67
68
|
|
|
68
69
|
// src/agents.ts
|
|
69
70
|
import { constants } from "node:fs";
|
|
70
|
-
import { access, realpath } from "node:fs/promises";
|
|
71
|
-
import {
|
|
71
|
+
import { access as access2, realpath as realpath2 } from "node:fs/promises";
|
|
72
|
+
import { homedir } from "node:os";
|
|
73
|
+
import { delimiter, join as join5 } from "node:path";
|
|
72
74
|
|
|
73
75
|
// src/amp.ts
|
|
74
76
|
import { mkdir, readFile, rm } from "node:fs/promises";
|
|
@@ -322,6 +324,66 @@ function parse(text) {
|
|
|
322
324
|
}
|
|
323
325
|
}
|
|
324
326
|
|
|
327
|
+
// src/stable-install.ts
|
|
328
|
+
import {
|
|
329
|
+
access,
|
|
330
|
+
cp,
|
|
331
|
+
mkdir as mkdir2,
|
|
332
|
+
readdir,
|
|
333
|
+
readFile as readFile3,
|
|
334
|
+
readlink,
|
|
335
|
+
realpath,
|
|
336
|
+
rename,
|
|
337
|
+
rm as rm3,
|
|
338
|
+
symlink
|
|
339
|
+
} from "node:fs/promises";
|
|
340
|
+
import { basename, dirname as dirname3, join as join4 } from "node:path";
|
|
341
|
+
function stableBin(home) {
|
|
342
|
+
return join4(home, "cli", "current", "dist", "bin.js");
|
|
343
|
+
}
|
|
344
|
+
async function installStableCli(home, script = process.argv[1]) {
|
|
345
|
+
const resolved = script ? await realpath(script).catch(() => void 0) : void 0;
|
|
346
|
+
if (!resolved || !/[/\\]_npx[/\\]/.test(resolved) || basename(resolved) !== "bin.js")
|
|
347
|
+
return void 0;
|
|
348
|
+
const packageDir = dirname3(dirname3(resolved));
|
|
349
|
+
const { version } = JSON.parse(await readFile3(join4(packageDir, "package.json"), "utf8"));
|
|
350
|
+
if (typeof version !== "string" || !/^[\w.+-]+$/.test(version)) return void 0;
|
|
351
|
+
const root = join4(home, "cli");
|
|
352
|
+
const target = join4(root, version);
|
|
353
|
+
await mkdir2(root, { recursive: true, mode: 448 });
|
|
354
|
+
if (!await isFile(join4(target, "dist", "bin.js"))) {
|
|
355
|
+
const staging = `${target}.${process.pid}.tmp`;
|
|
356
|
+
await rm3(staging, { force: true, recursive: true });
|
|
357
|
+
await cp(join4(packageDir, "dist"), join4(staging, "dist"), { recursive: true });
|
|
358
|
+
await cp(join4(packageDir, "package.json"), join4(staging, "package.json"));
|
|
359
|
+
await rm3(target, { force: true, recursive: true });
|
|
360
|
+
await rename(staging, target);
|
|
361
|
+
}
|
|
362
|
+
const current = join4(root, "current");
|
|
363
|
+
const linked = await readlink(current).catch(() => void 0);
|
|
364
|
+
const previous = linked ? basename(linked) : void 0;
|
|
365
|
+
if (previous !== version) {
|
|
366
|
+
const link = `${current}.${process.pid}.tmp`;
|
|
367
|
+
await rm3(link, { force: true });
|
|
368
|
+
await symlink(process.platform === "win32" ? target : version, link, "junction");
|
|
369
|
+
await rename(link, current).catch(async () => {
|
|
370
|
+
await rm3(current, { force: true, recursive: true });
|
|
371
|
+
await rename(link, current);
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
for (const entry of await readdir(root)) {
|
|
375
|
+
if (entry === "current" || entry === version || entry === previous) continue;
|
|
376
|
+
await rm3(join4(root, entry), { force: true, recursive: true });
|
|
377
|
+
}
|
|
378
|
+
return stableBin(home);
|
|
379
|
+
}
|
|
380
|
+
async function isFile(path) {
|
|
381
|
+
return access(path).then(
|
|
382
|
+
() => true,
|
|
383
|
+
() => false
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
|
|
325
387
|
// src/agents.ts
|
|
326
388
|
var AGENT_LABELS = {
|
|
327
389
|
amp: "Amp",
|
|
@@ -332,7 +394,7 @@ var AGENT_LABELS = {
|
|
|
332
394
|
var ADAPTERS = {
|
|
333
395
|
amp: {
|
|
334
396
|
files: ({ env, home }) => [ampPluginPath(env, home)],
|
|
335
|
-
detect: async ({ env, home }) => await exists(
|
|
397
|
+
detect: async ({ env, home }) => await exists(join5(env.XDG_CONFIG_HOME || join5(home, ".config"), "amp")) || await onPath(["amp"], env.PATH ?? ""),
|
|
336
398
|
install: async (command, { env, home }) => ({
|
|
337
399
|
state: await installAmpPlugin(ampPluginPath(env, home), command)
|
|
338
400
|
}),
|
|
@@ -357,7 +419,7 @@ var ADAPTERS = {
|
|
|
357
419
|
},
|
|
358
420
|
cursor: {
|
|
359
421
|
files: ({ home }) => [cursorHooksPath(home)],
|
|
360
|
-
detect: async ({ env, home }) => await exists(
|
|
422
|
+
detect: async ({ env, home }) => await exists(join5(home, ".cursor")) || await onPath(["cursor-agent", "cursor"], env.PATH ?? ""),
|
|
361
423
|
install: async (command, { home }) => ({
|
|
362
424
|
state: await installCursorHook(cursorHooksPath(home), command)
|
|
363
425
|
}),
|
|
@@ -365,10 +427,10 @@ var ADAPTERS = {
|
|
|
365
427
|
}
|
|
366
428
|
};
|
|
367
429
|
function claudeDirectory({ env, home }) {
|
|
368
|
-
return env.CLAUDE_CONFIG_DIR ||
|
|
430
|
+
return env.CLAUDE_CONFIG_DIR || join5(home, ".claude");
|
|
369
431
|
}
|
|
370
432
|
function claudeSettingsPath(environment) {
|
|
371
|
-
return
|
|
433
|
+
return join5(claudeDirectory(environment), "settings.json");
|
|
372
434
|
}
|
|
373
435
|
async function detectAgents(environment) {
|
|
374
436
|
const found = await Promise.all(
|
|
@@ -421,7 +483,7 @@ async function removeAgents(environment) {
|
|
|
421
483
|
);
|
|
422
484
|
}
|
|
423
485
|
function originalsStore({ env, home }) {
|
|
424
|
-
return originalsPath(env.PATRON_HOME ||
|
|
486
|
+
return originalsPath(env.PATRON_HOME || join5(home, ".patron"));
|
|
425
487
|
}
|
|
426
488
|
function failure(agent, error) {
|
|
427
489
|
return {
|
|
@@ -431,18 +493,20 @@ function failure(agent, error) {
|
|
|
431
493
|
state: "failed"
|
|
432
494
|
};
|
|
433
495
|
}
|
|
434
|
-
async function hookCommand(env = process.env, script = process.argv[1], node = process.execPath) {
|
|
496
|
+
async function hookCommand(env = process.env, script = process.argv[1], node = process.execPath, patronHome = env.PATRON_HOME || join5(homedir(), ".patron")) {
|
|
435
497
|
if (env.PATRON_HOOK_COMMAND) return env.PATRON_HOOK_COMMAND;
|
|
436
|
-
const resolved = script ? await
|
|
498
|
+
const resolved = script ? await realpath2(script).catch(() => void 0) : void 0;
|
|
437
499
|
if (resolved && /[/\\]bin\.js$/.test(resolved) && !/[/\\]_npx[/\\]/.test(resolved))
|
|
438
500
|
return `${quote(node)} ${quote(resolved)} hook`;
|
|
501
|
+
if (await exists(stableBin(patronHome)))
|
|
502
|
+
return `${quote(node)} ${quote(stableBin(patronHome))} hook`;
|
|
439
503
|
return "npx -y @tempoxyz/patron hook";
|
|
440
504
|
}
|
|
441
505
|
function quote(value) {
|
|
442
506
|
return /^[\w./:@-]+$/.test(value) ? value : `'${value.replaceAll("'", "'\\''")}'`;
|
|
443
507
|
}
|
|
444
508
|
async function exists(path) {
|
|
445
|
-
return
|
|
509
|
+
return access2(path).then(
|
|
446
510
|
() => true,
|
|
447
511
|
() => false
|
|
448
512
|
);
|
|
@@ -451,7 +515,7 @@ async function onPath(commands, path) {
|
|
|
451
515
|
for (const directory of path.split(delimiter)) {
|
|
452
516
|
if (!directory) continue;
|
|
453
517
|
for (const command of commands) {
|
|
454
|
-
const found = await
|
|
518
|
+
const found = await access2(join5(directory, command), constants.X_OK).then(
|
|
455
519
|
() => true,
|
|
456
520
|
() => false
|
|
457
521
|
);
|
|
@@ -617,9 +681,19 @@ ${styleText(title, "bold", color)}
|
|
|
617
681
|
}
|
|
618
682
|
|
|
619
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
|
+
}
|
|
620
690
|
var REFILL_LOCK_STALE_MS = 5 * 60 * 1e3;
|
|
621
691
|
var defaultOpenWallet = async (settings, config, issuer, maxUnits) => {
|
|
622
|
-
|
|
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");
|
|
623
697
|
return openWallet(settings, config.wallet, issuer, maxUnits);
|
|
624
698
|
};
|
|
625
699
|
async function buyWithinBudget(settings, config, wanted, openWallet = defaultOpenWallet) {
|
|
@@ -645,7 +719,7 @@ async function buyWithinBudget(settings, config, wanted, openWallet = defaultOpe
|
|
|
645
719
|
});
|
|
646
720
|
return tokens.length;
|
|
647
721
|
}
|
|
648
|
-
async function refill(base, openWallet = defaultOpenWallet) {
|
|
722
|
+
async function refill(base, openWallet = defaultOpenWallet, now = Date.now) {
|
|
649
723
|
const config = await readConfig(base.home);
|
|
650
724
|
if (!config || config.paused) return 0;
|
|
651
725
|
const settings = settingsFor(base, config);
|
|
@@ -656,7 +730,19 @@ async function refill(base, openWallet = defaultOpenWallet) {
|
|
|
656
730
|
if (!release) return 0;
|
|
657
731
|
try {
|
|
658
732
|
if (!needsRefill(await new FileTokenStore(settings.home).count())) return 0;
|
|
659
|
-
|
|
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
|
+
}
|
|
660
746
|
} finally {
|
|
661
747
|
await release();
|
|
662
748
|
}
|
|
@@ -709,6 +795,18 @@ function formatStatus(view, color) {
|
|
|
709
795
|
color
|
|
710
796
|
)
|
|
711
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
|
+
);
|
|
712
810
|
if (top.length > 0) lines.push(row("Top sites", top.join(" \xB7 "), color));
|
|
713
811
|
return lines.join("\n");
|
|
714
812
|
}
|
|
@@ -737,14 +835,15 @@ Usage: patron [command]
|
|
|
737
835
|
hook Run by agents after each web read (reads the event JSON)
|
|
738
836
|
|
|
739
837
|
Environment: PATRON_HOME, PATRON_GATEWAY_URL, PATRON_RELAY_URL, PATRON_NETWORK,
|
|
740
|
-
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).`;
|
|
741
840
|
var UsageError = class extends Error {
|
|
742
841
|
};
|
|
743
842
|
function defaults() {
|
|
744
843
|
const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
745
844
|
const color = supportsColor(process.stdout);
|
|
746
845
|
return {
|
|
747
|
-
agents: { env: process.env, home:
|
|
846
|
+
agents: { env: process.env, home: homedir2() },
|
|
748
847
|
color,
|
|
749
848
|
fetch: globalThis.fetch,
|
|
750
849
|
interactive,
|
|
@@ -848,6 +947,18 @@ async function main(deps) {
|
|
|
848
947
|
await status(deps, deps.interactive);
|
|
849
948
|
}
|
|
850
949
|
async function syncAgents(deps, config) {
|
|
950
|
+
await installStableCli(deps.settings.home).catch(() => void 0);
|
|
951
|
+
const command = await hookCommand(deps.agents.env);
|
|
952
|
+
const moved = (await installAgents(config.agents, command, deps.agents)).filter((result) => result.state === "installed").map((result) => result.agent);
|
|
953
|
+
if (moved.length > 0) {
|
|
954
|
+
say(
|
|
955
|
+
deps,
|
|
956
|
+
`Patron now starts with node for ${moved.map((a) => AGENT_LABELS[a]).join(", ")}.`
|
|
957
|
+
);
|
|
958
|
+
if (moved.includes("codex"))
|
|
959
|
+
say(deps, " Codex: trust the hook again in /hooks when it asks.");
|
|
960
|
+
say(deps);
|
|
961
|
+
}
|
|
851
962
|
const upgraded = await upgradeAgents(config.agents, deps.agents);
|
|
852
963
|
if (upgraded.length > 0) {
|
|
853
964
|
const names = upgraded.map((agent) => AGENT_LABELS[agent]).join(", ");
|
|
@@ -860,11 +971,7 @@ async function syncAgents(deps, config) {
|
|
|
860
971
|
(agent) => !config.agents.includes(agent)
|
|
861
972
|
);
|
|
862
973
|
if (added.length === 0) return;
|
|
863
|
-
const results = await installAgents(
|
|
864
|
-
added,
|
|
865
|
-
await hookCommand(deps.agents.env),
|
|
866
|
-
deps.agents
|
|
867
|
-
);
|
|
974
|
+
const results = await installAgents(added, command, deps.agents);
|
|
868
975
|
for (const line of formatAgentResults(results, deps.color)) say(deps, line);
|
|
869
976
|
say(deps);
|
|
870
977
|
const installed = results.filter((result) => result.state !== "failed").map((result) => result.agent);
|
|
@@ -886,14 +993,12 @@ async function setup(deps) {
|
|
|
886
993
|
});
|
|
887
994
|
const budget = await askBudget(deps, DEFAULT_MONTHLY_BUDGET_USD);
|
|
888
995
|
const budgetUnits = BigInt(budgetTokens(budget)) * TOKEN_PRICE_UNITS;
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
);
|
|
893
|
-
const { signIn } = await import("./wallet-WNPT6R4M.js");
|
|
894
|
-
const account = await signIn(settings, { budgetUnits, issuer, output: deps.stdout });
|
|
895
|
-
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);
|
|
896
1000
|
const config = {
|
|
1001
|
+
...account.linkPaymentMethodId ? { linkPaymentMethodId: account.linkPaymentMethodId } : {},
|
|
897
1002
|
address: account.address,
|
|
898
1003
|
agents: [],
|
|
899
1004
|
gatewayUrl: settings.gatewayUrl,
|
|
@@ -905,11 +1010,13 @@ async function setup(deps) {
|
|
|
905
1010
|
wallet: account.wallet
|
|
906
1011
|
};
|
|
907
1012
|
await writeConfig(settings.home, config);
|
|
908
|
-
const
|
|
1013
|
+
const starter = config.wallet === "link" ? PACK_SIZE : STARTER_TOKENS;
|
|
1014
|
+
const first = purchasableTokens(config, await readUsage(settings.home), starter);
|
|
909
1015
|
step(deps, `Buying ${first.toLocaleString("en-US")} seals (${tokensUsd(first)})`);
|
|
910
|
-
const bought = await buyWithinBudget(settings, config,
|
|
1016
|
+
const bought = await buyWithinBudget(settings, config, starter, deps.openWallet);
|
|
911
1017
|
done(deps, `${bought.toLocaleString("en-US")} seals ready`);
|
|
912
1018
|
deps.startRefill();
|
|
1019
|
+
await installStableCli(settings.home).catch(() => void 0);
|
|
913
1020
|
const agents = await detectAgents(deps.agents);
|
|
914
1021
|
if (agents.length === 0) {
|
|
915
1022
|
say(deps);
|
|
@@ -933,6 +1040,64 @@ async function setup(deps) {
|
|
|
933
1040
|
styleText("\u2713 Patron on. Agents now pay the sites they read.", "green", deps.color)
|
|
934
1041
|
);
|
|
935
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
|
+
}
|
|
936
1101
|
async function askBudget(deps, fallback) {
|
|
937
1102
|
const preset = process.env.PATRON_BUDGET_USD;
|
|
938
1103
|
if (!deps.interactive) {
|
|
@@ -955,14 +1120,21 @@ async function askBudget(deps, fallback) {
|
|
|
955
1120
|
}
|
|
956
1121
|
async function status(deps, menu) {
|
|
957
1122
|
const { config, settings } = await loadSetup(deps);
|
|
958
|
-
const [tokensLeft, usage, standing] = await Promise.all([
|
|
1123
|
+
const [tokensLeft, usage, standing, refillFailedAt] = await Promise.all([
|
|
959
1124
|
new FileTokenStore(settings.home).count(),
|
|
960
1125
|
readUsage(settings.home),
|
|
961
|
-
fetchStanding(settings.gatewayUrl, config.address, config.name, deps.fetch).catch(
|
|
1126
|
+
config.address ? fetchStanding(settings.gatewayUrl, config.address, config.name, deps.fetch).catch(
|
|
962
1127
|
() => void 0
|
|
963
|
-
)
|
|
1128
|
+
) : void 0,
|
|
1129
|
+
config.wallet === "link" ? linkRefillFailedAt(settings.home) : void 0
|
|
964
1130
|
]);
|
|
965
|
-
say(
|
|
1131
|
+
say(
|
|
1132
|
+
deps,
|
|
1133
|
+
formatStatus(
|
|
1134
|
+
{ config, linkRefillFailedAt: refillFailedAt, standing, tokensLeft, usage },
|
|
1135
|
+
deps.color
|
|
1136
|
+
)
|
|
1137
|
+
);
|
|
966
1138
|
if (!menu) return;
|
|
967
1139
|
const prompter = deps.prompter();
|
|
968
1140
|
let choice;
|
|
@@ -1004,7 +1176,7 @@ async function changeBudget(deps, input) {
|
|
|
1004
1176
|
} else budget = await askBudget(deps, config.monthlyBudgetUsd);
|
|
1005
1177
|
if (config.wallet === "tempo" && budget > config.monthlyBudgetUsd) {
|
|
1006
1178
|
step(deps, "Approve the new spending limit in Tempo Wallet");
|
|
1007
|
-
const { authorizeBudget } = await import("./wallet-
|
|
1179
|
+
const { authorizeBudget } = await import("./wallet-R3UWBXVC.js");
|
|
1008
1180
|
const issuer = await loadIssuer(settings.home, settings.gatewayUrl, {
|
|
1009
1181
|
fetch: deps.fetch
|
|
1010
1182
|
});
|
|
@@ -1113,10 +1285,14 @@ async function setName(deps, name) {
|
|
|
1113
1285
|
if (!name || !/^[\p{L}\p{N}_.-]{1,32}$/u.test(name))
|
|
1114
1286
|
throw new UsageError("Usage: patron name <handle> (1 to 32 letters, digits, _ . -)");
|
|
1115
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
|
+
);
|
|
1116
1292
|
const issuer = await loadIssuer(settings.home, settings.gatewayUrl, {
|
|
1117
1293
|
fetch: deps.fetch
|
|
1118
1294
|
});
|
|
1119
|
-
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));
|
|
1120
1296
|
const wallet = await open(settings, config, issuer, 0n);
|
|
1121
1297
|
await setProfile(settings.gatewayUrl, wallet, name, deps.fetch);
|
|
1122
1298
|
await writeConfig(settings.home, { ...config, name });
|
|
@@ -1147,7 +1323,7 @@ async function uninstall(deps) {
|
|
|
1147
1323
|
}
|
|
1148
1324
|
}
|
|
1149
1325
|
if (purge) {
|
|
1150
|
-
await
|
|
1326
|
+
await rm5(deps.settings.home, { force: true, recursive: true });
|
|
1151
1327
|
done(deps, "Local data deleted.");
|
|
1152
1328
|
} else
|
|
1153
1329
|
say(
|
|
@@ -3,10 +3,12 @@ const require = __patronCreateRequire(import.meta.url)
|
|
|
3
3
|
|
|
4
4
|
// src/refill-process.ts
|
|
5
5
|
import { spawn } from "node:child_process";
|
|
6
|
+
import { createRequire } from "node:module";
|
|
6
7
|
function startRefill(script = process.argv[1]) {
|
|
7
8
|
try {
|
|
8
9
|
if (!script) return;
|
|
9
|
-
const
|
|
10
|
+
const [command, args] = refillCommand(script);
|
|
11
|
+
const child = spawn(command, args, {
|
|
10
12
|
detached: true,
|
|
11
13
|
env: process.env,
|
|
12
14
|
stdio: "ignore"
|
|
@@ -16,6 +18,17 @@ function startRefill(script = process.argv[1]) {
|
|
|
16
18
|
} catch {
|
|
17
19
|
}
|
|
18
20
|
}
|
|
21
|
+
function refillCommand(script) {
|
|
22
|
+
try {
|
|
23
|
+
createRequire(script).resolve("mppx");
|
|
24
|
+
return [process.execPath, [...process.execArgv, script, "refill"]];
|
|
25
|
+
} catch {
|
|
26
|
+
return [
|
|
27
|
+
process.platform === "win32" ? "npx.cmd" : "npx",
|
|
28
|
+
["-y", "@tempoxyz/patron", "refill"]
|
|
29
|
+
];
|
|
30
|
+
}
|
|
31
|
+
}
|
|
19
32
|
function startPayment(payload, script = process.argv[1]) {
|
|
20
33
|
return new Promise((resolve) => {
|
|
21
34
|
try {
|
|
@@ -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
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
import {
|
|
11
11
|
startPayment,
|
|
12
12
|
startRefill
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-HEQBEBKL.js";
|
|
14
14
|
import {
|
|
15
15
|
resolveSettings
|
|
16
16
|
} from "./chunk-CF4LJZLC.js";
|
|
@@ -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
|
};
|