@tempoxyz/patron 0.0.28 → 0.0.30

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.
@@ -3,7 +3,7 @@ const require = __patronCreateRequire(import.meta.url)
3
3
  import {
4
4
  isExcludedByDefault,
5
5
  normalizePayee
6
- } from "./chunk-EISHX7Q3.js";
6
+ } from "./chunk-AKOXBXV3.js";
7
7
 
8
8
  // src/hook-input.ts
9
9
  var HOOK_TIMEOUT_MS = 2e3;
@@ -3,22 +3,22 @@ const require = __patronCreateRequire(import.meta.url)
3
3
  import {
4
4
  IssueResponse,
5
5
  IssuerInfo,
6
- RedeemResponse,
7
- ensureHome,
8
- readJson,
9
- statePaths,
10
- writePrivateFile
11
- } from "./chunk-EV7OA6GK.js";
6
+ RedeemResponse
7
+ } from "./chunk-ROVOCDKE.js";
12
8
  import {
13
9
  ISSUER_NAME,
14
10
  MAX_TOKENS_PER_PURCHASE,
15
11
  PACK_SIZE,
16
12
  TOKEN_PRICE_UNITS,
17
13
  TOKEN_TYPE,
14
+ ensureHome,
18
15
  fromBase64Url,
16
+ readJson,
17
+ statePaths,
19
18
  toBase64Url,
20
- withLock
21
- } from "./chunk-EISHX7Q3.js";
19
+ withLock,
20
+ writePrivateFile
21
+ } from "./chunk-AKOXBXV3.js";
22
22
  import {
23
23
  __commonJS,
24
24
  __export,
@@ -14990,7 +14990,7 @@ async function redeem(options) {
14990
14990
 
14991
14991
  // src/budget.ts
14992
14992
  var LINK_TOKEN_STEP = 10;
14993
- var REFILL_THRESHOLD = 200;
14993
+ var REFILL_THRESHOLD = 400;
14994
14994
  var STARTER_TOKENS = 100;
14995
14995
  function budgetTokens(monthlyBudgetUsd) {
14996
14996
  if (!Number.isFinite(monthlyBudgetUsd) || monthlyBudgetUsd <= 0) return 0;
@@ -15030,11 +15030,28 @@ import { dirname } from "node:path";
15030
15030
  function isObject(value) {
15031
15031
  return typeof value === "object" && value !== null && !Array.isArray(value);
15032
15032
  }
15033
- function isPatronHandler(handler) {
15033
+ var CLAUDE_HOOK_STATUS = "Patron: pay the site this read";
15034
+ var CODEX_HOOK_STATUS = "Patron: pay the sites this turn read";
15035
+ var PATRON_STATUSES = /* @__PURE__ */ new Set([
15036
+ CLAUDE_HOOK_STATUS,
15037
+ CODEX_HOOK_STATUS
15038
+ ]);
15039
+ var NO_COMMANDS = /* @__PURE__ */ new Set();
15040
+ function isPatronHandler(handler, known = NO_COMMANDS) {
15034
15041
  if (!isObject(handler) || typeof handler.command !== "string") return false;
15042
+ if (typeof handler.statusMessage === "string" && PATRON_STATUSES.has(handler.statusMessage))
15043
+ return true;
15044
+ if (known.has(handler.command)) return true;
15035
15045
  return /patron/i.test(handler.command) && /\shook\s*$/.test(handler.command);
15036
15046
  }
15037
- function withoutPatronHandlers(settings, event) {
15047
+ function patronCommands(settings, event) {
15048
+ const groups = isObject(settings.hooks) ? settings.hooks[event] : void 0;
15049
+ if (!Array.isArray(groups)) return [];
15050
+ return groups.flatMap(
15051
+ (group) => isObject(group) && Array.isArray(group.hooks) ? group.hooks.filter((handler) => isPatronHandler(handler)).map((handler) => handler.command) : []
15052
+ );
15053
+ }
15054
+ function withoutPatronHandlers(settings, event, known = NO_COMMANDS) {
15038
15055
  const groups = isObject(settings.hooks) ? settings.hooks[event] : void 0;
15039
15056
  if (!isObject(settings.hooks) || !Array.isArray(groups)) return { ...settings };
15040
15057
  let changed = false;
@@ -15044,7 +15061,7 @@ function withoutPatronHandlers(settings, event) {
15044
15061
  kept.push(group);
15045
15062
  continue;
15046
15063
  }
15047
- const handlers = group.hooks.filter((handler) => !isPatronHandler(handler));
15064
+ const handlers = group.hooks.filter((handler) => !isPatronHandler(handler, known));
15048
15065
  if (handlers.length === group.hooks.length) kept.push(group);
15049
15066
  else {
15050
15067
  changed = true;
@@ -15054,8 +15071,8 @@ function withoutPatronHandlers(settings, event) {
15054
15071
  if (!changed) return { ...settings };
15055
15072
  return withHooksKey(settings, event, kept);
15056
15073
  }
15057
- function withPatronGroup(settings, event, group) {
15058
- const next = withoutPatronHandlers(settings, event);
15074
+ function withPatronGroup(settings, event, group, known = NO_COMMANDS) {
15075
+ const next = withoutPatronHandlers(settings, event, known);
15059
15076
  const hooks = isObject(next.hooks) ? next.hooks : {};
15060
15077
  const groups = Array.isArray(hooks[event]) ? hooks[event] : [];
15061
15078
  return withHooksKey(next, event, [...groups, group]);
@@ -15065,7 +15082,7 @@ function hasPatronHandler(settings, event, options = {}) {
15065
15082
  if (!Array.isArray(groups)) return false;
15066
15083
  return groups.some(
15067
15084
  (group) => isObject(group) && (options.matcher === void 0 || group.matcher === options.matcher) && Array.isArray(group.hooks) && group.hooks.some(
15068
- (handler) => isPatronHandler(handler) && (options.command === void 0 || handler.command === options.command)
15085
+ (handler) => isPatronHandler(handler, options.known) && (options.command === void 0 || handler.command === options.command)
15069
15086
  )
15070
15087
  );
15071
15088
  }
@@ -15131,10 +15148,9 @@ function codexPaths(env, home) {
15131
15148
  const root = codexHome(env, home);
15132
15149
  return { config: join(root, "config.toml"), hooks: join(root, "hooks.json") };
15133
15150
  }
15134
- function withCodexStopHook(settings, command) {
15135
- return withPatronGroup(settings, EVENT, stopGroup(command));
15151
+ function withCodexStopHook(settings, command, known) {
15152
+ return withPatronGroup(settings, EVENT, stopGroup(command), known);
15136
15153
  }
15137
- var CODEX_HOOK_STATUS = "Patron: pay the sites this turn read";
15138
15154
  function stopGroup(command) {
15139
15155
  return {
15140
15156
  hooks: [
@@ -15150,7 +15166,7 @@ function stopGroup(command) {
15150
15166
  function hasStopGroup(settings, command) {
15151
15167
  const groups = isObject(settings.hooks) ? settings.hooks[EVENT] : void 0;
15152
15168
  const wanted = JSON.stringify(stopGroup(command));
15153
- return Array.isArray(groups) && groups.some((group) => JSON.stringify(group) === wanted);
15169
+ return Array.isArray(groups) && groups.some((group) => JSON.stringify(group) === wanted) && patronCommands(settings, EVENT).length === 1;
15154
15170
  }
15155
15171
  function codexNotifyLine(command) {
15156
15172
  const argv = ["sh", "-c", `${command} "$1"`, "patron"];
@@ -15174,6 +15190,21 @@ function withCodexNotify(toml, command) {
15174
15190
  text: lines.map((value, at) => at === index ? line : value).join("\n")
15175
15191
  };
15176
15192
  }
15193
+ function codexNotifyCommand(toml) {
15194
+ const lines = toml.split("\n");
15195
+ const line = lines[topLevelNotify(lines)];
15196
+ if (!line?.includes(CODEX_NOTIFY_MARKER)) return void 0;
15197
+ const array = /=\s*(\[.*\])/.exec(
15198
+ line.slice(0, line.lastIndexOf(CODEX_NOTIFY_MARKER))
15199
+ )?.[1];
15200
+ try {
15201
+ const argv = JSON.parse(array ?? "");
15202
+ const script = Array.isArray(argv) ? argv[2] : void 0;
15203
+ return typeof script === "string" && script.endsWith(' "$1"') ? script.slice(0, -' "$1"'.length) : void 0;
15204
+ } catch {
15205
+ return void 0;
15206
+ }
15207
+ }
15177
15208
  function withoutCodexNotify(toml) {
15178
15209
  const lines = toml.split("\n");
15179
15210
  const index = topLevelNotify(lines);
@@ -15187,11 +15218,11 @@ function topLevelNotify(lines) {
15187
15218
  }
15188
15219
  return -1;
15189
15220
  }
15190
- async function installCodex(paths, command, platform = process.platform) {
15221
+ async function installCodex(paths, command, platform = process.platform, known) {
15191
15222
  const settings = await readJsonObject(paths.hooks);
15192
15223
  const hookChanged = !hasStopGroup(settings, command);
15193
15224
  if (hookChanged)
15194
- await writeJsonObject(paths.hooks, withCodexStopHook(settings, command));
15225
+ await writeJsonObject(paths.hooks, withCodexStopHook(settings, command, known));
15195
15226
  let notify = "taken";
15196
15227
  if (platform !== "win32") {
15197
15228
  const toml = await readText(paths.config);
@@ -15209,11 +15240,11 @@ async function upgradeCodex(paths) {
15209
15240
  await writeJsonObject(paths.hooks, next);
15210
15241
  return true;
15211
15242
  }
15212
- async function removeCodex(paths) {
15243
+ async function removeCodex(paths, known) {
15213
15244
  let removed = false;
15214
15245
  const settings = await readJsonObject(paths.hooks);
15215
- if (hasPatronHandler(settings, EVENT)) {
15216
- await writeJsonObject(paths.hooks, withoutPatronHandlers(settings, EVENT));
15246
+ if (hasPatronHandler(settings, EVENT, known ? { known } : {})) {
15247
+ await writeJsonObject(paths.hooks, withoutPatronHandlers(settings, EVENT, known));
15217
15248
  removed = true;
15218
15249
  }
15219
15250
  const toml = await readText(paths.config);
@@ -15239,14 +15270,19 @@ async function writeText(path, text) {
15239
15270
 
15240
15271
  // src/issuer.ts
15241
15272
  var ISSUER_TTL_MS = 60 * 60 * 1e3;
15273
+ async function storedIssuer(home, gatewayUrl, now = Date.now()) {
15274
+ const cached = await readJson(statePaths(home).issuer).catch(() => void 0);
15275
+ const parsed = IssuerInfo.safeParse(cached?.issuer);
15276
+ if (!parsed.success || cached?.gatewayUrl !== gatewayUrl) return void 0;
15277
+ const at = typeof cached.at === "number" ? cached.at : Number.NEGATIVE_INFINITY;
15278
+ return { issuer: parsed.data, stale: !(now - at < ISSUER_TTL_MS) };
15279
+ }
15242
15280
  async function loadIssuer(home, gatewayUrl, options = {}) {
15243
15281
  const now = options.now ?? Date.now();
15244
15282
  const path = statePaths(home).issuer;
15245
15283
  if (!options.fresh) {
15246
- const cached = await readJson(path).catch(() => void 0);
15247
- const parsed = IssuerInfo.safeParse(cached?.issuer);
15248
- if (parsed.success && cached?.gatewayUrl === gatewayUrl && typeof cached.at === "number" && now - cached.at < ISSUER_TTL_MS)
15249
- return parsed.data;
15284
+ const cached = await storedIssuer(home, gatewayUrl, now);
15285
+ if (cached && !cached.stale) return cached.issuer;
15250
15286
  }
15251
15287
  const issuer = await getIssuer(gatewayUrl, options.fetch);
15252
15288
  await ensureHome(home);
@@ -15370,7 +15406,9 @@ export {
15370
15406
  parseUsd,
15371
15407
  tipTokens,
15372
15408
  isObject,
15409
+ CLAUDE_HOOK_STATUS,
15373
15410
  isPatronHandler,
15411
+ patronCommands,
15374
15412
  withoutPatronHandlers,
15375
15413
  withPatronGroup,
15376
15414
  hasPatronHandler,
@@ -15380,9 +15418,11 @@ export {
15380
15418
  withPatronStatus,
15381
15419
  codexHome,
15382
15420
  codexPaths,
15421
+ codexNotifyCommand,
15383
15422
  installCodex,
15384
15423
  upgradeCodex,
15385
15424
  removeCodex,
15425
+ storedIssuer,
15386
15426
  loadIssuer,
15387
15427
  FileTokenStore
15388
15428
  };
@@ -960,6 +960,203 @@ function normalizePayee(input) {
960
960
  return normalizeDomain(trimmed);
961
961
  }
962
962
 
963
+ // src/lock.ts
964
+ import { open, rm, stat } from "node:fs/promises";
965
+ async function withLock(path, fn, options = {}) {
966
+ const deadline = Date.now() + (options.timeoutMs ?? 3e3);
967
+ const staleMs = options.staleMs ?? 1e4;
968
+ for (; ; ) {
969
+ try {
970
+ const handle = await open(path, "wx", 384);
971
+ await handle.close();
972
+ break;
973
+ } catch (error) {
974
+ if (error.code !== "EEXIST") throw error;
975
+ const age = await stat(path).then(
976
+ (stats) => Date.now() - stats.mtimeMs,
977
+ () => 0
978
+ );
979
+ if (age > staleMs) {
980
+ await rm(path, { force: true });
981
+ continue;
982
+ }
983
+ if (Date.now() > deadline) throw new Error(`Patron state is busy: ${path}`);
984
+ await new Promise((resolve2) => setTimeout(resolve2, 10 + Math.random() * 20));
985
+ }
986
+ }
987
+ try {
988
+ return await fn();
989
+ } finally {
990
+ await rm(path, { force: true });
991
+ }
992
+ }
993
+ async function tryLock(path, staleMs) {
994
+ try {
995
+ const handle = await open(path, "wx", 384);
996
+ await handle.close();
997
+ } catch (error) {
998
+ if (error.code !== "EEXIST") throw error;
999
+ const age = await stat(path).then(
1000
+ (stats) => Date.now() - stats.mtimeMs,
1001
+ () => 0
1002
+ );
1003
+ if (age <= staleMs) return void 0;
1004
+ await rm(path, { force: true });
1005
+ return tryLock(path, staleMs);
1006
+ }
1007
+ return () => rm(path, { force: true });
1008
+ }
1009
+
1010
+ // src/state.ts
1011
+ import { randomBytes } from "node:crypto";
1012
+ import {
1013
+ chmod,
1014
+ lstat,
1015
+ mkdir,
1016
+ readFile,
1017
+ readlink,
1018
+ rename,
1019
+ rm as rm2,
1020
+ writeFile
1021
+ } from "node:fs/promises";
1022
+ import { dirname, join, resolve } from "node:path";
1023
+ var AGENT_IDS = ["claude-code", "codex", "cursor", "amp"];
1024
+ function statePaths(home) {
1025
+ return {
1026
+ config: join(home, "config.json"),
1027
+ issuer: join(home, "issuer.json"),
1028
+ key: join(home, "key"),
1029
+ linkRefill: join(home, "link-refill.json"),
1030
+ purchaseLock: join(home, "purchase.lock"),
1031
+ refillLock: join(home, "refill.lock"),
1032
+ refillWait: join(home, "refill-wait.lock"),
1033
+ spacing: join(home, "spacing.json"),
1034
+ spacingLock: join(home, "spacing.lock"),
1035
+ stateLock: join(home, "state.lock"),
1036
+ tokens: join(home, "tokens.enc"),
1037
+ usage: join(home, "usage.json"),
1038
+ usageLock: join(home, "usage.lock"),
1039
+ wallet: join(home, "wallet.json")
1040
+ };
1041
+ }
1042
+ function monthOf(now) {
1043
+ return `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`;
1044
+ }
1045
+ async function ensureHome(home) {
1046
+ await mkdir(home, { mode: 448, recursive: true });
1047
+ }
1048
+ async function readJson(path) {
1049
+ try {
1050
+ return JSON.parse(await readFile(path, "utf8"));
1051
+ } catch (error) {
1052
+ if (error.code === "ENOENT") return void 0;
1053
+ throw error;
1054
+ }
1055
+ }
1056
+ async function writePrivateFile(link, contents) {
1057
+ const target = await symlinkTarget(link);
1058
+ if (target !== link) {
1059
+ try {
1060
+ return await replaceFile(target, contents);
1061
+ } catch (error) {
1062
+ const code = error.code;
1063
+ if (code !== "EACCES" && code !== "EROFS" && code !== "EPERM") throw error;
1064
+ }
1065
+ }
1066
+ await replaceFile(link, contents);
1067
+ }
1068
+ async function symlinkTarget(path, depth = 0) {
1069
+ if (depth > 8) return path;
1070
+ try {
1071
+ const stats = await lstat(path);
1072
+ if (!stats.isSymbolicLink()) return path;
1073
+ return symlinkTarget(resolve(dirname(path), await readlink(path)), depth + 1);
1074
+ } catch {
1075
+ return path;
1076
+ }
1077
+ }
1078
+ async function replaceFile(path, contents) {
1079
+ const temporary = `${path}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`;
1080
+ try {
1081
+ await writeFile(temporary, contents, { mode: 384 });
1082
+ await chmod(temporary, 384);
1083
+ await rename(temporary, path);
1084
+ } catch (error) {
1085
+ await rm2(temporary, { force: true }).catch(() => void 0);
1086
+ throw error;
1087
+ }
1088
+ }
1089
+ async function readConfig(home) {
1090
+ const value = await readJson(statePaths(home).config).catch(() => void 0);
1091
+ if (!value || typeof value !== "object") return void 0;
1092
+ const config = value;
1093
+ if (config.version !== 1 || typeof config.address !== "string") return void 0;
1094
+ return {
1095
+ agents: Array.isArray(config.agents) ? config.agents.filter(
1096
+ (agent) => AGENT_IDS.includes(agent)
1097
+ ) : [],
1098
+ address: config.address,
1099
+ gatewayUrl: String(config.gatewayUrl ?? ""),
1100
+ monthlyBudgetUsd: Number(config.monthlyBudgetUsd ?? 0),
1101
+ network: config.network === "testnet" ? "testnet" : "mainnet",
1102
+ paused: config.paused === true,
1103
+ relayUrl: String(config.relayUrl ?? ""),
1104
+ version: 1,
1105
+ wallet: config.wallet === "key" || config.wallet === "link" ? config.wallet : "tempo",
1106
+ ...typeof config.linkPaymentMethodId === "string" ? { linkPaymentMethodId: config.linkPaymentMethodId } : {},
1107
+ ...typeof config.name === "string" ? { name: config.name } : {},
1108
+ ...typeof config.tokensPerPage === "number" ? { tokensPerPage: config.tokensPerPage } : {}
1109
+ };
1110
+ }
1111
+ async function writeConfig(home, config) {
1112
+ await ensureHome(home);
1113
+ await writePrivateFile(
1114
+ statePaths(home).config,
1115
+ `${JSON.stringify(config, null, " ")}
1116
+ `
1117
+ );
1118
+ }
1119
+ function emptyUsage(month) {
1120
+ return { boughtTokens: 0, domains: {}, month, paidPages: 0, tippedTokens: 0 };
1121
+ }
1122
+ async function readUsage(home, now = /* @__PURE__ */ new Date()) {
1123
+ const month = monthOf(now);
1124
+ const path = statePaths(home).usage;
1125
+ const value = await readJson(path).catch(() => {
1126
+ throw unreadableUsage(path);
1127
+ });
1128
+ if (value === void 0) return emptyUsage(month);
1129
+ if (!isUsageRecord(value)) throw unreadableUsage(path);
1130
+ if (value.month !== month) return emptyUsage(month);
1131
+ return {
1132
+ boughtTokens: value.boughtTokens,
1133
+ domains: value.domains && typeof value.domains === "object" ? { ...value.domains } : {},
1134
+ month,
1135
+ paidPages: Number(value.paidPages ?? 0),
1136
+ tippedTokens: Number(value.tippedTokens ?? 0)
1137
+ };
1138
+ }
1139
+ function isUsageRecord(value) {
1140
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
1141
+ const usage = value;
1142
+ return typeof usage.month === "string" && typeof usage.boughtTokens === "number" && Number.isFinite(usage.boughtTokens) && usage.boughtTokens >= 0;
1143
+ }
1144
+ function unreadableUsage(path) {
1145
+ return new Error(
1146
+ `${path} is unreadable, so Patron stops adding to your balance. Delete it to restart this month's budget count.`
1147
+ );
1148
+ }
1149
+ async function updateUsage(home, update, now = /* @__PURE__ */ new Date()) {
1150
+ await ensureHome(home);
1151
+ return withLock(statePaths(home).usageLock, async () => {
1152
+ const usage = await readUsage(home, now);
1153
+ update(usage);
1154
+ await writePrivateFile(statePaths(home).usage, `${JSON.stringify(usage)}
1155
+ `);
1156
+ return usage;
1157
+ });
1158
+ }
1159
+
963
1160
  // ../protocol/src/encoding.ts
964
1161
  function toBase64Url(bytes) {
965
1162
  let binary = "";
@@ -1164,53 +1361,6 @@ function tokensToUnits(tokens) {
1164
1361
  return BigInt(tokens) * TOKEN_PRICE_UNITS;
1165
1362
  }
1166
1363
 
1167
- // src/lock.ts
1168
- import { open, rm, stat } from "node:fs/promises";
1169
- async function withLock(path, fn, options = {}) {
1170
- const deadline = Date.now() + (options.timeoutMs ?? 3e3);
1171
- const staleMs = options.staleMs ?? 1e4;
1172
- for (; ; ) {
1173
- try {
1174
- const handle = await open(path, "wx", 384);
1175
- await handle.close();
1176
- break;
1177
- } catch (error) {
1178
- if (error.code !== "EEXIST") throw error;
1179
- const age = await stat(path).then(
1180
- (stats) => Date.now() - stats.mtimeMs,
1181
- () => 0
1182
- );
1183
- if (age > staleMs) {
1184
- await rm(path, { force: true });
1185
- continue;
1186
- }
1187
- if (Date.now() > deadline) throw new Error(`Patron state is busy: ${path}`);
1188
- await new Promise((resolve) => setTimeout(resolve, 10 + Math.random() * 20));
1189
- }
1190
- }
1191
- try {
1192
- return await fn();
1193
- } finally {
1194
- await rm(path, { force: true });
1195
- }
1196
- }
1197
- async function tryLock(path, staleMs) {
1198
- try {
1199
- const handle = await open(path, "wx", 384);
1200
- await handle.close();
1201
- } catch (error) {
1202
- if (error.code !== "EEXIST") throw error;
1203
- const age = await stat(path).then(
1204
- (stats) => Date.now() - stats.mtimeMs,
1205
- () => 0
1206
- );
1207
- if (age <= staleMs) return void 0;
1208
- await rm(path, { force: true });
1209
- return tryLock(path, staleMs);
1210
- }
1211
- return () => rm(path, { force: true });
1212
- }
1213
-
1214
1364
  export {
1215
1365
  TOKEN_PRICE_UNITS,
1216
1366
  PACK_SIZE,
@@ -1229,5 +1379,14 @@ export {
1229
1379
  formatUsd,
1230
1380
  tokensToUnits,
1231
1381
  withLock,
1232
- tryLock
1382
+ tryLock,
1383
+ AGENT_IDS,
1384
+ statePaths,
1385
+ ensureHome,
1386
+ readJson,
1387
+ writePrivateFile,
1388
+ readConfig,
1389
+ writeConfig,
1390
+ readUsage,
1391
+ updateUsage
1233
1392
  };
@@ -2,16 +2,22 @@ import { createRequire as __patronCreateRequire } from 'node:module'
2
2
  const require = __patronCreateRequire(import.meta.url)
3
3
 
4
4
  // src/settings.ts
5
+ import { existsSync } from "node:fs";
5
6
  import { homedir } from "node:os";
6
7
  import { join } from "node:path";
7
8
  var DEFAULT_GATEWAY_URL = "https://patron-gateway.tempo-dev.workers.dev";
8
9
  var DEFAULT_RELAY_URL = "https://patron-relay.tempo-dev.workers.dev";
9
- function resolveSettings(env = process.env) {
10
+ function inferHome(script) {
11
+ const match = script ? /^(.+)[/\\]cli[/\\][^/\\]+[/\\]dist[/\\]bin\.js$/.exec(script) : null;
12
+ const home = match?.[1];
13
+ return home && existsSync(join(home, "cli", "current")) ? home : void 0;
14
+ }
15
+ function resolveSettings(env = process.env, script = process.argv[1]) {
10
16
  const key = env.PATRON_PRIVATE_KEY?.trim();
11
17
  const privateKey = key && /^0x[0-9a-fA-F]{64}$/.test(key) ? key : void 0;
12
18
  return {
13
19
  gatewayUrl: env.PATRON_GATEWAY_URL || DEFAULT_GATEWAY_URL,
14
- home: env.PATRON_HOME || join(homedir(), ".patron"),
20
+ home: env.PATRON_HOME || inferHome(script) || join(homedir(), ".patron"),
15
21
  network: env.PATRON_NETWORK === "testnet" ? "testnet" : "mainnet",
16
22
  overridden: {
17
23
  gatewayUrl: Boolean(env.PATRON_GATEWAY_URL),
@@ -32,6 +38,7 @@ function settingsFor(settings, saved) {
32
38
  }
33
39
 
34
40
  export {
41
+ inferHome,
35
42
  resolveSettings,
36
43
  settingsFor
37
44
  };