@mipsel64/pi-remote-control
Pi extension that attaches running Pi sessions to Pi Remote Control (prc).
Package details
Install @mipsel64/pi-remote-control from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:@mipsel64/pi-remote-control- Package
@mipsel64/pi-remote-control- Version
0.1.8- Published
- Sep 30, 2026
- Downloads
- 1,217/mo · 1,217/wk
- Author
- vietanhduong
- License
- MIT
- Types
- extension
- Size
- 42.9 KB
- Dependencies
- 1 dependency · 0 peers
Pi manifest JSON
{
"extensions": [
"./extensions/remote-control.ts"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
Pi Remote Control
Control your already-running Pi sessions from a phone or browser: live output, send prompts, stop runs, switch models, and get a notification when a prompt finishes or Pi needs your input.
It has two parts: the prc server (one binary with the web UI built in) and the @mipsel64/pi-remote-control Pi extension, which attaches a Pi session when you run /rc.

Remote prompts run tools with your user's permissions. Treat access like SSH: keep the server on localhost or your private Tailscale network.
Quick start
1. Install the server. Download prc-vX.Y.Z-<platform>.tar.gz for darwin-arm64, darwin-amd64, linux-amd64, or linux-arm64 from Releases, plus checksums.txt, then:
shasum -a 256 -c checksums.txt --ignore-missing # Linux: sha256sum -c ...
tar -xzf prc-v*.tar.gz
mkdir -p ~/.local/bin && mv prc ~/.local/bin/
Or use Docker, or build from source with make install (needs Node 22.18+ and Rust). macOS may ask you to allow the unsigned binary in System Settings → Privacy & Security.
2. Create a config and start the server.
prc setup # writes ~/.config/prc/config.json and prints the admin password once
prc serve # http://127.0.0.1:8787
3. Attach Pi. Install the extension, then run /rc inside any Pi session you want to control:
pi install npm:@mipsel64/pi-remote-control
Open http://localhost:8787, sign in with the admin password, and pick the session.
Use it from your phone
Put Tailscale Serve in front of the server. Use Serve, not Funnel, so it stays private to your tailnet:
tailscale serve --bg 8787
tailscale serve status # shows https://<machine>.<tailnet>.ts.net
Set publicOrigin in ~/.config/prc/config.json to that exact address (no trailing slash), restart prc, and open it on your phone. http://localhost:8787 keeps working on the server itself.
For notifications on iPhone, open the address in Safari, tap Share → Add to Home Screen, open the app from the new icon, and tap the bell in the top-right corner.
To tell several servers apart on the Home Screen, pick an App icon colour in Settings before adding the shortcut. A shortcut keeps the icon it was added with, so remove it and add it again after changing the colour.
Pi commands
| Command | What it does |
|---|---|
/rc |
Attach this session (sends its full history) |
/rc status |
Show the connection state |
/rc close |
Detach and stop reconnecting |
/rc setup |
Show the endpoint and token source (token redacted); edit the client URL |
Pi shows a one-time "Remote control connected" notice when /rc connects, and the footer shows /rc connected in green, or a warning while it retries (automatic reconnects are silent). Attach again after /new to add the new session to the list.
Web-submitted steering during a run appears under queued until Pi consumes it; prompts held during compaction/retry gaps are shown there too. Idle Send goes straight into the conversation without a pending row. This is a display, not a second delivery queue. Stop clears the display; Pi keeps ownership of native queue/editor restoration. Browser and agent reconnects retain pending previews in the same extension runtime.
Pi's extension API exposes only whether native messages are pending, not their contents or dequeue events. The extension checks that flag every 250 ms while previews exist and reconciles confirmed pending entries when the native queue stays empty. Terminal dequeue/edit can therefore lag by a poll; clear-and-requeue between polls, or unrelated terminal messages still pending, can leave a preview until consumption or run settlement. Inputs transformed/handled by other extensions cannot be matched exactly during a continuing run; unconfirmed previews clear when it settles. Reloading the extension resets its display ledger. Terminal-only submissions are not mirrored in this list.
Configuration
Config files must be mode 0600. Restart prc after changing config.json, and run /rc close then /rc in Pi after changing either file.
Server: ~/.config/prc/config.json, created by prc setup:
{
"publicOrigin": "https://my-mac.tail1234.ts.net",
"agentToken": "<random token generated by prc setup>",
"adminPassword": "<random password printed by prc setup>",
"host": "127.0.0.1",
"port": 8787
}
| Field | Required | Meaning |
|---|---|---|
publicOrigin |
yes | The exact URL your browser uses. Logins from other origins are rejected. |
agentToken |
yes | Secret that Pi uses to connect. |
adminPassword |
yes | Browser login password. |
host, port |
no | Listen address. Default 127.0.0.1:8787. |
vapidPublicKey, vapidPrivateKey, vapidSubject |
no | Your own Web Push keys. By default they are generated automatically. |
Pi on another machine: ~/.config/prc/client.json on that machine. Use this when Pi does not run on the same machine as the server, or to override the URL or token that /rc reads from config.json:
{
"url": "wss://my-mac.tail1234.ts.net/agent",
"token": "<agentToken from the server's config.json>"
}
Both fields are optional. url must end in /agent and use wss://; ws:// is only allowed for localhost. /rc setup can edit url, but never asks for or displays the token, so edit token outside Pi (chmod 600 client.json).
Environment variables override the files:
| Variable | Used by | Meaning |
|---|---|---|
RC_PUBLIC_ORIGIN, RC_AGENT_TOKEN, RC_ADMIN_PASSWORD, RC_HOST, RC_PORT |
server | Override the matching config.json field. |
VAPID_PUBLIC_KEY, VAPID_PRIVATE_KEY, VAPID_SUBJECT |
server | Override the push keys. |
PI_RC_URL + PI_RC_AGENT_TOKEN |
extension | Set both to replace client.json. |
RC_CONFIG |
both | Use a different config.json path. client.json is read from the same directory. |
The server stores the latest transcript of the 50 most recent sessions, plus its push keys and subscriptions, in ~/.local/state/prc ($XDG_STATE_HOME/prc). Offline sessions stay readable; remove one with Remove in its ⋯ menu in the session list. To clear everything, stop prc and delete that directory.
Run as a service
From a source checkout, the Makefile installs a per-user service: a macOS LaunchAgent or a Linux systemd user unit. It needs no sudo.
| Command | What it does |
|---|---|
make setup |
Install to ~/.local/bin/prc, run prc setup if needed, then enable and start the service |
make restart |
Restart the service (REBUILD=1 builds and installs first) |
make status |
Show the service status |
make clean |
Remove the service and binary; config and state are kept |
Logs go to ~/Library/Logs/pi-remote-control/ on macOS, or journalctl --user -u pi-remote-control -f on Linux. With a release binary, use the templates in examples/ instead.
Docker
cat > prc.env <<EOF
RC_PUBLIC_ORIGIN=http://localhost:8787
RC_AGENT_TOKEN=$(openssl rand -base64 32)
RC_ADMIN_PASSWORD=$(openssl rand -base64 24)
EOF
chmod 600 prc.env
docker run -d --name prc --restart unless-stopped -p 127.0.0.1:8787:8787 \
--env-file prc.env -v prc-state:/var/lib/prc ghcr.io/mipsel64/pi-remote-control:latest
Tags: vX.Y.Z, latest (stable releases), and nightly (main). Keep the prc-state volume, which holds transcripts and push keys. Attach Pi with PI_RC_URL=ws://localhost:8787/agent and PI_RC_AGENT_TOKEN set to the token from prc.env.
Notifications
Tap the bell in the top-right corner to be notified whenever a prompt finishes or Pi is waiting on a dialog (a crossed-out bell means off). Over HTTPS, for example Tailscale Serve, it uses Web Push, which works on a locked phone. On http://localhost, notifications only appear while the tab is open. Push keys are generated on first start. A notification's title is the session name; its text is a short excerpt of the final reply (its closing question, if it asks one) or the waiting dialog's title. Web Push payloads are encrypted end to end, but the excerpt can show on a locked screen; hide previews in your phone's notification settings if that matters.
Development
npm test # extension
npm --prefix server/web test # web UI (also builds it)
cargo test --manifest-path server/Cargo.toml --locked # server
npm ci && npm --prefix server/web ci # E2E prerequisites
npx playwright install chromium # CI/Linux: add --with-deps
npm run test:e2e # builds real web/relay, drives Chromium + Pi SDK
make build # server/target/release/prc
make serve REBUILD=1 # build and run in the foreground
The E2E test uses the pinned local Pi SDK and a deterministic, gated provider/tool, without live credentials or provider requests. It starts only a loopback relay with temporary config/state and an isolated in-memory Pi session, and cleans them up. It exercises login/composer, multiple and identical steering prompts, browser/agent reconnect, consumption before settlement, delayed/handled input (including idle input that starts no run), Stop, and the public native clearQueue()/edit path. Stop's editor adapter is checked; actual terminal rendering and dequeue keybindings are not. Manual compaction/retry safety remains covered by the extension unit tests, not this browser E2E. The existing CI server job also runs the E2E regression guard after installing Chromium.
To release, set the same version in package.json and server/Cargo.toml, update both lockfiles, merge, and push a vX.Y.Z tag. CI then builds the binaries, the GHCR image, the npm package, and the GitHub release; tags such as v1.0.0-rc.1 become prereleases. Before the first release, create the free npm organization mipsel64 (it owns the @mipsel64 scope), publish @mipsel64/pi-remote-control once by hand (npm publish --access public), and add npm trusted publishing for release.yml.
Protocol and security details are in DESIGN.md.