* fix(web): migrate chat-path attachments on scratchlist park (#1226) When an image is attached via the normal upload adapter before scratchlist mode is enabled, toggling mode swapped adapters and send() dropped the metadata — park stored text-only and cleared chips. Migrate pending chat-path files into hub scratchlist storage on send, and fail closed if non-hub paths still reach the park wrapper. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): defer chat-path cleanup until scratchlist park succeeds send() migrates before scratchlist.add; deleting the original upload there left retries pointing at a missing chat blob when park failed. Stamp migratedFromPath and clean up only after the park attempt result. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): park scratchlist before composer.send clears chips assistant-ui empties text/attachments before onNew, so return false from park could not restore retryable state and rejected cleanup deleted the migrated hub blob. Intercept park from a live snapshot; clear only after accept; releaseWithoutDelete so clearAttachments keeps parked hubs. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): lock composer while scratchlist park is in flight Disable input/send and hide chip remove during migrate+add so mid-flight edits are not wiped on success and hub blobs are not deleted out from under the accepted entry. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): do not clear composer if park snapshot changed mid-flight Compare post-await composer state to the pre-park snapshot before clearing; disable DragDropZone and scratchlist promote while parking so parent paths cannot add chips the clear would silently drop. Addresses Codex Major on #1227 (preserve post-snapshot composer changes). Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): defer migrated chat-path cleanup until park snapshot clears Return ScratchlistParkResult.beforeClear from onParkScratchlist so finalizeMigratedScratchlistParkCleanup runs only after HappyComposer confirms the composer was unchanged mid-flight. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): flush rich mentions before scratchlist park snapshot Park snapshots composer.text after flushSerializedText so session @-mention chips serialize to markdown links before scratchlist add. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): validate park snapshot before scratchlist add Split prepare/commit/abort so mid-flight composer edits abort orphan hub blobs instead of parking a duplicate. Reuse restored hub paths in prepareScratchlistParkAttachments so remounted chips do not re-upload. Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): ignore scratchlist toggle hotkey while park is in flight Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com>
HAPI
Run official Claude Code / Codex / Cursor Agent / Grok Build / OpenCode sessions locally and control them remotely through a Web / PWA / Telegram Mini App.
Why HAPI? HAPI is a local-first alternative to Happy. See Why Not Happy? for the key differences.
Features
- Seamless Handoff - Work locally, switch to remote when needed, switch back anytime. No context loss, no session restart.
- Native First - HAPI wraps your AI agent instead of replacing it. Same terminal, same experience, same muscle memory.
- AFK Without Stopping - Step away from your desk? Approve AI requests from your phone with one tap.
- Your AI, Your Choice - Claude Code, Codex, Cursor Agent, Grok Build, OpenCode—different agents, one unified workflow.
- Terminal Anywhere - Run commands from your phone or browser, directly connected to the working machine.
- Voice Control - Talk to your AI agent hands-free using the built-in voice assistant.
- Workspace Browser - Opt-in via one or more
hapi runner start --workspace-root <path>flags: browse scoped file trees from the web and start sessions in allowed subdirectories.
Demo
https://github.com/user-attachments/assets/38230353-94c6-4dbe-9c29-b2a2cc457546
Getting Started
npx @twsxtd/hapi hub --relay # start hub with E2E encrypted relay
npx @twsxtd/hapi # run claude code
hapi server remains supported as an alias.
The terminal will display a URL and QR code. Scan the QR code with your phone or open the URL to access.
The relay uses WireGuard + TLS for end-to-end encryption. Your data is encrypted from your device to your machine.
For self-hosted options (Cloudflare Tunnel, Tailscale), see Installation
Docs
Build from source
bun install
bun run build:single-exe
Credits
HAPI means "哈皮" a Chinese transliteration of Happy. Great credit to the original project.