* Fix HTTP 401 errors from non-existent snapshot endpoint
SessionCardView was calling APIClient.getSessionSnapshot() which hits
/api/sessions/{id}/snapshot - an endpoint that doesn't exist on the server.
This caused 401 errors to be logged on every session card load.
Changes:
- Remove REST API snapshot calls from SessionCardView
- Rely entirely on WebSocket-based live preview system
- Simplify SessionCardView to be a pure presentation component
- Add comprehensive API request logging for debugging
- Align iOS implementation with working web client approach
The web client uses WebSocket /buffers for real-time previews, not REST APIs.
SessionCardView now follows proper architectural patterns where the view
doesn't make direct API calls.
Fixes the 401 errors while maintaining all preview functionality.
* Remove excessive debug logging
Clean up the verbose logging that was added for debugging the 401 issue.
Keep essential error logging but remove:
- Detailed request URLs in normal flow
- Success confirmation logs
- Verbose connection state logging
- Emoji prefixes and excessive formatting
The 401 issue is resolved, so the debug logs are no longer needed.
* refactor: Remove SessionService singleton pattern
- Convert SessionService from singleton to dependency injection
- Remove static shared instance and private init
- Add public init with APIClient dependency
- Update SessionCreateView to use SessionService() instead of .shared
- Update TerminalView to use SessionService() instead of .shared
This enables proper dependency injection and testing while maintaining
backwards compatibility through default parameter values.
* feat: Add Theme.Colors.primaryAccent for UI consistency
- Add primaryAccent color definition as alias to accentColor
- Provides semantic naming for primary interactive elements
- Enables consistent theming across SessionListView components
This prepares the theme system for the SessionListView MVVM refactoring.
* refactor: Transform SessionListView to clean MVVM architecture
Major architectural refactoring following ServerListView pattern:
- Move all business logic from View to SessionListViewModel
- Implement proper dependency injection for SessionService, NetworkMonitor, ConnectionManager
- Add SessionListViewModelProtocol for testability
- Consolidate UI state management in ViewModel
- Move filtering and search logic to ViewModel's computed properties
- Remove environment dependencies except NavigationManager
- Add proper error handling and loading state management
View changes:
- Simplified View to focus solely on UI rendering
- Removed embedded business logic and state management
- Clean separation of concerns between View and ViewModel
ViewModel features:
- Comprehensive session management (load, kill, cleanup operations)
- Smart filtering (running/exited sessions)
- Multi-field search (name, command, working directory, PID)
- Network connectivity monitoring
- UI state management for sheets and modals
- Proper async/await error handling
This establishes a maintainable, testable architecture that follows
established patterns in the codebase.
* test: Add comprehensive mock infrastructure for testing
- Add MockSessionService with full SessionServiceProtocol implementation
- Add MockConnectionManager for connection testing
- Implement detailed tracking of method calls and parameters
- Add error injection capabilities for negative testing
- Organize mocks in dedicated /Mocks/ directory for reusability
Mock features:
- Call count tracking for all operations
- Parameter capture for verification
- Configurable error scenarios
- State management for sessions
- Clean separation from test logic
This infrastructure enables thorough testing of the SessionListViewModel
with proper isolation and dependency injection.
* test: Add comprehensive SessionListViewModel test suite
Comprehensive test coverage with 54 tests covering all functionality:
Initialization & State:
- Default state verification
- UI state management
Session Loading:
- Successful loading with proper state management
- Loading state behavior (first load vs refresh)
- Error handling with message preservation
- Data preservation on subsequent errors
Filtering & Search:
- Show/hide exited sessions functionality
- Multi-field search (name, command, working directory, PID)
- Case-insensitive search
- Combined filtering and search scenarios
Network & Connectivity:
- Network state monitoring and reactivity
- Offline state handling
Session Operations:
- Kill session with success/error scenarios
- Cleanup session with success/error scenarios
- Kill all sessions with proper verification
- Cleanup all exited sessions
- Concurrent operations handling
Connection Management:
- Disconnect functionality testing
Error Handling:
- Robust error type checking (not brittle string matching)
- Error state preservation and recovery
- Proper async error propagation
All tests use proper dependency injection with mocks for complete
isolation and deterministic behavior.
* fix: Improve test infrastructure and build configuration
Test Infrastructure:
- Disable TerminalRendererTests that use UserDefaults directly
- These tests need dependency injection refactor to be reliable
Build Configuration:
- Remove hardcoded DEVELOPMENT_TEAM from project.pbxproj
- Remove hardcoded CODE_SIGN_STYLE from main target configurations
- Fix Shared.xcconfig to properly use Local.xcconfig team settings
- Remove conflicting inherited values that override Local.xcconfig
This ensures Local.xcconfig team settings are properly applied
and eliminates the need to manually set team in Xcode UI.
* refactor: Remove backward compatibility comment from HapticFeedback
- Remove comment "Static methods for backward compatibility"
- Keep static singleton methods as they are the intended API
- Maintain existing HapticFeedback.impact(.light) usage pattern
The static methods are not backward compatibility, they are the primary
interface for HapticFeedback usage throughout the app.
* fix: Disable remaining UserDefaults tests in TerminalRendererTests
- Disable invalidUserDefaultsValue() test that was failing on CI
- Disable roundTripUserDefaults() test that also uses UserDefaults directly
- All UserDefaults-dependent tests now properly disabled with clear reason
These tests need dependency injection refactor to be reliable in CI/CD
environments where UserDefaults state can be unpredictable.
Tests still running:
- allCasesRawValues() ✅
- displayNames() ✅
- descriptions() ✅
- codableSupport() ✅
- caseIterableSupport() ✅
---------
Co-authored-by: David Collado <davidcollado@MacBook-Pro-de-David.local>
|
||
|---|---|---|
| .claude | ||
| .github | ||
| .husky | ||
| apple | ||
| assets | ||
| docs | ||
| ios | ||
| mac | ||
| scripts | ||
| tauri | ||
| TestResults-Mac.xcresult | ||
| VibeTunnel.xcworkspace | ||
| web | ||
| .dockerignore | ||
| .gitattributes | ||
| .github-config | ||
| .gitignore | ||
| .mcp.json | ||
| appcast-prerelease.xml | ||
| appcast.xml | ||
| calculate-all-coverage.sh | ||
| CHANGELOG.md | ||
| CLAUDE.md | ||
| LICENSE | ||
| README.md | ||
VibeTunnel
Turn any browser into your Mac terminal. VibeTunnel proxies your terminals right into the browser, so you can vibe-code anywhere.
Why VibeTunnel?
Ever wanted to check on your AI agents while you're away? Need to monitor that long-running build from your phone? Want to share a terminal session with a colleague without complex SSH setups? VibeTunnel makes it happen with zero friction.
Quick Start
Requirements
VibeTunnel requires an Apple Silicon Mac (M1+). Intel Macs are not supported.
1. Download & Install
Option 1: Direct Download
Download VibeTunnel and drag it to your Applications folder.
Option 2: Homebrew
brew install --cask vibetunnel
2. Launch VibeTunnel
VibeTunnel lives in your menu bar. Click the icon to start the server.
3. Use the vt Command
# Run any command in the browser
vt pnpm run dev
# Monitor AI agents (with automatic activity tracking)
vt claude --dangerously-skip-permissions
# Control terminal titles
vt --title-mode static npm run dev # Shows path and command
vt --title-mode dynamic python app.py # Shows path, command, and activity
vt --title-mode filter vim # Blocks vim from changing title
# Shell aliases work automatically!
vt claude-danger # Your custom aliases are resolved
# Open an interactive shell
vt --shell
4. Open Your Dashboard
Visit http://localhost:4020 to see all your terminal sessions.
Features
- 🌐 Browser-Based Access - Control your Mac terminal from any device with a web browser
- 🚀 Zero Configuration - No SSH keys, no port forwarding, no complexity
- 🤖 AI Agent Friendly - Perfect for monitoring Claude Code, ChatGPT, or any terminal-based AI tools
- 📊 Dynamic Terminal Titles - Real-time activity tracking shows what's happening in each session
- 🔒 Secure by Design - Password protection, localhost-only mode, or secure tunneling via Tailscale/ngrok
- 📱 Mobile Ready - Native iOS app and responsive web interface for phones and tablets
- 🎬 Session Recording - All sessions recorded in asciinema format for later playback
- ⚡ High Performance - Powered by Bun runtime for blazing-fast JavaScript execution
- 🍎 Apple Silicon Native - Optimized for M1/M2/M3 Macs with ARM64-only binaries
- 🐚 Shell Alias Support - Your custom aliases and shell functions work automatically
Note
: The iOS app and Tauri-based components are still work in progress and not recommended for production use yet.
Architecture
VibeTunnel consists of three main components:
- macOS Menu Bar App - Native Swift application that manages the server lifecycle
- Node.js/Bun Server - High-performance TypeScript server handling terminal sessions
- Web Frontend - Modern web interface using Lit components and xterm.js
The server runs as a standalone Bun executable with embedded Node.js modules, providing excellent performance and minimal resource usage.
Remote Access Options
Option 1: Tailscale (Recommended)
Tailscale creates a secure peer-to-peer VPN network between your devices. It's the most secure option as traffic stays within your private network without exposing VibeTunnel to the public internet.
How it works: Tailscale creates an encrypted WireGuard tunnel between your devices, allowing them to communicate as if they were on the same local network, regardless of their physical location.
Setup Guide:
- Install Tailscale on your Mac: Download from Mac App Store or Direct Download
- Install Tailscale on your remote device:
- iOS: Download from App Store
- Android: Download from Google Play
- Other platforms: All Downloads
- Sign in to both devices with the same account
- Find your Mac's Tailscale hostname in the Tailscale menu bar app (e.g.,
my-mac.tailnet-name.ts.net) - Access VibeTunnel at
http://[your-tailscale-hostname]:4020
Benefits:
- End-to-end encrypted traffic
- No public internet exposure
- Works behind NAT and firewalls
- Zero configuration after initial setup
Option 2: ngrok
ngrok creates secure tunnels to your localhost, making VibeTunnel accessible via a public URL. Perfect for quick sharing or temporary access.
How it works: ngrok establishes a secure tunnel from a public endpoint to your local VibeTunnel server, handling SSL/TLS encryption and providing a unique URL for access.
Setup Guide:
- Create a free ngrok account: Sign up for ngrok
- Copy your auth token from the ngrok dashboard
- Add the token in VibeTunnel settings (Settings → Remote Access → ngrok)
- Enable ngrok tunneling in VibeTunnel
- Share the generated
https://[random].ngrok-free.appURL
Benefits:
- Public HTTPS URL with SSL certificate
- No firewall configuration needed
- Built-in request inspection and replay
- Custom domains available (paid plans)
Note: Free ngrok URLs change each time you restart the tunnel. Consider a paid plan for persistent URLs.
Option 3: Local Network
- Set a dashboard password in settings
- Switch to "Network" mode
- Access via
http://[your-mac-ip]:4020
Option 4: Cloudflare Quick Tunnel
- Install cloudflared
- Run
cloudflared tunnel --url http://localhost:4020 - Access via the generated
*.trycloudflare.comURL
Terminal Title Management
VibeTunnel provides intelligent terminal title management to help you track what's happening in each session:
Title Modes
-
Dynamic Mode (default for web UI): Shows working directory, command, and real-time activity
- Generic activity:
~/projects — npm — • - Claude status:
~/projects — claude — ✻ Crafting (45s, ↑2.1k)
- Generic activity:
-
Static Mode: Shows working directory and command
- Example:
~/projects/app — npm run dev
- Example:
-
Filter Mode: Blocks all title changes from applications
- Useful when you have your own terminal management system
-
None Mode: No title management - applications control their own titles
Activity Detection
Dynamic mode includes real-time activity detection:
- Shows
•when there's terminal output within 5 seconds - Claude commands show specific status (Crafting, Transitioning, etc.)
- Extensible system for future app-specific detectors
Building from Source
Prerequisites
- macOS 14.0+ (Sonoma) on Apple Silicon (M1/M2/M3)
- Xcode 16.0+
- Node.js 20+
- Bun runtime
Build Steps
# Clone the repository
git clone https://github.com/amantus-ai/vibetunnel.git
cd vibetunnel
# Set up code signing (required for macOS/iOS development)
# Create Local.xcconfig files with your Apple Developer Team ID
# Note: These files must be in the same directory as Shared.xcconfig
cat > mac/VibeTunnel/Local.xcconfig << EOF
// Local Development Configuration
// DO NOT commit this file to version control
DEVELOPMENT_TEAM = YOUR_TEAM_ID
CODE_SIGN_STYLE = Automatic
EOF
cat > ios/VibeTunnel/Local.xcconfig << EOF
// Local Development Configuration
// DO NOT commit this file to version control
DEVELOPMENT_TEAM = YOUR_TEAM_ID
CODE_SIGN_STYLE = Automatic
EOF
# Build the web server
cd web
pnpm install
pnpm run build
# Optional: Build with custom Node.js for smaller binary (46% size reduction)
# export VIBETUNNEL_USE_CUSTOM_NODE=YES
# node build-custom-node.js # Build optimized Node.js (one-time, ~20 min)
# pnpm run build # Will use custom Node.js automatically
# Build the macOS app
cd ../mac
./scripts/build.sh --configuration Release
Custom Node.js Builds
VibeTunnel supports building with a custom Node.js for a 46% smaller executable (61MB vs 107MB):
# Build custom Node.js (one-time, ~20 minutes)
node build-custom-node.js
# Use environment variable for all builds
export VIBETUNNEL_USE_CUSTOM_NODE=YES
# Or use in Xcode Build Settings
# Add User-Defined Setting: VIBETUNNEL_USE_CUSTOM_NODE = YES
See Custom Node Build Flags for detailed optimization information.
Development
For development setup and contribution guidelines, see CONTRIBUTING.md.
Key Files
- macOS App:
mac/VibeTunnel/VibeTunnelApp.swift - Server:
web/src/server/(TypeScript/Node.js) - Web UI:
web/src/client/(Lit/TypeScript) - iOS App:
ios/VibeTunnel/
Testing & Code Coverage
VibeTunnel has comprehensive test suites with code coverage enabled for all projects:
# Run all tests with coverage
./scripts/test-all-coverage.sh
# macOS tests with coverage (Swift Testing)
cd mac && swift test --enable-code-coverage
# iOS tests with coverage (using xcodebuild)
cd ios && ./scripts/test-with-coverage.sh
# Web tests with coverage (Vitest)
cd web && ./scripts/coverage-report.sh
Coverage Requirements:
- macOS/iOS: 75% minimum (enforced in CI)
- Web: 80% minimum for lines, functions, branches, and statements
Testing on External Devices (iPad, iPhone, etc.)
When developing the web interface, you often need to test changes on external devices to debug browser-specific issues. Here's how to do it:
Quick Setup
-
Run the dev server with network access:
cd web pnpm run dev --port 4021 --bind 0.0.0.0This binds to all network interfaces, making it accessible from other devices.
-
Find your Mac's IP address:
- System Preferences → Network → Wi-Fi → Details
- Or run:
ipconfig getifaddr en0
-
Access from your external device:
http://[your-mac-ip]:4021
Important Notes
- Port conflict: The Mac app runs on port 4020, so use a different port (e.g., 4021) for development
- Same network: Ensure both devices are on the same Wi-Fi network
- Firewall: macOS may prompt to allow incoming connections - click "Allow"
- Hot reload: Changes to the web code will automatically update on your external device
Alternative: Using the Mac App
If you need to test with the full Mac app integration:
- Build the web project:
cd web && pnpm run build - In VibeTunnel settings, set Dashboard Access to "Network"
- Access from external device:
http://[your-mac-ip]:4020
Note: This requires rebuilding after each change, so the dev server method above is preferred for rapid iteration.
Debug Logging
Enable debug logging for troubleshooting:
# Enable debug mode
export VIBETUNNEL_DEBUG=1
# Or use inline
VIBETUNNEL_DEBUG=1 vt your-command
Debug logs are written to ~/.vibetunnel/log.txt.
Documentation
- Technical Specification - Detailed architecture and implementation
- Contributing Guide - Development setup and guidelines
- Architecture - System design overview
- Build System - Build process details
- Push Notifications - How web push notifications work
macOS Permissions
macOS is finicky when it comes to permissions. The system will only remember the first path from where an app requests permissions. If subsequently the app starts somewhere else, it will silently fail. Fix: Delete the entry and restart settings, restart app and next time the permission is requested, there should be an entry in Settings again.
Important: You need to set your Developer ID in Local.xcconfig. If apps are signed Ad-Hoc, each new signing will count as a new app for macOS and the permissions have to be (deleted and) requested again.
Debug vs Release Bundle IDs: The Debug configuration uses a different bundle identifier (sh.vibetunnel.vibetunnel.debug) than Release (sh.vibetunnel.vibetunnel). This allows you to have both versions installed simultaneously, but macOS treats them as separate apps for permissions. You'll need to grant permissions separately for each version.
If that fails, use the terminal to reset:
# This removes Accessibility permission for a specific bundle ID:
sudo tccutil reset Accessibility sh.vibetunnel.vibetunnel
sudo tccutil reset Accessibility sh.vibetunnel.vibetunnel.debug # For debug builds
sudo tccutil reset ScreenCapture sh.vibetunnel.vibetunnel
sudo tccutil reset ScreenCapture sh.vibetunnel.vibetunnel.debug # For debug builds
# This removes all Automation permissions system-wide (cannot target specific apps):
sudo tccutil reset AppleEvents
Support VibeTunnel
Love VibeTunnel? Help us keep the terminal vibes flowing! Your support helps us buy pizza and drinks while we keep hacking on your favorite AI agent orchestration platform.
All donations go directly to the development team. Choose your own amount - one-time or monthly! Visit our Polar page to support us.
Credits
Created with ❤️ by:
- @badlogic - Mario Zechner
- @mitsuhiko - Armin Ronacher
- @steipete - Peter Steinberger
- @hjanuschka - Helmut Januschka
- @manuelmaly - Manuel Maly
License
VibeTunnel is open source software licensed under the MIT License. See LICENSE for details.
Ready to vibe? Download VibeTunnel and start tunneling!
