Skip to content

Installation

Install the HAPI CLI and set up the hub. For phone client builds and pairing, see Native apps (iOS / Android); for browser installation, see PWA.

Prerequisites

  • At least one supported agent CLI installed (Claude Code, Codex, Cursor Agent, Grok Build, OpenCode, DeepSeek Harness ACP server, and more — see Supported Agents)

Verify your CLI is installed:

bash
# For Claude Code
claude --version

# For OpenAI Codex CLI
codex --version

# For Cursor Agent CLI
agent --version

# For Grok Build CLI
grok --version

# For OpenCode CLI
opencode --version

Architecture

HAPI uses these runtime roles:

ComponentRoleRequired
CLIWraps AI coding agents, runs sessionsYes
HubCentral coordinator: persistence, real-time sync, remote accessYes
RunnerBackground service for remote session spawningOptional
ClientNative iOS/Android, Web/PWA or Telegram Mini AppOne client for remote control

How they work together

The CLI and Runner connect to the hub over Socket.IO; clients send actions through REST and receive live updates through SSE. The Runner spawns CLI sessions when requested through the hub. See the architecture overview.

  • CLI: Choose an agent with hapi, or start one directly with hapi <agent>. The CLI wraps your AI agent and syncs with the hub. Scripts must specify the agent explicitly.
  • Hub: Run hapi hub. Stores sessions, handles permissions, enables remote access.
  • Runner: Run hapi runner start. Lets you spawn sessions from native or web clients without keeping a terminal open.
  • Client: Pair a native app with the HTTPS hub origin and access token, or log in through the web app.

Typical workflows

Local only: hapi hubhapi → work in terminal

Remote access: hapi hub --relayhapi runner start → pair a native app or open the web app

Install the CLI

bash
npm install -g @twsxtd/hapi --registry=https://registry.npmjs.org

Recommendation: use the official npm registry for global install. Some mirrors may not sync platform packages in time.

Or with Homebrew:

bash
brew install tiann/tap/hapi

Other install options

npx (no install)
bash
npx @twsxtd/hapi
Prebuilt binary

Download the latest release from GitHub Releases.

bash
xattr -d com.apple.quarantine ./hapi
chmod +x ./hapi
sudo mv ./hapi /usr/local/bin/
Build from source

Requires Bun 1.4.0.

bash
git clone https://github.com/tiann/hapi.git
cd hapi
bun install
bun build:single-exe

./cli/dist-exe/<target>/hapi

<target> is the Bun build target (e.g., bun-linux-x64, bun-darwin-arm64); it defaults to the host platform and architecture.

Hub 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
bash
hapi hub --relay

The 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

For relay key management, TCP fallback, and self-hosted tunnel alternatives, see Deployment.

Local Only

bash
hapi hub
# or
hapi hub --no-relay

The hub listens on http://localhost:3006 by default.

On first run, HAPI:

  1. Creates ~/.hapi/
  2. Generates a secure access token
  3. 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
VariableDefaultsettings.jsonDescription
CLI_API_TOKENAuto-generatedcliApiTokenShared secret for authentication
HAPI_API_URLhttp://localhost:3006apiUrlHub URL for CLI connections
HAPI_EXTRA_HEADERS_JSON-extraHeadersJSON object of extra outbound headers for CLI → hub HTTP/WebSocket requests
HAPI_LISTEN_HOST127.0.0.1listenHostHub HTTP bind address
HAPI_LISTEN_PORT3006listenPortHub HTTP port
HAPI_PUBLIC_URL-publicUrlPublic URL for external access
CORS_ORIGINS-corsOriginsAllowed CORS origins (comma-separated)
TELEGRAM_BOT_TOKEN-telegramBotTokenTelegram Bot API token
TELEGRAM_NOTIFICATIONtruetelegramNotificationEnable Telegram notifications
SERVERCHAN_SENDKEY-serverChanSendKeyServer酱 (ServerChan) SendKey for push notifications
SERVERCHAN_NOTIFICATIONtrueserverChanNotificationEnable ServerChan notifications
SERVERCHAN_BACKGROUND_ONLYfalseserverChanBackgroundOnlyOnly send ServerChan notifications when no visible HAPI connection exists in the namespace
HAPI_RELAY_APIrelay.hapi.run-Relay API domain for the public relay
HAPI_RELAY_AUTHPer-hub key issued by the relayrelayAuthKeyRelay auth key override (set only when an operator provides a key)
HAPI_RELAY_FORCE_TCPfalse-Force TCP mode for relay
HAPI_OFFICIAL_WEB_URLhttps://app.hapi.run-Official web app origin, added to CORS when the relay is enabled
VAPID_SUBJECTmailto:[email protected]-Web Push contact info
HAPI_ANDROID_PUSHautoandroidPushModeAndroid push: official relay by default, direct FCM when private credentials are configured; also accepts relay, fcm, off
HAPI_IOS_PUSHrelayiosPushModeiOS push: relay, direct apns, or off
HAPI_PUSH_RELAY_URLhttps://push.hapi.runiosPushRelayUrlShared Android/iOS push relay, independent of the network tunnel
FCM_SERVICE_ACCOUNT_PATH-fcmServiceAccountPathDirect FCM credentials for private builds using the same Firebase project
HAPI_HOME~/.hapi-Config directory path
DB_PATH~/.hapi/hapi.db-Database file path
HAPI_EXPERIMENTAL--CLI: enable experimental features (true/1/yes)
ELEVENLABS_API_KEY-Settings / envElevenLabs API key for voice + dictation
ELEVENLABS_AGENT_IDAuto-created-Custom ElevenLabs agent ID
GEMINI_API_KEY / GOOGLE_API_KEY-Settings / envGemini Live voice assistant
DASHSCOPE_API_KEY / QWEN_API_KEY-Settings / envQwen Realtime voice assistant
VOICE_BACKENDAuto-detected-Default assistant backend: elevenlabs, gemini-live, or qwen-realtime
OPENAI_API_KEY-Settings / envOpenAI API key for dictation (gpt-transcribe / gpt-live-transcribe)
DEEPGRAM_API_KEY-Settings / envDeepgram API key for dictation (nova-3)
GROQ_API_KEY-Settings / envGroq API key for dictation (whisper-large-v3)
TRANSCRIPTION_BASE_URL-Settings / envOpenAI-compatible/local transcription base URL
TRANSCRIPTION_MODEL-Settings / envModel for the OpenAI-compatible transcription endpoint
TRANSCRIPTION_API_KEY-Settings / envOptional bearer token for that endpoint
HAPI_TITLE_PROVIDER_BASE_URL--Server-only OpenAI-compatible Chat Completions base URL for generated session titles
HAPI_TITLE_PROVIDER_API_KEY--Server-only API key for generated session titles; never sent to the browser
HAPI_TITLE_PROVIDER_MODEL--Server-only lightweight model used for generated session titles
HAPI_TITLE_SUGGESTION_RATE_LIMIT5-Maximum title suggestions per session in the rate-limit window
HAPI_TITLE_SUGGESTION_RATE_WINDOW_MS600000-Title suggestion rate-limit window in milliseconds
HAPI_TITLE_PROVIDER_MAX_TOKENS64-Maximum completion-token budget per generated title; raise for reasoning-model providers
HAPI_TITLE_PROVIDER_TIMEOUT_MS10000-Title provider request timeout in milliseconds

The session rename dialog's Generate action is unavailable until all three HAPI_TITLE_PROVIDER_* variables are configured on the Hub. The provider is called only on demand; the existing manual rename flow does not require these variables. Each request sends recent visible user/assistant conversation text (up to 200 stored messages and a bounded prompt) to that configured provider.

settings.json example

Configuration priority: ENV > settings.json > default

When ENV values are set and not present in settings.json, they are automatically saved. HAPI_EXTRA_HEADERS_JSON is not automatically saved, so access credentials are not persisted unexpectedly.

json
{
  "$schema": "https://hapi.run/docs/schemas/settings.schema.json",
  "listenHost": "0.0.0.0",
  "listenPort": 3006,
  "publicUrl": "https://your-domain.com",
  "extraHeaders": {
    "Cookie": "CF_Authorization=..."
  }
}

JSON Schema: settings.schema.json

CLI setup

If the hub is not on localhost, set these before running hapi:

bash
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:

bash
hapi auth login

Authentication commands:

bash
hapi auth status
hapi auth login
hapi auth logout

Each 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

Diagnostics

Run hapi doctor for a full diagnostics report: configuration, runner status, logs, and relevant environment info.

bash
hapi doctor          # Diagnostics report
hapi doctor clean    # Kill runaway hapi processes

Runner setup

Run a background service for remote session spawning:

bash
hapi runner start
hapi runner status
hapi runner logs
hapi runner stop

With 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

Split hub + remote runner (peer discovery)

When the hub runs on one host and the runner on another, agents inside runner-spawned sessions should discover peers via MCP list_peers (same hub credentials as the session CLI). Prefer that over shelling hapi ping-peer --list.

[Hub host]  hapi hub          ← sessions DB + /api/sessions

     │ HAPI_API_URL + CLI_API_TOKEN

[Runner host]  hapi runner start  → spawns session CLIs


              agent session  → MCP list_peers / inspect_peer / ping_peer

On the runner host, configure the same hub URL and token the hub uses:

bash
export HAPI_API_URL="http://your-hub:3006"   # or Tailscale / public URL
export CLI_API_TOKEN="your-token-here"
# or: hapi auth login   # saves the token; still set HAPI_API_URL for a remote hub
hapi runner start

Session CLI may export an explicit non-default HAPI_API_URL (from env or settings) into child env so shell helpers hit the same remote hub. It does not mirror CLI_API_TOKEN into wrapped agents (settings/prompt-backed secrets stay out of agent env; a fresh hapi re-reads ~/.hapi/settings.json, and systemd/env tokens already inherit). Prefer MCP list_peers inside a session. Web terminal PTYs still strip hub secrets. If --list fails with an auth/URL error, the message points at hapi auth login and the configured hub URL.

Additional runner commands:

bash
hapi runner list                      # List active sessions
hapi runner stop-session <sessionId>  # Stop a single session managed by the runner

Use --workspace-root <path> to restrict which directories the runner can browse and spawn sessions in. Repeat the flag to allow multiple directories; supports ~ expansion:

bash
hapi runner start --workspace-root ~/projects --workspace-root ~/work

Without --workspace-root, directory browsing and spawning both accept paths anywhere the runner's OS account can access. The iOS directory picker starts at the home directory and can navigate above it. Configuring roots restricts both browsing and spawning to those roots, including symlink targets.

For running the hub and runner as persistent background services (pm2, launchd, systemd), see Deployment. Supervised installs should set HAPI_RUNNER_SUPERVISED=1 on the runner process (systemd Environment= / pm2 --env) so the web Restart control can safely stop-runner knowing the supervisor will cold-start it.

Multi-machine hubs

You can run one hub and runners on many machines (each machine installs its own CLI). When you upgrade the hub, upgrade the HAPI CLI on every machine that parents sessions. After the CLI binary on disk changes, that machine’s runner normally self-restarts via version handoff (unless HAPI_DISABLE_VERSION_HANDOFF=1). Until a runner reports the capabilities the hub requires, the web UI shows a Runner out of date banner (minimizable / snoozeable) with the host name and upgrade steps. The banner’s per-host Restart is only an escape hatch when handoff is stuck or disabled — the hub never downloads or installs packages on remotes.

Security notes

  • Keep tokens secret and rotate if needed
  • Use HTTPS for public access
  • Restrict CORS origins in production
Firewall example (ufw)
bash
ufw allow from 192.168.1.0/24 to any port 3006

Released under the AGPL-3.0 License. · Privacy Policy