A mobile-friendly web UI for coding agents, written in Go. Ships as a single static binary (~24 MB) with zero runtime dependencies.
Supports four ACP agents and Pi native RPC. Built for personal use over Tailscale.
This is the Go port β single binary, embedded frontend, pure-Go SQLite.
The original Python implementation is on thepythonbranch.
- Streaming chat β real-time SSE with Markdown, KaTeX, and Mermaid rendering
- Workspace explorer β file tree with previews, drag-and-drop upload, folder downloads (ZIP)
- Code editor β tabbed CodeMirror 6 editor (13 languages, Vim mode, search/replace, pop-out windows)
- Agent controls β approve/deny tool calls, queue follow-ups, mid-turn steering, slash commands
- Rich media β paste images, attach files, OpenGraph link previews
- SQLite storage β messages, media, and full-text search in a single file
- PWA β installable, dark/light themes, responsive from phone to desktop
- Single binary β no Python, no Node.js, no virtualenv, no runtime dependencies
- Extension system β backend routes, SSE events, and frontend UI panels
- Multi-agent β switch between Copilot, Codex, Claude, OpenCode, and Pi at runtime
| Agent | Binary | Version | Status |
|---|---|---|---|
| GitHub Copilot | copilot-language-server --acp --stdio |
v1.472.0 | β |
| OpenAI Codex | codex-acp |
v0.11.1 | β |
| Claude | claude-agent-acp |
v0.29.2 | β |
| Pi | pi-acp |
v0.0.26 | β |
| OpenCode | opencode acp |
v1.14.31 | β |
| Feature | Copilot | Codex | Claude | Pi | OpenCode |
|---|---|---|---|---|---|
| Image support | β | β | β | β | β |
| Embedded context | β | β | β | β | β |
| MCP support | β | β (HTTP) | β (HTTP + SSE) | β | β (HTTP + SSE) |
| Session list/close | β | β | β | β | β |
| Session fork/resume | β | β | β | β | β |
| Prompt queueing | β | β | β | β | β |
| Free models | β | β | β | β | β |
| Auth | GitHub OAuth | ChatGPT / API key | Pre-configured | Terminal login | opencode auth |
All five implement ACP protocol v1 over stdio JSON-RPC. OpenCode provides free models (gpt-5-nano, hy3-preview-free, etc.) making it ideal for CI testing. Pi also supports a richer native RPC mode with streaming drafts, thinking traces, tool events, and live model control.
# Build
git clone https://github.com/rcarmo/vibes.git && cd vibes
make build
# Run with GitHub Copilot (default)
./vibes
# Override detected ACP backends if needed
VIBES_CODEX_AGENT="codex-acp" ./vibes
VIBES_COPILOT_AGENT="copilot-language-server --acp --stdio" ./vibes
# Run with Pi native RPC selected by default (richer features)
VIBES_DEFAULT_AGENT=pi ./vibes
# Open in browser
open http://localhost:8080make build # frontend bundle + embedded Go binaryThis produces a fully self-contained binary with all HTML, CSS, JS, fonts, and icons embedded at compile time via //go:embed.
make build-linux-arm64 # Raspberry Pi, ARM Proxmox
make build-darwin-arm64 # macOS Apple Silicon
make build-all # all platforms# Local build
docker build -t vibes .
docker run -p 8080:8080 vibes
# Prebuilt release images from GHCR
docker pull ghcr.io/rcarmo/vibes:vX.Y.Z
docker run -p 8080:8080 ghcr.io/rcarmo/vibes:vX.Y.ZThe Dockerfile produces a scratch-based image (~24 MB) β just the binary + CA certificates.
Release tags publish multi-arch images (linux/amd64, linux/arm64) to GHCR.
Install the ACP agents you want to use:
npm install -g @github/copilot-language-server # GitHub Copilot
npm install -g @openai/codex # OpenAI Codex (includes codex-acp)
npm install -g @agentclientprotocol/claude-agent-acp # Claude
npm install -g pi-acp # Pi (ACP adapter)
npm install -g opencode-ai # OpenCode (free models)
npm install -g @mariozechner/pi-coding-agent # Pi (native, for RPC mode)All configuration is via environment variables with VIBES_ prefix:
| Variable | Default | Description |
|---|---|---|
VIBES_HOST |
0.0.0.0 |
Bind address |
VIBES_PORT |
8080 |
Port |
VIBES_DB_PATH |
database/vibes.db |
SQLite database |
VIBES_DEBUG |
false |
Verbose logging |
VIBES_COPILOT_AGENT |
copilot-language-server --acp --stdio |
Copilot ACP command |
VIBES_COPILOT_ENABLED |
true |
Enable Copilot backend discovery |
VIBES_CODEX_AGENT |
codex-acp |
Codex ACP command |
VIBES_CODEX_ENABLED |
true |
Enable Codex backend discovery |
VIBES_DEFAULT_AGENT |
acp |
Default backend (acp is treated as copilot for compatibility) |
VIBES_PI_ENABLED |
true |
Enable Pi native RPC discovery/probing (set false to disable) |
VIBES_PI_AGENT |
pi |
Pi binary path |
VIBES_PERMISSION_TIMEOUT |
30 |
Permission request timeout (seconds) |
VIBES_PERMISSION_AUTO_APPROVE |
false |
Auto-approve all tool calls |
VIBES_DISCONNECT_TIMEOUT |
300 |
Agent keepalive after last SSE client disconnects |
VIBES_ACP_DEBUG |
false |
Wire-level ACP logging |
VIBES_ACP_MCP_SERVERS_JSON |
(unset) | JSON array of ACP MCP servers to pass to ACP sessions (stdio supported; http/sse filtered by negotiated agent capabilities) |
VIBES_EXTENSIONS_DIR |
extensions |
Extension scan directory |
VIBES_CORS_ALLOW_ORIGIN |
(unset) | Enable CORS for a specific origin (or *) |
VIBES_API_TOKEN |
(unset) | Optional API token for sensitive/mutating routes (X-API-Token or Authorization: Bearer) |
VIBES_ENABLE_TERMINAL |
false |
Enable terminal WebSocket endpoint (/terminal/ws) |
VIBES_ENABLE_PPROF |
false |
Enable pprof endpoints under /debug/pprof (guarded by token when set) |
See docs/CONFIGURATION.md for full reference, including ACP MCP server JSON examples and current ACP client-service limitations.
# Require token auth on sensitive/mutating endpoints
export VIBES_API_TOKEN="change-me"
# Restrict browser CORS to your UI origin
export VIBES_CORS_ALLOW_ORIGIN="https://your-ui.example"
# Keep dangerous debug/shell surfaces disabled unless needed
export VIBES_ENABLE_TERMINAL=false
export VIBES_ENABLE_PPROF=falseβββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Browser (SPA) β
β Preact + HTM β CodeMirror 6 β KaTeX β Mermaid β SSE client β
ββββββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββ
β HTTP + SSE
ββββββββββββββββββββββββββββββββββ΄βββββββββββββββββββββββββββββββββ
β Go HTTP Server (chi) β
β β
β ββββββββββββ ββββββββββββ ββββββββββββ ββββββββββββ β
β β Timeline β β Media β βWorkspace β β Agents β β
β β Routes β β Routes β β Routes β β Routes β β
β ββββββ¬ββββββ ββββββ¬ββββββ ββββββ¬ββββββ ββββββ¬ββββββ β
β β β β β β
β ββββββ΄ββββββββββββββ΄βββββββββββββ΄ββββββββββββββ΄βββββββββββββββ β
β β SQLite (WAL, FTS5, JSON columns) β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β
β βββββββββββββββββ ββββββββββββββββββββββββββββββββββββββ β
β β SSE Broker βββββββββΊβ Agent Registry β β
β β (per-client β β βββββββββββ βββββββββββ β β
β β buffered β β β ACP β β Pi β β β
β β fanout) β β βProvider β βProvider β β β
β βββββββββββββββββ β ββββββ¬βββββ ββββββ¬βββββ β β
β β β β β β
β βββββββββββββββββ β ββββββ΄βββββββββββββ΄βββββ β β
β β Extensions β β β Subprocess Manager β β β
β β (registry) β β ββββββ¬ββββββββββββββββββ β β
β βββββββββββββββββ βββββββββΌβββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββ
β stdio (JSON-RPC / NDJSON)
ββββββββββββββββββΌβββββββββββββββββ
βΌ βΌ βΌ
ββββββββββββββββ ββββββββββββββ ββββββββββββββ
βcopilot-lang- β β codex-acp β βclaude-agentβ
βserver --acp β β β β -acp β
ββββββββββββββββ ββββββββββββββ ββββββββββββββ
β
βββββββββ΄βββββββββ
β pi --mode β
β rpc β
ββββββββββββββββββ
1. User types message in compose box
2. Frontend POSTs to /agent/default/message
3. Server stores user message in SQLite
4. Server broadcasts "new_post" via SSE
5. Server spawns prompt on the active AgentProvider (async)
6. Agent streams events on stdout:
- ACP: session/update notifications (draft/thought/tool_call)
- Pi: NDJSON events (text_delta/thinking_delta/tool_execution)
7. Provider routes events β SSE broker β all connected clients
8. Frontend renders streaming draft/thought/status panels
9. Agent completes turn β final response stored in SQLite
10. Server broadcasts "agent_response" via SSE
11. Frontend renders final message in timeline
Server β Client (text/event-stream):
connected Connection established
new_post User posted a message
agent_draft Streaming text from agent
agent_thought Agent thinking/reasoning stream
agent_status Tool call / status update
agent_session_update ACP session metadata/usage update
agent_response Final agent response (turn complete)
agent_request Permission request (approve/deny)
agent_request_timeout Permission timed out
agent_error Agent error
interaction_updated Post metadata changed
interaction_deleted Post removed
| Concern | Python (original) | Go (this port) |
|---|---|---|
| Deployment | virtualenv + pip + Python runtime | Single static binary |
| Memory | ~80 MB baseline (Python + aiohttp) | ~15 MB baseline |
| Boot time | 2β3s (Python import chain) | <100 ms |
| Cross-compile | Not practical | Makefile targets such as make build-linux-arm64 |
| Concurrency | asyncio (single thread) | Goroutines (multi-core) |
| SQLite binding | C extension (requires build tools) | Pure Go (modernc.org/sqlite) |
| Asset delivery | Separate static directory | Embedded via //go:embed |
| Container | ~200 MB Python image | ~24 MB scratch image |
- stdlib
net/httpcompatible β works with any middleware - Minimal API surface, no framework lock-in
- Context-based routing with URL parameters
- No reflection, no code generation
- Pure Go β no CGo, no C compiler needed at build time
- Enables
CGO_ENABLED=0for truly static binaries - Enables cross-compilation without C toolchain
- Same SQLite feature set (WAL, FTS5, JSON functions, virtual columns)
- ~10% slower than CGo version β acceptable for a single-user app
- Single binary deployment β copy one file, run it
- No "missing static files" errors in production
- Atomic updates (binary = code + assets together)
- Same pattern as rcarmo/webterm
- Trade-off: rebuild binary to update frontend (use
make build)
- Full typed schema for all ACP types (content blocks, tool calls, sessions)
ClientSideConnectionwith notification/request handlerstransport.Spawnfor subprocess lifecycle- Contrib helpers: ToolCallTracker, PermissionBroker, SessionAccumulator
- The only Go ACP SDK with schema parity as of April 2026
Inspired by piclaw's extension architecture:
type Extension interface {
ID() string
Name() string
Version() string
Init(ctx context.Context) error
Shutdown(ctx context.Context) error
Routes() []Route // HTTP routes to mount
EventTypes() []string // SSE event types emitted
StaticDir() string // Static assets (/ext/{id}/)
Manifest() *Manifest // Frontend UI panels
}
type Manifest struct {
Panels []PanelDef // sidebar, main, overlay, toolbar
CSS []string // CSS to inject
JS []string // JS to load
}Extensions can:
- Register backend API routes (mounted on chi router)
- Emit custom SSE event types
- Serve static assets under
/ext/{id}/ - Declare frontend UI panels (sidebar, toolbar, overlays, main area)
vibes/
βββ cmd/
β βββ vibes/main.go # Entry point
β βββ acp-test/main.go # ACP agent verification tool
βββ internal/
β βββ app/app.go # Application wiring (chi + DB + agents + SSE)
β βββ config/config.go # Environment-based configuration
β βββ db/
β β βββ db.go # SQLite open + WAL + connection management
β β βββ migrations.go # Schema v1 (interactions, media, whitelist, FTS5)
β β βββ queries.go # Typed CRUD + search + whitelist
β β βββ db_test.go # 11 unit tests
β β βββ db_fuzz_test.go # 3 fuzz targets
β βββ agent/
β β βββ provider.go # Provider interface + Registry
β β βββ provider_test.go # Registry tests
β β βββ acp/client.go # ACP client (wraps acp-sdk-go)
β β βββ pi/
β β βββ client.go # Pi native RPC client
β β βββ client_test.go # Event routing tests
β βββ server/sse/
β β βββ broker.go # SSE connection management + fanout
β β βββ broker_test.go # Subscribe/broadcast/HTTP tests
β βββ routes/
β β βββ timeline.go # GET/POST/DELETE timeline, thread, search
β β βββ media.go # Upload, serve, thumbnail, info
β β βββ workspace.go # Tree, file CRUD, rename, download, upload
β β βββ agents.go # List, status, message, whitelist
β β βββ commands.go # Slash commands (/model, /shell, etc.)
β β βββ opengraph.go # Link preview fetcher
β β βββ permissions.go # Permission broker + follow-up queue
β β βββ routes_test.go # 12 route tests
β β βββ routes_fuzz_test.go # Path traversal + parsing fuzz
β β βββ commands_test.go # Command dispatch tests
β β βββ permissions_test.go # Queue + shell + permission tests
β βββ extensions/
β βββ registry.go # Extension discovery + lifecycle
β βββ registry_test.go # Registration + manifest tests
β βββ registry_fuzz_test.go # ID fuzzing
βββ static/ # Frontend (embedded at compile time)
β βββ index.html # SPA entry point
β βββ manifest.json # PWA manifest
β βββ css/styles.css # Main stylesheet/base rules
β βββ css/components/ # Per-component CSS split-outs
β βββ dist/app.{js,css} # Bundled frontend (Bun)
β βββ js/ # Source modules (Preact + HTM)
β β βββ app.js # Root SPA component
β β βββ api.js # API client
β β βββ components/ # UI components
β β βββ panes/ # Popout editor
β β βββ vendor/ # Vendored Preact, CodeMirror, KaTeX
β βββ fonts/ # KaTeX math fonts
βββ tests/features/*.feature # BDD user stories (Gherkin)
βββ tests/steps/*.spec.mjs # Playwright implementations (46 scenarios)
βββ config/endpoints.json # Custom action definitions
βββ docs/ # Protocol and integration docs
βββ embed.go # //go:embed all:static
βββ Dockerfile # Multi-stage: builder β scratch
βββ Makefile # build, test, fuzz, lint, e2e, cross-compile
βββ playwright.config.mjs
βββ go.mod / go.sum
βββ GO_PORT.md # Architecture deep-dive
βββ LICENSE # MIT
| Method | Endpoint | Description |
|---|---|---|
| GET | /health |
Health check |
| GET | /timeline |
Paginated posts (newest first) |
| POST | /post |
Create post |
| DELETE | /post/{id} |
Delete post (optional cascade) |
| GET | /thread/{id} |
Get thread |
| POST | /thread |
Reply to thread |
| GET | /search?q= |
Full-text search |
| POST | /media/upload |
Upload media file |
| GET | /media/{id} |
Serve media |
| GET | /media/{id}/thumbnail |
Serve thumbnail |
| GET | /media/{id}/info |
Media metadata |
| GET | /workspace/tree |
File tree |
| GET | /workspace/file?path= |
Read file content (includes mtime) |
| PUT | /workspace/file |
Write file (optional optimistic-lock mtime) |
| DELETE | /workspace/file?path= |
Delete file |
| POST | /workspace/create |
Create file/directory |
| POST | /workspace/rename |
Rename/move |
| GET | /workspace/raw?path= |
Raw file download |
| GET | /workspace/download?path= |
Download file/dir (ZIP) |
| POST | /workspace/upload |
Upload to workspace |
| GET | /agents |
List agents and backend provider metadata |
| GET | /agent/providers |
Backend discovery/capability map |
| GET/POST | /thread/{id}/backend |
Read or switch thread backend affinity |
| GET | /agent/status |
Current agent state |
| GET | /agent/context |
Context window usage |
| GET | /agent/commands |
List slash commands |
| POST | /agent/{id}/message |
Send message to agent |
| GET | /agent/whitelist |
List permission patterns |
| POST | /agent/whitelist |
Add pattern |
| DELETE | /agent/whitelist |
Remove pattern |
| POST | /agent/respond |
Respond to permission request |
| GET | /avatar/{kind} |
Agent/user avatar |
| GET | /sse/stream |
SSE event stream |
| GET | /terminal/ws |
Terminal WebSocket (only when VIBES_ENABLE_TERMINAL=true) |
| GET | /debug/pprof/* |
pprof profiling endpoints (only when VIBES_ENABLE_PPROF=true) |
make help # Show all targets
make build # Full build (frontend + Go binary)
make dev # Run from source with debug logging
make serve # Build and run
make test # Unit tests
make race # Race detector
make coverage # Coverage report
make fuzz # All 9 fuzz targets
make check # lint + test + coverage
make test-acp # Verify ACP agent handshakes
make e2e # Playwright BDD UX suite + PDF report
make build-all # Cross-compile (linux-amd64, linux-arm64, darwin-arm64)
make clean # Remove artifacts| Layer | Tests | Coverage |
|---|---|---|
internal/config |
3 unit + 3 fuzz | 100% |
internal/agent |
6 unit | 100% |
internal/agent/pi |
4 unit | 20% |
internal/extensions |
7 unit + 1 fuzz | 96% |
internal/server/sse |
4 unit | 85% |
internal/db |
11 unit + 3 fuzz | 79% |
internal/routes |
20 unit + 2 fuzz | 41% |
| Total | 55+ tests, 9 fuzz targets | 52% |
Plus 46 Playwright BDD scenarios for end-to-end UX validation.
| Shortcut | Action |
|---|---|
Cmd/Ctrl+S |
Save current editor tab |
Cmd/Ctrl+F |
Search/replace in editor |
Escape |
Close active editor tab |
Alt+V |
Toggle Vim mode |
Alt+W |
Toggle whitespace visibility |
β / β |
Cycle compose history |
The original Python implementation (aiohttp, 388 unit tests, 34 E2E tests) is on the python branch:
pip install -U git+https://github.com/rcarmo/vibes.git@pythonThe Go and Python versions share the same frontend, API, SSE events, and SQLite schema β they are interchangeable.
MIT
