@tempoxyz/patron 0.0.20 → 0.0.21

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 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, and this month's per-site counts, which never leave the machine.
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-Y2W56T2X.js");
7
+ const { runHookProcess } = await import("./chunks/hook-process-FNSWFVFP.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-Y2W56T2X.js");
11
+ const { runPayProcess } = await import("./chunks/hook-process-FNSWFVFP.js");
12
12
  await runPayProcess().catch(() => void 0);
13
13
  process.exit(0);
14
14
  } else {
15
- const { run } = await import("./chunks/-RNUFDXSV.js");
15
+ const { run } = await import("./chunks/-NGLRKGBT.js");
16
16
  process.exitCode = await run(process.argv.slice(2));
17
17
  }
@@ -31,7 +31,7 @@ import {
31
31
  } from "./chunk-UFBFF3R6.js";
32
32
  import {
33
33
  startRefill
34
- } from "./chunk-CV2WYKM7.js";
34
+ } from "./chunk-HEQBEBKL.js";
35
35
  import {
36
36
  DEFAULT_MONTHLY_BUDGET_USD,
37
37
  MAX_TOKENS_PER_PAGE,
@@ -62,13 +62,14 @@ import "./chunk-GTMLTZRN.js";
62
62
 
63
63
  // src/index.ts
64
64
  import { readFileSync } from "node:fs";
65
- import { rm as rm3 } from "node:fs/promises";
66
- import { homedir } from "node:os";
65
+ import { rm as rm4 } from "node:fs/promises";
66
+ import { homedir as homedir2 } from "node:os";
67
67
 
68
68
  // src/agents.ts
69
69
  import { constants } from "node:fs";
70
- import { access, realpath } from "node:fs/promises";
71
- import { delimiter, join as join4 } from "node:path";
70
+ import { access as access2, realpath as realpath2 } from "node:fs/promises";
71
+ import { homedir } from "node:os";
72
+ import { delimiter, join as join5 } from "node:path";
72
73
 
73
74
  // src/amp.ts
74
75
  import { mkdir, readFile, rm } from "node:fs/promises";
@@ -322,6 +323,66 @@ function parse(text) {
322
323
  }
323
324
  }
324
325
 
326
+ // src/stable-install.ts
327
+ import {
328
+ access,
329
+ cp,
330
+ mkdir as mkdir2,
331
+ readdir,
332
+ readFile as readFile3,
333
+ readlink,
334
+ realpath,
335
+ rename,
336
+ rm as rm3,
337
+ symlink
338
+ } from "node:fs/promises";
339
+ import { basename, dirname as dirname3, join as join4 } from "node:path";
340
+ function stableBin(home) {
341
+ return join4(home, "cli", "current", "dist", "bin.js");
342
+ }
343
+ async function installStableCli(home, script = process.argv[1]) {
344
+ const resolved = script ? await realpath(script).catch(() => void 0) : void 0;
345
+ if (!resolved || !/[/\\]_npx[/\\]/.test(resolved) || basename(resolved) !== "bin.js")
346
+ return void 0;
347
+ const packageDir = dirname3(dirname3(resolved));
348
+ const { version } = JSON.parse(await readFile3(join4(packageDir, "package.json"), "utf8"));
349
+ if (typeof version !== "string" || !/^[\w.+-]+$/.test(version)) return void 0;
350
+ const root = join4(home, "cli");
351
+ const target = join4(root, version);
352
+ await mkdir2(root, { recursive: true, mode: 448 });
353
+ if (!await isFile(join4(target, "dist", "bin.js"))) {
354
+ const staging = `${target}.${process.pid}.tmp`;
355
+ await rm3(staging, { force: true, recursive: true });
356
+ await cp(join4(packageDir, "dist"), join4(staging, "dist"), { recursive: true });
357
+ await cp(join4(packageDir, "package.json"), join4(staging, "package.json"));
358
+ await rm3(target, { force: true, recursive: true });
359
+ await rename(staging, target);
360
+ }
361
+ const current = join4(root, "current");
362
+ const linked = await readlink(current).catch(() => void 0);
363
+ const previous = linked ? basename(linked) : void 0;
364
+ if (previous !== version) {
365
+ const link = `${current}.${process.pid}.tmp`;
366
+ await rm3(link, { force: true });
367
+ await symlink(process.platform === "win32" ? target : version, link, "junction");
368
+ await rename(link, current).catch(async () => {
369
+ await rm3(current, { force: true, recursive: true });
370
+ await rename(link, current);
371
+ });
372
+ }
373
+ for (const entry of await readdir(root)) {
374
+ if (entry === "current" || entry === version || entry === previous) continue;
375
+ await rm3(join4(root, entry), { force: true, recursive: true });
376
+ }
377
+ return stableBin(home);
378
+ }
379
+ async function isFile(path) {
380
+ return access(path).then(
381
+ () => true,
382
+ () => false
383
+ );
384
+ }
385
+
325
386
  // src/agents.ts
326
387
  var AGENT_LABELS = {
327
388
  amp: "Amp",
@@ -332,7 +393,7 @@ var AGENT_LABELS = {
332
393
  var ADAPTERS = {
333
394
  amp: {
334
395
  files: ({ env, home }) => [ampPluginPath(env, home)],
335
- detect: async ({ env, home }) => await exists(join4(env.XDG_CONFIG_HOME || join4(home, ".config"), "amp")) || await onPath(["amp"], env.PATH ?? ""),
396
+ detect: async ({ env, home }) => await exists(join5(env.XDG_CONFIG_HOME || join5(home, ".config"), "amp")) || await onPath(["amp"], env.PATH ?? ""),
336
397
  install: async (command, { env, home }) => ({
337
398
  state: await installAmpPlugin(ampPluginPath(env, home), command)
338
399
  }),
@@ -357,7 +418,7 @@ var ADAPTERS = {
357
418
  },
358
419
  cursor: {
359
420
  files: ({ home }) => [cursorHooksPath(home)],
360
- detect: async ({ env, home }) => await exists(join4(home, ".cursor")) || await onPath(["cursor-agent", "cursor"], env.PATH ?? ""),
421
+ detect: async ({ env, home }) => await exists(join5(home, ".cursor")) || await onPath(["cursor-agent", "cursor"], env.PATH ?? ""),
361
422
  install: async (command, { home }) => ({
362
423
  state: await installCursorHook(cursorHooksPath(home), command)
363
424
  }),
@@ -365,10 +426,10 @@ var ADAPTERS = {
365
426
  }
366
427
  };
367
428
  function claudeDirectory({ env, home }) {
368
- return env.CLAUDE_CONFIG_DIR || join4(home, ".claude");
429
+ return env.CLAUDE_CONFIG_DIR || join5(home, ".claude");
369
430
  }
370
431
  function claudeSettingsPath(environment) {
371
- return join4(claudeDirectory(environment), "settings.json");
432
+ return join5(claudeDirectory(environment), "settings.json");
372
433
  }
373
434
  async function detectAgents(environment) {
374
435
  const found = await Promise.all(
@@ -421,7 +482,7 @@ async function removeAgents(environment) {
421
482
  );
422
483
  }
423
484
  function originalsStore({ env, home }) {
424
- return originalsPath(env.PATRON_HOME || join4(home, ".patron"));
485
+ return originalsPath(env.PATRON_HOME || join5(home, ".patron"));
425
486
  }
426
487
  function failure(agent, error) {
427
488
  return {
@@ -431,18 +492,20 @@ function failure(agent, error) {
431
492
  state: "failed"
432
493
  };
433
494
  }
434
- async function hookCommand(env = process.env, script = process.argv[1], node = process.execPath) {
495
+ async function hookCommand(env = process.env, script = process.argv[1], node = process.execPath, patronHome = env.PATRON_HOME || join5(homedir(), ".patron")) {
435
496
  if (env.PATRON_HOOK_COMMAND) return env.PATRON_HOOK_COMMAND;
436
- const resolved = script ? await realpath(script).catch(() => void 0) : void 0;
497
+ const resolved = script ? await realpath2(script).catch(() => void 0) : void 0;
437
498
  if (resolved && /[/\\]bin\.js$/.test(resolved) && !/[/\\]_npx[/\\]/.test(resolved))
438
499
  return `${quote(node)} ${quote(resolved)} hook`;
500
+ if (await exists(stableBin(patronHome)))
501
+ return `${quote(node)} ${quote(stableBin(patronHome))} hook`;
439
502
  return "npx -y @tempoxyz/patron hook";
440
503
  }
441
504
  function quote(value) {
442
505
  return /^[\w./:@-]+$/.test(value) ? value : `'${value.replaceAll("'", "'\\''")}'`;
443
506
  }
444
507
  async function exists(path) {
445
- return access(path).then(
508
+ return access2(path).then(
446
509
  () => true,
447
510
  () => false
448
511
  );
@@ -451,7 +514,7 @@ async function onPath(commands, path) {
451
514
  for (const directory of path.split(delimiter)) {
452
515
  if (!directory) continue;
453
516
  for (const command of commands) {
454
- const found = await access(join4(directory, command), constants.X_OK).then(
517
+ const found = await access2(join5(directory, command), constants.X_OK).then(
455
518
  () => true,
456
519
  () => false
457
520
  );
@@ -744,7 +807,7 @@ function defaults() {
744
807
  const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
745
808
  const color = supportsColor(process.stdout);
746
809
  return {
747
- agents: { env: process.env, home: homedir() },
810
+ agents: { env: process.env, home: homedir2() },
748
811
  color,
749
812
  fetch: globalThis.fetch,
750
813
  interactive,
@@ -848,6 +911,18 @@ async function main(deps) {
848
911
  await status(deps, deps.interactive);
849
912
  }
850
913
  async function syncAgents(deps, config) {
914
+ await installStableCli(deps.settings.home).catch(() => void 0);
915
+ const command = await hookCommand(deps.agents.env);
916
+ const moved = (await installAgents(config.agents, command, deps.agents)).filter((result) => result.state === "installed").map((result) => result.agent);
917
+ if (moved.length > 0) {
918
+ say(
919
+ deps,
920
+ `Patron now starts with node for ${moved.map((a) => AGENT_LABELS[a]).join(", ")}.`
921
+ );
922
+ if (moved.includes("codex"))
923
+ say(deps, " Codex: trust the hook again in /hooks when it asks.");
924
+ say(deps);
925
+ }
851
926
  const upgraded = await upgradeAgents(config.agents, deps.agents);
852
927
  if (upgraded.length > 0) {
853
928
  const names = upgraded.map((agent) => AGENT_LABELS[agent]).join(", ");
@@ -860,11 +935,7 @@ async function syncAgents(deps, config) {
860
935
  (agent) => !config.agents.includes(agent)
861
936
  );
862
937
  if (added.length === 0) return;
863
- const results = await installAgents(
864
- added,
865
- await hookCommand(deps.agents.env),
866
- deps.agents
867
- );
938
+ const results = await installAgents(added, command, deps.agents);
868
939
  for (const line of formatAgentResults(results, deps.color)) say(deps, line);
869
940
  say(deps);
870
941
  const installed = results.filter((result) => result.state !== "failed").map((result) => result.agent);
@@ -910,6 +981,7 @@ async function setup(deps) {
910
981
  const bought = await buyWithinBudget(settings, config, STARTER_TOKENS, deps.openWallet);
911
982
  done(deps, `${bought.toLocaleString("en-US")} seals ready`);
912
983
  deps.startRefill();
984
+ await installStableCli(settings.home).catch(() => void 0);
913
985
  const agents = await detectAgents(deps.agents);
914
986
  if (agents.length === 0) {
915
987
  say(deps);
@@ -1147,7 +1219,7 @@ async function uninstall(deps) {
1147
1219
  }
1148
1220
  }
1149
1221
  if (purge) {
1150
- await rm3(deps.settings.home, { force: true, recursive: true });
1222
+ await rm4(deps.settings.home, { force: true, recursive: true });
1151
1223
  done(deps, "Local data deleted.");
1152
1224
  } else
1153
1225
  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 child = spawn(process.execPath, [...process.execArgv, script, "refill"], {
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 {
@@ -10,7 +10,7 @@ import {
10
10
  import {
11
11
  startPayment,
12
12
  startRefill
13
- } from "./chunk-CV2WYKM7.js";
13
+ } from "./chunk-HEQBEBKL.js";
14
14
  import {
15
15
  resolveSettings
16
16
  } from "./chunk-CF4LJZLC.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tempoxyz/patron",
3
- "version": "0.0.20",
3
+ "version": "0.0.21",
4
4
  "description": "Pay the websites your AI agents read, privately",
5
5
  "type": "module",
6
6
  "bin": {