The native iOS and Android apps are now published. Replace the
build-from-source install path with store links, mark the native apps as
paid and the Web/PWA as the free client, and add store buttons to the
website hero. Source builds remain documented for developers.
* feat(cli): add HAPI_DISABLE_PEER_TOOLS to hide peer-session tools from agents
Wrapped agents with peer tools can browse the session list and message
other sessions, polluting peer contexts on busy runners. Setting
HAPI_DISABLE_PEER_TOOLS=1 (true/yes also accepted) in the runner
environment now removes the peer surface entirely instead of
advertising it and refusing:
- the session MCP server does not register list_peers / inspect_peer /
ping_peer, and they are dropped from the toolNames consumed by
Claude --allowedTools and the codex/generic bridge --tools lists
- the stdio bridge filters peer names even when a stale --tools value
lists them explicitly
- flavor system prompts (claude/codex/opencode) swap the peer steer
block for a short notice that keeps the hub-path-is-not-a-file guard
while pointing citations at the user
- the peer core (pingPeer/inspectPeer/listPeerSessions) refuses with a
new 'disabled' error code before any network call, so shell
`hapi ping-peer` / `inspect-peer` from inside a wrapped agent fails
loudly as defense in depth
Operators scope the variable to the runner (systemd/launchd/guard env);
an interactive terminal without it keeps full CLI access.
via [HAPI](https://hapi.run)
Co-Authored-By: HAPI <noreply@hapi.run>
Co-Authored-By: Claude Code <noreply@anthropic.com>
* test(cli): cover peer-tool prompt steer and default bridge tool list
Adds regression tests for the two surfaces the kill switch mutates beyond
the explicit --tools path:
- happyMcpStdioBridge: default tool list (no --tools) keeps the peer tools
as a positive control, then HAPI_DISABLE_PEER_TOOLS=1 filters them out
of the same default seed.
- New cross-flavor prompt test: the citation steer baked into the
claude/codex/opencode prompt consts swaps to the disabled notice under
the env flag and keeps the flavor-specific "Call \"<tool>\"" instruction
by default (module consts evaluate env at import, so each mode is
imported fresh via vi.resetModules + dynamic import).
via [HAPI](https://hapi.run)
Co-Authored-By: HAPI <noreply@hapi.run>
Co-Authored-By: Claude Code <noreply@anthropic.com>
* test(cli): keep peer-tools env set through prompt assertions
Keeps HAPI_DISABLE_PEER_TOOLS applied until after the assertions and
restores it at test end. Some upstream shapes resolve the citation steer
at call time (getSystemPrompt(env)) rather than baking it into the
import-time const, so restoring the env inside the import helper made
those shapes observe the enabled steer and fail.
via [HAPI](https://hapi.run)
Co-Authored-By: HAPI <noreply@hapi.run>
Co-Authored-By: Claude Code <noreply@anthropic.com>
---------
Co-authored-by: HAPI <noreply@hapi.run>
Co-authored-by: Claude Code <noreply@anthropic.com>
Separate top-level help and version flags from agent arguments. Require explicit agents in scripts and preserve command argument boundaries.
Validation: bun typecheck, bun run test, targeted runner integration tests, and PTY/source/compiled argv smoke checks.
Use one native app-server for terminal, Web and phone clients while retaining the existing CLI and Runner lifecycle.
Synchronize native queues, permissions, question history and steering state; preserve explicit permission precedence and per-turn usage models. Resume inactive clear commands through Runner and reject independent child cold resumes.
Add shared-runtime regression tests, generated protocol fixtures and lifecycle documentation.
* feat(cli): MCP list_peers + runner hub auth inheritance
Runner-spawned agents could not discover same-hub peers without
sitting on the hub host or pasting a session id. Add MCP list_peers
(in-process credentials), export HAPI_API_URL/CLI_API_TOKEN after
auth init for shell fallbacks, and clearer auth failure hints.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): do not export default hub URL into HAPI_API_URL
exportHapiHubAuthEnv was writing the implicit localhost default into
process.env, which made maybeAutoStartServer skip starting the bundled
hub. Only export HAPI_API_URL when the URL came from env or settings;
always still export CLI_API_TOKEN. Also fill missing deliveryMode on
abort restore so web typecheck matches RawSendError (main tip unblock).
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): widen initializeApiUrl mock return type in test
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): never export CLI_API_TOKEN; exclude self from list_peers
Keep settings/prompt-backed hub secrets out of wrapped agent env so
shell JWT+curl cannot bypass peer-tool approval. Fresh hapi re-reads
settings; env-backed tokens already inherit. list_peers omits the
calling session from the shortlist.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): resolve peer labels via summary/path like web titles
list_peers was showing (unnamed) for ordinary sessions because titles
live in metadata.summary.text. Match web getSessionTitle and collapse
whitespace so each peer stays one agent-readable line.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli,hub): emit full peer ids and honor GET /sessions?limit
Short 8-char prefixes collide across UUID namespaces; print full ids so
resolveSessionByPrefix stays unambiguous. Honor optional limit after sort
so listPeerSessions stops loading the whole namespace for scheduled counts.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(hub): type sessions limit test mock as Map<string, number>
CI tsc rejected Map<string, null> for getNextScheduledAtBySessionIds.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli,hub): unbounded ping resolve; peer list order=updatedAt
Keep GET /sessions?limit only for discovery callers. ping/inspect omit
limit so full UUIDs outside the first 500 stay resolvable. Peer lists
pass order=updatedAt so truncation matches newest-first. Basename
fallback splits Windows paths.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): auto-approve ACP title List Peer Sessions
Permission derivation prefers request.title; match the MCP tool title
form so default-mode ACP sessions do not prompt on discovery.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): pad list_peers fetch; split hub URL vs token hints
Fetch limit+2 when excluding the caller so overflow still surfaces at
limit=100. Clarify that auth login only saves the token, not HAPI_API_URL.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(cli): use boolean overflow for ping-peer --list
Match MCP list_peers: fetch limit+1 and mark hasMore instead of claiming
an exact omitted count from a 200-row sample.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix(hub): tolerate mocked machineCache without expireInactive
CI flake: 5s inactivity tick hit test doubles that only stubbed
getOnlineMachinesByNamespace. Optional-call + stub the method.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat(cursor): add support for Cursor Agent CLI integration
- Introduced new command `hapi cursor` to start Cursor Agent sessions.
- Added functionality for resuming sessions and managing permission modes.
- Updated documentation to include Cursor Agent usage and installation instructions.
- Enhanced existing codebase to accommodate Cursor as a recognized agent flavor.
- Implemented local and remote session handling for Cursor Agent.
This update expands HAPI's capabilities by integrating support for the Cursor Agent, allowing users to leverage its features alongside existing agents.
* Remove TODO.md file as it is no longer needed following the integration of Cursor Agent CLI support. This cleanup helps streamline project documentation and reflects the completion of the associated tasks.
* feat(cursor): implement remote mode and fix --hapi-starting-mode
- Consume --hapi-starting-mode in cursor command (do not forward to agent)
- Implement cursorRemoteLauncher: spawn agent -p with stream-json, --trust
- Add cursorEventConverter for NDJSON parsing (system/assistant/tool_call/result)
- Multi-turn via --resume session_id
- Update docs: cursor supports both local and remote modes
Made-with: Cursor
* fix: type error
* fix(cursor): address PR review - model UI, sessionId metadata, duplicate flags
- HappyComposer: use isClaudeFlavor for model mode (cursor has no model modes)
- cursorLocalLauncher: call onSessionFound for resume so cursorSessionId in metadata
- cursorCommand: do not forward parsed flags to cursorArgs (avoid duplicates)
Made-with: Cursor
Introduce comprehensive documentation of HAPI's Seamless Handoff feature:
- README: Add Seamless Handoff to Features section
- how-it-works: Add complete Seamless Handoff guide with local/remote modes and switching workflow
- quick-start: Add Seamless Handoff link to Next steps
- AppShowcase: Add third card featuring "Switch Freely" mode switching capability
- Locales: Update English and Chinese translations for showcase content
- Support both local and remote server deployment
- Add direct public IP access option for remote servers
- Remove tunnel requirement messaging
- Organize remote access options with details sections
- Initialize VitePress documentation site with config, index, and guides
- Add guides for quick-start, installation, PWA, how-it-works, FAQ, and why HAPI
- Update .gitignore to exclude VitePress cache directory
- Update logo.svg with actual icon from web/public/icon.svg
- Simplify README.md with link to full installation guide
- Remove redundant WHY_NOT_HAPPY.md (content migrated to why-hapi guide)