@tempoxyz/patron 0.0.12 → 0.0.13

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 CHANGED
@@ -4,10 +4,10 @@ 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-2Y5LU62B.js");
7
+ const { runHookProcess } = await import("./chunks/hook-process-TN5MFLK5.js");
8
8
  await runHookProcess().catch(() => void 0);
9
9
  process.exit(0);
10
10
  } else {
11
- const { run } = await import("./chunks/-KL2FSIN2.js");
11
+ const { run } = await import("./chunks/-ALBR7TS6.js");
12
12
  process.exitCode = await run(process.argv.slice(2));
13
13
  }
@@ -13,34 +13,33 @@ import {
13
13
  budgetTokens,
14
14
  buyTokens,
15
15
  clampTokensPerPage,
16
+ codexHome,
17
+ codexPaths,
16
18
  formatUsd,
19
+ hasPatronHandler,
20
+ installCodex,
21
+ isObject,
22
+ isPatronHandler,
17
23
  loadIssuer,
18
24
  needsRefill,
19
25
  normalizePayee,
20
26
  parseUsd,
21
27
  profileMessage,
22
28
  purchasableTokens,
29
+ readJsonObject,
23
30
  redeem,
31
+ removeCodex,
24
32
  resolveSettings,
25
33
  settingsFor,
26
34
  startRefill,
27
35
  tipTokens,
28
- tokensToUnits
29
- } from "./chunk-EDGGHF4L.js";
30
- import {
31
- codexHome,
32
- codexPaths,
33
- hasPatronHandler,
34
- installCodex,
35
- isObject,
36
- isPatronHandler,
37
- readJsonObject,
38
- removeCodex,
36
+ tokensToUnits,
37
+ upgradeCodex,
39
38
  withHooksKey,
40
39
  withPatronGroup,
41
40
  withoutPatronHandlers,
42
41
  writeJsonObject
43
- } from "./chunk-E4XN5ZK6.js";
42
+ } from "./chunk-5VAR4V2W.js";
44
43
  import {
45
44
  AGENT_IDS,
46
45
  ensureHome,
@@ -116,6 +115,15 @@ async function installAmpPlugin(path, command) {
116
115
  await writePrivateFile(path, source);
117
116
  return "installed";
118
117
  }
118
+ async function upgradeAmpPlugin(path) {
119
+ const current = await readText(path);
120
+ if (current === void 0 || !current.startsWith(AMP_PLUGIN_MARKER)) return false;
121
+ const literal = /^const COMMAND = (".*")$/m.exec(current)?.[1];
122
+ if (!literal) return false;
123
+ const command = JSON.parse(literal);
124
+ if (typeof command !== "string") return false;
125
+ return await installAmpPlugin(path, command) === "installed";
126
+ }
119
127
  async function removeAmpPlugin(path) {
120
128
  const current = await readText(path);
121
129
  if (current === void 0 || !current.startsWith(AMP_PLUGIN_MARKER)) return "missing";
@@ -135,11 +143,41 @@ async function readText(path) {
135
143
  var CLAUDE_MATCHER = "WebFetch|WebSearch";
136
144
  var HOOK_TIMEOUT_SECONDS = 30;
137
145
  var EVENT = "PostToolUse";
138
- function withPatronHook(settings, command) {
139
- return withPatronGroup(settings, EVENT, {
140
- hooks: [{ async: true, command, timeout: HOOK_TIMEOUT_SECONDS, type: "command" }],
146
+ var CLAUDE_HOOK_STATUS = "Patron: pay the site this read";
147
+ function patronGroup(command) {
148
+ return {
149
+ hooks: [
150
+ {
151
+ async: true,
152
+ command,
153
+ statusMessage: CLAUDE_HOOK_STATUS,
154
+ timeout: HOOK_TIMEOUT_SECONDS,
155
+ type: "command"
156
+ }
157
+ ],
141
158
  matcher: CLAUDE_MATCHER
142
- });
159
+ };
160
+ }
161
+ function withPatronHook(settings, command) {
162
+ return withPatronGroup(settings, EVENT, patronGroup(command));
163
+ }
164
+ function patronCommand(settings) {
165
+ const groups = isObject(settings.hooks) ? settings.hooks[EVENT] : void 0;
166
+ const handler = (Array.isArray(groups) ? groups : []).flatMap(
167
+ (group) => isObject(group) && Array.isArray(group.hooks) ? group.hooks : []
168
+ ).find(isPatronHandler);
169
+ return typeof handler?.command === "string" ? handler.command : void 0;
170
+ }
171
+ async function upgradeClaudeHook(path) {
172
+ const settings = await readJsonObject(path);
173
+ const command = patronCommand(settings);
174
+ if (!command) return false;
175
+ const groups = isObject(settings.hooks) ? settings.hooks[EVENT] : void 0;
176
+ const wanted = JSON.stringify(patronGroup(command));
177
+ if (Array.isArray(groups) && groups.some((group) => JSON.stringify(group) === wanted))
178
+ return false;
179
+ await writeJsonObject(path, withPatronHook(settings, command));
180
+ return true;
143
181
  }
144
182
  function withoutPatronHook(settings) {
145
183
  return withoutPatronHandlers(settings, EVENT);
@@ -303,7 +341,8 @@ var ADAPTERS = {
303
341
  install: async (command, { env, home }) => ({
304
342
  state: await installAmpPlugin(ampPluginPath(env, home), command)
305
343
  }),
306
- remove: ({ env, home }) => removeAmpPlugin(ampPluginPath(env, home))
344
+ remove: ({ env, home }) => removeAmpPlugin(ampPluginPath(env, home)),
345
+ upgrade: ({ env, home }) => upgradeAmpPlugin(ampPluginPath(env, home))
307
346
  },
308
347
  "claude-code": {
309
348
  files: (environment) => [claudeSettingsPath(environment)],
@@ -311,13 +350,15 @@ var ADAPTERS = {
311
350
  install: async (command, environment) => ({
312
351
  state: await installClaudeHook(claudeSettingsPath(environment), command)
313
352
  }),
314
- remove: (environment) => removeClaudeHook(claudeSettingsPath(environment))
353
+ remove: (environment) => removeClaudeHook(claudeSettingsPath(environment)),
354
+ upgrade: (environment) => upgradeClaudeHook(claudeSettingsPath(environment))
315
355
  },
316
356
  codex: {
317
357
  files: ({ env, home }) => Object.values(codexPaths(env, home)),
318
358
  detect: async ({ env, home }) => await exists(codexHome(env, home)) || await onPath(["codex"], env.PATH ?? ""),
319
359
  install: (command, { env, home }) => installCodex(codexPaths(env, home), command),
320
- remove: ({ env, home }) => removeCodex(codexPaths(env, home))
360
+ remove: ({ env, home }) => removeCodex(codexPaths(env, home)),
361
+ upgrade: ({ env, home }) => upgradeCodex(codexPaths(env, home))
321
362
  },
322
363
  cursor: {
323
364
  files: ({ home }) => [cursorHooksPath(home)],
@@ -359,6 +400,14 @@ async function installAgents(agents, command, environment) {
359
400
  })
360
401
  );
361
402
  }
403
+ async function upgradeAgents(agents, environment) {
404
+ const changed = await Promise.all(
405
+ agents.map(
406
+ async (agent) => await ADAPTERS[agent].upgrade?.(environment).catch(() => false) ? agent : void 0
407
+ )
408
+ );
409
+ return changed.filter((agent) => agent !== void 0);
410
+ }
362
411
  async function removeAgents(environment) {
363
412
  const store = originalsStore(environment);
364
413
  return Promise.all(
@@ -800,13 +849,13 @@ async function main(deps) {
800
849
  await status(deps, deps.interactive);
801
850
  }
802
851
  async function syncAgents(deps, config) {
803
- if (config.agents.includes("codex")) {
804
- const { codexPaths: codexPaths2, upgradeCodex } = await import("./codex-UEAW7Y4U.js");
805
- const paths = codexPaths2(deps.agents.env, deps.agents.home);
806
- if (await upgradeCodex(paths).catch(() => false)) {
807
- say(deps, "Updated Patron\u2019s Codex hook; trust it again in /hooks when Codex asks.");
808
- say(deps);
809
- }
852
+ const upgraded = await upgradeAgents(config.agents, deps.agents);
853
+ if (upgraded.length > 0) {
854
+ const names = upgraded.map((agent) => AGENT_LABELS[agent]).join(", ");
855
+ say(deps, `Updated Patron for ${names}.`);
856
+ if (upgraded.includes("codex"))
857
+ say(deps, " Codex: trust the hook again in /hooks when it asks.");
858
+ say(deps);
810
859
  }
811
860
  const added = (await detectAgents(deps.agents)).filter(
812
861
  (agent) => !config.agents.includes(agent)
@@ -12270,7 +12270,7 @@ var $ZodObject = /* @__PURE__ */ $constructor("$ZodObject", (inst, def) => {
12270
12270
  }
12271
12271
  return propValues;
12272
12272
  });
12273
- const isObject2 = isObject;
12273
+ const isObject3 = isObject;
12274
12274
  const catchall = def.catchall;
12275
12275
  let value;
12276
12276
  const memo2 = globalConfig.memoizer;
@@ -12278,7 +12278,7 @@ var $ZodObject = /* @__PURE__ */ $constructor("$ZodObject", (inst, def) => {
12278
12278
  inst._zod.parse = (payload, ctx) => {
12279
12279
  value ?? (value = _normalized.value);
12280
12280
  const input2 = payload.value;
12281
- if (!isObject2(input2)) {
12281
+ if (!isObject3(input2)) {
12282
12282
  payload.issues.push({
12283
12283
  expected: "object",
12284
12284
  code: "invalid_type",
@@ -12414,7 +12414,7 @@ var $ZodObjectJIT = /* @__PURE__ */ $constructor("$ZodObjectJIT", (inst, def) =>
12414
12414
  return doc.compile();
12415
12415
  };
12416
12416
  let fastpass;
12417
- const isObject2 = isObject;
12417
+ const isObject3 = isObject;
12418
12418
  const jit = !globalConfig.jitless;
12419
12419
  const allowsEval2 = allowsEval;
12420
12420
  const fastEnabled = jit && allowsEval2.value;
@@ -12423,7 +12423,7 @@ var $ZodObjectJIT = /* @__PURE__ */ $constructor("$ZodObjectJIT", (inst, def) =>
12423
12423
  inst._zod.parse = (payload, ctx) => {
12424
12424
  value ?? (value = _normalized.value);
12425
12425
  const input2 = payload.value;
12426
- if (!isObject2(input2)) {
12426
+ if (!isObject3(input2)) {
12427
12427
  payload.issues.push({
12428
12428
  expected: "object",
12429
12429
  code: "invalid_type",
@@ -35691,6 +35691,209 @@ function startRefill(script = process.argv[1]) {
35691
35691
  }
35692
35692
  }
35693
35693
 
35694
+ // src/codex.ts
35695
+ import { mkdir as mkdir2, readFile as readFile3 } from "node:fs/promises";
35696
+ import { dirname as dirname2, join as join2 } from "node:path";
35697
+
35698
+ // src/hook-files.ts
35699
+ import { mkdir, readFile as readFile2 } from "node:fs/promises";
35700
+ import { dirname } from "node:path";
35701
+ function isObject2(value) {
35702
+ return typeof value === "object" && value !== null && !Array.isArray(value);
35703
+ }
35704
+ function isPatronHandler(handler) {
35705
+ if (!isObject2(handler) || typeof handler.command !== "string") return false;
35706
+ return /patron/i.test(handler.command) && /\shook\s*$/.test(handler.command);
35707
+ }
35708
+ function withoutPatronHandlers(settings, event) {
35709
+ const groups = isObject2(settings.hooks) ? settings.hooks[event] : void 0;
35710
+ if (!isObject2(settings.hooks) || !Array.isArray(groups)) return { ...settings };
35711
+ let changed = false;
35712
+ const kept = [];
35713
+ for (const group of groups) {
35714
+ if (!isObject2(group) || !Array.isArray(group.hooks)) {
35715
+ kept.push(group);
35716
+ continue;
35717
+ }
35718
+ const handlers = group.hooks.filter((handler) => !isPatronHandler(handler));
35719
+ if (handlers.length === group.hooks.length) kept.push(group);
35720
+ else {
35721
+ changed = true;
35722
+ if (handlers.length > 0) kept.push({ ...group, hooks: handlers });
35723
+ }
35724
+ }
35725
+ if (!changed) return { ...settings };
35726
+ return withHooksKey(settings, event, kept);
35727
+ }
35728
+ function withPatronGroup(settings, event, group) {
35729
+ const next = withoutPatronHandlers(settings, event);
35730
+ const hooks = isObject2(next.hooks) ? next.hooks : {};
35731
+ const groups = Array.isArray(hooks[event]) ? hooks[event] : [];
35732
+ return withHooksKey(next, event, [...groups, group]);
35733
+ }
35734
+ function hasPatronHandler(settings, event, options = {}) {
35735
+ const groups = isObject2(settings.hooks) ? settings.hooks[event] : void 0;
35736
+ if (!Array.isArray(groups)) return false;
35737
+ return groups.some(
35738
+ (group) => isObject2(group) && (options.matcher === void 0 || group.matcher === options.matcher) && Array.isArray(group.hooks) && group.hooks.some(
35739
+ (handler) => isPatronHandler(handler) && (options.command === void 0 || handler.command === options.command)
35740
+ )
35741
+ );
35742
+ }
35743
+ function withHooksKey(settings, event, entries) {
35744
+ const hooks = isObject2(settings.hooks) ? { ...settings.hooks } : {};
35745
+ if (entries.length > 0) hooks[event] = entries;
35746
+ else delete hooks[event];
35747
+ const next = { ...settings };
35748
+ if (Object.keys(hooks).length > 0) next.hooks = hooks;
35749
+ else delete next.hooks;
35750
+ return next;
35751
+ }
35752
+ async function readJsonObject(path) {
35753
+ let text;
35754
+ try {
35755
+ text = await readFile2(path, "utf8");
35756
+ } catch (error62) {
35757
+ if (error62.code === "ENOENT") return {};
35758
+ throw error62;
35759
+ }
35760
+ if (text.trim() === "") return {};
35761
+ let value;
35762
+ try {
35763
+ value = JSON.parse(text);
35764
+ } catch {
35765
+ throw new Error(`${path} is not valid JSON; fix it, then rerun patron.`);
35766
+ }
35767
+ if (!isObject2(value)) throw new Error(`${path} is not a JSON object; left unchanged.`);
35768
+ return value;
35769
+ }
35770
+ async function writeJsonObject(path, value) {
35771
+ await mkdir(dirname(path), { recursive: true });
35772
+ await writePrivateFile(path, `${JSON.stringify(value, null, 2)}
35773
+ `);
35774
+ }
35775
+
35776
+ // src/codex.ts
35777
+ var CODEX_NOTIFY_MARKER = "# added by patron";
35778
+ var HOOK_TIMEOUT_SECONDS = 10;
35779
+ var EVENT = "Stop";
35780
+ function codexHome(env, home) {
35781
+ return env.CODEX_HOME || join2(home, ".codex");
35782
+ }
35783
+ function codexPaths(env, home) {
35784
+ const root = codexHome(env, home);
35785
+ return { config: join2(root, "config.toml"), hooks: join2(root, "hooks.json") };
35786
+ }
35787
+ function withCodexStopHook(settings, command) {
35788
+ return withPatronGroup(settings, EVENT, stopGroup(command));
35789
+ }
35790
+ var CODEX_HOOK_STATUS = "Patron: pay the sites this turn read";
35791
+ function stopGroup(command) {
35792
+ return {
35793
+ hooks: [
35794
+ {
35795
+ command,
35796
+ statusMessage: CODEX_HOOK_STATUS,
35797
+ timeout: HOOK_TIMEOUT_SECONDS,
35798
+ type: "command"
35799
+ }
35800
+ ]
35801
+ };
35802
+ }
35803
+ function hasStopGroup(settings, command) {
35804
+ const groups = isObject2(settings.hooks) ? settings.hooks[EVENT] : void 0;
35805
+ const wanted = JSON.stringify(stopGroup(command));
35806
+ return Array.isArray(groups) && groups.some((group) => JSON.stringify(group) === wanted);
35807
+ }
35808
+ function codexNotifyLine(command) {
35809
+ const argv = ["sh", "-c", `${command} "$1"`, "patron"];
35810
+ return `notify = [${argv.map((arg) => JSON.stringify(arg)).join(", ")}] ${CODEX_NOTIFY_MARKER}`;
35811
+ }
35812
+ function withCodexNotify(toml, command) {
35813
+ const lines = toml === "" ? [] : toml.split("\n");
35814
+ const line = codexNotifyLine(command);
35815
+ const index = topLevelNotify(lines);
35816
+ if (index === -1)
35817
+ return {
35818
+ state: "installed",
35819
+ text: lines.length === 0 ? `${line}
35820
+ ` : [line, ...lines].join("\n")
35821
+ };
35822
+ const current = lines[index] ?? "";
35823
+ if (!current.includes(CODEX_NOTIFY_MARKER)) return { state: "taken", text: toml };
35824
+ if (current.trimEnd() === line) return { state: "unchanged", text: toml };
35825
+ return {
35826
+ state: "installed",
35827
+ text: lines.map((value, at) => at === index ? line : value).join("\n")
35828
+ };
35829
+ }
35830
+ function withoutCodexNotify(toml) {
35831
+ const lines = toml.split("\n");
35832
+ const index = topLevelNotify(lines);
35833
+ if (index === -1 || !lines[index]?.includes(CODEX_NOTIFY_MARKER)) return void 0;
35834
+ return lines.filter((_, at) => at !== index).join("\n");
35835
+ }
35836
+ function topLevelNotify(lines) {
35837
+ for (const [index, line] of lines.entries()) {
35838
+ if (/^\s*\[/.test(line)) return -1;
35839
+ if (/^\s*notify\s*=/.test(line)) return index;
35840
+ }
35841
+ return -1;
35842
+ }
35843
+ async function installCodex(paths, command, platform = process.platform) {
35844
+ const settings = await readJsonObject(paths.hooks);
35845
+ const hookChanged = !hasStopGroup(settings, command);
35846
+ if (hookChanged)
35847
+ await writeJsonObject(paths.hooks, withCodexStopHook(settings, command));
35848
+ let notify = "taken";
35849
+ if (platform !== "win32") {
35850
+ const toml = await readText(paths.config);
35851
+ const edit = withCodexNotify(toml, command);
35852
+ notify = edit.state;
35853
+ if (edit.state === "installed") await writeText(paths.config, edit.text);
35854
+ }
35855
+ const state = hookChanged || notify === "installed" ? "installed" : "unchanged";
35856
+ return notify === "taken" ? { note: "open /hooks in Codex and trust it to start paying", state } : { note: "trust it in /hooks when Codex asks", state };
35857
+ }
35858
+ async function upgradeCodex(paths) {
35859
+ const settings = await readJsonObject(paths.hooks);
35860
+ const groups = isObject2(settings.hooks) ? settings.hooks[EVENT] : void 0;
35861
+ const handler = (Array.isArray(groups) ? groups : []).flatMap(
35862
+ (group) => isObject2(group) && Array.isArray(group.hooks) ? group.hooks : []
35863
+ ).find(isPatronHandler);
35864
+ const command = typeof handler?.command === "string" ? handler.command : void 0;
35865
+ if (!command || hasStopGroup(settings, command)) return false;
35866
+ await writeJsonObject(paths.hooks, withCodexStopHook(settings, command));
35867
+ return true;
35868
+ }
35869
+ async function removeCodex(paths) {
35870
+ let removed = false;
35871
+ const settings = await readJsonObject(paths.hooks);
35872
+ if (hasPatronHandler(settings, EVENT)) {
35873
+ await writeJsonObject(paths.hooks, withoutPatronHandlers(settings, EVENT));
35874
+ removed = true;
35875
+ }
35876
+ const toml = await readText(paths.config);
35877
+ const without = withoutCodexNotify(toml);
35878
+ if (without !== void 0) {
35879
+ await writeText(paths.config, without);
35880
+ removed = true;
35881
+ }
35882
+ return removed ? "removed" : "missing";
35883
+ }
35884
+ async function readText(path) {
35885
+ try {
35886
+ return await readFile3(path, "utf8");
35887
+ } catch (error62) {
35888
+ if (error62.code === "ENOENT") return "";
35889
+ throw error62;
35890
+ }
35891
+ }
35892
+ async function writeText(path, text) {
35893
+ await mkdir2(dirname2(path), { recursive: true });
35894
+ await writePrivateFile(path, text);
35895
+ }
35896
+
35694
35897
  export {
35695
35898
  TOKEN_PRICE_UNITS,
35696
35899
  PACK_SIZE,
@@ -35714,6 +35917,19 @@ export {
35714
35917
  needsRefill,
35715
35918
  parseUsd,
35716
35919
  tipTokens,
35920
+ isObject2 as isObject,
35921
+ isPatronHandler,
35922
+ withoutPatronHandlers,
35923
+ withPatronGroup,
35924
+ hasPatronHandler,
35925
+ withHooksKey,
35926
+ readJsonObject,
35927
+ writeJsonObject,
35928
+ codexHome,
35929
+ codexPaths,
35930
+ installCodex,
35931
+ upgradeCodex,
35932
+ removeCodex,
35717
35933
  loadIssuer,
35718
35934
  resolveSettings,
35719
35935
  settingsFor,
@@ -3,6 +3,7 @@ const require = __patronCreateRequire(import.meta.url)
3
3
  import {
4
4
  FileTokenStore,
5
5
  clampTokensPerPage,
6
+ codexHome,
6
7
  loadIssuer,
7
8
  needsRefill,
8
9
  normalizePayee,
@@ -11,10 +12,7 @@ import {
11
12
  settingsFor,
12
13
  spend,
13
14
  startRefill
14
- } from "./chunk-EDGGHF4L.js";
15
- import {
16
- codexHome
17
- } from "./chunk-E4XN5ZK6.js";
15
+ } from "./chunk-5VAR4V2W.js";
18
16
  import {
19
17
  readConfig,
20
18
  readUsage,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tempoxyz/patron",
3
- "version": "0.0.12",
3
+ "version": "0.0.13",
4
4
  "description": "Pay the websites your AI agents read, privately",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,230 +0,0 @@
1
- import { createRequire as __patronCreateRequire } from 'node:module'
2
- const require = __patronCreateRequire(import.meta.url)
3
- import {
4
- writePrivateFile
5
- } from "./chunk-6TTNL3HT.js";
6
-
7
- // src/codex.ts
8
- import { mkdir as mkdir2, readFile as readFile2 } from "node:fs/promises";
9
- import { dirname as dirname2, join } from "node:path";
10
-
11
- // src/hook-files.ts
12
- import { mkdir, readFile } from "node:fs/promises";
13
- import { dirname } from "node:path";
14
- function isObject(value) {
15
- return typeof value === "object" && value !== null && !Array.isArray(value);
16
- }
17
- function isPatronHandler(handler) {
18
- if (!isObject(handler) || typeof handler.command !== "string") return false;
19
- return /patron/i.test(handler.command) && /\shook\s*$/.test(handler.command);
20
- }
21
- function withoutPatronHandlers(settings, event) {
22
- const groups = isObject(settings.hooks) ? settings.hooks[event] : void 0;
23
- if (!isObject(settings.hooks) || !Array.isArray(groups)) return { ...settings };
24
- let changed = false;
25
- const kept = [];
26
- for (const group of groups) {
27
- if (!isObject(group) || !Array.isArray(group.hooks)) {
28
- kept.push(group);
29
- continue;
30
- }
31
- const handlers = group.hooks.filter((handler) => !isPatronHandler(handler));
32
- if (handlers.length === group.hooks.length) kept.push(group);
33
- else {
34
- changed = true;
35
- if (handlers.length > 0) kept.push({ ...group, hooks: handlers });
36
- }
37
- }
38
- if (!changed) return { ...settings };
39
- return withHooksKey(settings, event, kept);
40
- }
41
- function withPatronGroup(settings, event, group) {
42
- const next = withoutPatronHandlers(settings, event);
43
- const hooks = isObject(next.hooks) ? next.hooks : {};
44
- const groups = Array.isArray(hooks[event]) ? hooks[event] : [];
45
- return withHooksKey(next, event, [...groups, group]);
46
- }
47
- function hasPatronHandler(settings, event, options = {}) {
48
- const groups = isObject(settings.hooks) ? settings.hooks[event] : void 0;
49
- if (!Array.isArray(groups)) return false;
50
- return groups.some(
51
- (group) => isObject(group) && (options.matcher === void 0 || group.matcher === options.matcher) && Array.isArray(group.hooks) && group.hooks.some(
52
- (handler) => isPatronHandler(handler) && (options.command === void 0 || handler.command === options.command)
53
- )
54
- );
55
- }
56
- function withHooksKey(settings, event, entries) {
57
- const hooks = isObject(settings.hooks) ? { ...settings.hooks } : {};
58
- if (entries.length > 0) hooks[event] = entries;
59
- else delete hooks[event];
60
- const next = { ...settings };
61
- if (Object.keys(hooks).length > 0) next.hooks = hooks;
62
- else delete next.hooks;
63
- return next;
64
- }
65
- async function readJsonObject(path) {
66
- let text;
67
- try {
68
- text = await readFile(path, "utf8");
69
- } catch (error) {
70
- if (error.code === "ENOENT") return {};
71
- throw error;
72
- }
73
- if (text.trim() === "") return {};
74
- let value;
75
- try {
76
- value = JSON.parse(text);
77
- } catch {
78
- throw new Error(`${path} is not valid JSON; fix it, then rerun patron.`);
79
- }
80
- if (!isObject(value)) throw new Error(`${path} is not a JSON object; left unchanged.`);
81
- return value;
82
- }
83
- async function writeJsonObject(path, value) {
84
- await mkdir(dirname(path), { recursive: true });
85
- await writePrivateFile(path, `${JSON.stringify(value, null, 2)}
86
- `);
87
- }
88
-
89
- // src/codex.ts
90
- var CODEX_NOTIFY_MARKER = "# added by patron";
91
- var HOOK_TIMEOUT_SECONDS = 10;
92
- var EVENT = "Stop";
93
- function codexHome(env, home) {
94
- return env.CODEX_HOME || join(home, ".codex");
95
- }
96
- function codexPaths(env, home) {
97
- const root = codexHome(env, home);
98
- return { config: join(root, "config.toml"), hooks: join(root, "hooks.json") };
99
- }
100
- function withCodexStopHook(settings, command) {
101
- return withPatronGroup(settings, EVENT, stopGroup(command));
102
- }
103
- var CODEX_HOOK_STATUS = "Patron: pay the sites this turn read";
104
- function stopGroup(command) {
105
- return {
106
- hooks: [
107
- {
108
- command,
109
- statusMessage: CODEX_HOOK_STATUS,
110
- timeout: HOOK_TIMEOUT_SECONDS,
111
- type: "command"
112
- }
113
- ]
114
- };
115
- }
116
- function hasStopGroup(settings, command) {
117
- const groups = isObject(settings.hooks) ? settings.hooks[EVENT] : void 0;
118
- const wanted = JSON.stringify(stopGroup(command));
119
- return Array.isArray(groups) && groups.some((group) => JSON.stringify(group) === wanted);
120
- }
121
- function codexNotifyLine(command) {
122
- const argv = ["sh", "-c", `${command} "$1"`, "patron"];
123
- return `notify = [${argv.map((arg) => JSON.stringify(arg)).join(", ")}] ${CODEX_NOTIFY_MARKER}`;
124
- }
125
- function withCodexNotify(toml, command) {
126
- const lines = toml === "" ? [] : toml.split("\n");
127
- const line = codexNotifyLine(command);
128
- const index = topLevelNotify(lines);
129
- if (index === -1)
130
- return {
131
- state: "installed",
132
- text: lines.length === 0 ? `${line}
133
- ` : [line, ...lines].join("\n")
134
- };
135
- const current = lines[index] ?? "";
136
- if (!current.includes(CODEX_NOTIFY_MARKER)) return { state: "taken", text: toml };
137
- if (current.trimEnd() === line) return { state: "unchanged", text: toml };
138
- return {
139
- state: "installed",
140
- text: lines.map((value, at) => at === index ? line : value).join("\n")
141
- };
142
- }
143
- function withoutCodexNotify(toml) {
144
- const lines = toml.split("\n");
145
- const index = topLevelNotify(lines);
146
- if (index === -1 || !lines[index]?.includes(CODEX_NOTIFY_MARKER)) return void 0;
147
- return lines.filter((_, at) => at !== index).join("\n");
148
- }
149
- function topLevelNotify(lines) {
150
- for (const [index, line] of lines.entries()) {
151
- if (/^\s*\[/.test(line)) return -1;
152
- if (/^\s*notify\s*=/.test(line)) return index;
153
- }
154
- return -1;
155
- }
156
- async function installCodex(paths, command, platform = process.platform) {
157
- const settings = await readJsonObject(paths.hooks);
158
- const hookChanged = !hasStopGroup(settings, command);
159
- if (hookChanged)
160
- await writeJsonObject(paths.hooks, withCodexStopHook(settings, command));
161
- let notify = "taken";
162
- if (platform !== "win32") {
163
- const toml = await readText(paths.config);
164
- const edit = withCodexNotify(toml, command);
165
- notify = edit.state;
166
- if (edit.state === "installed") await writeText(paths.config, edit.text);
167
- }
168
- const state = hookChanged || notify === "installed" ? "installed" : "unchanged";
169
- return notify === "taken" ? { note: "open /hooks in Codex and trust it to start paying", state } : { note: "trust it in /hooks when Codex asks", state };
170
- }
171
- async function upgradeCodex(paths) {
172
- const settings = await readJsonObject(paths.hooks);
173
- const groups = isObject(settings.hooks) ? settings.hooks[EVENT] : void 0;
174
- const handler = (Array.isArray(groups) ? groups : []).flatMap(
175
- (group) => isObject(group) && Array.isArray(group.hooks) ? group.hooks : []
176
- ).find(isPatronHandler);
177
- const command = typeof handler?.command === "string" ? handler.command : void 0;
178
- if (!command || hasStopGroup(settings, command)) return false;
179
- await writeJsonObject(paths.hooks, withCodexStopHook(settings, command));
180
- return true;
181
- }
182
- async function removeCodex(paths) {
183
- let removed = false;
184
- const settings = await readJsonObject(paths.hooks);
185
- if (hasPatronHandler(settings, EVENT)) {
186
- await writeJsonObject(paths.hooks, withoutPatronHandlers(settings, EVENT));
187
- removed = true;
188
- }
189
- const toml = await readText(paths.config);
190
- const without = withoutCodexNotify(toml);
191
- if (without !== void 0) {
192
- await writeText(paths.config, without);
193
- removed = true;
194
- }
195
- return removed ? "removed" : "missing";
196
- }
197
- async function readText(path) {
198
- try {
199
- return await readFile2(path, "utf8");
200
- } catch (error) {
201
- if (error.code === "ENOENT") return "";
202
- throw error;
203
- }
204
- }
205
- async function writeText(path, text) {
206
- await mkdir2(dirname2(path), { recursive: true });
207
- await writePrivateFile(path, text);
208
- }
209
-
210
- export {
211
- isObject,
212
- isPatronHandler,
213
- withoutPatronHandlers,
214
- withPatronGroup,
215
- hasPatronHandler,
216
- withHooksKey,
217
- readJsonObject,
218
- writeJsonObject,
219
- CODEX_NOTIFY_MARKER,
220
- codexHome,
221
- codexPaths,
222
- withCodexStopHook,
223
- CODEX_HOOK_STATUS,
224
- codexNotifyLine,
225
- withCodexNotify,
226
- withoutCodexNotify,
227
- installCodex,
228
- upgradeCodex,
229
- removeCodex
230
- };
@@ -1,29 +0,0 @@
1
- import { createRequire as __patronCreateRequire } from 'node:module'
2
- const require = __patronCreateRequire(import.meta.url)
3
- import {
4
- CODEX_HOOK_STATUS,
5
- CODEX_NOTIFY_MARKER,
6
- codexHome,
7
- codexNotifyLine,
8
- codexPaths,
9
- installCodex,
10
- removeCodex,
11
- upgradeCodex,
12
- withCodexNotify,
13
- withCodexStopHook,
14
- withoutCodexNotify
15
- } from "./chunk-E4XN5ZK6.js";
16
- import "./chunk-6TTNL3HT.js";
17
- export {
18
- CODEX_HOOK_STATUS,
19
- CODEX_NOTIFY_MARKER,
20
- codexHome,
21
- codexNotifyLine,
22
- codexPaths,
23
- installCodex,
24
- removeCodex,
25
- upgradeCodex,
26
- withCodexNotify,
27
- withCodexStopHook,
28
- withoutCodexNotify
29
- };