@pushary/agent-hooks 0.4.5 → 0.5.0

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.
@@ -1,4 +1,9 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ addClaudeMcpServer,
4
+ addPusharyHooks,
5
+ addPusharyToolPermissions
6
+ } from "../chunk-AC4UYAGX.js";
2
7
 
3
8
  // bin/pushary-setup.ts
4
9
  import { existsSync, readFileSync, writeFileSync, appendFileSync, mkdirSync } from "fs";
@@ -10,7 +15,9 @@ import { fileURLToPath } from "url";
10
15
  var CLAUDE_SETTINGS = join(homedir(), ".claude", "settings.json");
11
16
  var CLAUDE_JSON = join(homedir(), ".claude.json");
12
17
  var CURSOR_MCP = join(".cursor", "mcp.json");
13
- var SKILL_DIR = join(homedir(), ".claude", "skills", "pushary");
18
+ var CURSOR_RULES_DIR = join(".cursor", "rules");
19
+ var CLAUDE_SKILL_DIR = join(homedir(), ".claude", "skills", "pushary");
20
+ var CODEX_SKILL_DIR = join(homedir(), ".codex", "skills", "pushary");
14
21
  var SHELL_FILES = [".zshrc", ".bashrc"].map((f) => join(homedir(), f));
15
22
  var dim = (s) => `\x1B[2m${s}\x1B[0m`;
16
23
  var bold = (s) => `\x1B[1m${s}\x1B[0m`;
@@ -30,7 +37,8 @@ var writeJson = (path, data) => {
30
37
  if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
31
38
  writeFileSync(path, JSON.stringify(data, null, 2) + "\n", "utf-8");
32
39
  };
33
- var spinner = async (label, fn) => {
40
+ var formatError = (err) => err instanceof Error ? err.message : String(err);
41
+ var spinner = async (label, fn, options = {}) => {
34
42
  const frames = [" ", ". ", ".. ", "..."];
35
43
  let i = 0;
36
44
  const interval = setInterval(() => {
@@ -41,10 +49,11 @@ var spinner = async (label, fn) => {
41
49
  clearInterval(interval);
42
50
  process.stdout.write(`\r ${check} ${label}
43
51
  `);
44
- } catch {
52
+ } catch (err) {
45
53
  clearInterval(interval);
46
- process.stdout.write(`\r ${yellow("!")} ${label} ${dim("(skipped)")}
54
+ process.stdout.write(`\r ${yellow("!")} ${label} ${dim(`(${formatError(err)})`)}
47
55
  `);
56
+ if (!options.optional) throw err;
48
57
  }
49
58
  };
50
59
  var getPackageVersion = () => {
@@ -76,74 +85,37 @@ var installGlobally = async () => {
76
85
  execSync("npm install -g @pushary/agent-hooks@latest", { stdio: "ignore" });
77
86
  });
78
87
  };
79
- var addMcpServer = (apiKey) => {
80
- const data = readJson(CLAUDE_JSON);
81
- const mcpServers = data.mcpServers ?? {};
82
- mcpServers.pushary = {
83
- type: "http",
84
- url: "https://pushary.com/api/mcp/mcp",
85
- headers: { Authorization: `Bearer ${apiKey}` }
86
- };
87
- data.mcpServers = mcpServers;
88
- writeJson(CLAUDE_JSON, data);
89
- };
90
- var addPermissionHooks = (settings) => {
91
- const hooks = settings.hooks ?? {};
92
- const preToolUse = hooks.PreToolUse ?? [];
93
- if (!JSON.stringify(preToolUse).includes("pushary-hook")) {
94
- preToolUse.push({
95
- matcher: "Bash|Write|Edit",
96
- hooks: [{
97
- type: "command",
98
- command: "pushary-hook",
99
- timeout: 120
100
- }]
101
- });
102
- hooks.PreToolUse = preToolUse;
103
- }
104
- const postToolUse = hooks.PostToolUse ?? [];
105
- if (!JSON.stringify(postToolUse).includes("pushary-post-hook")) {
106
- postToolUse.push({
107
- matcher: "Bash|Write|Edit",
108
- hooks: [{
109
- type: "command",
110
- command: "pushary-post-hook",
111
- timeout: 10
112
- }]
113
- });
114
- hooks.PostToolUse = postToolUse;
115
- }
116
- const stop = hooks.Stop ?? [];
117
- if (!JSON.stringify(stop).includes("pushary-stop-hook")) {
118
- stop.push({
119
- hooks: [{
120
- type: "command",
121
- command: "pushary-stop-hook",
122
- timeout: 10
123
- }]
124
- });
125
- hooks.Stop = stop;
126
- }
127
- settings.hooks = hooks;
88
+ var _cachedSkillContent = null;
89
+ var fetchSkillContent = async () => {
90
+ if (_cachedSkillContent) return _cachedSkillContent;
91
+ const __dirname = dirname(fileURLToPath(import.meta.url));
92
+ const candidates = [
93
+ join(__dirname, "..", "..", "data", "SKILL.md"),
94
+ join(__dirname, "..", "data", "SKILL.md")
95
+ ];
96
+ const source = candidates.find((path) => existsSync(path));
97
+ if (!source) throw new Error("packaged skill not found");
98
+ _cachedSkillContent = readFileSync(source, "utf-8");
99
+ return _cachedSkillContent;
128
100
  };
129
- var addToolPermissions = (settings) => {
130
- const permissions = settings.permissions ?? {};
131
- const allow = permissions.allow ?? [];
132
- const filtered = allow.filter((r) => !r.includes("pushary"));
133
- const rule = "mcp__pushary__*";
134
- if (!filtered.includes(rule)) filtered.push(rule);
135
- permissions.allow = filtered;
136
- settings.permissions = permissions;
101
+ var installSkillToDir = async (dir, label) => {
102
+ await spinner(label, async () => {
103
+ const content = await fetchSkillContent();
104
+ if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
105
+ writeFileSync(join(dir, "SKILL.md"), content, "utf-8");
106
+ });
137
107
  };
138
- var installSkill = async () => {
139
- await spinner("Installing Pushary skill", async () => {
140
- const res = await fetch("https://raw.githubusercontent.com/pushary/pushary-skill/main/skills/pushary/SKILL.md", {
141
- signal: AbortSignal.timeout(1e4)
142
- });
143
- if (!res.ok) throw new Error(`Failed to fetch skill (${res.status})`);
144
- const content = await res.text();
145
- if (!existsSync(SKILL_DIR)) mkdirSync(SKILL_DIR, { recursive: true });
146
- writeFileSync(join(SKILL_DIR, "SKILL.md"), content, "utf-8");
108
+ var installCursorRule = async () => {
109
+ await spinner("Installing Pushary rules", async () => {
110
+ const content = await fetchSkillContent();
111
+ const body = content.replace(/^---[\s\S]*?---\n*/m, "");
112
+ const mdc = `---
113
+ alwaysApply: true
114
+ ---
115
+
116
+ ${body}`;
117
+ if (!existsSync(CURSOR_RULES_DIR)) mkdirSync(CURSOR_RULES_DIR, { recursive: true });
118
+ writeFileSync(join(CURSOR_RULES_DIR, "pushary.mdc"), mdc, "utf-8");
147
119
  });
148
120
  };
149
121
  var setupClaudeCode = async (apiKey) => {
@@ -152,19 +124,21 @@ var setupClaudeCode = async (apiKey) => {
152
124
  `);
153
125
  const settings = readJson(CLAUDE_SETTINGS);
154
126
  await spinner("Adding MCP server (type: http)", async () => {
155
- addMcpServer(apiKey);
127
+ const data = readJson(CLAUDE_JSON);
128
+ addClaudeMcpServer(data, apiKey);
129
+ writeJson(CLAUDE_JSON, data);
156
130
  });
157
131
  await spinner("Auto-allowing Pushary tools", async () => {
158
- addToolPermissions(settings);
132
+ addPusharyToolPermissions(settings);
159
133
  });
160
134
  await installGlobally();
161
135
  await spinner("Adding hooks (PreToolUse, PostToolUse, Stop)", async () => {
162
- addPermissionHooks(settings);
136
+ addPusharyHooks(settings);
163
137
  });
164
138
  await spinner(`Writing ${CLAUDE_SETTINGS}`, async () => {
165
139
  writeJson(CLAUDE_SETTINGS, settings);
166
140
  });
167
- await installSkill();
141
+ await installSkillToDir(CLAUDE_SKILL_DIR, "Installing Pushary skill");
168
142
  console.log();
169
143
  console.log(` ${dim("What this configured:")}`);
170
144
  console.log(` ${dim("\u2022")} MCP server: your agent can send notifications and ask questions`);
@@ -196,10 +170,7 @@ var setupHermes = async (_apiKey) => {
196
170
  console.log(` ${dim(" Install Python 3.10+ and re-run setup to fix.")}`);
197
171
  }
198
172
  await spinner("Enabling plugin", async () => {
199
- try {
200
- execSync("hermes plugins enable pushary", { stdio: "ignore" });
201
- } catch {
202
- }
173
+ execSync("hermes plugins enable pushary", { stdio: "ignore" });
203
174
  });
204
175
  console.log();
205
176
  console.log(` ${dim("What this configured:")}`);
@@ -250,6 +221,7 @@ notify = ["${pusharyCodexPath}"]
250
221
  appendFileSync(codexConfig, notifyLine, "utf-8");
251
222
  }
252
223
  });
224
+ await installSkillToDir(CODEX_SKILL_DIR, "Installing Pushary skill");
253
225
  console.log();
254
226
  console.log(` ${dim("What this configured:")}`);
255
227
  console.log(` ${dim("\u2022")} MCP server: Codex can send notifications and ask questions`);
@@ -270,7 +242,7 @@ var setupCursor = async (apiKey) => {
270
242
  config.mcpServers = mcpServers;
271
243
  writeJson(CURSOR_MCP, config);
272
244
  });
273
- await installSkill();
245
+ await installCursorRule();
274
246
  };
275
247
  var saveApiKey = async (apiKey) => {
276
248
  await spinner("Saving API key to shell profile", async () => {
@@ -379,4 +351,10 @@ var main = async () => {
379
351
  console.log(` ${dim("3.")} Run ${cyan("npx @pushary/agent-hooks doctor")} to verify`);
380
352
  console.log();
381
353
  };
382
- main();
354
+ main().catch((err) => {
355
+ console.log();
356
+ console.log(` ${yellow("!")} Setup failed: ${formatError(err)}`);
357
+ console.log(` ${dim("Run")} ${cyan("npx @pushary/agent-hooks doctor")} ${dim("after fixing the issue, then rerun setup.")}`);
358
+ console.log();
359
+ process.exit(1);
360
+ });
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  handleStop
4
- } from "../chunk-EQE6Z4YQ.js";
5
- import "../chunk-VUNL35KE.js";
4
+ } from "../chunk-P4JH2Q7Z.js";
5
+ import "../chunk-KTP2EPVB.js";
6
+ import "../chunk-VIST7ACL.js";
6
7
 
7
8
  // bin/pushary-stop-hook.ts
8
9
  var main = async () => {
@@ -0,0 +1,136 @@
1
+ // src/claude-config.ts
2
+ var PUSHARY_MCP_URL = "https://pushary.com/api/mcp/mcp";
3
+ var PUSHARY_PERMISSION_RULE = "mcp__pushary__*";
4
+ var asRecord = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
5
+ var ensureRecord = (target, key) => {
6
+ const existing = asRecord(target[key]);
7
+ if (existing) return existing;
8
+ const created = {};
9
+ target[key] = created;
10
+ return created;
11
+ };
12
+ var isPusharyPermission = (rule) => typeof rule === "string" && (rule.includes("pushary") || rule.includes("MCP(pushary"));
13
+ var isPusharyHook = (entry) => {
14
+ const hooks = asRecord(entry)?.hooks;
15
+ if (!Array.isArray(hooks)) return false;
16
+ return hooks.some((hook) => {
17
+ const command = String(asRecord(hook)?.command ?? "");
18
+ return command.includes("pushary-hook") || command.includes("pushary-post-hook") || command.includes("pushary-stop-hook");
19
+ });
20
+ };
21
+ var addClaudeMcpServer = (config, apiKey) => {
22
+ const mcpServers = ensureRecord(config, "mcpServers");
23
+ mcpServers.pushary = {
24
+ type: "http",
25
+ url: PUSHARY_MCP_URL,
26
+ headers: { Authorization: `Bearer ${apiKey}` }
27
+ };
28
+ };
29
+ var removeClaudeMcpServers = (config) => {
30
+ let changed = false;
31
+ const removeFrom = (target) => {
32
+ const mcpServers = asRecord(target.mcpServers);
33
+ if (!mcpServers?.pushary) return;
34
+ delete mcpServers.pushary;
35
+ if (Object.keys(mcpServers).length === 0) delete target.mcpServers;
36
+ changed = true;
37
+ };
38
+ removeFrom(config);
39
+ const projects = asRecord(config.projects);
40
+ if (projects) {
41
+ for (const project of Object.values(projects)) {
42
+ const projectConfig = asRecord(project);
43
+ if (projectConfig) removeFrom(projectConfig);
44
+ }
45
+ }
46
+ return changed;
47
+ };
48
+ var addPusharyToolPermissions = (settings) => {
49
+ const permissions = ensureRecord(settings, "permissions");
50
+ const allow = Array.isArray(permissions.allow) ? permissions.allow : [];
51
+ const filtered = allow.filter((rule) => !isPusharyPermission(rule));
52
+ if (!filtered.includes(PUSHARY_PERMISSION_RULE)) {
53
+ filtered.push(PUSHARY_PERMISSION_RULE);
54
+ }
55
+ permissions.allow = filtered;
56
+ };
57
+ var addPusharyHooks = (settings) => {
58
+ const hooks = ensureRecord(settings, "hooks");
59
+ const preToolUse = Array.isArray(hooks.PreToolUse) ? hooks.PreToolUse : [];
60
+ if (!preToolUse.some(isPusharyHook)) {
61
+ preToolUse.push({
62
+ matcher: "Bash|Write|Edit",
63
+ hooks: [{
64
+ type: "command",
65
+ command: "pushary-hook",
66
+ timeout: 120
67
+ }]
68
+ });
69
+ hooks.PreToolUse = preToolUse;
70
+ }
71
+ const postToolUse = Array.isArray(hooks.PostToolUse) ? hooks.PostToolUse : [];
72
+ if (!postToolUse.some(isPusharyHook)) {
73
+ postToolUse.push({
74
+ matcher: "Bash|Write|Edit",
75
+ hooks: [{
76
+ type: "command",
77
+ command: "pushary-post-hook",
78
+ timeout: 10
79
+ }]
80
+ });
81
+ hooks.PostToolUse = postToolUse;
82
+ }
83
+ const stop = Array.isArray(hooks.Stop) ? hooks.Stop : [];
84
+ if (!stop.some(isPusharyHook)) {
85
+ stop.push({
86
+ hooks: [{
87
+ type: "command",
88
+ command: "pushary-stop-hook",
89
+ timeout: 10
90
+ }]
91
+ });
92
+ hooks.Stop = stop;
93
+ }
94
+ };
95
+ var removePusharySettings = (settings) => {
96
+ let changed = removeClaudeMcpServers(settings);
97
+ const permissions = asRecord(settings.permissions);
98
+ if (permissions && Array.isArray(permissions.allow)) {
99
+ const filtered = permissions.allow.filter((rule) => !isPusharyPermission(rule));
100
+ if (filtered.length !== permissions.allow.length) {
101
+ if (filtered.length === 0) {
102
+ delete permissions.allow;
103
+ } else {
104
+ permissions.allow = filtered;
105
+ }
106
+ if (Object.keys(permissions).length === 0) delete settings.permissions;
107
+ changed = true;
108
+ }
109
+ }
110
+ const hooks = asRecord(settings.hooks);
111
+ if (hooks) {
112
+ for (const key of ["PreToolUse", "PostToolUse", "Stop"]) {
113
+ const entries = hooks[key];
114
+ if (!Array.isArray(entries)) continue;
115
+ const filtered = entries.filter((entry) => !isPusharyHook(entry));
116
+ if (filtered.length !== entries.length) {
117
+ if (filtered.length === 0) {
118
+ delete hooks[key];
119
+ } else {
120
+ hooks[key] = filtered;
121
+ }
122
+ changed = true;
123
+ }
124
+ }
125
+ if (Object.keys(hooks).length === 0) delete settings.hooks;
126
+ }
127
+ return changed;
128
+ };
129
+
130
+ export {
131
+ addClaudeMcpServer,
132
+ removeClaudeMcpServers,
133
+ addPusharyToolPermissions,
134
+ addPusharyHooks,
135
+ removePusharySettings
136
+ };
@@ -0,0 +1,195 @@
1
+ import {
2
+ askUser,
3
+ sendNotification,
4
+ waitForAnswer
5
+ } from "./chunk-KTP2EPVB.js";
6
+ import {
7
+ getApiKey,
8
+ getBaseUrl
9
+ } from "./chunk-VIST7ACL.js";
10
+
11
+ // src/policy.ts
12
+ import { createHash } from "crypto";
13
+ import { existsSync, readFileSync, writeFileSync } from "fs";
14
+ import { join } from "path";
15
+ import { tmpdir } from "os";
16
+ var CACHE_TTL_MS = 5 * 60 * 1e3;
17
+ var cacheFile = (apiKey) => {
18
+ const hash = createHash("sha256").update(apiKey).digest("hex").slice(0, 12);
19
+ return join(tmpdir(), `pushary-policy-${hash}.json`);
20
+ };
21
+ var fetchPolicy = async (apiKey) => {
22
+ const baseUrl = getBaseUrl();
23
+ const response = await fetch(`${baseUrl}/api/mcp/policy`, {
24
+ headers: { "Authorization": `Bearer ${apiKey}` },
25
+ signal: AbortSignal.timeout(1e4)
26
+ });
27
+ if (!response.ok) {
28
+ throw new Error(`Failed to fetch policy: ${response.status}`);
29
+ }
30
+ return response.json();
31
+ };
32
+ var getPolicy = async (apiKey) => {
33
+ const path = cacheFile(apiKey);
34
+ if (existsSync(path)) {
35
+ try {
36
+ const stat = readFileSync(path, "utf-8");
37
+ const cached = JSON.parse(stat);
38
+ if (!cached._cachedAt || Date.now() - cached._cachedAt < CACHE_TTL_MS) {
39
+ return cached;
40
+ }
41
+ } catch {
42
+ }
43
+ }
44
+ const policy = await fetchPolicy(apiKey);
45
+ writeFileSync(path, JSON.stringify({ ...policy, _cachedAt: Date.now() }), "utf-8");
46
+ return policy;
47
+ };
48
+ var resolvePolicy = (config, toolName) => {
49
+ const exact = config.policies.find((p) => p.tool === toolName);
50
+ if (exact) return exact;
51
+ const wildcard = config.policies.find((p) => p.tool === "*");
52
+ if (wildcard) return wildcard;
53
+ return {
54
+ tool: toolName,
55
+ timeoutSeconds: config.defaultTimeoutSeconds,
56
+ timeoutAction: config.defaultTimeoutAction,
57
+ mode: config.defaultMode ?? "push_first",
58
+ pushFirstSeconds: config.defaultPushFirstSeconds ?? 10
59
+ };
60
+ };
61
+
62
+ // src/hook.ts
63
+ import { basename } from "path";
64
+ import { writeFileSync as writeFileSync2, mkdirSync, existsSync as existsSync2 } from "fs";
65
+ import { join as join2 } from "path";
66
+ import { tmpdir as tmpdir2 } from "os";
67
+ var describeToolCall = (input) => {
68
+ const { tool_name, tool_input } = input;
69
+ switch (tool_name) {
70
+ case "Bash":
71
+ return `bash: ${tool_input.command ?? "(no command)"}`;
72
+ case "Write":
73
+ return `write file: ${tool_input.file_path ?? "(unknown path)"}`;
74
+ case "Edit":
75
+ return `edit file: ${tool_input.file_path ?? "(unknown path)"}`;
76
+ case "Read":
77
+ return `read file: ${tool_input.file_path ?? "(unknown path)"}`;
78
+ default:
79
+ return `${tool_name}: ${JSON.stringify(tool_input).slice(0, 200)}`;
80
+ }
81
+ };
82
+ var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
83
+ var allow = () => ({
84
+ hookSpecificOutput: {
85
+ hookEventName: "PreToolUse",
86
+ permissionDecision: "allow"
87
+ }
88
+ });
89
+ var deny = (reason) => ({
90
+ hookSpecificOutput: {
91
+ hookEventName: "PreToolUse",
92
+ permissionDecision: "deny",
93
+ permissionDecisionReason: reason
94
+ }
95
+ });
96
+ var ask = (reason) => ({
97
+ hookSpecificOutput: {
98
+ hookEventName: "PreToolUse",
99
+ permissionDecision: "ask",
100
+ ...reason ? { permissionDecisionReason: reason } : {}
101
+ }
102
+ });
103
+ var PENDING_DIR = join2(tmpdir2(), "pushary-pending");
104
+ var savePendingQuestion = (correlationId) => {
105
+ if (!existsSync2(PENDING_DIR)) mkdirSync(PENDING_DIR, { recursive: true });
106
+ writeFileSync2(join2(PENDING_DIR, correlationId), "", "utf-8");
107
+ };
108
+ var pollForAnswer = async (apiKey, correlationId, deadlineMs, pollInterval = 2e3) => {
109
+ while (Date.now() < deadlineMs) {
110
+ const remaining = Math.min(Math.max(deadlineMs - Date.now(), 1e3), 3e4);
111
+ const answer = await waitForAnswer(apiKey, correlationId, remaining);
112
+ if (answer.answered) return answer;
113
+ if (Date.now() + pollInterval >= deadlineMs) break;
114
+ await sleep(pollInterval);
115
+ }
116
+ return { answered: false };
117
+ };
118
+ var handlePushOnly = async (apiKey, description, projectName, timeoutSeconds, timeoutAction) => {
119
+ const result = await askUser(apiKey, {
120
+ question: `Allow ${description}?`,
121
+ type: "confirm",
122
+ context: `Agent wants to run this in ${projectName}`,
123
+ agentName: `Claude Code - ${projectName}`
124
+ });
125
+ const deadline = Date.now() + timeoutSeconds * 1e3;
126
+ const answer = await pollForAnswer(apiKey, result.correlationId, deadline);
127
+ if (answer.answered) {
128
+ return answer.value === "yes" ? allow() : deny("Denied via push notification");
129
+ }
130
+ switch (timeoutAction) {
131
+ case "approve":
132
+ return allow();
133
+ case "deny":
134
+ return deny("No response within timeout");
135
+ default:
136
+ return ask("No push response, asking in terminal");
137
+ }
138
+ };
139
+ var handleTerminalOnly = () => {
140
+ return ask();
141
+ };
142
+ var handlePushFirst = async (apiKey, description, projectName, pushFirstSeconds) => {
143
+ const result = await askUser(apiKey, {
144
+ question: `Allow ${description}?`,
145
+ type: "confirm",
146
+ context: `Agent wants to run this in ${projectName}`,
147
+ agentName: `Claude Code - ${projectName}`
148
+ });
149
+ const deadline = Date.now() + pushFirstSeconds * 1e3;
150
+ const answer = await pollForAnswer(apiKey, result.correlationId, deadline, 1500);
151
+ if (answer.answered) {
152
+ return answer.value === "yes" ? allow() : deny("Denied via push notification");
153
+ }
154
+ savePendingQuestion(result.correlationId);
155
+ return ask("Sent as push notification. You can also approve here.");
156
+ };
157
+ var handleNotifyOnly = async (apiKey, description, projectName) => {
158
+ try {
159
+ await sendNotification(apiKey, {
160
+ title: "Agent needs approval",
161
+ body: description,
162
+ agentName: `Claude Code - ${projectName}`
163
+ });
164
+ } catch {
165
+ }
166
+ return ask();
167
+ };
168
+ var handlePreToolUse = async (input) => {
169
+ const apiKey = getApiKey();
170
+ const policy = await getPolicy(apiKey);
171
+ const toolPolicy = resolvePolicy(policy, input.tool_name);
172
+ if (toolPolicy.timeoutSeconds === 0 && toolPolicy.timeoutAction === "approve") {
173
+ return allow();
174
+ }
175
+ const description = describeToolCall(input);
176
+ const projectName = basename(input.cwd ?? process.cwd());
177
+ switch (toolPolicy.mode) {
178
+ case "push_only":
179
+ return handlePushOnly(apiKey, description, projectName, toolPolicy.timeoutSeconds, toolPolicy.timeoutAction);
180
+ case "terminal_only":
181
+ return handleTerminalOnly();
182
+ case "push_first":
183
+ return handlePushFirst(apiKey, description, projectName, toolPolicy.pushFirstSeconds);
184
+ case "notify_only":
185
+ return handleNotifyOnly(apiKey, description, projectName);
186
+ default:
187
+ return handlePushFirst(apiKey, description, projectName, toolPolicy.pushFirstSeconds);
188
+ }
189
+ };
190
+
191
+ export {
192
+ getPolicy,
193
+ resolvePolicy,
194
+ handlePreToolUse
195
+ };
@@ -0,0 +1,27 @@
1
+ import {
2
+ callMcpTool
3
+ } from "./chunk-VIST7ACL.js";
4
+
5
+ // src/api.ts
6
+ var askUser = async (apiKey, params) => {
7
+ return callMcpTool(apiKey, "ask_user", { ...params });
8
+ };
9
+ var waitForAnswer = async (apiKey, correlationId, timeoutMs = 3e4) => {
10
+ return callMcpTool(apiKey, "wait_for_answer", {
11
+ correlationId,
12
+ timeoutMs
13
+ });
14
+ };
15
+ var cancelQuestion = async (apiKey, correlationId) => {
16
+ await callMcpTool(apiKey, "cancel_question", { correlationId });
17
+ };
18
+ var sendNotification = async (apiKey, params) => {
19
+ await callMcpTool(apiKey, "send_notification", { ...params });
20
+ };
21
+
22
+ export {
23
+ askUser,
24
+ waitForAnswer,
25
+ cancelQuestion,
26
+ sendNotification
27
+ };
@@ -1,11 +1,35 @@
1
+ import {
2
+ cancelQuestion
3
+ } from "./chunk-KTP2EPVB.js";
1
4
  import {
2
5
  getApiKey,
3
6
  getBaseUrl
4
- } from "./chunk-VUNL35KE.js";
7
+ } from "./chunk-VIST7ACL.js";
5
8
 
6
9
  // src/events.ts
7
10
  import { hostname } from "os";
8
11
  import { basename } from "path";
12
+ import { readdirSync, unlinkSync } from "fs";
13
+ import { join } from "path";
14
+ import { tmpdir } from "os";
15
+ var PENDING_DIR = join(tmpdir(), "pushary-pending");
16
+ var cleanupPendingQuestions = async () => {
17
+ try {
18
+ const files = readdirSync(PENDING_DIR);
19
+ const apiKey = getApiKey();
20
+ for (const correlationId of files) {
21
+ try {
22
+ await cancelQuestion(apiKey, correlationId);
23
+ } catch {
24
+ }
25
+ try {
26
+ unlinkSync(join(PENDING_DIR, correlationId));
27
+ } catch {
28
+ }
29
+ }
30
+ } catch {
31
+ }
32
+ };
9
33
  var reportEvent = async (event) => {
10
34
  const apiKey = getApiKey();
11
35
  const baseUrl = getBaseUrl();
@@ -40,6 +64,7 @@ var handlePostToolUse = async (input) => {
40
64
  default:
41
65
  action = `${input.tool_name}: done`;
42
66
  }
67
+ await cleanupPendingQuestions();
43
68
  const isError = input.tool_result && ("error" in input.tool_result || "is_error" in input.tool_result);
44
69
  await reportEvent({
45
70
  event: isError ? "tool_error" : "tool_complete",