@pushary/agent-hooks 0.7.0 → 0.8.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.
package/data/SKILL.md CHANGED
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: pushary
3
- version: 0.1.0
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
- Add Pushary to your MCP configuration:
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
- "url": "https://pushary.com/api/mcp/mcp"
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. Supports three question types. Returns a `correlationId` that you pass to `wait_for_answer` to get the response.
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
- | agentName | string | No | Identifies which agent is asking (e.g., "Claude Code - myproject") |
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:** `{ "correlationId": "uuid", "status": "pending", "expiresInSeconds": 600 }`
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
- Long-poll for the user's response to a question sent via `ask_user`. Blocks until the user responds or the timeout is reached.
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 `wait_for_answer` returns `answered: false` after 3 attempts, do NOT execute the command. Notify the user that the operation was skipped due to no response.
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
- Follow this exact sequence when you need a decision from the user:
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
- for attempt in 1..3:
265
- answer = wait_for_answer({ correlationId, timeoutMs: 55000 })
266
- if answer.answered:
267
- // answer.value = "JWT tokens" (the selected option)
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.
@@ -394,17 +394,34 @@ var main = async () => {
394
394
  console.log(` ${dim("Push notifications for AI coding agents")}`);
395
395
  console.log();
396
396
  await checkForUpdates(version);
397
- console.log(` ${dim("Get your API key at")} ${cyan("pushary.com/sign-up")}`);
398
- console.log();
399
- const apiKey = await input({ message: "API key:" });
400
- if (!apiKey.trim() || !/^pk_[a-f0-9]+\.[a-f0-9]+$/.test(apiKey.trim())) {
397
+ const envKey = process.env.PUSHARY_API_KEY?.trim();
398
+ let trimmedKey;
399
+ if (envKey && /^pk_[a-f0-9]+\.[a-f0-9]+$/.test(envKey)) {
400
+ const masked = `${envKey.slice(0, 8)}...${envKey.slice(-4)}`;
401
+ console.log(` ${check} Found API key in environment: ${dim(masked)}`);
402
+ console.log();
403
+ const useExisting = await confirm({ message: "Use this key?", default: true });
404
+ if (useExisting) {
405
+ trimmedKey = envKey;
406
+ } else {
407
+ const apiKey = await input({ message: "API key:" });
408
+ trimmedKey = apiKey.trim();
409
+ }
410
+ } else {
411
+ console.log(` ${dim("Paste your API key from the onboarding page.")}`);
412
+ console.log(` ${dim("Can't find it? Copy it from:")} ${cyan("pushary.com/dashboard/agent/settings")}`);
413
+ console.log();
414
+ const apiKey = await input({ message: "API key:" });
415
+ trimmedKey = apiKey.trim();
416
+ }
417
+ if (!trimmedKey || !/^pk_[a-f0-9]+\.[a-f0-9]+$/.test(trimmedKey)) {
401
418
  console.log(`
402
- ${yellow("!")} Invalid key format. Expected: pk_xxx.sk_xxx`);
403
- console.log(` ${dim("Get yours at")} ${cyan("https://pushary.com/sign-up?from=ai-coding")}
419
+ ${yellow("!")} Invalid key format. Expected: ${dim("pk_xxx.xxx")}`);
420
+ console.log(` ${dim("Copy your key from")} ${cyan("https://pushary.com/dashboard/agent/settings")}`);
421
+ console.log(` ${dim("Or sign up at")} ${cyan("https://pushary.com/sign-up?from=ai-coding")}
404
422
  `);
405
423
  process.exit(1);
406
424
  }
407
- const trimmedKey = apiKey.trim();
408
425
  const agents = await checkbox({
409
426
  message: "Which agents do you use? " + dim("(space = toggle, enter = confirm)"),
410
427
  choices: [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pushary/agent-hooks",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
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",