@pushary/agent-hooks 0.7.0 → 0.8.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.
- package/data/SKILL.md +38 -27
- package/dist/bin/pushary-setup.js +39 -7
- package/package.json +1 -1
package/data/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: pushary
|
|
3
|
-
version: 0.1
|
|
3
|
+
version: 0.4.1
|
|
4
4
|
description: Push notifications and human-in-the-loop for AI coding agents. Send alerts when tasks finish, ask questions (yes/no, multiple choice, or free text) via push, and get answers from the user's lock screen. Use these tools proactively — do not wait for the user to ask for notifications. Works with Cursor, Claude Code, Windsurf, Hermes, and any MCP client.
|
|
5
5
|
metadata:
|
|
6
6
|
hermes:
|
|
@@ -47,13 +47,23 @@ Use these tools proactively. Do not wait for the user to ask for notifications.
|
|
|
47
47
|
|
|
48
48
|
## Setup
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
Run the CLI setup (recommended — configures MCP, hooks, permissions, and skill in one step):
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
npx @pushary/agent-hooks@latest setup
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Or add Pushary manually to your MCP configuration:
|
|
51
57
|
|
|
52
58
|
```json
|
|
53
59
|
{
|
|
54
60
|
"mcpServers": {
|
|
55
61
|
"pushary": {
|
|
56
|
-
"
|
|
62
|
+
"type": "http",
|
|
63
|
+
"url": "https://pushary.com/api/mcp/mcp",
|
|
64
|
+
"headers": {
|
|
65
|
+
"Authorization": "Bearer YOUR_API_KEY"
|
|
66
|
+
}
|
|
57
67
|
}
|
|
58
68
|
}
|
|
59
69
|
}
|
|
@@ -61,6 +71,12 @@ Add Pushary to your MCP configuration:
|
|
|
61
71
|
|
|
62
72
|
Sign up at https://pushary.com/sign-up?from=ai-coding to get your API key.
|
|
63
73
|
|
|
74
|
+
After setup, verify with:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
npx @pushary/agent-hooks@latest doctor
|
|
78
|
+
```
|
|
79
|
+
|
|
64
80
|
## Tools
|
|
65
81
|
|
|
66
82
|
### send_notification
|
|
@@ -144,7 +160,7 @@ When `askQuestion` is provided, the response includes a `linkedCorrelationId` yo
|
|
|
144
160
|
|
|
145
161
|
### ask_user
|
|
146
162
|
|
|
147
|
-
Send a question to the user via push notification
|
|
163
|
+
Send a question to the user via push notification and wait for their answer. By default, this tool **blocks** until the user responds or the timeout is reached — no need to call `wait_for_answer` separately.
|
|
148
164
|
|
|
149
165
|
**Parameters:**
|
|
150
166
|
|
|
@@ -155,13 +171,20 @@ Send a question to the user via push notification. Supports three question types
|
|
|
155
171
|
| options | string[] | No | Choices for select type (2-6 options). Required when type is select. |
|
|
156
172
|
| placeholder | string | No | Placeholder text for input type (max 200 chars) |
|
|
157
173
|
| context | string | No | What the agent is working on, shown above the question (max 500 chars) |
|
|
158
|
-
|
|
|
174
|
+
| wait | boolean | No | Wait for the answer before returning (default: true). Set false for manual polling. |
|
|
175
|
+
| timeoutMs | integer | No | Max wait time in ms (max 55000). Uses site policy if omitted. |
|
|
176
|
+
| agentName | string | No | Identifies which agent is asking. Format: "{Agent} - {project}" (e.g., "Claude Code - myproject") |
|
|
159
177
|
| callbackUrl | string | No | Webhook URL to POST the answer to when the user responds |
|
|
160
178
|
| subscriberIds | string[] | No | Target specific subscriber IDs |
|
|
161
179
|
| externalIds | string[] | No | Target by external IDs |
|
|
162
180
|
| tags | string[] | No | Target by subscriber tags |
|
|
163
181
|
|
|
164
|
-
**Returns
|
|
182
|
+
**Returns (when wait=true, default):**
|
|
183
|
+
- `{ "answered": true, "value": "yes", "correlationId": "uuid" }` — user responded
|
|
184
|
+
- `{ "answered": false, "timedOut": true, "correlationId": "uuid" }` — timeout reached
|
|
185
|
+
|
|
186
|
+
**Returns (when wait=false):**
|
|
187
|
+
- `{ "correlationId": "uuid", "status": "pending", "expiresInSeconds": 600 }` — use `wait_for_answer` to poll
|
|
165
188
|
|
|
166
189
|
**Example — confirm (yes/no):**
|
|
167
190
|
|
|
@@ -200,7 +223,7 @@ Send a question to the user via push notification. Supports three question types
|
|
|
200
223
|
|
|
201
224
|
### wait_for_answer
|
|
202
225
|
|
|
203
|
-
|
|
226
|
+
Poll for the user's response to a question sent via `ask_user` with `wait: false`. Not needed when using the default blocking mode.
|
|
204
227
|
|
|
205
228
|
**Parameters:**
|
|
206
229
|
|
|
@@ -235,21 +258,13 @@ Before executing any of the following, you MUST call `ask_user` with type "confi
|
|
|
235
258
|
- Network configuration changes (firewall, DNS, proxy)
|
|
236
259
|
- Any command the user has flagged as dangerous
|
|
237
260
|
|
|
238
|
-
If `
|
|
261
|
+
If `ask_user` returns `answered: false`, do NOT execute the command. Send a notification that the operation was skipped due to no response.
|
|
239
262
|
|
|
240
263
|
This is not optional. Treat it as a hard constraint, not a suggestion.
|
|
241
264
|
|
|
242
265
|
## Human-in-the-Loop Flow
|
|
243
266
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
1. Call `ask_user` with a clear, concise question and the appropriate type.
|
|
247
|
-
2. Immediately call `wait_for_answer` with the returned `correlationId` and `timeoutMs: 55000`.
|
|
248
|
-
3. If `wait_for_answer` returns `{ "answered": false }`, retry the same `wait_for_answer` call up to 3 times. The answer persists in Redis for 10 minutes, so it will be there when the user responds.
|
|
249
|
-
4. Once you receive `{ "answered": true, "value": "..." }`, act on the decision.
|
|
250
|
-
5. If the user answers in chat before the push response arrives, continue normally and call `cancel_question` to clean up.
|
|
251
|
-
|
|
252
|
-
**Pseudocode:**
|
|
267
|
+
One tool call — `ask_user` blocks and returns the answer:
|
|
253
268
|
|
|
254
269
|
```
|
|
255
270
|
result = ask_user({
|
|
@@ -259,19 +274,15 @@ result = ask_user({
|
|
|
259
274
|
context: "Setting up authentication for the new API",
|
|
260
275
|
agentName: "Claude Code - myproject"
|
|
261
276
|
})
|
|
262
|
-
correlationId = result.correlationId
|
|
263
277
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
// proceed with the chosen approach
|
|
269
|
-
break
|
|
270
|
-
|
|
271
|
-
if not answer.answered after 3 attempts:
|
|
272
|
-
// user did not respond — pick the safe default or ask in chat
|
|
278
|
+
if result.answered:
|
|
279
|
+
// result.value = "JWT tokens" — proceed with the chosen approach
|
|
280
|
+
else:
|
|
281
|
+
// user did not respond — pick the safe default or notify and skip
|
|
273
282
|
```
|
|
274
283
|
|
|
284
|
+
If the user answers in chat before the push response arrives, continue normally and call `cancel_question` with the `correlationId` to clean up.
|
|
285
|
+
|
|
275
286
|
## Identifying Your Agent
|
|
276
287
|
|
|
277
288
|
Always pass `agentName` when you are one of multiple possible agents the user may be running. The user sees this in the notification title to know which agent is asking.
|
|
@@ -26,6 +26,14 @@ var green = (s) => `\x1B[32m${s}\x1B[0m`;
|
|
|
26
26
|
var cyan = (s) => `\x1B[36m${s}\x1B[0m`;
|
|
27
27
|
var yellow = (s) => `\x1B[33m${s}\x1B[0m`;
|
|
28
28
|
var check = green("\u2713");
|
|
29
|
+
var parseKeyFlag = () => {
|
|
30
|
+
const args = process.argv.slice(2);
|
|
31
|
+
for (let i = 0; i < args.length; i++) {
|
|
32
|
+
if (args[i] === "--key" && args[i + 1]) return args[i + 1].trim();
|
|
33
|
+
if (args[i].startsWith("--key=")) return args[i].slice(6).trim();
|
|
34
|
+
}
|
|
35
|
+
return void 0;
|
|
36
|
+
};
|
|
29
37
|
var readJson = (path) => {
|
|
30
38
|
try {
|
|
31
39
|
return JSON.parse(readFileSync(path, "utf-8"));
|
|
@@ -394,17 +402,41 @@ var main = async () => {
|
|
|
394
402
|
console.log(` ${dim("Push notifications for AI coding agents")}`);
|
|
395
403
|
console.log();
|
|
396
404
|
await checkForUpdates(version);
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
const
|
|
400
|
-
|
|
405
|
+
const keyPattern = /^pk_[a-f0-9]+\.[a-f0-9]+$/;
|
|
406
|
+
const flagKey = parseKeyFlag();
|
|
407
|
+
const envKey = process.env.PUSHARY_API_KEY?.trim();
|
|
408
|
+
let trimmedKey;
|
|
409
|
+
if (flagKey && keyPattern.test(flagKey)) {
|
|
410
|
+
const masked = `${flagKey.slice(0, 8)}...${flagKey.slice(-4)}`;
|
|
411
|
+
console.log(` ${check} Using API key: ${dim(masked)}`);
|
|
412
|
+
console.log();
|
|
413
|
+
trimmedKey = flagKey;
|
|
414
|
+
} else if (envKey && keyPattern.test(envKey)) {
|
|
415
|
+
const masked = `${envKey.slice(0, 8)}...${envKey.slice(-4)}`;
|
|
416
|
+
console.log(` ${check} Found API key in environment: ${dim(masked)}`);
|
|
417
|
+
console.log();
|
|
418
|
+
const useExisting = await confirm({ message: "Use this key?", default: true });
|
|
419
|
+
if (useExisting) {
|
|
420
|
+
trimmedKey = envKey;
|
|
421
|
+
} else {
|
|
422
|
+
const apiKey = await input({ message: "API key:" });
|
|
423
|
+
trimmedKey = apiKey.trim();
|
|
424
|
+
}
|
|
425
|
+
} else {
|
|
426
|
+
console.log(` ${dim("Paste your API key from the onboarding page.")}`);
|
|
427
|
+
console.log(` ${dim("Can't find it? Copy it from:")} ${cyan("pushary.com/dashboard/agent/settings")}`);
|
|
428
|
+
console.log();
|
|
429
|
+
const apiKey = await input({ message: "API key:" });
|
|
430
|
+
trimmedKey = apiKey.trim();
|
|
431
|
+
}
|
|
432
|
+
if (!trimmedKey || !keyPattern.test(trimmedKey)) {
|
|
401
433
|
console.log(`
|
|
402
|
-
${yellow("!")} Invalid key format. Expected: pk_xxx.
|
|
403
|
-
console.log(` ${dim("
|
|
434
|
+
${yellow("!")} Invalid key format. Expected: ${dim("pk_xxx.xxx")}`);
|
|
435
|
+
console.log(` ${dim("Copy your key from")} ${cyan("https://pushary.com/dashboard/agent/settings")}`);
|
|
436
|
+
console.log(` ${dim("Or sign up at")} ${cyan("https://pushary.com/sign-up?from=ai-coding")}
|
|
404
437
|
`);
|
|
405
438
|
process.exit(1);
|
|
406
439
|
}
|
|
407
|
-
const trimmedKey = apiKey.trim();
|
|
408
440
|
const agents = await checkbox({
|
|
409
441
|
message: "Which agents do you use? " + dim("(space = toggle, enter = confirm)"),
|
|
410
442
|
choices: [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pushary/agent-hooks",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
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",
|