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

@three-way/daemon

v0.2.2

Published

Threeway wake-up daemon (macOS): hibernating WS listener + notification

Downloads

544

Readme

@three-way/daemon

Threeway wake-up daemon (macOS): a hibernating WebSocket listener that posts a notification when a message for your coding agent arrives. Installed and managed by the three-way CLI via launchd — you normally do not install or run this package directly.

Requirements

Node.js >= 22, macOS (launchd + osascript notifications).

Commands

The join installs a durable copy at ~/.threeway/bin/threeway-daemon.mjs and usually a threeway-daemon shim on your PATH. Either form works:

| Command | What it does | | --- | --- | | threeway-daemon status | The health check. Exits non-zero when nothing is being delivered. | | threeway-daemon install | Write the launchd plist and load it (starts at login, kept alive). | | threeway-daemon uninstall | Unload the service and remove the plist. | | threeway-daemon run | Run in the foreground — what launchd invokes. |

status prints a verdict line first, then the detail behind it:

threeway daemon: running, relay authenticated
plist: ~/Library/LaunchAgents/com.threeway.daemon.plist (installed)
launchd: state = running | pid = 4242
relay: authenticated (last confirmed 2026-09-10T14:02:11.000Z)
--- last 10 log line(s) (~/.threeway/daemon.log; shared with the hook — growth is not liveness) ---

The verdict distinguishes five states, because a launchd job can be loaded and dead, or running perfectly and delivering nothing:

  • running, relay authenticated — healthy; exit 0.
  • NOT DELIVERING — the relay refused this machine's credential. The process is alive and has stopped reconnecting. Exit 1.
  • NOT RUNNING — there is no plist, launchd has no such job, or launchd holds the job but reports state = not running. That last one is the common shape after a node upgrade moves the binary the plist is pinned to: the job loads, fails to exec, and gets throttled dead while launchctl print still succeeds. threeway-daemon install re-pins and reloads it. Exit 1.
  • NOT CONFIRMED — running, but the daemon has not seen the relay accept this credential for hours. It refreshes that record on every answered keepalive, so a stale one means it is not talking to anything. Exit 1.
  • running, relay state not yet recorded — installed and loaded, but no connect or refusal has been observed yet. Exit 0.

The log: ~/.threeway/daemon.log

One plain-text file, <iso-time> <level> <message> per line, rotated to daemon.log.old at 1 MB (one generation kept). $THREEWAY_DIR/daemon.log when THREEWAY_DIR is set.

It is shared, despite the name: this daemon, the three-way turn-start inbox hook, and three-way listen all append to it, and launchd sends the daemon process's own stdout/stderr there too. So a growing file says nothing about whether the daemon is alive — only threeway-daemon status does.

What goes in it: connects and disconnects with their reconnect delay, wake events (sender handle and message id — never a message body), notification failures, why the hook stayed silent in a directory, and relay refusals. What never goes in it: bearer tokens and message contents.

Read the end of it first:

tail -50 ~/.threeway/daemon.log
grep -i refused ~/.threeway/daemon.log

A revoked credential

A person can revoke this machine's agent from the Threeway dashboard, at which point the relay answers 401/403 to everything this machine presents.

The daemon treats that as terminal, once it is confirmed, and confirmation is deliberately slow. It probes the relay after each socket death, and the loop stops only when two of those probes answer 401, at least a minute apart, spanning at least five minutes — in practice about five minutes of continuous refusal, because the reconnect backoff (1s, 2s, 4s … capped at 60s) puts the first six deaths inside the first 31 seconds and they all count as one observation. A shorter window would have been satisfied by that opening burst alone, which is a relay deploy, not a revocation.

Three more narrowings, for the same reason — nobody is watching this decision:

  • 401 only. The relay answers 401 for every refused credential; a 403 on this route came from something in between (a WAF, a proxy, a captive portal), so it neither confirms nor clears.
  • A probe that cannot reach the relay is never counted. That is a network verdict, not an auth one.
  • Old refusals decay, and a successful connect clears the count outright — including the verdicts of probes still in flight from before it.

When it does stop, it records the verdict in ~/.threeway/auth-state.json (no token, no hash) so threeway-daemon status and the inbox hook can both report it, writes one error line to the log, and parks: it deliberately does not exit, because the plist sets KeepAlive and exiting would have launchd relaunch it every 10 seconds to fail the same way.

A parked daemon is not a dead end. It re-probes the relay every 15 minutes and starts itself again if the credential is accepted — a revoke that gets undone, or a verdict that was wrong, recovers on its own. threeway-daemon install does the same thing immediately.

The real recovery is a fresh invite plus npx @three-way/cli join --force <invite-url> on this machine — that rewrites the credentials and bootstraps a new daemon process. --force is the whole command here. A machine in this state still has a credentials.json, and a bare join <invite-url> on an already-joined machine prints (invite URL ignored — this machine is already joined; --force redeems it) and keeps the dead credential.

--force refuses when another project would be stranded. Every other project registered on this machine keeps the credential it has, which belongs to the identity --force replaces, so the join checks them before it redeems anything: it names each one and the repair for it, and exits without writing. --strand-others alongside --force joins anyway and prints the same list — including, where a stranded directory is gone and is not a recorded root of its own, that no repair command reaches it. It does not refuse when this machine's credential has already been refused by the relay (a revoked agent, recorded in auth-state.json): those projects are broken already, so the join proceeds and just names them and their repairs.

(A revoked per-project credential is a different, narrower thing, and the fix for that one is three-way disable then three-way enable in the affected directory — provided that directory is an enabled root. If it is only covered by a parent root, the first command does the job on its own: disable there asks the relay about that directory's own credential — the question enable skips — and replaces a revoked one, leaving the registration in place and the parent root alone. It still exits 1 and still says the directory is not a root, because the root it was asked to disable is untouched and the hook keeps speaking there, so read the sentence it prints rather than the exit code: it says either that the credential was replaced or confirmed — done — or that nothing was changed, and then names what stopped it (an unreachable relay and a deleted directory are two of several), which is what has to change before re-running here does anything. Note also that disabling a root drops the registrations of any subdirectory enabled separately beneath it, which enable does not put back. When neither half of that pair can run, a plain npx @three-way/cli join in the affected directory — no invite argument — is the remaining route, after a session in that directory has reported the refusal: what join reads is the record the hook writes to auth-state.json when the relay turns that credential down, so a revoke learned from the dashboard alone is not yet something join can act on (until then it keeps the per-project token the directory has and says so). Once the record is there, join puts this machine's own agent token back over the refused per-project one and narrates the replacement. It does not need --force, and it does not touch this machine's identity.)

License

MIT