hop2
v0.11.42
Published
π° Hop into your terminal from anywhere. Secure remote shell access with 2FA via Cloudflare Tunnel. Next-gen rewrite with native terminal sharing.
Maintainers
Readme
π° hop
Terminal access for humans and agents.
Hop gives you secure browser access to local terminals and a built-in MCP server for creating, driving, and auditing agent sessions over the same runtime.
macOS/Linux β Requires cloudflared for tunneling
(\(\
( -.-) "hop into your shell"
o_(")(")Why Hop
- Browser terminal from anywhere β Tunnel through Cloudflare with no port forwarding
- MCP-native terminal control β Claude Code, Codex, Gemini, Cursor, and other MCP clients can create terminals, stream output, and send input through Hop
- One runtime for humans and agents β The browser UI, local CLI, and MCP server operate on the same sessions
- Operationally useful primitives β Named sessions, port sessions, auto-attach, and explicit workspace snapshots
- Auditability built in β Per-session logs, isolated agent history, and TUI-aware capture defaults
- Security when you need it β Password + 2FA, custom domains, and multi-user support
π Quick Install
Most users only need the npm install. Source install is mainly for development or local hacking.
From npm (easiest)
npm install -g hop2That installs the nightly β a build from the latest green main, which
is usually what you want here since almost every commit is a fix. To hold
still on a version that was deliberately cut and used:
npm install -g hop2@stableThis installs:
hopβ main CLI / daemon / browser terminal entrypointhop-mcpβ MCP server exposing Hop terminals and sessionshop-mcp-setupβ MCP client auto-config helper
Then:
hopIf you want agent access too:
hop-mcp-setupFrom source (one-liner)
git clone https://github.com/jzthree/hop.git ~/.hop && \
cd ~/.hop && npm install && \
sudo ln -sf ~/.hop/hop /usr/local/bin/hopFrom source (manual)
# Clone the repo
git clone https://github.com/jzthree/hop.git ~/.hop
# Install dependencies
cd ~/.hop && npm install
# Add to PATH (pick one)
# Option A: Symlink (requires sudo)
sudo ln -sf ~/.hop/hop /usr/local/bin/hop
# Option B: Add to PATH
echo 'export PATH="$HOME/.hop:$PATH"' >> ~/.zshrc
source ~/.zshrcQuick Start
Browser access
hophop is safe to put at the end of your shell rc (~/.zshrc): it only launches
for interactive terminals β non-interactive shells (ssh host <cmd>, scp/rsync,
scripts, cron) and shells already inside a hop session are silently unaffected.
First run:
- Optionally set a password with
hop password set - Scan the authenticator QR code
- Note the URL
- Press Enter to start your first local terminal
Then open the URL from another device, sign in, and pick or create a session.
MCP access
hop
hop-mcp-setup
hop agent-skill installThen restart your MCP client. hop-mcp-setup auto-detects and configures supported clients such as Claude Code, Claude Desktop, Cursor, Gemini CLI, Codex CLI, VS Code / Copilot, and Antigravity. hop agent-skill install installs Hop's bundled orchestration instructions for Claude Code and Codex.
Typical use cases:
- Launch a dedicated Claude Code / Codex / Gemini terminal and drive it over multiple turns
- Create isolated agent sessions with per-session history and audit logs
- Read terminal output incrementally instead of screen-scraping a browser
- Build higher-level agent workflows on top of stable terminal/session primitives
For full MCP usage, tools, and subagent workflows, see README-MCP.md.
Everyday Workflows
Create and Reuse Sessions
Create multiple independent terminal sessions from the Session Picker (/sessions):
- Create a new named shell session
- Join any existing session
- Share a live session between multiple devices
- Save selected live terminal sessions into a named workspace when you want to reuse them later
Scratch sessions stay live-only unless you explicitly save them into a workspace. To save reusable definitions from the CLI:
hop session add workspace-shell --cwd ~/src/my-project --workspace dev
hop session add myapp --port 3000 --workspace devUse hop workspace save <name> to snapshot selected live terminal sessions, or hop session add ... --workspace <name> when you want to define a reusable terminal or port entry directly.
Inspect or remove saved workspace definitions with:
hop workspace show dev
hop workspace delete devDrive a Subagent Over MCP
Use one dedicated terminal per subagent.
- Create a terminal with
hop_create_terminaland setname/cwd. - Start the agent CLI with
hop_write_terminal. - Wait for readiness with
hop_wait_terminal(until_prompt=true). - Prefer
hopx_agent_turn(mode="auto")orhopx_send_and_wait(...)for one turn at a time. - For long waits, use
hopx_agent_turn(async=true, ...)or the lower-levelhop_wait_terminal(async=true, ...)+hop_wait_poll(...)(hop_wait_startwas removed; usehop_wait_terminal(async=true)). - Interrupt with
hop_send_key(key="ctrl_c")and close withhop_close_terminal.
Safety tips:
- Keep one subagent per terminal.
- Do not queue a second instruction before reading the first response.
- For attached user sessions, confirm
agentPermittedbefore sending input (grant it withhop session permit <name>).
Share a Local Web App
Expose a local HTTP or WebSocket service through your Hop tunnel:
hop session add myapp --port 3000 --workspace devResult:
- local service stays on localhost
- Hop exposes it at
https://your-tunnel-url/s/myapp/ - WebSocket apps work too
Reattach Locally
Attach your local terminal client to an existing terminal session:
hop attach <session>This is useful when you want a browser, an MCP client, and a local shell to converge on the same named session.
Local terminal sessions are persistent by default: they keep running in the Hay host if the local CLI detaches or the Hop daemon restarts. Press Ctrl+Q twice inside the local Hop view to kill a session for everyone; Ctrl+G detaches and leaves it running.
The attached-terminal UI adapts light/dark themes to your terminal and puts the session state at a glance:
Use Hop on Mobile
Native iOS app: hop-ios β a SwiftUI client for iPhone with a real terminal (SwiftTerm), optimistic-echo typing, bell notifications with lock-screen reply, Siri/Shortcuts/Spotlight/Handoff integration, and a fixed-height terminal keyboard. It consumes hop's public HTTP + WebSocket API and needs no changes to hop.
Hopβs mobile web UI includes:
- a terminal-oriented accessory row with
Esc,Tab,Ctrl,Alt, and arrows - a native keyboard button for dictation, spellcheck, and autocomplete
- a floating menu for keyboard toggle and session switching; the switcher starts with user sessions and can toggle to agent sessions or all sessions
- draggable controls designed for one-handed use
Restore Sessions (incl. Claude Code)
A restart kills the underlying processes, but hop can rebuild your sessions and β for Claude Code β resume each conversation where it left off.
hop claude-hook install # one-time: lets hop track each session's Claude conversation
hop restore # recreate saved sessions; claude ones resume their own conversation
hop restore --dry-run # preview what would be restoredHow it works: every hop terminal gets a HOP_SESSION env var; a Claude Code SessionStart hop hook records which conversation is running in each session. On hop restore, each session is reopened in its original directory and claude sessions relaunch with claude --resume <id> β so even multiple Claude sessions in the same directory each come back to their own conversation. Plain-shell sessions reopen a shell in their last directory and replay their last screen: on a graceful shutdown hop saves each session's recent output and, on restore, paints it back above a dim ββββ session restored ββββ separator before the fresh prompt β so you keep the context even though the old process is gone. hop status shows how many sessions can be restored.
hop claude-hook install adds a SessionStart entry to ~/.claude/settings.json (backed up first); hop claude-hook remove reverts it. The hook is a no-op outside hop terminals.
Render LaTeX math (hop math)
Glance at a formula without leaving the terminal:
hop math 'e^{i\pi} + 1 = 0'
hop math '\frac{-b \pm \sqrt{b^2 - 4ac}}{2a}'
echo '\sum_{n=1}^{\infty} \frac{1}{n^2} = \frac{\pi^2}{6}' | hop mathAgents working inside hop terminals can render formulas for the humans watching by invoking the command themselves β install the bundled skills with hop agent-skill install so Claude and Codex sessions know to do this (plus ring the bell when blocked, and keep output phone-friendly).
hop math renders a 2D Unicode layout β fractions, roots, sums/limits, sub/superscripts, Greek + operators, and accents β with no dependencies. Inline-image rendering (Kitty/iTerm) is deferred until graphics support lands in the web client (xterm.js): the real payoff is math that renders inside a shared hop session, which the re-rendering clients can't show yet, so until then it's Unicode everywhere.
Show something a terminal cannot draw (hop view, hop port)
A terminal can only print text, which makes it the wrong surface for the
thing you most often want back from a long-running job: a plot, a report, a
rendered write-up. hop view publishes a file behind hop's auth and prints a
link β the web client and the phone app both open it in place, with the
session's cookie, so nothing has to be public.
hop view --title "ROC curve: new model vs baseline" results/roc_curve.png
hop view --title "Q3 analysis" report.html analysis.pdf
generate_summary | hop view --name findings.md --title "What the profiler found" -
hop view --list # what this session published
hop view --rm x.png # unpublishHTML, PDF, images, video and markdown all render (markdown is converted at
serve time β headings, tables, fenced code). --title is what a human sees
in the session's Views list, so it is worth writing properly. Published files
are grouped by session: a result belongs to the conversation that
produced it.
For something live rather than a file, hop port 5173 proxies a local web
server through the same auth (WebSocket-safe, so dev-server hot reload
works), attached to the current session β --standalone makes it a room of
its own instead.
Agents do this for you when the bundled skill is installed (hop agent-skill
install): the convention is to hand over a titled view rather than describe
a result in prose.
Operations
Logging and History
Hop always writes per-session audit logs.
Default behavior:
- agent-created sessions get isolated shell history files
- user-created sessions keep normal shell history unless you opt into isolation
- TUI apps switch to diff-suppressed keyframe capture automatically
Paths:
- scratch sessions:
~/.hop2/runtime/history/<internal-session>.history - scratch audit logs:
~/.hop2/runtime/logs/<internal-session>/audit.ndjson - workspace-backed sessions:
~/.hop2/workspaces/<workspace>/history/<internal-session>.history - workspace-backed audit logs:
~/.hop2/workspaces/<workspace>/logs/<internal-session>/audit.ndjson
Key tunables:
HOP_SESSION_HISTORY_SIZEHOP_AGENT_HISTORY_ISOLATIONHOP_USER_HISTORY_ISOLATIONHOP_AGENT_POST_START_HISTORY_INITHOP_POST_START_HISTORY_INITHOP_SESSION_AUDIT_INLINE_MAX_BYTESHOP_SESSION_TUI_KEYFRAME_INTERVAL_MSHOP_SESSION_TUI_KEYFRAME_TAIL_CHARSHOP_SESSION_TUI_KEYFRAME_MAX_LINES
Users, Domains, and Sharing
Admin flow:
- Set a password with
hop password set. - Configure a hostname with
hop domain hop.yourdomain.com. - Add a user with
hop user add alice. - Export credentials with
hop user export alice.
User flow:
npm install -g hop2
hop client ./credentials.jsonFirst run prompts for password setup and TOTP enrollment, then the user logs in at their assigned URL such as https://alice.hop.yourdomain.com.
Self-service signup (optional)
Instead of adding every user by hand, the landing host can serve a public
signup page. Visitors ask for a subdomain, confirm an email address on a
domain you nominate, and wait for you to approve β approval runs the same
hop user add + hop user export provisioning as the manual flow, then
emails them a one-time link to collect their credentials.
hop mail setup # SMTP (a Gmail App Password works)
hop registration admin jianzhou # reserve your own subdomain
hop registration domain uchicago.edu
hop registration enableThen review requests as they arrive:
hop user pending
hop user approve alice
hop user reject bob "please use your own name"Notes:
- Off by default. Enabling it makes the bare hostname public; your own
browser keeps working while its session cookie is valid, and
https://<landing-host>/signinalways reaches the login page. - Nothing runs on your machine for them. A registrant gets a Cloudflare tunnel and a DNS record; hop itself runs on their computer, with their own password and TOTP.
- The tunnel lives in your Cloudflare account and the subdomain is under
your domain, so an approved user can serve anything they like at
their-name.your-domain. Approve people you would vouch for, and usehop user remove <name>to revoke (delete the DNS record by hand). - Registrations live in
~/.hop2/.registrations.json(0600). Verification and claim links are stored only as hashes.
Security
- Password + TOTP is supported; password is required for custom domains
- Login cookies are
httpOnly,secure, andsameSite=lax - Browser sessions persist across
hop stop/startby default through a stable session secret - The local server binds only to
127.0.0.1 - Cloudflare Tunnel provides end-to-end TLS
Secrets are stored in ~/.hop2/. Set HOP_PERSIST_SESSION_SECRET=0 if you want login cookies invalidated on every daemon restart.
Runtime and Recovery
Hop uses the external Hay host runtime for PTY hosting and session recovery.
- active PTYs live in a separate local hay-host process
- the host survives daemon restarts
- host state is tracked in
~/.hop2/.hay-host-state - startup reconciles already-running Hay sessions before applying workspace state
HOP_TERMINAL_BACKENDis accepted only for compatibility and ignored
Reference
Related Docs
- README-MCP.md for MCP tools, helper wrappers, and agent-driving patterns
- INTEGRATION.md for the current Hop/Hay architecture
- hay/README.md for the vendored Hay workspace
Commands
| Command | Description |
|---------|-------------|
| hop | Start hop daemon/tunnel if needed, then launch a local terminal. Unnamed launches do not reuse a session that already has a local CLI attached. |
| hop start | Start the hop daemon and print QR codes |
| hop attach [session] | Attach to an existing terminal session |
| hop attach all | Attach sequentially to all terminal sessions |
| hop local [session] | Start a daemonless local terminal ([session] attaches if it exists) |
| hop stop | Stop the hop daemon (terminal sessions keep running) |
| hop stop --all | Stop the daemon, the hay-host, and all terminal sessions |
| hop status | Show daemon, tunnel, and session status |
| hop doctor | Run environment/health checks |
| hop logs [-f] | Show daemon logs (-f to follow) |
| hop health [--restart] | Check tunnel health (--restart forces a tunnel restart) |
| hop url | Print current tunnel URL |
| hop qr | Show QR code for current tunnel URL |
| hop qr auth | Show QR code for authenticator app setup |
| hop config theme [name] | Show or set the session naming theme |
| hop domain <hostname> | Set custom domain (named tunnel) |
| hop domain-clear | Remove custom domain, use random URLs |
| hop password set | Set/change password |
| hop password clear | Remove password protection |
| hop user list | List users |
| hop user add <name> | Add user + subdomain |
| hop user remove <name> | Remove user |
| hop user export <name> | Export user credentials |
| hop session list | List live terminal sessions |
| hop session list --all | Include saved definitions from all workspaces alongside live sessions (-a is shorthand) |
| hop session add <name> [--cwd P] --workspace W | Save a terminal session definition to a named workspace |
| hop session add <name> --port N --workspace W | Save a port session definition to a named workspace |
| hop session permit <name> | Allow agent access to a session |
| hop session block <name> | Block agent access to a session |
| hop workspace list | List workspaces |
| hop workspace create <name> | Create an empty workspace |
| hop workspace show <name> | Show saved definitions in a workspace |
| hop workspace save <name> | Save selected live terminal sessions into a workspace |
| hop workspace load <name> | Start sessions defined in a workspace |
| hop workspace delete <name> [--yes] | Delete a workspace (asks before deleting a non-empty one) |
| hop session rename <old> <new> | Rename a session |
| hop session remove <name> | Remove a session |
| hop client <credentials> | Run hop with exported credentials |
| hop wipe [--all] [--yes] | Remove workspace-defined sessions (--all also kills live sessions after a confirmation; --yes skips it) |
| hop wipe --agent | Close all agent-created terminal API sessions |
Local CLI mouse capture is off by default so terminal-native selection and context menus keep working. Use Option+M / Alt+M inside the local Hop view to toggle Hop mouse selection when needed. Use Option+B / Alt+B to toggle the status bar. CLI defaults live under hay-cli in ~/.hop.json.
Development
The Hay source is vendored in ./hay. Hop serves hay/apps/web/dist when present.
npm run build
npm testThis rebuilds Hay and syncs hay/apps/web/dist into hay-web/. npm test runs a basic syntax sanity check on the main hop entrypoint. If ./hay exists and the dist folder is missing, hop auto-builds Hay on startup.
Troubleshooting
- Authenticator QR again: run
hop qr auth - Reset TOTP entirely: delete
.auth_secretand restart Hop - Reset a client install: delete
~/.hop2/clients/<tunnel-id>/and runhop clientagain - Tunnel not starting: install
cloudflaredwithbrew install cloudflared - Tunnel returning
502after a network change: runhop health --restart - Stuck tunnel processes:
pkill cloudflaredLicense
MIT
