@pushary/agent-hooks 0.89.13 → 0.90.1

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.
Files changed (78) hide show
  1. package/CHANGELOG.md +73 -0
  2. package/data/SKILL.md +1 -1
  3. package/data/cursor-plugin/skills/pushary/SKILL.md +1 -1
  4. package/data/vscode-plugin/skills/pushary/SKILL.md +1 -1
  5. package/dist/bin/pushary-bell-hook.js +4 -4
  6. package/dist/bin/pushary-bell.js +8 -8
  7. package/dist/bin/pushary-claude.js +72 -21
  8. package/dist/bin/pushary-clean.js +16 -42
  9. package/dist/bin/pushary-codex-bridge.js +37 -12
  10. package/dist/bin/pushary-codex-hook.js +45 -12
  11. package/dist/bin/pushary-codex.js +7 -7
  12. package/dist/bin/pushary-connect.js +11 -10
  13. package/dist/bin/pushary-daemon.js +187 -63
  14. package/dist/bin/pushary-disconnect.js +8 -8
  15. package/dist/bin/pushary-doctor.js +108 -25
  16. package/dist/bin/pushary-elicitation-hook.js +4 -4
  17. package/dist/bin/pushary-gemini-bridge.js +33 -11
  18. package/dist/bin/pushary-gemini-hook.js +21 -10
  19. package/dist/bin/pushary-hook.js +13 -13
  20. package/dist/bin/pushary-login.js +18 -7
  21. package/dist/bin/pushary-logout.js +6 -6
  22. package/dist/bin/pushary-mode.js +28 -21
  23. package/dist/bin/pushary-notification-hook.js +7 -7
  24. package/dist/bin/pushary-opencode-hook.js +8 -8
  25. package/dist/bin/pushary-permission-denied-hook.js +11 -11
  26. package/dist/bin/pushary-permission-hook.js +11 -11
  27. package/dist/bin/pushary-post-hook.js +14 -8
  28. package/dist/bin/pushary-prompt-hook.js +7 -7
  29. package/dist/bin/pushary-session-end-hook.js +7 -7
  30. package/dist/bin/pushary-session-start-hook.js +7 -7
  31. package/dist/bin/pushary-setup.js +168 -45
  32. package/dist/bin/pushary-stats.js +4 -4
  33. package/dist/bin/pushary-status.js +11 -10
  34. package/dist/bin/pushary-stop-hook.js +14 -8
  35. package/dist/bin/pushary-stopfailure-hook.js +7 -7
  36. package/dist/bin/pushary-suggestions.js +2 -2
  37. package/dist/bin/pushary-transcripts.js +6 -6
  38. package/dist/bin/pushary-upgrade.js +15 -15
  39. package/dist/bin/pushary-wait.js +5 -5
  40. package/dist/bin/pushary.js +1 -1
  41. package/dist/{chunk-B2HND22S.js → chunk-2LAHXHBR.js} +1 -1
  42. package/dist/{chunk-GMJDQDXU.js → chunk-43GMYSS4.js} +1 -0
  43. package/dist/{chunk-OX35BSPV.js → chunk-4LOPSGLP.js} +70 -11
  44. package/dist/{chunk-JTBDBKHU.js → chunk-54ELC7D7.js} +1 -1
  45. package/dist/{chunk-C56VHSXU.js → chunk-5NAZ7SFS.js} +1 -1
  46. package/dist/{chunk-4IPSMEGT.js → chunk-7FE2KABY.js} +12 -2
  47. package/dist/{chunk-RAEPAUNJ.js → chunk-7TPQEWUZ.js} +1 -1
  48. package/dist/{chunk-ZO43JTT3.js → chunk-AJYV3VGU.js} +1 -1
  49. package/dist/{chunk-QFJZHVVL.js → chunk-AZPIQG4G.js} +35 -1
  50. package/dist/{chunk-5MNTWAAJ.js → chunk-B6H2KAQC.js} +29 -22
  51. package/dist/{chunk-MNTZKFZI.js → chunk-BQBEH6TV.js} +5 -5
  52. package/dist/{chunk-WHDSJ6PD.js → chunk-BVBEURWJ.js} +1 -1
  53. package/dist/{chunk-ZWEKONU6.js → chunk-CELX75MW.js} +1 -1
  54. package/dist/{chunk-ZP3SNUP7.js → chunk-CNPAT3HL.js} +3 -2
  55. package/dist/{chunk-YXT55PYF.js → chunk-DYP6SMOR.js} +1 -1
  56. package/dist/chunk-DZDI62LY.js +145 -0
  57. package/dist/{chunk-VF3RTVWY.js → chunk-EZIEXHYM.js} +15 -7
  58. package/dist/{chunk-7JZCADBR.js → chunk-F2ICEWZL.js} +101 -19
  59. package/dist/{chunk-JHJHCMAF.js → chunk-F7OURW3C.js} +24 -6
  60. package/dist/{chunk-VASZ6TWW.js → chunk-FQL5E6NG.js} +2 -2
  61. package/dist/{chunk-JVLB5CAJ.js → chunk-GL3FFM7A.js} +7 -4
  62. package/dist/{chunk-5YEF55AX.js → chunk-H2QZRQR2.js} +1 -1
  63. package/dist/{chunk-S4BLNILH.js → chunk-I463AEG3.js} +1 -1
  64. package/dist/{chunk-ANMHIZZY.js → chunk-JW4KO5AF.js} +1 -1
  65. package/dist/{chunk-3YGGEDJK.js → chunk-KUZWXPUS.js} +3 -2
  66. package/dist/{chunk-N4QJ3CLX.js → chunk-L4E2BIJ2.js} +80 -7
  67. package/dist/{chunk-NBDU3R3G.js → chunk-OSALSIUF.js} +1 -1
  68. package/dist/{chunk-KNLKHJZR.js → chunk-PYA4FLKI.js} +32 -21
  69. package/dist/chunk-QCGNGEGE.js +15 -0
  70. package/dist/chunk-S6LYP7QA.js +127 -0
  71. package/dist/{chunk-M3UABYRT.js → chunk-UZNKVRLE.js} +1 -1
  72. package/dist/{chunk-FOGW6O2Y.js → chunk-VHWCVD42.js} +1 -1
  73. package/dist/{chunk-QINYXLA5.js → chunk-VNMKIJZB.js} +15 -2
  74. package/dist/{chunk-CBGGUEKE.js → chunk-Y6BWPERV.js} +90 -7
  75. package/dist/src/index.d.ts +3 -0
  76. package/dist/src/index.js +11 -11
  77. package/package.json +1 -1
  78. package/dist/chunk-MSTHT2M7.js +0 -72
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  renderCommandHelp
3
- } from "./chunk-GMJDQDXU.js";
3
+ } from "./chunk-43GMYSS4.js";
4
4
  import {
5
5
  getPackageVersion
6
6
  } from "./chunk-DINDL2CF.js";
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  HOOK_BUDGETS,
3
3
  OPENCODE_APPROVAL_EVENT
4
- } from "./chunk-QINYXLA5.js";
4
+ } from "./chunk-VNMKIJZB.js";
5
5
 
6
6
  // src/opencode-plugin.ts
7
7
  var OPENCODE_PLUGIN_MARKER = "pushary-opencode-plugin w1";
@@ -3,7 +3,7 @@ import {
3
3
  decodePublicKey,
4
4
  generateSessionKey,
5
5
  wrapSessionKey
6
- } from "./chunk-4IPSMEGT.js";
6
+ } from "./chunk-7FE2KABY.js";
7
7
  import {
8
8
  fetchTranscriptRecipients,
9
9
  uploadTranscriptRecords
@@ -55,9 +55,10 @@ var postLiveSessionUntilAccepted = async (options, path, body) => {
55
55
  }
56
56
  throw new Error("Live session stopped before it linked");
57
57
  };
58
- var startLiveSessionHeartbeat = (options, body, intervalMs = LIVE_SESSION_HEARTBEAT_MS) => {
58
+ var startLiveSessionHeartbeat = (options, body, intervalMs = LIVE_SESSION_HEARTBEAT_MS, onBeat) => {
59
59
  const timer = setInterval(() => {
60
60
  if (!options.signal.aborted) {
61
+ onBeat?.();
61
62
  void postLiveSession(options, "/api/agent/event", body).catch(() => {
62
63
  });
63
64
  }
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  describeKeyCheck,
3
3
  keyCheckFromResponse
4
- } from "./chunk-ZO43JTT3.js";
4
+ } from "./chunk-AJYV3VGU.js";
5
5
  import {
6
6
  createIo
7
7
  } from "./chunk-5RUIFDTP.js";
@@ -0,0 +1,145 @@
1
+ import {
2
+ CLAUDE_HOOK_EVENTS,
3
+ GEMINI_HOOK_EVENTS,
4
+ hasGeminiHooks
5
+ } from "./chunk-GL3FFM7A.js";
6
+ import {
7
+ CODEX_HOOK_EVENTS,
8
+ hasCodexHooks
9
+ } from "./chunk-KUZWXPUS.js";
10
+ import {
11
+ CURSOR_GATE_EVENTS,
12
+ HOOK_LOCATOR_BINARY
13
+ } from "./chunk-VNMKIJZB.js";
14
+
15
+ // src/diagnostics/wiring.ts
16
+ var record = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
17
+ var CLAUDE_HOOK_BINARY = /pushary-[a-z-]*hook/;
18
+ var EXACT_HOOK_BINARY = /^pushary-[a-z-]*(hook|gate)$/;
19
+ var pusharyHookExecutable = (command) => {
20
+ const tokens = command.match(/"[^"]*"|'[^']*'|\S+/g) ?? [];
21
+ for (const raw of tokens.reverse()) {
22
+ const token = raw[0] === '"' && raw.at(-1) === '"' || raw[0] === "'" && raw.at(-1) === "'" ? raw.slice(1, -1) : raw;
23
+ const name = token.split(/[/\\]/).pop()?.replace(/\.(cmd|exe|mjs)$/i, "") ?? "";
24
+ if (name === HOOK_LOCATOR_BINARY || EXACT_HOOK_BINARY.test(name)) return token;
25
+ }
26
+ return null;
27
+ };
28
+ var claudeWired = ({ claudeJson, settings, skillExists }) => {
29
+ if (record(record(claudeJson)?.mcpServers)?.pushary) return true;
30
+ const s = record(settings);
31
+ if (s) {
32
+ const hooks = JSON.stringify(s.hooks ?? {});
33
+ if (CLAUDE_HOOK_BINARY.test(hooks) || hooks.includes(HOOK_LOCATOR_BINARY)) return true;
34
+ const allow = record(s.permissions)?.allow;
35
+ if (Array.isArray(allow) && allow.some((rule) => String(rule).includes("mcp__pushary__"))) return true;
36
+ }
37
+ return skillExists;
38
+ };
39
+ var codexWired = ({ config, hooks, skillExists }) => {
40
+ if (record(record(config)?.mcp_servers)?.pushary) return true;
41
+ const h = record(hooks);
42
+ if (h && hasCodexHooks(h)) return true;
43
+ return skillExists;
44
+ };
45
+ var geminiWired = ({ settings }) => {
46
+ const s = record(settings);
47
+ if (!s) return false;
48
+ if (record(s.mcpServers)?.pushary) return true;
49
+ return hasGeminiHooks(s);
50
+ };
51
+ var cursorWired = ({ userHooks, mcp, pluginDirExists }) => {
52
+ if (pluginDirExists) return true;
53
+ if (record(record(mcp)?.mcpServers)?.pushary) return true;
54
+ const hooks = record(record(userHooks)?.hooks);
55
+ if (!hooks) return false;
56
+ return Object.values(hooks).some((entries) => Array.isArray(entries) && entries.some((entry) => pusharyHookExecutable(String(record(entry)?.command ?? "")) !== null));
57
+ };
58
+ var cursorMcpAuthorization = (pluginMcp, userMcp) => {
59
+ const pluginServers = record(record(pluginMcp)?.mcpServers);
60
+ const usePlugin = pluginServers !== null && Object.hasOwn(pluginServers, "pushary");
61
+ const servers = usePlugin ? pluginServers : record(record(userMcp)?.mcpServers);
62
+ const headers = record(record(servers?.pushary)?.headers);
63
+ return {
64
+ authorization: typeof headers?.Authorization === "string" ? headers.Authorization : null,
65
+ source: usePlugin ? "plugin mcp.json" : "~/.cursor/mcp.json"
66
+ };
67
+ };
68
+ var LONG_HOLD_SECONDS = 600;
69
+ var toSeconds = (timeout, unit) => {
70
+ if (typeof timeout !== "number" || !Number.isFinite(timeout)) return null;
71
+ return unit === "milliseconds" ? timeout / 1e3 : timeout;
72
+ };
73
+ var registeredHooks = (entries, shape, unit) => {
74
+ if (!Array.isArray(entries)) return [];
75
+ return entries.flatMap((entry, entryIndex) => {
76
+ const container = record(entry);
77
+ if (!container) return [];
78
+ const hooks = shape === "nested" ? Array.isArray(container.hooks) ? container.hooks : [] : [container];
79
+ return hooks.flatMap((hook, hookIndex) => {
80
+ const command = record(hook)?.command;
81
+ if (typeof command !== "string" || command.trim() === "") return [];
82
+ return [{
83
+ command,
84
+ timeoutSeconds: toSeconds(record(hook)?.timeout, unit),
85
+ order: [entryIndex, hookIndex]
86
+ }];
87
+ });
88
+ });
89
+ };
90
+ var runsBefore = (a, b) => a[0] < b[0] || a[0] === b[0] && a[1] < b[1];
91
+ var holdersOnEvent = (event, hooks) => {
92
+ const ours = hooks.find((hook) => pusharyHookExecutable(hook.command) !== null);
93
+ if (!ours) return [];
94
+ return hooks.filter((hook) => pusharyHookExecutable(hook.command) === null).map((hook) => ({
95
+ event,
96
+ command: hook.command,
97
+ timeoutSeconds: hook.timeoutSeconds,
98
+ position: runsBefore(hook.order, ours.order) ? "before" : "after"
99
+ }));
100
+ };
101
+ var otherHolders = (hooks, events, shape, unit) => {
102
+ const table = record(hooks);
103
+ if (!table) return [];
104
+ return events.flatMap((event) => holdersOnEvent(event, registeredHooks(table[event], shape, unit)));
105
+ };
106
+ var approvalEvents = (table) => table.filter((hook) => hook.delivery === "approval").map((hook) => hook.event);
107
+ var CLAUDE_GATE_EVENTS = approvalEvents(CLAUDE_HOOK_EVENTS);
108
+ var CODEX_GATE_EVENTS = approvalEvents(CODEX_HOOK_EVENTS);
109
+ var GEMINI_GATE_EVENTS = approvalEvents(GEMINI_HOOK_EVENTS);
110
+ var claudeOtherHolders = (settings) => otherHolders(record(settings)?.hooks, CLAUDE_GATE_EVENTS, "nested", "seconds");
111
+ var codexOtherHolders = (hooks) => otherHolders(record(hooks)?.hooks, CODEX_GATE_EVENTS, "nested", "seconds");
112
+ var geminiOtherHolders = (settings) => otherHolders(record(settings)?.hooks, GEMINI_GATE_EVENTS, "nested", "milliseconds");
113
+ var cursorOtherHolders = (userHooks) => otherHolders(record(userHooks)?.hooks, CURSOR_GATE_EVENTS, "flat", "seconds");
114
+ var holdsLong = (holder) => holder.timeoutSeconds !== null && holder.timeoutSeconds > LONG_HOLD_SECONDS;
115
+ var SHELLS = /* @__PURE__ */ new Set(["/bin/sh", "/bin/bash", "/bin/zsh", "/usr/bin/env"]);
116
+ var holderExecutable = (command) => {
117
+ const pathLike = command.match(/[^\s"']*[/\\][^\s"']*/g) ?? [];
118
+ const target = pathLike.find((token) => !SHELLS.has(token));
119
+ return target ?? command.trim().split(/\s+/)[0] ?? command;
120
+ };
121
+ var describeHoldTimeout = (holder) => {
122
+ const seconds = holder.timeoutSeconds;
123
+ if (seconds === null) return "no timeout set, so the agent default applies";
124
+ if (seconds >= 3600 && seconds % 3600 === 0) return `${seconds / 3600}h timeout`;
125
+ if (seconds >= 60 && seconds % 60 === 0) return `${seconds / 60}m timeout`;
126
+ return `${seconds}s timeout`;
127
+ };
128
+ var describeOtherHolder = (holder) => `another hook on ${holder.event}: ${holderExecutable(holder.command)} (${describeHoldTimeout(holder)}, runs ${holder.position} ours)`;
129
+ var describeHoldSentence = (agentLabel) => `${agentLabel} waits for every hook on an event, so the terminal cannot move until that one returns, however fast your phone answered.`;
130
+
131
+ export {
132
+ pusharyHookExecutable,
133
+ claudeWired,
134
+ codexWired,
135
+ geminiWired,
136
+ cursorWired,
137
+ cursorMcpAuthorization,
138
+ claudeOtherHolders,
139
+ codexOtherHolders,
140
+ geminiOtherHolders,
141
+ cursorOtherHolders,
142
+ holdsLong,
143
+ describeOtherHolder,
144
+ describeHoldSentence
145
+ };
@@ -6,10 +6,14 @@ import {
6
6
  var HOOK_SHELL = "/bin/sh -c";
7
7
  var LOCATE = 'B=$(command -v "$0") || exit 0; [ -f "$B" ] || exit 0; [ -x "$B" ] || exit 0';
8
8
  var NODE_PRESENT = "command -v node >/dev/null 2>&1 || exit 0";
9
+ var CURSOR_NO_OPINION = { permission: "ask" };
10
+ var shellDoubleQuoted = (text) => `"${text.replace(/(["\\$`])/g, "\\$1")}"`;
11
+ var CURSOR_MISS = `{ printf %s ${shellDoubleQuoted(JSON.stringify(CURSOR_NO_OPINION))}; exit 0; }`;
9
12
  var HOOK_GUARD_SCRIPTS = {
10
13
  native_binary: `${LOCATE}; exec "$B" "$@"`,
11
14
  node_binary: `${LOCATE}; ${NODE_PRESENT}; exec "$B" "$@"`,
12
- node_script: `[ -f "$0" ] || exit 0; ${NODE_PRESENT}; exec node "$0" "$@"`
15
+ node_script: `[ -f "$0" ] || exit 0; ${NODE_PRESENT}; exec node "$0" "$@"`,
16
+ cursor_gate_script: `[ -f "$0" ] || ${CURSOR_MISS}; command -v node >/dev/null 2>&1 || ${CURSOR_MISS}; exec node "$0" "$@"`
13
17
  };
14
18
  var BINARY_GUARDS = {
15
19
  native: "native_binary",
@@ -19,21 +23,25 @@ var guardKindOf = (target) => {
19
23
  switch (target.kind) {
20
24
  case "node":
21
25
  return "node_script";
26
+ case "cursor_gate":
27
+ return "cursor_gate_script";
22
28
  case "binary":
23
29
  return BINARY_GUARDS[target.runtime];
24
30
  }
25
31
  };
32
+ var targetPath = (target) => target.kind === "binary" ? target.path : target.script;
33
+ var runsThroughNode = (target) => target.kind !== "binary";
26
34
  var guardHookCommand = (target, platform = process.platform) => {
27
- const path = quoteIfNeeded(target.kind === "node" ? target.script : target.path);
28
- if (platform === "win32") return target.kind === "node" ? `node ${path}` : path;
35
+ const path = quoteIfNeeded(targetPath(target));
36
+ if (platform === "win32") return runsThroughNode(target) ? `node ${path}` : path;
29
37
  return `${HOOK_SHELL} '${HOOK_GUARD_SCRIPTS[guardKindOf(target)]}' ${path}`;
30
38
  };
31
39
  var guardBinary = (path, platform) => guardHookCommand({ kind: "binary", path, runtime: "node" }, platform);
32
- var guardNodeScript = (script, platform) => guardHookCommand({ kind: "node", script }, platform);
40
+ var guardCursorGate = (script, platform) => guardHookCommand({ kind: "cursor_gate", script }, platform);
33
41
  var guardedArgv = (target, args = [], platform = process.platform) => {
34
- const path = target.kind === "node" ? target.script : target.path;
42
+ const path = targetPath(target);
35
43
  if (platform === "win32") {
36
- return target.kind === "node" ? ["node", path, ...args] : [path, ...args];
44
+ return runsThroughNode(target) ? ["node", path, ...args] : [path, ...args];
37
45
  }
38
46
  const [shell, ...shellArgs] = HOOK_SHELL.split(/\s+/);
39
47
  return [shell, ...shellArgs, HOOK_GUARD_SCRIPTS[guardKindOf(target)], path, ...args];
@@ -41,6 +49,6 @@ var guardedArgv = (target, args = [], platform = process.platform) => {
41
49
 
42
50
  export {
43
51
  guardBinary,
44
- guardNodeScript,
52
+ guardCursorGate,
45
53
  guardedArgv
46
54
  };
@@ -7,10 +7,10 @@ import {
7
7
  } from "./chunk-L4ZCFMWW.js";
8
8
  import {
9
9
  getMachineId
10
- } from "./chunk-JTBDBKHU.js";
10
+ } from "./chunk-54ELC7D7.js";
11
11
  import {
12
12
  pusharyDir
13
- } from "./chunk-3YGGEDJK.js";
13
+ } from "./chunk-KUZWXPUS.js";
14
14
  import {
15
15
  getPackageVersion
16
16
  } from "./chunk-DINDL2CF.js";
@@ -383,18 +383,32 @@ var manageDaemonService = (action, options = {}) => {
383
383
  }
384
384
  return applyDaemonService(action, definition, { run: options.run });
385
385
  };
386
+ var nominalDaemonService = (options) => buildDaemonService({
387
+ platform: options.platform ?? process.platform,
388
+ homeDir: options.homeDir ?? homedir(),
389
+ stateDir: options.stateDir ?? pusharyDir(),
390
+ execPath: options.execPath ?? process.execPath,
391
+ daemonEntry: options.daemonEntry ?? process.execPath,
392
+ pathEnv: options.pathEnv ?? process.env.PATH ?? "",
393
+ uid: options.uid ?? process.getuid?.()
394
+ });
386
395
  var hasDaemonServiceDefinition = (options = {}) => {
387
- const definition = buildDaemonService({
388
- platform: options.platform ?? process.platform,
389
- homeDir: options.homeDir ?? homedir(),
390
- stateDir: options.stateDir ?? pusharyDir(),
391
- execPath: options.execPath ?? process.execPath,
392
- daemonEntry: options.daemonEntry ?? process.execPath,
393
- pathEnv: options.pathEnv ?? process.env.PATH ?? "",
394
- uid: options.uid ?? process.getuid?.()
395
- });
396
+ const definition = nominalDaemonService(options);
396
397
  return definition !== null && existsSync(definition.definitionPath);
397
398
  };
399
+ var daemonServiceDefinitionPath = (options = {}) => nominalDaemonService(options)?.definitionPath ?? null;
400
+ var readInstalledDaemonService = (options = {}) => {
401
+ const definition = nominalDaemonService(options);
402
+ if (!definition) return null;
403
+ try {
404
+ return {
405
+ path: definition.definitionPath,
406
+ contents: readFileSync(definition.definitionPath, definition.encoding ?? "utf-8")
407
+ };
408
+ } catch {
409
+ return null;
410
+ }
411
+ };
398
412
  var spawnDetachedDaemon = (plan, options = {}) => {
399
413
  const spawnImpl = options.spawnImpl ?? spawnClaude;
400
414
  const child = spawnImpl(plan.command, plan.args, {
@@ -418,13 +432,19 @@ var processIsAlive = (pid) => {
418
432
  };
419
433
  var isString = (value) => typeof value === "string" && value.length > 0;
420
434
  var isTimestamp = (value) => isString(value) && Number.isFinite(Date.parse(value));
435
+ var daemonConfigurationId = (apiKey) => createHash("sha256").update(apiKey).digest("hex").slice(0, 16);
421
436
  var currentConfigurationId = () => {
422
437
  try {
423
- return createHash("sha256").update(getApiKey()).digest("hex").slice(0, 16);
438
+ return daemonConfigurationId(getApiKey());
424
439
  } catch {
425
440
  return null;
426
441
  }
427
442
  };
443
+ var parseDaemonLastError = (value) => {
444
+ if (typeof value !== "object" || value === null) return null;
445
+ const error = value;
446
+ return isString(error.message) && isTimestamp(error.at) ? { message: error.message, at: error.at } : null;
447
+ };
428
448
  var parseDaemonState = (value) => {
429
449
  if (typeof value !== "object" || value === null) return null;
430
450
  const state = value;
@@ -438,7 +458,8 @@ var parseDaemonState = (value) => {
438
458
  startedAt: state.startedAt,
439
459
  updatedAt: state.updatedAt,
440
460
  lastSuccessfulHeartbeatAt: state.lastSuccessfulHeartbeatAt,
441
- lastSuccessfulControlAt: state.lastSuccessfulControlAt ?? null
461
+ lastSuccessfulControlAt: state.lastSuccessfulControlAt ?? null,
462
+ lastError: parseDaemonLastError(state.lastError)
442
463
  };
443
464
  };
444
465
  var parseLockOwner = (value) => {
@@ -461,16 +482,17 @@ var readDaemonStatus = (options = {}) => {
461
482
  startedAt: null,
462
483
  updatedAt: null,
463
484
  lastSuccessfulHeartbeatAt: null,
464
- lastSuccessfulControlAt: null
485
+ lastSuccessfulControlAt: null,
486
+ lastError: null
465
487
  };
466
488
  }
467
489
  const value = state.kind === "ok" ? parseDaemonState(state.value) : null;
468
490
  if (value) {
469
491
  const alive = (options.isProcessAlive ?? processIsAlive)(value.pid);
470
492
  const fresh = (options.now ?? Date.now)() - Date.parse(value.updatedAt) <= LOCAL_LEASE_TIMEOUT_MS;
471
- const expectedConfigurationId = options.configurationId === void 0 ? currentConfigurationId() : options.configurationId;
493
+ const expectedConfigurationId2 = options.configurationId === void 0 ? currentConfigurationId() : options.configurationId;
472
494
  return {
473
- kind: !alive || !fresh ? "unresponsive" : value.version !== installedVersion ? "version-mismatch" : expectedConfigurationId !== null && value.configurationId !== null && value.configurationId !== expectedConfigurationId ? "configuration-mismatch" : "running",
495
+ kind: !alive || !fresh ? "unresponsive" : value.version !== installedVersion ? "version-mismatch" : expectedConfigurationId2 !== null && value.configurationId !== null && value.configurationId !== expectedConfigurationId2 ? "configuration-mismatch" : "running",
474
496
  installedVersion,
475
497
  machineId: value.machineId,
476
498
  pid: value.pid,
@@ -479,7 +501,8 @@ var readDaemonStatus = (options = {}) => {
479
501
  startedAt: value.startedAt,
480
502
  updatedAt: value.updatedAt,
481
503
  lastSuccessfulHeartbeatAt: value.lastSuccessfulHeartbeatAt,
482
- lastSuccessfulControlAt: value.lastSuccessfulControlAt
504
+ lastSuccessfulControlAt: value.lastSuccessfulControlAt,
505
+ lastError: value.lastError
483
506
  };
484
507
  }
485
508
  return {
@@ -492,7 +515,8 @@ var readDaemonStatus = (options = {}) => {
492
515
  startedAt: null,
493
516
  updatedAt: null,
494
517
  lastSuccessfulHeartbeatAt: null,
495
- lastSuccessfulControlAt: null
518
+ lastSuccessfulControlAt: null,
519
+ lastError: null
496
520
  };
497
521
  };
498
522
  var safeUnlink = (path) => {
@@ -561,7 +585,8 @@ var acquireDaemonLease = (options = {}) => {
561
585
  startedAt,
562
586
  updatedAt: startedAt,
563
587
  lastSuccessfulHeartbeatAt: null,
564
- lastSuccessfulControlAt: null
588
+ lastSuccessfulControlAt: null,
589
+ lastError: null
565
590
  };
566
591
  try {
567
592
  writeJsonAtomic(statePath(dir), state, 384);
@@ -605,6 +630,14 @@ var acquireDaemonLease = (options = {}) => {
605
630
  lastSuccessfulControlAt: timestamp
606
631
  });
607
632
  },
633
+ recordError: (message, at = now()) => {
634
+ const timestamp = new Date(at).toISOString();
635
+ updateState({
636
+ ...currentState,
637
+ updatedAt: timestamp,
638
+ lastError: { message: message.slice(0, 240), at: timestamp }
639
+ });
640
+ },
608
641
  shouldYield: () => {
609
642
  if (!isOwner()) return true;
610
643
  const target = readJsonSafe(targetPath(dir));
@@ -670,6 +703,44 @@ var stopDaemonRunning = async (options = {}) => {
670
703
  safeUnlink(lockPath(dir));
671
704
  return { kind: "stopped" };
672
705
  };
706
+ var needsLoginPath = (dir) => join(dir, "daemon-needs-login.json");
707
+ var isLoginReason = (value) => value === "key_rejected" || value === "plan_inactive";
708
+ var parseDaemonNeedsLogin = (value) => {
709
+ if (typeof value !== "object" || value === null) return null;
710
+ const record = value;
711
+ if (!isLoginReason(record.reason) || !isTimestamp(record.at)) return null;
712
+ if (!(record.configurationId === null || isString(record.configurationId))) return null;
713
+ return { reason: record.reason, at: record.at, configurationId: record.configurationId };
714
+ };
715
+ var expectedConfigurationId = (options) => options.configurationId === void 0 ? currentConfigurationId() : options.configurationId;
716
+ var writeDaemonNeedsLogin = (reason, options = {}) => {
717
+ const record = {
718
+ reason,
719
+ at: new Date((options.now ?? Date.now)()).toISOString(),
720
+ configurationId: expectedConfigurationId(options)
721
+ };
722
+ writeJsonAtomic(needsLoginPath(options.dir ?? pusharyDir()), record, 384);
723
+ return record;
724
+ };
725
+ var readDaemonNeedsLogin = (options = {}) => {
726
+ const stored = readJsonSafe(needsLoginPath(options.dir ?? pusharyDir()));
727
+ const record = stored.kind === "ok" ? parseDaemonNeedsLogin(stored.value) : null;
728
+ if (!record) return { kind: "none" };
729
+ return record.configurationId === expectedConfigurationId(options) ? { kind: "blocked", record } : { kind: "stale", record };
730
+ };
731
+ var clearDaemonNeedsLogin = (options = {}) => {
732
+ const path = needsLoginPath(options.dir ?? pusharyDir());
733
+ if (!existsSync(path)) return false;
734
+ safeUnlink(path);
735
+ return true;
736
+ };
737
+ var describeDaemonLoginBlock = (record) => record.reason === "key_rejected" ? `this machine's key was rejected at ${record.at}; run \`pushary login\` to sign in again` : `this workspace had no active plan at ${record.at}; pick one at pushary.com/dashboard/settings?tab=billing, then run \`pushary login\``;
738
+ var recoverDaemonAfterLogin = (options = {}) => {
739
+ const cleared = clearDaemonNeedsLogin(options);
740
+ if (!cleared || !hasDaemonServiceDefinition(options)) return { cleared, restarted: false, detail: null };
741
+ const started = manageDaemonService("start", options);
742
+ return started.kind === "ok" ? { cleared, restarted: true, detail: null } : { cleared, restarted: false, detail: started.detail };
743
+ };
673
744
  var deactivateDaemonService = async (action, options) => {
674
745
  const platform = options.platform ?? process.platform;
675
746
  const supported = ["darwin", "linux", "win32"].includes(platform);
@@ -966,6 +1037,10 @@ var ensureDaemonRunning = async (options = {}) => {
966
1037
  return { kind: "not-configured", status: initial, detail: "Pushary is not configured on this machine" };
967
1038
  }
968
1039
  if (initial.kind === "running") return { kind: "running", status: initial };
1040
+ const loginBlock = readDaemonNeedsLogin(options);
1041
+ if (loginBlock.kind === "blocked") {
1042
+ return { kind: "unavailable", status: initial, detail: describeDaemonLoginBlock(loginBlock.record) };
1043
+ }
969
1044
  let handoffStatus;
970
1045
  try {
971
1046
  handoffStatus = await requestDaemonHandoff(initial, options);
@@ -1020,10 +1095,17 @@ export {
1020
1095
  parseDaemonAction,
1021
1096
  manageDaemonService,
1022
1097
  hasDaemonServiceDefinition,
1098
+ daemonServiceDefinitionPath,
1099
+ readInstalledDaemonService,
1023
1100
  readDaemonStatus,
1024
1101
  acquireDaemonLease,
1025
1102
  requestDaemonHandoff,
1026
1103
  stopDaemonRunning,
1104
+ writeDaemonNeedsLogin,
1105
+ readDaemonNeedsLogin,
1106
+ clearDaemonNeedsLogin,
1107
+ describeDaemonLoginBlock,
1108
+ recoverDaemonAfterLogin,
1027
1109
  stopDaemonService,
1028
1110
  disableDaemonService,
1029
1111
  replaceDaemonService,
@@ -72,13 +72,13 @@ import { homedir, tmpdir } from "os";
72
72
  import { createHash } from "crypto";
73
73
  import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync, renameSync, unlinkSync, rmSync, statSync } from "fs";
74
74
  var PENDING_DIR = join(tmpdir(), "pushary-pending");
75
- var COMMAND_DIR = join(homedir(), ".pushary", "run", "commands");
75
+ var commandRoot = () => process.env.PUSHARY_COMMAND_DIR?.trim() || join(homedir(), ".pushary", "run", "commands");
76
76
  var DEFAULT_SESSION = "_no_session";
77
77
  var GRACE_MS = 10 * 60 * 1e3;
78
78
  var sanitize = (sessionId) => sessionId.replace(/[^A-Za-z0-9_-]/g, "_").slice(0, 128) || DEFAULT_SESSION;
79
79
  var dirFor = (sessionId) => join(PENDING_DIR, sanitize(sessionId));
80
80
  var commandDirFor = (sessionId, namespace = "") => join(
81
- COMMAND_DIR,
81
+ commandRoot(),
82
82
  createHash("sha256").update(namespace).digest("hex").slice(0, 16),
83
83
  sanitize(sessionId)
84
84
  );
@@ -122,12 +122,12 @@ var pruneExpiredCommands = (dir) => {
122
122
  var pruneExpiredCommandStore = () => {
123
123
  let namespaces;
124
124
  try {
125
- namespaces = readdirSync(COMMAND_DIR, { withFileTypes: true });
125
+ namespaces = readdirSync(commandRoot(), { withFileTypes: true });
126
126
  } catch {
127
127
  return;
128
128
  }
129
129
  for (const namespace of namespaces.filter((entry) => entry.isDirectory())) {
130
- const namespaceDir = join(COMMAND_DIR, namespace.name);
130
+ const namespaceDir = join(commandRoot(), namespace.name);
131
131
  let sessions;
132
132
  try {
133
133
  sessions = readdirSync(namespaceDir, { withFileTypes: true });
@@ -189,10 +189,27 @@ var removePendingCommand = (sessionId, id, namespace = "") => {
189
189
  }
190
190
  };
191
191
  var isDefaultSession = (sessionId) => sanitize(sessionId) === DEFAULT_SESSION;
192
- var savePendingQuestion = (sessionId, correlationId) => {
192
+ var savePendingQuestion = (sessionId, correlationId, toolUseId) => {
193
193
  const dir = dirFor(sessionId);
194
194
  if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
195
- writeFileSync(join(dir, correlationId), "", "utf-8");
195
+ writeFileSync(join(dir, correlationId), toolUseId ?? "", "utf-8");
196
+ };
197
+ var listPendingQuestionsForToolUse = (sessionId, toolUseId) => {
198
+ if (!toolUseId) return [];
199
+ const dir = dirFor(sessionId);
200
+ let files;
201
+ try {
202
+ files = readdirSync(dir);
203
+ } catch {
204
+ return [];
205
+ }
206
+ return files.filter((name) => {
207
+ try {
208
+ return readFileSync(join(dir, name), "utf-8") === toolUseId;
209
+ } catch {
210
+ return false;
211
+ }
212
+ });
196
213
  };
197
214
  var listPendingQuestions = (sessionId) => {
198
215
  const dir = dirFor(sessionId);
@@ -241,6 +258,7 @@ export {
241
258
  removePendingCommand,
242
259
  isDefaultSession,
243
260
  savePendingQuestion,
261
+ listPendingQuestionsForToolUse,
244
262
  listPendingQuestions,
245
263
  removePendingQuestion,
246
264
  removePendingSession
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  openCodeConfigDirFrom
3
- } from "./chunk-ZWEKONU6.js";
3
+ } from "./chunk-CELX75MW.js";
4
4
  import {
5
5
  codexHomeFrom
6
- } from "./chunk-3YGGEDJK.js";
6
+ } from "./chunk-KUZWXPUS.js";
7
7
 
8
8
  // src/vscode-config.ts
9
9
  import { homedir } from "os";
@@ -1,17 +1,17 @@
1
1
  import {
2
2
  PRETOOLUSE_GATED_TOOLS,
3
3
  PRETOOLUSE_PHONE_ANSWERED_TOOLS
4
- } from "./chunk-B2HND22S.js";
4
+ } from "./chunk-2LAHXHBR.js";
5
5
  import {
6
6
  guardBinary
7
- } from "./chunk-VF3RTVWY.js";
7
+ } from "./chunk-EZIEXHYM.js";
8
8
  import {
9
9
  isProtectedByScope
10
- } from "./chunk-3YGGEDJK.js";
10
+ } from "./chunk-KUZWXPUS.js";
11
11
  import {
12
12
  HOOK_BUDGETS,
13
13
  HOOK_LOCATOR_BINARY
14
- } from "./chunk-QINYXLA5.js";
14
+ } from "./chunk-VNMKIJZB.js";
15
15
 
16
16
  // src/claude-config.ts
17
17
  import { join } from "path";
@@ -234,6 +234,7 @@ var GEMINI_HOOK_EVENTS = [
234
234
  { event: "BeforeTool", matcher: "run_shell_command|write_file|replace", delivery: "approval", timeoutMs: HOOK_BUDGETS.gemini.budgetSeconds * 1e3 },
235
235
  { event: "AfterTool", matcher: "run_shell_command|write_file|replace", delivery: "telemetry", timeoutMs: 1e4 },
236
236
  { event: "BeforeAgent", delivery: "telemetry", timeoutMs: 1e4 },
237
+ { event: "AfterAgent", delivery: "telemetry", timeoutMs: 1e4 },
237
238
  { event: "SessionStart", delivery: "telemetry", timeoutMs: 1e4 },
238
239
  { event: "SessionEnd", delivery: "telemetry", timeoutMs: 1e4 }
239
240
  ];
@@ -330,6 +331,7 @@ var missingGeminiHookEvents = (settings) => {
330
331
  export {
331
332
  parseVersion,
332
333
  compareVersion,
334
+ CLAUDE_HOOK_EVENTS,
333
335
  addClaudeMcpServer,
334
336
  isClaudeMcpSnapshot,
335
337
  captureClaudeMcpServer,
@@ -340,6 +342,7 @@ export {
340
342
  addPusharyHooks,
341
343
  removePusharySettings,
342
344
  GEMINI_HOOK_BINARY,
345
+ GEMINI_HOOK_EVENTS,
343
346
  addGeminiMcpServer,
344
347
  addGeminiHooks,
345
348
  removeGeminiSettings,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  parseVersion
3
- } from "./chunk-JVLB5CAJ.js";
3
+ } from "./chunk-GL3FFM7A.js";
4
4
 
5
5
  // src/setup/agent-probe.ts
6
6
  import { execSync } from "child_process";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  shouldDeferToNativeMode
3
- } from "./chunk-QINYXLA5.js";
3
+ } from "./chunk-VNMKIJZB.js";
4
4
 
5
5
  // src/native-defer.ts
6
6
  var nativeDeferOptions = () => {
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-2UMNXADU.js";
4
4
  import {
5
5
  normalizeInstallSource
6
- } from "./chunk-QINYXLA5.js";
6
+ } from "./chunk-VNMKIJZB.js";
7
7
 
8
8
  // src/install-source.ts
9
9
  import { readFileSync } from "fs";
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  HOOK_BUDGETS,
3
3
  HOOK_LOCATOR_BINARY
4
- } from "./chunk-QINYXLA5.js";
4
+ } from "./chunk-VNMKIJZB.js";
5
5
 
6
6
  // src/removal-scope.ts
7
7
  var namesAppLocator = (command) => command.includes(HOOK_LOCATOR_BINARY);
@@ -29,7 +29,7 @@ var codexHooksJson = () => join(codexHome(), "hooks.json");
29
29
  var codexAgentsMd = () => join(codexHome(), "AGENTS.md");
30
30
  var codexSkillDir = () => join(codexHome(), "skills", "pushary");
31
31
  var CODEX_HOOK_EVENTS = [
32
- { event: "PermissionRequest", matcher: "Bash|apply_patch", delivery: "approval", timeout: HOOK_BUDGETS.codex.budgetSeconds, statusMessage: "Waiting for your phone" },
32
+ { event: "PermissionRequest", delivery: "approval", timeout: HOOK_BUDGETS.codex.budgetSeconds, statusMessage: "Waiting for your phone" },
33
33
  { event: "PreToolUse", matcher: "Bash|apply_patch", delivery: "approval", timeout: HOOK_BUDGETS.codex.budgetSeconds, statusMessage: "Checking Pushary policy" },
34
34
  { event: "PostToolUse", matcher: "Bash|apply_patch", delivery: "telemetry", timeout: 10 },
35
35
  { event: "UserPromptSubmit", delivery: "telemetry", timeout: 10 },
@@ -286,6 +286,7 @@ export {
286
286
  codexHooksJson,
287
287
  codexAgentsMd,
288
288
  codexSkillDir,
289
+ CODEX_HOOK_EVENTS,
289
290
  addCodexMcpServer,
290
291
  readCodexMcpAuth,
291
292
  addCodexHooks,