@pushary/agent-hooks 0.95.2 → 0.95.5

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 (98) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +1 -1
  3. package/data/cursor-plugin/.cursor-plugin/plugin.json +1 -1
  4. package/data/cursor-plugin/CHANGELOG.md +5 -0
  5. package/data/cursor-plugin/README.md +6 -8
  6. package/data/cursor-plugin/hooks/hooks.json +56 -1
  7. package/data/cursor-plugin/scripts/pushary-gate.mjs +120 -71
  8. package/data/vscode-plugin/.claude-plugin/plugin.json +1 -1
  9. package/data/vscode-plugin/CHANGELOG.md +5 -0
  10. package/data/vscode-plugin/README.md +7 -15
  11. package/data/vscode-plugin/hooks/hooks.json +49 -0
  12. package/data/vscode-plugin/scripts/pushary-gate.mjs +208 -88
  13. package/dist/bin/pushary-bell-hook.js +20 -22
  14. package/dist/bin/pushary-bell.js +9 -11
  15. package/dist/bin/pushary-claude.js +58 -46
  16. package/dist/bin/pushary-clean.js +21 -20
  17. package/dist/bin/pushary-codex-bridge.js +120 -25
  18. package/dist/bin/pushary-codex-hook.js +43 -63
  19. package/dist/bin/pushary-codex.js +11 -10
  20. package/dist/bin/pushary-connect.js +8 -10
  21. package/dist/bin/pushary-cowork.js +5 -5
  22. package/dist/bin/pushary-daemon.js +26 -28
  23. package/dist/bin/pushary-disconnect.js +11 -10
  24. package/dist/bin/pushary-doctor.js +25 -25
  25. package/dist/bin/pushary-elicitation-hook.js +30 -31
  26. package/dist/bin/pushary-gemini-bridge.js +9 -11
  27. package/dist/bin/pushary-gemini-hook.js +45 -79
  28. package/dist/bin/pushary-hook.js +27 -29
  29. package/dist/bin/pushary-login.js +57 -31
  30. package/dist/bin/pushary-logout.js +6 -8
  31. package/dist/bin/pushary-mode.js +5 -5
  32. package/dist/bin/pushary-notification-hook.js +22 -24
  33. package/dist/bin/pushary-opencode-hook.js +38 -74
  34. package/dist/bin/pushary-permission-denied-hook.js +25 -27
  35. package/dist/bin/pushary-permission-hook.js +25 -27
  36. package/dist/bin/pushary-post-hook.js +22 -24
  37. package/dist/bin/pushary-prompt-hook.js +22 -24
  38. package/dist/bin/pushary-session-end-hook.js +22 -24
  39. package/dist/bin/pushary-session-start-hook.js +25 -27
  40. package/dist/bin/pushary-setup.js +78 -55
  41. package/dist/bin/pushary-stats.js +4 -4
  42. package/dist/bin/pushary-status.js +10 -12
  43. package/dist/bin/pushary-stop-hook.js +22 -24
  44. package/dist/bin/pushary-stopfailure-hook.js +22 -24
  45. package/dist/bin/pushary-suggestions.js +2 -2
  46. package/dist/bin/pushary-transcript-register.js +18 -20
  47. package/dist/bin/pushary-transcripts.js +7 -9
  48. package/dist/bin/pushary-upgrade.js +17 -19
  49. package/dist/bin/pushary-wait.js +5 -5
  50. package/dist/bin/pushary.js +1 -1
  51. package/dist/{chunk-7LUFYRH4.js → chunk-2D7BAOMY.js} +7 -7
  52. package/dist/{chunk-3ZUTNTZA.js → chunk-2ZTL3PGN.js} +1 -1
  53. package/dist/{chunk-EPFDN2ZT.js → chunk-32RMQO5Z.js} +1 -1
  54. package/dist/{chunk-EZIEXHYM.js → chunk-3AJIK5AO.js} +5 -2
  55. package/dist/{chunk-IPHOUBTU.js → chunk-4PX4QK2Z.js} +1 -1
  56. package/dist/{chunk-UW4LWAWY.js → chunk-4RF52LSU.js} +8 -8
  57. package/dist/{chunk-UOTSWS5N.js → chunk-67YNQ3QA.js} +43 -98
  58. package/dist/{chunk-G4TL4YWH.js → chunk-6AEM5RCS.js} +1 -1
  59. package/dist/{chunk-G24SEVCI.js → chunk-6VANQN5Q.js} +3 -7
  60. package/dist/{chunk-CKOLDI47.js → chunk-BDI2MXTT.js} +2 -2
  61. package/dist/{chunk-YKLCWWEI.js → chunk-BMANRGJQ.js} +1 -1
  62. package/dist/{chunk-2XUPP4WM.js → chunk-CUEZMPLJ.js} +1 -1
  63. package/dist/{chunk-A4R6BZCK.js → chunk-D62EONG3.js} +1 -1
  64. package/dist/{chunk-ZDAT5KMU.js → chunk-D6PRC3YQ.js} +1 -1
  65. package/dist/{chunk-MOY5X6HJ.js → chunk-DLLAWCQB.js} +1 -1
  66. package/dist/{chunk-DSTLA2TC.js → chunk-GOPIMNVC.js} +1 -1
  67. package/dist/{chunk-CH6DTGQR.js → chunk-HBHEX3JQ.js} +3 -3
  68. package/dist/{chunk-DNVDKHQD.js → chunk-HCMM3BOW.js} +226 -58
  69. package/dist/{chunk-76XICHYT.js → chunk-HGUZOUB2.js} +4 -4
  70. package/dist/{chunk-E3NF5CL2.js → chunk-HQIVR7MM.js} +6 -3
  71. package/dist/{chunk-JDFB3YKG.js → chunk-I6GZX3FT.js} +3 -3
  72. package/dist/{chunk-3OXHYPZG.js → chunk-IM2A5H33.js} +4 -1
  73. package/dist/{chunk-V27AHID3.js → chunk-IVQMNLSA.js} +3 -14
  74. package/dist/{chunk-4LL6LG5X.js → chunk-KAVDAZ7A.js} +43 -1
  75. package/dist/chunk-KBITFQST.js +6 -0
  76. package/dist/{chunk-AMZJ3CKW.js → chunk-KI4PA7MQ.js} +1 -1
  77. package/dist/{chunk-IAEXPTXA.js → chunk-N4VNLISN.js} +2 -2
  78. package/dist/{chunk-VX3BWMC5.js → chunk-OEKFD5BC.js} +1 -1
  79. package/dist/{chunk-QF4RI5CD.js → chunk-OI3DJA7N.js} +1 -1
  80. package/dist/{chunk-WB2C7VPF.js → chunk-OW6AZDY2.js} +3 -3
  81. package/dist/{chunk-4XXUHWIK.js → chunk-QHWXUNMN.js} +1 -1
  82. package/dist/{chunk-M7WWEFG6.js → chunk-QQGNFHIK.js} +2 -2
  83. package/dist/{chunk-YJAQRKH5.js → chunk-RLXVW4AM.js} +1 -1
  84. package/dist/{chunk-GUVCYYDK.js → chunk-S5CMLR35.js} +94 -15
  85. package/dist/{chunk-EUAQ4YAN.js → chunk-UCS65VVR.js} +1 -1
  86. package/dist/{chunk-WM5XMCID.js → chunk-VOSGZ6PH.js} +4 -4
  87. package/dist/{chunk-QUE76J4H.js → chunk-VTO3TNIB.js} +1 -1
  88. package/dist/{chunk-6FAHPGCZ.js → chunk-WDKHZRAS.js} +183 -25
  89. package/dist/{chunk-5GLPYGKS.js → chunk-WRDLHR2F.js} +2 -2
  90. package/dist/{chunk-RVSEGFPD.js → chunk-XLOELLAS.js} +9 -4
  91. package/dist/chunk-XS33QBJA.js +112 -0
  92. package/dist/{chunk-FQUV5KTM.js → chunk-Z2UWFEJW.js} +8 -8
  93. package/dist/src/index.d.ts +8 -1
  94. package/dist/src/index.js +11 -12
  95. package/package.json +1 -1
  96. package/dist/chunk-BC3VCZ3E.js +0 -86
  97. package/dist/chunk-RER3FRTD.js +0 -265
  98. package/dist/chunk-X7YWD6IS.js +0 -44
@@ -0,0 +1,112 @@
1
+ import {
2
+ callMcpTool
3
+ } from "./chunk-EYRGBYZG.js";
4
+ import {
5
+ installProxyDispatcher
6
+ } from "./chunk-SAF6HGAA.js";
7
+
8
+ // src/api.ts
9
+ import { randomUUID } from "crypto";
10
+
11
+ // src/validate.ts
12
+ var isPolicyConfig = (data) => {
13
+ if (!data || typeof data !== "object") return false;
14
+ const d = data;
15
+ return (d.resolvedAgent === void 0 || typeof d.resolvedAgent === "string") && Array.isArray(d.policies) && typeof d.defaultTimeoutSeconds === "number" && typeof d.defaultTimeoutAction === "string";
16
+ };
17
+ var isAskUserResponse = (data) => {
18
+ if (!data || typeof data !== "object") return false;
19
+ const d = data;
20
+ return typeof d.correlationId === "string" && typeof d.status === "string" && (d.answered === void 0 || typeof d.answered === "boolean") && (d.value === void 0 || typeof d.value === "string") && (d.note === void 0 || typeof d.note === "string") && (d.waitEndsAt === void 0 || typeof d.waitEndsAt === "string");
21
+ };
22
+ var isWaitForAnswerResponse = (data) => {
23
+ if (!data || typeof data !== "object") return false;
24
+ const d = data;
25
+ return typeof d.answered === "boolean";
26
+ };
27
+
28
+ // src/api.ts
29
+ installProxyDispatcher();
30
+ var DECISION_STOP_REASON = "Approval was cancelled or became unavailable. Retry only after resolving it.";
31
+ var isDecisionStopped = (result) => result.handoffAction === "stop" || ["cancelled", "unavailable", "stopped", "missing"].includes(result.status ?? "");
32
+ var isLocalQuestionHandoff = (result) => result.suppressed === true || result.noDevices === true || result.status === "terminal" || result.status === "notified";
33
+ var STOPPED = { answered: false, handoffAction: "stop" };
34
+ var askUser = async (apiKey, params, timeoutMs = 15e3) => {
35
+ const identified = params.toolUseId || params.requestId ? params : { ...params, requestId: randomUUID() };
36
+ const result = await callMcpTool(apiKey, "ask_user", { ...identified, wait: false }, { maxRetries: 3, timeoutMs });
37
+ if (!isAskUserResponse(result)) throw new Error("Invalid ask_user response");
38
+ return result;
39
+ };
40
+ var waitForAnswer = async (apiKey, correlationId, timeoutMs = 3e4, signal) => {
41
+ const result = await callMcpTool(apiKey, "wait_for_answer", {
42
+ correlationId,
43
+ timeoutMs
44
+ }, { timeoutMs: timeoutMs + 1e4, signal });
45
+ if (!isWaitForAnswerResponse(result)) throw new Error("Invalid wait_for_answer response");
46
+ return result;
47
+ };
48
+ var WAIT_RESPONSE_GRACE_MS = 2e3;
49
+ var MIN_LONG_POLL_MS = 1e3;
50
+ var MAX_LONG_POLL_MS = 3e4;
51
+ var nextLongPollMs = (deadlineMs, nowMs) => {
52
+ const hold = Math.min(deadlineMs - nowMs - WAIT_RESPONSE_GRACE_MS, MAX_LONG_POLL_MS);
53
+ return hold >= MIN_LONG_POLL_MS ? hold : null;
54
+ };
55
+ var pollForAnswer = async (apiKey, correlationId, deadlineMs, pollInterval = 2e3) => {
56
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
57
+ while (Date.now() < deadlineMs) {
58
+ const hold = nextLongPollMs(deadlineMs, Date.now());
59
+ if (hold === null) {
60
+ await sleep(Math.max(0, deadlineMs - Date.now()));
61
+ continue;
62
+ }
63
+ let answer;
64
+ try {
65
+ answer = await waitForAnswer(apiKey, correlationId, hold, AbortSignal.timeout(Math.max(1, deadlineMs - Date.now())));
66
+ } catch {
67
+ const late = await withdrawQuestion(apiKey, correlationId);
68
+ return late.answered ? late : STOPPED;
69
+ }
70
+ if (isDecisionStopped(answer) || answer.answered) return answer;
71
+ if (answer.status === "expired") return STOPPED;
72
+ await sleep(Math.min(pollInterval, Math.max(0, deadlineMs - Date.now())));
73
+ }
74
+ return withdrawQuestion(apiKey, correlationId);
75
+ };
76
+ var cancelQuestion = async (apiKey, correlationId, signal) => {
77
+ return callMcpTool(apiKey, "cancel_question", { correlationId }, { signal, timeoutMs: 4e3, maxRetries: 1 });
78
+ };
79
+ var withdrawQuestion = async (apiKey, correlationId, transport = { cancelQuestion, waitForAnswer }) => {
80
+ try {
81
+ const cancelled = await transport.cancelQuestion(apiKey, correlationId, AbortSignal.timeout(4e3));
82
+ if (!cancelled || isDecisionStopped(cancelled)) return STOPPED;
83
+ if (cancelled.cancelled === true) return { answered: false, status: "withdrawn" };
84
+ const late = await transport.waitForAnswer(apiKey, correlationId, 1e3, AbortSignal.timeout(2e3));
85
+ if (late.answered || isDecisionStopped(late)) return late;
86
+ return late.status === "expired" ? { answered: false, status: "withdrawn" } : STOPPED;
87
+ } catch {
88
+ return STOPPED;
89
+ }
90
+ };
91
+ var waitForQuestion = async (apiKey, question, deadlineMs, pollInterval = 2e3) => {
92
+ if (isDecisionStopped(question) || question.answered) return { ...question, answered: !!question.answered };
93
+ if (question.status === "expired") return STOPPED;
94
+ return isLocalQuestionHandoff(question) ? withdrawQuestion(apiKey, question.correlationId) : pollForAnswer(apiKey, question.correlationId, deadlineMs, pollInterval);
95
+ };
96
+ var sendNotification = async (apiKey, params, timeoutMs = 15e3, signal) => {
97
+ await callMcpTool(apiKey, "send_notification", { ...params }, { maxRetries: signal ? 1 : 3, timeoutMs, signal });
98
+ };
99
+
100
+ export {
101
+ isPolicyConfig,
102
+ DECISION_STOP_REASON,
103
+ isDecisionStopped,
104
+ isLocalQuestionHandoff,
105
+ askUser,
106
+ waitForAnswer,
107
+ nextLongPollMs,
108
+ cancelQuestion,
109
+ withdrawQuestion,
110
+ waitForQuestion,
111
+ sendNotification
112
+ };
@@ -1,16 +1,16 @@
1
1
  import {
2
2
  detectClaudeVersion
3
- } from "./chunk-G4TL4YWH.js";
3
+ } from "./chunk-6AEM5RCS.js";
4
4
  import {
5
5
  addClaudeMcpServer,
6
6
  addPusharyHooks,
7
7
  addPusharyToolPermissions
8
- } from "./chunk-76XICHYT.js";
8
+ } from "./chunk-HGUZOUB2.js";
9
9
  import {
10
10
  claudeWired,
11
11
  codexWired,
12
12
  geminiWired
13
- } from "./chunk-CH6DTGQR.js";
13
+ } from "./chunk-HBHEX3JQ.js";
14
14
  import {
15
15
  CLAUDE_HOOK_EVENTS,
16
16
  GEMINI_HOOK_BINARY,
@@ -21,10 +21,10 @@ import {
21
21
  needsReregistration,
22
22
  registrationSignatures,
23
23
  supportedClaudeEvents
24
- } from "./chunk-7LUFYRH4.js";
24
+ } from "./chunk-2D7BAOMY.js";
25
25
  import {
26
26
  guardBinary
27
- } from "./chunk-EZIEXHYM.js";
27
+ } from "./chunk-3AJIK5AO.js";
28
28
  import {
29
29
  resolveGlobalBinDir,
30
30
  resolveGlobalBinary
@@ -40,14 +40,14 @@ import {
40
40
  codexHooksJson,
41
41
  hasCodexHooks,
42
42
  vscodePluginMcp
43
- } from "./chunk-WB2C7VPF.js";
43
+ } from "./chunk-OW6AZDY2.js";
44
44
  import {
45
45
  readJsonSafe,
46
46
  writeJsonAtomic
47
- } from "./chunk-4LL6LG5X.js";
47
+ } from "./chunk-KAVDAZ7A.js";
48
48
  import {
49
49
  HOOK_LOCATOR_BINARY
50
- } from "./chunk-6FAHPGCZ.js";
50
+ } from "./chunk-WDKHZRAS.js";
51
51
 
52
52
  // src/reapply.ts
53
53
  import { join } from "path";
@@ -160,6 +160,7 @@ interface AskUserParams {
160
160
  interface AskUserResponse {
161
161
  correlationId: string;
162
162
  status: string;
163
+ handoffAction?: string;
163
164
  delivery?: {
164
165
  web: number;
165
166
  mobile: number;
@@ -173,13 +174,19 @@ interface AskUserResponse {
173
174
  answerUrl?: string;
174
175
  }
175
176
  interface AnswerResult {
177
+ status?: string;
178
+ handoffAction?: string;
176
179
  answered: boolean;
177
180
  value?: string;
178
181
  note?: string;
179
182
  }
180
183
  declare const askUser: (apiKey: string, params: AskUserParams, timeoutMs?: number) => Promise<AskUserResponse>;
181
184
  declare const waitForAnswer: (apiKey: string, correlationId: string, timeoutMs?: number, signal?: AbortSignal) => Promise<AnswerResult>;
182
- declare const cancelQuestion: (apiKey: string, correlationId: string, signal?: AbortSignal) => Promise<void>;
185
+ declare const cancelQuestion: (apiKey: string, correlationId: string, signal?: AbortSignal) => Promise<{
186
+ cancelled?: boolean;
187
+ status?: string;
188
+ handoffAction?: string;
189
+ }>;
183
190
 
184
191
  declare const getApiKey: () => string;
185
192
  declare const getBaseUrl: () => string;
package/dist/src/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  handlePreToolUse
3
- } from "../chunk-UOTSWS5N.js";
4
- import "../chunk-A4R6BZCK.js";
5
- import "../chunk-EUAQ4YAN.js";
3
+ } from "../chunk-67YNQ3QA.js";
4
+ import "../chunk-D62EONG3.js";
5
+ import "../chunk-UCS65VVR.js";
6
6
  import "../chunk-YHG74UFF.js";
7
7
  import {
8
8
  fetchAccountPublicKey
@@ -15,29 +15,28 @@ import {
15
15
  handlePostToolUse,
16
16
  handleStop,
17
17
  reportEvent
18
- } from "../chunk-DNVDKHQD.js";
18
+ } from "../chunk-HCMM3BOW.js";
19
19
  import {
20
20
  askUser,
21
21
  cancelQuestion,
22
22
  waitForAnswer
23
- } from "../chunk-RER3FRTD.js";
24
- import "../chunk-CKOLDI47.js";
25
- import "../chunk-V27AHID3.js";
26
- import "../chunk-IAEXPTXA.js";
27
- import "../chunk-WB2C7VPF.js";
23
+ } from "../chunk-XS33QBJA.js";
24
+ import "../chunk-BDI2MXTT.js";
25
+ import "../chunk-IVQMNLSA.js";
26
+ import "../chunk-N4VNLISN.js";
27
+ import "../chunk-OW6AZDY2.js";
28
28
  import "../chunk-EYRGBYZG.js";
29
29
  import "../chunk-SAF6HGAA.js";
30
30
  import "../chunk-7QLSKOSU.js";
31
31
  import "../chunk-KZERVKTD.js";
32
- import "../chunk-X7YWD6IS.js";
33
- import "../chunk-4LL6LG5X.js";
32
+ import "../chunk-KAVDAZ7A.js";
34
33
  import {
35
34
  getApiKey,
36
35
  getBaseUrl
37
36
  } from "../chunk-2UMNXADU.js";
38
37
  import {
39
38
  resolvePolicy
40
- } from "../chunk-6FAHPGCZ.js";
39
+ } from "../chunk-WDKHZRAS.js";
41
40
  export {
42
41
  askUser,
43
42
  cancelQuestion,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pushary/agent-hooks",
3
- "version": "0.95.2",
3
+ "version": "0.95.5",
4
4
  "description": "Permission hooks for AI coding agents: route tool approvals through Pushary push notifications",
5
5
  "keywords": [
6
6
  "pushary",
@@ -1,86 +0,0 @@
1
- // src/shell-alias.ts
2
- import { homedir } from "os";
3
- import { existsSync, readFileSync, appendFileSync, writeFileSync } from "fs";
4
- import { join } from "path";
5
- var MARKER_START = "# >>> pushary >>>";
6
- var MARKER_END = "# <<< pushary <<<";
7
- var CANDIDATE_RCS = [".zshrc", ".zprofile", ".bashrc", ".bash_profile"];
8
- var aliasBlock = () => `${MARKER_START}
9
- # Make \`claude\` reachable from your phone when idle (runs it through Pushary).
10
- # Remove this block, or run \`pushary clean\`, to undo.
11
- alias claude='pushary claude'
12
- ${MARKER_END}
13
- `;
14
- var hasClaudeAlias = (content) => content.includes(MARKER_START) || /alias\s+claude=['"]?pushary claude/.test(content);
15
- var stripPusharyBlock = (content) => {
16
- const lines = content.split("\n");
17
- const out = [];
18
- let inBlock = false;
19
- for (const line of lines) {
20
- const trimmed = line.trim();
21
- if (trimmed === MARKER_START) {
22
- inBlock = true;
23
- continue;
24
- }
25
- if (trimmed === MARKER_END) {
26
- inBlock = false;
27
- continue;
28
- }
29
- if (!inBlock) out.push(line);
30
- }
31
- return out.join("\n").replace(/\n{3,}/g, "\n\n");
32
- };
33
- var resolveShellRc = () => {
34
- if (process.platform === "win32") return null;
35
- const shell = (process.env.SHELL ?? "").split("/").pop() ?? "";
36
- const home = homedir();
37
- if (shell === "zsh") return { path: join(home, ".zshrc"), shell };
38
- if (shell === "bash") {
39
- const bashrc = join(home, ".bashrc");
40
- const profile = join(home, ".bash_profile");
41
- if (process.platform === "darwin") return { path: profile, shell };
42
- return { path: existsSync(bashrc) ? bashrc : profile, shell };
43
- }
44
- return null;
45
- };
46
- var installClaudeAlias = () => {
47
- const rc = resolveShellRc();
48
- if (!rc) return { installed: false, reason: "unsupported-shell" };
49
- let content = "";
50
- try {
51
- content = readFileSync(rc.path, "utf-8");
52
- } catch {
53
- }
54
- if (hasClaudeAlias(content)) return { installed: false, path: rc.path, reason: "exists" };
55
- const gap = content.length > 0 && !content.endsWith("\n") ? "\n\n" : content.length > 0 ? "\n" : "";
56
- appendFileSync(rc.path, `${gap}${aliasBlock()}`, "utf-8");
57
- return { installed: true, path: rc.path };
58
- };
59
- var removeClaudeAlias = () => {
60
- const removed = [];
61
- for (const name of CANDIDATE_RCS) {
62
- const path = join(homedir(), name);
63
- if (!existsSync(path)) continue;
64
- let content;
65
- try {
66
- content = readFileSync(path, "utf-8");
67
- } catch {
68
- continue;
69
- }
70
- const stripped = stripPusharyBlock(content);
71
- if (stripped !== content) {
72
- try {
73
- writeFileSync(path, stripped, "utf-8");
74
- removed.push(path);
75
- } catch {
76
- }
77
- }
78
- }
79
- return removed;
80
- };
81
-
82
- export {
83
- resolveShellRc,
84
- installClaudeAlias,
85
- removeClaudeAlias
86
- };
@@ -1,265 +0,0 @@
1
- import {
2
- callMcpTool
3
- } from "./chunk-EYRGBYZG.js";
4
- import {
5
- installProxyDispatcher
6
- } from "./chunk-SAF6HGAA.js";
7
-
8
- // src/api.ts
9
- import { randomUUID } from "crypto";
10
-
11
- // src/validate.ts
12
- var isPolicyConfig = (data) => {
13
- if (!data || typeof data !== "object") return false;
14
- const d = data;
15
- return Array.isArray(d.policies) && typeof d.defaultTimeoutSeconds === "number" && typeof d.defaultTimeoutAction === "string";
16
- };
17
- var isAskUserResponse = (data) => {
18
- if (!data || typeof data !== "object") return false;
19
- const d = data;
20
- return typeof d.correlationId === "string" && typeof d.status === "string" && (d.answered === void 0 || typeof d.answered === "boolean") && (d.value === void 0 || typeof d.value === "string") && (d.note === void 0 || typeof d.note === "string") && (d.waitEndsAt === void 0 || typeof d.waitEndsAt === "string");
21
- };
22
- var isWaitForAnswerResponse = (data) => {
23
- if (!data || typeof data !== "object") return false;
24
- const d = data;
25
- return typeof d.answered === "boolean";
26
- };
27
-
28
- // src/api.ts
29
- installProxyDispatcher();
30
- var askUser = async (apiKey, params, timeoutMs = 15e3) => {
31
- const identified = params.toolUseId || params.requestId ? params : { ...params, requestId: randomUUID() };
32
- const result = await callMcpTool(apiKey, "ask_user", { ...identified, wait: false }, { maxRetries: 3, timeoutMs });
33
- if (!isAskUserResponse(result)) throw new Error("Invalid ask_user response");
34
- return result;
35
- };
36
- var waitForAnswer = async (apiKey, correlationId, timeoutMs = 3e4, signal) => {
37
- const result = await callMcpTool(apiKey, "wait_for_answer", {
38
- correlationId,
39
- timeoutMs
40
- }, { timeoutMs: timeoutMs + 1e4, signal });
41
- if (!isWaitForAnswerResponse(result)) throw new Error("Invalid wait_for_answer response");
42
- return result;
43
- };
44
- var pollForAnswer = async (apiKey, correlationId, deadlineMs, pollInterval = 2e3) => {
45
- const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
46
- while (Date.now() < deadlineMs) {
47
- const remaining = Math.min(Math.max(deadlineMs - Date.now(), 1e3), 3e4);
48
- let answer;
49
- try {
50
- answer = await waitForAnswer(apiKey, correlationId, remaining);
51
- } catch {
52
- if (Date.now() + pollInterval >= deadlineMs) break;
53
- await sleep(pollInterval);
54
- continue;
55
- }
56
- if (answer.answered) return answer;
57
- if (Date.now() + pollInterval >= deadlineMs) break;
58
- await sleep(pollInterval);
59
- }
60
- return { answered: false };
61
- };
62
- var cancelQuestion = async (apiKey, correlationId, signal) => {
63
- await callMcpTool(apiKey, "cancel_question", { correlationId }, { signal });
64
- };
65
- var sendNotification = async (apiKey, params, timeoutMs = 15e3, signal) => {
66
- await callMcpTool(apiKey, "send_notification", { ...params }, { maxRetries: signal ? 1 : 3, timeoutMs, signal });
67
- };
68
-
69
- // src/pending.ts
70
- import { join } from "path";
71
- import { homedir, tmpdir } from "os";
72
- import { createHash } from "crypto";
73
- import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync, renameSync, unlinkSync, rmSync, statSync } from "fs";
74
- var PENDING_DIR = join(tmpdir(), "pushary-pending");
75
- var commandRoot = () => process.env.PUSHARY_COMMAND_DIR?.trim() || join(homedir(), ".pushary", "run", "commands");
76
- var DEFAULT_SESSION = "_no_session";
77
- var GRACE_MS = 10 * 60 * 1e3;
78
- var sanitize = (sessionId) => sessionId.replace(/[^A-Za-z0-9_-]/g, "_").slice(0, 128) || DEFAULT_SESSION;
79
- var dirFor = (sessionId) => join(PENDING_DIR, sanitize(sessionId));
80
- var commandDirFor = (sessionId, namespace = "") => join(
81
- commandRoot(),
82
- createHash("sha256").update(namespace).digest("hex").slice(0, 16),
83
- sanitize(sessionId)
84
- );
85
- var legacyCommandFile = (sessionId, namespace = "") => join(commandDirFor(sessionId, namespace), "command.json");
86
- var commandFile = (sessionId, id, namespace = "") => join(commandDirFor(sessionId, namespace), `${createHash("sha256").update(id).digest("hex")}.json`);
87
- var readCommandFile = (file) => {
88
- try {
89
- const value = JSON.parse(readFileSync(file, "utf-8"));
90
- if (typeof value.id !== "string" || typeof value.text !== "string" || typeof value.expiresAtMs !== "number") return null;
91
- if (value.expiresAtMs <= Date.now()) {
92
- unlinkSync(file);
93
- return null;
94
- }
95
- return {
96
- ...value,
97
- delivered: value.delivered === true,
98
- ...value.failed === true ? { failed: true } : {}
99
- };
100
- } catch {
101
- return null;
102
- }
103
- };
104
- var readPendingCommandById = (sessionId, id, namespace = "") => {
105
- const current = readCommandFile(commandFile(sessionId, id, namespace));
106
- if (current?.id === id) return current;
107
- const legacy = readCommandFile(legacyCommandFile(sessionId, namespace));
108
- return legacy?.id === id ? legacy : null;
109
- };
110
- var pruneExpiredCommands = (dir) => {
111
- let files;
112
- try {
113
- files = readdirSync(dir).filter((file) => file.endsWith(".json"));
114
- } catch {
115
- return [];
116
- }
117
- return files.flatMap((file) => {
118
- const command = readCommandFile(join(dir, file));
119
- return command ? [command] : [];
120
- });
121
- };
122
- var pruneExpiredCommandStore = () => {
123
- let namespaces;
124
- try {
125
- namespaces = readdirSync(commandRoot(), { withFileTypes: true });
126
- } catch {
127
- return;
128
- }
129
- for (const namespace of namespaces.filter((entry) => entry.isDirectory())) {
130
- const namespaceDir = join(commandRoot(), namespace.name);
131
- let sessions;
132
- try {
133
- sessions = readdirSync(namespaceDir, { withFileTypes: true });
134
- } catch {
135
- continue;
136
- }
137
- for (const session of sessions.filter((entry) => entry.isDirectory())) {
138
- pruneExpiredCommands(join(namespaceDir, session.name));
139
- }
140
- }
141
- };
142
- var readPendingCommand = (sessionId, namespace = "") => {
143
- const commands = pruneExpiredCommands(commandDirFor(sessionId, namespace));
144
- return commands.find((command) => !command.delivered && !command.failed) ?? commands[0] ?? null;
145
- };
146
- var savePendingCommand = (sessionId, command, namespace = "") => {
147
- const dir = commandDirFor(sessionId, namespace);
148
- try {
149
- mkdirSync(dir, { recursive: true, mode: 448 });
150
- pruneExpiredCommandStore();
151
- if (readPendingCommandById(sessionId, command.id, namespace)) return false;
152
- writeFileSync(commandFile(sessionId, command.id, namespace), JSON.stringify(command), { encoding: "utf-8", mode: 384, flag: "wx" });
153
- return true;
154
- } catch {
155
- return false;
156
- }
157
- };
158
- var updatePendingCommand = (sessionId, id, update, namespace = "") => {
159
- const command = readPendingCommandById(sessionId, id, namespace);
160
- if (!command) return false;
161
- const currentFile = commandFile(sessionId, id, namespace);
162
- const file = existsSync(currentFile) ? currentFile : legacyCommandFile(sessionId, namespace);
163
- const temporary = `${file}.${process.pid}.tmp`;
164
- try {
165
- writeFileSync(temporary, JSON.stringify({ ...command, ...update, text: "" }), { encoding: "utf-8", mode: 384 });
166
- renameSync(temporary, file);
167
- return true;
168
- } catch {
169
- try {
170
- unlinkSync(temporary);
171
- } catch {
172
- }
173
- return false;
174
- }
175
- };
176
- var markPendingCommandDelivered = (sessionId, id, namespace = "") => updatePendingCommand(sessionId, id, { delivered: true, failed: void 0 }, namespace);
177
- var markPendingCommandFailed = (sessionId, id, namespace = "") => updatePendingCommand(sessionId, id, { delivered: false, failed: true }, namespace);
178
- var removePendingCommand = (sessionId, id, namespace = "") => {
179
- if (!readPendingCommandById(sessionId, id, namespace)) return;
180
- try {
181
- unlinkSync(commandFile(sessionId, id, namespace));
182
- } catch {
183
- }
184
- try {
185
- if (readCommandFile(legacyCommandFile(sessionId, namespace))?.id === id) {
186
- unlinkSync(legacyCommandFile(sessionId, namespace));
187
- }
188
- } catch {
189
- }
190
- };
191
- var isDefaultSession = (sessionId) => sanitize(sessionId) === DEFAULT_SESSION;
192
- var savePendingQuestion = (sessionId, correlationId, toolUseId) => {
193
- const dir = dirFor(sessionId);
194
- if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
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
- });
213
- };
214
- var listPendingQuestions = (sessionId) => {
215
- const dir = dirFor(sessionId);
216
- let files;
217
- try {
218
- files = readdirSync(dir);
219
- } catch {
220
- return [];
221
- }
222
- if (!isDefaultSession(sessionId)) return files;
223
- const cutoff = Date.now() - GRACE_MS;
224
- return files.filter((name) => {
225
- try {
226
- return statSync(join(dir, name)).mtimeMs < cutoff;
227
- } catch {
228
- return false;
229
- }
230
- });
231
- };
232
- var removePendingQuestion = (sessionId, correlationId) => {
233
- try {
234
- unlinkSync(join(dirFor(sessionId), correlationId));
235
- } catch {
236
- }
237
- };
238
- var removePendingSession = (sessionId) => {
239
- try {
240
- rmSync(dirFor(sessionId), { recursive: true, force: true });
241
- } catch {
242
- }
243
- };
244
-
245
- export {
246
- isPolicyConfig,
247
- askUser,
248
- waitForAnswer,
249
- pollForAnswer,
250
- cancelQuestion,
251
- sendNotification,
252
- DEFAULT_SESSION,
253
- readPendingCommandById,
254
- readPendingCommand,
255
- savePendingCommand,
256
- markPendingCommandDelivered,
257
- markPendingCommandFailed,
258
- removePendingCommand,
259
- isDefaultSession,
260
- savePendingQuestion,
261
- listPendingQuestionsForToolUse,
262
- listPendingQuestions,
263
- removePendingQuestion,
264
- removePendingSession
265
- };
@@ -1,44 +0,0 @@
1
- import {
2
- configFilePath
3
- } from "./chunk-2UMNXADU.js";
4
- import {
5
- normalizeInstallSource
6
- } from "./chunk-6FAHPGCZ.js";
7
-
8
- // src/install-source.ts
9
- import { readFileSync } from "fs";
10
- var ENV_OVERRIDE = "PUSHARY_INSTALL_SOURCE";
11
- var PLUGIN_ROOT = "CLAUDE_PLUGIN_ROOT";
12
- var EDITOR_PLUGIN_MARKERS = ["cursor-plugin", "vscode-plugin"];
13
- var detectInstallSource = (env = process.env, argv = process.argv) => {
14
- const declared = env[ENV_OVERRIDE]?.trim();
15
- if (declared) return normalizeInstallSource(declared);
16
- const pluginRoot = env[PLUGIN_ROOT]?.trim();
17
- if (pluginRoot) {
18
- return EDITOR_PLUGIN_MARKERS.some((marker) => pluginRoot.includes(marker)) ? "editor_plugin" : "claude_plugin";
19
- }
20
- const entry = argv[1] ?? "";
21
- if (entry.includes("_npx")) return "npx";
22
- if (entry) return "npm";
23
- return "unknown";
24
- };
25
- var storedInstallSource = () => {
26
- try {
27
- const parsed = JSON.parse(readFileSync(configFilePath(), "utf-8"));
28
- const stored = parsed.installSource;
29
- return typeof stored === "string" && stored ? normalizeInstallSource(stored) : void 0;
30
- } catch {
31
- return void 0;
32
- }
33
- };
34
- var resolveInstallSourceFor = (config) => {
35
- const stored = config.installSource;
36
- if (typeof stored === "string" && stored) return normalizeInstallSource(stored);
37
- return detectInstallSource();
38
- };
39
- var reportedInstallSource = () => storedInstallSource() ?? detectInstallSource();
40
-
41
- export {
42
- resolveInstallSourceFor,
43
- reportedInstallSource
44
- };