Everything the package does, in one page: install, the setup wizard, every command, every config key, the agents and events it hooks, the channels it delivers to, and what happens when something fails.
Updated 2026-10-16 for v1.7.1 · plain-text version for LLMs · README on GitHub
anotifier is a free, open-source, zero-dependency Node.js CLI that sends a notification when an AI coding agent finishes a task or needs your input.
It hooks into the native event system of Claude Code, OpenAI Codex CLI, Cursor, Google Gemini CLI, and Antigravity CLI, then delivers to the channels you enable: a desktop toast (Windows, macOS, Linux, WSL), a phone push via ntfy (Android and iOS, no account), a webhook (Slack, Discord, Telegram, or any HTTP endpoint), and a terminal bell. All agents share one config file at ~/.anotifier/config.json.
npm i -g anotifier installs exactly one package.anotifier setup or run anotifier telemetry on; they are off otherwise (see the usage stats section below).Run one command; the setup wizard detects your platform and installed agents and wires the hooks.
npx anotifier@latest setupRestart your AI tools afterwards so they pick up the new hooks. Other install paths:
# Global install, then run the wizard
npm i -g anotifier
anotifier setupAs a Claude Code plugin (hooks auto-register; run /anotifier:setup inside Claude Code to wire the other agents):
/plugin marketplace add DevinoSolutions/anotifier-for-claude-codex-cursor
/plugin install anotifier@anotifierStandalone, without npm:
# Windows (PowerShell)
irm https://raw.githubusercontent.com/DevinoSolutions/anotifier-for-claude-codex-cursor/main/setup/install.ps1 | iex# macOS / Linux
curl -fsSL https://raw.githubusercontent.com/DevinoSolutions/anotifier-for-claude-codex-cursor/main/setup/install.sh | bash| Platform | Requirement | Notes |
|---|---|---|
| All | Node.js ≥ 18 | Already present for every supported agent. |
| Windows | PowerShell 7+ (pwsh) | Toasts use the BurntToast module; setup installs it if missing. See Claude Code and Codex notifications on Windows. |
| macOS | osascript | Built in. No extra install. |
| Linux | notify-send (libnotify) | Optional, for desktop toasts. Without a desktop session the toast fails, is logged to ~/.anotifier/errors.log, and the other channels still deliver. |
| WSL | WSL2 interop + Windows PowerShell | Both on by default. Toasts are routed to Windows; see how the toast crosses from WSL into Windows. |
It writes one shared config, then adds hook entries to each detected agent's own config file, backing up every file it touches first.
~/.claude/settings.json, ~/.codex, ~/.cursor, ~/.gemini).osascript on macOS or notify-send on Linux.anotifier-<16 random chars>.~/.anotifier/config.json.~/.anotifier/backups/ first.| Agent | File patched | Hook events registered |
|---|---|---|
| Claude Code | ~/.claude/settings.json | Stop, Notification |
| Codex CLI | ~/.codex/hooks.json (+ hooks = true under [features] in ~/.codex/config.toml) | Stop, PermissionRequest, SessionStart |
| Cursor | ~/.cursor/hooks.json | stop |
| Gemini CLI | ~/.gemini/settings.json | AfterAgent, Notification |
Each hook runs node <path>/src/notify.mjs --source <agent> with the agent's event JSON on stdin. The agent gives the hook 10 seconds (Claude Code, Codex) or 30 (Gemini CLI); inside that, every channel runs in parallel with its own shorter timeout (5 to 7 seconds), and the hook always exits successfully, so a broken channel can never stall the agent.
Seven commands: setup, status, test, config, doctor, snooze, and uninstall.
anotifier setup # First-time setup wizard
anotifier status # Wired tools, config summary, recent hook errors
anotifier test [channel] # Fire a test notification (toast | ntfy | webhook | bell | both)
anotifier config [section] # Interactive settings (ntfy | webhook | sounds | events | sentry)
anotifier doctor [--deep] # Diagnose delivery per channel (--deep verifies real delivery)
anotifier snooze [dur] # Silence every channel (30m | 2h | 90s | 45 = 45 minutes)
anotifier snooze off # Cancel the snooze early
anotifier uninstall # Remove anotifier's hooks from every agent
anotifier --version # Version, plus an update notice if one is available| Command | What it does | Exit code |
|---|---|---|
setup | Detects agents, writes config, patches hooks, backs up originals. | 1 if no agent was found or any patch failed. |
status | Shows version, platform, toast backend, Sentry, snooze, quiet hours, usage alerts, context alerts (1.6.0 and later) and whether the Claude Code statusline is wired (1.5.0 and later), ntfy URL, webhook origin, wired tools with their events, per-event toggles, and the last 8 hook errors from ~/.anotifier/errors.log. | 1 if the config file is invalid. |
test [channel] | Sends a real test notification through one channel or all enabled ones. | 1 for an unknown channel name. |
config [section] | Interactive editor for ntfy, webhook, sounds, events, or sentry. Writes once at the end; Ctrl+C saves nothing. | 1 when not run in a terminal. |
doctor [--deep] [--json] [--strict] | Static checks for config, toast backend, bell, ntfy, and webhook. --deep fires a marker notification and reads it back from Notification Center (macOS) or dunst history (Linux). --json prints machine-readable output. --strict turns deep warnings into failures. | 1 if any check fails. |
snooze [dur|off] | Silences all channels until the deadline stored in ~/.anotifier/.snooze.json. No argument prints the current state. | 1 for an unparseable duration. |
uninstall | Removes only the hooks anotifier manages, and puts your original Claude Code statusline back (1.5.0 and later). Your own hooks and the backups stay. | 0 |
Three normalized events: task_complete, needs_input, and session_start.
| Event | Meaning | Default toast sound | Default priority | Default ntfy tags |
|---|---|---|---|---|
task_complete | The agent finished its turn. | IM | default | white_check_mark |
needs_input | The agent asked a question or wants permission to run a tool. | Reminder | urgent | bell,warning |
session_start | A new session started (Codex only). Every channel is off for this event by default, the webhook included from 1.7.1 (before 1.7.1 an enabled webhook still received it). Set webhookEnabled: true (or toastEnabled, ntfyEnabled, terminalBellEnabled) to get it on that channel. | Default | low | rocket |
| Agent | task_complete comes from | needs_input comes from |
|---|---|---|
| Claude Code | Stop hook | Notification hook (permission prompts, questions) |
| Codex CLI | Stop hook | PermissionRequest hook (approval to run a command) |
| Cursor | stop hook | — |
| Gemini CLI | AfterAgent hook | Notification hook |
Every notification is titled <project> · <agent> (for example my-app · Claude Code), where the project is the name of the working directory the agent runs in. The body is <project>: Task complete or <project>: Needs your input, unless rich content replaces it (see below).
Duplicate hook fires within 1.5 seconds for the same agent, event, and session are collapsed by an atomic lock file, so Cursor's double stop produces one notification.
Desktop toast, phone push via ntfy, webhook, and terminal bell. Each is toggled independently and can be disabled per event.
| OS | Backend | Details |
|---|---|---|
| Windows | BurntToast (PowerShell 7+) | Per-agent icon, per-event sound (Default, IM, Mail, Reminder, SMS, Alarm), click-to-focus via a custom agentfocus:// URI that brings the terminal or VS Code window that fired the hook to the front. |
| macOS | osascript | Built in. Windows sound names are mapped to the closest system sound; unknown names fall back to the default sound. |
| Linux | notify-send (libnotify) | Event priority maps to notify-send urgency. Sound names are ignored. |
| WSL | PowerShell interop | Detected automatically; toasts cross to the Windows host instead of needing a Linux notification daemon. |
{
"toast": {
"enabled": true,
"clickToFocus": true,
"richContent": true
}
}ntfy is a free, open-source pub/sub push service with Android and iOS apps and no account requirement. anotifier publishes to one topic; every device subscribed to it gets every agent's alerts in one stream.
io.heckel.ntfy on Google Play) or iOS (App Store).anotifier setup and answer yes to phone notifications, or add the block below.https://ntfy.sh/anotifier-k3j9x2m1p8q4r7s6.anotifier test ntfy. Your phone should buzz within a second.{
"ntfy": {
"enabled": true,
"server": "https://ntfy.sh",
"topic": "anotifier-<random>",
"click": "",
"richContent": false
}
}server: any ntfy server, including a self-hosted one.fallbackServer (1.7.0 and later, optional): a second ntfy server. If server fails (unreachable, a timeout, or an error answer such as a 429 daily limit), the same push is sent once to the fallback with the same topic. With a fallback set, each attempt waits at most 3 s instead of 5 s. For example, set your own server as server and https://ntfy.sh as fallbackServer, and subscribe your phone to the topic on both. If the main server accepts a push but answers slowly, a phone subscribed on both can get it twice. anotifier status shows a fallback line, and anotifier test ntfy warns when only the fallback delivered.click: URL opened when you tap the notification. Empty means no link.richContent: off by default for privacy. Public ntfy.sh topics are guessable, so the body stays generic (my-app: Task complete) unless you run a private server and opt in.priority (min, low, default, high, urgent) becomes the ntfy push priority; ntfyTags become the emoji tags.· separator, non-Latin project names) are RFC 2047-encoded in the HTTP header and decoded by ntfy, so they display correctly.Set webhook.enabled to true and a webhook.url. format picks the payload shape. authorization, if set, is sent as the Authorization header for every format.
{
"webhook": {
"enabled": true,
"url": "https://hooks.slack.com/services/...",
"format": "slack",
"richContent": true
}
}| format | URL to use | Payload |
|---|---|---|
slack | Slack Incoming Webhook URL | Slack message JSON. |
discord | Discord channel webhook URL | { content } with the title in bold and the message on the next line. See the Discord page. |
telegram | https://api.telegram.org/bot<token>/sendMessage plus chatId | { chat_id, text } with the title and message on two lines. |
generic | Any HTTPS endpoint | { title, message, source, project, event, timestamp } as JSON. |
An audible ding in the terminal that launched the agent. For Claude Code 2.1.141 and newer it rings through Claude Code's own terminal write path (the terminalSequence field in the hook reply), which is safe in tmux, GNU screen, and on Windows. Other agents get a direct BEL byte to the controlling terminal. anotifier doctor warns when your terminal is known to swallow the bell.
{ "terminalBell": { "enabled": true } }For Claude Code, toasts and webhooks show what the agent said or asked, read from the session transcript; other agents and phone push get the generic text.
A needs_input notification carries Claude's own question; a task_complete notification carries the last assistant message. Both are collapsed to one line and trimmed to about 180 characters. Session-start notifications stay generic. anotifier reads the transcript for Claude Code only, so Codex, Cursor, and Gemini CLI always get <project>: Task complete style text. The project name stays in the title on every channel, so a rich body never hides which repo finished. Cursor is the exception: anotifier doesn't read a project folder from its stop event, so Cursor alerts are titled just Cursor.
| Channel | Config key | Default | Why |
|---|---|---|---|
| Toast | toast.richContent | true | Your own screen. |
| Webhook | webhook.richContent | true | Your own endpoint. |
| ntfy | ntfy.richContent | false | Public ntfy.sh topics are guessable; conversation text must not leak there. |
One JSON file, ~/.anotifier/config.json, shared by every agent. Missing keys fall back to these defaults.
{
"ntfy": { "enabled": true, "server": "https://ntfy.sh", "topic": "", "click": "", "richContent": false },
"toast": { "enabled": true, "clickToFocus": true, "richContent": true },
"terminalBell": { "enabled": true },
"webhook": { "enabled": false, "url": "", "format": "generic", "richContent": true },
"sentry": { "enabled": false, "dsn": "" },
"updateCheck": { "enabled": true },
"usageAlerts": { "enabled": true, "thresholds": [70, 85, 95] },
"contextAlerts": { "enabled": true, "threshold": 85 },
"quietHours": { "enabled": false, "from": "22:00", "to": "08:00" },
"events": {
"task_complete": { "toastSound": "IM", "priority": "default", "ntfyTags": "white_check_mark" },
"needs_input": { "toastSound": "Reminder", "priority": "urgent", "ntfyTags": "bell,warning" },
"session_start": {
"toastSound": "Default", "priority": "low", "ntfyTags": "rocket",
"toastEnabled": false, "ntfyEnabled": false, "terminalBellEnabled": false, "webhookEnabled": false
}
},
"sources": {
"claude": { "label": "Claude Code", "icon": "https://…/claude-app-icon.png" },
"codex": { "label": "Codex", "icon": "https://openai.com/favicon.ico" },
"gemini": { "label": "Gemini", "icon": "https://…/gemini_sparkle.svg" },
"cursor": { "label": "Cursor", "icon": "https://cursor.com/apple-touch-icon.png" }
}
}| Key | Type | Default | Description |
|---|---|---|---|
ntfy.enabled | boolean | true | Send phone push notifications. |
ntfy.server | URL | https://ntfy.sh | ntfy server, public or self-hosted. |
ntfy.fallbackServer | URL | unset | Optional second ntfy server, tried once if ntfy.server fails (1.7.0 and later). |
ntfy.topic | string | "" | Topic to publish to. Set by the wizard. |
ntfy.click | URL | "" | Opened when you tap the push. Empty = none. |
ntfy.richContent | boolean | false | Put the agent's words in the push body. Keep off on public servers. |
toast.enabled | boolean | true | Desktop toasts. |
toast.clickToFocus | boolean | true | Clicking the toast focuses the originating window (Windows); how it picks the window. |
toast.richContent | boolean | true | Claude Code toasts show the transcript snippet. |
terminalBell.enabled | boolean | true | Ring the launching terminal. |
webhook.enabled | boolean | false | POST to a webhook. |
webhook.url | URL | "" | Destination. Treated as a secret. |
webhook.format | enum | generic | generic, slack, discord, or telegram. |
webhook.chatId | string | — | Telegram chat id (telegram format only). |
webhook.authorization | string | — | Sent verbatim as the Authorization header. |
webhook.richContent | boolean | true | Claude Code webhooks carry the transcript snippet. |
sentry.enabled | boolean | false | Mirror hook errors to your own Sentry project. Opt-in, zero-dependency envelope client, error data only. |
sentry.dsn | string | "" | Your Sentry DSN. |
updateCheck.enabled | boolean | true | Ask the npm registry at most once a day and announce a new version at most once per version through your existing channels (never the bell). false disables the check and its state file entirely. |
usageAlerts.enabled | boolean | true | Claude Code only, 1.5.0 and later. Warn before a usage limit; false turns the warnings off (the statusline keeps working). |
usageAlerts.thresholds | number[] | [70, 85, 95] | Percentages above 0 and at most 100 at which the 5-hour and weekly windows each warn once. An invalid list is reported and the default applies. |
contextAlerts.enabled | boolean | true | Claude Code only, 1.6.0 and later. Warn once per chat before it auto-compacts; false turns the warning off (the statusline and the usage warnings keep working). |
contextAlerts.threshold | number | 85 | Percentage above 0 and at most 100 of the auto-compact window at which a chat warns once. An invalid value is reported and 85 applies. |
quietHours.enabled | boolean | false | Silence all channels inside a daily window. |
quietHours.from / to | HH:MM | 22:00 / 08:00 | Local time, 24-hour. Start inclusive, end exclusive, may span midnight. |
events.<event>.toastSound | string | per event | Windows BurntToast sound name; mapped on macOS, ignored on Linux. |
events.<event>.priority | enum | per event | min, low, default, high, urgent. Drives ntfy priority and Linux urgency. |
events.<event>.ntfyTags | string | per event | Comma-separated ntfy emoji tags. |
events.<event>.toastEnabled | boolean | true | Per-event toast switch. |
events.<event>.ntfyEnabled | boolean | true | Per-event push switch. |
events.<event>.webhookEnabled | boolean | true | Per-event webhook switch. |
events.<event>.terminalBellEnabled | boolean | true | Per-event bell switch. |
events.needs_input.idleReminderPriority | enum | default | Priority for Claude's "waiting for your input" idle reminder only. |
sources.<agent>.label | string | per agent | Name shown in the title. |
sources.<agent>.icon | URL | per agent | Icon URL used by toasts and ntfy. |
Use snooze for a one-off pause and quiet hours for a recurring nightly window. Either one alone silences every channel.
anotifier snooze 30m # also 2h, 90s, or a bare number of minutes (45)
anotifier snooze # prints "Snoozed until 14:32" or "Not snoozed"
anotifier snooze off # cancel early{ "quietHours": { "enabled": true, "from": "22:00", "to": "08:00" } }~/.anotifier/.snooze.json and expires on its own.22:00 to 08:00 covers 22:00–23:59 and 00:00–07:59. from equal to to turns the feature off rather than silencing a full day.anotifier status shows both the snooze deadline and whether you are inside the quiet window right now.Version 1.5.0 and later sends one notification when your Claude Code 5-hour or weekly usage crosses 70%, 85% and 95%, on Claude.ai Pro and Max plans.
Claude Code · 5-hour limit at 72%); the body names the chat, the threshold and the reset time. Priority is default at 70%, high at 85% and urgent at 95%.rate_limits) only to Claude.ai Pro and Max subscribers, and only to its statusline command. anotifier setup therefore routes the Claude Code statusline through src/statusline.mjs. A statusline you already have is wrapped and still prints your line; with none, anotifier prints model · 5h N% · 7d N%. anotifier uninstall restores the original.usageAlerts.enabled and usageAlerts.thresholds (see the config table). A plugin-only install does not run setup, so wire statusLine by hand to src/statusline.mjs.{ "usageAlerts": { "enabled": true, "thresholds": [70, 85, 95] } }Version 1.6.0 and later sends one notification per Claude Code chat when its context passes 85% of the auto-compact window, so you can wrap up or run /compact first.
Claude Code · context at 87%); the body names the chat and the size, such as 174K of 200K tokens. Priority is high.session_id) warns once and never again, even after /compact brings its context back down. /clear starts a new session, which can warn again.CLAUDE_CODE_AUTO_COMPACT_WINDOW environment variable, then the autoCompactWindow setting that /autocompact writes (clamped to 100K to 1M), then the model's own window. It never exceeds the model's window.DISABLE_AUTO_COMPACT or DISABLE_COMPACT set to a true value, or autoCompactEnabled: false). The --autocompact flag and managed settings are invisible to a statusline, so with either the warning can be late or missing.anotifier setup covers both; a plugin-only install must wire statusLine by hand.contextAlerts.enabled and contextAlerts.threshold (see the config table).{ "contextAlerts": { "enabled": true, "threshold": 85 } }The agent is never interrupted. The error is appended to ~/.anotifier/errors.log and shown by anotifier status.
anotifier status lists the last 8 hook errors with timestamp and context, and points at the log file.anotifier doctor runs static checks per channel; doctor --deep sends a real marker notification and reads it back from Notification Center (macOS) or dunst history (Linux). Windows gets a static PowerShell + BurntToast + execution-policy check; the Windows and WSL troubleshooting table maps each error to a fix.sentry.enabled + sentry.dsn) sends error events only, through a built-in zero-dependency client. No SDK is bundled.Only if you say yes, and only in version 1.3.0 and later (1.2.6 and earlier send nothing). They are off by default; a plugin-only install never runs setup, so it never sends any.
# 1.3.0 and later only
anotifier telemetry # on/off, your install id, and the exact counts waiting to be sent
anotifier telemetry off # stop, and delete the local install id and pending counts
anotifier telemetry onanotifier setup you run in an interactive terminal asks (default Yes). A setup that is piped, run through an agent, or whose input closes never asks and leaves stats off, and re-running setup never overrides your earlier choice.posthog.devino.ca, readable by the anotifier maintainers, keyed by a random install id in ~/.anotifier/.telemetry.json. The server is configured to discard client IP addresses and no location is derived.anotifier telemetry shows your install id; anotifier telemetry off deletes it locally. To have data already sent removed, open a GitHub issue with the id.--deep or off, everything else as other), exit code and duration, plus setup (agents found, backend ready, config rebuilt) and the per-channel results of test, doctor and uninstall. Errors report only a clamped error class name and code.DO_NOT_TRACK=1, ANOTIFIER_TELEMETRY=0, or a CI environment turns it off regardless of the setting.Run anotifier uninstall. It removes only the hooks anotifier manages, restores your statusline (1.5.0 and later), and leaves your own hooks and its backups in place.
anotifier uninstall
# then, if you installed globally
npm uninstall -g anotifierCodex trust hashes that anotifier added under [hooks.state] in ~/.codex/config.toml are removed too. Original configs remain at ~/.anotifier/backups/ for you to keep or delete, along with ~/.anotifier/config.json.
Against the real agents and real operating systems, with the delivered notification read back out of each OS's own notification store.
wpndatabase.db (Windows). A silently dropped toast turns CI red.PermissionRequest alert fires.No. It runs locally with zero dependencies. Only the notification text goes to channels you configured yourself, and phone push is generic by default because public ntfy topics are guessable. Version 1.3.0 and later can also send opt-in anonymous usage stats, which are off unless you say yes and never include code, conversation, message text, paths or topics.
Claude Code (terminal and VS Code extension), OpenAI Codex CLI, Cursor's agent, Google Gemini CLI, and, from 1.4.0, Google's Antigravity CLI. All are wired by one setup command and share one config. Antigravity CLI, which replaced Gemini CLI for Google AI Pro and Ultra subscribers, uses its own hook file (~/.gemini/config/hooks.json); anotifier wires its Stop event, so it alerts on "Task complete" and sends no needs-input alert (Antigravity hooks have no such event; Antigravity's own notifications setting covers permission prompts and questions). It has not yet been tested against a live Antigravity CLI.
Yes. The hooks fire whether the agent runs in a standalone terminal, the VS Code integrated terminal, or the Claude Code extension, and no VS Code extension is required. On Windows, clicking the toast brings forward the window whose title contains the project folder.
No. Hooks always exit successfully and return the reply the agent expects. Failures go to ~/.anotifier/errors.log and appear in anotifier status.
No. ntfy is free and account-free. Install the app, subscribe to the topic setup prints, done. You can point anotifier at a self-hosted ntfy server too.
Not yet. anotifier alerts you the instant an approval is requested and, on Windows, click-to-focus brings you back to the right window, but the decision is made at the keyboard. Remote Allow/Deny is on the roadmap pending a security design.
Set the per-event switch, for example events.session_start.toastEnabled: false, or events.task_complete.webhookEnabled: false. Use snooze or quiet hours to silence everything for a while.
Yes. It is open source under AGPL-3.0 with no paid tier and no account. Version 1.3.0 and later offer opt-in usage stats that never include message text, paths or topics. Sponsorship is welcome but never required.
Per-agent setup guides, a comparison with the alternatives, and the source.