npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

node-macos-alerter

v0.1.0

Published

Node.js wrapper around the alerter macOS notification CLI

Downloads

28

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:

  1. Checks for a cached binary at <packageRoot>/bin/alerter.
  2. If absent, fetches the release zip from GitHub (alerter-26.5.zip), following HTTPS redirects using Node's built-in https module — no extra runtime dependencies.
  3. Extracts the binary with unzip (always available on macOS), sets chmod 0o755, and caches it at <packageRoot>/bin/.
  4. 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.