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

@paigy/harness

v0.2.4

Published

Run Claude Code / Codex on this machine, bridged to Paigy — launchable from your phone.

Readme

paigy-harness

Runs Claude Code and Codex on your machine, mirrors every turn to your Paigy inbox, and — when you ask it to — rings your phone when an agent is blocked (#804).

Why this exists

Hooks already cover turn capture — .claude/settings.json, .codex/hooks.json, and paigy-listen + PAIGY_ON_WAKE (#486) between them can observe and wake any harness without a desktop app. Two things need a process that actually owns the agent:

  • Permission prompts reach you. When Claude Code wants to run something destructive or edit outside the workspace, today it blocks on a terminal nobody is watching. Here it becomes an approve/deny on your phone, and the agent unblocks with your answer.
  • Capture is structural, not advisory. contact fires because a model chose to call it. A turn boundary in a harness is a fact of the runtime.
  • The conversation goes both ways. Your words reach a running agent's stdin — so you can steer it from a walk, not just approve what it already decided to do.

The round trip

The point isn't notifications, it's a conversation with an agent that happens to be running on your laptop. Both directions close (paigy/conversation.ts):

  • Answering. The agent's turn ends on a question → it banners → your reply goes straight back into stdin as the next turn. Without this the loop is one-directional and dies at the first idle.
  • Initiating. You say something unprompted from your phone → it arrives as a request on the thread → same stdin. You never have to wait to be asked.

Both ride the same thread, so the phone shows one conversation. endsWithQuestion is what decides "the agent is waiting on you" versus "here's your history", and it is deliberately conservative: a false positive buzzes someone who was never asked anything.

Answers are read out of whatever shape they arrive in (spokenText) — a tapped option, a spoken sentence, a whole call log — because the words are what the agent needs, not the envelope. A silence is never turned into a prompt; the agent must not end up talking to itself on your behalf.

It is not a chat client. The terminal stays where you work; this window is config plus a log of what's being mirrored. A second place to read agent output would defeat the point — the second place is your phone.

Approvals: bypass by default, ask when told

Buzz taught us the default (block/buzz auto-approves every session/request_permission with allow_once): an agent that stalls on every tool call is an agent nobody runs. So bypass is the default mode — the driver pins the adapter's permission mode to bypassPermissions and answers whatever still asks — but unlike Buzz, every auto-approval still lands on the rail as history. Bypass means "don't stall the agent", never "don't tell the user".

ask mode is the original #804 behavior, one toggle away: blocked actions banner (or ring, when destructive) and your answer unblocks the process. Ask pins the adapter to default (manual prompts) explicitly — claude-agent-acp otherwise opens in auto, a classifier that answers permission prompts in the user's place, which is precisely the job this mode reserves for the user.

One rail, level per notification

Mirrored turns and blocking asks ride the same notifications rail. What separates silent history from "answer me now" is urgency, not a second store:

| Event | Level | Why | | --- | --- | --- | | a turn (agent or user) | inbox | lands, never buzzes; searchable, handoff-able history | | a blocked action | banner | claims attention; escalates on its own if unanswered | | a blocked destructive action | call | rm -rf doesn't get to wait for the escalation ladder | | a turn that asked you something | banner | a question filed silently is a conversation that dies | | a turn that failed | push | worth knowing soon, not worth a sound | | our own parse failure | — | not on the rail at all; it's our problem, not yours |

paigy/level.ts decides; apps/api/src/notify/levels.ts still arbitrates against your session mode and account permissions and can only lower it.

The catalog and the doctor

The other thing Buzz got right: connecting a harness should be a status line and a button, not a wiki page. harness/catalog.ts is a compiled-in table per runtime — the binaries to probe (PATH plus the dirs a GUI app can't see; Finder-launched apps don't get your shell PATH), an auth probe (codex login status), a login hint, and an install one-liner. The window renders it as a doctor: ✓ ready, ◐ needs login (with the command to run), ✗ missing (with an Install button — the command comes from the catalog, never the renderer). Starting a half-configured harness doesn't fail silently: the hint lands on your Paigy rail as a push (nudgeSetup).

One transport: ACP

Both harnesses speak ACP — the Agent Client Protocol, JSON-RPC over stdio — through their adapters: @agentclientprotocol/claude-agent-acp and …/codex-acp. One driver (acp.ts), no parser museum; ACP gave Codex the permission channel codex exec never had, and it's the door to every other harness that speaks it (Goose, Cursor, Devin…) as a catalog entry. The stream-json Claude adapter this replaced lives in git history.

Layout

src/harness/   events.ts   the neutral HarnessEvent the driver produces
               acp.ts      the ACP driver — both harnesses, any ACP agent tomorrow
               catalog.ts  which harnesses exist, detection, auth probes, installs
               session.ts  spawn, line-split, lifecycle — the only impure file here
src/paigy/     level.ts        event → NotifyLevel
               bridge.ts       mirror turns, ask permissions/questions, setup nudges
               conversation.ts the round trip — your words into a running agent's stdin
src/run.ts     the whole bridge, Electron-free — what main.ts and cli.ts both drive
src/main.ts    Electron main; renderer/ is a doctor, a form and a log
src/cli.ts     `paigy-harness` — the same bridge from a terminal or a service unit

Parsing lives in the drivers so the whole protocol surface is testable without spawning anything.

Pair this Mac (the beacon)

Unpaired (or still borrowing a legacy ~/.paigy slot), the window shows a QR — the device flow's verification URL. Scan it with the phone camera, approve on the phone, and the token lands in the app's own Desktop slot: the machine becomes its own identity ("Mauricio's Mac"), sessions and hatched agents mint underneath it, and the default slot goes back to belonging to whatever terminal agent paired it. Legacy setups keep working until scanned — the beacon is an upgrade, never a wall.

Allowed workspaces

An allow-list of folders at ~/.paigy/workspaces.json — device config, shared by the window and the headless host, so the phone's offer never depends on which entry point is running. A session may only start inside a granted folder (the desktop equivalent of a permission scope), curated through the window's native picker or paigy-harness host --grant DIR. This list is exactly what the phone offers when you launch or assign a session. The CLI's positional-run path deliberately does NOT enforce it: a path typed into your own shell is its own grant.

Launch from the phone

While a host is running (window open, or paigy-harness host), its heartbeat advertises ready harnesses + granted workspaces. On the phone: Agents → "+ New session" picks from those options and gives the session the pairing flow's identity gestures — a name and a voice — because a session IS a minted pairing: its turns and everything you say back ride its own conversation. The notes assign picker offers the same as "New session · " rows, naming the session after the note and delivering the brief as its opening request.

Hatch — pair one device, mint many agents

paigy-harness hatch "Name" mints a pre-paired sibling identity under your account — your existing pairing is the ceremony; no code, no phone round-trip. It lands in its own ~/.paigy slot:

paigy-harness hatch "Voice bug hunter"
PAIGY_AGENT="Voice bug hunter" claude                 # any tool speaks as it
paigy-harness --identity "Voice bug hunter" --harness codex "fix the flaky test"

Hatched agents appear on the phone's Agents screen like any pairing — renameable, re-voiceable, revocable.

Install anywhere (the curl path)

curl -fsSL https://paigy.ai/install | sh

Installs the npm CLI (one self-contained file — the workspace libs are bundled) and runs paigy-harness setup: QR-pairs the machine, hatches each detected agent's identity (no codes), registers the MCP + statusline, grants a workspace, installs the host service. paigy-harness service installs a macOS launchd agent so hosting survives reboots — the host loop IS the service, launchd just keeps it alive (the old "no daemon" note was about the WAKE channel, which still belongs to paigy-listen).

Run it

pnpm --filter @paigy/harness dev          # the window (status + host; sessions start from the phone)
pnpm --filter @paigy/harness build
node apps/desktop/dist/cli.js --doctor    # or headless: paigy-harness
node apps/desktop/dist/cli.js --harness codex --cwd ~/repo "fix the flaky test"
node apps/desktop/dist/cli.js host --grant ~/projects   # standby, launchable from the phone

The window deliberately has NO run form — launching moved to the phone (companion.md). It shows pairing, the doctor, the workspace allow-list, a Stop button, and the log.

Headless flags: --harness claude|codex, --mode bypass|ask, --cwd DIR, --identity NAME, --grace SECONDS, --doctor; subcommands host [--grant DIR]… and hatch NAME. In ask mode the CLI is TERMINAL-FIRST: a blocked action or open question prints in the terminal and waits --grace (default 90s); a person at the keyboard answers in place, an unattended terminal escalates to your phone (0 = phone immediately; non-TTY runs always go straight to the phone). The CLI is the proof the harness doesn't need the app: run.ts/host.ts never import Electron — a machine with only the CLI is exactly as launchable as one with the window.

Pairing is one-time and lives in ~/.paigy — the app reads it, never mints it. If you've never paired: paigy-harness setup.

Uninstall

curl -fsSL https://paigy.ai/uninstall.sh | sh removes the harness surface (service, global package, workspace list, the machine's Desktop slot — other agents' slots survive). Then revoke the machine on the phone's Agents screen: server-side tokens outlive local files.

The FULL Paigy strip (tested 2026-08-03) additionally removes, in order: every ~/.paigy slot (unpairs ALL local agents — re-pairing resets voices), the ACP adapters (npm rm -g @agentclientprotocol/{claude-agent-acp,codex-acp}), Electron userData, the statusline block + paigy@paigy plugin enablement + marketplace + mcp__paigy__* permission entries in ~/.claude/settings.json, the project .mcp.json paigy server, the plugin cache/registry entries, and ~/.npm/_npx (so @paigy/mcp@latest can't serve stale). Hooks can recreate ~/.paigy — delete it LAST, after the hooks are gone.

Known gaps (#804)

  • Protocol churn is the standing risk. Neither stream-json nor ACP adapter behavior is a stability contract. Every driver fails soft by design: an unrecognized frame is skipped, never fatal. If mirroring goes quiet after a CLI update, re-verify the frame names in the driver doc comments first.
  • Auth probes are vendor commands (claude auth status, codex login status) and can drift with CLI releases; a probe failure reads as "needs login", so drift shows up as a nagging hint, not a broken start.
  • No session resume, no packaging. Signing, notarization and auto-update are unstarted (they block on Apple credentials, not code).
  • Concurrent sessions (2026-08-03): every claimed spec runs under its OWN minted token — per-run identity is threaded through every network touch (run.ts binds submit/check/ack once per session), so parallel sessions never share a conversation. "Stop sessions" stops them all; per-session stop is UI away.
  • The host service IS the daemon (paigy-harness service, launchd, KeepAlive) — it claims phone launches and runs sessions. The WAKE channel for terminal-agent replies still belongs to paigy-listen + PAIGY_ON_WAKE (#486); spawn-on-wake needs the slot-lease design in companion.md before it auto-wires.