@trygocode/notify 0.6.5 → 0.6.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/README.md CHANGED
@@ -530,6 +530,17 @@ npm publish, real-device E2E), see
530
530
 
531
531
  ## Changelog
532
532
 
533
+ ### 0.6.6
534
+
535
+ - **Claude questions are now classified by notification type.** Setup installs
536
+ the `Notification` hook only for `permission_prompt`, `idle_prompt`,
537
+ `elicitation_dialog`, and `agent_needs_input`. The dispatcher repeats that
538
+ check from stdin so existing 0.6.5 hook configs are protected immediately by
539
+ `@latest`, even before setup is rerun.
540
+ - Non-input events such as `auth_success`, `elicitation_complete`,
541
+ `elicitation_response`, and `agent_completed` no longer produce a misleading
542
+ “Agent needs you” notification or duplicate the Claude `Stop` completion.
543
+
533
544
  ### 0.6.5
534
545
 
535
546
  - **Fixed Cursor lifecycle notifications on current Cursor releases.** The
@@ -66,6 +66,16 @@ export const CLAUDE_HOOK_COMMANDS = {
66
66
  Stop: 'npx -y @trygocode/notify@latest on-stop --source claude_code --dedupe-key "$CLAUDE_SESSION_ID-stop" --quiet || true',
67
67
  Notification: 'npx -y @trygocode/notify@latest on-notification --source claude_code --dedupe-key "$CLAUDE_SESSION_ID-notify" --quiet || true',
68
68
  };
69
+ /**
70
+ * Claude `Notification` types that genuinely require the user's attention.
71
+ * Keep this matcher in lockstep with `CLAUDE_INPUT_NOTIFICATION_TYPES` in
72
+ * `cli.ts`, which provides a defensive payload check for existing 0.6.5
73
+ * installs whose matcher is still empty until setup is rerun.
74
+ *
75
+ * Deliberately excluded: `auth_success`, `elicitation_complete`,
76
+ * `elicitation_response`, and `agent_completed`.
77
+ */
78
+ export const CLAUDE_NOTIFICATION_MATCHER = "permission_prompt|idle_prompt|elicitation_dialog|agent_needs_input";
69
79
  /**
70
80
  * The Claude event we DELIBERATELY do not register and actively scrub on every
71
81
  * setup. We used to fire a `finished` push on `SubagentStop` ("Subagent done"),
@@ -193,9 +203,14 @@ function stripOurHooks(groups) {
193
203
  }
194
204
  return { groups: out, removed };
195
205
  }
196
- /** Build the matcher group we add for a single event. */
197
- function ourHookGroup(command) {
198
- return { hooks: [{ type: "command", command }] };
206
+ /** Build the hook group we add for a single event. */
207
+ function ourHookGroup(event, command) {
208
+ const group = {
209
+ hooks: [{ type: "command", command }],
210
+ };
211
+ if (event === "Notification")
212
+ group.matcher = CLAUDE_NOTIFICATION_MATCHER;
213
+ return group;
199
214
  }
200
215
  /**
201
216
  * Merge our three hook events into `settings.hooks`, preserving the user's own
@@ -209,7 +224,7 @@ function mergeHooks(settings) {
209
224
  for (const [event, command] of Object.entries(CLAUDE_HOOK_COMMANDS)) {
210
225
  const existing = Array.isArray(hooks[event]) ? hooks[event] : [];
211
226
  const preserved = stripOurHooks(existing).groups;
212
- preserved.push(ourHookGroup(command));
227
+ preserved.push(ourHookGroup(event, command));
213
228
  hooks[event] = preserved;
214
229
  }
215
230
  // Migrate old installs: strip OUR command from any event we no longer install
package/dist/src/cli.js CHANGED
@@ -748,12 +748,46 @@ export async function cmdOnStop(args, deps = {}) {
748
748
  // PRD §0.5 / §4.4: a stop hook must NEVER block the turn — always exit 0.
749
749
  return 0;
750
750
  }
751
+ /**
752
+ * Claude Notification types that actually mean the agent is waiting for the
753
+ * user. This defensive payload filter protects users who upgraded the executable
754
+ * to 0.6.6 through `@latest` but have not rerun setup yet, so their installed
755
+ * 0.6.5 Notification group still has no matcher.
756
+ */
757
+ export const CLAUDE_INPUT_NOTIFICATION_TYPES = new Set([
758
+ "permission_prompt",
759
+ "idle_prompt",
760
+ "elicitation_dialog",
761
+ "agent_needs_input",
762
+ ]);
763
+ /**
764
+ * Extract Claude Code's notification discriminator. A missing/invalid payload
765
+ * returns undefined so legacy Claude versions (whose Notification stdin did not
766
+ * include `notification_type`) retain the historical awaiting-input behavior.
767
+ */
768
+ export function parseClaudeNotificationType(hookStdin) {
769
+ if (!hookStdin || hookStdin.trim() === "")
770
+ return undefined;
771
+ try {
772
+ const parsed = JSON.parse(hookStdin);
773
+ if (!parsed || typeof parsed !== "object")
774
+ return undefined;
775
+ const notificationType = parsed.notification_type;
776
+ return typeof notificationType === "string" && notificationType.trim() !== ""
777
+ ? notificationType.trim()
778
+ : undefined;
779
+ }
780
+ catch {
781
+ return undefined;
782
+ }
783
+ }
751
784
  /**
752
785
  * Handle Claude Code's `Notification` hook while avoiding Cursor's third-party
753
- * import duplicate. Standalone Claude payloads are forwarded to the normal
754
- * awaiting-input send. Cursor-imported payloads carry `cursor_version` and are
755
- * ignored because Cursor does not support the Claude Notification event and its
756
- * native question hook is authoritative when available.
786
+ * import duplicate and non-input notification classes. Standalone Claude input
787
+ * requests are forwarded to the normal awaiting-input send. Known non-input
788
+ * events (`auth_success`, elicitation completion/response, `agent_completed`)
789
+ * are ignored. Cursor-imported payloads carry `cursor_version` and are ignored
790
+ * because Cursor's native question hook is authoritative when available.
757
791
  */
758
792
  export async function cmdOnNotification(args, deps = {}) {
759
793
  const flags = parseFlags(args);
@@ -763,6 +797,11 @@ export async function cmdOnNotification(args, deps = {}) {
763
797
  : await (deps.readStdin ?? readStdinIfPipe)();
764
798
  if (isClaudeHookImportedByCursor(source, hookStdin))
765
799
  return 0;
800
+ const notificationType = parseClaudeNotificationType(hookStdin);
801
+ if (notificationType !== undefined &&
802
+ !CLAUDE_INPUT_NOTIFICATION_TYPES.has(notificationType)) {
803
+ return 0;
804
+ }
766
805
  const forwarded = [
767
806
  "--kind",
768
807
  "awaiting_input",
@@ -1,2 +1,2 @@
1
1
  // Single source of truth for the CLI version. Keep in sync with package.json.
2
- export const VERSION = "0.6.5";
2
+ export const VERSION = "0.6.6";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trygocode/notify",
3
- "version": "0.6.5",
3
+ "version": "0.6.6",
4
4
  "description": "Free phone + branded desktop notifications for any coding agent (Cursor, Claude Code, OpenCode, Ralph/Homer) via the GoCode app.",
5
5
  "license": "MIT",
6
6
  "type": "module",