English | 简体中文
Keep one managed terminal session available across desktop and mobile.
TermPilot is a local-first tool for continuing the same managed terminal session from your phone while it keeps running on your computer.
Tip
Documentation site: TermPilot Docs · Quick Start · Deployment Guide · Agent Operations · Troubleshooting · CLI Reference
Important
TermPilot does not import arbitrary Terminal or iTerm tabs. A session must be created or managed by TermPilot to be available on mobile.
TermPilot is built around one narrow path:
- one managed session already exists on your computer
- you leave your desk
- you still want that exact session on your phone
That session can be Claude Code, a deployment, a migration, or any other long-running terminal task. The product is designed around keeping the same session available across devices.
Phone browser / PWA -- https / wss --> relay <-- ws / wss -- agent on your computer
|
+-- pairing, grant routing,
audit metadata, web UI
The system has three runtime pieces:
relay: HTTP + WebSocket entrypoint, web UI hosting, pairing, access control, encrypted message routingagent: daemon running on your computer, managing local sessions and keeping session content on-deviceapp: mobile web UI served by the relay
- Unified CLI package for relay, agent, and session commands
tmux-backed managed sessions with output replay served by the agent- Local-first session state: titles, cwd, status details, and terminal output stay on the agent host
- Device-scoped pairing, access grants, and encrypted browser-to-agent session messages
- Relay persistence limited to pairing, grant, and audit metadata, with SQLite as the default long-running store and optional PostgreSQL via
DATABASE_URL - Mobile web UI focused on viewing, light input, and shortcut controls on the same session
- Mobile terminal workspace tuned for phone use, with dedicated keyboard and command sections plus a focus mode for wider viewing
- Browser notifications for device offline, session exit, and suspected stale managed sessions while the page is in the background
- Adaptive output polling: active sessions stay snappy, while long-detached managed sessions are polled more conservatively
- Incremental replay and foreground recovery to keep long-running sessions responsive when the page returns from the background
- Managed command sessions include lightweight stale-session governance for long-detached, no-output leftovers
This is a deliberately narrow scope focused on session continuity across your own devices.
If you redeploy or migrate from an older binding without local keys, re-pair the device.
If you want the structured docs flow instead of reading the repository README top to bottom, use this path:
- Product overview: Why TermPilot
- First setup: Quick Start
- Relay deployment: Deployment Guide
- Agent lifecycle and session operations: Agent Operations
- Problem diagnosis: Troubleshooting
- Runtime design: Security Design, Architecture, Protocol
On both the server and your computer:
Node.js 22+@fengye404/termpilot
On your computer:
tmux
Install:
npm install -g @fengye404/termpilotRun on a server or another machine reachable from your phone:
termpilot relayUseful variants:
termpilot relay start
termpilot relay stop
termpilot relay runBy default, the relay starts in the background, listens on 0.0.0.0:8787, serves both the web UI and /ws, and persists relay metadata to ~/.termpilot/relay.db.
Run on your computer:
termpilot agentOn first run, the agent asks for the relay host and port, then:
- saves local config
- starts a background daemon
- prints a one-time pairing code
If you want the agent to stay up permanently, let a system process manager run:
termpilot agent --foreground --relay wss://your-domain.com/wsOpen the relay URL in your phone browser:
http://your-domain.com:8787- or
https://your-domain.combehind a reverse proxy
Enter the pairing code shown on your computer. After that, you land on the session list for that device.
For Claude Code:
termpilot claude codeFor any other managed command:
termpilot run -- opencodeIf you want to create a plain shell session first:
termpilot create --name my-task --cwd /path/to/projectThen attach it explicitly:
termpilot list
termpilot attach --sid <sid>If you only remember one rule:
termpilot run -- <command>means “start a managed session around this command”termpilot create+termpilot attachmeans “create a plain shell session, then re-enter it when needed”
If you are iterating on the repository locally, avoid mixing your development runs with the globally installed termpilot binary and ~/.termpilot state.
Add this alias to ~/.zshrc:
tpdev() {
local root="/Users/fengye/workspace/TermPilot"
local home="/tmp/termpilot-local"
local cmd="${1:-help}"
if [ "$#" -gt 0 ]; then
shift
fi
case "$cmd" in
help|-h|--help)
cat <<'EOF'
tpdev build
tpdev reset
tpdev fresh
tpdev refresh
tpdev relay ...
tpdev agent ...
tpdev claude code
tpdev run -- <command>
EOF
;;
build)
(cd "$root" && pnpm build)
;;
reset)
(cd "$root" && TERMPILOT_HOME="$home" pnpm local:reset)
;;
fresh|refresh)
(cd "$root" && pnpm build && TERMPILOT_HOME="$home" pnpm local:reset)
;;
*)
(cd "$root" && TERMPILOT_HOME="$home" node dist/cli.js "$cmd" "$@")
;;
esac
}Reload your shell:
source ~/.zshrcThen use this local-only flow:
tpdev fresh
tpdev relay run
tpdev agent --relay ws://127.0.0.1:8787/ws --pair
tpdev claude codetpdev fresh is the recommended starting point after code changes because tpdev relay ... and tpdev agent ... still execute the built dist/cli.js.
tpdev refresh is supported as an alias of tpdev fresh.
If your local relay / agent state becomes messy during debugging, reset the entire local sandbox with:
tpdev resetThat command stops the local agent and relay tied to /tmp/termpilot-local, kills any tmux sessions recorded in that local state directory, and removes the directory itself.
This setup gives you:
- the CLI built from your current repository instead of the globally installed npm package
- an isolated local state directory at
/tmp/termpilot-local - a fast browser test loop against
http://127.0.0.1:8787without publishing to npm first
If you want to launch a different managed command, use:
tpdev run -- <command>termpilot relay
termpilot relay stop
termpilot relay run
termpilot agent
termpilot agent --pair
termpilot agent status
termpilot agent stop
termpilot pair
termpilot create --name my-task --cwd /path/to/project
termpilot list
termpilot attach --sid <sid>
termpilot kill --sid <sid>
termpilot grants
termpilot audit --limit 20
termpilot revoke --token <accessToken>
termpilot doctor
termpilot claude code
termpilot run -- <command>Default local state directory:
~/.termpilot
Common files:
config.json: saved relay configuration for the agentagent-runtime.json: background agent runtime staterelay-runtime.json: background relay runtime statestate.json: local managed session statedevice-key.json: local agent device keypairagent.log/relay.log: logs
Useful environment variables:
TERMPILOT_HOMETERMPILOT_RELAY_URLTERMPILOT_DEVICE_IDTERMPILOT_AGENT_TOKENTERMPILOT_RELAY_STORETERMPILOT_SQLITE_PATHTERMPILOT_ORPHAN_WARNING_MSTERMPILOT_MANAGED_SESSION_AUTOCLEANUP_MSHOSTPORTDATABASE_URLTERMPILOT_PAIRING_TTL_MINUTES
Managed command cleanup defaults:
TERMPILOT_ORPHAN_WARNING_MS: detached and idle warning threshold, default3600000(1 hour)TERMPILOT_MANAGED_SESSION_AUTOCLEANUP_MS: detached and idle auto-clean threshold, default43200000(12 hours)
Relay store defaults:
TERMPILOT_RELAY_STORE:sqliteby default, can be set tomemoryTERMPILOT_SQLITE_PATH: SQLite file path, default~/.termpilot/relay.db
Examples:
TERMPILOT_HOME=/data/termpilot termpilot agent
TERMPILOT_RELAY_URL=wss://your-domain.com/ws termpilot agent
HOST=0.0.0.0 PORT=8787 termpilot relayFor a quick private trial:
- run
termpilot relaydirectly on a reachable machine - open
http://your-ip:8787on mobile - point the agent to
ws://your-ip:8787/ws
For regular use:
- run
termpilot relayon a server - put a reverse proxy in front of it
- use
https://your-domain.comon mobile - use
wss://your-domain.com/wsfor the agent - keep relay metadata on SQLite or PostgreSQL instead of volatile memory
Minimal Caddy example:
your-domain.com {
reverse_proxy 127.0.0.1:8787
}For deployment and runtime details, continue with:
Use the published relay image directly:
docker pull fengye404/termpilot-relay:latestRun it with a persistent state volume:
docker run -d \
--name termpilot-relay \
-p 8787:8787 \
-e TERMPILOT_AGENT_TOKEN=change-me \
-v termpilot-relay-data:/var/lib/termpilot \
fengye404/termpilot-relay:latestInside the container, relay metadata persists to /var/lib/termpilot/relay.db by default. If you want reproducible rollout, pin a version tag such as fengye404/termpilot-relay:0.3.9.
TermPilot is intentionally not trying to be:
- a remote desktop
- a GUI control layer
- an importer for arbitrary existing terminal tabs
- a full terminal log archive system
- a general-purpose multi-tenant operations platform
If a task should remain visible and controllable from mobile, start it inside a TermPilot-managed session from the beginning.
This repository is a pnpm workspace monorepo:
src/cli.ts: top-level CLI entrypointagent/: desktop agent and local session managementrelay/: relay serverapp/: mobile web UIpackages/protocol/: shared protocol definitionsdocs/: VitePress documentation site
Run locally:
pnpm install
pnpm dev:relay
pnpm dev:app
pnpm dev:agentUseful checks:
pnpm typecheck
pnpm build
pnpm test:ui-smoke
pnpm check:stability
pnpm test:isolation