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

@ahpd/push

v0.14.0

Published

Expo push notifications for the ahpd daemon: a push: resource provider a device registers with, and the sender that tells it when a session needs a person

Readme

@ahpd/push

npm CI license MIT node >=22 Agent Host Protocol 0.9.0

Push notifications for the @ahpd/server daemon. A phone registers its Expo push token once, and hears about a session that needs a person while its app is closed.

It is two halves. A push: resource provider keeps the registered devices, and the plugin watches the host's own moments and sends to the devices whose client created or opened the session that started waiting or finished a turn.

Part of ahpd. The source is in packages/push.

Install

ahpd plugin install @ahpd/push
ahpd restart

Update, remove and one-run flags such as --plugin are in Plugins.

Options

In the configuration file, with every option set:

{
  "plugins": [
    "@ahpd/agent-claude",
    {
      "name": "@ahpd/push",
      "options": {
        "title": "Build box",
        "accessToken": { "$secret": "host:expo" }
      }
    }
  ]
}

No option is required. A daemon that names none still pushes, titled with its own name and without an access token.

| Option | Default | What it does | | --- | --- | --- | | title | the daemon's name | What a notification is titled. A blank one falls back to the daemon's name | | accessToken | none | An Expo access token, where the project has push security on. Given here as a { "$secret": "<name>" }, the vault is read at each send rather than at load |

accessToken is writeOnly, so reading the options back answers <set> rather than the token, and secretAtUse, so the name in { "$secret": ... } reaches the plugin unresolved and is read with host.secret when a send happens. A name the vault does not hold fails that send, with one line in the log and nothing else.

The push: scheme

A device is addressed by its install id, which is the URI path:

| Command | What it does | | --- | --- | | resourceWrite push://devices/<id> | Registers a device, replacing what the id held | | resourceRead push://devices/<id> | The record, as JSON, without its token | | resourceResolve push://devices/<id> | Its metadata. A device that is not there resolves to the shape of one, so a client can draw the form before it writes | | resourceList push://devices | The registered device ids | | resourceDelete push://devices/<id> | Unregisters. A device that is not there is refused -32008 |

The body is JSON with a non-empty token and a platform of ios or android; lang is optional and kept with the device. Anything else is refused -32602. A write with createOnly against an id that is already there is refused -32010, and a URI of another scheme is refused -32602 rather than read as a path.

push:// and push://devices are directories, and nothing below push://devices/<id> names anything.

A registration is gated like any other write, so it needs file:write: a guest with read only cannot register. A client finds out whether a host can push from _meta.ahpd.resourceProviders, the same way it finds computer.

What a notification says

| | | | --- | --- | | Title | The title option, or the daemon's name | | Body | A session is waiting for your answer, or A session is waiting for your approval for toolConfirmation, or A session finished when the session's own chat completes a turn | | Data | { uri: <session>, kind }, which only the app reads. kind is the request's kind, or turnComplete |

A notification never repeats what the session is asking. The text passes through Expo's service and sits on a lock screen, and neither is where a prompt belongs; which session it is travels in data, which a client opens.

Which sessions a device hears

A device hears only about the sessions its own client created or opened, which is the rule the plugin is built around: a device is registered by a connection, the host names the client that created a session and each client that subscribed to one, and a device written by a connection that never introduced itself is kept and sent nothing.

Three more things keep a phone from being woken twice:

  • One message per (session, id) pair. The protocol's action is an upsert keyed by id, so the same entry set again sends nothing; the same id from another session is a different pair and sends.
  • A session that stops waiting sends nothing. There is no second notification that clears the first, so the pair is forgotten and a session that asks the same thing again is a new wait.
  • A turn that was stopped sends nothing, and neither does a turn of a worker's chat. A worker finishes its own turns inside one of the session's.
  • Nothing is sent for a request that is not a person being asked. toolClientExecution is a client running a call that already cleared its gate, so it is not one.

Where devices are kept

One file, push-devices.json, under the daemon's configuration directory, written whole and read when the daemon starts. It is written 0600, because a push token is a credential to that device's notifications.

A device Expo answers DeviceNotRegistered for is removed: the token has been retired, and a message sent there is wasted from then on. The receipts are read at the next send rather than on a timer, so a daemon nobody is waiting on reads nothing.

Documentation

| | | | --- | --- | | PLUGINS.md | Writing and loading a plugin, and the events a plugin may watch | | GET-STARTED.md | Running the daemon |

License

MIT.