@tempoxyz/patron 0.0.27 → 0.0.29
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/{-PBBSDRMM.js → -OLVAZG3H.js} +236 -101
- package/dist/chunks/{chunk-TV5VLFTD.js → chunk-3CYJJJR3.js} +3 -2
- package/dist/chunks/{chunk-WZRGCKEK.js → chunk-EISHX7Q3.js} +54 -32
- package/dist/chunks/{chunk-HVTTV2A5.js → chunk-HQ2ZLUTT.js} +1 -1
- package/dist/chunks/chunk-OQXACACU.js +37 -0
- package/dist/chunks/chunk-SAEZ54SS.js +15496 -0
- package/dist/chunks/{chunk-F5CY5O7O.js → chunk-ZOQJ4CZE.js} +19132 -34415
- package/dist/chunks/{hook-3CYJEB2M.js → hook-HYV4BSCN.js} +16 -13
- package/dist/chunks/{hook-process-QQPBFRY6.js → hook-process-6WSLYXC7.js} +7 -7
- package/dist/chunks/{wallet-OJP2JREM.js → wallet-LOLL66ID.js} +5 -3
- package/package.json +1 -1
- package/dist/chunks/chunk-MZJEM2SE.js +0 -163
- package/dist/chunks/chunk-O64T22EK.js +0 -54
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-6WSLYXC7.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-6WSLYXC7.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/-OLVAZG3H.js");
|
|
16
16
|
process.exitCode = await run(process.argv.slice(2));
|
|
17
17
|
}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { createRequire as __patronCreateRequire } from 'node:module'
|
|
2
2
|
const require = __patronCreateRequire(import.meta.url)
|
|
3
3
|
import {
|
|
4
|
+
CLAUDE_HOOK_STATUS,
|
|
4
5
|
FileTokenStore,
|
|
5
6
|
LINK_TOKEN_STEP,
|
|
6
|
-
LeaderboardResponse,
|
|
7
|
-
PatronInfo,
|
|
8
7
|
STARTER_TOKENS,
|
|
9
8
|
budgetTokens,
|
|
10
9
|
buyTokens,
|
|
11
10
|
codexHome,
|
|
11
|
+
codexNotifyCommand,
|
|
12
12
|
codexPaths,
|
|
13
13
|
hasPatronHandler,
|
|
14
14
|
installCodex,
|
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
loadIssuer,
|
|
18
18
|
needsRefill,
|
|
19
19
|
parseUsd,
|
|
20
|
-
|
|
20
|
+
patronCommands,
|
|
21
21
|
purchasableTokens,
|
|
22
22
|
readJsonObject,
|
|
23
23
|
redeem,
|
|
@@ -29,26 +29,20 @@ import {
|
|
|
29
29
|
withPatronStatus,
|
|
30
30
|
withoutPatronHandlers,
|
|
31
31
|
writeJsonObject
|
|
32
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-SAEZ54SS.js";
|
|
33
33
|
import {
|
|
34
34
|
startRefill
|
|
35
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-3CYJJJR3.js";
|
|
36
36
|
import {
|
|
37
|
-
DEFAULT_MONTHLY_BUDGET_USD,
|
|
38
|
-
MAX_TOKENS_PER_PAGE,
|
|
39
|
-
MIN_TOKENS_PER_PAGE,
|
|
40
|
-
PACK_SIZE,
|
|
41
|
-
TOKEN_PRICE_UNITS,
|
|
42
|
-
clampTokensPerPage,
|
|
43
|
-
formatUsd,
|
|
44
|
-
normalizePayee,
|
|
45
37
|
resolveSettings,
|
|
46
|
-
settingsFor
|
|
47
|
-
|
|
48
|
-
} from "./chunk-WZRGCKEK.js";
|
|
38
|
+
settingsFor
|
|
39
|
+
} from "./chunk-OQXACACU.js";
|
|
49
40
|
import {
|
|
50
41
|
AGENT_IDS,
|
|
42
|
+
LeaderboardResponse,
|
|
43
|
+
PatronInfo,
|
|
51
44
|
ensureHome,
|
|
45
|
+
profileMessage,
|
|
52
46
|
readConfig,
|
|
53
47
|
readJson,
|
|
54
48
|
readUsage,
|
|
@@ -56,11 +50,20 @@ import {
|
|
|
56
50
|
updateUsage,
|
|
57
51
|
writeConfig,
|
|
58
52
|
writePrivateFile
|
|
59
|
-
} from "./chunk-
|
|
53
|
+
} from "./chunk-ZOQJ4CZE.js";
|
|
60
54
|
import {
|
|
55
|
+
DEFAULT_MONTHLY_BUDGET_USD,
|
|
56
|
+
MAX_TOKENS_PER_PAGE,
|
|
57
|
+
MIN_TOKENS_PER_PAGE,
|
|
58
|
+
PACK_SIZE,
|
|
59
|
+
TOKEN_PRICE_UNITS,
|
|
60
|
+
clampTokensPerPage,
|
|
61
|
+
formatUsd,
|
|
62
|
+
normalizePayee,
|
|
63
|
+
tokensToUnits,
|
|
61
64
|
tryLock,
|
|
62
65
|
withLock
|
|
63
|
-
} from "./chunk-
|
|
66
|
+
} from "./chunk-EISHX7Q3.js";
|
|
64
67
|
import "./chunk-GTMLTZRN.js";
|
|
65
68
|
|
|
66
69
|
// src/index.ts
|
|
@@ -70,9 +73,9 @@ import { homedir as homedir2 } from "node:os";
|
|
|
70
73
|
|
|
71
74
|
// src/agents.ts
|
|
72
75
|
import { constants } from "node:fs";
|
|
73
|
-
import { access as access2, realpath as realpath2 } from "node:fs/promises";
|
|
76
|
+
import { access as access2, readFile as readFile4, realpath as realpath2 } from "node:fs/promises";
|
|
74
77
|
import { homedir } from "node:os";
|
|
75
|
-
import { delimiter, join as join5 } from "node:path";
|
|
78
|
+
import { delimiter, join as join5, resolve } from "node:path";
|
|
76
79
|
|
|
77
80
|
// src/amp.ts
|
|
78
81
|
import { mkdir, readFile, rm } from "node:fs/promises";
|
|
@@ -134,13 +137,17 @@ async function installAmpPlugin(path, command) {
|
|
|
134
137
|
return "installed";
|
|
135
138
|
}
|
|
136
139
|
async function upgradeAmpPlugin(path) {
|
|
140
|
+
const command = await ampPluginCommand(path);
|
|
141
|
+
if (command === void 0) return false;
|
|
142
|
+
return await installAmpPlugin(path, command) === "installed";
|
|
143
|
+
}
|
|
144
|
+
async function ampPluginCommand(path) {
|
|
137
145
|
const current = await readText(path);
|
|
138
|
-
if (current === void 0 || !current.startsWith(AMP_PLUGIN_MARKER)) return
|
|
146
|
+
if (current === void 0 || !current.startsWith(AMP_PLUGIN_MARKER)) return void 0;
|
|
139
147
|
const literal = /^const COMMAND = (".*")$/m.exec(current)?.[1];
|
|
140
|
-
if (!literal) return
|
|
148
|
+
if (!literal) return void 0;
|
|
141
149
|
const command = JSON.parse(literal);
|
|
142
|
-
|
|
143
|
-
return await installAmpPlugin(path, command) === "installed";
|
|
150
|
+
return typeof command === "string" ? command : void 0;
|
|
144
151
|
}
|
|
145
152
|
async function removeAmpPlugin(path) {
|
|
146
153
|
const current = await readText(path);
|
|
@@ -161,7 +168,6 @@ async function readText(path) {
|
|
|
161
168
|
var CLAUDE_MATCHER = "WebFetch|WebSearch";
|
|
162
169
|
var HOOK_TIMEOUT_SECONDS = 30;
|
|
163
170
|
var EVENT = "PostToolUse";
|
|
164
|
-
var CLAUDE_HOOK_STATUS = "Patron: pay the site this read";
|
|
165
171
|
function patronGroup(command) {
|
|
166
172
|
return {
|
|
167
173
|
hooks: [
|
|
@@ -176,8 +182,8 @@ function patronGroup(command) {
|
|
|
176
182
|
matcher: CLAUDE_MATCHER
|
|
177
183
|
};
|
|
178
184
|
}
|
|
179
|
-
function withPatronHook(settings, command) {
|
|
180
|
-
return withPatronGroup(settings, EVENT, patronGroup(command));
|
|
185
|
+
function withPatronHook(settings, command, known) {
|
|
186
|
+
return withPatronGroup(settings, EVENT, patronGroup(command), known);
|
|
181
187
|
}
|
|
182
188
|
async function upgradeClaudeHook(path) {
|
|
183
189
|
const settings = await readJsonObject(path);
|
|
@@ -186,8 +192,8 @@ async function upgradeClaudeHook(path) {
|
|
|
186
192
|
await writeJsonObject(path, next);
|
|
187
193
|
return true;
|
|
188
194
|
}
|
|
189
|
-
function withoutPatronHook(settings) {
|
|
190
|
-
return withoutPatronHandlers(settings, EVENT);
|
|
195
|
+
function withoutPatronHook(settings, known) {
|
|
196
|
+
return withoutPatronHandlers(settings, EVENT, known);
|
|
191
197
|
}
|
|
192
198
|
function hasPatronHook(settings, command) {
|
|
193
199
|
return hasPatronHandler(settings, EVENT, {
|
|
@@ -195,16 +201,17 @@ function hasPatronHook(settings, command) {
|
|
|
195
201
|
...command === void 0 ? {} : { command }
|
|
196
202
|
});
|
|
197
203
|
}
|
|
198
|
-
async function installClaudeHook(path, command) {
|
|
204
|
+
async function installClaudeHook(path, command, known) {
|
|
199
205
|
const settings = await readJsonObject(path);
|
|
200
|
-
if (hasPatronHook(settings, command))
|
|
201
|
-
|
|
206
|
+
if (hasPatronHook(settings, command) && patronCommands(settings, EVENT).length === 1)
|
|
207
|
+
return "unchanged";
|
|
208
|
+
await writeJsonObject(path, withPatronHook(settings, command, known));
|
|
202
209
|
return "installed";
|
|
203
210
|
}
|
|
204
|
-
async function removeClaudeHook(path) {
|
|
211
|
+
async function removeClaudeHook(path, known) {
|
|
205
212
|
const settings = await readJsonObject(path);
|
|
206
|
-
if (!hasPatronHandler(settings, EVENT)) return "missing";
|
|
207
|
-
await writeJsonObject(path, withoutPatronHook(settings));
|
|
213
|
+
if (!hasPatronHandler(settings, EVENT, known ? { known } : {})) return "missing";
|
|
214
|
+
await writeJsonObject(path, withoutPatronHook(settings, known));
|
|
208
215
|
return "removed";
|
|
209
216
|
}
|
|
210
217
|
|
|
@@ -216,42 +223,46 @@ var EVENT2 = "postToolUse";
|
|
|
216
223
|
function cursorHooksPath(home) {
|
|
217
224
|
return join2(home, ".cursor", "hooks.json");
|
|
218
225
|
}
|
|
219
|
-
function withCursorHook(config, command) {
|
|
220
|
-
const stripped = withoutCursorHook(config);
|
|
226
|
+
function withCursorHook(config, command, known) {
|
|
227
|
+
const stripped = withoutCursorHook(config, known);
|
|
221
228
|
const next = typeof stripped.version === "number" ? stripped : { version: 1, ...stripped };
|
|
222
229
|
const hooks = isObject(next.hooks) ? next.hooks : {};
|
|
223
230
|
const entries = Array.isArray(hooks[EVENT2]) ? hooks[EVENT2] : [];
|
|
224
231
|
return withHooksKey(next, EVENT2, [...entries, cursorEntry(command)]);
|
|
225
232
|
}
|
|
226
|
-
function withoutCursorHook(config) {
|
|
233
|
+
function withoutCursorHook(config, known) {
|
|
227
234
|
const entries = isObject(config.hooks) ? config.hooks[EVENT2] : void 0;
|
|
228
|
-
|
|
235
|
+
const patron = (entry) => isPatronHandler(entry, known);
|
|
236
|
+
if (!Array.isArray(entries) || !entries.some(patron)) return { ...config };
|
|
229
237
|
return withHooksKey(
|
|
230
238
|
config,
|
|
231
239
|
EVENT2,
|
|
232
|
-
entries.filter((entry) => !
|
|
240
|
+
entries.filter((entry) => !patron(entry))
|
|
233
241
|
);
|
|
234
242
|
}
|
|
235
243
|
function cursorEntry(command) {
|
|
236
244
|
return { command, matcher: CURSOR_MATCHER, timeout: HOOK_TIMEOUT_SECONDS2 };
|
|
237
245
|
}
|
|
238
|
-
function hasCursorHook(config, command) {
|
|
246
|
+
function hasCursorHook(config, command, known) {
|
|
239
247
|
const entries = isObject(config.hooks) ? config.hooks[EVENT2] : void 0;
|
|
240
248
|
if (!Array.isArray(entries)) return false;
|
|
241
|
-
if (command === void 0) return entries.some(isPatronHandler);
|
|
249
|
+
if (command === void 0) return entries.some((entry) => isPatronHandler(entry, known));
|
|
242
250
|
const wanted = JSON.stringify(cursorEntry(command));
|
|
243
|
-
|
|
251
|
+
const patron = entries.filter(
|
|
252
|
+
(entry) => isPatronHandler(entry, known) || JSON.stringify(entry) === wanted
|
|
253
|
+
);
|
|
254
|
+
return patron.length === 1 && JSON.stringify(patron[0]) === wanted;
|
|
244
255
|
}
|
|
245
|
-
async function installCursorHook(path, command) {
|
|
256
|
+
async function installCursorHook(path, command, known) {
|
|
246
257
|
const config = await readJsonObject(path);
|
|
247
|
-
if (hasCursorHook(config, command)) return "unchanged";
|
|
248
|
-
await writeJsonObject(path, withCursorHook(config, command));
|
|
258
|
+
if (hasCursorHook(config, command, known)) return "unchanged";
|
|
259
|
+
await writeJsonObject(path, withCursorHook(config, command, known));
|
|
249
260
|
return "installed";
|
|
250
261
|
}
|
|
251
|
-
async function removeCursorHook(path) {
|
|
262
|
+
async function removeCursorHook(path, known) {
|
|
252
263
|
const config = await readJsonObject(path);
|
|
253
|
-
if (!hasCursorHook(config)) return "missing";
|
|
254
|
-
await writeJsonObject(path, withoutCursorHook(config));
|
|
264
|
+
if (!hasCursorHook(config, void 0, known)) return "missing";
|
|
265
|
+
await writeJsonObject(path, withoutCursorHook(config, known));
|
|
255
266
|
return "removed";
|
|
256
267
|
}
|
|
257
268
|
|
|
@@ -408,32 +419,33 @@ var ADAPTERS = {
|
|
|
408
419
|
install: async (command, { env, home }) => ({
|
|
409
420
|
state: await installAmpPlugin(ampPluginPath(env, home), command)
|
|
410
421
|
}),
|
|
422
|
+
// The plugin file starts with Patron's marker, so it needs no known commands.
|
|
411
423
|
remove: ({ env, home }) => removeAmpPlugin(ampPluginPath(env, home)),
|
|
412
424
|
upgrade: ({ env, home }) => upgradeAmpPlugin(ampPluginPath(env, home))
|
|
413
425
|
},
|
|
414
426
|
"claude-code": {
|
|
415
427
|
files: (environment) => [claudeSettingsPath(environment)],
|
|
416
428
|
detect: async (environment) => await exists(claudeDirectory(environment)) || await onPath(["claude"], environment.env.PATH ?? ""),
|
|
417
|
-
install: async (command, environment) => ({
|
|
418
|
-
state: await installClaudeHook(claudeSettingsPath(environment), command)
|
|
429
|
+
install: async (command, environment, known) => ({
|
|
430
|
+
state: await installClaudeHook(claudeSettingsPath(environment), command, known)
|
|
419
431
|
}),
|
|
420
|
-
remove: (environment) => removeClaudeHook(claudeSettingsPath(environment)),
|
|
432
|
+
remove: (environment, known) => removeClaudeHook(claudeSettingsPath(environment), known),
|
|
421
433
|
upgrade: (environment) => upgradeClaudeHook(claudeSettingsPath(environment))
|
|
422
434
|
},
|
|
423
435
|
codex: {
|
|
424
436
|
files: ({ env, home }) => Object.values(codexPaths(env, home)),
|
|
425
437
|
detect: async ({ env, home }) => await exists(codexHome(env, home)) || await onPath(["codex"], env.PATH ?? ""),
|
|
426
|
-
install: (command, { env, home }) => installCodex(codexPaths(env, home), command),
|
|
427
|
-
remove: ({ env, home }) => removeCodex(codexPaths(env, home)),
|
|
438
|
+
install: (command, { env, home }, known) => installCodex(codexPaths(env, home), command, process.platform, known),
|
|
439
|
+
remove: ({ env, home }, known) => removeCodex(codexPaths(env, home), known),
|
|
428
440
|
upgrade: ({ env, home }) => upgradeCodex(codexPaths(env, home))
|
|
429
441
|
},
|
|
430
442
|
cursor: {
|
|
431
443
|
files: ({ home }) => [cursorHooksPath(home)],
|
|
432
444
|
detect: async ({ env, home }) => await exists(join5(home, ".cursor")) || await onPath(["cursor-agent", "cursor"], env.PATH ?? ""),
|
|
433
|
-
install: async (command, { home }) => ({
|
|
434
|
-
state: await installCursorHook(cursorHooksPath(home), command)
|
|
445
|
+
install: async (command, { home }, known) => ({
|
|
446
|
+
state: await installCursorHook(cursorHooksPath(home), command, known)
|
|
435
447
|
}),
|
|
436
|
-
remove: ({ home }) => removeCursorHook(cursorHooksPath(home))
|
|
448
|
+
remove: ({ home }, known) => removeCursorHook(cursorHooksPath(home), known)
|
|
437
449
|
}
|
|
438
450
|
};
|
|
439
451
|
function claudeDirectory({ env, home }) {
|
|
@@ -452,12 +464,13 @@ async function detectAgents(environment) {
|
|
|
452
464
|
}
|
|
453
465
|
async function installAgents(agents, command, environment) {
|
|
454
466
|
const store = originalsStore(environment);
|
|
467
|
+
const known = await knownCommands(environment, [command]);
|
|
455
468
|
return Promise.all(
|
|
456
469
|
agents.map(async (agent) => {
|
|
457
470
|
let recorded = [];
|
|
458
471
|
try {
|
|
459
472
|
recorded = await rememberOriginals(store, ADAPTERS[agent].files(environment));
|
|
460
|
-
const { note, state } = await ADAPTERS[agent].install(command, environment);
|
|
473
|
+
const { note, state } = await ADAPTERS[agent].install(command, environment, known);
|
|
461
474
|
if (state !== "installed") await forgetOriginals(store, recorded);
|
|
462
475
|
return { agent, label: AGENT_LABELS[agent], state, ...note ? { note } : {} };
|
|
463
476
|
} catch (error) {
|
|
@@ -477,10 +490,17 @@ async function upgradeAgents(agents, environment) {
|
|
|
477
490
|
}
|
|
478
491
|
async function removeAgents(environment) {
|
|
479
492
|
const store = originalsStore(environment);
|
|
493
|
+
const patronHome = environment.env.PATRON_HOME || join5(environment.home, ".patron");
|
|
494
|
+
const stable = `${quote(process.execPath)} ${quote(stableBin(patronHome))} hook`;
|
|
495
|
+
const known = await knownCommands(environment, [
|
|
496
|
+
await hookCommand(environment.env, process.argv[1], process.execPath, patronHome),
|
|
497
|
+
stable,
|
|
498
|
+
withPatronHome(stable, environment.env.PATRON_HOME)
|
|
499
|
+
]);
|
|
480
500
|
return Promise.all(
|
|
481
501
|
AGENT_IDS.map(async (agent) => {
|
|
482
502
|
try {
|
|
483
|
-
const state = await ADAPTERS[agent].remove(environment);
|
|
503
|
+
const state = await ADAPTERS[agent].remove(environment, known);
|
|
484
504
|
if (state === "removed")
|
|
485
505
|
await restoreOriginals(store, ADAPTERS[agent].files(environment)).catch(
|
|
486
506
|
() => void 0
|
|
@@ -492,6 +512,32 @@ async function removeAgents(environment) {
|
|
|
492
512
|
})
|
|
493
513
|
);
|
|
494
514
|
}
|
|
515
|
+
async function knownCommands({ env, home }, extra) {
|
|
516
|
+
const paths = codexPaths(env, home);
|
|
517
|
+
const found = await Promise.all([
|
|
518
|
+
readJsonObject(claudeSettingsPath({ env, home })).then(
|
|
519
|
+
(settings) => patronCommands(settings, "PostToolUse"),
|
|
520
|
+
() => []
|
|
521
|
+
),
|
|
522
|
+
readJsonObject(paths.hooks).then(
|
|
523
|
+
(settings) => patronCommands(settings, "Stop"),
|
|
524
|
+
() => []
|
|
525
|
+
),
|
|
526
|
+
readFile4(paths.config, "utf8").then(
|
|
527
|
+
(toml) => [codexNotifyCommand(toml)],
|
|
528
|
+
() => []
|
|
529
|
+
),
|
|
530
|
+
ampPluginCommand(ampPluginPath(env, home)).then(
|
|
531
|
+
(command) => [command],
|
|
532
|
+
() => []
|
|
533
|
+
)
|
|
534
|
+
]);
|
|
535
|
+
return new Set(
|
|
536
|
+
[...found.flat(), ...extra].filter(
|
|
537
|
+
(command) => typeof command === "string" && command !== ""
|
|
538
|
+
)
|
|
539
|
+
);
|
|
540
|
+
}
|
|
495
541
|
function originalsStore({ env, home }) {
|
|
496
542
|
return originalsPath(env.PATRON_HOME || join5(home, ".patron"));
|
|
497
543
|
}
|
|
@@ -505,6 +551,9 @@ function failure(agent, error) {
|
|
|
505
551
|
}
|
|
506
552
|
async function hookCommand(env = process.env, script = process.argv[1], node = process.execPath, patronHome = env.PATRON_HOME || join5(homedir(), ".patron")) {
|
|
507
553
|
if (env.PATRON_HOOK_COMMAND) return env.PATRON_HOOK_COMMAND;
|
|
554
|
+
return withPatronHome(await runCommand(script, node, patronHome), env.PATRON_HOME);
|
|
555
|
+
}
|
|
556
|
+
async function runCommand(script, node, patronHome) {
|
|
508
557
|
const resolved = script ? await realpath2(script).catch(() => void 0) : void 0;
|
|
509
558
|
if (resolved && /[/\\]bin\.js$/.test(resolved) && !/[/\\]_npx[/\\]/.test(resolved))
|
|
510
559
|
return `${quote(node)} ${quote(resolved)} hook`;
|
|
@@ -512,6 +561,9 @@ async function hookCommand(env = process.env, script = process.argv[1], node = p
|
|
|
512
561
|
return `${quote(node)} ${quote(stableBin(patronHome))} hook`;
|
|
513
562
|
return "npx -y @tempoxyz/patron hook";
|
|
514
563
|
}
|
|
564
|
+
function withPatronHome(command, patronHome) {
|
|
565
|
+
return patronHome ? `PATRON_HOME=${quote(resolve(patronHome))} ${command}` : command;
|
|
566
|
+
}
|
|
515
567
|
function quote(value) {
|
|
516
568
|
return /^[\w./:@-]+$/.test(value) ? value : `'${value.replaceAll("'", "'\\''")}'`;
|
|
517
569
|
}
|
|
@@ -697,16 +749,24 @@ async function linkRefillFailedAt(home) {
|
|
|
697
749
|
const value = await readJson(statePaths(home).linkRefill).catch(() => void 0);
|
|
698
750
|
return typeof value?.failedAt === "number" ? value.failedAt : void 0;
|
|
699
751
|
}
|
|
700
|
-
var REFILL_LOCK_STALE_MS = 5 * 60 * 1e3;
|
|
701
752
|
var defaultOpenWallet = async (settings, config, issuer, maxUnits) => {
|
|
702
753
|
if (config.wallet === "link") {
|
|
703
|
-
const { openLinkWallet } = await import("./wallet-
|
|
754
|
+
const { openLinkWallet } = await import("./wallet-LOLL66ID.js");
|
|
704
755
|
return openLinkWallet(config, maxUnits);
|
|
705
756
|
}
|
|
706
|
-
const { openWallet } = await import("./wallet-
|
|
757
|
+
const { openWallet } = await import("./wallet-LOLL66ID.js");
|
|
707
758
|
return openWallet(settings, config.wallet, issuer, maxUnits);
|
|
708
759
|
};
|
|
760
|
+
var REFILL_LOCK_STALE_MS = 5 * 60 * 1e3;
|
|
709
761
|
async function buyWithinBudget(settings, config, wanted, openWallet = defaultOpenWallet) {
|
|
762
|
+
await ensureHome(settings.home);
|
|
763
|
+
return withLock(
|
|
764
|
+
statePaths(settings.home).purchaseLock,
|
|
765
|
+
() => buyLocked(settings, config, wanted, openWallet),
|
|
766
|
+
{ staleMs: REFILL_LOCK_STALE_MS, timeoutMs: REFILL_LOCK_STALE_MS }
|
|
767
|
+
);
|
|
768
|
+
}
|
|
769
|
+
async function buyLocked(settings, config, wanted, openWallet) {
|
|
710
770
|
const usage = await readUsage(settings.home);
|
|
711
771
|
const count = purchasableTokens(config, usage, wanted);
|
|
712
772
|
if (count === 0) return 0;
|
|
@@ -729,10 +789,28 @@ async function buyWithinBudget(settings, config, wanted, openWallet = defaultOpe
|
|
|
729
789
|
});
|
|
730
790
|
return tokens.length;
|
|
731
791
|
}
|
|
792
|
+
var REFILL_DELAY_MS = { max: 10 * 60 * 1e3, min: 60 * 1e3 };
|
|
793
|
+
async function delayedRefill(base, openWallet = defaultOpenWallet, sleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms)), random = Math.random) {
|
|
794
|
+
if (!await readConfig(base.home)) return 0;
|
|
795
|
+
const release = await tryLock(
|
|
796
|
+
statePaths(base.home).refillWait,
|
|
797
|
+
REFILL_DELAY_MS.max + REFILL_LOCK_STALE_MS
|
|
798
|
+
);
|
|
799
|
+
if (!release) return 0;
|
|
800
|
+
try {
|
|
801
|
+
await sleep(
|
|
802
|
+
REFILL_DELAY_MS.min + random() * (REFILL_DELAY_MS.max - REFILL_DELAY_MS.min)
|
|
803
|
+
);
|
|
804
|
+
} finally {
|
|
805
|
+
await release();
|
|
806
|
+
}
|
|
807
|
+
return refill(base, openWallet);
|
|
808
|
+
}
|
|
732
809
|
async function refill(base, openWallet = defaultOpenWallet, now = Date.now) {
|
|
733
810
|
const config = await readConfig(base.home);
|
|
734
811
|
if (!config || config.paused) return 0;
|
|
735
812
|
const settings = settingsFor(base, config);
|
|
813
|
+
await loadIssuer(settings.home, settings.gatewayUrl).catch(() => void 0);
|
|
736
814
|
const release = await tryLock(
|
|
737
815
|
statePaths(settings.home).refillLock,
|
|
738
816
|
REFILL_LOCK_STALE_MS
|
|
@@ -846,6 +924,7 @@ PATRON_PRIVATE_KEY (testnet automation), PATRON_BUDGET_USD (non-interactive setu
|
|
|
846
924
|
PATRON_WALLET=link (pay with Link through link-cli), PATRON_LINK_CLI (its path).`;
|
|
847
925
|
var UsageError = class extends Error {
|
|
848
926
|
};
|
|
927
|
+
var TIP_BATCH = 8;
|
|
849
928
|
function defaults() {
|
|
850
929
|
const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
851
930
|
const color = supportsColor(process.stdout);
|
|
@@ -861,6 +940,19 @@ function defaults() {
|
|
|
861
940
|
stdout: process.stdout
|
|
862
941
|
};
|
|
863
942
|
}
|
|
943
|
+
var REFRESHES_STABLE_COPY = /* @__PURE__ */ new Set([
|
|
944
|
+
"status",
|
|
945
|
+
"pause",
|
|
946
|
+
"resume",
|
|
947
|
+
"budget",
|
|
948
|
+
"per-page",
|
|
949
|
+
"tip",
|
|
950
|
+
"name"
|
|
951
|
+
]);
|
|
952
|
+
async function refreshStableCli(deps) {
|
|
953
|
+
const config = await readConfig(deps.settings.home).catch(() => void 0);
|
|
954
|
+
if (config) await installStableCli(deps.settings.home).catch(() => void 0);
|
|
955
|
+
}
|
|
864
956
|
async function run(argv, overrides = {}) {
|
|
865
957
|
const base = { ...defaults(), ...overrides };
|
|
866
958
|
let shared;
|
|
@@ -873,6 +965,8 @@ async function run(argv, overrides = {}) {
|
|
|
873
965
|
};
|
|
874
966
|
const [command, ...args] = argv;
|
|
875
967
|
try {
|
|
968
|
+
if (command !== void 0 && REFRESHES_STABLE_COPY.has(command))
|
|
969
|
+
await refreshStableCli(deps);
|
|
876
970
|
switch (command) {
|
|
877
971
|
case void 0:
|
|
878
972
|
await main(deps);
|
|
@@ -900,7 +994,7 @@ async function run(argv, overrides = {}) {
|
|
|
900
994
|
await uninstall(deps);
|
|
901
995
|
return 0;
|
|
902
996
|
case "refill":
|
|
903
|
-
await refill(deps.settings, deps.openWallet).catch(() => 0);
|
|
997
|
+
await (args[0] === "--delay" ? delayedRefill(deps.settings, deps.openWallet) : refill(deps.settings, deps.openWallet)).catch(() => 0);
|
|
904
998
|
return 0;
|
|
905
999
|
case "help":
|
|
906
1000
|
case "--help":
|
|
@@ -1069,7 +1163,7 @@ async function tempoSignIn(deps, budgetUnits, issuer) {
|
|
|
1069
1163
|
deps,
|
|
1070
1164
|
deps.settings.privateKey ? "Using PATRON_PRIVATE_KEY" : "Signing in with Tempo Wallet"
|
|
1071
1165
|
);
|
|
1072
|
-
const { signIn } = await import("./wallet-
|
|
1166
|
+
const { signIn } = await import("./wallet-LOLL66ID.js");
|
|
1073
1167
|
const account = await signIn(deps.settings, {
|
|
1074
1168
|
budgetUnits,
|
|
1075
1169
|
issuer,
|
|
@@ -1109,8 +1203,8 @@ async function askBudget(deps, fallback) {
|
|
|
1109
1203
|
const preset = process.env.PATRON_BUDGET_USD;
|
|
1110
1204
|
if (!deps.interactive) {
|
|
1111
1205
|
const value = preset ? parseUsd(preset) : fallback;
|
|
1112
|
-
if (value === void 0 || value
|
|
1113
|
-
throw new UsageError("PATRON_BUDGET_USD must be
|
|
1206
|
+
if (value === void 0 || value < 1e-3)
|
|
1207
|
+
throw new UsageError("PATRON_BUDGET_USD must be at least 0.001.");
|
|
1114
1208
|
return value;
|
|
1115
1209
|
}
|
|
1116
1210
|
const prompter = deps.prompter();
|
|
@@ -1181,9 +1275,9 @@ async function changeBudget(deps, input) {
|
|
|
1181
1275
|
if (budget === void 0 || budget < 1e-3)
|
|
1182
1276
|
throw new UsageError("Usage: patron budget <usd>, such as patron budget 10");
|
|
1183
1277
|
} else budget = await askBudget(deps, config.monthlyBudgetUsd);
|
|
1184
|
-
if (config.wallet === "tempo" && budget
|
|
1278
|
+
if (config.wallet === "tempo" && budget >= config.monthlyBudgetUsd) {
|
|
1185
1279
|
step(deps, "Approve the new spending limit in Tempo Wallet");
|
|
1186
|
-
const { authorizeBudget } = await import("./wallet-
|
|
1280
|
+
const { authorizeBudget } = await import("./wallet-LOLL66ID.js");
|
|
1187
1281
|
const issuer = await loadIssuer(settings.home, settings.gatewayUrl, {
|
|
1188
1282
|
fetch: deps.fetch
|
|
1189
1283
|
});
|
|
@@ -1237,43 +1331,78 @@ async function tip(deps, url, amount = "0.10") {
|
|
|
1237
1331
|
throw new Error(
|
|
1238
1332
|
`This tip needs ${tokensUsd(missing)} more than this month's budget allows. Raise it with patron budget <usd>.`
|
|
1239
1333
|
);
|
|
1240
|
-
|
|
1241
|
-
|
|
1334
|
+
const amount2 = Math.max(missing, purchasableTokens(config, usage, PACK_SIZE));
|
|
1335
|
+
step(deps, `Adding ${tokensUsd(amount2)} to your balance`);
|
|
1336
|
+
await buyWithinBudget(settings, config, amount2, deps.openWallet);
|
|
1242
1337
|
}
|
|
1243
1338
|
const issuer = await loadIssuer(settings.home, settings.gatewayUrl, {
|
|
1244
1339
|
fetch: deps.fetch
|
|
1245
1340
|
});
|
|
1246
|
-
|
|
1341
|
+
let stopped = false;
|
|
1342
|
+
const stop = () => {
|
|
1343
|
+
stopped = true;
|
|
1344
|
+
process.off("SIGINT", stop);
|
|
1345
|
+
process.off("SIGTERM", stop);
|
|
1346
|
+
};
|
|
1347
|
+
process.once("SIGINT", stop);
|
|
1348
|
+
process.once("SIGTERM", stop);
|
|
1247
1349
|
let paid = 0;
|
|
1350
|
+
let sent = 0;
|
|
1248
1351
|
let rejection;
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
batch.
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1352
|
+
try {
|
|
1353
|
+
while (sent < count && !stopped) {
|
|
1354
|
+
const batch = await store.takeMany(Math.min(TIP_BATCH, count - sent));
|
|
1355
|
+
if (batch.length === 0) break;
|
|
1356
|
+
sent += batch.length;
|
|
1357
|
+
const unsent = [];
|
|
1358
|
+
let rejected = 0;
|
|
1359
|
+
const results = await Promise.all(
|
|
1360
|
+
batch.map(
|
|
1361
|
+
(token) => redeem({
|
|
1362
|
+
domain,
|
|
1363
|
+
fetch: deps.fetch,
|
|
1364
|
+
issuer,
|
|
1365
|
+
relayUrl: settings.relayUrl,
|
|
1366
|
+
token
|
|
1367
|
+
}).catch(() => {
|
|
1368
|
+
unsent.push(token);
|
|
1369
|
+
return void 0;
|
|
1370
|
+
})
|
|
1371
|
+
)
|
|
1372
|
+
);
|
|
1373
|
+
const ok = results.filter((result) => result?.ok).length;
|
|
1374
|
+
for (const result of results)
|
|
1375
|
+
if (result && !result.ok) {
|
|
1376
|
+
rejected += 1;
|
|
1377
|
+
rejection = result.error ?? "rejected";
|
|
1378
|
+
}
|
|
1379
|
+
sent -= unsent.length;
|
|
1380
|
+
await store.add(unsent);
|
|
1381
|
+
if (ok > 0)
|
|
1382
|
+
await updateUsage(settings.home, (usage) => {
|
|
1383
|
+
usage.domains[domain] = (usage.domains[domain] ?? 0) + ok;
|
|
1384
|
+
usage.tippedTokens += ok;
|
|
1385
|
+
});
|
|
1386
|
+
paid += ok;
|
|
1387
|
+
if (count > TIP_BATCH && ok > 0)
|
|
1388
|
+
say(
|
|
1389
|
+
deps,
|
|
1390
|
+
styleText(
|
|
1391
|
+
` ${tokensUsd(paid)} of ${tokensUsd(count)} sent`,
|
|
1392
|
+
"dim",
|
|
1393
|
+
deps.color
|
|
1394
|
+
)
|
|
1395
|
+
);
|
|
1396
|
+
if (ok === 0 && (rejected > 0 || unsent.length > 0)) break;
|
|
1269
1397
|
}
|
|
1398
|
+
} finally {
|
|
1399
|
+
process.off("SIGINT", stop);
|
|
1400
|
+
process.off("SIGTERM", stop);
|
|
1270
1401
|
}
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
usage.tippedTokens += paid;
|
|
1276
|
-
});
|
|
1402
|
+
if (stopped && sent < count)
|
|
1403
|
+
throw new Error(
|
|
1404
|
+
`Tip stopped: tipped ${domain} ${tokensUsd(paid)} of ${tokensUsd(count)}; the rest stays in your balance.`
|
|
1405
|
+
);
|
|
1277
1406
|
if (paid === 0)
|
|
1278
1407
|
throw new Error(
|
|
1279
1408
|
rejection ? `Patron rejected the tip to ${domain}: ${rejection}.` : `Could not reach Patron to tip ${domain}; your balance was kept.`
|
|
@@ -1300,7 +1429,7 @@ async function setName(deps, name) {
|
|
|
1300
1429
|
const issuer = await loadIssuer(settings.home, settings.gatewayUrl, {
|
|
1301
1430
|
fetch: deps.fetch
|
|
1302
1431
|
});
|
|
1303
|
-
const open = deps.openWallet ?? (async (s, c, i, max) => (await import("./wallet-
|
|
1432
|
+
const open = deps.openWallet ?? (async (s, c, i, max) => (await import("./wallet-LOLL66ID.js")).openWallet(s, c.wallet, i, max));
|
|
1304
1433
|
const wallet = await open(settings, config, issuer, 0n);
|
|
1305
1434
|
await setProfile(settings.gatewayUrl, wallet, name, deps.fetch);
|
|
1306
1435
|
await writeConfig(settings.home, { ...config, name });
|
|
@@ -1308,9 +1437,11 @@ async function setName(deps, name) {
|
|
|
1308
1437
|
}
|
|
1309
1438
|
async function uninstall(deps) {
|
|
1310
1439
|
const config = await readConfig(deps.settings.home);
|
|
1311
|
-
const
|
|
1312
|
-
|
|
1313
|
-
|
|
1440
|
+
const removed = await removeAgents(deps.agents);
|
|
1441
|
+
const results = removed.filter((result) => result.state !== "missing");
|
|
1442
|
+
const notFound = removed.filter(
|
|
1443
|
+
(result) => result.state === "missing" && config?.agents.includes(result.agent)
|
|
1444
|
+
).map((result) => result.label);
|
|
1314
1445
|
say(deps, styleText("Patron uninstall", "bold", deps.color));
|
|
1315
1446
|
for (const line of formatAgentResults(results, deps.color)) say(deps, line);
|
|
1316
1447
|
if (results.length === 0)
|
|
@@ -1342,6 +1473,10 @@ async function uninstall(deps) {
|
|
|
1342
1473
|
deps.color
|
|
1343
1474
|
)
|
|
1344
1475
|
);
|
|
1476
|
+
if (notFound.length > 0)
|
|
1477
|
+
throw new Error(
|
|
1478
|
+
`Found no Patron hook to remove for ${notFound.join(", ")}. If a hook running "patron hook" is still in its settings, delete it by hand.`
|
|
1479
|
+
);
|
|
1345
1480
|
}
|
|
1346
1481
|
function readVersion() {
|
|
1347
1482
|
for (const path of ["../package.json", "../../package.json"]) {
|
|
@@ -4,10 +4,11 @@ const require = __patronCreateRequire(import.meta.url)
|
|
|
4
4
|
// src/refill-process.ts
|
|
5
5
|
import { spawn } from "node:child_process";
|
|
6
6
|
import { createRequire } from "node:module";
|
|
7
|
-
function startRefill(script = process.argv[1]) {
|
|
7
|
+
function startRefill(options = {}, script = process.argv[1]) {
|
|
8
8
|
try {
|
|
9
9
|
if (!script) return;
|
|
10
|
-
const [command,
|
|
10
|
+
const [command, refillArgs] = refillCommand(script);
|
|
11
|
+
const args = options.delayed ? [...refillArgs, "--delay"] : refillArgs;
|
|
11
12
|
const child = spawn(command, args, {
|
|
12
13
|
detached: true,
|
|
13
14
|
env: process.env,
|