@pushary/agent-hooks 0.2.5 → 0.2.6
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/pushary-setup.js +161 -93
- package/package.json +1 -1
|
@@ -5,6 +5,7 @@ import { existsSync, readFileSync, writeFileSync, appendFileSync, mkdirSync } fr
|
|
|
5
5
|
import { join } from "path";
|
|
6
6
|
import { homedir } from "os";
|
|
7
7
|
import { createInterface } from "readline";
|
|
8
|
+
import { execSync } from "child_process";
|
|
8
9
|
var rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
9
10
|
var ask = (q) => new Promise((r) => rl.question(q, r));
|
|
10
11
|
var CLAUDE_SETTINGS = join(homedir(), ".claude", "settings.json");
|
|
@@ -14,6 +15,8 @@ var dim = (s) => `\x1B[2m${s}\x1B[0m`;
|
|
|
14
15
|
var bold = (s) => `\x1B[1m${s}\x1B[0m`;
|
|
15
16
|
var green = (s) => `\x1B[32m${s}\x1B[0m`;
|
|
16
17
|
var cyan = (s) => `\x1B[36m${s}\x1B[0m`;
|
|
18
|
+
var yellow = (s) => `\x1B[33m${s}\x1B[0m`;
|
|
19
|
+
var check = green("\u2713");
|
|
17
20
|
var readJson = (path) => {
|
|
18
21
|
try {
|
|
19
22
|
return JSON.parse(readFileSync(path, "utf-8"));
|
|
@@ -26,38 +29,56 @@ var writeJson = (path, data) => {
|
|
|
26
29
|
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
27
30
|
writeFileSync(path, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
28
31
|
};
|
|
29
|
-
var
|
|
30
|
-
|
|
31
|
-
|
|
32
|
+
var spinner = async (label, fn) => {
|
|
33
|
+
const frames = [" ", ". ", ".. ", "..."];
|
|
34
|
+
let i = 0;
|
|
35
|
+
const interval = setInterval(() => {
|
|
36
|
+
process.stdout.write(`\r ${dim(frames[i++ % frames.length])} ${label}`);
|
|
37
|
+
}, 200);
|
|
38
|
+
try {
|
|
39
|
+
await fn();
|
|
40
|
+
clearInterval(interval);
|
|
41
|
+
process.stdout.write(`\r ${check} ${label}
|
|
32
42
|
`);
|
|
33
|
-
|
|
43
|
+
} catch (err) {
|
|
44
|
+
clearInterval(interval);
|
|
45
|
+
process.stdout.write(`\r ${yellow("!")} ${label} ${dim("(skipped)")}
|
|
46
|
+
`);
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
var installGlobally = async () => {
|
|
50
|
+
await spinner("Installing pushary-hook globally", async () => {
|
|
51
|
+
execSync("npm install -g @pushary/agent-hooks@latest", { stdio: "ignore" });
|
|
52
|
+
});
|
|
53
|
+
};
|
|
54
|
+
var addMcpServer = (settings, apiKey) => {
|
|
34
55
|
const mcpServers = settings.mcpServers ?? {};
|
|
35
56
|
mcpServers.pushary = {
|
|
36
57
|
url: "https://pushary.com/api/mcp/mcp",
|
|
37
58
|
headers: { Authorization: `Bearer ${apiKey}` }
|
|
38
59
|
};
|
|
39
60
|
settings.mcpServers = mcpServers;
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
settings.hooks = hooks;
|
|
56
|
-
}
|
|
61
|
+
};
|
|
62
|
+
var addPermissionHooks = (settings) => {
|
|
63
|
+
const hooks = settings.hooks ?? {};
|
|
64
|
+
const preToolUse = hooks.PreToolUse ?? [];
|
|
65
|
+
if (!JSON.stringify(preToolUse).includes("pushary-hook")) {
|
|
66
|
+
preToolUse.push({
|
|
67
|
+
matcher: "Bash|Write|Edit",
|
|
68
|
+
hooks: [{
|
|
69
|
+
type: "command",
|
|
70
|
+
command: "pushary-hook",
|
|
71
|
+
timeout: 120
|
|
72
|
+
}]
|
|
73
|
+
});
|
|
74
|
+
hooks.PreToolUse = preToolUse;
|
|
75
|
+
settings.hooks = hooks;
|
|
57
76
|
}
|
|
77
|
+
};
|
|
78
|
+
var addToolPermissions = (settings) => {
|
|
58
79
|
const permissions = settings.permissions ?? {};
|
|
59
80
|
const allow = permissions.allow ?? [];
|
|
60
|
-
const
|
|
81
|
+
const tools = [
|
|
61
82
|
"mcp__pushary__send_notification",
|
|
62
83
|
"mcp__pushary__ask_user",
|
|
63
84
|
"mcp__pushary__ask_user_yes_no",
|
|
@@ -67,52 +88,95 @@ ${bold("Claude Code Setup")}
|
|
|
67
88
|
"mcp__pushary__get_subscriber",
|
|
68
89
|
"mcp__pushary__count_subscribers"
|
|
69
90
|
];
|
|
70
|
-
for (const tool of
|
|
91
|
+
for (const tool of tools) {
|
|
71
92
|
if (!allow.includes(tool)) allow.push(tool);
|
|
72
93
|
}
|
|
73
94
|
permissions.allow = allow;
|
|
74
95
|
settings.permissions = permissions;
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
console.log(`
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
96
|
+
};
|
|
97
|
+
var setupClaudeCode = async (apiKey) => {
|
|
98
|
+
console.log(`
|
|
99
|
+
${bold("Setting up Claude Code")}
|
|
100
|
+
`);
|
|
101
|
+
const settings = readJson(CLAUDE_SETTINGS);
|
|
102
|
+
await spinner("Adding MCP server", async () => {
|
|
103
|
+
addMcpServer(settings, apiKey);
|
|
104
|
+
});
|
|
105
|
+
await spinner("Auto-allowing Pushary tools", async () => {
|
|
106
|
+
addToolPermissions(settings);
|
|
107
|
+
});
|
|
108
|
+
await installGlobally();
|
|
109
|
+
await spinner("Adding permission hooks (Bash, Write, Edit)", async () => {
|
|
110
|
+
addPermissionHooks(settings);
|
|
111
|
+
});
|
|
112
|
+
await spinner(`Writing ${CLAUDE_SETTINGS}`, async () => {
|
|
113
|
+
writeJson(CLAUDE_SETTINGS, settings);
|
|
114
|
+
});
|
|
115
|
+
console.log();
|
|
116
|
+
console.log(` ${dim("What this configured:")}`);
|
|
117
|
+
console.log(` ${dim("\u2022")} MCP server: your agent can send notifications and ask questions`);
|
|
118
|
+
console.log(` ${dim("\u2022")} Permission hooks: approve Bash/Write/Edit from your phone`);
|
|
119
|
+
console.log(` ${dim("\u2022")} Auto-allowed tools: no permission prompts for Pushary`);
|
|
120
|
+
};
|
|
121
|
+
var setupHermes = async (apiKey) => {
|
|
122
|
+
console.log(`
|
|
123
|
+
${bold("Setting up Hermes Agent")}
|
|
124
|
+
`);
|
|
125
|
+
await spinner("Installing hermes-plugin-pushary", async () => {
|
|
126
|
+
try {
|
|
127
|
+
execSync("pip install hermes-plugin-pushary", { stdio: "ignore" });
|
|
128
|
+
} catch {
|
|
129
|
+
try {
|
|
130
|
+
execSync("pip3 install hermes-plugin-pushary", { stdio: "ignore" });
|
|
131
|
+
} catch {
|
|
132
|
+
throw new Error("pip not found");
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
await spinner("Enabling plugin", async () => {
|
|
137
|
+
try {
|
|
138
|
+
execSync("hermes plugins enable pushary", { stdio: "ignore" });
|
|
139
|
+
} catch {
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
console.log();
|
|
143
|
+
console.log(` ${dim("What this configured:")}`);
|
|
144
|
+
console.log(` ${dim("\u2022")} Native tools: pushary_notify, pushary_ask, pushary_wait, pushary_cancel`);
|
|
145
|
+
console.log(` ${dim("\u2022")} Auto-notifications: push alert when tools return errors`);
|
|
146
|
+
console.log(` ${dim("\u2022")} Session alerts: opt-in with PUSHARY_AUTO_NOTIFY_SESSION_END=1`);
|
|
81
147
|
};
|
|
82
148
|
var setupCursor = async (apiKey) => {
|
|
83
149
|
console.log(`
|
|
84
|
-
${bold("Cursor
|
|
150
|
+
${bold("Setting up Cursor")}
|
|
85
151
|
`);
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
152
|
+
await spinner("Adding MCP server to .cursor/mcp.json", async () => {
|
|
153
|
+
const config = readJson(CURSOR_MCP);
|
|
154
|
+
const mcpServers = config.mcpServers ?? {};
|
|
155
|
+
mcpServers.pushary = {
|
|
156
|
+
url: "https://pushary.com/api/mcp/mcp",
|
|
157
|
+
headers: { Authorization: `Bearer ${apiKey}` }
|
|
158
|
+
};
|
|
159
|
+
config.mcpServers = mcpServers;
|
|
160
|
+
writeJson(CURSOR_MCP, config);
|
|
161
|
+
});
|
|
95
162
|
};
|
|
96
|
-
var saveApiKey = (apiKey) => {
|
|
97
|
-
|
|
163
|
+
var saveApiKey = async (apiKey) => {
|
|
164
|
+
await spinner("Saving API key to shell profile", async () => {
|
|
165
|
+
const exportLine = `
|
|
98
166
|
export PUSHARY_API_KEY="${apiKey}"
|
|
99
167
|
`;
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
appendFileSync(shellFile, exportLine, "utf-8");
|
|
107
|
-
console.log(` ${green("done")} Added PUSHARY_API_KEY to ${dim(shellFile)}`);
|
|
168
|
+
const shellFile = SHELL_FILES.find((f) => existsSync(f));
|
|
169
|
+
if (shellFile) {
|
|
170
|
+
const content = readFileSync(shellFile, "utf-8");
|
|
171
|
+
if (!content.includes("PUSHARY_API_KEY")) {
|
|
172
|
+
appendFileSync(shellFile, exportLine, "utf-8");
|
|
173
|
+
}
|
|
108
174
|
}
|
|
109
|
-
|
|
110
|
-
|
|
175
|
+
process.env.PUSHARY_API_KEY = apiKey;
|
|
176
|
+
});
|
|
111
177
|
};
|
|
112
178
|
var sendTestNotification = async (apiKey) => {
|
|
113
|
-
|
|
114
|
-
${bold("Sending test notification...")}`);
|
|
115
|
-
try {
|
|
179
|
+
await spinner("Sending test notification", async () => {
|
|
116
180
|
const response = await fetch("https://pushary.com/api/v1/server/send", {
|
|
117
181
|
method: "POST",
|
|
118
182
|
headers: {
|
|
@@ -124,72 +188,76 @@ ${bold("Sending test notification...")}`);
|
|
|
124
188
|
body: "Your AI agent can now send you push notifications."
|
|
125
189
|
})
|
|
126
190
|
});
|
|
127
|
-
if (response.ok) {
|
|
128
|
-
const data = await response.json();
|
|
129
|
-
if (data.delivered && data.delivered > 0) {
|
|
130
|
-
console.log(` ${green("done")} Check your phone!`);
|
|
131
|
-
} else {
|
|
132
|
-
console.log(` ${dim("Notification sent but no subscribers found. Enable notifications on your phone at pushary.com first.")}`);
|
|
133
|
-
}
|
|
134
|
-
} else {
|
|
191
|
+
if (!response.ok) {
|
|
135
192
|
const err = await response.json().catch(() => ({}));
|
|
136
|
-
|
|
193
|
+
throw new Error(err.error ?? response.statusText);
|
|
137
194
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
195
|
+
const data = await response.json();
|
|
196
|
+
if (!data.delivered || data.delivered === 0) {
|
|
197
|
+
throw new Error("no subscribers");
|
|
198
|
+
}
|
|
199
|
+
});
|
|
141
200
|
};
|
|
142
201
|
var main = async () => {
|
|
143
|
-
console.log(
|
|
144
|
-
${bold("Pushary
|
|
145
|
-
`);
|
|
146
|
-
console.log(
|
|
147
|
-
`);
|
|
148
|
-
|
|
202
|
+
console.log();
|
|
203
|
+
console.log(` ${bold("Pushary")} ${dim("v" + (process.env.npm_package_version ?? "0.2"))}`);
|
|
204
|
+
console.log(` ${dim("Push notifications for AI coding agents")}`);
|
|
205
|
+
console.log();
|
|
206
|
+
console.log(` ${dim("Get your API key at")} ${cyan("pushary.com/sign-up")}`);
|
|
207
|
+
console.log();
|
|
208
|
+
const apiKey = await ask(` API key: `);
|
|
149
209
|
if (!apiKey.trim() || !apiKey.includes(".")) {
|
|
150
|
-
console.log(
|
|
210
|
+
console.log(`
|
|
211
|
+
${yellow("!")} Invalid key format. Expected: pk_xxx.sk_xxx`);
|
|
212
|
+
console.log(` ${dim("Get yours at")} ${cyan("https://pushary.com/sign-up?from=ai-coding")}
|
|
213
|
+
`);
|
|
151
214
|
rl.close();
|
|
152
215
|
process.exit(1);
|
|
153
216
|
}
|
|
154
217
|
const trimmedKey = apiKey.trim();
|
|
155
|
-
console.log(`
|
|
156
|
-
Which agents do you use?
|
|
157
|
-
`);
|
|
158
|
-
console.log(` 1. ${cyan("Claude Code")}`);
|
|
159
|
-
console.log(` 2. ${cyan("Cursor")}`);
|
|
160
|
-
console.log(` 3. ${cyan("Both")}`);
|
|
161
|
-
console.log(` 4. ${cyan("Other")} ${dim("(just save the API key)")}`);
|
|
162
218
|
console.log();
|
|
163
|
-
|
|
164
|
-
|
|
219
|
+
console.log(` ${bold("Which agent do you use?")}`);
|
|
220
|
+
console.log();
|
|
221
|
+
console.log(` ${cyan("1.")} Claude Code ${dim("MCP + permission hooks + auto-allowed tools")}`);
|
|
222
|
+
console.log(` ${cyan("2.")} Hermes ${dim("native plugin + auto-error notifications")}`);
|
|
223
|
+
console.log(` ${cyan("3.")} Cursor ${dim("MCP server")}`);
|
|
224
|
+
console.log(` ${cyan("4.")} All ${dim("configure everything")}`);
|
|
225
|
+
console.log(` ${cyan("5.")} Other ${dim("just save the API key")}`);
|
|
226
|
+
console.log();
|
|
227
|
+
const choice = await ask(` Choice ${dim("[1-5]")}: `);
|
|
228
|
+
await saveApiKey(trimmedKey);
|
|
165
229
|
switch (choice.trim()) {
|
|
166
230
|
case "1":
|
|
167
231
|
await setupClaudeCode(trimmedKey);
|
|
168
232
|
break;
|
|
169
233
|
case "2":
|
|
170
|
-
await
|
|
234
|
+
await setupHermes(trimmedKey);
|
|
171
235
|
break;
|
|
172
236
|
case "3":
|
|
173
|
-
await setupClaudeCode(trimmedKey);
|
|
174
237
|
await setupCursor(trimmedKey);
|
|
175
238
|
break;
|
|
176
239
|
case "4":
|
|
240
|
+
await setupClaudeCode(trimmedKey);
|
|
241
|
+
await setupHermes(trimmedKey);
|
|
242
|
+
await setupCursor(trimmedKey);
|
|
243
|
+
break;
|
|
244
|
+
case "5":
|
|
177
245
|
default:
|
|
178
246
|
break;
|
|
179
247
|
}
|
|
180
|
-
|
|
181
|
-
Send a test notification? ${dim("[Y/n]")} `);
|
|
248
|
+
console.log();
|
|
249
|
+
const test = await ask(` Send a test notification? ${dim("[Y/n]")} `);
|
|
182
250
|
if (test.toLowerCase() !== "n") {
|
|
183
251
|
await sendTestNotification(trimmedKey);
|
|
184
252
|
}
|
|
185
|
-
console.log(
|
|
186
|
-
${green("Setup complete.")}
|
|
187
|
-
|
|
188
|
-
console.log(
|
|
253
|
+
console.log();
|
|
254
|
+
console.log(` ${green(bold("Setup complete."))}`);
|
|
255
|
+
console.log();
|
|
256
|
+
console.log(` ${dim("Next:")}`);
|
|
189
257
|
console.log(` ${dim("1.")} Enable notifications on your phone at ${cyan("pushary.com")}`);
|
|
190
|
-
console.log(` ${dim("2.")}
|
|
191
|
-
console.log(` ${dim("3.")} Start coding
|
|
192
|
-
|
|
258
|
+
console.log(` ${dim("2.")} Restart your agent to load the new config`);
|
|
259
|
+
console.log(` ${dim("3.")} Start coding \u2014 your agent will notify you automatically`);
|
|
260
|
+
console.log();
|
|
193
261
|
rl.close();
|
|
194
262
|
};
|
|
195
263
|
main();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pushary/agent-hooks",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.6",
|
|
4
4
|
"description": "Permission hooks for AI coding agents — route tool approvals through Pushary push notifications",
|
|
5
5
|
"author": "Pushary <business@pushary.com>",
|
|
6
6
|
"homepage": "https://pushary.com",
|