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

@neevparikh/pirouette

v0.13.16

Published

Long-lived pi coding agents in the cloud, with a web dashboard and a CLI.

Readme

pirouette

Run long-lived pi coding agents on a cloud VM, with a web dashboard for talking to them and a CLI (pru) for managing the box.

You provision one EC2 instance with a Docker container; pirouette's server runs inside the container and manages a pool of pi agents. Each agent gets its own git worktree so they can work on different branches in parallel.

What this is

  • Single-user. Designed for one person on one cloud box. No multi-user features, no public access path.
  • Long-running. Agents survive across SSH disconnects, browser refreshes, container restarts, even instance reboots (state lives on a persistent volume).
  • Pi-native. Uses pi-coding-agent directly — same session format, same extensions, same provider plumbing. If you've used pi locally you'll recognize the model.
  • Web + CLI. Browser dashboard for chatting; pru for provisioning, shelling in, viewing logs, shipping local changes.
  • Provider-pluggable. Two host providers today:
    • ec2 (default): pirouette owns the lifecycle — provisions an EC2 instance, attaches an EBS data volume, runs the container.
    • byo-host: you provide an SSH-reachable Linux host (e.g. a METR k8s devpod); pirouette installs itself there over SSH. No AWS calls, no Docker. Toggle via provider.kind in ~/.pirouette/config.toml.

What this isn't

  • Not a multi-tenant service. Anyone who can reach the dashboard's port has full shell access on your container (the agents have bash/edit/write tools by design). Today the only thing keeping that perimeter narrow is your AWS security group + SSH tunnel. See Trust model below.
  • Not yet authenticated at the application layer. A random shared bearer token is on the roadmap.
  • Not an eval-style harness. No sandboxing of agent actions beyond what the container itself provides.

Install

npm install -g @neevparikh/pirouette   # provides both `pirouette` and `pru`

Quick start (cloud)

The primary use case. Provisions an EC2 instance, attaches a 500 GiB EBS volume, runs your Docker image, installs pirouette inside it, and serves the dashboard at a Tailscale HTTPS URL.

One-time setup:

  1. Install the AWS CLI and aws sso login (or otherwise authenticate) to a profile that can create EC2 + EBS in your target region.
  2. Create ~/.pirouette/config.toml with your AWS + tailnet info — see Configuration below.

Then:

pru preflight     # read-only: verify AWS config + resource discovery
pru setup         # provision: instance + EBS + container + server

The last step of pru setup prints a one-time bring-up recipe for Tailscale on the host (install + tailscale up --ssh --hostname=...

  • tailscale serve --bg --https=443 http://localhost:7777) which gives you https://<host>.<tailnet>.ts.net/ as the dashboard URL. Drop that into server.public_url in your config and you're ready.

Day-to-day:

pru open          # open the dashboard URL in your browser
pru ssh           # shell into the container
pru status        # instance state + server health
pru logs -f       # tail server logs

When you're done for a while:

pru teardown      # stop the instance; EBS preserved (state survives)

To rebuild from scratch:

pru destroy [--delete-volume]   # terminate; optionally also delete EBS

Quick start (byo-host)

If you already have an SSH-reachable Linux box you want to use — a METR k8s devpod, a long-running personal VM, your team's shared researcher box — the byo-host provider points pirouette at it without any provisioning. Pirouette uploads a bootstrap script over SSH that handles the home migration, pirouette install, and tmux session.

The remote needs node, npm, git, tmux, sshd, a non-root sudo-able user, and (recommended) yadm for dotfiles. Use the same image you'd use for pru setup — the entrypoint contract is identical (see Container image requirements).

One-time setup:

  1. Make sure you have an ~/.ssh/config entry for the host (e.g. Host gpu with HostName, User, etc.). Test it: ssh gpu echo ok.

  2. Write ~/.pirouette/config.toml:

    [provider]
    kind = "byo-host"
    
    [provider.byo-host]
    ssh_alias       = "gpu"             # entry in ~/.ssh/config
    persistent_root = "/data"           # mount-point of the persistent volume
    user            = "neev"            # SSH login user
    # home_dir = ""                     # optional override; default ${persistent_root}/home/${user}
    # data_dir = ""                     # optional override; default ${persistent_root}/pirouette/data
    
    [container]
    npm_package = "@neevparikh/pirouette@latest"
    
    [dotfiles]
    clone_url           = "https://github.com/you/dotfiles.git"  # optional
    authorized_keys_url = "https://github.com/you.keys"          # recommended

Then:

pru preflight     # ssh alias resolves, ssh probe, persistent root exists, tooling present
pru setup         # upload bootstrap, run it, push secrets, wait for /api/health

Laptop access goes through an SSH tunnel (the server binds 127.0.0.1 on the remote — see Trust model):

# Easiest: add a LocalForward line to your ssh_config:
#   Host gpu
#     LocalForward 7777 localhost:7777
# Then any `ssh gpu` opens the tunnel as a side-effect.
export PIROUETTE_URL=http://localhost:7777
pru open          # dashboard
pru list          # CLI

Day-to-day (same commands as the EC2 path; provider-aware under the hood):

pru ssh           # shell on the remote (no host/container split)
pru logs -f       # tail the server log
pru status        # ssh probe + home-symlink health + tmux state
pru sync          # local rebuild -> remote install -> tmux restart
pru sync --npm    # upgrade to latest published package
pru sync --secrets   # re-push laptop pi auth state
pru teardown      # kill the pirouette tmux session (host stays up)
pru destroy       # clear local state (use --delete-volume to also rm -rf the persistent dirs)

Provisioning the devpod itself, GPU allocation, etc. — those are your responsibility (use whatever you already use, e.g. METR's devpod TUI). Pirouette doesn't touch the pod lifecycle.

Quick start (local dev)

For developing pirouette itself, or running agents locally without any remote host:

pirouette server                # binds 127.0.0.1:7777
open http://localhost:7777

Local mode skips the entire AWS / Docker / Tailscale layer — you're just running the server process directly. Most useful for working on the dashboard or the server code. Set PIROUETTE_URL=http://localhost:7777 in the same shell so the CLI talks to your local server instead of the cloud one.

Configuration

Pirouette reads TOML from three places, in order (later wins):

  1. Built-in defaults
  2. ./pirouette.toml (packaged with the tool; generic defaults only)
  3. ~/.pirouette/config.toml (your per-user overrides; not checked in)

pru config show prints the effective merged config; pru config edit opens your override file in $EDITOR.

Required fields for pru setup

pru setup will refuse to run until these are set in ~/.pirouette/config.toml:

| key | what it is | |---|---| | aws.network.vpc_name | Name tag of the VPC to launch into | | aws.network.subnet_name_pattern | Name-tag glob for private subnets; first alphabetical match wins | | aws.network.security_group_name | Existing SG attached to the instance (must allow SSH inbound from your location) | | aws.tags.Owner | Tag applied to every created resource — usually your email | | instance.key_name | An existing EC2 keypair; if missing, pirouette imports ssh.public_key_path under this name | | container.image | Dev container image the instance runs (see container requirements) | | container.container_user | Non-root user baked into that image (used for bind-mount paths) | | container.npm_package | The npm package spec to install inside the container (e.g. @your-scope/pirouette@latest) |

Container image requirements

Any image you use as container.image needs:

  • A non-root user (container.container_user) with passwordless sudo
  • node + npm installed globally
  • tmux, git, curl, and an ssh server
  • (Optional) yadm if you want dotfiles support

npx27/dev-unfetched satisfies all of this out of the box (Arch Linux, user neev, uid 1000). Build your own for a leaner footprint.

Minimal ~/.pirouette/config.toml

[aws]
profile = "my-aws-profile"
region  = "us-west-2"

[aws.network]
vpc_name            = "my-vpc"
subnet_name_pattern = "my-private-subnet-*"
security_group_name = "my-dev-sg"

[aws.tags]
Owner = "[email protected]"

[instance]
key_name = "[email protected]"

[container]
image          = "npx27/dev-unfetched:latest"   # or your own image
container_user = "neev"                         # match your image's user
npm_package    = "@neevparikh/pirouette@latest"

# Optional — both are skipped if empty.
[dotfiles]
clone_url           = "https://github.com/you/dotfiles.git"
authorized_keys_url = "https://github.com/you.keys"

Commands

Agents

| command | purpose | |---|---| | pru launch <name> | Create a new pi agent (--repo, --model, --thinking optional) | | pru list | List all agents and their state | | pru send <agent> <msg> | Send a message to an agent | | pru stop <agent> | Stop an agent (keeps its state) | | pru rm <agent> | Remove an agent; --all also deletes its worktree + session files | | pru status | Show remote instance + server health |

You can also create agents from the web UI by typing @<newname> message in the input bar.

Infrastructure

| command | purpose | |---|---| | pru preflight | Read-only: validate AWS config + resource discovery | | pru setup | Provision / resume the EC2 instance + start the container | | pru teardown | Stop the instance; EBS preserved | | pru destroy [--delete-volume] | Terminate; optionally delete EBS | | pru open | Open the dashboard (uses server.public_url) | | pru ssh / pru ssh --host | Shell into the container (agent forwarded) / the EC2 host | | pru tunnel <port> | Forward an extra port (mainly for OAuth loopback flows — see below) | | pru logs [-f] | Tail server logs (--tmux, --entrypoint, --boot for other sources) | | pru sync | Ship local changes to the remote container (dev loop) | | pru sync --npm | Upgrade the container from the npm registry | | pru sync --secrets | Re-push laptop's auth state (auth.json etc.) without redeploying |

Config

| command | purpose | |---|---| | pru config show | Show effective merged config | | pru config path | Print config file search paths | | pru config edit | Open ~/.pirouette/config.toml in $EDITOR |

Environment variables

Rarely needed — the CLI reads config from TOML. These override specific runtime values.

| var | default | purpose | |---|---|---| | PIROUETTE_HOST | 127.0.0.1 (container path passes 0.0.0.0) | Server bind host | | PIROUETTE_PORT | 7777 | Server port (or container.pirouette_port in config) | | PIROUETTE_DATA_DIR | .pirouette/data | Server data directory | | PIROUETTE_URL | server.public_url from config | CLI → server URL (overrides config; useful for local dev with npm run dev) | | AWS_PROFILE | — | Overrides aws.profile |

Authenticating tools inside the container

Most modern CLIs you'd run in the container support device flow — they print a URL and a short code, you approve on any device, the CLI polls a server until it sees the approval. No local callback, no port forwarding required:

| tool | what to run | |---|---| | AWS SSO | aws sso login (default behavior) | | GitHub CLI | gh auth login --web | | gcloud | gcloud auth login --no-launch-browser | | Tailscale | tailscale up |

For these you just pru ssh, run the command, copy the URL it prints into your laptop browser, approve, done.

The exception is OAuth tools that only support the "loopback IP" flow — they spin up a local HTTP server on a random port and require the browser to redirect to http://localhost:<port>. gws (Google Workspace CLI) is one such tool. For these you need to forward the callback port from your laptop to the container:

# Terminal 1 — inside container
pru ssh
gws auth login --services drive,sheets
# Note the port from the URL it prints, e.g. redirect_uri=http://localhost:42103

# Terminal 2 — on laptop
pru tunnel 42103
# (foreground; ctrl-c to close when auth is done)

# Terminal 3 (or just paste into your browser): open the URL gws printed

Use LOCAL:REMOTE syntax if you need different ports on each side (e.g. pru tunnel 8080:42103), or --background to add the forward and return immediately (close later with pru tunnel --close 42103).

Under the hood, pru tunnel reuses the SSH ControlMaster connection that pirouette sets up at pru setup time (~/.pirouette/ssh-control/), so adding/removing forwards is instant after the first SSH call. If no master exists it falls back to spawning a fresh ssh -L … process.

Troubleshooting

Tailnet outage — SSH-tunnel escape hatch

The canonical access path goes through Tailscale. If your tailnet is unreachable (account issue, ACL change, Tailscale incident, etc.), you can fall back to a manual SSH tunnel as long as your AWS SG and SSH key still work:

# Start a port forward in one terminal:
ssh -L 7777:localhost:7777 -N pirouette

# In another terminal, point the CLI + browser at localhost:
export PIROUETTE_URL=http://localhost:7777
pru open

This is intentionally not wired up as a pru subcommand — if you're in this situation you're already debugging something out of band, and the two-line manual recipe is clearer than pru open --tunnel magic.

Trust model

Pirouette has no application-layer authentication. The HTTP and WebSocket APIs are wide open to anyone who can reach the listener. What keeps that narrow today depends on the provider:

  • ec2 provider: AWS security group (only port 22 inbound, only from the source SG you configure, e.g. a Tailscale subnet router) + the SSH key required to open the port-forward to the container. The container binds 0.0.0.0:7777 (Docker port-mapping needs it); the SG
    • SSH layer is the actual perimeter.
  • byo-host provider: the bootstrap binds the server to 127.0.0.1 on the remote. The only way in is an SSH tunnel from your laptop; the remote's network can't reach the listener at all. No 0.0.0.0 exposure even on a shared k8s pod network.
  • pirouette server (local-dev): binds 127.0.0.1 for the same reason.
  • Same-origin web app — the dashboard is served from the same listener as the API. Cross-origin requests are rejected by Host validation (HTTP) and Origin validation (WebSocket); there are no Access-Control-Allow-* headers. (DNS-rebinding defense; not auth.)

In practice: anyone who can establish a TCP connection to the dashboard port has shell access on the host pirouette runs on. The agents have full bash/edit/write tools by design. The SG + SSH tunnel (EC2) or SSH-tunnel-only access (byo-host) is the perimeter.

Things you're trusting (the supply chain)

  • The npm package @neevparikh/pirouette (or whatever you set container.npm_package to).
  • The dotfiles repo at dotfiles.clone_url (yadm clone over HTTPS).
  • The keys served at dotfiles.authorized_keys_url (used as authorized_keys for the container's sshd).
  • Your AWS account's network isolation.
  • Trust-on-first-use SSH host keys (StrictHostKeyChecking=accept-new). Fine for a private VPC; pre-seed ~/.ssh/known_hosts manually if you're sharing a network with untrusted parties.

Browser libraries (marked, marked-highlight, DOMPurify, highlight.js, Tailwind) are vendored at build time — no CDN dependency at runtime.

Operational mitigations

If your threat model is stricter than what's enforced by code today:

  1. Don't broaden the SG.
  2. Don't bind the dashboard to a public IP. PIROUETTE_HOST=0.0.0.0 is for the container path; everything else should stay loopback.
  3. Treat anyone with read access to your laptop's ~/.ssh/ as having full pirouette access.

Architecture

Your laptop                      EC2 instance                       Docker container
───────────       SSH tunnel    ─────────────       port 7777      ──────────────────
pru CLI  ─────── localhost:7777 ──── :7777 ──── pirouette server
                                                                    ├── agent manager (pi SDK)
                                                                    ├── HTTP + WebSocket
                                                                    └── web dashboard (static)
Browser  ─────── localhost:7777 ──── :7777 ────
                                                                    sshd on :22
pru ssh ────── jump via host ─────── :2222 ────  zsh + yadm dotfiles
                                                                                     │
                                                 persistent EBS volume at /data ────┘

License

MIT.