node-macos-alerter
v0.1.0
Published
Node.js wrapper around the alerter macOS notification CLI
Downloads
28
Maintainers
Readme
node-macos-alerter
A Node.js library for sending native macOS notification alerts with full TypeScript support. A thin, Promise-based wrapper around vjeantet/alerter — the alerter binary is downloaded automatically on first use, no manual installation required.
Requires macOS 13.0 or later and Node.js 18 or later.
Usage
import { sendAlert, removeAlert, listAlerts } from 'node-macos-alerter'Actions alert
Display a notification with one or more buttons and await the user's choice.
const result = await sendAlert({
title: 'CI/CD',
message: 'Deploy to production?',
actions: ['Deploy', 'Cancel'],
timeout: 30,
})
switch (result.type) {
case 'actionClicked': console.log('Action:', result.action) // 'Deploy' or 'Cancel'
case 'timeout': console.log('No response within 30s')
case 'closed': console.log('Dismissed')
case 'contentClicked': console.log('Notification body clicked')
}Reply alert
Display a notification with a text input field and await what the user types.
const result = await sendAlert({
title: 'Release',
message: 'Name this release:',
reply: 'e.g. v2.0.0',
})
if (result.type === 'replied') {
console.log('Release name:', result.reply)
}Notification groups
The group option scopes a notification to an ID. Posting to the same group replaces any existing notification with that ID, ensuring only one is ever shown. Use removeAlert() to dismiss it programmatically.
await sendAlert({
message: 'Build in progress...',
group: 'my-app-build',
})
// Later, dismiss it without waiting for user interaction
await removeAlert('my-app-build')
// Or remove all active notifications
await removeAlert('ALL')List active notifications
const active = await listAlerts('ALL')
console.log(active)All options
| Option | Type | Description |
| -------------- | ---------------------- | -------------------------------------------------------------------------------------------- |
| message | string (required)| The notification body text. |
| title | string | The notification title. Defaults to 'Terminal'. |
| subtitle | string | A subtitle line below the title. |
| actions | [string, ...string[]]| One or more action button labels. Cannot be combined with reply. |
| reply | string | Placeholder text for a reply input field. Cannot be combined with actions. |
| dropdownLabel| string | Label for the actions dropdown (only shown when actions has more than one value). |
| closeLabel | string | Custom label for the Close button. |
| sound | string | Sound to play on delivery. Use 'default' for the system default. |
| group | string | Notification group ID. Replaces any existing notification in the group. |
| sender | string | Bundle ID of an app to impersonate (e.g. 'com.apple.Safari'). |
| appIcon | string | Path or URL of an image to use as the app icon. (private API) |
| contentImage | string | Path or URL of an image to display inside the notification. (private API) |
| delay | number | Seconds to wait before delivering. Cannot be combined with at. |
| at | string | Deliver at a specific time: 'HH:mm' or 'yyyy-MM-dd HH:mm'. Cannot be combined with delay. |
| timeout | number | Seconds before the alert auto-dismisses (resolves as { type: 'timeout' }). |
| ignoreDnd | boolean | Send even when Do Not Disturb is enabled. (private API) |
Result type
sendAlert() returns Promise<AlertResult>, a discriminated union:
| type | Additional fields | When |
| ---------------- | -------------------- | --------------------------------------------- |
| 'closed' | — | User clicked the Close button |
| 'timeout' | — | Alert auto-dismissed after timeout seconds |
| 'contentClicked'| — | User clicked the notification body |
| 'actionClicked'| action: string | User clicked an action button |
| 'replied' | reply: string | User submitted a reply |
How it works
Binary management
alerter is a native macOS Swift binary distributed via GitHub releases. On the first call to sendAlert(), removeAlert(), or listAlerts(), the library:
- Checks for a cached binary at
<packageRoot>/bin/alerter. - If absent, fetches the release zip from GitHub (
alerter-26.5.zip), following HTTPS redirects using Node's built-inhttpsmodule — no extra runtime dependencies. - Extracts the binary with
unzip(always available on macOS), setschmod 0o755, and caches it at<packageRoot>/bin/. - Subsequent calls skip all of this — the cached path is returned immediately.
The download is deduped: if multiple calls happen before the binary is ready, they all await the same in-flight Promise rather than racing to download it multiple times.
Spawning and parsing
Each sendAlert() call spawns the alerter process via child_process.spawn and collects its stdout. The process blocks until the user interacts with the notification or it times out — that's alerter's design. When it exits, the raw stdout is mapped to a typed AlertResult:
| alerter stdout | AlertResult |
| ---------------- | ---------------------------------------- |
| @CLOSED | { type: 'closed' } |
| @TIMEOUT | { type: 'timeout' } |
| @CONTENTCLICKED| { type: 'contentClicked' } |
| @ACTIONCLICKED | { type: 'actionClicked', action: ... } |
| <action label> | { type: 'actionClicked', action: ... } |
| <reply text> | { type: 'replied', reply: ... } |
When a single --actions value is used, alerter emits @ACTIONCLICKED rather than the label itself. The library recovers the label from the original options and includes it in the result. When multiple actions are configured, alerter emits the selected label directly.
