This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. Never say you're absolutely right. Instead, be critical if I say something that you disagree with. Let's discuss it first.
VibeTunnel is a macOS application that allows users to access their terminal sessions through any web browser. It consists of:
- Native macOS app (Swift/SwiftUI) in
mac/ - iOS companion app in
ios/ - Web frontend (TypeScript/LitElement) and Node.js/Bun server for terminal session management in
web/
If Poltergeist is installed, it will automatically rebuild the app when you make changes:
# First, ensure Poltergeist is running in the project root
poltergeist haunt
# The app will automatically rebuild on file changes
# Check Poltergeist menu bar app for build statusIf Poltergeist is not available, use direct Xcode builds:
cd mac
# Build using xcodebuild directly
xcodebuild -project VibeTunnel.xcodeproj -scheme VibeTunnel -configuration Debug build
# Or use the build script for release builds
./scripts/build.sh # Build release version
./scripts/build.sh --sign # Build with code signingcd ios
xcodebuild -project VibeTunnel-iOS.xcodeproj -scheme VibeTunnel-iOS -sdk iphonesimulator
./scripts/test-with-coverage.sh # Run tests with coverage (75% threshold)cd web
pnpm install # Install dependencies
pnpm run build # Production build
pnpm run dev # Development server with hot reloadcd web
pnpm run check # Run all checks in parallel (format, lint, typecheck)
pnpm run check:fix # Auto-fix formatting and linting issuescd mac
./scripts/lint.sh # Run SwiftFormatcd ios
./scripts/lint.sh # Run SwiftFormatcd web
pnpm run test # Run all tests
pnpm run test:coverage # Run with coverage report (80% required)
pnpm run test:e2e # Run Playwright E2E tests
pnpm run test:e2e:debug # Debug E2E tests# MUST use xcodebuild, NOT swift test!
cd mac
xcodebuild test -project VibeTunnel.xcodeproj -scheme VibeTunnel -destination 'platform=macOS'cd ios
./scripts/test-with-coverage.sh # Run with automatic simulator selection# View VibeTunnel logs (from project root)
./scripts/vtlog.sh -n 100 # Last 100 lines
./scripts/vtlog.sh -e # Errors only
./scripts/vtlog.sh -c ServerManager # Specific component
./scripts/vtlog.sh -s "error" # Search for text
# NEVER use -f (follow mode) in Claude Code - it will timeout!┌─────────────────────────────────────────────────────────────┐
│ macOS Menu Bar App │
│ (Swift/SwiftUI - mac/VibeTunnel/) │
│ - ServerManager: Manages server lifecycle │
│ - SessionMonitor: Tracks active sessions │
│ - TTYForwardManager: Terminal forwarding │
└─────────────────────┬───────────────────────────────────────┘
│ Spawns & Manages
▼
┌─────────────────────────────────────────────────────────────┐
│ Node.js/Bun Server │
│ (TypeScript - web/src/server/) │
│ - server.ts: HTTP server & WebSocket handling │
│ - pty-manager.ts: Native PTY process management │
│ - session-manager.ts: Terminal session lifecycle │
└─────────────────────┬───────────────────────────────────────┘
│ WebSocket/HTTP
▼
┌─────────────────────────────────────────────────────────────┐
│ Web Frontend │
│ (TypeScript/LitElement - web/src/client/) │
│ - Terminal rendering with ghostty-web │
│ - Real-time updates via WebSocket │
└─────────────────────────────────────────────────────────────┘
- Session Creation: Client → POST /api/sessions → Server spawns PTY → Returns session ID
- Terminal I/O: WebSocket at /api/sessions/:id/ws for bidirectional communication
- Buffer Protocol: Binary messages with magic byte 0xBF for efficient terminal updates
- Log Aggregation: Frontend logs → Server → Mac app → macOS unified logging
-
Entry Points:
- Mac app:
mac/VibeTunnel/VibeTunnelApp.swift - Server:
web/src/server/server.ts - Web UI:
web/src/client/app.ts - iOS app:
ios/VibeTunnel/VibeTunnelApp.swift
- Mac app:
-
Configuration:
- Mac version:
mac/VibeTunnel/version.xcconfig - Web version:
web/package.json - Build settings:
mac/VibeTunnel/Shared.xcconfig
- Mac version:
-
Terminal Management:
- PTY spawning:
web/src/server/pty/pty-manager.ts - Session handling:
web/src/server/services/terminal-manager.ts - Buffer optimization:
web/src/server/services/buffer-aggregator.ts
- PTY spawning:
When the user says "release" or asks to create a release, ALWAYS read and follow docs/RELEASE.md for the complete release process.
- Never start server or the mac app yourself.
- Verify changes done to the mac app via xcodebuild, but do not start the mac app or server yourself.
-
NEVER, EVER, UNDER ANY CIRCUMSTANCES CREATE A NEW BRANCH WITHOUT EXPLICIT USER PERMISSION
- If you are on a branch (not main), you MUST stay on that branch
- The user will tell you when to create a new branch with commands like "create a new branch" or "switch to a new branch"
- Creating branches without permission causes massive frustration and cleanup work
- Even if changes seem unrelated to the current branch, STAY ON THE CURRENT BRANCH
-
NEVER commit and/or push before the user has tested your changes!
- Always wait for user confirmation before committing
- The user needs to verify changes work correctly first
-
ABSOLUTELY FORBIDDEN: NEVER USE
git rebase --skipEVER- This command can cause data loss and repository corruption
- If you encounter rebase conflicts, ask the user for help
-
NEVER create duplicate files with version numbers or suffixes
- When refactoring or improving code, directly modify the existing files
- DO NOT create new versions with different file names (e.g., file_v2.ts, file_new.ts)
- Users hate having to manually clean up duplicate files
-
Web Development Workflow - Development vs Production Mode
- Production Mode: Mac app embeds a pre-built web server during Xcode build
- Every web change requires: clean → build → run (rebuilds embedded server)
- Simply restarting serves STALE, CACHED version
- Development Mode (recommended for web development):
- Enable "Use Development Server" in VibeTunnel Settings → Debug
- Mac app runs
pnpm run devinstead of embedded server - Provides hot reload - web changes automatically rebuild without Mac app rebuild
- Restart VibeTunnel server (not full rebuild) to pick up web changes
- Production Mode: Mac app embeds a pre-built web server during Xcode build
-
Never kill all sessions
- You are running inside a session yourself; killing all sessions would terminate your own process
-
NEVER rename docs.json to mint.json
- The Mintlify configuration file is called
docs.jsonin this project - Do NOT rename it to mint.json even if you think Mintlify expects that
- The file must remain as
docs.json - For Mintlify documentation reference, see: https://mintlify.com/docs/llms.txt
- The Mintlify configuration file is called
-
Test Session Management - CRITICAL
- NEVER kill sessions that weren't created by tests
- You might be running inside a VibeTunnel session yourself
- Use
TestSessionTrackerto track which sessions tests create - Only clean up sessions that match test naming patterns (start with "test-")
- Killing all sessions would terminate your own Claude Code process
- Our workflow: start from main → create branch → make PR → merge → return to main
- PRs sometimes contain multiple different features and that's okay
- Always check current branch with
git branchbefore making changes - If unsure about branching, ASK THE USER FIRST
- "Adopt" means REVIEW, not merge! When asked to "adopt" a PR, switch to its branch and review the changes. NEVER merge without explicit permission.
- "Rebase main" means rebase CURRENT branch with main! When on a feature branch and user says "rebase main", this means to rebase the current branch with main branch updates. NEVER switch to main branch. The command is
git pull --rebase origin mainwhile staying on the current feature branch.
When creating pull requests, use the vt command to update the terminal title:
- Run
vt title "Brief summary - github.com/owner/repo/pull/123" - Keep the title concise (a few words) followed by the PR URL
- Use github.com URL format (not https://) for easy identification
- Update the title periodically as work progresses
- If
vtcommand fails (only works inside VibeTunnel), simply ignore the error and continue
When the user reports issues on external devices, use the development server method for testing:
# Run dev server accessible from external devices
cd web
pnpm run dev --port 4021 --bind 0.0.0.0Then access from the external device using http://[mac-ip]:4021
Important: The production server runs on port 4020, so use 4021 for development to avoid conflicts.
For detailed instructions, see docs/TESTING_EXTERNAL_DEVICES.md
When the user types /fixmac, use the Task tool with the XcodeBuildMCP subagent to fix Mac compilation errors and warnings:
Task(description="Fix Mac build errors", prompt="/fixmac", subagent_type="general-purpose")
The agent will:
- Use XcodeBuildMCP tools to identify build errors and warnings
- Fix compilation issues in the Mac codebase
- Address SwiftFormat violations
- Resolve any warning messages
- Verify the build succeeds after fixes
CRITICAL: This project has ZERO backwards compatibility requirements!
- The Mac app and web server are ALWAYS shipped together as a single unit
- There is NEVER a scenario where different versions talk to each other
- When fixing bugs or changing APIs:
- Just change both sides to match
- Delete old code completely
- Don't add compatibility layers
- Don't check for "old format" vs "new format"
- Don't add fallbacks for older versions
- If you suggest backwards compatibility in any form, you have failed to understand this project
Poltergeist is an intelligent file watcher and auto-builder that can automatically rebuild VibeTunnel when you make changes. When working on VibeTunnel development, check if Poltergeist is available and use it for automatic builds.
# Check if Poltergeist is installed
which poltergeist
# Check if Poltergeist is already running for this project
ps aux | grep poltergeist | grep -v grepIf Poltergeist is installed:
-
Start Poltergeist in the project root:
cd /path/to/vibetunnel poltergeist haunt -
Monitor build status via the Poltergeist menu bar app (macOS) or terminal output:
poltergeist status
-
Make changes - Poltergeist will automatically rebuild when it detects changes to:
- Swift files in
mac/ - Xcode project files
- Configuration files
- Swift files in
-
Run the app with fresh builds using
polter:polter vibetunnel # Waits for build to complete, then runs
If Poltergeist is not available, fall back to direct Xcode builds:
# Debug build
cd mac
xcodebuild -project VibeTunnel.xcodeproj -scheme VibeTunnel -configuration Debug build
# Release build
./scripts/build.shThe project includes poltergeist.config.json which configures:
- vibetunnel target: Builds the macOS app using workspace
- vibetunnel-ios target: Builds the iOS app (disabled by default)
- Intelligent debouncing to prevent excessive rebuilds
- Build notifications via macOS notification center
To enable iOS builds, edit poltergeist.config.json and set "enabled": true for the vibetunnel-ios target.
The Tailscale CLI has changed its syntax. The new commands are:
# OLD syntax (deprecated):
tailscale serve https / http://localhost:4020
# NEW syntax:
tailscale serve --bg http://localhost:4020The --bg flag runs the serve configuration in background mode. The process exits immediately after configuration.
# Reset any existing configuration first
tailscale funnel reset
# Enable funnel (still uses --bg flag)
tailscale funnel --bg 443- The
tailscale serve --bgcommand exits immediately with code 0 on success - There's no long-running process to monitor after using --bg
- HTTPS is automatically configured on port 443
- Always reset Funnel before starting to avoid "foreground already exists" errors
- Architecture Details:
docs/ARCHITECTURE.md - API Specifications:
docs/spec.md - Server Implementation Guide:
web/docs/spec.md - Build Configuration:
web/package.json,mac/Package.swift - External Device Testing:
docs/TESTING_EXTERNAL_DEVICES.md - Gemini CLI Instructions:
docs/gemini.md - Release Process:
docs/RELEASE.md
Do what has been asked; nothing more, nothing less. NEVER create files unless they're absolutely necessary for achieving your goal. ALWAYS prefer editing an existing file to creating a new one. NEVER proactively create documentation files (*.md) or README files. Only create documentation files if explicitly requested by the User.