- Executive Summary
- System Architecture
- Data Flows
- Core Components
- Server Implementation
- Web Frontend
- iOS Application
- Security Model
- Session Management
- CLI Integration
- API Specifications
- Binary Buffer Protocol
- User Interface
- Configuration System
- Build and Release
- Testing Strategy
- Performance Requirements
- Error Handling
- Update System
- Platform Integration
- Data Formats
VibeTunnel is a macOS application that provides browser-based access to Mac terminals, designed to make terminal access as simple as opening a web page. The project specifically targets developers and engineers who need to monitor AI agents (like Claude Code) remotely.
- Zero-Configuration Terminal Access: Launch terminals with a simple
vtcommand - Browser-Based Interface: Access terminals from any modern web browser
- Real-Time Streaming: Live terminal updates via WebSocket with binary buffer optimization
- Session Recording: Full asciinema format recording support
- Security Options: Password protection, localhost-only mode, Tailscale/ngrok integration
- High-Performance Server: Node.js server with Bun runtime for optimal JavaScript performance
- Auto-Updates: Sparkle framework integration for seamless updates
- AI Agent Integration: Special support for Claude Code with shortcuts
- iOS Companion App: Mobile terminal access from iPhone/iPad
- Native macOS App: Swift 6.0, SwiftUI, macOS 14.0+
- iOS App: Swift 6.0, SwiftUI, iOS 17.0+
- Server: Node.js/TypeScript with Bun runtime
- Web Frontend: TypeScript, Lit Web Components, Tailwind CSS
- Terminal Emulation: ghostty-web with custom buffer optimization
- Build System: Xcode, Swift Package Manager, npm/Bun
- Distribution: Signed/notarized DMG with Sparkle updates
┌─────────────────────────────────────────────────────────────┐
│ macOS Application │
│ ┌─────────────┐ ┌──────────────┐ ┌──────────────────┐ │
│ │ Menu Bar UI │ │ Server │ │ Session │ │
│ │ (SwiftUI) │──│ Manager │──│ Monitor │ │
│ └─────────────┘ └──────────────┘ └──────────────────┘ │
│ │ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Node.js/Bun Server Process │ │
│ │ ┌──────────────────────────────────────────────┐ │ │
│ │ │ Standalone Bun executable with embedded │ │ │
│ │ │ TypeScript server and native PTY modules │ │ │
│ │ └──────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
┌──────┴──────┐
│ HTTP/WS API │
└──────┬──────┘
│
┌──────────────────────────────┴──────────────────────────────┐
│ Client Applications │
├─────────────────────────────────────────────────────────────┤
│ Web Browser iOS App │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ Dashboard │ │ Native Swift │ │
│ │ (Lit/TS) │ │ Terminal UI │ │
│ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────┘
- Terminal Launch: User executes
vtcommand - Server Check: ServerManager ensures Bun server is running
- Session Creation: HTTP POST to create new terminal session
- PTY Allocation: Server allocates pseudo-terminal via node-pty
- WebSocket Upgrade: Client establishes
/wsconnection (v3 framing) - Terminal Transport: Multiplexed binary frames (
STDOUT+SNAPSHOT_VT) - Recording: Session data recorded in asciinema format
- Session Cleanup: Resources freed on terminal exit
- Single Server Implementation: One Node.js/Bun server handles everything
- Protocol-Oriented Swift: Clean interfaces between macOS components
- Binary Optimization: WebSocket v3 framing + VT snapshot v1 for previews/resync
- Thread Safety: Swift actors and Node.js event loop for concurrent safety
- Minimal Dependencies: Only essential third-party libraries
- User Privacy: No telemetry or user tracking
- User launches the
vtcommand or selects New Session from the UI. ServerManagerverifies that the Bun server is running and starts it if needed.- A
POST /api/sessionsrequest triggersTerminalManager.createTerminal()on the server. PtyManager.spawn()allocates a new PTY process and stores session metadata.- The server responds with the session ID and WebSocket URL.
- Clients connect to
/wsandSUBSCRIBE(sessionId, flags)using WS v3 framing. - Terminal output and input are recorded in asciinema format when recording is enabled.
- On process exit, resources are cleaned up and the client is notified.
- Keyboard input from the browser or iOS app is sent as WS v3 frames (
INPUT_TEXT/INPUT_KEY). WsV3Hubroutes input toPtyManager.- PTY output is tailed and sent back as
STDOUTframes. - Server-side VT snapshots (
SNAPSHOT_VT) are streamed for previews/resync. - The client updates its terminal display accordingly.
- Starting the macOS app or running
vtlaunchesServerManager. BunServerspawns the Bun-based HTTP/WebSocket server process.- Health checks hit
/api/healthto verify the server is alive. - On stop or crash,
ServerManagergracefully shuts down or restarts the process.
- When network mode is enabled, the server binds to
0.0.0.0for remote access. NgrokServiceor Tailscale can expose a secure public URL.- Remote clients reach the server through the tunnel and communicate over HTTPS.
- Clients request the dashboard or a session endpoint.
- Basic Auth middleware checks credentials stored via
DashboardKeychain. - Local bypass or token-based headers are honored if configured.
- Successful authentication allows API and WebSocket communication.
Location: mac/VibeTunnel/Core/Services/ServerManager.swift
Responsibilities:
- Manages Bun server process lifecycle (start/stop/restart)
- Handles server configuration (port, bind address)
- Provides log streaming from server process
- Coordinates with other services (Ngrok, SessionMonitor)
- Manages server health checks
Key Methods:
func start() async
func stop() async
func restart() async
func clearAuthCache() asyncState Management:
- Uses
@Observablefor SwiftUI integration @MainActorensures UI thread safety- Publishes server state changes
- Maintains server configuration in UserDefaults
Location: mac/VibeTunnel/Core/Services/BunServer.swift
Responsibilities:
- Spawns and manages the Bun executable process
- Handles process I/O streaming
- Monitors process health and auto-restarts
- Passes configuration via command-line arguments
Key Features:
- Embedded vibetunnel binary built with Bun
- Native PTY support via node-pty module
- Automatic crash recovery
- Log streaming to ServerManager
Location: mac/VibeTunnel/Core/Services/SessionMonitor.swift
Responsibilities:
- Polls server for active sessions
- Tracks session lifecycle
- Provides session counts for UI
- Handles session cleanup
Key Features:
- Real-time session tracking via polling
- Session metadata caching
- Automatic cleanup detection
- Performance monitoring
Location: mac/VibeTunnel/Core/Services/TerminalManager.swift
Responsibilities:
- Integrates with macOS terminal applications
- Handles terminal app selection (Terminal.app, iTerm2, etc.)
- Manages AppleScript execution for terminal launching
- Provides terminal detection utilities
Location: mac/VibeTunnel/Core/Services/NgrokService.swift
Responsibilities:
- Manages ngrok tunnel lifecycle
- Provides secure public URLs
- Handles authentication token storage
- Monitors tunnel status
Configuration:
- API key management via Keychain
- Custom domain support
- Region selection
- Basic auth integration
Location: web/src/server/ directory
Architecture: The server is built as a standalone Bun executable that embeds:
- TypeScript server code compiled to JavaScript
- Native node-pty module for PTY support
- Express.js for HTTP handling
- ws library for WebSocket support
- All dependencies bundled into single binary
Key Components:
server.ts- HTTP server initialization and lifecycleapp.ts- Express application setup and middlewarenative/vt-fwd- External terminal forwarder (Zig, built asvibetunnel-fwd)pty/pty-manager.ts- Native PTY process managementpty/session-manager.ts- Terminal session lifecycleservices/terminal-manager.ts- High-level terminal operationsservices/ws-v3-hub.ts- Unified/wsWebSocket v3 hubservices/cast-output-hub.ts- Cast tailing → v3STDOUTservices/git-status-hub.ts- Git status updates → v3EVENTroutes/sessions.ts- REST API endpoints
Server Features:
- High-performance Bun runtime (3x faster than Node.js)
- Zero-copy buffer operations
- Native PTY handling with proper signal forwarding
- Asciinema recording for all sessions
- WebSocket v3 framing (
/ws) + VT snapshot v1 for previews/resync - Graceful shutdown handling
Build Process:
# Build standalone executable
cd web && node build-native.js
# Creates web/native/vibetunnel (60MB Bun executable)Location: web/src/client/ directory
Core Technologies:
- TypeScript for type safety
- Lit Web Components for modern component architecture
- Tailwind CSS for styling
- ghostty-web for terminal rendering
- Unified WebSocket v3 transport (/ws)
web/src/client/
├── components/
│ ├── app-header.ts - Application header
│ ├── session-list.ts - Active session listing
│ ├── session-card.ts - Individual session display
│ ├── session-view.ts - Terminal container
│ ├── terminal.ts - ghostty-web wrapper
│ └── vibe-terminal-buffer.ts - Binary buffer handler
├── services/
│ └── terminal-socket-client.ts - WebSocket v3 transport
├── utils/
│ ├── terminal-renderer.ts - Terminal rendering utilities
│ ├── terminal-preferences.ts - User preferences
│ └── url-highlighter.ts - URL detection in terminal
└── styles.css - Tailwind configuration
Dashboard:
- Real-time session listing with 3-second polling
- One-click terminal creation
- Session metadata display (command, duration, status)
- Responsive grid layout
Terminal Interface:
- Full ANSI color support via ghostty-web
- Binary buffer protocol for efficient updates
- Copy/paste functionality
- Responsive terminal sizing
- URL highlighting and click support
- Mobile-friendly touch interactions
Performance Optimizations:
- WebSocket v3 framing (
VTmagic, multiplexed sessions) - Snapshot cadence control (previews vs interactive)
- Asciicast tailing with pruning detection
- WebSocket reconnection and resubscribe logic
- Lazy loading of terminal sessions
Location: ios/VibeTunnel/ directory
Purpose: Native iOS companion app for mobile terminal access
Key Components:
VibeTunnelApp.swift- Main app entry and lifecycleBufferWebSocketClient.swift- WebSocket client with binary protocolTerminalView.swift- Native terminal renderingGhosttyWebView.swift- Ghostty web renderer (WKWebView)TerminalBufferRenderer.swift- Buffer snapshot → ANSI conversionSessionService.swift- Session management API client
- Native SwiftUI interface
- Server connection management
- Terminal rendering with gesture support
- Session listing and management
- Recording export functionality
- Advanced keyboard support
The iOS app speaks the same terminal transport as the web client:
- Single
/wsWebSocket (v3 framing) - Multiplexed sessions (
sessionIdin each frame) - VT snapshot v1 payloads for previews/resync
Authentication Modes:
- System user password authentication (default)
- Optional SSH key authentication (
--enable-ssh-keys) - No authentication mode (
--no-auth) - Local bypass authentication (
--allow-local-bypass)
Local Bypass Security:
- Allows localhost connections to bypass authentication
- Optional token authentication via
--local-auth-token - Implements anti-spoofing checks (IP, headers, hostname)
- See
web/SECURITY.mdfor detailed security implications
Implementation:
- Main auth middleware:
web/src/server/middleware/auth.ts - Local bypass logic:
web/src/server/middleware/auth.ts:24-87 - Security checks:
web/src/server/middleware/auth.ts:25-48
Access Control:
- Localhost-only mode by default (127.0.0.1)
- Network mode binds to 0.0.0.0
- CORS configuration for web access
- No built-in TLS (use reverse proxy or tunnels)
Secure Tunneling:
- Tailscale integration for VPN access
- Ngrok support for secure public URLs
- Both provide TLS encryption
- Authentication handled by tunnel providers
macOS App Privileges:
- Hardened runtime with specific entitlements
- Allows unsigned executable memory (for Bun)
- Allows DYLD environment variables
- Code signed with Developer ID
- Notarized for Gatekeeper approval
Data Protection:
- No persistent storage of terminal content
- Session recordings stored temporarily
- Passwords in Keychain with access control
- No telemetry or analytics
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ Created │ --> │ Active │ --> │ Exited │ --> │ Cleaned │
└─────────┘ └─────────┘ └─────────┘ └─────────┘
TypeScript Definition (web/src/server/pty/types.ts):
export interface Session {
id: string;
pid: number;
command: string;
args: string[];
cwd: string;
startTime: number;
status: 'running' | 'exited';
exitCode?: number;
cols: number;
rows: number;
recordingPath?: string;
}Creation:
- Generate unique session ID (UUID)
- Spawn PTY process with command
- Initialize asciinema recording
- Register with SessionManager
- Return session details to client
Monitoring:
- Process exit detection
- Automatic status updates
- Resource usage tracking
- Idle timeout handling (optional)
Termination:
- SIGTERM to process group
- PTY cleanup
- Recording finalization
- WebSocket closure notification
- Memory cleanup
Installation:
The vt command is installed as a wrapper script that automatically prepends 'fwd' to commands when using the Bun server.
Script Location: /usr/local/bin/vt
#!/bin/bash
# VibeTunnel CLI wrapper for Bun server
exec /usr/local/bin/vibetunnel fwd "$@"Location: Embedded in app bundle, copied to /usr/local/bin/vibetunnel
Commands:
vibetunnel serve- Start server (used internally)vibetunnel fwd [command]- Forward terminal sessionvibetunnel version- Show version information
Command Parsing:
- Automatic 'fwd' prepending for vt wrapper
- Shell detection and setup
- Working directory preservation
- Environment variable handling
Session Creation Flow:
- Parse command-line arguments
- Ensure server is running
- Create session via API
- Open browser to session URL
- Return session information
Base URL: http://localhost:4020 (default)
Authentication: Optional HTTP Basic Auth
GET /api/health
{
"status": "ok",
"version": "1.0.0"
}GET /api/sessions
{
"sessions": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"command": "zsh",
"args": [],
"cwd": "/Users/username",
"startTime": 1704060000000,
"status": "running",
"cols": 80,
"rows": 24
}
]
}POST /api/sessions
// Request
{
"command": ["/bin/zsh", "-l"],
"workingDir": "/Users/username",
"name": "optional",
"cols": 80,
"rows": 24
}
// Response
{
"sessionId": "550e8400-e29b-41d4-a716-446655440000",
"createdAt": "2025-12-19T08:00:00.000Z"
}DELETE /api/sessions/:id
{
"success": true
}GET /api/sessions/:id/snapshot Returns current terminal buffer state for initial render
POST /api/sessions/:id/input Send keyboard input to terminal
POST /api/sessions/:id/resize
{
"cols": 120,
"rows": 40
}Endpoint: GET /ws (WebSocket upgrade)
Terminal transport uses binary WebSocket v3 framing (multiplexed sessions).
Details: docs/websocket.md.
Terminal transport is a binary protocol layered on a single WebSocket (/ws).
It multiplexes sessions and supports both:
- live PTY output (
STDOUT) - server-rendered previews / hard resync (
SNAPSHOT_VT, VT snapshot v1)
See docs/websocket.md (frame layout + message types).
- Client connects to
/ws(one socket). - Client sends
SUBSCRIBE(sessionId, flags)for each interested session. - Server streams
STDOUT/SNAPSHOT_VT/EVENTframes per subscription. - Client sends
INPUT_TEXT/INPUT_KEY/RESIZEframes back.
Server:
web/src/server/services/ws-v3-hub.ts(frame routing + subscriptions)web/src/server/services/cast-output-hub.ts(tails cast →STDOUT)web/src/server/services/terminal-manager.ts(VT snapshot v1 →SNAPSHOT_VT)
Web Client (web/src/client/components/vibe-terminal-buffer.ts):
- Consumes
STDOUTandSNAPSHOT_VTviaweb/src/client/services/terminal-socket-client.ts
iOS Client (ios/VibeTunnel/Services/BufferWebSocketClient.swift):
- Same
/wsv3 framing + VT snapshot v1 decoding
Components:
- Status icon indicating server state
- Quick access menu
- Session count display
- Settings access
- About/Help options
State Indicators:
- Gray: Server stopped
- Green: Server running
- Red: Error state
- Animated: Starting/stopping
General Tab:
- Server port configuration
- Launch at login toggle
- Show in Dock option
- Update channel selection
Dashboard Tab:
- Access mode (localhost/network)
- Password protection toggle
- Authentication settings
- Dashboard URL display
Advanced Tab:
- Cleanup on startup
- CLI tools installation
- Server console access
- Debug logging
Debug Tab (hidden by default):
- Server type display (Bun only)
- Console log viewer
- Diagnostic information
Storage: UserDefaults.standard
Key Settings:
serverPort: String = "4020"
dashboardAccessMode: String = "localhost"
dashboardPasswordEnabled: Bool = false
launchAtLogin: Bool = false
showDockIcon: Bool = false
cleanupOnStartup: Bool = trueDashboardKeychain Service:
- Stores dashboard password securely
- Uses kSecClassInternetPassword
- Server and port-specific storage
- Handles password updates/deletion
- App Launch: Load settings from UserDefaults
- Server Start: Pass configuration via CLI arguments
- Runtime Changes: Update server without restart where possible
- Password Changes: Clear server auth cache
Requirements:
- Xcode 16.0+
- macOS 14.0+ SDK
- Node.js 22.12+
- Bun runtime
Build Process:
# Complete build
cd mac && ./scripts/build.sh --configuration Release --sign
# Development build (with Poltergeist if available)
poltergeist # Automatic rebuilds on file changes
# Or manual build
cd mac && xcodebuild -project VibeTunnel.xcodeproj -scheme VibeTunnel -configuration Debug buildBuild Phases:
- Build Bun executable from web sources
- Compile Swift application
- Copy resources (Bun binary, web assets)
- Code sign application
- Create DMG for distribution
Entitlements (mac/VibeTunnel/VibeTunnel.entitlements):
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>Release Process:
- Build and sign application
- Create notarized DMG
- Generate Sparkle appcast
- Upload to GitHub releases
- Update appcast XML
Package Contents:
- ARM64-only binary (Apple Silicon required)
- Embedded Bun server executable
- Web assets and resources
- Sparkle update framework
Framework: Swift Testing (Swift 6)
Test Organization:
mac/VibeTunnelTests/
├── ServerManagerTests.swift
├── SessionMonitorTests.swift
├── TerminalManagerTests.swift
├── DashboardKeychainTests.swift
├── CLIInstallerTests.swift
├── NetworkUtilityTests.swift
└── Utilities/
├── TestTags.swift
├── TestFixtures.swift
└── MockHTTPClient.swift
Test Tags:
.critical- Core functionality.networking- Network operations.concurrency- Async operations.security- Security features
Framework: Vitest
Test Structure:
web/src/test/
├── e2e/
│ ├── hq-mode.e2e.test.ts
│ └── server-smoke.e2e.test.ts
├── setup.ts
└── test-utils.ts
Coverage Requirements:
- 80% line coverage
- 80% function coverage
- 80% branch coverage
Terminal I/O:
- Keystroke to display: < 50ms
- Binary buffer update: < 100ms
- WebSocket ping/pong: < 10ms
API Response Times:
- Session list: < 50ms
- Session creation: < 200ms
- Health check: < 10ms
Memory:
- macOS app idle: < 50MB
- Bun server idle: < 100MB
- Per session: < 10MB
- Buffer cache: 64KB per session
CPU:
- Idle: < 1%
- Active session: < 5%
- Multiple sessions: Linear scaling
Concurrent Sessions:
- Target: 50 simultaneous sessions
- Tested: 100+ sessions
- Graceful degradation
- Buffer pooling for efficiency
User Errors:
- Port already in use
- Invalid configuration
- Authentication failures
- Permission denied
System Errors:
- Server crash/restart
- PTY allocation failures
- Process spawn errors
- WebSocket disconnections
Server Crashes:
- Automatic restart by ServerManager
- Session state preserved in memory
- Client reconnection supported
- Graceful degradation
Client Disconnections:
- WebSocket auto-reconnect
- Exponential backoff
- Session state preserved
- Buffer replay on reconnect
Configuration:
- Update check interval: 24 hours
- Automatic download in background
- User prompt for installation
- Delta updates supported
Update Channels:
- Stable: Production releases
- Pre-release: Beta testing
- Check appcast.xml for updates
- Download update package
- Verify EdDSA signature
- Prompt user for installation
- Install and restart application
System Features:
- Launch at login via SMAppService
- Menu bar and Dock modes
- Notification Center support
- Keyboard shortcuts
- AppleScript support
Supported Terminals:
- Terminal.app (default)
- iTerm2
- Warp
- Alacritty
- Hyper
- kitty
Detection Method:
- Check bundle identifiers
- Verify app existence
- User preference storage
Format: Asciinema v2
Header:
{
"version": 2,
"width": 80,
"height": 24,
"timestamp": 1704060000,
"command": "/bin/zsh",
"title": "VibeTunnel Session"
}Events: Newline-delimited JSON
[0.123456, "o", "terminal output"]
[0.234567, "i", "keyboard input"]
Sessions are ephemeral and exist only in server memory. Recordings are stored temporarily in the system temp directory and cleaned up after 24 hours or on server restart with cleanup enabled.
VibeTunnel achieves its goal of simple, secure terminal access through a carefully architected system combining native macOS development with modern web technologies. The single Node.js/Bun server implementation provides excellent performance while maintaining simplicity.
The binary buffer protocol ensures efficient terminal streaming, while the clean architectural boundaries enable independent evolution of components. With careful attention to macOS platform conventions and user expectations, VibeTunnel delivers a professional-grade solution for terminal access needs. This specification serves as the authoritative reference for understanding, maintaining, and extending the VibeTunnel project.