Install the HAPI CLI and set up the hub.
- Claude Code, OpenAI Codex CLI, Cursor Agent CLI, Google Gemini CLI, or OpenCode CLI installed
Verify your CLI is installed:
# For Claude Code
claude --version
# For OpenAI Codex CLI
codex --version
# For Cursor Agent CLI
agent --version
# For Google Gemini CLI
gemini --version
# For OpenCode CLI
opencode --versionHAPI has three components:
| Component | Role | Required |
|---|---|---|
| CLI | Wraps AI agents (Claude/Codex/Cursor/Gemini/OpenCode), runs sessions | Yes |
| Hub | Central coordinator: persistence, real-time sync, remote access | Yes |
| Runner | Background service for remote session spawning | Optional |
┌─────────────────────────────────────────────────────┐
│ Your Machine │
│ │
│ ┌─────────┐ Socket.IO ┌─────────────┐ │
│ │ CLI │◄───────────────►│ Hub │ │
│ │+ Agent │ │ + SQLite │ │
│ └─────────┘ └──────┬──────┘ │
│ ▲ │ SSE │
│ │ spawn ▼ │
│ ┌────┴────┐ ┌─────────────┐ │
│ │ Runner │◄────RPC────────►│ Web App │ │
│ │(背景) │ └─────────────┘ │
│ └─────────┘ │
└─────────────────────────────────────────────────────┘
│
[Tunnel / Public URL]
│
┌─────▼─────┐
│ Phone/Web │
└───────────┘
- CLI: Start a session with
hapi. The CLI wraps your AI agent and syncs with the hub. - Hub: Run
hapi hub. Stores sessions, handles permissions, enables remote access. - Runner: Run
hapi runner start. Lets you spawn sessions from phone/web without keeping a terminal open.
Local only: hapi hub → hapi → work in terminal
Remote access: hapi hub --relay → hapi runner start → control from phone/web
npm install -g @twsxtd/hapi --registry=https://registry.npmjs.orgRecommendation: use the official npm registry for global install. Some mirrors may not sync platform packages in time.
Or with Homebrew:
brew install tiann/tap/hapinpx (no install)
npx @twsxtd/hapiPrebuilt binary
Download the latest release from GitHub Releases.
xattr -d com.apple.quarantine ./hapi
chmod +x ./hapi
sudo mv ./hapi /usr/local/bin/Build from source
git clone https://github.com/tiann/hapi.git
cd hapi
bun install
bun build:single-exe
./cli/dist/hapiThe hub can be deployed on:
- Local desktop (default) - Run on your development machine
- Remote host - Deploy the hub on a VPS, cloud host, or any machine with network access
hapi hub --relayThe terminal displays a URL and QR code. Scan to access from anywhere.
hapi server remains supported as an alias.
- End-to-end encrypted with WireGuard + TLS
- No configuration needed
- Works behind NAT, firewalls, and any network
Tip: The relay uses UDP by default. If you experience connectivity issues, set
HAPI_RELAY_FORCE_TCP=trueto force TCP mode.
hapi hub
# or
hapi hub --no-relayThe hub listens on http://localhost:3006 by default.
On first run, HAPI:
- Creates
~/.hapi/ - Generates a secure access token
- Prints the token and saves it to
~/.hapi/settings.json
Config files
~/.hapi/
├── settings.json # Main configuration
├── hapi.db # SQLite database (hub)
├── runner.state.json # Runner process state
└── logs/ # Log files
Environment variables
| Variable | Default | settings.json | Description |
|---|---|---|---|
CLI_API_TOKEN |
Auto-generated | cliApiToken |
Shared secret for authentication |
HAPI_API_URL |
http://localhost:3006 |
apiUrl |
Hub URL for CLI connections |
HAPI_EXTRA_HEADERS_JSON |
- | - | JSON object of extra outbound headers for CLI → hub HTTP/WebSocket requests |
HAPI_LISTEN_HOST |
127.0.0.1 |
listenHost |
Hub HTTP bind address |
HAPI_LISTEN_PORT |
3006 |
listenPort |
Hub HTTP port |
HAPI_PUBLIC_URL |
- | publicUrl |
Public URL for external access |
CORS_ORIGINS |
- | corsOrigins |
Allowed CORS origins (comma-separated) |
TELEGRAM_BOT_TOKEN |
- | telegramBotToken |
Telegram Bot API token |
TELEGRAM_NOTIFICATION |
true |
telegramNotification |
Enable Telegram notifications |
HAPI_RELAY_FORCE_TCP |
false |
- | Force TCP mode for relay |
VAPID_SUBJECT |
mailto:admin@hapi.run |
- | Web Push contact info |
HAPI_HOME |
~/.hapi |
- | Config directory path |
DB_PATH |
~/.hapi/hapi.db |
- | Database file path |
ELEVENLABS_API_KEY |
- | - | ElevenLabs API key for voice |
ELEVENLABS_AGENT_ID |
Auto-created | - | Custom ElevenLabs agent ID |
settings.json example
Configuration priority: ENV > settings.json > default
When ENV values are set and not present in settings.json, they are automatically saved.
{
"$schema": "https://hapi.run/docs/schemas/settings.schema.json",
"listenHost": "0.0.0.0",
"listenPort": 3006,
"publicUrl": "https://your-domain.com"
}JSON Schema: settings.schema.json
If the hub is not on localhost, set these before running hapi:
export HAPI_API_URL="http://your-hub:3006"
export CLI_API_TOKEN="your-token-here"
export HAPI_EXTRA_HEADERS_JSON='{"Cookie":"CF_Authorization=..."}'Or use interactive login:
hapi auth loginAuthentication commands:
hapi auth status
hapi auth login
hapi auth logoutEach machine gets a unique ID stored in ~/.hapi/settings.json. This allows:
- Multiple machines to connect to one hub
- Remote session spawning on specific machines
- Machine health monitoring
If you prefer not to use the public relay (e.g., for lower latency or self-managed infrastructure), you can use these alternatives:
Cloudflare Tunnel
https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/
Note: Cloudflare Quick Tunnels (TryCloudflare) are not supported because they do not support SSE, which HAPI uses for real-time updates. Use a Named Tunnel instead.
Named tunnel setup:
# Install cloudflared: https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/
# Create and configure a named tunnel
cloudflared tunnel create hapi
cloudflared tunnel route dns hapi hapi.yourdomain.com
# Run the tunnel
cloudflared tunnel --protocol http2 run hapiTip: Use
--protocol http2instead of QUIC (the default) to avoid potential timeout issues with long-lived connections.
Tailscale
https://tailscale.com/download
sudo tailscale up
hapi hubAccess via your Tailscale IP:
http://100.x.x.x:3006
Public IP / Reverse Proxy
If the hub has a public IP, access directly via http://your-hub-ip:3006.
Use HTTPS (via Nginx, Caddy, etc.) for production.
Self-signed certificates (HTTPS)
If HAPI_API_URL is set to an https://... URL with a self-signed (or otherwise untrusted) certificate, the CLI may fail with:
Error: self signed certificate
Recommended fixes (in order):
- Use a publicly trusted certificate (e.g., Let's Encrypt)
- Trust your private CA (recommended for private networks)
- Dev-only workaround: disable TLS verification (insecure)
# Preferred: trust your own CA
export NODE_EXTRA_CA_CERTS="/path/to/your-ca.pem"
# Dev-only workaround: disable TLS verification (INSECURE)
export NODE_TLS_REJECT_UNAUTHORIZED=0If you use the dev-only workaround, assume MITM risk; do not use on public networks.
Enable Telegram notifications and Mini App access:
- Message @BotFather and create a bot
- Set the bot token and public URL
- Start the hub and bind your account
export TELEGRAM_BOT_TOKEN="your-bot-token"
export HAPI_PUBLIC_URL="https://your-public-url"
hapi hubThen message your bot with /start, open the app, and enter your CLI_API_TOKEN.
Troubleshooting:
- If binding fails, verify
HAPI_PUBLIC_URLis accessible from the internet - Telegram Mini App requires HTTPS (not HTTP)
Run a background service for remote session spawning:
hapi runner start
hapi runner status
hapi runner logs
hapi runner stopWith the runner running:
- Your machine appears in the "Machines" list
- You can spawn sessions remotely from the web app
- Sessions persist even when the terminal is closed
Alternative: pm2
If you prefer pm2 for process management:
pm2 start "hapi runner start-sync" --name hapi-runner
pm2 saveKeep HAPI running persistently so it survives terminal closes, system restarts, and continues running in the background.
Quick: nohup
Simple one-liner for quick background runs:
# Hub
nohup hapi hub --relay > ~/.hapi/logs/hub.log 2>&1 &
# Runner
nohup hapi runner start-sync > ~/.hapi/logs/runner.log 2>&1 &View logs:
tail -f ~/.hapi/logs/hub.log
tail -f ~/.hapi/logs/runner.logStop processes:
pkill -f "hapi hub"
pkill -f "hapi runner"pm2 (recommended for Node.js users)
pm2 provides process management with auto-restart on crashes and system reboot.
# Install pm2
npm install -g pm2
# Start hub and runner
pm2 start "hapi hub --relay" --name hapi-hub
pm2 start "hapi runner start-sync" --name hapi-runner
# View status and logs
pm2 status
pm2 logs hapi-hub
pm2 logs hapi-runner
# Auto-restart on system reboot
pm2 startup # Follow the printed instructions
pm2 save # Save current process listmacOS: launchd
Create plist files for automatic startup on macOS.
Hub (~/Library/LaunchAgents/com.hapi.hub.plist):
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.hapi.hub</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/hapi</string>
<string>hub</string>
<string>--relay</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/Users/YOUR_USERNAME/.hapi/logs/hub.log</string>
<key>StandardErrorPath</key>
<string>/Users/YOUR_USERNAME/.hapi/logs/hub.log</string>
</dict>
</plist>Runner (~/Library/LaunchAgents/com.hapi.runner.plist):
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.hapi.runner</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/hapi</string>
<string>runner</string>
<string>start-sync</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
<key>StandardOutPath</key>
<string>/Users/YOUR_USERNAME/.hapi/logs/runner.log</string>
<key>StandardErrorPath</key>
<string>/Users/YOUR_USERNAME/.hapi/logs/runner.log</string>
</dict>
</plist>Load/unload services:
# Load (start)
launchctl load ~/Library/LaunchAgents/com.hapi.hub.plist
launchctl load ~/Library/LaunchAgents/com.hapi.runner.plist
# Unload (stop)
launchctl unload ~/Library/LaunchAgents/com.hapi.hub.plist
launchctl unload ~/Library/LaunchAgents/com.hapi.runner.plistmacOS sleep note: macOS may suspend background processes when the display sleeps. Use
caffeinateto prevent this:caffeinate -dimsu hapi hub --relayOr run
caffeinate -dimsuin a separate terminal while HAPI is running.
Linux: systemd
Create user-level systemd services for automatic startup.
Hub (~/.config/systemd/user/hapi-hub.service):
[Unit]
Description=HAPI Hub
After=network.target
[Service]
Type=simple
ExecStart=/usr/local/bin/hapi hub --relay
Restart=always
RestartSec=5
[Install]
WantedBy=default.targetRunner (~/.config/systemd/user/hapi-runner.service):
[Unit]
Description=HAPI Runner
After=network.target hapi-hub.service
[Service]
Type=simple
KillMode=process
ExecStart=/usr/local/bin/hapi runner start-sync
Restart=always
RestartSec=5
[Install]
WantedBy=default.targetWhy
KillMode=process? The runner spawns each agent session as a detached child process (detached: trueincli/src/runner/run.ts) so that sessions stay alive when the runner exits. WithoutKillMode=process, systemd's defaultKillMode=control-groupsends SIGTERM to every PID in the runner's cgroup when the unit stops, defeating the detach and forcibly archiving every running session.KillMode=processpreserves the contract: stopping or restarting the runner only signals the runner itself; agent sessions stay alive, and a fresh runner re-establishes control via the existing socket.io reconnect path. This applies to runner upgrades, manual restarts, and any reboot in which the runner unit is stopped before agents have finished.
Enable and start:
# Reload systemd
systemctl --user daemon-reload
# Enable (auto-start on login)
systemctl --user enable hapi-hub
systemctl --user enable hapi-runner
# Start now
systemctl --user start hapi-hub
systemctl --user start hapi-runner
# View status/logs
systemctl --user status hapi-hub
journalctl --user -u hapi-hub -fPersist after logout: To keep services running even when not logged in:
loginctl enable-linger $USER
Enable voice control:
- Get an API key from elevenlabs.io
- Set the environment variable:
export ELEVENLABS_API_KEY="your-api-key"
hapi hub --relaySee Voice Assistant for usage details.
- Keep tokens secret and rotate if needed
- Use HTTPS for public access
- Restrict CORS origins in production
Firewall example (ufw)
ufw allow from 192.168.1.0/24 to any port 3006