Installation
Install the HAPI CLI and set up the hub.
Prerequisites
- Claude Code, OpenAI Codex 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 Google Gemini CLI
gemini --version
# For OpenCode CLI
opencode --versionArchitecture
HAPI has three components:
| Component | Role | Required |
|---|---|---|
| CLI | Wraps AI agents (Claude/Codex/Gemini/OpenCode), runs sessions | Yes |
| Hub | Central coordinator: persistence, real-time sync, remote access | Yes |
| Runner | Background service for remote session spawning | Optional |
How they work together
┌─────────────────────────────────────────────────────┐
│ 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.
Typical workflows
Local only: hapi hub → hapi → work in terminal
Remote access: hapi hub --relay → hapi runner start → control from phone/web
Install the CLI
npm install -g @twsxtd/hapiOr with Homebrew:
brew install tiann/tap/hapiOther install options
npx (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/hapiHub setup
The 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
Default: Public Relay (recommended)
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
Local Only
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 filesEnvironment variables
| Variable | Default | Description |
|---|---|---|
CLI_API_TOKEN | Auto-generated | Shared secret for authentication |
HAPI_API_URL | http://localhost:3006 | Hub URL for CLI |
HAPI_LISTEN_HOST | 127.0.0.1 | HTTP service bind address |
HAPI_LISTEN_PORT | 3006 | HTTP service port |
HAPI_PUBLIC_URL | - | Public URL for external access |
HAPI_HOME | ~/.hapi | Config directory path |
DB_PATH | ~/.hapi/hapi.db | Database file path |
CORS_ORIGINS | - | Allowed CORS origins |
ELEVENLABS_API_KEY | - | ElevenLabs API key for voice |
ELEVENLABS_AGENT_ID | Auto-created | Custom ElevenLabs agent ID |
CLI setup
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"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
Operations
Self-hosted tunnels
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/
Quick tunnel (temporary URL, changes on restart):
# Install cloudflared: https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/
cloudflared tunnel --protocol http2 --url http://localhost:3006Copy the generated URL and set it:
export HAPI_PUBLIC_URL="https://your-tunnel.trycloudflare.com"
hapi hubNamed tunnel (persistent URL):
# 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 hapiNote: 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:3006Public 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.
Telegram setup
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)
Runner setup
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 --foreground" --name hapi-runner
pm2 saveBackground service deployment
Keep 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 --foreground > ~/.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 --foreground" --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</string>
<string>--foreground</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:bashcaffeinate -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
ExecStart=/usr/local/bin/hapi runner start --foreground
Restart=always
RestartSec=5
[Install]
WantedBy=default.targetEnable 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:
bashloginctl enable-linger $USER
Voice assistant setup
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.
Security notes
- 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