# AGENT Source: https://docs.vibetunnel.sh/AGENT # AGENT.md ## Build/Test Commands * **Web**: `cd web && pnpm run check` (format, lint, typecheck), `pnpm run dev` (port 4020), `pnpm run test` (Vitest) * **Mac**: `cd mac && ./scripts/build.sh` (Release), `./scripts/build.sh --configuration Debug`, `./scripts/lint.sh` (SwiftFormat + SwiftLint) * **Single test**: `cd web && pnpm run test path/to/test.spec.ts` or `xcodebuild test -project VibeTunnel-Mac.xcodeproj -scheme VibeTunnel-Mac` ## Architecture * **Native macOS** (Swift/SwiftUI) in `mac/` - main app + terminal session management * **iOS companion** in `ios/` - mobile interface * **Web stack** in `web/` - TypeScript/LitElement frontend + Node.js/Bun server for terminal sessions * **Key APIs**: `/api/sessions` (create), `/api/sessions/:id/input` (send), `/api/sessions/:id/stream` (SSE output), `/buffers` (WebSocket binary) * **Entry points**: `mac/VibeTunnel/VibeTunnelApp.swift`, `web/src/client/app.ts`, `web/src/server/server.ts` ## Code Style * **TypeScript**: camelCase vars/functions, PascalCase classes/interfaces, UPPER\_SNAKE\_CASE constants, `.js` imports, JSDoc, singleton exports * **Swift**: PascalCase types, camelCase properties/methods, `// MARK: -` sections, `@Observable` models, `@MainActor` UI, protocol-oriented design * **Imports**: System frameworks first (Swift), external libs first (TS), relative paths with `../`, specific imports preferred * **Error handling**: Try-catch with logging (TS), custom error enums with `LocalizedError` (Swift) * **No backwards compatibility** - Mac app and web server ship together, change both sides simultaneously # AGENTS Source: https://docs.vibetunnel.sh/AGENTS # VibeTunnel Notes * Mac app and server ship together; no backwards compatibility needed. * Web: session header truncation for mobile overflow (#561). * iOS tasks: use iOS 26 simulator. # CLAUDE Source: https://docs.vibetunnel.sh/CLAUDE # CLAUDE.md 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. ## Project Overview 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/` ## Common Development Commands ### Building the Project #### macOS App with Poltergeist (Recommended if installed) If Poltergeist is installed, it will automatically rebuild the app when you make changes: ```bash theme={null} # 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 status ``` #### macOS App without Poltergeist (Fallback) If Poltergeist is not available, use direct Xcode builds: ```bash theme={null} 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 signing ``` #### iOS App ```bash theme={null} cd ios xcodebuild -project VibeTunnel-iOS.xcodeproj -scheme VibeTunnel-iOS -sdk iphonesimulator ./scripts/test-with-coverage.sh # Run tests with coverage (75% threshold) ``` #### Web Frontend ```bash theme={null} cd web pnpm install # Install dependencies pnpm run build # Production build pnpm run dev # Development server with hot reload ``` ### Code Quality Commands #### Web (MUST run before committing) ```bash theme={null} cd web pnpm run check # Run all checks in parallel (format, lint, typecheck) pnpm run check:fix # Auto-fix formatting and linting issues ``` #### macOS ```bash theme={null} cd mac ./scripts/lint.sh # Run SwiftFormat ``` #### iOS ```bash theme={null} cd ios ./scripts/lint.sh # Run SwiftFormat ``` ### Testing Commands #### Web Tests ```bash theme={null} cd 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 ``` #### macOS Tests ```bash theme={null} # MUST use xcodebuild, NOT swift test! cd mac xcodebuild test -project VibeTunnel.xcodeproj -scheme VibeTunnel -destination 'platform=macOS' ``` #### iOS Tests ```bash theme={null} cd ios ./scripts/test-with-coverage.sh # Run with automatic simulator selection ``` ### Debugging and Logs ```bash theme={null} # 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! ``` ## High-Level Architecture ### Component Overview ``` ┌─────────────────────────────────────────────────────────────┐ │ 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 │ └─────────────────────────────────────────────────────────────┘ ``` ### Key Communication Flows 1. **Session Creation**: Client → POST /api/sessions → Server spawns PTY → Returns session ID 2. **Terminal I/O**: WebSocket at /api/sessions/:id/ws for bidirectional communication 3. **Buffer Protocol**: Binary messages with magic byte 0xBF for efficient terminal updates 4. **Log Aggregation**: Frontend logs → Server → Mac app → macOS unified logging ### Critical File Locations * **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` * **Configuration**: * Mac version: `mac/VibeTunnel/version.xcconfig` * Web version: `web/package.json` * Build settings: `mac/VibeTunnel/Shared.xcconfig` * **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` ## Critical Development Rules ### Release Process When the user says "release" or asks to create a release, ALWAYS read and follow `docs/RELEASE.md` for the complete release process. ### ABSOLUTE CARDINAL RULES - VIOLATION MEANS IMMEDIATE FAILURE * 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. 1. **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 2. **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 3. **ABSOLUTELY FORBIDDEN: NEVER USE `git rebase --skip` EVER** * This command can cause data loss and repository corruption * If you encounter rebase conflicts, ask the user for help 4. **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 5. **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 dev` instead 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 6. **Never kill all sessions** * You are running inside a session yourself; killing all sessions would terminate your own process 7. **NEVER rename docs.json to mint.json** * The Mintlify configuration file is called `docs.json` in 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](https://mintlify.com/docs/llms.txt) 8. **Test Session Management - CRITICAL** * NEVER kill sessions that weren't created by tests * You might be running inside a VibeTunnel session yourself * Use `TestSessionTracker` to 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 ### Git Workflow Reminders * 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 branch` before 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 main` while staying on the current feature branch. ### Terminal Title Management with VT 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 `vt` command fails (only works inside VibeTunnel), simply ignore the error and continue ## Testing on External Devices (iPad, Safari, etc.) When the user reports issues on external devices, use the development server method for testing: ```bash theme={null} # Run dev server accessible from external devices cd web pnpm run dev --port 4021 --bind 0.0.0.0 ``` Then 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` ## Slash Commands ### /fixmac Command 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: 1. Use XcodeBuildMCP tools to identify build errors and warnings 2. Fix compilation issues in the Mac codebase 3. Address SwiftFormat violations 4. Resolve any warning messages 5. Verify the build succeeds after fixes ## NO BACKWARDS COMPATIBILITY - EVER! **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 Integration 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. ### Checking for Poltergeist ```bash theme={null} # Check if Poltergeist is installed which poltergeist # Check if Poltergeist is already running for this project ps aux | grep poltergeist | grep -v grep ``` ### Using Poltergeist for Development If Poltergeist is installed: 1. **Start Poltergeist** in the project root: ```bash theme={null} cd /path/to/vibetunnel poltergeist haunt ``` 2. **Monitor build status** via the Poltergeist menu bar app (macOS) or terminal output: ```bash theme={null} poltergeist status ``` 3. **Make changes** - Poltergeist will automatically rebuild when it detects changes to: * Swift files in `mac/` * Xcode project files * Configuration files 4. **Run the app** with fresh builds using `polter`: ```bash theme={null} polter vibetunnel # Waits for build to complete, then runs ``` ### Fallback Without Poltergeist If Poltergeist is not available, fall back to direct Xcode builds: ```bash theme={null} # Debug build cd mac xcodebuild -project VibeTunnel.xcodeproj -scheme VibeTunnel -configuration Debug build # Release build ./scripts/build.sh ``` ### Poltergeist Configuration The 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. ## Tailscale CLI Updates (as of August 2025) The Tailscale CLI has changed its syntax. The new commands are: ### Tailscale Serve (HTTPS proxy to local services) ```bash theme={null} # OLD syntax (deprecated): tailscale serve https / http://localhost:4020 # NEW syntax: tailscale serve --bg http://localhost:4020 ``` The `--bg` flag runs the serve configuration in background mode. The process exits immediately after configuration. ### Tailscale Funnel (Public internet access) ```bash theme={null} # Reset any existing configuration first tailscale funnel reset # Enable funnel (still uses --bg flag) tailscale funnel --bg 443 ``` ### Important Notes: * The `tailscale serve --bg` command 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 ## Key Files Quick Reference * 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` # important-instruction-reminders 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. # GEMINI Source: https://docs.vibetunnel.sh/GEMINI # Gemini Operational Instructions When I am operating within this project, I must adhere to the following instructions: 1. **Read `claude.md`:** Before performing any task, I must first read the `claude.md` file located in the current working directory. 2. **Contextual Awareness:** If the current working directory is a subfolder, I must read all `claude.md` files found in the current folder and all of its parent directories up to the project root. This ensures I have the full context, from the general project-level instructions to the specific subdirectory instructions. I will use the `read_many_files` tool to gather this information efficiently. # Docs Source: https://docs.vibetunnel.sh/apple/docs/index # Apple UI Documentation Index ## Quick Navigation ### Liquid Glass Design * [Overview](liquid-glass/overview.md) - Core concepts, platform availability * [AppKit Implementation](liquid-glass/appkit.md) - NSGlassEffectView usage * [SwiftUI Implementation](liquid-glass/swiftui.md) - glassEffect() modifier * [Common Patterns](liquid-glass/patterns.md) - Reusable components & techniques ### Toolbar Features * [SwiftUI Toolbar Features](toolbar/swiftui-features.md) - Customization, search, transitions ## API Quick Lookup ### Liquid Glass APIs | Task | SwiftUI | AppKit | | ---------------- | ------------------------------------------- | -------------------------------- | | Apply glass | `.glassEffect()` | `NSGlassEffectView()` | | Set shape | `.glassEffect(in: .rect(cornerRadius: 16))` | `glass.cornerRadius = 16` | | Add tint | `.glassEffect(.regular.tint(.blue))` | `glass.tintColor = NSColor.blue` | | Make interactive | `.glassEffect(.regular.interactive())` | Custom mouse tracking | | Container | `GlassEffectContainer { }` | `NSGlassEffectContainerView()` | | Button style | `.buttonStyle(.glass)` | Custom GlassButton class | | Morphing | `.glassEffectID("id", in: namespace)` | Manual animation | ### Toolbar APIs | Task | SwiftUI Code | | ---------------------- | ---------------------------------------------------------- | | Customizable toolbar | `.toolbar(id: "main") { }` | | Add item | `ToolbarItem(id: "save") { }` | | Add spacer | `ToolbarSpacer(.flexible)` | | Minimize search | `.searchToolbarBehavior(.minimize)` | | Reposition system item | `DefaultToolbarItem(kind: .search, placement: .bottomBar)` | | Transition source | `.matchedTransitionSource(id: "btn", in: namespace)` | ## Decision Trees ### When to Use Liquid Glass ``` Need glass effect? ├─ Single view → .glassEffect() / NSGlassEffectView └─ Multiple views ├─ Need merging → Use Container └─ Independent → Individual glass effects ``` ### Choosing Framework ``` Platform target? ├─ iOS only → SwiftUI ├─ macOS only │ ├─ Need AppKit integration → NSGlassEffectView │ └─ Pure SwiftUI app → .glassEffect() └─ Cross-platform → SwiftUI with platform checks ``` ## Performance Guidelines | Scenario | Recommendation | Max Count | | ------------- | ---------------------------- | --------- | | Static UI | Individual glass effects | 5-10 | | Dynamic lists | Container + lazy loading | 20-30 | | Animations | Disable during transitions | N/A | | Scrolling | Disable glass when scrolling | N/A | ## Common Tasks ### Create Glass Button * **SwiftUI**: `Button("Click").buttonStyle(.glass)` * **AppKit**: See [Glass Button Pattern](liquid-glass/patterns.md#glass-button) ### Animate Glass Tint * **SwiftUI**: `.glassEffect(.regular.tint(condition ? .blue : .clear))` * **AppKit**: `NSAnimationContext` with `animator().tintColor` ### Merge Glass Effects * **SwiftUI**: Wrap in `GlassEffectContainer(spacing: 40)` * **AppKit**: Use `NSGlassEffectContainerView` with spacing ### Custom Toolbar * **SwiftUI**: `.toolbar(id: "main")` with `ToolbarItem(id: "item")` ## Platform Requirements | Feature | iOS/iPadOS | macOS | visionOS | | --------------------- | ---------- | ----- | -------- | | Liquid Glass | 17.0+ | 14.0+ | 1.0+ | | Customizable Toolbars | 17.0+ | 14.0+ | N/A | | Glass Button Style | 17.0+ | 14.0+ | 1.0+ | | Matched Transitions | 17.0+ | 14.0+ | 1.0+ | ## Migration Guide ### AppKit → SwiftUI ```swift theme={null} // AppKit let glass = NSGlassEffectView() glass.cornerRadius = 16 glass.tintColor = NSColor.blue // SwiftUI equivalent View() .glassEffect(.regular.tint(.blue), in: .rect(cornerRadius: 16)) ``` ### Old Toolbar → New Toolbar ```swift theme={null} // Old .toolbar { Button("Save") { } } // New (customizable) .toolbar(id: "main") { ToolbarItem(id: "save") { Button("Save") { } } } ``` ## Resources ### Apple Documentation * [NSGlassEffectView](https://developer.apple.com/documentation/AppKit/NSGlassEffectView) * [View.glassEffect](https://developer.apple.com/documentation/SwiftUI/View/glassEffect) * [CustomizableToolbarContent](https://developer.apple.com/documentation/SwiftUI/CustomizableToolbarContent) ### Sample Projects * [Landmarks: Liquid Glass](https://developer.apple.com/documentation/SwiftUI/Landmarks-Building-an-app-with-Liquid-Glass) ### WWDC Sessions * Liquid Glass design principles * Toolbar customization in SwiftUI # Appkit Source: https://docs.vibetunnel.sh/apple/docs/liquid-glass/appkit # Liquid Glass in AppKit ## Quick Reference | Class | Purpose | Key Properties | | ---------------------------- | ---------------------- | ------------------------------------------ | | `NSGlassEffectView` | Single glass effect | `contentView`, `cornerRadius`, `tintColor` | | `NSGlassEffectContainerView` | Multiple glass effects | `contentView`, `spacing` | ## NSGlassEffectView ### Basic Usage ```swift theme={null} let glassView = NSGlassEffectView(frame: NSRect(x: 0, y: 0, width: 200, height: 100)) glassView.cornerRadius = 16.0 glassView.tintColor = NSColor.systemBlue.withAlphaComponent(0.3) glassView.contentView = myContentView ``` ### Interactive Glass ```swift theme={null} class InteractiveGlass: NSGlassEffectView { override func mouseEntered(with event: NSEvent) { NSAnimationContext.runAnimationGroup { context in context.duration = 0.2 animator().tintColor = NSColor.accent.withAlphaComponent(0.2) } } override func mouseExited(with event: NSEvent) { NSAnimationContext.runAnimationGroup { _ in animator().tintColor = nil } } } ``` ## NSGlassEffectContainerView ### Container Setup ```swift theme={null} let container = NSGlassEffectContainerView() container.spacing = 40.0 // Merge distance let contentView = NSView() container.contentView = contentView // Add multiple glass views to contentView [glass1, glass2, glass3].forEach { contentView.addSubview($0) } ``` ### Animated Merging ```swift theme={null} NSAnimationContext.runAnimationGroup { context in context.duration = 0.5 // Move glass views closer to trigger merge glass2.animator().frame.origin.x -= 50 } ``` ## Custom Components ### Glass Button ```swift theme={null} class GlassButton: NSButton { private let glass = NSGlassEffectView() override init(frame: NSRect) { super.init(frame: frame) setup() } required init?(coder: NSCoder) { super.init(coder: coder) setup() } private func setup() { bezelStyle = .rounded isBordered = false glass.autoresizingMask = [.width, .height] glass.cornerRadius = 8.0 addSubview(glass, positioned: .below, relativeTo: nil) } } ``` ### Glass Toolbar ```swift theme={null} // Add glass to toolbar area let toolbar = NSToolbar(identifier: "main") let glassView = NSGlassEffectView() glassView.frame = NSRect(x: 0, y: view.bounds.height - 50, width: view.bounds.width, height: 50) glassView.autoresizingMask = [.width, .minYMargin] view.addSubview(glassView) ``` ## Best Practices ### Z-Order * Only `contentView` guaranteed inside effect * Arbitrary subviews may render incorrectly ### Performance * Batch in containers when possible * Limit total glass views (5-10 max) * Disable when scrolling for performance ### Animation * Use `NSAnimationContext` for smooth transitions * Standard duration: 0.2-0.5 seconds * Animate `tintColor` for state changes ## Common Issues | Issue | Solution | | --------------------- | -------------------------------------------- | | Glass not visible | Check view hierarchy, ensure added to window | | Performance lag | Use containers, reduce glass count | | Merge not working | Check container spacing value | | Content outside glass | Use contentView property exclusively | ## See Also * [Overview](overview.md) * [SwiftUI Implementation](swiftui.md) * [Common Patterns](patterns.md) * [Apple Docs: NSGlassEffectView](https://developer.apple.com/documentation/AppKit/NSGlassEffectView) # Overview Source: https://docs.vibetunnel.sh/apple/docs/liquid-glass/overview # Liquid Glass Design Overview ## What is Liquid Glass? Dynamic material design combining optical glass properties with fluidity: * **Blurs** content behind it * **Reflects** color and light from surroundings * **Reacts** to touch and pointer interactions * **Morphs** between shapes during transitions ## Platform Availability | Platform | Framework | Primary Class/Modifier | Min Version | | ---------- | --------- | ---------------------- | ----------- | | macOS | AppKit | `NSGlassEffectView` | macOS 14.0+ | | macOS | SwiftUI | `.glassEffect()` | macOS 14.0+ | | iOS/iPadOS | SwiftUI | `.glassEffect()` | iOS 17.0+ | ## Core Concepts ### Glass Variants * **Regular**: Standard glass effect * **Prominent**: Enhanced visibility with tint * **Interactive**: Responds to user input ### Container Optimization Containers improve performance and enable effect merging: * **AppKit**: `NSGlassEffectContainerView` * **SwiftUI**: `GlassEffectContainer` ### Key Properties * `cornerRadius` / `shape`: Visual appearance * `tintColor` / `.tint()`: Color overlay * `spacing`: Merge distance threshold ## Quick Start ### SwiftUI ```swift theme={null} Text("Hello").glassEffect() Button("Click").buttonStyle(.glass) ``` ### AppKit ```swift theme={null} let glass = NSGlassEffectView() glass.cornerRadius = 12.0 glass.contentView = myView ``` ## Performance Guidelines 1. Use containers for multiple glass views 2. Limit total glass effects on screen 3. Consider GPU impact on older devices 4. Batch similar effects together ## Next Steps * [AppKit Implementation](appkit.md) * [SwiftUI Implementation](swiftui.md) * [Common Patterns](patterns.md) # Patterns Source: https://docs.vibetunnel.sh/apple/docs/liquid-glass/patterns # Liquid Glass Common Patterns ## Cross-Platform Patterns ### State-Based Glass **SwiftUI** ```swift theme={null} .glassEffect(.regular.tint(isActive ? .blue : .clear)) ``` **AppKit** ```swift theme={null} glassView.tintColor = isActive ? NSColor.systemBlue.withAlphaComponent(0.3) : nil ``` ### Hover Effects **SwiftUI** ```swift theme={null} @State private var isHovered = false Text("Hover") .glassEffect(.regular.tint(isHovered ? .blue : .clear)) .onHover { isHovered = $0 } ``` **AppKit** ```swift theme={null} override func mouseEntered(with: NSEvent) { animator().tintColor = NSColor.systemBlue.withAlphaComponent(0.2) } ``` ### Animated Transitions **SwiftUI** ```swift theme={null} .animation(.spring(duration: 0.3), value: glassState) ``` **AppKit** ```swift theme={null} NSAnimationContext.runAnimationGroup { context in context.duration = 0.3 // animations } ``` ## UI Component Patterns ### Glass Card ```swift theme={null} // SwiftUI struct GlassCard: View { let content: Content var body: some View { content .padding() .glassEffect(in: .rect(cornerRadius: 16)) } } // AppKit class GlassCard: NSView { let glass = NSGlassEffectView() init(content: NSView) { super.init(frame: .zero) glass.cornerRadius = 16 glass.contentView = content addSubview(glass) } } ``` ### Glass Badge ```swift theme={null} // SwiftUI struct GlassBadge: View { let count: Int var body: some View { Text("\(count)") .padding(.horizontal, 8) .glassEffect(.regular.tint(.red)) } } ``` ### Glass Toolbar ```swift theme={null} // SwiftUI .toolbar { ToolbarItemGroup { Button("One") { }.buttonStyle(.glass) Button("Two") { }.buttonStyle(.glass) } } ``` ## Layout Patterns ### Grid of Glass Items ```swift theme={null} // SwiftUI GlassEffectContainer(spacing: 20) { LazyVGrid(columns: [GridItem(.adaptive(minimum: 100))]) { ForEach(items) { item in ItemView(item).glassEffect() } } } ``` ### Merging Glass Groups ```swift theme={null} // SwiftUI with union @Namespace private var ns ForEach(items.indices) { i in ItemView(items[i]) .glassEffect() .glassEffectUnion(id: groupID(for: i), namespace: ns) } ``` ## Animation Patterns ### Pulse Effect ```swift theme={null} // SwiftUI @State private var isPulsing = false Circle() .glassEffect(.regular.tint(isPulsing ? .blue : .clear)) .animation(.easeInOut(duration: 1).repeatForever(), value: isPulsing) .onAppear { isPulsing = true } ``` ### Morphing Between States ```swift theme={null} // SwiftUI @Namespace private var namespace if expanded { LargeView().glassEffect().glassEffectID("morph", in: namespace) } else { SmallView().glassEffect().glassEffectID("morph", in: namespace) } ``` ## Performance Patterns ### Lazy Loading Glass ```swift theme={null} // SwiftUI ScrollView { LazyVStack { ForEach(items) { item in ItemView(item) .glassEffect(isEnabled: item.isVisible) } } } ``` ### Batch Processing ```swift theme={null} // AppKit let container = NSGlassEffectContainerView() container.spacing = 20 // Add all glass views to container's contentView ``` ## Decision Matrix | Use Case | SwiftUI | AppKit | | -------------- | ---------------------- | ---------------------------- | | Simple glass | `.glassEffect()` | `NSGlassEffectView` | | Multiple glass | `GlassEffectContainer` | `NSGlassEffectContainerView` | | Button styling | `.buttonStyle(.glass)` | Custom `GlassButton` class | | Morphing | `.glassEffectID()` | Manual animation | | Performance | Container + lazy | Container + batch | ## Tips & Tricks 1. **Merge Control**: Adjust container spacing to control merge distance 2. **State Changes**: Use tint color for visual feedback 3. **Touch Feedback**: Enable `.interactive()` for user interaction 4. **Performance**: Disable glass when off-screen 5. **Consistency**: Match corner radius across app ## See Also * [Overview](overview.md) * [AppKit Implementation](appkit.md) * [SwiftUI Implementation](swiftui.md) # Swiftui Source: https://docs.vibetunnel.sh/apple/docs/liquid-glass/swiftui # Liquid Glass in SwiftUI ## Quick Reference | Modifier/View | Purpose | Parameters | | ---------------------- | ----------------------- | ----------------------------- | | `.glassEffect()` | Apply glass to view | `Glass`, `Shape`, `isEnabled` | | `GlassEffectContainer` | Optimize multiple glass | `spacing`, `content` | | `.buttonStyle(.glass)` | Glass button style | N/A | | `.glassEffectID()` | Morphing transitions | `id`, `namespace` | ## Basic Implementation ### Simple Glass Effect ```swift theme={null} Text("Hello") .padding() .glassEffect() // Default: regular glass, capsule shape ``` ### Custom Shape & Tint ```swift theme={null} Image(systemName: "star") .glassEffect( .regular.tint(.blue).interactive(), in: .rect(cornerRadius: 12) ) ``` ## Glass Variants ### Configuration Options ```swift theme={null} // Regular glass .glassEffect(.regular) // With tint .glassEffect(.regular.tint(.orange)) // Interactive (responds to touch) .glassEffect(.regular.interactive()) // Combined .glassEffect(.regular.tint(.blue).interactive()) ``` ## Container Usage ### Multiple Glass Views ```swift theme={null} GlassEffectContainer(spacing: 40) { HStack(spacing: 40) { ForEach(items) { item in ItemView(item) .glassEffect() } } } ``` ### Union Effect ```swift theme={null} @Namespace private var namespace GlassEffectContainer { HStack { ForEach(items.indices, id: \.self) { index in ItemView(items[index]) .glassEffect() .glassEffectUnion( id: index < 2 ? "group1" : "group2", namespace: namespace ) } } } ``` ## Morphing Transitions ### Animated Morphing ```swift theme={null} @State private var expanded = false @Namespace private var namespace GlassEffectContainer { if expanded { LargeView() .glassEffect() .glassEffectID("item", in: namespace) } else { SmallView() .glassEffect() .glassEffectID("item", in: namespace) } } .animation(.spring(), value: expanded) ``` ## Button Styles ### Glass Buttons ```swift theme={null} // Standard glass button Button("Action") { } .buttonStyle(.glass) // Prominent glass button Button("Important") { } .buttonStyle(.glassProminent) ``` ## Toolbar Integration ### Glass in Toolbar ```swift theme={null} .toolbar { ToolbarItem(placement: .primaryAction) { Button("Save") { } .buttonStyle(.glass) } } ``` ### Custom Toolbar Background ```swift theme={null} .toolbar { ToolbarItem(placement: .principal) { Text("Title") .sharedBackgroundVisibility(.hidden) } } ``` ## Advanced Techniques ### Conditional Glass ```swift theme={null} @State private var glassEnabled = true Text("Dynamic") .glassEffect(isEnabled: glassEnabled) ``` ### Navigation Transitions ```swift theme={null} @Namespace private var namespace NavigationStack { Content() .toolbar { ToolbarItem { Button("Open") { } .matchedTransitionSource(id: "btn", in: namespace) } } .sheet(isPresented: $show) { DetailView() .navigationTransition(.zoom(sourceID: "btn", in: namespace)) } } ``` ## Performance Tips 1. **Container Usage**: Always wrap multiple glass views 2. **Spacing**: Smaller = merge closer, larger = merge farther 3. **Limit Count**: 5-10 glass effects maximum 4. **Disable When Hidden**: Use `isEnabled` parameter ## Common Patterns | Pattern | Implementation | | ---------------- | ------------------------------------------- | | Toggle glass | `.glassEffect(isEnabled: condition)` | | Group merge | `.glassEffectUnion(id:namespace:)` | | Custom shape | `.glassEffect(in: .rect(cornerRadius: 20))` | | State indication | `.tint(isActive ? .blue : .clear)` | ## Platform Notes * **iOS**: Bottom bar placement works well * **iPadOS**: Consider larger touch targets * **macOS**: Toolbar customization expected ## See Also * [Overview](overview.md) * [AppKit Implementation](appkit.md) * [Common Patterns](patterns.md) * [Apple Docs: glassEffect](https://developer.apple.com/documentation/SwiftUI/View/glassEffect) # Modern swift Source: https://docs.vibetunnel.sh/apple/docs/modern-swift # Modern Swift Development Write idiomatic SwiftUI code following Apple's latest architectural recommendations and best practices. ## Core Philosophy * SwiftUI is the default UI paradigm for Apple platforms - embrace its declarative nature * Avoid legacy UIKit patterns and unnecessary abstractions * Focus on simplicity, clarity, and native data flow * Let SwiftUI handle the complexity - don't fight the framework ## Architecture Guidelines ### 1. Embrace Native State Management Use SwiftUI's built-in property wrappers appropriately: * `@State` - Local, ephemeral view state * `@Binding` - Two-way data flow between views * `@Observable` - Shared state (iOS 17+) * `@ObservableObject` - Legacy shared state (pre-iOS 17) * `@Environment` - Dependency injection for app-wide concerns ### 2. State Ownership Principles * Views own their local state unless sharing is required * State flows down, actions flow up * Keep state as close to where it's used as possible * Extract shared state only when multiple views need it ### 3. Modern Async Patterns * Use `async/await` as the default for asynchronous operations * Leverage `.task` modifier for lifecycle-aware async work * Avoid Combine unless absolutely necessary * Handle errors gracefully with try/catch ### 4. View Composition * Build UI with small, focused views * Extract reusable components naturally * Use view modifiers to encapsulate common styling * Prefer composition over inheritance ### 5. Code Organization * Organize by feature, not by type (avoid Views/, Models/, ViewModels/ folders) * Keep related code together in the same file when appropriate * Use extensions to organize large files * Follow Swift naming conventions consistently ## Implementation Patterns ### Simple State Example ```swift theme={null} struct CounterView: View { @State private var count = 0 var body: some View { VStack { Text("Count: \(count)") Button("Increment") { count += 1 } } } } ``` ### Shared State with @Observable ```swift theme={null} @Observable class UserSession { var isAuthenticated = false var currentUser: User? func signIn(user: User) { currentUser = user isAuthenticated = true } } struct MyApp: App { @State private var session = UserSession() var body: some Scene { WindowGroup { ContentView() .environment(session) } } } ``` ### Async Data Loading ```swift theme={null} struct ProfileView: View { @State private var profile: Profile? @State private var isLoading = false @State private var error: Error? var body: some View { Group { if isLoading { ProgressView() } else if let profile { ProfileContent(profile: profile) } else if let error { ErrorView(error: error) } } .task { await loadProfile() } } private func loadProfile() async { isLoading = true defer { isLoading = false } do { profile = try await ProfileService.fetch() } catch { self.error = error } } } ``` ## Best Practices ### DO: * Write self-contained views when possible * Use property wrappers as intended by Apple * Test logic in isolation, preview UI visually * Handle loading and error states explicitly * Keep views focused on presentation * Use Swift's type system for safety ### DON'T: * Create ViewModels for every view * Move state out of views unnecessarily * Add abstraction layers without clear benefit * Use Combine for simple async operations * Fight SwiftUI's update mechanism * Overcomplicate simple features ## Testing Strategy * Unit test business logic and data transformations * Use SwiftUI Previews for visual testing * Test @Observable classes independently * Keep tests simple and focused * Don't sacrifice code clarity for testability ## Modern Swift Features * Use Swift Concurrency (async/await, actors) * Leverage Swift 6 data race safety when available * Utilize property wrappers effectively * Embrace value types where appropriate * Use protocols for abstraction, not just for testing ## Summary Write SwiftUI code that looks and feels like SwiftUI. The framework has matured significantly - trust its patterns and tools. Focus on solving user problems rather than implementing architectural patterns from other platforms. # Swiftui features Source: https://docs.vibetunnel.sh/apple/docs/toolbar/swiftui-features # SwiftUI Toolbar Features ## Quick Reference | Feature | Modifier/Type | Purpose | | ---------------------------- | -------------------- | --------------------------- | | `.toolbar(id:)` | Customizable toolbar | User can add/remove/reorder | | `ToolbarSpacer` | Spacing control | Fixed or flexible spacing | | `.searchToolbarBehavior()` | Search field display | Minimize/expand behavior | | `DefaultToolbarItem` | System items | Reposition system controls | | `.matchedTransitionSource()` | Transitions | Zoom from toolbar items | ## Customizable Toolbars ### Basic Setup ```swift theme={null} .toolbar(id: "main") { ToolbarItem(id: "save") { SaveButton() } ToolbarItem(id: "share") { ShareButton() } ToolbarSpacer(.flexible) ToolbarItem(id: "more") { MoreButton() } } ``` ### Spacer Types ```swift theme={null} ToolbarSpacer(.fixed) // Fixed width ToolbarSpacer(.flexible) // Pushes items apart ``` ## Search Integration ### Minimize Behavior ```swift theme={null} @State private var searchText = "" NavigationStack { ContentView() .searchable($searchText) .searchToolbarBehavior(.minimize) // Compact search button } ``` ### Repositioning Search ```swift theme={null} .toolbar { ToolbarItem(placement: .bottomBar) { Button1() } DefaultToolbarItem(kind: .search, placement: .bottomBar) ToolbarItem(placement: .bottomBar) { Button2() } } ``` ## Placement Options ### Common Placements ```swift theme={null} .toolbar { // Navigation bar ToolbarItem(placement: .navigationBarLeading) { } ToolbarItem(placement: .navigationBarTrailing) { } ToolbarItem(placement: .principal) { } // Bottom bar (iOS) ToolbarItem(placement: .bottomBar) { } // Large title area ToolbarItem(placement: .largeSubtitle) { CustomSubtitle() } } ``` ### Large Subtitle ```swift theme={null} NavigationStack { Content() .navigationTitle("Title") .navigationSubtitle("Subtitle") .toolbar { ToolbarItem(placement: .largeSubtitle) { // Overrides navigationSubtitle CustomSubtitleView() } } } ``` ## Visual Effects ### Matched Transitions ```swift theme={null} @State private var showDetail = false @Namespace private var namespace NavigationStack { Content() .toolbar { ToolbarItem { Button("Open") { showDetail = true } .matchedTransitionSource(id: "btn", in: namespace) } } .sheet(isPresented: $showDetail) { DetailView() .navigationTransition(.zoom(sourceID: "btn", in: namespace)) } } ``` ### Background Visibility ```swift theme={null} .toolbar(id: "main") { ToolbarItem(id: "status", placement: .principal) { StatusView() .sharedBackgroundVisibility(.hidden) // No glass background } } ``` ## System Items ### Default Items ```swift theme={null} .toolbar { // Reposition system search DefaultToolbarItem(kind: .search, placement: .bottomBar) // Sidebar toggle DefaultToolbarItem(kind: .sidebar, placement: .navigationBarLeading) } ``` ## Platform Considerations ### iOS/iPadOS ```swift theme={null} #if os(iOS) .toolbar { ToolbarItemGroup(placement: .bottomBar) { // Bottom bar items for iPhone } } #endif ``` ### macOS ```swift theme={null} #if os(macOS) .toolbar { ToolbarItem(placement: .automatic) { // macOS toolbar items } } #endif ``` ## Common Patterns ### Dynamic Toolbar ```swift theme={null} @State private var isEditing = false .toolbar { if isEditing { ToolbarItem(id: "done") { DoneButton() } } else { ToolbarItem(id: "edit") { EditButton() } } } ``` ### Grouped Actions ```swift theme={null} .toolbar { ToolbarItemGroup(placement: .bottomBar) { Button("One") { } Button("Two") { } Button("Three") { } } } ``` ### Contextual Items ```swift theme={null} @State private var selection: Item? .toolbar { if selection != nil { ToolbarItem { DeleteButton() } ToolbarItem { ShareButton() } } } ``` ## Best Practices 1. **Unique IDs**: Use meaningful identifiers for customizable items 2. **Logical Groups**: Use spacers to group related actions 3. **Platform Awareness**: Test on all target platforms 4. **Consistent Placement**: Follow platform conventions 5. **Minimal Items**: Avoid overcrowding toolbars ## Troubleshooting | Issue | Solution | | ---------------------- | ----------------------------------------- | | Items not customizable | Add `id` to toolbar and items | | Search not minimizing | Apply `.searchToolbarBehavior(.minimize)` | | Transition not working | Check namespace and ID match | | Items hidden | Check placement compatibility | ## References * [Apple Docs: ToolbarContent](https://developer.apple.com/documentation/SwiftUI/ToolbarContent) * [Apple Docs: CustomizableToolbarContent](https://developer.apple.com/documentation/SwiftUI/CustomizableToolbarContent) * [Liquid Glass Integration](../liquid-glass/swiftui.md) # INDEX Source: https://docs.vibetunnel.sh/docs/INDEX # VibeTunnel Documentation Index This index provides a comprehensive overview of all documentation in the VibeTunnel project, organized by category and purpose. ## 📚 Main Documentation ### Getting Started * [**README.md**](../README.md) - Project overview, quick start guide, and basic usage * [**introduction.mdx**](introduction.mdx) - Mintlify documentation landing page * [**docs.json**](../docs.json) - Mintlify documentation configuration ### Architecture & Design * [**ARCHITECTURE.md**](ARCHITECTURE.md) - System architecture, component relationships, data flow * [**architecture-mario.md**](architecture-mario.md) - Alternative architecture documentation * [**spec.md**](spec.md) - Core technical specifications and protocols * [**ios-spec.md**](ios-spec.md) - iOS companion app specification ### Development Guides * [**CONTRIBUTING.md**](CONTRIBUTING.md) - Contributing guidelines and development workflow * [**development.md**](development.md) - Development setup, code style, patterns * [**build-system.md**](build-system.md) - Build system overview and usage * [**deployment.md**](deployment.md) - Deployment and distribution guide * [**RELEASE.md**](RELEASE.md) - Comprehensive release process documentation ### Feature Documentation * [**authentication.md**](authentication.md) - Authentication system and security * [**push-notification.md**](push-notification.md) - Push notification implementation * [**security.md**](security.md) - Security configuration and best practices * [**keyboard-shortcuts.md**](keyboard-shortcuts.md) - Keyboard shortcut reference ### Testing * [**testing.md**](testing.md) - Testing strategy and test suite documentation * [**TESTING\_EXTERNAL\_DEVICES.md**](TESTING_EXTERNAL_DEVICES.md) - Testing on external devices (iPad, etc.) ### Tools & Utilities * [**claude.md**](claude.md) - Claude CLI usage guide * [**gemini.md**](gemini.md) - Gemini CLI for large codebase analysis * [**custom-node.md**](custom-node.md) - Custom Node.js build documentation ### Reference * [**project-overview.md**](project-overview.md) - High-level project overview * [**files.md**](files.md) - File catalog and organization * [**logging-style-guide.md**](logging-style-guide.md) - Logging conventions and style guide * [**CHANGELOG.md**](../CHANGELOG.md) - Project changelog ## 🍎 Platform-Specific Documentation ### macOS (`mac/`) * [**mac/README.md**](../mac/README.md) - macOS app overview and quick start * [**mac/docs/code-signing.md**](../mac/docs/code-signing.md) - Comprehensive code signing guide * [**mac/docs/BuildArchitectures.md**](../mac/docs/BuildArchitectures.md) - Build architecture details * [**mac/docs/BuildRequirements.md**](../mac/docs/BuildRequirements.md) - Build requirements * [**mac/docs/sparkle-keys.md**](../mac/docs/sparkle-keys.md) - Sparkle update framework keys * [**mac/docs/sparkle-stats-store.md**](../mac/docs/sparkle-stats-store.md) - Update statistics ### iOS (`ios/`) * [**ios/README.md**](../ios/README.md) - iOS app overview * [**ios/CLAUDE.md**](../ios/CLAUDE.md) - iOS development guidelines for Claude ### Web (`web/`) * [**web/README.md**](../web/README.md) - Web server and frontend overview * [**web/docs/spec.md**](../web/docs/spec.md) - Web server implementation specification * [**web/docs/performance.md**](../web/docs/performance.md) - Performance optimization guide * [**web/docs/playwright-testing.md**](../web/docs/playwright-testing.md) - Playwright E2E testing * [**web/docs/socket-protocol.md**](../web/docs/socket-protocol.md) - WebSocket protocol documentation * [**web/docs/terminal-titles.md**](../web/docs/terminal-titles.md) - Terminal title management * [**web/docs/VT\_INSTALLATION.md**](../web/docs/VT_INSTALLATION.md) - VT command installation * [**web/docs/npm.md**](../web/docs/npm.md) - NPM package documentation ### Apple Shared (`apple/`) * [**apple/docs/modern-swift.md**](../apple/docs/modern-swift.md) - Modern Swift patterns * [**apple/docs/swift-concurrency.md**](../apple/docs/swift-concurrency.md) - Swift concurrency guide * [**apple/docs/swift-testing-playbook.md**](../apple/docs/swift-testing-playbook.md) - Swift testing best practices * [**apple/docs/swiftui.md**](../apple/docs/swiftui.md) - SwiftUI guidelines * [**apple/docs/logging-private-fix.md**](../apple/docs/logging-private-fix.md) - Logging configuration ## 🤖 AI Assistant Guidelines ### CLAUDE.md Files These files provide specific instructions for Claude AI when working with different parts of the codebase: * [**CLAUDE.md**](../CLAUDE.md) - Main project guidelines for Claude * [**web/CLAUDE.md**](../web/CLAUDE.md) - Web development specific instructions * [**mac/CLAUDE.md**](../mac/CLAUDE.md) - macOS development guidelines * [**ios/CLAUDE.md**](../ios/CLAUDE.md) - iOS development guidelines ### GEMINI.md * [**GEMINI.md**](../GEMINI.md) - Instructions for Gemini AI assistant ## 📋 Documentation Standards When adding new documentation: 1. **Location**: Place documentation in the most relevant directory * General docs in `/docs` * Platform-specific docs in their respective directories * Keep related documentation together 2. **Naming**: Use clear, descriptive names * UPPERCASE.md for important documents (README, CHANGELOG, etc.) * lowercase-with-hyphens.md for regular documentation * Include platform prefix when needed (ios-spec.md) 3. **Content**: Follow consistent structure * Start with a clear title and overview * Include practical examples * Add cross-references to related docs * Keep content up-to-date with code changes 4. **Maintenance**: Regular reviews * Remove outdated documentation * Update when features change * Consolidate duplicate content * Maintain this index when adding/removing docs # INDEX old Source: https://docs.vibetunnel.sh/docs/INDEX-old # VibeTunnel Documentation Index This index provides a comprehensive overview of all documentation in the VibeTunnel project, organized by category and purpose. ## 📚 Main Documentation ### Getting Started * [**README.md**](../README.md) - Project overview, quick start guide, and basic usage * [**introduction.mdx**](introduction.mdx) - Mintlify documentation landing page * [**docs.json**](../docs.json) - Mintlify documentation configuration ### Architecture & Design * [**ARCHITECTURE.md**](ARCHITECTURE.md) - System architecture, component relationships, data flow * [**architecture-mario.md**](architecture-mario.md) - Alternative architecture documentation * [**spec.md**](spec.md) - Core technical specifications and protocols * [**ios-spec.md**](ios-spec.md) - iOS companion app specification ### Development Guides * [**CONTRIBUTING.md**](CONTRIBUTING.md) - Contributing guidelines and development workflow * [**development.md**](development.md) - Development setup, code style, patterns * [**build-system.md**](build-system.md) - Build system overview and usage * [**deployment.md**](deployment.md) - Deployment and distribution guide * [**RELEASE.md**](RELEASE.md) - Comprehensive release process documentation ### Feature Documentation * [**authentication.md**](authentication.md) - Authentication system and security * [**push-notification.md**](push-notification.md) - Push notification implementation * [**security.md**](security.md) - Security configuration and best practices * [**keyboard-shortcuts.md**](keyboard-shortcuts.md) - Keyboard shortcut reference ### Testing * [**testing.md**](testing.md) - Testing strategy and test suite documentation * [**TESTING\_EXTERNAL\_DEVICES.md**](TESTING_EXTERNAL_DEVICES.md) - Testing on external devices (iPad, etc.) ### Tools & Utilities * [**claude.md**](claude.md) - Claude CLI usage guide * [**gemini.md**](gemini.md) - Gemini CLI for large codebase analysis * [**custom-node.md**](custom-node.md) - Custom Node.js build documentation ### Reference * [**project-overview.md**](project-overview.md) - High-level project overview * [**files.md**](files.md) - File catalog and organization * [**logging-style-guide.md**](logging-style-guide.md) - Logging conventions and style guide * [**CHANGELOG.md**](../CHANGELOG.md) - Project changelog ## 🍎 Platform-Specific Documentation ### macOS (`mac/`) * [**mac/README.md**](../mac/README.md) - macOS app overview and quick start * [**mac/docs/code-signing.md**](../mac/docs/code-signing.md) - Comprehensive code signing guide * [**mac/docs/BuildArchitectures.md**](../mac/docs/BuildArchitectures.md) - Build architecture details * [**mac/docs/BuildRequirements.md**](../mac/docs/BuildRequirements.md) - Build requirements * [**mac/docs/sparkle-keys.md**](../mac/docs/sparkle-keys.md) - Sparkle update framework keys * [**mac/docs/sparkle-stats-store.md**](../mac/docs/sparkle-stats-store.md) - Update statistics ### iOS (`ios/`) * [**ios/README.md**](../ios/README.md) - iOS app overview * [**ios/CLAUDE.md**](../ios/CLAUDE.md) - iOS development guidelines for Claude ### Web (`web/`) * [**web/README.md**](../web/README.md) - Web server and frontend overview * [**web/docs/spec.md**](../web/docs/spec.md) - Web server implementation specification * [**web/docs/performance.md**](../web/docs/performance.md) - Performance optimization guide * [**web/docs/playwright-testing.md**](../web/docs/playwright-testing.md) - Playwright E2E testing * [**web/docs/socket-protocol.md**](../web/docs/socket-protocol.md) - WebSocket protocol documentation * [**web/docs/terminal-titles.md**](../web/docs/terminal-titles.md) - Terminal title management * [**web/docs/VT\_INSTALLATION.md**](../web/docs/VT_INSTALLATION.md) - VT command installation * [**web/docs/npm.md**](../web/docs/npm.md) - NPM package documentation ### Apple Shared (`apple/`) * [**apple/docs/modern-swift.md**](../apple/docs/modern-swift.md) - Modern Swift patterns * [**apple/docs/swift-concurrency.md**](../apple/docs/swift-concurrency.md) - Swift concurrency guide * [**apple/docs/swift-testing-playbook.md**](../apple/docs/swift-testing-playbook.md) - Swift testing best practices * [**apple/docs/swiftui.md**](../apple/docs/swiftui.md) - SwiftUI guidelines * [**apple/docs/logging-private-fix.md**](../apple/docs/logging-private-fix.md) - Logging configuration ## 🤖 AI Assistant Guidelines ### CLAUDE.md Files These files provide specific instructions for Claude AI when working with different parts of the codebase: * [**CLAUDE.md**](../CLAUDE.md) - Main project guidelines for Claude * [**web/CLAUDE.md**](../web/CLAUDE.md) - Web development specific instructions * [**mac/CLAUDE.md**](../mac/CLAUDE.md) - macOS development guidelines * [**ios/CLAUDE.md**](../ios/CLAUDE.md) - iOS development guidelines ### GEMINI.md * [**GEMINI.md**](../GEMINI.md) - Instructions for Gemini AI assistant ## 📋 Documentation Standards When adding new documentation: 1. **Location**: Place documentation in the most relevant directory * General docs in `/docs` * Platform-specific docs in their respective directories * Keep related documentation together 2. **Naming**: Use clear, descriptive names * UPPERCASE.md for important documents (README, CHANGELOG, etc.) * lowercase-with-hyphens.md for regular documentation * Include platform prefix when needed (ios-spec.md) 3. **Content**: Follow consistent structure * Start with a clear title and overview * Include practical examples * Add cross-references to related docs * Keep content up-to-date with code changes 4. **Maintenance**: Regular reviews * Remove outdated documentation * Update when features change * Consolidate duplicate content * Maintain this index when adding/removing docs # RELEASE Source: https://docs.vibetunnel.sh/docs/RELEASE # VibeTunnel Release Documentation This guide provides comprehensive documentation for creating and publishing releases for VibeTunnel, a macOS menu bar application using Sparkle 2.x for automatic updates. ## ✅ Standard Release Flow (RepoBar parity) 1. **Version + changelog** * Update `VibeTunnel/version.xcconfig` (`MARKETING_VERSION`, `CURRENT_PROJECT_VERSION`). * Sync `../web/package.json` version. * Finalize the top section in `CHANGELOG.md` (no “Unreleased”). 2. **Run the full release script** * `./scripts/release.sh beta ` or `./scripts/release.sh stable` * Generates appcast entries with HTML notes from `CHANGELOG.md`. * Release notes helper: `./mac/scripts/generate-release-notes.sh > RELEASE_NOTES.md` 3. **Sparkle UX verification** * About → “Check for Updates…” * Menu only shows “Update ready, restart now?” once the update is downloaded. * Sparkle dialog shows formatted release notes (not escaped HTML). ## 🚀 Quick Release Commands ### Standard Release Flow ```bash theme={null} # 1. Update versions vim VibeTunnel/version.xcconfig # Set MARKETING_VERSION and increment CURRENT_PROJECT_VERSION vim ../web/package.json # Match version with MARKETING_VERSION # 2. Update changelog vim CHANGELOG.md # Add entry for new version # 3. Run release export SPARKLE_ACCOUNT="VibeTunnel" ./scripts/release.sh beta 5 # For beta.5 ./scripts/release.sh stable # For stable release # If interrupted, resume with: ./scripts/release.sh --resume # Check release status: ./scripts/release.sh --status ``` ### If Release Script Fails #### After Notarization Success ```bash theme={null} # 1. Create DMG (if missing) ./scripts/create-dmg.sh build/Build/Products/Release/VibeTunnel.app # 2. Create GitHub release gh release create "v1.0.0-beta.5" \ --title "VibeTunnel 1.0.0-beta.5" \ --prerelease \ --notes-file RELEASE_NOTES.md \ build/VibeTunnel-*.dmg \ build/VibeTunnel-*.zip # 3. Get Sparkle signature (ALWAYS use -f flag!) sign_update -f private/sparkle_private_key build/VibeTunnel-*.dmg --account VibeTunnel # 4. Update appcast manually (add to appcast-prerelease.xml) # 5. Commit and push git add ../appcast-prerelease.xml git commit -m "Update appcast for v1.0.0-beta.5" git push ``` ## 🎯 Release Process Overview VibeTunnel uses an automated release process that handles all the complexity of: * Building the supported Apple Silicon (arm64) application and embedded server resources * Code signing and notarization with Apple * Creating DMG and ZIP files * Publishing to GitHub * Updating Sparkle appcast files with EdDSA signatures ## ⚠️ Version Management Best Practices ### Critical Version Rules 1. **Version Configuration Source of Truth** * ALL version information is stored in `VibeTunnel/version.xcconfig` * The Xcode project must reference these values using `$(MARKETING_VERSION)` and `$(CURRENT_PROJECT_VERSION)` * NEVER hardcode versions in the Xcode project 2. **Pre-release Version Suffixes** * For pre-releases, the suffix MUST be in version.xcconfig BEFORE running release.sh * Example: To release beta 2, set `MARKETING_VERSION = 1.0.0-beta.2` in version.xcconfig * The release script will NOT add suffixes - it uses the version exactly as configured 3. **Build Number Management** * Build numbers MUST be incremented for EVERY release (including pre-releases) * Build numbers MUST be monotonically increasing * Sparkle uses build numbers, not version strings, to determine if an update is available ### Common Version Management Mistakes ❌ **MISTAKE**: Running `./scripts/release.sh beta 2` when version.xcconfig already has `1.0.0-beta.2` * **Result**: Creates version `1.0.0-beta.2-beta.2` (double suffix) * **Fix**: The release type and number are only for tagging, not version modification ❌ **MISTAKE**: Forgetting to increment build number * **Result**: Sparkle won't detect the update even with a new version * **Fix**: Always increment CURRENT\_PROJECT\_VERSION in version.xcconfig ❌ **MISTAKE**: Hardcoding versions in Xcode project instead of using version.xcconfig * **Result**: Version mismatches between built app and expected version * **Fix**: Ensure Xcode project uses `$(MARKETING_VERSION)` and `$(CURRENT_PROJECT_VERSION)` ### Version Workflow Example For releasing 1.0.0-beta.2: 1. **Edit version.xcconfig**: ``` MARKETING_VERSION = 1.0.0-beta.2 # Add suffix here CURRENT_PROJECT_VERSION = 105 # Increment from previous build ``` 2. **Verify configuration**: ```bash theme={null} ./scripts/preflight-check.sh # This will warn if version already has a suffix ``` 3. **Run release**: ```bash theme={null} ./scripts/release.sh beta 2 # The "beta 2" parameters are ONLY for git tagging ``` ## 📋 Pre-Release Checklist **Automated Checklist**: Run `./scripts/release-checklist.sh` for an interactive pre-release validation. Before running ANY release commands, verify these items: ### ⚠️ CRITICAL: Sparkle Signature Verification * [ ] **Verify private key exists at `private/sparkle_private_key`** * [ ] **Confirm you will use the `-f` flag with ALL sign\_update commands** * [ ] **Test sign a dummy file to ensure correct key:** ```bash theme={null} echo "test" > test.txt sign_update -f private/sparkle_private_key test.txt # Should produce a signature starting with a valid EdDSA signature rm test.txt ``` * [ ] **NEVER use sign\_update without the `-f` flag!** * [ ] **The public key in Info.plist is: `AGCY8w5vHirVfGGDGc8Szc5iuOqupZSh9pMj/Qs67XI=`** * [ ] **Run signature validation script:** ```bash theme={null} ./scripts/validate-sparkle-signature.sh # Should show all signatures are valid ``` ### Environment Setup * [ ] Ensure stable internet connection (notarization requires consistent connectivity) * [ ] Check Apple Developer status page for any service issues * [ ] Have at least 30 minutes available (full release takes 15-20 minutes) * [ ] Close other resource-intensive applications * [ ] Ensure you're on main branch ```bash theme={null} git checkout main git pull --rebase origin main git status # Check for uncommitted changes ``` ### Version Verification * [ ] **⚠️ CRITICAL: Version in version.xcconfig is EXACTLY what you want to release** ```bash theme={null} grep MARKETING_VERSION VibeTunnel/version.xcconfig # For beta.2 should show: MARKETING_VERSION = 1.0.0-beta.2 # NOT: MARKETING_VERSION = 1.0.0 ``` ⚠️ **WARNING**: The release script uses this version AS-IS. It will NOT add suffixes! * [ ] **Build number is incremented** ```bash theme={null} grep CURRENT_PROJECT_VERSION VibeTunnel/version.xcconfig # Must be higher than the last release ``` * [ ] **Web package.json version matches macOS version** ```bash theme={null} # Check web version matches macOS version grep '"version"' ../web/package.json # Should match MARKETING_VERSION from version.xcconfig ``` ⚠️ **IMPORTANT**: The web frontend version must be synchronized with the macOS app version! * [ ] **CHANGELOG.md has entry for this version** ```bash theme={null} grep "## \[1.0.0-beta.2\]" CHANGELOG.md # Must exist with release notes ``` ### Environment Variables * [ ] Set required environment variables: ```bash theme={null} export SPARKLE_ACCOUNT="VibeTunnel" export APP_STORE_CONNECT_KEY_ID="YOUR_KEY_ID" export APP_STORE_CONNECT_ISSUER_ID="YOUR_ISSUER_ID" export APP_STORE_CONNECT_API_KEY_P8="-----BEGIN PRIVATE KEY----- YOUR_PRIVATE_KEY_CONTENT -----END PRIVATE KEY-----" ``` ### Clean Build * [ ] Clean build and derived data if needed: ```bash theme={null} ./scripts/clean.sh rm -rf build DerivedData rm -rf ~/Library/Developer/Xcode/DerivedData/VibeTunnel-* ``` ### File Verification * [ ] CHANGELOG.md exists and has entry for new version * [ ] Sparkle private key exists at expected location * [ ] No stuck DMG volumes in /Volumes/ ```bash theme={null} # Check for stuck volumes ls /Volumes/VibeTunnel* # Unmount if needed for volume in /Volumes/VibeTunnel*; do hdiutil detach "$volume" -force done ``` * [ ] **Check for unexpected files in the app bundle** ```bash theme={null} # Check for node_modules or other development files find build/Build/Products/Release/VibeTunnel.app -name "node_modules" -type d find build/Build/Products/Release/VibeTunnel.app -name "*.jar" -type f # Should return empty - no development files in release build ``` ## 🚀 Creating a Release ### Step 1: Pre-flight Check ```bash theme={null} # Run the comprehensive release checklist ./scripts/release-checklist.sh # Then run the automated preflight check ./scripts/preflight-check.sh ``` These scripts validate your environment is ready for release. ### Step 2: CRITICAL Pre-Release Version Check **IMPORTANT**: Before running the release script, ensure your version.xcconfig is set correctly: 1. For beta releases: The MARKETING\_VERSION should already include the suffix (e.g., `1.0.0-beta.2`) 2. The release script will NOT add additional suffixes - it uses the version as-is 3. Always verify the version before proceeding: ```bash theme={null} grep MARKETING_VERSION VibeTunnel/version.xcconfig # Should show: MARKETING_VERSION = 1.0.0-beta.2 ``` **Common Mistake**: If the version is already `1.0.0-beta.2` and you run `./scripts/release.sh beta 2`, it will create `1.0.0-beta.2-beta.2` which is wrong! ### Step 3: Create/Update CHANGELOG.md Before creating any release, ensure the CHANGELOG.md file exists in the project root (`/vibetunnel/CHANGELOG.md`) and contains a proper section for the version being released: ```markdown theme={null} # Changelog All notable changes to VibeTunnel will be documented in this file. ## [1.0.0-beta.2] - 2025-06-19 ### 🎨 UI Improvements - **Enhanced feature** - Description of the improvement ... ``` **CRITICAL**: The release process uses the CHANGELOG.md file in the project root as the single source of truth for release notes. The changelog must be updated with the new version section BEFORE running the release script. **Key Points**: * **Location**: CHANGELOG.md must be at `/vibetunnel/CHANGELOG.md` (project root, NOT in `mac/`) * **No RELEASE\_NOTES.md files**: The release process does NOT use RELEASE\_NOTES.md files * **Per-Version Extraction**: The release script automatically extracts ONLY the changelog section for the specific version being released * **GitHub Release**: Uses the extracted markdown content directly (via `generate-release-notes.sh`) * **Sparkle Appcast**: Converts the markdown to HTML for update dialogs The release script uses these helper scripts: * `generate-release-notes.sh` - Extracts markdown release notes for GitHub * `changelog-to-html.sh` - Converts markdown to HTML for Sparkle appcast * `find-changelog.sh` - Reliably locates CHANGELOG.md from any directory ### Step 4: Create the Release ⚠️ **CRITICAL UNDERSTANDING**: The release script parameters are ONLY for: 1. Git tag creation 2. Determining if it's a pre-release on GitHub 3. Validation that your version.xcconfig matches your intent The script will NEVER modify the version - it uses version.xcconfig exactly as configured! For long-running operations, consider using screen or tmux: ```bash theme={null} # Run in a screen/tmux session to prevent disconnection screen -S release ./scripts/release.sh beta 5 --verbose --log ``` **IMPORTANT**: When using Claude Code or any automated tool, NEVER run the release script in the background. Always run it directly in the foreground to ensure proper completion and error handling. ```bash theme={null} # For stable releases: ./scripts/release.sh stable # For pre-releases: ./scripts/release.sh beta 2 # The "beta 2" parameters are ONLY for git tagging ``` **Script Validation**: The release script now includes: * Double-suffix detection (prevents 1.0.0-beta.2-beta.2) * Build number uniqueness check * Version consistency verification * Notarization credential validation **IMPORTANT**: The release script does NOT automatically increment build numbers. You must manually update the build number in VibeTunnel.xcodeproj before running the script, or it will fail the pre-flight check. The script will: 1. Validate build number is unique and incrementing 2. Build, sign, and notarize the app 3. Create a DMG 4. Publish to GitHub 5. Update the appcast files with EdDSA signatures 6. Commit and push all changes **Note**: Notarization can take 5-10 minutes depending on Apple's servers. This is normal. ### Step 5: Verify Success * Check the GitHub releases page * **IMPORTANT**: Verify the GitHub release shows ONLY the current version's changelog, not the entire history * If it shows the full changelog, the release notes were not generated correctly * The release should only show changes for that specific version (e.g., beta.10 shows only beta.10 changes) * **Monitor app size**: Verify the DMG size is reasonable (expected: \~42-44 MB) ```bash theme={null} # Check DMG size ls -lh build/VibeTunnel-*.dmg # Compare with previous releases gh release list --limit 5 | grep -E "beta|stable" # Download and check sizes for tag in $(gh release list --limit 5 | awk '{print $3}'); do echo "=== $tag ===" gh release view "$tag" --json assets --jq '.assets[] | "\(.name): \(.size) bytes (\(.size/1024/1024 | floor) MB)"' done ``` * If size increased significantly (>5MB), investigate for bundled development files * Verify the appcast was updated correctly with proper changelog content * **Critical**: Verify the Sparkle signature is correct: ```bash theme={null} # Download and verify the DMG signature curl -L -o test.dmg sign_update -f private/sparkle_private_key test.dmg --account VibeTunnel # Compare with appcast sparkle:edSignature ``` * Test updating from a previous version * **Important**: Verify that the Sparkle update dialog shows the formatted changelog, not HTML tags * **CRITICAL**: Check that update installs without "improperly signed" errors * If you get "improperly signed" error, the appcast has wrong signature * Regenerate with: `sign_update -f private/sparkle_private_key [dmg-file]` * Update appcast XML with correct signature * Run `./scripts/validate-sparkle-signature.sh` to verify all signatures * Verify Stats.store is serving the updated appcast (1-minute cache): ```bash theme={null} curl -H "User-Agent: VibeTunnel/X.X.X Sparkle/2.7.1" \ https://stats.store/api/v1/appcast/appcast-prerelease.xml | \ grep sparkle:edSignature ``` ### If Interrupted If the release script is interrupted: ```bash theme={null} ./scripts/check-release-status.sh 1.0.0-beta.5 ./scripts/release.sh --resume ``` ## 🛠️ Manual Process (If Needed) If the automated script fails, here's the manual process: ### 1. Update Version Numbers Edit version configuration files: **macOS App** (`VibeTunnel/version.xcconfig`): * Update MARKETING\_VERSION * Update CURRENT\_PROJECT\_VERSION (build number) **Web Frontend** (`../web/package.json`): * Update "version" field to match MARKETING\_VERSION **Note**: The Xcode project file is named `VibeTunnel-Mac.xcodeproj` ### 2. Clean and Build Universal Binary ```bash theme={null} rm -rf build DerivedData ./scripts/build.sh --configuration Release ``` ### 3. Sign and Notarize ```bash theme={null} ./scripts/sign-and-notarize.sh build/Build/Products/Release/VibeTunnel.app ``` ### 4. Create DMG and ZIP ```bash theme={null} ./scripts/create-dmg.sh build/Build/Products/Release/VibeTunnel.app ./scripts/create-zip.sh build/Build/Products/Release/VibeTunnel.app ``` ### 5. Sign DMG for Sparkle ```bash theme={null} export PATH="$HOME/.local/bin:$PATH" # CRITICAL: Always use -f flag with private key file! sign_update -f private/sparkle_private_key build/VibeTunnel-X.X.X.dmg ``` ### 6. Create GitHub Release ```bash theme={null} gh release create "v1.0.0-beta.1" \ --title "VibeTunnel 1.0.0-beta.1" \ --notes "Beta release 1" \ --prerelease \ build/VibeTunnel-*.dmg \ build/VibeTunnel-*.zip ``` ### 7. Update Appcast ```bash theme={null} ./scripts/update-appcast.sh git add appcast*.xml git commit -m "Update appcast for v1.0.0-beta.1" git push ``` ## 🔍 Verification Commands ```bash theme={null} # Check release artifacts ls -la build/VibeTunnel-*.dmg ls -la build/VibeTunnel-*.zip # Check GitHub release gh release view v1.0.0-beta.5 # Verify Sparkle signature (ALWAYS use -f flag!) curl -L -o test.dmg [github-dmg-url] sign_update -f private/sparkle_private_key test.dmg --account VibeTunnel # Check appcast grep "1.0.0-beta.5" ../appcast-prerelease.xml # Verify app in DMG hdiutil attach test.dmg spctl -a -vv /Volumes/VibeTunnel/VibeTunnel.app hdiutil detach /Volumes/VibeTunnel ``` ## ⚠️ Critical Requirements ### 1. Build Numbers MUST Increment Sparkle uses build numbers (CFBundleVersion) to determine updates, NOT version strings! | Version | Build | Result | | ------------ | ----- | ------------------------ | | 1.0.0-beta.1 | 100 | ✅ | | 1.0.0-beta.2 | 101 | ✅ | | 1.0.0-beta.3 | 99 | ❌ Build went backwards | | 1.0.0 | 101 | ❌ Duplicate build number | ### 2. Required Environment Variables ```bash theme={null} export APP_STORE_CONNECT_KEY_ID="YOUR_KEY_ID" export APP_STORE_CONNECT_ISSUER_ID="YOUR_ISSUER_ID" export APP_STORE_CONNECT_API_KEY_P8="-----BEGIN PRIVATE KEY----- YOUR_PRIVATE_KEY_CONTENT -----END PRIVATE KEY-----" ``` ### 3. Prerequisites * Xcode 16.4+ installed * Node.js 24 and Bun (for web frontend build) ```bash theme={null} # Install Bun curl -fsSL https://bun.sh/install | bash ``` * Rustup; `native/vt-fwd/rust-toolchain.toml` pins the release forwarder toolchain * GitHub CLI authenticated: `gh auth status` * Apple Developer ID certificate in Keychain * Sparkle tools in `~/.local/bin/` (sign\_update, generate\_appcast) ## 🔐 Sparkle Configuration ### ⚠️ CRITICAL: Sparkle Private Key Management **ALWAYS use the file-based private key for signing!** VibeTunnel uses EdDSA signatures for Sparkle updates. The correct private key is stored at: * `private/sparkle_ed_private_key` (clean key file - REQUIRED for sign\_update) * `private/sparkle_private_key` (commented version for documentation) **CRITICAL**: The sign\_update tool requires a clean key file with ONLY the base64 key. If you only have the commented version, the scripts will automatically extract and create the clean version. **WARNING**: Your system may have multiple Sparkle private keys: 1. **File-based key** (CORRECT) - Matches the public key in Info.plist 2. **Keychain key** (WRONG) - May produce incompatible signatures **ALWAYS use the `-f` flag when signing:** ```bash theme={null} # ✅ CORRECT - Uses file-based key sign_update -f private/sparkle_ed_private_key build/VibeTunnel-*.dmg # ❌ WRONG - May use keychain key sign_update build/VibeTunnel-*.dmg ``` The public key in Info.plist is: `AGCY8w5vHirVfGGDGc8Szc5iuOqupZSh9pMj/Qs67XI=` **Key File Format Requirements**: * The clean key file (`sparkle_ed_private_key`) must contain ONLY the base64 key * No comments, no extra lines, just the key: `SMYPxE98bJ5iLdHTLHTqGKZNFcZLgrT5Hyjh79h3TaU=` * The scripts handle this automatically by extracting from the commented file ### Sparkle Requirements for Non-Sandboxed Apps VibeTunnel is not sandboxed, which simplifies Sparkle configuration: #### 1. Entitlements (VibeTunnel.entitlements) ```xml theme={null} com.apple.security.app-sandbox com.apple.security.cs.allow-unsigned-executable-memory com.apple.security.cs.disable-library-validation ``` #### 2. Info.plist Configuration ```swift theme={null} "SUEnableInstallerLauncherService": false, // Not needed for non-sandboxed apps "SUEnableDownloaderService": false, // Not needed for non-sandboxed apps ``` #### 3. Code Signing Requirements The notarization script handles all signing correctly: 1. **Do NOT use --deep flag** when signing the app 2. Sign the app with hardened runtime and entitlements The `notarize-app.sh` script should sign the app: ```bash theme={null} # Sign the app WITHOUT --deep flag codesign --force --sign "Developer ID Application" --entitlements VibeTunnel.entitlements --options runtime VibeTunnel.app ``` ### Architecture Support The native VibeTunnel app supports Apple Silicon (arm64) Macs. The npm package remains available for Intel Macs. The release build creates one arm64 application and matching embedded server resources. This approach: * Simplifies distribution with one DMG/ZIP per release * Works seamlessly with Sparkle auto-updates * Prevents host-architecture native resources from being embedded in an incompatible app ## 📋 Update Channels VibeTunnel supports two update channels: 1. **Stable Channel** (`appcast.xml`) * Production releases only * Default for all users 2. **Pre-release Channel** (`appcast-prerelease.xml`) * Includes beta, alpha, and RC versions * Users opt-in via Settings ## 🐛 Common Issues and Solutions ### Version and Build Number Issues #### Double Version Suffix (e.g., 1.0.0-beta.2-beta.2) **Problem**: Version has double suffix after running release script. **Cause**: version.xcconfig already had the suffix, and you provided the same suffix to release.sh. **Solution**: 1. Clean up the botched release: ```bash theme={null} # Delete the bad tag git tag -d v1.0.0-beta.2-beta.2 git push origin :refs/tags/v1.0.0-beta.2-beta.2 # Delete the GitHub release gh release delete v1.0.0-beta.2-beta.2 --yes # Fix version.xcconfig # Set to the correct version without double suffix ``` 2. Re-run the release with correct parameters #### Build Script Reports Version Mismatch **Problem**: Build script warns that built version doesn't match version.xcconfig. **Cause**: Xcode project is not properly configured to use version.xcconfig values. **Solution**: 1. Open VibeTunnel.xcodeproj in Xcode 2. Select the project, then the target 3. In Build Settings, ensure: * MARKETING\_VERSION = `$(MARKETING_VERSION)` * CURRENT\_PROJECT\_VERSION = `$(CURRENT_PROJECT_VERSION)` #### Preflight Check Warns About Existing Suffix **Problem**: Preflight check shows "Version already contains pre-release suffix". **Solution**: This is a helpful warning! It reminds you to use matching parameters: ```bash theme={null} # If version.xcconfig has "1.0.0-beta.2" ./scripts/release.sh beta 2 # Correct - matches the suffix ``` ### App Size Issues #### Unexpected Size Increase **Problem**: DMG size increased significantly (>5MB) between releases. **Common Causes**: 1. **Development dependencies bundled**: node\_modules, JAR files, or other dev files 2. **Build cache not cleaned**: Old artifacts included 3. **New frameworks added**: Legitimate size increase from new dependencies **Solution**: ```bash theme={null} # 1. Check app bundle contents find build/Build/Products/Release/VibeTunnel.app -name "node_modules" -type d find build/Build/Products/Release/VibeTunnel.app -name "*.jar" -type f find build/Build/Products/Release/VibeTunnel.app -type f -size +1M -ls # 2. Compare with previous release # Extract previous DMG hdiutil attach VibeTunnel-previous.dmg du -sh /Volumes/VibeTunnel/VibeTunnel.app/Contents/* hdiutil detach /Volumes/VibeTunnel # 3. Clean and rebuild ./scripts/clean.sh rm -rf ~/Library/Developer/Xcode/DerivedData/VibeTunnel-* ./scripts/build.sh --configuration Release ``` **Prevention**: * Add size checks to release script * Ensure .gitignore includes all development paths * Regular audits of app bundle contents ### Common Version Sync Issues #### Web Version Out of Sync **Problem**: Web server shows different version than macOS app (e.g., "beta.3" when app is "beta.4"). **Cause**: web/package.json was not updated when version.xcconfig was changed. **Solution**: 1. Update package.json to match version.xcconfig: ```bash theme={null} # Check current versions grep MARKETING_VERSION VibeTunnel/version.xcconfig grep "version" ../web/package.json # Update web version to match vim ../web/package.json ``` 2. Validate sync before building: ```bash theme={null} cd ../web && node scripts/validate-version-sync.js ``` **Note**: The web UI automatically displays the version from package.json (injected at build time). ### "Uncommitted changes detected" ```bash theme={null} git status --porcelain # Check what's changed git stash # Temporarily store changes # Run release git stash pop # Restore changes ``` ### Appcast Shows HTML Tags Instead of Formatted Text **Problem**: Sparkle update dialog shows escaped HTML like `<h2>` instead of formatted text. **Root Cause**: The generate-appcast.sh script is escaping HTML content from GitHub release descriptions. **Solution**: 1. Ensure CHANGELOG.md has the proper section for the release version BEFORE running release script 2. The appcast should use local CHANGELOG.md, not GitHub release body 3. If the appcast is wrong, manually fix the generate-appcast.sh script to use local changelog content ### Build Numbers Not Incrementing **Problem**: Sparkle doesn't detect new version as an update. **Solution**: Always increment the build number in the Xcode project before releasing. ### Stuck DMG Volumes **Problem**: "Resource temporarily unavailable" errors when creating DMG. **Symptoms**: * `hdiutil: create failed - Resource temporarily unavailable` * Multiple VibeTunnel volumes visible in Finder * DMG creation fails repeatedly **Solution**: ```bash theme={null} # Manually unmount all VibeTunnel volumes for volume in /Volumes/VibeTunnel*; do hdiutil detach "$volume" -force done # Kill any stuck DMG processes pkill -f "VibeTunnel.*\.dmg" ``` **Prevention**: Scripts now clean up volumes automatically before DMG creation. ### Build Number Already Exists **Problem**: Sparkle requires unique build numbers for each release. **Solution**: 1. Check existing build numbers: ```bash theme={null} grep -E '[0-9]+' ../appcast*.xml ``` 2. Update `mac/VibeTunnel/version.xcconfig`: ``` CURRENT_PROJECT_VERSION = ``` ### Notarization Failures **Problem**: App notarization fails or takes too long. **Common Causes**: * Missing API credentials * Network issues * Apple service outages * Unsigned frameworks or binaries **Solution**: ```bash theme={null} # Check notarization status xcrun notarytool history --key-id "$APP_STORE_CONNECT_KEY_ID" \ --key "$APP_STORE_CONNECT_API_KEY_P8" \ --issuer-id "$APP_STORE_CONNECT_ISSUER_ID" # Get detailed log for failed submission xcrun notarytool log --key-id ... ``` **Normal Duration**: Notarization typically takes 2-10 minutes. If it's taking longer than 15 minutes, check Apple System Status. ### GitHub Release Already Exists **Problem**: Tag or release already exists on GitHub. **Solution**: The release script now prompts you to: 1. Delete the existing release and tag 2. Cancel the release **Prevention**: Always pull latest changes before releasing. ### DMG Shows "Unnotarized Developer ID" **Problem**: The DMG shows as "Unnotarized Developer ID" when checked with spctl. **Explanation**: This is NORMAL - DMGs are not notarized themselves, only the app inside is notarized. Check the app inside: it should show "Notarized Developer ID". ### Generate Appcast Fails **Problem**: `generate-appcast.sh` failed with GitHub API error despite valid authentication. **Workaround**: * Manually add entry to appcast-prerelease.xml * Use signature from: `sign_update [dmg] --account VibeTunnel` * Follow existing entry format (see template below) ## 🔧 Troubleshooting Common Issues ### Script Timeouts If the release script times out: 1. Check `.release-state` for the last successful step 2. Run `./scripts/release.sh --resume` to continue 3. Or manually complete remaining steps (see Manual Recovery below) ### Manual Recovery Steps If automated release fails after notarization: 1. **Create DMG** (if missing): ```bash theme={null} ./scripts/create-dmg.sh build/Build/Products/Release/VibeTunnel.app ``` 2. **Create GitHub Release**: ```bash theme={null} gh release create "v$VERSION" \ --title "VibeTunnel $VERSION" \ --notes-file RELEASE_NOTES.md \ --prerelease \ build/VibeTunnel-*.dmg \ build/VibeTunnel-*.zip ``` 3. **Sign DMG for Sparkle**: ```bash theme={null} export SPARKLE_ACCOUNT="VibeTunnel" sign_update build/VibeTunnel-$VERSION.dmg --account VibeTunnel ``` 4. **Update Appcast Manually**: * Add entry to appcast-prerelease.xml with signature from step 3 * Commit and push: `git add appcast*.xml && git commit -m "Update appcast" && git push` ### "Update is improperly signed" Error **Problem**: Users see "The update is improperly signed and could not be validated." **Cause**: The DMG was signed with the wrong Sparkle key (default instead of VibeTunnel account). **Quick Fix**: ```bash theme={null} # 1. Download the DMG from GitHub curl -L -o fix.dmg # 2. Generate correct signature sign_update fix.dmg --account VibeTunnel # 3. Update appcast-prerelease.xml with the new sparkle:edSignature # 4. Commit and push ``` **Prevention**: The updated scripts now always use `--account VibeTunnel`. ### Debug Sparkle Updates ```bash theme={null} # Monitor VibeTunnel logs log stream --predicate 'process == "VibeTunnel"' --level debug # Check XPC errors log stream --predicate 'process == "VibeTunnel"' | grep -i -E "(sparkle|xpc|installer)" # Verify XPC services codesign -dvv "VibeTunnel.app/Contents/Frameworks/Sparkle.framework/Versions/B/XPCServices/Installer.xpc" ``` ### Verify Signing and Notarization ```bash theme={null} # Check app signature ./scripts/verify-app.sh build/VibeTunnel-1.0.0.dmg # Verify XPC bundle IDs (should be org.sparkle-project.*) /usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" \ "VibeTunnel.app/Contents/Frameworks/Sparkle.framework/Versions/B/XPCServices/Installer.xpc/Contents/Info.plist" ``` ### Appcast Issues ```bash theme={null} # Verify appcast has correct build numbers ./scripts/verify-appcast.sh # Check if build number is "1" (common bug) grep '' appcast-prerelease.xml ``` ## 📝 Appcast Entry Template ```xml theme={null} VibeTunnel VERSION https://github.com/amantus-ai/vibetunnel/releases/download/vVERSION/VibeTunnel-VERSION.dmg BUILD_NUMBER VERSION VibeTunnel VERSION

Pre-release version

]]>
DATE
``` ## 🎯 Release Success Criteria * [ ] GitHub release created with both DMG and ZIP * [ ] DMG downloads and mounts correctly * [ ] App inside DMG shows as notarized * [ ] Appcast updated and pushed * [ ] Sparkle signature in appcast matches DMG * [ ] Version and build numbers correct everywhere * [ ] Previous version can update via Sparkle ## 🚨 Emergency Fixes ### Wrong Sparkle Signature ```bash theme={null} # 1. Get correct signature sign_update [dmg-url] --account VibeTunnel # 2. Update appcast-prerelease.xml with correct signature # 3. Commit and push immediately ``` ### Missing from Appcast ```bash theme={null} # Users won't see update until appcast is fixed # Add entry manually following template above # Test with: curl https://raw.githubusercontent.com/amantus-ai/vibetunnel/main/appcast-prerelease.xml ``` ### Build Number Conflict ```bash theme={null} # If Sparkle complains about duplicate build number # Increment build number in version.xcconfig # Create new release with higher build number # Old release will be ignored by Sparkle ``` ## 🔍 Key File Locations **Important**: Files are not always where scripts expect them to be. **Key Locations**: * **Appcast files**: Located in project root (`/vibetunnel/`), NOT in `mac/` * `appcast.xml` * `appcast-prerelease.xml` * **CHANGELOG.md**: Can be in either: * `mac/CHANGELOG.md` (preferred by release script) * Project root `/vibetunnel/CHANGELOG.md` (common location) * **Sparkle private key**: Usually in `mac/private/sparkle_private_key` ## 📚 Helper Scripts ### Changelog Management Scripts #### `generate-release-notes.sh` Extracts release notes for a specific version from CHANGELOG.md: ```bash theme={null} # Get release notes for a specific version ./scripts/generate-release-notes.sh 1.0.0-beta.11 # Works from any directory cd /tmp && /path/to/scripts/generate-release-notes.sh 1.0.0-beta.11 ``` #### `find-changelog.sh` Reliably locates CHANGELOG.md from any directory: ```bash theme={null} # Find the changelog file ./scripts/find-changelog.sh # Output: /path/to/vibetunnel/CHANGELOG.md ``` #### `fix-release-changelogs.sh` Updates existing GitHub releases to use per-version changelogs: ```bash theme={null} # Dry run to see what would change ./scripts/fix-release-changelogs.sh --dry-run # Actually update releases ./scripts/fix-release-changelogs.sh # Update a specific release ./scripts/fix-release-changelogs.sh v1.0.0-beta.11 ``` ## 📚 Common Commands ### Test Sparkle Signature ```bash theme={null} # Find sign_update binary find . -name sign_update -type f # Test signing with specific account ./path/to/sign_update file.dmg -f private/sparkle_private_key -p --account VibeTunnel ``` ### Verify Appcast URLs ```bash theme={null} # Check that appcast files are accessible curl -I https://raw.githubusercontent.com/amantus-ai/vibetunnel/main/appcast.xml curl -I https://raw.githubusercontent.com/amantus-ai/vibetunnel/main/appcast-prerelease.xml ``` ### Manual Appcast Generation ```bash theme={null} # If automatic generation fails cd mac export SPARKLE_ACCOUNT="VibeTunnel" ./scripts/generate-appcast.sh ``` ### Release Status Script Create `scripts/check-release-status.sh`: ```bash theme={null} #!/bin/bash VERSION=$1 echo "Checking release status for v$VERSION..." # Check local artifacts echo -n "✓ Local DMG: " [ -f "build/VibeTunnel-$VERSION.dmg" ] && echo "EXISTS" || echo "MISSING" echo -n "✓ Local ZIP: " [ -f "build/VibeTunnel-$VERSION.zip" ] && echo "EXISTS" || echo "MISSING" # Check GitHub echo -n "✓ GitHub Release: " gh release view "v$VERSION" &>/dev/null && echo "EXISTS" || echo "MISSING" # Check appcast echo -n "✓ Appcast Entry: " grep -q "$VERSION" ../appcast-prerelease.xml && echo "EXISTS" || echo "MISSING" ``` ## 📋 Post-Release Verification 1. **Check GitHub Release**: * Verify assets are attached * Check file sizes match * Ensure release notes are formatted correctly 2. **Test Update in App**: * Install previous version * Check for updates * Verify update downloads and installs * Check signature verification in Console.app 3. **Monitor for Issues**: * Watch Console.app for Sparkle errors * Check GitHub issues for user reports * Verify download counts on GitHub ## 🛠️ Recommended Script Improvements Based on release experience, consider implementing: ### 1. Release Script Enhancements Add state tracking for resumability: ```bash theme={null} # Add to release.sh # State file to track progress STATE_FILE=".release-state" # Save state after each major step save_state() { echo "$1" > "$STATE_FILE" } # Resume from last state resume_from_state() { if [ -f "$STATE_FILE" ]; then LAST_STATE=$(cat "$STATE_FILE") echo "Resuming from: $LAST_STATE" fi } # Add --resume flag handling if [[ "$1" == "--resume" ]]; then resume_from_state shift fi ``` ### 2. Better Progress Reporting ```bash theme={null} # Add progress function progress() { local step=$1 local total=$2 local message=$3 echo "[${step}/${total}] ${message}" } # Use throughout script progress 1 8 "Running pre-flight checks..." progress 2 8 "Building application..." ``` ### 3. Parallel Operations Where possible, run independent operations in parallel: ```bash theme={null} # Run signing and changelog generation in parallel { sign_app & PID1=$! generate_changelog & PID2=$! wait $PID1 $PID2 } ``` ## 📝 Key Learnings 1. **Always use explicit accounts** when dealing with signing operations 2. **Clean up resources** (volumes, processes) before operations 3. **Verify file locations** - don't assume standard paths 4. **Test the full update flow** before announcing the release 5. **Keep credentials secure** but easily accessible for scripts 6. **Document everything** - future you will thank present you 7. **Plan for long-running operations** - notarization can take 10+ minutes 8. **Implement resumable workflows** - scripts should handle interruptions gracefully 9. **DMG signing is separate from notarization** - DMGs themselves aren't notarized, only the app inside 10. **Command timeouts** are a real issue - use screen/tmux for releases ### Additional Lessons from Recent Releases #### DMG Notarization Confusion **Issue**: The DMG shows as "Unnotarized Developer ID" when checked with spctl, but this is normal. **Explanation**: * DMGs are not notarized themselves - only the app inside is notarized * The app inside the DMG shows correctly as "Notarized Developer ID" * This is expected behavior and not an error #### Release Script Timeout Handling **Issue**: Release script timed out during notarization (took \~5 minutes). **Solution**: * Run release scripts in a terminal without timeout constraints * Consider using `screen` or `tmux` for long operations * Add progress indicators to show the script is still running #### Repository Name Parsing Issue **Issue**: `generate-appcast.sh` was including `.git` suffix when parsing repository name from git remote URL. **Fix**: * Updated regex to strip `.git` suffix: `${BASH_REMATCH[2]%.git}` * This caused GitHub API calls to fail with 404 errors * Always test script changes with actual GitHub API calls #### Private Key Format Requirements **Issue**: The sign\_update tool fails with "ERROR! Failed to decode base64 encoded key data" when the private key file contains comments. **Solution**: * Create a clean private key file containing ONLY the base64 key: `private/sparkle_ed_private_key` * The commented key file (`private/sparkle_private_key`) is kept for documentation * All scripts now use the clean key file automatically * Scripts will extract the key from the commented file if the clean one doesn't exist #### State Tracking and Resume Capability **New Feature**: Release process now supports interruption and resumption. * Added `release-state.sh` for state management * Tracks 9 major release steps with progress * Use `./scripts/release.sh --resume` to continue interrupted release * Use `./scripts/release.sh --status` to check current state * State file at `.release-state` contains progress information ## 🚀 Long-term Improvements 1. **CI/CD Integration**: Move releases to GitHub Actions for reliability 2. **Release Dashboard**: Web UI showing release progress and status 3. **Automated Testing**: Test Sparkle updates in CI before publishing 4. **Rollback Capability**: Script to quickly revert a bad release 5. **Release Templates**: Pre-configured release notes and changelog formats 6. **Monitoring Improvements**: Add detailed logging with timestamps and metrics ## Summary The VibeTunnel release process is complex but well-automated. The main challenges are: * Command timeouts during long operations (especially notarization) * Lack of resumability after failures * Missing progress indicators * No automated recovery options * File location confusion Following this guide and implementing the suggested improvements will make releases more reliable and less stressful, especially when using tools with timeout constraints. **Remember**: Always use the automated release script, ensure build numbers increment, and test updates before announcing! ## 📚 Important Links * [Sparkle Sandboxing Guide](https://sparkle-project.org/documentation/sandboxing/) * [Sparkle Code Signing](https://sparkle-project.org/documentation/sandboxing/#code-signing) * [Apple Notarization](https://developer.apple.com/documentation/security/notarizing_macos_software_before_distribution) * [GitHub Releases API](https://docs.github.com/en/rest/releases/releases) # TESTING EXTERNAL DEVICES Source: https://docs.vibetunnel.sh/docs/TESTING_EXTERNAL_DEVICES # Testing VibeTunnel on External Devices This guide explains how to test VibeTunnel development changes on external devices like iPads, iPhones, and other computers. ## Overview When developing VibeTunnel's web interface, you may encounter browser-specific issues that only appear on certain devices (e.g., Safari on iPad). This guide shows you how to test your local development changes on these devices without deploying. ## Quick Start: Development Server Method This is the recommended approach for rapid iteration during development. ### 1. Start the Development Server ```bash theme={null} cd web pnpm run dev --port 4021 --bind 0.0.0.0 ``` **Key parameters:** * `--port 4021`: Use a different port than the production server (4020) * `--bind 0.0.0.0`: Bind to all network interfaces (not just localhost) ### 2. Find Your Mac's IP Address **Option A: System Preferences** 1. Open System Preferences → Network 2. Select Wi-Fi → Advanced → TCP/IP 3. Look for "IPv4 Address" **Option B: Terminal** ```bash theme={null} # For Wi-Fi connection ipconfig getifaddr en0 # For Ethernet connection ipconfig getifaddr en1 ``` ### 3. Access from External Device Open a browser on your external device and navigate to: ``` http://[your-mac-ip]:4021 ``` Example: `http://192.168.1.42:4021` ## Production Build Method Use this method when you need to test with the full Mac app integration. ### 1. Build the Web Project ```bash theme={null} cd web pnpm run build ``` ### 2. Configure VibeTunnel for Network Access 1. Open VibeTunnel from the menu bar 2. Go to Settings → Dashboard Access 3. Select "Network" mode 4. Set a dashboard password (required for network access) ### 3. Access from External Device ``` http://[your-mac-ip]:4020 ``` ## Common Issues and Solutions ### Cannot Connect from External Device **Check network connectivity:** * Ensure both devices are on the same Wi-Fi network * Verify the IP address is correct * Try pinging your Mac from the external device **Check firewall settings:** * macOS may block incoming connections * When prompted, click "Allow" for Node.js/Bun * Check System Preferences → Security & Privacy → Firewall **Verify server is running:** ```bash theme={null} # Check if the port is listening lsof -i :4021 ``` ### Changes Not Appearing **For development server:** * Hot reload should work automatically * Try hard refresh on the external device (Cmd+Shift+R on Safari) * Check the terminal for build errors **For production build:** * You must rebuild after each change: `pnpm run build` * Restart the VibeTunnel server after building * Clear browser cache on the external device ### Safari-Specific Issues **Enable Developer Mode on iOS/iPadOS:** 1. Settings → Safari → Advanced → Web Inspector (ON) 2. Connect device to Mac via USB 3. Open Safari on Mac → Develop menu → \[Your Device] 4. Select the page to inspect **Common Safari quirks:** * Different touch event handling * Stricter security policies * Different viewport behavior * WebSocket connection issues ## Advanced Testing Scenarios ### Testing with HTTPS Some features may require HTTPS. Use ngrok for secure tunneling: ```bash theme={null} # Install ngrok brew install ngrok # Create tunnel to dev server ngrok http 4021 ``` ### Testing Different Network Conditions Use Chrome DevTools or Safari Web Inspector to simulate: * Slow network connections * Offline mode * Different device viewports ### Multi-Device Testing Test on multiple devices simultaneously: ```bash theme={null} # Terminal 1: Development server pnpm run dev --port 4021 --bind 0.0.0.0 # Terminal 2: Production server (if needed) # VibeTunnel app handles this automatically ``` ## Security Considerations **Development only:** * Only use `--bind 0.0.0.0` on trusted networks * The dev server has no authentication * Consider using a firewall to restrict access **Production testing:** * Always set a dashboard password * Use Tailscale or ngrok for remote access * Never expose unprotected servers to the internet ## Debugging Tips ### Console Access on Mobile **Safari on iOS/iPadOS:** 1. Enable Web Inspector (see above) 2. Connect device to Mac 3. Use Safari Developer Tools **Chrome on Android:** 1. Enable Developer Options 2. Connect via USB 3. Open chrome://inspect on desktop Chrome ### Network Debugging Monitor network requests: ```bash theme={null} # Watch incoming connections sudo lsof -i -P | grep LISTEN | grep 4021 # Monitor HTTP traffic sudo tcpdump -i en0 port 4021 ``` ### Performance Profiling Use browser developer tools to: * Profile JavaScript performance * Analyze rendering bottlenecks * Check memory usage * Monitor WebSocket traffic ## Best Practices 1. **Always test on real devices** - Emulators don't catch all issues 2. **Test on multiple browsers** - Safari, Chrome, Firefox behave differently 3. **Check different orientations** - Portrait and landscape modes 4. **Test with poor network** - Not everyone has fast Wi-Fi 5. **Verify touch interactions** - Mouse events ≠ touch events 6. **Check responsive design** - Different screen sizes and resolutions ## Related Documentation * [README.md](../README.md) - General setup and usage * [CONTRIBUTING.md](CONTRIBUTING.md) - Development workflow * [spec.md](spec.md) - Technical specification * [architecture.md](architecture.md) - System architecture # Architecture mario Source: https://docs.vibetunnel.sh/docs/architecture-mario # VibeTunnel Architecture Analysis - Mario's Technical Deep Dive This document contains comprehensive technical insights from Mario's debugging session about VibeTunnel's architecture, critical performance issues, and detailed solutions. ## Executive Summary Mario identified two critical issues causing performance problems in VibeTunnel: 1. **850MB Session Bug**: External terminal sessions (via `vibetunnel-fwd`) bypass the clear sequence truncation in `stream-watcher.ts`, sending entire gigabyte files instead of the last 2MB 2. **Resize Loop**: Claude terminal app issues full clear sequence (`\x1b[2J`) and re-renders entire scroll buffer on every resize event, creating exponential data growth Note: A third issue with Node-PTY's shared pipe architecture causing Electron crashes has already been resolved with a custom PTY implementation. ## System Architecture ### Core Components ``` ┌─────────────┐ ┌──────────────┐ ┌─────────────┐ │ Client │────▶│ Web Server │────▶│ PTY Process │ └─────────────┘ └──────────────┘ └─────────────┘ │ │ │ │ ▼ ▼ │ ┌──────────┐ ┌──────────┐ └─────────────▶│ Terminal │ │ Ascinema │ │ Manager │ │ Files │ └──────────┘ └──────────┘ ``` ### Detailed Sequence Flow ```mermaid theme={null} sequenceDiagram participant UI as Browser/Electron participant WS as WebSocket participant Server as Node Server participant PTY as PTYManager participant FWD as vibetunnel-fwd participant Proc as User Process UI->>WS: keystrokes WS->>Server: /api/sessions/:id/input Server->>PTY: IPC Socket message PTY->>Proc: write to stdin Proc-->>PTY: stdout (ANSI sequences) PTY-->>Server: write to ascinema file alt External Terminal FWD-->>TTY: mirror stdout to terminal end Server-->>UI: WS v3 stream (truncated + multiplexed) ``` ### Key Files and Their Roles | File | Purpose | Critical Functions | | --------------------- | ---------------------------- | ----------------------------------------------------- | | `server.ts` | Main web server | HTTP endpoints, WebSocket handling | | `pty-manager.ts` | PTY lifecycle management | `createSession()`, `setupPtyHandlers()` | | `stream-watcher.ts` | Monitors ascinema files | `sendExistingContent()` - implements clear truncation | | `vibetunnel-fwd` | External terminal forwarding | Process spawning, **BYPASSES TRUNCATION** | | `terminal-manager.ts` | Binary buffer rendering | Converts ANSI to binary cells format | ### Data Flow Paths #### Input Path (Keystroke → Terminal) 1. Browser captures key press 2. WebSocket v3 sends to `/ws` (`INPUT_TEXT`/`INPUT_KEY`/`RESIZE`) 3. Server writes to IPC socket 4. PTY Manager writes to process stdin 5. Process executes command #### Output Path (Terminal → Browser) 1. Process writes to stdout 2. PTY Manager captures via `onData` handler 3. Writes to asciinema cast file (with write queue for backpressure) 4. CastOutputHub tails file + scans for last clear sequence (pruning) 5. Client receives via `/ws` v3: * `STDOUT` (UTF-8 bytes) * `SNAPSHOT_VT` (server-rendered VT snapshot bytes) ### Binary Cell Buffer Format The terminal manager pre-renders terminal output into a binary format for efficient transmission: ``` For each cell at (row, column): - Character (UTF-8 encoded) - Foreground color (RGB values) - Background color (RGB values) - Attributes (bold, italic, underline, etc.) ``` Benefits: * Server-side ANSI parsing eliminates client CPU usage * Efficient binary transmission reduces bandwidth * Only last 10,000 lines kept in memory * Client simply renders pre-computed cells ## Critical Bugs Analysis ### 1. The 850MB Session Loading Bug **Symptom**: Sessions with large output (850MB+) cause infinite loading and browser unresponsiveness. **Root Cause**: External terminal sessions via `vibetunnel-fwd` bypass the clear sequence truncation logic. **Technical Details**: ```javascript theme={null} // In stream-watcher.ts - WORKING CORRECTLY sendExistingContent() { // Scans backwards for last clear sequence const lastClear = content.lastIndexOf('\x1b[2J'); // Sends only content after clear return content.slice(lastClear); // 2MB instead of 850MB } ``` **Evidence from Testing**: * Test file: 980MB containing 2,400 clear sequences * Server-created sessions: Correctly send only last 2MB * External terminal sessions: Send entire 980MB file * Processing time: 2-3 seconds to scan 1GB file * Client receives instant replay for 2MB truncated content **The Issue**: External terminal path doesn't trigger `sendExistingContent()`, sending gigabyte files to clients. ### 2. Resize Event Performance Catastrophe **Problem**: Each resize event causes Claude to re-render the entire terminal history. **Claude's Behavior**: ``` 1. Resize event received 2. Claude issues clear sequence: \x1b[2J 3. Re-renders ENTIRE scroll buffer from line 1 4. Rendering causes viewport changes 5. Viewport changes trigger resize event 6. GOTO step 1 (infinite loop) ``` **Technical Evidence**: * In 850MB session: each resize → full buffer re-render * Claude renders from "Welcome to Claude" message every time * Mobile UI particularly problematic (frequent resize events) * Header button position shifts during rendering indicate viewport instability * Session with 39 resize events can generate 850MB+ files **Contributing Factors**: * React Ink (TUI framework) unnecessarily re-renders entire components * Session-detail-view has buggy resize observer * Mobile Safari behaves differently than desktop at same viewport size * Touch events vs mouse events complicate scrolling behavior ### 3. Node-PTY Architecture Flaw (ALREADY FIXED) This issue has been resolved by implementing a custom PTY solution without the shared pipe architecture. ## Ascinema Format Details VibeTunnel uses the ascinema format for recording terminal sessions: ```javascript theme={null} // Format: [timestamp, event_type, data] [1.234, "o", "Hello World\n"] // Output event [1.235, "i", "k"] // Input event (keypress) [1.236, "r", "80x24"] // Resize event ``` Clear sequence detection: ```javascript theme={null} const CLEAR_SEQUENCE = '\x1b[2J'; // ANSI clear screen const CLEAR_WITH_HOME = '\x1b[H\x1b[2J'; // Home + clear function findLastClearSequence(buffer) { // Search from end for efficiency let lastClear = buffer.lastIndexOf(CLEAR_SEQUENCE); return lastClear === -1 ? 0 : lastClear; } ``` ## Proposed Solutions ### Priority 1: Fix External Terminal Clear Truncation (IMMEDIATE) **Problem**: External terminal sessions don't use `sendExistingContent()` truncation. **Investigation Needed**: 1. Trace how `vibetunnel-fwd` connects to client streams 2. Determine why it bypasses stream-watcher's truncation 3. Ensure external terminals use same code path as server sessions 4. Test with 980MB file to verify fix **Expected Impact**: Immediate fix for users experiencing infinite loading with large sessions. ### Priority 2: Fix Resize Handling (INVESTIGATION) **Debugging Approach**: 1. Instrument session-detail-view with resize observer logging 2. Identify what causes viewport expansion 3. Implement resize event debouncing 4. Fix mobile-specific issues: * Keyboard state affects scrolling * Touch vs mouse event handling * Scrollbar visibility problems **Code to Add**: ```javascript theme={null} // Add to session-detail-view let resizeCount = 0; new ResizeObserver((entries) => { console.log(`Resize ${++resizeCount}:`, entries[0].contentRect); // Debounce resize events clearTimeout(this.resizeTimeout); this.resizeTimeout = setTimeout(() => { this.handleResize(); }, 100); }).observe(this.terminalElement); ``` ## Implementation Details ### Write Queue Implementation The PTY manager implements backpressure handling: ```javascript theme={null} class WriteQueue { constructor(writer) { this.queue = []; this.writing = false; this.writer = writer; } async write(data) { this.queue.push(data); if (!this.writing) { await this.flush(); } } async flush() { this.writing = true; while (this.queue.length > 0) { const chunk = this.queue.shift(); await this.writer.write(chunk); } this.writing = false; } } ``` ### Platform-Specific Considerations **macOS**: * Screen Recording permission required for terminal access * Terminal.app specific behaviors and quirks **Mobile Safari**: * Different behavior than desktop Safari at same viewport * Touch events complicate scrolling * Keyboard state affects scroll behavior * Missing/hidden scrollbars * Viewport meta tag issues **Windows (Future)**: * ConPTY vs WinPTY support * Different ANSI sequence handling * Path normalization requirements ## Performance Metrics | Metric | Current | After Fix | | -------------------------- | ------------------ | ------------------- | | 980MB session initial load | Infinite/Crash | 2-3 seconds | | Data sent to client | 980MB | 2MB | | Memory per terminal | 50-100MB | Target: 10MB | | Clear sequence scan time | N/A | \~2 seconds for 1GB | | Resize event storms | Exponential growth | Debounced | ## Testing and Debugging ### Test Large Session Handling ```bash theme={null} # Create large session file cd web npm run dev # In another terminal, create session SESSION_ID=$(curl -X POST localhost:3000/api/sessions | jq -r .id) # Stop server, inject large file cp /path/to/850mb-test-file ~/.vibetunnel/sessions/$SESSION_ID/stdout # Restart and verify truncation works npm run dev ``` ### Debug Resize Events ```javascript theme={null} // Add to any component to detect resize loops window.addEventListener('resize', () => { console.count('resize'); console.trace('Resize triggered from:'); }); ``` ### Monitor Network Traffic * Check `/ws` traffic (v3 frames) for `STDOUT` and `SNAPSHOT_VT` * Verify pruning works (no replay before last clear) * Use `/api/sessions/:id/snapshot` for snapshot/export debugging ## Architectural Insights ### Why Current Architecture Works (When Not Bugged) 1. **Simplicity**: "Es ist die todeleinfachste Variante" - It's the simplest possible approach 2. **Efficiency**: 2MB instead of 980MB transmission after clear sequence truncation 3. **Server-side rendering**: Binary cell format eliminates client ANSI parsing ### Community Contribution Challenges * High development velocity makes contribution difficult * "Velocity kills" - rapid changes discourage contributors * LitElement/Web Components unfamiliar to most developers * Large file sizes cause AI tools to refuse processing ### Future Architecture Considerations **Go Migration Benefits**: * Automatic test dependency tracking * Only runs tests that changed * Pre-allocated buffers minimize GC * Better suited for AI-assisted development **Rust Benefits**: * 2MB static binary * 10MB RAM usage * Direct C interop for PTY code * No garbage collection overhead ## Action Plan Summary 1. **Immediate (End of Week)**: Fix external terminal truncation bug * Debug why `vibetunnel-fwd` bypasses `sendExistingContent()` * Deploy fix for immediate user relief 2. **Short Term**: Comprehensive resize fix * Debug session-detail-view triggers * Implement proper debouncing * Fix mobile-specific issues 3. **Long Term**: Consider architecture migration * Evaluate Rust forward binary * Consider Go for web server * Maintain backwards compatibility ## Key Technical Quotes * "Wir schicken 2MB statt 980MB" - We send 2MB instead of 980MB * "Die haben einen Shared Pipe, wo alle reinschreiben" - They have a shared pipe where everyone writes * "Es gibt keinen Grund, warum ich von da weg alles neu rendern muss" - There's no reason to re-render everything from the beginning * "Das ist known good" - Referring to battle-tested implementations This architecture analysis provides the technical foundation for fixing VibeTunnel's critical performance issues while maintaining its elegant simplicity. # Auggie Source: https://docs.vibetunnel.sh/docs/auggie # Auggie CLI VibeTunnel can monitor and control Augment Code's Auggie CLI like other terminal-based coding agents. Auggie sessions are recognized as AI assistant sessions in both the web dashboard and macOS menu, including the action that asks the agent to update its terminal title. ## Install Auggie Auggie requires Node.js 20 or later. Install the CLI from npm: ```bash theme={null} npm install -g @augmentcode/auggie ``` Authenticate once: ```bash theme={null} auggie login ``` See Augment's [installation](https://docs.augmentcode.com/cli/setup-auggie/install-auggie-cli) and [authentication](https://docs.augmentcode.com/cli/setup-auggie/authentication) guides for current requirements. ## Run with VibeTunnel Start an interactive Auggie session through the `vt` wrapper: ```bash theme={null} cd /path/to/project vt auggie ``` You can also pass an initial prompt: ```bash theme={null} vt auggie "Review the current changes" ``` The session then appears in VibeTunnel's web, macOS, and mobile clients. Terminal input, output, reconnection, and session controls work the same as for other interactive CLI tools. ## Quick Start `auggie` is included in the default Quick Start command list. Existing customized lists are preserved; add `auggie` in **Settings > Quick Start** or reset the list to defaults to expose the button. ## Terminal Titles For an active Auggie session, use the wand action in the web dashboard or macOS menu. VibeTunnel sends a prompt asking Auggie to run: ```bash theme={null} vt title "Brief description of current task" ``` This replaces a generic process name with the agent's current task in session lists. # Authentication Source: https://docs.vibetunnel.sh/docs/authentication # VibeTunnel Authentication System VibeTunnel supports multiple authentication modes to balance security and convenience for different use cases. ## Authentication Modes ### 1. Default Mode (Password Authentication) **Usage:** Start VibeTunnel without any auth flags ```bash theme={null} npm run dev # or ./vibetunnel ``` **Behavior:** * Shows login page with user avatar (on macOS) * Requires system user password authentication * Uses JWT tokens for session management * SSH key functionality is hidden **Best for:** Personal use with secure password authentication ### 2. SSH Key Mode **Usage:** Enable SSH key authentication alongside password ```bash theme={null} npm run dev -- --enable-ssh-keys # or ./vibetunnel --enable-ssh-keys ``` **Behavior:** * Shows login page with both password and SSH key options * Users can generate Ed25519 SSH keys in the browser * SSH keys are stored securely in browser localStorage * Optional password protection for private keys * SSH keys work for both web and terminal authentication **Best for:** Power users who prefer SSH key authentication ### 3. SSH Keys Only Mode **Usage:** Disable password authentication, SSH keys only ```bash theme={null} ./vibetunnel --disallow-user-password # or ./vibetunnel --disallow-user-password --enable-ssh-keys # redundant, auto-enabled ``` **Behavior:** * Shows login page with SSH key options only * Password authentication form is hidden * Automatically enables `--enable-ssh-keys` * User avatar still displayed with "SSH key authentication required" message * Most secure authentication mode **Best for:** High-security environments, organizations requiring key-based auth ### 4. No Authentication Mode **Usage:** Disable authentication completely ```bash theme={null} npm run dev -- --no-auth # or ./vibetunnel --no-auth ``` **Behavior:** * Bypasses login page entirely * Direct access to dashboard * No authentication required * Auto-logs in as current system user * **Overrides all other auth flags** **Best for:** Local development, trusted networks, or demo environments ### 5. Tailscale Serve Integration Mode **Usage:** Enable integrated Tailscale Serve support ```bash theme={null} npm run dev -- --enable-tailscale-serve # or ./vibetunnel --enable-tailscale-serve ``` **Behavior:** * Automatically starts `tailscale serve` as a background process * Forces server to bind to localhost (127.0.0.1) for security * Enables Tailscale identity header authentication * Provides HTTPS access without exposing ports * No manual Tailscale configuration required **macOS App Integration:** * Toggle available in Settings → Remote Access → Tailscale Integration * Shows HTTPS URL in menu bar when enabled * Automatically manages the Tailscale Serve process lifecycle **Security Model:** * Server only listens on localhost when enabled * All external access goes through Tailscale's secure proxy * Identity headers are automatically validated * No risk of header spoofing from external sources **Best for:** Easy, secure remote access through Tailscale network ## User Avatar System ### macOS Integration On macOS, VibeTunnel automatically displays the user's system profile picture: * **Data Source:** Uses `dscl . -read /Users/$USER JPEGPhoto` to extract avatar * **Format:** Converts hex data to base64 JPEG * **Fallback:** Uses `Picture` attribute if JPEGPhoto unavailable * **Display:** Shows in login form with welcome message ### Other Platforms On non-macOS systems: * Displays a generic SVG avatar icon * Maintains consistent UI layout * No system integration required ## Command Line Options ### Server Startup Flags ```bash theme={null} # Authentication options --enable-ssh-keys Enable SSH key authentication UI and functionality --disallow-user-password Disable password auth, SSH keys only (auto-enables --enable-ssh-keys) --no-auth Disable authentication (auto-login as current user) --enable-tailscale-serve Enable Tailscale Serve integration (auto-starts proxy, forces localhost) # Other options --port Server port (default: 4020) --bind
Bind address (default: 0.0.0.0) --debug Enable debug logging ``` ### Example Commands ```bash theme={null} # Default password-only authentication npm run dev # Enable SSH keys alongside password npm run dev -- --enable-ssh-keys # SSH keys only (most secure) ./vibetunnel --disallow-user-password # No authentication for local development (npm run dev uses this by default) npm run dev -- --no-auth # Production with SSH keys on custom port ./vibetunnel --enable-ssh-keys --port 8080 # High-security production (SSH keys only) ./vibetunnel --disallow-user-password --port 8080 # Tailscale Serve integration (secure remote access) ./vibetunnel --enable-tailscale-serve --port 4020 # No manual configuration needed - everything handled automatically ``` ## Security Considerations ### Password Authentication * Uses system PAM authentication * Uses the configured VibeTunnel username and password instead when both `VIBETUNNEL_USERNAME` and `VIBETUNNEL_PASSWORD` are set * Sends the password to the VibeTunnel host over the current connection for verification * Does not persist the password * Use an encrypted connection; SSH key mode avoids sending a password through the browser * Otherwise validates against actual system user passwords * JWT tokens expire after 24 hours * Secure session management ### SSH Key Authentication * Generates Ed25519 keys (most secure) * Private keys stored in browser localStorage * Optional password protection for private keys * Keys work for both web and terminal access * Challenge-response authentication flow ### No Authentication Mode * **⚠️ Security Warning:** Only use in trusted environments * Suitable for local development or demo purposes * Not recommended for production or public networks ### Tailscale Authentication * **⚠️ Security Warning:** Only use when bound to localhost * Requires Tailscale Serve proxy for header injection * Provides SSO-like experience for Tailscale users * Headers are trusted only from Tailscale proxy ## Configuration API ### Frontend Configuration Endpoint The frontend can query the server's authentication configuration: ```javascript theme={null} // GET /api/auth/config { "enableSSHKeys": false, "disallowUserPassword": false, "noAuth": false } ``` This allows the UI to: * Show/hide SSH key options dynamically * Hide password form when disallowed * Skip login page when no-auth is enabled * Adapt interface based on server configuration ## SSH Key Management ### Key Generation Process * **Algorithm:** Ed25519 (most secure and modern SSH key type) * **Browser Implementation:** Uses Web Crypto API for secure key generation * **Storage:** Browser localStorage (optionally encrypted with user password) * **Format:** PEM format for compatibility with standard SSH tools * **Naming:** User-defined names for organization **Detailed Process:** 1. Browser generates Ed25519 key pair using `crypto.subtle.generateKey()` 2. Private key optionally encrypted with user-provided password 3. Public key formatted in SSH wire format for server validation 4. Keys stored in browser localStorage with unique identifiers ### Key Import * Supports unencrypted Ed25519 keys in OpenSSH or PKCS#8 PEM format * Derives and validates the real public key before storing the private key * Rejects encrypted, malformed, truncated, or internally inconsistent keys with a clear error * Compatibility with keys generated by `ssh-keygen -t ed25519` ### SSH Key Authentication Flow **Challenge-Response Process:** 1. **Challenge Request:** Client requests authentication challenge from `/api/auth/challenge` 2. **Challenge Generation:** Server creates 32-byte random challenge with 5-minute expiry 3. **Key Selection:** Client selects appropriate SSH key from browser storage 4. **Signature Creation:** Browser signs challenge using private key via Web Crypto API 5. **Signature Submission:** Client sends signed challenge to `/api/auth/ssh-login` 6. **Server Verification:** * Server parses SSH public key wire format * Validates signature using Node.js crypto module * Checks public key against user's `~/.ssh/authorized_keys` * Issues JWT token upon successful verification **Key Authorization:** * Server reads `~/.ssh/authorized_keys` file for target user * Validates submitted public key is present in authorized keys * Supports both current user and other system users * Handles standard SSH authorized\_keys format ### Key Setup Instructions **For Users:** 1. Generate SSH key in VibeTunnel web interface 2. Download public key file 3. Add to server's authorized\_keys: ```bash theme={null} # Append public key to authorized_keys cat vibetunnel-key.pub >> ~/.ssh/authorized_keys # Set proper permissions chmod 600 ~/.ssh/authorized_keys chmod 700 ~/.ssh ``` 4. Test authentication through VibeTunnel login **Security Best Practices:** * Use password protection for private keys in shared environments * Regularly rotate SSH keys (recommended: every 90 days) * Remove unused keys from authorized\_keys * Monitor authentication logs for suspicious activity ## Tailscale Authentication Details ### How Tailscale Serve Works Tailscale Serve acts as a reverse proxy that: 1. Receives requests from your tailnet 2. Adds identity headers based on the authenticated Tailscale user 3. Forwards requests to your local service ### Identity Headers When a request comes through Tailscale Serve, these headers are added: * `tailscale-user-login`: The user's email address or login * `tailscale-user-name`: The user's display name * `tailscale-user-profile-pic`: URL to the user's profile picture ### Setup Instructions 1. **Start VibeTunnel with integrated Tailscale Serve:** ```bash theme={null} ./vibetunnel --enable-tailscale-serve --port 4020 ``` Or use the macOS app and enable the toggle in Settings → Remote Access 2. **Access via Tailscale:** ``` https://[your-machine-name].[tailnet-name].ts.net ``` ### Security Model * VibeTunnel trusts identity headers ONLY from localhost connections * Tailscale Serve ensures headers cannot be spoofed by external users * Direct access to VibeTunnel port would allow header forgery * Always bind to `127.0.0.1` when using Tailscale authentication ### Integration with Other Auth Modes Tailscale Serve integration can be combined with other authentication modes: ```bash theme={null} # Tailscale Serve + SSH keys as fallback ./vibetunnel --enable-tailscale-serve --enable-ssh-keys # Tailscale Serve + local bypass for scripts ./vibetunnel --enable-tailscale-serve --allow-local-bypass ``` **Note**: The `--enable-tailscale-serve` flag automatically manages both the Tailscale proxy and authentication. ## Implementation Details ### Authentication Flow 1. **Server startup** determines available auth modes 2. **Frontend queries** `/api/auth/config` for configuration 3. **UI renders** appropriate authentication options 4. **User authenticates** via chosen method 5. **JWT token issued** for session management 6. **Subsequent requests** use Bearer token authentication ### Avatar Implementation ```bash theme={null} # macOS avatar extraction dscl . -read /Users/$USER JPEGPhoto | tail -1 | xxd -r -p > avatar.jpg # Server endpoint GET /api/auth/avatar/:userId ``` ### File Structure ``` src/ ├── server/ │ ├── middleware/auth.ts # Authentication middleware │ ├── routes/auth.ts # Authentication routes │ ├── services/auth-service.ts # JWT and user management │ └── server.ts # Server configuration └── client/ ├── components/auth-login.ts # Login UI component ├── services/auth-client.ts # Frontend auth service └── services/ssh-agent.ts # SSH key management ``` ## Troubleshooting ### Common Issues **Login page shows briefly then disappears (no-auth mode)** * This is expected behavior - the page quickly redirects to dashboard **SSH section not showing** * Ensure server started with `--enable-ssh-keys` flag * Check browser console for configuration loading errors **Avatar not displaying** * macOS only feature - other platforms show generic icon * Check user has profile picture set in System Preferences **Authentication fails** * Verify system password is correct * Check server logs for detailed error messages * Ensure proper permissions for PAM authentication ### Debug Mode Enable debug logging for detailed authentication flow: ```bash theme={null} npm run dev -- --debug --enable-ssh-keys ``` This provides verbose logging of: * Authentication attempts * Token validation * SSH key operations * Configuration loading # Changelog management Source: https://docs.vibetunnel.sh/docs/changelog-management # Changelog Management Guide This guide explains how to maintain the CHANGELOG.md and GitHub releases for VibeTunnel. ## Overview We maintain a comprehensive changelog that: * Documents all user-facing changes * Credits contributors properly with GitHub links * Tracks first-time contributors for each release * Provides clear, user-friendly descriptions ## Using the `/changelog` Command The `/changelog` command in Claude Code analyzes git history to generate comprehensive changelogs: ```bash theme={null} # In Claude Code, simply type: /changelog ``` This command: * Analyzes commit history beyond just titles * Examines actual file changes to understand user impact * Groups changes by category (Features, Bug Fixes, Performance, etc.) * Writes user-focused descriptions (not developer jargon) ## Changelog Format ### Version Header ```markdown theme={null} ## [1.0.0-beta.13] - 2024-12-20 ``` ### Categories * **Features** - New functionality * **Improvements** - Enhancements to existing features * **Bug Fixes** - Fixed issues * **Performance** - Speed/efficiency improvements * **Developer Experience** - Build, test, or development improvements ### Attribution Format Every change should credit its contributor: ```markdown theme={null} - Added systemd service management for Linux deployments (via [@hewigovens](https://github.com/hewigovens)) (#419) ``` Format: `- Description (via [@username](https://github.com/username)) (#PR)` ### First-time Contributors Section For releases with new contributors: ```markdown theme={null} ### First-time Contributors - [@hewigovens](https://github.com/hewigovens) - Added systemd service management for Linux (#419) ``` ## Identifying Contributors ### Finding First-time Contributors ```bash theme={null} # Get all contributors up to a specific release git log --format="%an|%ae" v1.0.0-beta.12 | sort -u # Get contributors for a specific release git log --format="%an|%ae" v1.0.0-beta.12..v1.0.0-beta.13 | sort -u # Compare to find first-timers ``` ### Mapping Changes to Contributors ```bash theme={null} # Find who made specific changes git log --oneline --author="username" v1.0.0-beta.12..v1.0.0-beta.13 # Get detailed commit info with files git log --stat --author="username" v1.0.0-beta.12..v1.0.0-beta.13 ``` ## Special Cases ### Bot Contributors Do not highlight bot contributors as first-time contributors or include them in the contributors list: * `devin-ai-integration[bot]` * `blacksmith-sh[bot]` * Other `*[bot]` accounts **Important**: Bot contributions should be completely excluded from: * First-time contributors sections * The main contributors list at the end of CHANGELOG.md * GitHub release notes contributors sections Bot changes can be mentioned in regular changelog entries (e.g., "Added SwiftLint hooks") but without attribution. ### Core Team Core team members (repository owners) don't need "(via @username)" attribution unless specifically requested. ### Multiple Contributors If multiple people worked on a feature: ```markdown theme={null} - Feature description (via [@user1](https://github.com/user1), [@user2](https://github.com/user2)) (#123) ``` ## GitHub Releases ### Creating a Release 1. **Generate changelog** using `/changelog` command 2. **Review and edit** the generated content 3. **Update CHANGELOG.md** with the new version section 4. **Create GitHub release**: ```bash theme={null} gh release create v1.0.0-beta.14 \ --title "v1.0.0-beta.14" \ --notes-file release-notes.md \ --prerelease ``` ### Release Notes Format The GitHub release should include: 1. **Highlights** - 2-3 major changes 2. **Full changelog** - Copy from CHANGELOG.md 3. **First-time contributors** - If applicable 4. **Installation instructions** - Brief reminder Example: ```markdown theme={null} ## Highlights - 🐧 Linux systemd service support for production deployments - 🔧 Improved authentication reliability - 🚀 Better performance for large terminal outputs ## What's Changed [Copy from CHANGELOG.md] ## First-time Contributors - @hewigovens made their first contribution in #419 ## Installation See [installation instructions](https://github.com/vibetunnel/vibetunnel#installation) ``` ### Updating Existing Releases To add first-time contributors to existing releases: ```bash theme={null} # Edit a release gh release edit v1.0.0-beta.13 --notes-file updated-notes.md # Or use the GitHub web UI ``` ## Workflow Summary 1. **Before release**: Run `/changelog` to analyze changes 2. **Review output**: Ensure proper attribution and user-friendly descriptions 3. **Update CHANGELOG.md**: Add new version section with proper formatting 4. **Create release notes**: Include highlights and first-time contributors 5. **Create GitHub release**: Use `gh release create` or web UI 6. **Verify**: Check that all contributors are properly credited ## Tips * Always verify contributor GitHub usernames for correct links * Use clear, non-technical language in descriptions * Include PR numbers for traceability * Group related changes together * Highlight breaking changes prominently * Credit everyone who contributed, no matter how small ## Example Workflow ```bash theme={null} # 1. Generate changelog /changelog # 2. Create release notes file cat > release-notes.md << 'EOF' ## Highlights - 🎯 Major feature one - 🐛 Critical bug fix - ⚡ Performance improvement ## What's Changed [Paste from CHANGELOG.md] ## First-time Contributors - @newcontributor made their first contribution in #123 ## Installation See [installation instructions](https://github.com/vibetunnel/vibetunnel#installation) EOF # 3. Create release gh release create v1.0.0-beta.14 \ --title "v1.0.0-beta.14" \ --notes-file release-notes.md \ --prerelease # 4. Clean up rm release-notes.md ``` # Cjk ime input Source: https://docs.vibetunnel.sh/docs/cjk-ime-input # VibeTunnel CJK IME Input Implementation ## Overview VibeTunnel provides comprehensive Chinese, Japanese, and Korean (CJK) Input Method Editor (IME) support across both desktop and mobile platforms. The implementation uses platform-specific approaches to ensure optimal user experience: * **Desktop**: Cursor-positioned text input with native browser IME integration * **Mobile**: Native virtual keyboard with direct input handling ## Architecture ### Core Components ``` SessionView ├── InputManager (Main input coordination layer) │ ├── Platform detection (mobile vs desktop) │ ├── DesktopIMEInput component integration (desktop only) │ ├── Keyboard input handling │ ├── WebSocket/HTTP input routing │ └── Terminal cursor position access ├── DesktopIMEInput (Desktop-specific IME component) │ ├── Cursor-positioned input element creation │ ├── IME composition event handling │ ├── Global paste handling │ ├── Dynamic cursor positioning │ └── Focus management ├── DirectKeyboardManager (Mobile input handling) │ ├── Native virtual keyboard integration │ ├── Direct input processing │ └── Quick keys toolbar ├── LifecycleEventManager (Event interception & coordination) └── Terminal Components (Cursor position providers) ``` ## Implementation Details ### Cursor Position Tracking **File**: `cursor-position.ts` The cursor position tracking system uses renderer-specific cursor coordinates: #### Coordinate System ```typescript theme={null} export function calculateCursorPosition( cursorX: number, // 0-based column position cursorY: number, // 0-based row position fontSize: number, // Terminal font size in pixels container: Element, // Terminal container element sessionStatus: string, // Session status for validation ): { x: number; y: number } | null; ``` #### Position Calculation Process 1. **Character Measurement**: Dynamically measures actual character width using font metrics 2. **Absolute Positioning**: Calculates page-absolute cursor coordinates 3. **Container Relative**: Converts to position relative to `#session-terminal` container 4. **IME Positioning**: Returns coordinates suitable for IME input placement #### Terminal Type Support * **Ghostty Terminal (`vibe-terminal`)**: Uses the active buffer cursor and renderer cell metrics. * **Buffer Terminal (`vibe-terminal-buffer`)**: Uses `buffer.cursorX/Y` from VT snapshot data. #### Key Features * **Precise Alignment**: Accounts for exact character width and line height * **Container Aware**: Handles side panels and complex layouts * **Font Responsive**: Adapts to different font sizes and families * **Platform Consistent**: Same calculation logic across all terminal types #### Error Handling The function includes comprehensive error handling and graceful fallbacks: * Returns `null` when session is not running * Returns `null` when container element is not found * Returns `null` when character measurement fails * Falls back to absolute coordinates if session container is missing ### Platform Detection **File**: `mobile-utils.ts` VibeTunnel automatically detects the platform and chooses the appropriate IME strategy: ```typescript theme={null} export function detectMobile(): boolean { return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test( navigator.userAgent, ); } ``` ### Desktop Implementation #### 1. DesktopIMEInput Component **File**: `ime-input.ts` A dedicated component for desktop browsers that creates and manages a native text input: * Positioned dynamically at terminal cursor location * Uses a normal input size so browsers can anchor native candidate windows reliably * Transparent background and hidden caret keep the terminal visually unobstructed * Handles all CJK composition events through standard DOM APIs * Focuses when the user clicks the terminal, with short animation-frame retries for browser timing * Clean lifecycle management with proper cleanup #### 2. Desktop Input Manager Integration **File**: `input-manager.ts` The `InputManager` detects platform and creates the appropriate IME component: ```typescript theme={null} private setupIMEInput(): void { // Skip IME input setup on mobile devices (they use native keyboard) if (detectMobile()) { logger.log('Skipping IME input setup on mobile device'); return; } // Create desktop IME input component this.imeInput = new DesktopIMEInput({ container: terminalContainer, onTextInput: (text: string) => this.sendInputText(text), onSpecialKey: (key: string) => this.sendInput(key), getCursorInfo: () => { const terminalElement = this.callbacks?.getTerminalElement?.(); if ( terminalElement && 'getCursorInfo' in terminalElement && typeof terminalElement.getCursorInfo === 'function' ) { return terminalElement.getCursorInfo(); } return null; } }); } ``` #### 3. Desktop Focus Management **File**: `ime-input.ts` Desktop IME focus follows terminal clicks. The implementation avoids polling because repeatedly stealing focus interferes with native candidate selection: ```typescript theme={null} focus(): void { this.updatePosition(); this.input.focus(); requestAnimationFrame(() => { if (document.activeElement !== this.input) { requestAnimationFrame(() => this.input.focus()); } }); } ``` ### Mobile Implementation #### 1. Direct Keyboard Manager **File**: `direct-keyboard-manager.ts` Mobile devices use the native virtual keyboard with a visible input field: * Standard HTML input element (not hidden) * Native virtual keyboard with CJK support * Quick keys toolbar for common terminal operations * No special IME handling needed (OS provides it) #### 2. Mobile Input Flow **Files**: `session-view.ts`, `lifecycle-event-manager.ts` Mobile input handling follows a different flow: 1. User taps terminal area 2. Native virtual keyboard appears with CJK support 3. User types or selects from IME candidates 4. Input is sent directly to terminal 5. No desktop composition bridge is needed ## Platform Differences ### Key Implementation Differences | Aspect | Desktop | Mobile | | -------------------- | -------------------------------- | ---------------------------- | | **Input Element** | Cursor-positioned standard input | Visible standard input field | | **IME Handling** | Custom composition events | Native OS keyboard | | **Positioning** | Follows terminal cursor | Fixed position or overlay | | **Focus Management** | Click focus with bounded retries | Standard focus behavior | | **Keyboard** | Physical + software IME | Virtual keyboard with IME | | **Integration** | Transparent terminal overlay | Visible UI component | | **Performance** | Minimal overhead | Standard input performance | ### Technical Architecture Differences #### Desktop Implementation ```typescript theme={null} // Creates a browser-compatible input at the terminal cursor const input = document.createElement("input"); input.style.width = "200px"; input.style.height = "24px"; input.style.backgroundColor = "transparent"; input.style.caretColor = "transparent"; // Handles IME composition events input.addEventListener("compositionstart", handleStart); input.addEventListener("compositionend", handleEnd); // Positions at terminal cursor input.style.left = `${cursorX}px`; input.style.top = `${cursorY}px`; ``` #### Mobile Implementation ```typescript theme={null} // Uses DirectKeyboardManager with visible input const input = document.createElement("input"); input.type = "text"; input.placeholder = "Type here..."; // Standard visible input - no special IME handling needed // OS handles IME automatically through virtual keyboard // No composition event handling required ``` ### User Experience Differences #### Desktop Experience * **Seamless**: No visible UI changes * **Cursor following**: IME popup appears at terminal cursor * **Click to focus**: Click anywhere in terminal area * **Traditional**: Works like native terminal IME * **Paste support**: Global paste handling anywhere in terminal #### Mobile Experience * **Touch-first**: Designed for finger interaction * **Visible input**: Clear indication of where to type * **Quick keys**: Easy access to terminal-specific keys * **Gesture support**: Touch gestures and haptic feedback * **Keyboard management**: Handles virtual keyboard show/hide ## Platform-Specific Features ### Desktop Features * **Dynamic cursor positioning**: IME popup follows terminal cursor exactly * **Global paste handling**: Paste works anywhere in terminal area * **Composition state tracking**: Via native `KeyboardEvent.isComposing` plus the `data-ime-composing` DOM attribute * **Focus management**: Click focus plus bounded browser-timing retries * **Transparent integration**: Native input text appears at the terminal cursor without a separate control * **Performance optimized**: Minimal resource usage when not composing ### Mobile Features * **Native virtual keyboard**: Full OS-level CJK IME integration * **Quick keys toolbar**: Touch-friendly terminal keys (Tab, Esc, Ctrl, etc.) * **Touch-optimized UI**: Larger tap targets and touch gestures * **Auto-capitalization control**: Intelligently disabled for terminal accuracy * **Viewport management**: Graceful handling of keyboard show/hide animations * **Direct input mode**: Option to use hidden input for power users ## User Experience ### Desktop Workflow ``` User clicks terminal → Cursor-positioned input focuses → Types CJK → Browser shows IME candidates → User selects → Text appears in terminal ``` ### Mobile Workflow ``` User taps terminal → Virtual keyboard appears → Types CJK → OS shows IME candidates → User selects → Text appears in terminal ``` ### Visual Behavior * **Desktop**: Transparent input text and native IME popup at the terminal cursor * **Mobile**: Standard input field with native virtual keyboard * **Both platforms**: Seamless CJK text input with full IME support ## Performance ### Optimization Features * One input element and listener set per active desktop session * Dynamic positioning only calculated when needed * Minimal DOM footprint (single input element) * Clean event delegation and lifecycle management * Click-to-focus behavior without polling * Proper cleanup prevents memory leaks during session changes ## Code Reference ### Primary Files * `ime-input.ts` - Desktop input creation, composition events, positioning, focus, paste, and cleanup * `input-manager.ts` - Input coordination, desktop/mobile selection, terminal routing, and lifecycle * `terminal.ts` - Ghostty renderer cursor geometry used to anchor the native input * `lifecycle-event-manager.ts` - Keyboard interception that leaves active composition to the browser * `direct-keyboard-manager.ts` - Mobile keyboard handling * `mobile-utils.ts` - Mobile detection utilities ### Supporting Files * `session-view.ts` - Container element and terminal integration * `ime-constants.ts` - IME-related key filtering utilities * `terminal-constants.ts` - Terminal element IDs, font settings, and IME positioning constants ## Browser Compatibility Works with all major browsers that support: * IME composition events (`compositionstart`, `compositionupdate`, `compositionend`) * Clipboard API for paste functionality * Standard DOM positioning APIs The affected macOS boundary has live coverage with Chrome and Safari using Chinese Pinyin. Automated coverage verifies the composition lifecycle and cursor anchoring independently of the operating-system candidate UI. ## Configuration ### Automatic Platform Detection CJK IME support is automatically configured based on the detected platform: * **Desktop**: Cursor-positioned IME input with native candidate-window anchoring * **Mobile**: Native virtual keyboard with OS IME ### Requirements 1. User has CJK input method enabled in their OS 2. Desktop: User clicks in terminal area to focus 3. Mobile: User taps terminal or input field 4. User switches to CJK input mode in their OS ## Troubleshooting ### Common Issues * **IME candidates not showing**: Ensure browser supports composition events * **Text not appearing**: Check if terminal session is active and receiving input * **Paste not working**: Verify clipboard permissions in browser ### Debug Information Comprehensive logging available in browser console: * `🔍 Setting up IME input on desktop device` - Platform detection * `[ime-input]` - Desktop IME component events * `[direct-keyboard-manager]` - Mobile keyboard events * State tracking through DOM attributes: * `data-ime-composing` - IME composition active (desktop) * `data-ime-input-focused` - IME input has focus (desktop) * Mobile detection logs showing user agent analysis *** ## Recent Improvements (v1.0.0-beta.16+) ### Unified Cursor Position Tracking * **Shared Utility**: Created `cursor-position.ts` for consistent cursor calculation across all terminal types * **Container-Aware Positioning**: Fixed IME positioning issues with side panels and complex layouts * **Precise Alignment**: Improved character width measurement for pixel-perfect cursor alignment * **Debug Logging**: Enhanced debug output with comprehensive coordinate information ### Technical Improvements * **Code Deduplication**: Eliminated \~120 lines of duplicate cursor calculation code * **Maintainability**: Single source of truth for cursor positioning logic * **Type Safety**: Improved TypeScript interfaces and error handling * **Performance**: More efficient coordinate conversion with optimized calculations ### Element ID Centralization * **Constants File**: Created `terminal-constants.ts` to centralize all critical terminal element IDs * **Prevention of Breakage**: Changes to IDs like `session-terminal`, `buffer-container`, or `terminal-container` now only require updates in one location * **Consistent References**: All components now import `TERMINAL_IDS` constants instead of using hardcoded strings * **Type Safety**: Constants are strongly typed to prevent typos and ensure consistent usage across the codebase *** **Status**: ✅ Production Ready\ **Platforms**: Desktop (Windows, macOS, Linux) and Mobile (iOS, Android)\ **Version**: VibeTunnel Web v1.0.0-beta.16+\ **Last Updated**: 2026-06-13 # Claude Source: https://docs.vibetunnel.sh/docs/claude # Claude CLI Usage Guide The Claude CLI is a powerful command-line interface for interacting with Claude. This guide covers basic usage, advanced features, and important considerations when using Claude as an agent. ## Installation ```bash theme={null} # Install via npm npm install -g @anthropic/claude-cli # Or use directly with npx npx @anthropic/claude-cli ``` ## Basic Usage ### Recommended: Use VibeTunnel for Better Visibility When working within VibeTunnel, use `vt claude` instead of `claude` directly. This provides better visibility into what Claude is doing: ```bash theme={null} # Use vt claude for better monitoring vt claude "What is the capital of France?" # VibeTunnel will show Claude's activities in real-time vt claude -f src/*.js "Refactor this code" ``` ### One-Shot Prompts ```bash theme={null} # Simple question vt claude "What is the capital of France?" # Multi-line prompt with quotes vt claude "Explain the following code: function fibonacci(n) { if (n <= 1) return n; return fibonacci(n - 1) + fibonacci(n - 2); }" ``` ### Input Methods ```bash theme={null} # Pipe input from another command echo "Hello, world!" | vt claude "Translate this to Spanish" # Read prompt from file vt claude < prompt.txt # Use heredoc for complex prompts vt claude << 'EOF' Analyze this SQL query for performance issues: SELECT * FROM users WHERE created_at > '2023-01-01' EOF ``` ### File Context ```bash theme={null} # Single file context vt claude -f script.js "Explain what this script does" # Multiple files vt claude -f src/*.js -f tests/*.test.js "Find potential bugs" # With explicit file references vt claude -f config.json -f app.js "How does the app use the config?" ``` ## Advanced Features ### Timeout Considerations ⏱️ **Important**: Claude can be slow but thorough. When calling Claude from scripts or other tools, **set a timeout of more than 10 minutes**: ```bash theme={null} # Example: Using timeout command timeout 900s vt claude -f "*.js" "Refactor this codebase" # Example: In a Python script subprocess.run(['vt', 'claude', 'analyze this'], timeout=900) # Example: In a Node.js script await exec('vt claude "complex task"', { timeout: 900000 }) // milliseconds ``` Claude may take time to: * Analyze large codebases thoroughly * Consider multiple approaches before responding * Verify its suggestions are correct * Generate comprehensive solutions **Note**: Claude itself has no built-in timeout mechanism. The calling process must implement timeout handling. ⚠️ **Critical**: Even if a timeout occurs, Claude may have already modified multiple files before being interrupted. After any Claude invocation (successful or timed out): 1. **Re-read all files** that were passed to Claude 2. **Check related files** that Claude might have modified (imports, dependencies, tests) 3. **Use version control** to see what changed: `git status` and `git diff` 4. **Never assume** the operation failed completely - partial changes are common ```bash theme={null} # Example: Safe Claude invocation pattern git add -A # Stage current state timeout 900s vt claude -f src/*.js "Refactor error handling" || true git status # See what changed git diff # Review all modifications # In scripts: Always check for changes vt claude -f config.json "Update settings" || echo "Claude timed out" # Still need to check if config.json was modified! ``` ### Environment Variables ```bash theme={null} # Set API key export ANTHROPIC_API_KEY="your-key-here" # Set model (if supported) export CLAUDE_MODEL="claude-3-opus-20240229" ``` ### Output Formatting ```bash theme={null} # Save response to file vt claude "Write a Python hello world" > hello.py # Append to file vt claude "Add error handling" >> hello.py # Process output with other tools vt claude "List 10 programming languages" | grep -i python ``` ### Interactive Mode ```bash theme={null} # Start interactive session vt claude -i # With initial context vt claude -i -f project.md "Let's work on this project" ``` ## Important Considerations: Claude as an Agent ⚠️ **Critical Understanding**: Claude is an intelligent agent that aims to be helpful and thorough. This means: ### Default Behavior When you use Claude via CLI, it will: * **Analyze the full context** of your request * **Make reasonable inferences** about what you need * **Perform additional helpful actions** beyond the literal request * **Verify and validate** its work * **Provide explanations** and context ### Example of Agent Behavior ```bash theme={null} # What you ask: vt claude -f buggy.js "Fix the syntax error on line 5" # What Claude might do: # 1. Fix the syntax error on line 5 # 2. Notice and fix other syntax errors # 3. Identify potential bugs # 4. Suggest better practices # 5. Format the code # 6. Add helpful comments ``` ### Controlling Agent Behavior If you need Claude to perform **ONLY** specific actions without additional help: #### Strict Mode Prompting ```bash theme={null} # Explicit constraints vt claude -f config.json "Change ONLY the 'port' value to 8080. Make NO other changes. Do NOT fix any other issues you might notice. Do NOT add comments or formatting. Output ONLY the modified line." # Surgical edits vt claude -f script.sh "Replace EXACTLY the string 'localhost' with '0.0.0.0' on line 23. Make NO other modifications to the file. Do NOT analyze or improve the script." ``` #### Best Practices for Strict Operations 1. **Be explicit about constraints**: ```bash theme={null} vt claude "List EXACTLY 3 items. No more, no less. No explanations." ``` 2. **Use precise language**: ```bash theme={null} # Instead of: "Fix the typo" # Use: "Change 'recieve' to 'receive' on line 42 ONLY" ``` 3. **Specify output format**: ```bash theme={null} vt claude "Output ONLY valid JSON, no markdown formatting, no explanations" ``` 4. **Chain commands for control**: ```bash theme={null} # Use grep/sed for deterministic edits instead of Claude vt claude -f file.py "Find the typo" | grep -n "recieve" sed -i 's/recieve/receive/g' file.py ``` ## Use Cases ### When to Use Claude's Agent Capabilities * **Code review**: Let Claude analyze thoroughly * **Debugging**: Benefit from comprehensive analysis * **Learning**: Get detailed explanations * **Refactoring**: Allow intelligent improvements ### When to Constrain Claude * **CI/CD pipelines**: Need deterministic behavior * **Automated scripts**: Require predictable outputs * **Specific edits**: Want surgical precision * **Integration with other tools**: Need exact output formats ## Examples ### Development Workflow ```bash theme={null} # Let Claude be helpful (default) vt claude -f app.js -f test.js "Add error handling" # Constrained for automation vt claude -f config.yml "Output ONLY the value of 'database.host'. No formatting." > db_host.txt ``` ### Script Integration ```bash theme={null} #!/bin/bash # Get exactly what you need PORT=$(vt claude -f config.json "Print ONLY the port number. Nothing else.") echo "Server will run on port: $PORT" ``` ## Tips 1. **Test first**: Always test Claude's behavior before using in automation 2. **Be explicit**: Over-specify when you need exact behavior 3. **Use version control**: Claude might make helpful changes you didn't expect 4. **Review outputs**: Especially in automated workflows 5. **Leverage intelligence**: Don't over-constrain when you want smart help ## Command Reference ```bash theme={null} vt claude --help # Show all options vt claude --version # Show version vt claude -f FILE # Include file context vt claude -i # Interactive mode vt claude --no-markdown # Disable markdown formatting vt claude --json # JSON output (if supported) ``` **Note**: When not using VibeTunnel, replace `vt claude` with just `claude` in all commands above. Remember: Claude is designed to be a helpful assistant. This is usually what you want, but sometimes you need precise, limited actions. Plan accordingly! # Api reference Source: https://docs.vibetunnel.sh/docs/core/api-reference # API Reference ## Base URL * Development: `http://localhost:4020` * Production: Configurable via settings ## Authentication ### Token Generation ```http theme={null} POST /api/auth/token Content-Type: application/json { "password": "optional-password" } ``` **Response** ```json theme={null} { "token": "jwt-token-string", "expiresIn": 86400 } ``` ## Session Management ### Create Session ```http theme={null} POST /api/sessions Authorization: Bearer Content-Type: application/json { "command": "zsh", "args": [], "cwd": "/Users/username", "env": {}, "name": "Session Name", "cols": 80, "rows": 24 } ``` **Response** ```json theme={null} { "id": "session-uuid", "name": "Session Name", "created": "2024-01-01T00:00:00Z", "status": "running", "pid": 12345 } ``` ### List Sessions ```http theme={null} GET /api/sessions Authorization: Bearer ``` **Response** ```json theme={null} [ { "id": "session-uuid", "name": "Session 1", "created": "2024-01-01T00:00:00Z", "status": "running", "pid": 12345 } ] ``` ### Get Session Details ```http theme={null} GET /api/sessions/:id Authorization: Bearer ``` ### Delete Session ```http theme={null} DELETE /api/sessions/:id Authorization: Bearer ``` ### Resize Terminal ```http theme={null} POST /api/sessions/:id/resize Authorization: Bearer Content-Type: application/json { "cols": 120, "rows": 40 } ``` ## WebSocket Connection ### WebSocket v3 (`/ws`) * Endpoint: `GET /ws` (WebSocket upgrade) * Framing: binary v3 frames (`"VT"` magic, version `3`, type, sessionId, payload) * Multiplexing: one socket carries multiple session subscriptions Protocol details: `docs/websocket.md`. ```javascript theme={null} const ws = new WebSocket('ws://localhost:4020/ws?token=JWT_TOKEN'); ws.binaryType = 'arraybuffer'; ``` ## Health Check ### Server Status ```http theme={null} GET /api/health ``` **Response** ```json theme={null} { "status": "healthy", "uptime": 3600, "version": "1.0.0", "sessions": 5 } ``` ## Error Responses | Status | Error | Description | | ------ | ------------ | ---------------------- | | 400 | Bad Request | Invalid parameters | | 401 | Unauthorized | Missing/invalid token | | 404 | Not Found | Session not found | | 409 | Conflict | Session already exists | | 500 | Server Error | Internal error | **Error Format** ```json theme={null} { "error": "Error message", "code": "ERROR_CODE", "details": {} } ``` ## Rate Limiting * **Session Creation**: 10 per minute * **API Calls**: 100 per minute * **WebSocket Messages**: Unlimited ## Terminal Transport (WebSocket v3) Terminal I/O uses a single `/ws` WebSocket with binary v3 framing and multiplexed sessions. Details: `docs/websocket.md`. ## Session Recording Sessions are recorded in asciinema v2 format: ```json theme={null} { "version": 2, "width": 80, "height": 24, "timestamp": 1234567890, "env": { "SHELL": "/bin/zsh", "TERM": "xterm-256color" } } ``` Event format: ```json theme={null} [timestamp, "o", "output data"] ``` ## See Also * [WebSocket Protocol Details](protocols.md) * [Authentication Guide](../features/authentication.md) * [Server Implementation](../platform/web.md) # Architecture Source: https://docs.vibetunnel.sh/docs/core/architecture # Architecture Overview ## System Design VibeTunnel consists of three main components working together: ``` ┌─────────────────────────────────────────────────────┐ │ macOS Menu Bar Application │ │ (Swift/SwiftUI) │ │ ┌──────────────────────────────────────────────┐ │ │ │ ServerManager: Lifecycle & process control │ │ │ │ SessionMonitor: Active session tracking │ │ │ │ TTYForwardManager: Terminal forwarding │ │ │ └──────────────────────────────────────────────┘ │ └────────────────────┬────────────────────────────────┘ │ Spawns & Manages ┌────────────────────▼────────────────────────────────┐ │ Node.js/Bun Server Process │ │ (TypeScript) │ │ ┌──────────────────────────────────────────────┐ │ │ │ HTTP Server: REST API endpoints │ │ │ │ WebSocket Server: Real-time terminal I/O │ │ │ │ PTY Manager: Native terminal processes │ │ │ │ Session Manager: Lifecycle & state │ │ │ └──────────────────────────────────────────────┘ │ └────────────────────┬────────────────────────────────┘ │ HTTP/WebSocket ┌────────────────────▼────────────────────────────────┐ │ Client Applications │ ├──────────────────────────────────────────────────────┤ │ Web Browser │ iOS App │ │ (Lit/TypeScript) │ (Swift/SwiftUI) │ └──────────────────────────────────────────────────────┘ ``` ## Component Responsibilities ### macOS Application | Component | File | Purpose | | ----------------- | ---------------------------------------- | --------------------------------- | | ServerManager | `mac/VibeTunnel/ServerManager.swift` | Server lifecycle, port management | | SessionMonitor | `mac/VibeTunnel/SessionMonitor.swift` | Track active sessions | | TTYForwardManager | `mac/VibeTunnel/TTYForwardManager.swift` | CLI integration | | MenuBarUI | `mac/VibeTunnel/MenuBarView.swift` | User interface | ### Server Process | Component | File | Purpose | | --------------- | -------------------------------------------- | ---------------------------- | | HTTP Server | `web/src/server/server.ts` | REST API, WebSocket upgrade | | PTY Manager | `web/src/server/pty/pty-manager.ts` | Terminal process spawning | | Session Manager | `web/src/server/pty/session-manager.ts` | Session state & cleanup | | WsV3Hub | `web/src/server/services/ws-v3-hub.ts` | `/ws` v3 framing + multiplex | | CastOutputHub | `web/src/server/services/cast-output-hub.ts` | Cast tail → `STDOUT` frames | ### Web Frontend | Component | File | Purpose | | ---------------- | --------------------------------------------------- | -------------------------- | | App Shell | `web/src/client/app.ts` | Main application container | | Terminal View | `web/src/client/components/terminal.ts` | ghostty-web integration | | Session List | `web/src/client/components/session-list.ts` | Active sessions UI | | WebSocket Client | `web/src/client/services/terminal-socket-client.ts` | `/ws` v3 transport | ## Data Flow ### Session Creation ``` User → vt command → TTYForwardManager → HTTP POST /api/sessions → Server creates PTY → Returns session ID → Opens browser → WebSocket connection established → Terminal ready ``` ### Terminal I/O ``` User types → v3 `INPUT_*` frame → Server PTY write PTY output → Cast tail → v3 `STDOUT` frames Server-side Ghostty → v1 VT snapshots → v3 `SNAPSHOT_VT` frames Client render: ghostty-web (interactive) + VT snapshots (previews) ``` ### Session Cleanup ``` Terminal exit → PTY close → Session manager cleanup → WebSocket close → Client notification → UI update ``` ## Communication Protocols ### HTTP REST API * Session CRUD operations * Authentication endpoints * Health checks * See [API Reference](api-reference.md) ### WebSocket Protocol * Terminal transport: `/ws` WebSocket v3 framing (multiplexed sessions) * Details: `docs/websocket.md` ### Inter-Process Communication * Mac app spawns Bun server as child process * Environment variables for configuration * File-based PID tracking * Signal handling for graceful shutdown ## Security Architecture ### Authentication Flow ``` Client → Password (optional) → Server validates → JWT token generated → Token in Authorization header → Server validates on each request ``` ### Network Security * Localhost-only by default * Optional LAN exposure with authentication * Tailscale/ngrok integration for remote access * WSS/HTTPS in production ### Process Isolation * Each session runs in separate PTY process * User permissions inherited from server * No privilege escalation * Resource limits per session ## Performance Optimizations ### Buffer Aggregation * Batch terminal output every 16ms * Reduce WebSocket message frequency * Binary protocol reduces payload size ### Connection Management * WebSocket connection pooling * Automatic reconnection with backoff * Ping/pong for keep-alive ### Resource Management * Lazy loading of terminal sessions * Automatic cleanup of idle sessions * Memory-mapped session recordings ## Platform Integration ### macOS Features * Menu bar application * Sparkle auto-updates * Code signing & notarization * Launch at login ### iOS Features * Native Swift UI * Background session support * Push notifications * Handoff support ### Web Standards * Progressive Web App capable * Service Worker for offline * WebAssembly for performance * Responsive design ## Build & Deployment ### Build Pipeline ``` 1. TypeScript compilation → JavaScript bundle 2. Bun standalone executable generation 3. Swift compilation → macOS app 4. Embed server in app bundle 5. Code sign & notarize 6. DMG creation with Sparkle ``` ### Configuration * Runtime: Environment variables * Build-time: xcconfig files * User preferences: macOS defaults system * Server config: JSON files ## Monitoring & Debugging ### Logging * Unified logging to macOS Console * Structured JSON logs from server * Session-specific log filtering * See `./scripts/vtlog.sh` ### Metrics * Session count & duration * Message throughput * Error rates * Resource usage ## See Also * [Development Guide](../guides/development.md) * [API Reference](api-reference.md) * [Security Model](../features/authentication.md) # Protocols Source: https://docs.vibetunnel.sh/docs/core/protocols # Protocol Specifications ## Terminal Transport (WebSocket v3) VibeTunnel uses a **single** WebSocket endpoint for terminal transport, multiplexing sessions over binary frames. ### Connection Establishment ```javascript theme={null} const ws = new WebSocket('ws://localhost:4020/ws?token=JWT_TOKEN'); ws.binaryType = 'arraybuffer'; ``` ### Subscriptions * Subscribe per session: send a v3 `SUBSCRIBE` frame with `sessionId` + flags (`Stdout`, `Snapshots`, `Events`). * Global events: use an empty `sessionId` and the `Events` flag. Source of truth: `docs/websocket.md` and `web/src/shared/ws-v3.ts`. ### Error Codes | Code | Meaning | Action | | ---- | ---------------- | ---------------- | | 1000 | Normal closure | Session ended | | 1001 | Going away | Server shutdown | | 1003 | Unsupported data | Protocol error | | 1008 | Policy violation | Auth failed | | 1011 | Server error | Retry connection | ## PTY Protocol ### Process Spawning ```typescript theme={null} interface PTYOptions { name: string; cols: number; rows: number; cwd: string; env: Record; command: string; args: string[]; } ``` ### Control Sequences | Sequence | Purpose | Example | | -------- | ---------------- | ----------------- | | `\x03` | SIGINT (Ctrl+C) | Interrupt process | | `\x04` | EOF (Ctrl+D) | End input | | `\x1a` | SIGTSTP (Ctrl+Z) | Suspend process | | `\x1c` | SIGQUIT (Ctrl+) | Quit process | | `\x7f` | Backspace | Delete character | ### Terminal Modes ```typescript theme={null} // Raw mode for full control pty.setRawMode(true); // Canonical mode for line editing pty.setRawMode(false); ``` ## Session Recording Protocol ### Asciinema v2 Format **Header**: ```json theme={null} { "version": 2, "width": 80, "height": 24, "timestamp": 1704067200, "env": { "SHELL": "/bin/zsh", "TERM": "xterm-256color" } } ``` **Events**: ```json theme={null} [0.123456, "o", "$ ls -la\r\n"] [0.234567, "o", "total 48\r\n"] [1.345678, "i", "c"] [1.456789, "i", "l"] [1.567890, "i", "e"] ``` Event types: * `o`: Output from terminal * `i`: Input from user * `r`: Terminal resize ### Recording Storage ``` ~/.vibetunnel/recordings/ ├── session-uuid-1.cast ├── session-uuid-2.cast └── metadata.json ``` ## HTTP Protocol ### Request Headers ```http theme={null} Authorization: Bearer Content-Type: application/json X-Session-ID: X-Client-Version: 1.0.0 ``` ### Response Headers ```http theme={null} X-Request-ID: X-RateLimit-Limit: 100 X-RateLimit-Remaining: 95 X-RateLimit-Reset: 1704067200 ``` ### Status Codes | Code | Meaning | Usage | | ---- | ----------------- | -------------------- | | 200 | OK | Successful operation | | 201 | Created | Session created | | 204 | No Content | Session deleted | | 400 | Bad Request | Invalid parameters | | 401 | Unauthorized | Auth required | | 404 | Not Found | Session not found | | 409 | Conflict | Session exists | | 429 | Too Many Requests | Rate limited | | 500 | Server Error | Internal error | ## Terminal Transport (WebSocket v3) VibeTunnel uses a single WebSocket endpoint for terminal transport: * Endpoint: `GET /ws` (upgrade) * Binary framing: `"VT"` magic + version + type + sessionId + payload * Multiplexing: one socket can carry multiple sessions * Subscriptions: flags for `stdout`, `snapshots`, `events` Details: `docs/websocket.md`. ## Authentication Protocol ### JWT Token Structure ```json theme={null} { "header": { "alg": "HS256", "typ": "JWT" }, "payload": { "sub": "user-id", "iat": 1704067200, "exp": 1704153600, "sessionId": "session-uuid" } } ``` ### Token Refresh Flow ``` 1. Client token expires in 5 minutes 2. Client requests refresh: POST /api/auth/refresh 3. Server validates refresh token 4. Server issues new access token 5. Client updates Authorization header ``` ## See Also * [API Reference](api-reference.md) * [Security Guide](../features/authentication.md) * [WebSocket Implementation](../platform/web.md#websocket) # Custom node Source: https://docs.vibetunnel.sh/docs/custom-node # Custom Node.js Build ## Motivation VibeTunnel uses Node.js Single Executable Applications (SEA) to create a standalone terminal server. However, the standard Node.js binary is quite large: * **Standard Node.js binary**: \~110MB * **Custom minimal Node.js**: \~68MB (38% reduction) * **Final executable size**: \~72MB * **Final app size impact**: Saves roughly 40MB compared with embedding the standard runtime We don't need many Node.js features for VibeTunnel: * Small ICU data is sufficient for Unicode-aware JavaScript and English locale data * No npm package manager in the binary * No inspector/debugging protocol * No V8 snapshots or code cache By building a custom Node.js with small ICU and without the remaining features, we achieve a significantly smaller app bundle while maintaining full functionality. ## Build Behavior ### Debug Mode (Xcode) * Uses system Node.js for faster iteration * No custom Node.js compilation required * Build output shows: `"Debug build - using system Node.js for faster builds"` * If a custom Node.js was previously built, it will be reused for consistency ### Release Mode (Xcode) * Automatically builds custom minimal Node.js on first run * Compilation takes 10-20 minutes but is cached for future builds * Uses the custom Node.js to create a smaller executable * Build output shows version and size comparison ## Prerequisites ### Required Build Tools For optimal build performance, the following tools are required: * **Ninja**: Build system for faster compilation (significantly faster than Make) * **ccache**: Compiler cache to speed up rebuilds #### Installation * **macOS**: `brew install ninja ccache` * **Linux**: `apt-get install ninja-build ccache` (or equivalent for your distribution) The build script will automatically use these tools if available, falling back to Make if Ninja is not found. ## Build Automation ### Release Builds The release script (`mac/scripts/release.sh`) forces a fresh web/native build and requires the pinned custom Node.js runtime. It builds and caches the runtime when needed; releases fail instead of silently embedding the full system Node.js binary. ### Manual Custom Node.js Build To build the custom Node.js manually (outside of Xcode): ```bash theme={null} cd web node build-custom-node.js ``` This will: 1. Download the latest Node.js source 2. Configure it without unnecessary features 3. Build with optimizations (`-Os`, `-flto`, etc.) 4. Cache the result in `web/.node-builds/` To use the custom Node.js for building the executable: ```bash theme={null} cd web npm run build -- --custom-node ``` Or directly: ```bash theme={null} node build-native.js --custom-node ``` ## Build Process Details ### Automatic Detection The build system automatically searches for custom Node.js builds in `.node-builds/` when `--custom-node` is passed without a path. It finds the most recent build by checking directory modification times. ### Code Signing on macOS When building the executable: 1. The Node.js binary is injected with our JavaScript code (SEA process) 2. The binary is stripped to remove debug symbols 3. The executable is re-signed with an ad-hoc signature Note: You may see a warning about "invalidating the code signature" during the strip process - this is expected and harmless since we re-sign immediately after. ## Technical Details ### Features Disabled * `--with-intl=small-icu` - Keeps Unicode semantics with reduced locale data * `--without-npm` - Excludes npm from the binary * `--without-corepack` - Removes package manager wrapper * `--without-inspector` - Disables debugging protocol * `--without-node-snapshot` - Skips V8 snapshot (\~2-3MB) * `--without-node-code-cache` - Skips code cache (\~1-2MB) ### Optimization Flags * `-Os` - Optimize for size * `-flto` - Link-time optimization * `-ffunction-sections` / `-fdata-sections` - Enable dead code elimination * `-Wl,-dead_strip` - Remove unused code at link time ### Build Cache Custom Node.js builds are stored in `web/.node-builds/` and are excluded from git via `.gitignore`. The build system automatically detects and reuses existing builds. ## File Locations * Build script: `web/build-custom-node.js` * Native executable builder: `web/build-native.js` * Xcode integration: `mac/scripts/build-web-frontend.sh` * Build output: `web/.node-builds/node-v*-minimal/` * Final executable: `web/native/vibetunnel` ## Troubleshooting ### Custom Node.js not detected * Ensure the build completed successfully: check for `.node-builds/node-v*-minimal/out/Release/node` * In Debug mode, the system will use custom Node.js if already built * In Release mode, it will build custom Node.js automatically if not present ### Code signature warnings The warning "changes being made to the file will invalidate the code signature" is expected and handled automatically. The build process re-signs the executable after all modifications. ## Known Limitations * The custom Node.js build process takes 10-20 minutes on first run * Cross-compilation is not supported - you must build on the target platform * The custom build excludes some features that may be needed by certain npm packages * Native module compatibility issues may occur when mixing Node.js versions # Authentication Source: https://docs.vibetunnel.sh/docs/features/authentication # Authentication & Security ## Overview VibeTunnel supports multiple authentication modes: * **None** (localhost only) * **Password** (simple shared secret) * **Token** (JWT-based) * **External** (Tailscale, ngrok) ## Configuration ### Security Settings | Setting | Default | Options | | -------------- | --------- | ---------------------- | | Authentication | None | None, Password, Token | | Network | Localhost | Localhost, LAN, Public | | Password | - | User-defined | | Token Expiry | 24h | 1h-7d | ### Enable Authentication ```swift theme={null} // Via Settings UI Settings → Security → Enable Password // Via defaults defaults write com.steipete.VibeTunnel authEnabled -bool true defaults write com.steipete.VibeTunnel authPassword -string "secret" ``` ## Password Authentication ### Server Configuration ```typescript theme={null} // server/config.ts export const config = { auth: { enabled: process.env.AUTH_ENABLED === 'true', password: process.env.AUTH_PASSWORD, } }; ``` ### Client Login ```typescript theme={null} // POST /api/auth/login const response = await fetch('/api/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ password: 'secret' }) }); const { token } = await response.json(); localStorage.setItem('auth_token', token); ``` ## Token Authentication ### JWT Structure ```json theme={null} { "header": { "alg": "HS256", "typ": "JWT" }, "payload": { "sub": "user-id", "iat": 1704067200, "exp": 1704153600, "scope": ["sessions:read", "sessions:write"] } } ``` ### Token Generation ```typescript theme={null} // server/services/auth.ts import jwt from 'jsonwebtoken'; export function generateToken(userId: string): string { return jwt.sign( { sub: userId, scope: ['sessions:read', 'sessions:write'] }, process.env.JWT_SECRET, { expiresIn: '24h' } ); } ``` ### Signing Secret Persistence When `JWT_SECRET` is unset, the server generates a 64-byte signing secret and stores it at `~/.vibetunnel/jwt-secret` with `0600` permissions. The same key is reused after a restart, keeping existing browser tokens valid. Set `JWT_SECRET` to supply an operator-managed key; rotating or deleting the active key invalidates existing tokens. ### Token Validation ```typescript theme={null} // server/middleware/auth.ts export async function validateToken(req: Request): Promise { const token = req.headers.authorization?.replace('Bearer ', ''); if (!token) return false; try { const decoded = jwt.verify(token, process.env.JWT_SECRET); req.user = decoded; return true; } catch { return false; } } ``` ## Network Security ### Localhost Only (Default) ```typescript theme={null} // server/server.ts const server = Bun.serve({ hostname: '127.0.0.1', // Localhost only port: 4020, }); ``` ### LAN Access ```typescript theme={null} // Enable LAN with authentication required const server = Bun.serve({ hostname: '0.0.0.0', // All interfaces port: 4020, }); // Require auth for non-localhost app.use((req, res, next) => { if (req.ip !== '127.0.0.1' && !req.authenticated) { return res.status(401).json({ error: 'Authentication required' }); } next(); }); ``` ### HTTPS/WSS ```typescript theme={null} // Production TLS const server = Bun.serve({ fetch: app.fetch, tls: { cert: Bun.file('cert.pem'), key: Bun.file('key.pem'), }, }); ``` ## External Access ### Tailscale Integration ```bash theme={null} # Enable Tailscale tailscale up # Access via Tailscale network http://your-machine.tailnet:4020 ``` ### ngrok Tunnel ```bash theme={null} # Start ngrok tunnel ngrok http 4020 # Access via public URL https://abc123.ngrok.io ``` ## Session Security ### Isolation Each session runs in a separate process with user permissions: ```typescript theme={null} // pty-manager.ts const pty = spawn(shell, args, { uid: process.getuid(), // Run as current user gid: process.getgid(), env: sanitizeEnv(env), // Clean environment }); ``` ### Resource Limits ```typescript theme={null} // Prevent resource exhaustion const limits = { maxSessions: 50, maxOutputBuffer: 10 * 1024 * 1024, // 10MB sessionTimeout: 24 * 60 * 60 * 1000, // 24 hours }; ``` ## Security Headers ```typescript theme={null} // server/middleware/security.ts app.use((req, res, next) => { res.setHeader('X-Frame-Options', 'DENY'); res.setHeader('X-Content-Type-Options', 'nosniff'); res.setHeader('X-XSS-Protection', '1; mode=block'); res.setHeader('Strict-Transport-Security', 'max-age=31536000'); res.setHeader('Content-Security-Policy', "default-src 'self'"); next(); }); ``` ## Audit Logging ```typescript theme={null} // server/services/audit.ts export function logAccess(event: AuditEvent) { const entry = { timestamp: new Date(), ip: event.ip, action: event.action, sessionId: event.sessionId, success: event.success, }; fs.appendFileSync('audit.log', JSON.stringify(entry) + '\n'); } ``` ## Best Practices 1. **Always use authentication** for non-localhost access 2. **Rotate tokens** regularly 3. **Use HTTPS/WSS** in production 4. **Limit session lifetime** to prevent resource exhaustion 5. **Monitor audit logs** for suspicious activity 6. **Keep dependencies updated** for security patches ## Threat Model | Threat | Mitigation | | ------------------- | --------------------- | | Unauthorized access | Password/token auth | | Session hijacking | JWT expiry, HTTPS | | Resource exhaustion | Rate limiting, quotas | | Code injection | Input sanitization | | Network sniffing | TLS encryption | ## Compliance ### Data Protection * No persistent storage of terminal content * Sessions cleared on exit * Optional recording with user consent ### Access Control * Authentication required for remote access * Session isolation per user * No privilege escalation ## See Also * [API Reference](../core/api-reference.md#authentication) * [Network Setup](../guides/quickstart.md#remote-development) * [Security Headers](../platform/web.md#security) # Forwarder Source: https://docs.vibetunnel.sh/docs/forwarder # Native Forwarder `vibetunnel-fwd` is VibeTunnel's per-session native process. The `vt` wrapper launches it around a command; the web server watches the resulting session artifacts and sends control messages over a Unix socket. ```text theme={null} vt -> vibetunnel-fwd -> PTY -> child command -> session files and ipc.sock <- web server ``` The implementation is a Rust crate in `native/vt-fwd`. It supports macOS and Linux; Windows is not supported. ## Session Contract Each session uses `~/.vibetunnel/control//` by default. `VIBETUNNEL_CONTROL_DIR` overrides the control root. * `session.json` contains the current `SessionInfo` state. * `stdout` is an asciinema v2 stream with output, input, resize, and exit records. * `stdin` is a private FIFO kept for compatibility. * `ipc.sock` accepts live input and control commands and is removed when the forwarder exits. The session JSON schema is defined in `web/src/shared/types.ts`. The forwarder creates private directories and artifacts so session contents are not readable by other local users. ## IPC Contract Frames use one byte for the message type, a four-byte big-endian payload length, and the payload. `STDIN_DATA` payloads are raw bytes; `CONTROL_CMD` payloads are JSON. The command set includes resize, kill, reset-size, and update-title. Heartbeat frames are echoed. The server-side framing helpers are in `web/src/server/pty/socket-protocol.ts`. The forwarder independently enforces a 1 MiB inbound payload limit in `native/vt-fwd/src/control_socket.rs`; an oversized frame closes only the offending connection. Unknown message types are ignored. ## CLI Contract The forwarder accepts a command after its options: ```text theme={null} vibetunnel-fwd [--session-id ] [--title-mode ] [--verbosity ] [args...] ``` `--update-title` updates an existing session and exits. Title modes are `none`, `filter`, and `static`. `VIBETUNNEL_TITLE_MODE`, `VIBETUNNEL_LOG_LEVEL`, `VIBETUNNEL_DEBUG`, and `VIBETUNNEL_CONTROL_DIR` are accepted as inherited overrides; explicit CLI options take precedence. The `vt` and web wrappers normally pass title mode and verbosity as CLI options. For the child command, the forwarder exports the active `VIBETUNNEL_SESSION_ID` and `TERM=xterm-256color`. ## Build and Test Install rustup; `native/vt-fwd/rust-toolchain.toml` selects Rust 1.97.0 and the required rustfmt and Clippy components. `native/vt-fwd/Cargo.lock` pins crate dependencies. ```bash theme={null} cd native/vt-fwd cargo fmt --all -- --check cargo clippy --all-targets --all-features --locked -- -D warnings cargo test --all-targets --locked cargo build --release --locked python3 test/e2e.py target/release/vibetunnel-fwd ``` The release binary is `native/vt-fwd/target/release/vibetunnel-fwd`. `node web/scripts/build-fwd-rust.js` builds and installs the host binary for web and macOS packaging; the npm build creates platform-specific forwarder directories. # Gemini Source: https://docs.vibetunnel.sh/docs/gemini # Using Gemini CLI for Large Codebase Analysis When analyzing large codebases or multiple files that might exceed context limits, use the Gemini CLI with its massive context window (up to 2 million tokens). Use `gemini -p` to leverage Google Gemini's large context capacity. ## Prerequisites Ensure Gemini CLI is installed before using these commands. Installation instructions: [https://github.com/google/gemini-cli#installation](https://github.com/google/gemini-cli#installation) ## File and Directory Inclusion Syntax Use the `@` syntax to include files and directories in your Gemini prompts. The paths should be relative to WHERE you run the gemini command: ### Examples: **Single file analysis:** ```bash theme={null} gemini -p "@web/src/server/server.ts Explain this file's purpose and structure" ``` **Multiple files:** ```bash theme={null} gemini -p "@package.json @web/src/client/app.ts Analyze the dependencies used in the code" ``` **Entire directory:** ```bash theme={null} gemini -p "@src/ Summarize the architecture of this codebase" ``` **Multiple directories:** ```bash theme={null} gemini -p "@src/ @tests/ Analyze test coverage for the source code" ``` **Current directory and subdirectories:** ```bash theme={null} gemini -p "@./ Give me an overview of this entire project" # Or use --all_files flag: gemini --all_files -p "Analyze the project structure and dependencies" ``` **⚠️ Security Warning:** Using `@./` may include sensitive files like `.env`, `.git/`, or API keys. Consider using specific directories or exclusion patterns. ## Implementation Verification Examples **Check if a feature is implemented:** ```bash theme={null} gemini -p "@src/ @lib/ Has dark mode been implemented in this codebase? Show me the relevant files and functions" ``` **Verify authentication implementation:** ```bash theme={null} gemini -p "@src/ @middleware/ Is JWT authentication implemented? List all auth-related endpoints and middleware" ``` **Check for specific patterns:** ```bash theme={null} gemini -p "@src/ Are there any React hooks that handle WebSocket connections? List them with file paths" ``` **Verify error handling:** ```bash theme={null} gemini -p "@src/ @api/ Is proper error handling implemented for all API endpoints? Show examples of try-catch blocks" ``` **Check for rate limiting:** ```bash theme={null} gemini -p "@backend/ @middleware/ Is rate limiting implemented for the API? Show the implementation details" ``` **Verify caching strategy:** ```bash theme={null} gemini -p "@src/ @lib/ @services/ Is Redis caching implemented? List all cache-related functions and their usage" ``` **Check for specific security measures:** ```bash theme={null} gemini -p "@src/ @api/ Are SQL injection protections implemented? Show how user inputs are sanitized" ``` **Verify test coverage for features:** ```bash theme={null} gemini -p "@src/payment/ @tests/ Is the payment processing module fully tested? List all test cases" ``` ## Error Scenarios and Handling **Gemini CLI not installed:** ```bash theme={null} # Error: command not found: gemini # Solution: Install Gemini CLI following instructions at https://github.com/google/gemini-cli#installation ``` **Invalid file paths:** ```bash theme={null} gemini -p "@nonexistent/path/ Analyze this code" # Gemini will report: "Error: Path 'nonexistent/path/' does not exist" # The command continues with any valid paths provided ``` **Network connectivity issues:** ```bash theme={null} # If API calls fail, you'll see: # "Error: Failed to connect to Gemini API" # Solution: Check your internet connection and API key configuration ``` **File encoding problems:** ```bash theme={null} # Non-UTF8 files are automatically skipped with a warning: # "Warning: Skipping binary or non-UTF8 file: path/to/file" ``` **Context limit exceeded:** ```bash theme={null} # For extremely large codebases: # "Error: Context limit exceeded (2M tokens)" # Solution: Use more specific paths or directories instead of @./ ``` **Timeout during analysis:** ```bash theme={null} # Default timeout is 30 seconds for API calls # For longer operations, use the --timeout flag: gemini --timeout 120 -p "@large-codebase/ Analyze architecture" ``` ## When to Use Gemini CLI Use `gemini -p` when: * Analyzing entire codebases or large directories * Comparing multiple large files * Need to understand project-wide patterns or architecture * Current context window is insufficient for the task * Working with files totaling more than 100KB * Verifying if specific features, patterns, or security measures are implemented * Checking for the presence of certain coding patterns across the entire codebase ## Important Notes * Paths in @ syntax are relative to your current working directory when invoking gemini * The CLI will include file contents directly in the context * The --yolo flag bypasses confirmation prompts (use cautiously) * Gemini's context window can handle entire codebases that would overflow Claude's context * When checking implementations, be specific about what you're looking for to get accurate results * Symlinks are followed by default - be cautious with circular references * Binary files are automatically excluded from analysis * Use `.geminiignore` file to exclude specific patterns (similar to `.gitignore`) ## Using .geminiignore Create a `.geminiignore` file in your project root to exclude files and directories from Gemini analysis. The syntax follows `.gitignore` patterns: ``` # Environment and secrets .env* *.key *.pem secrets/ credentials/ # Build artifacts dist/ build/ *.min.js *.bundle.js # Dependencies node_modules/ vendor/ venv/ # Large generated files package-lock.json yarn.lock *.generated.ts # Test fixtures test/fixtures/large-data/ **/*.snapshot.js # IDE and OS files .idea/ .vscode/ .DS_Store ``` **Pattern Examples:** * `*.log` - Exclude all log files * `temp/` - Exclude temp directory * `!important.log` - Include important.log even if \*.log is excluded * `src/**/test.js` - Exclude test.js in any subdirectory of src * `/config.json` - Exclude config.json only in root directory ## Error Handling * If paths don't exist, Gemini will report an error and continue with valid paths * File encoding issues are handled gracefully (non-UTF8 files are skipped) * For large repositories, consider using specific subdirectories to avoid timeouts # Git hooks Source: https://docs.vibetunnel.sh/docs/git-hooks # Git Hooks in VibeTunnel ## Overview VibeTunnel uses Git hooks exclusively for its **follow mode** feature. These hooks monitor repository changes and enable automatic branch synchronization across team members. ## Purpose Git hooks in VibeTunnel serve a single, specific purpose: * **Follow Mode**: Automatically sync worktrees when team members switch branches * **Session Title Updates**: Display current git operations in terminal session titles **Important**: If you're not using follow mode, git hooks are not needed and serve no other purpose in VibeTunnel. ## How It Works ### Hook Installation When follow mode is enabled, VibeTunnel installs two Git hooks: * `post-commit`: Triggered after commits * `post-checkout`: Triggered after branch checkouts These hooks execute a simple command: ```bash theme={null} vt git event ``` ### Event Flow 1. **Git Operation**: User performs a commit or checkout 2. **Hook Trigger**: Git executes the VibeTunnel hook 3. **Event Notification**: `vt git event` sends repository path to VibeTunnel server 4. **Server Processing**: The `/api/git/event` endpoint: * Updates session titles (e.g., `Terminal [checkout: feature-branch]`) * Checks follow mode configuration * Syncs branches if follow mode is active ### Follow Mode Synchronization When follow mode is enabled for a branch: 1. VibeTunnel monitors checkouts to the followed branch 2. If detected, it automatically switches your worktree to that branch 3. If branches have diverged, follow mode is automatically disabled ## Technical Implementation ### Hook Script Content ```bash theme={null} #!/bin/sh # VibeTunnel Git hook - post-checkout # This hook notifies VibeTunnel when Git events occur # Check if vt command is available if command -v vt >/dev/null 2>&1; then # Run in background to avoid blocking Git operations vt git event & fi # Always exit successfully exit 0 ``` ### Hook Management * **Installation**: `installGitHooks()` in `web/src/server/utils/git-hooks.ts` * **Safe Chaining**: Existing hooks are backed up and chained * **Cleanup**: Original hooks are restored when uninstalling ### API Endpoints * `POST /api/git/event`: Receives git event notifications * `POST /api/worktrees/follow`: Enables follow mode and installs hooks * `GET /api/git/follow`: Checks follow mode status ## File Locations * **Hook Management**: `web/src/server/utils/git-hooks.ts` * **Event Handler**: `web/src/server/routes/git.ts` (lines 189-481) * **Follow Mode**: `web/src/server/routes/worktrees.ts` (lines 580-630) * **CLI Integration**: `web/bin/vt` (git event command) ## Configuration Follow mode stores configuration in git config: ```bash theme={null} git config vibetunnel.followBranch ``` ## Security Considerations * Hooks run with minimal permissions * Commands execute in background to avoid blocking Git * Existing hooks are preserved and chained safely * Hooks are repository-specific, not global ## Troubleshooting ### Hooks Not Working * Verify `vt` command is in PATH * Check hook permissions: `ls -la .git/hooks/post-*` * Ensure hooks are executable: `chmod +x .git/hooks/post-*` ### Follow Mode Issues * Check configuration: `git config vibetunnel.followBranch` * Verify hooks installed: `cat .git/hooks/post-checkout` * Review server logs for git event processing ## Summary Git hooks in VibeTunnel are: * **Single-purpose**: Only used for follow mode functionality * **Optional**: Not required unless using follow mode * **Safe**: Preserve existing hooks and run non-blocking * **Automatic**: Managed by VibeTunnel when enabling/disabling follow mode If you're not using follow mode for team branch synchronization, you don't need git hooks installed. # Git worktree follow mode Source: https://docs.vibetunnel.sh/docs/git-worktree-follow-mode # Git Worktree Follow Mode Specification ## Overview Follow mode is a feature that enables automatic synchronization between Git worktrees and the main repository. It ensures team members stay on the same branch by automatically switching branches when changes are detected. ## Core Concept Follow mode creates a **unidirectional sync** from a worktree to the main repository: * When someone switches branches in a worktree * The main repository automatically follows that branch change * This keeps the main repository synchronized with active development ## When Follow Mode Should Be Available ### ✅ Follow Mode SHOULD appear when: 1. **Creating a session in a worktree** * You've selected a worktree from the dropdown * The session will run in that worktree's directory * Follow mode will sync the main repository to match this worktree's branch 2. **Viewing worktrees in the Worktree Manager** * Each worktree (except main) shows a "Follow" button * Enables following that specific worktree's branch 3. **Session list with worktree sessions** * Repository headers show follow mode status * Dropdown allows changing which worktree to follow ### ❌ Follow Mode should NOT appear when: 1. **No worktree is selected** (using main repository) * There's nothing to follow - you're already in the main repo * Follow mode has no purpose without a worktree 2. **Repository has no worktrees** * No worktrees exist to follow * Only the main repository is available 3. **Not in a Git repository** * Obviously, no Git features available ## UI Behavior Rules ### Session Creation Form ```typescript theme={null} // Show follow mode toggle only when: const showFollowModeToggle = gitRepoInfo?.isGitRepo && selectedWorktree !== undefined && selectedWorktree !== 'none'; ``` #### Toggle States: 1. **Worktree Selected**: * Show: "Follow Mode" toggle * Description: "Keep main repository in sync with this worktree" * Default: OFF (user must explicitly enable) 2. **No Worktree Selected**: * Hide the entire follow mode section * No toggle should be visible 3. **Follow Mode Already Active**: * Show: "Follow Mode" toggle (disabled) * Description: "Currently following: \[branch-name]" * Info: User must disable from worktree manager ### Worktree Manager Each worktree row shows: * **"Follow" button**: When not currently following * **"Following" button** (green): When actively following this worktree * **No button**: For the main worktree (can't follow itself) ### Session List Repository headers show: * **Purple badge**: When follow mode is active, shows branch name * **Dropdown**: To change follow mode settings per repository ## Technical Implementation ### State Logic ```typescript theme={null} // Follow mode is only meaningful when: // 1. We have a worktree to follow // 2. We're not already in that worktree // 3. The main repo can switch to that branch const canEnableFollowMode = ( worktree: Worktree, currentLocation: string, mainRepoPath: string ) => { // Can't follow if we're in the main repo with no worktree selected if (currentLocation === mainRepoPath && !worktree) { return false; } // Can't follow the main worktree if (worktree.isMainWorktree) { return false; } // Can follow if we're creating a session in a worktree if (worktree && currentLocation === worktree.path) { return true; } return false; }; ``` ### Configuration Storage Follow mode state is stored in Git config: ```bash theme={null} # Enable follow mode for a branch git config vibetunnel.followBranch "feature/new-ui" # Check current follow mode git config vibetunnel.followBranch # Disable follow mode git config --unset vibetunnel.followBranch ``` ### Synchronization Rules 1. **Automatic Sync**: * Triggered by `post-checkout` git hook in worktrees * Only syncs if main repo has no uncommitted changes * Disables follow mode if branches have diverged 2. **Manual Override**: * Users can always manually switch branches * Follow mode doesn't prevent manual git operations * Re-enables when returning to the followed branch ## User Experience Guidelines ### Clear Messaging 1. **When Enabling**: * "Follow mode will keep your main repository on the same branch as this worktree" * "Enable to automatically sync branch changes" 2. **When Active**: * "Following worktree: feature/new-ui" * "Main repository syncs with this worktree's branch" 3. **When Disabled**: * "Follow mode disabled due to uncommitted changes" * "Branches have diverged - follow mode disabled" ### Visual Indicators * **Toggle Switch**: Only visible when applicable * **Status Badge**: Purple badge with branch name when active * **Button States**: Clear "Follow"/"Following" states in worktree manager ## Error Handling ### Common Scenarios 1. **Uncommitted Changes**: * Disable follow mode automatically * Show notification to user * Don't lose any work 2. **Branch Divergence**: * Detect when branches have different commits * Disable follow mode to prevent conflicts * Notify user of the situation 3. **Worktree Deletion**: * Automatically disable follow mode * Clean up git config * Update UI immediately ## Summary Follow mode should be: * **Contextual**: Only shown when it makes sense * **Safe**: Never causes data loss or conflicts * **Clear**: Users understand what it does * **Automatic**: Works in the background when enabled The key principle: **Follow mode only exists when there's a worktree to follow**. Without a worktree selection, the feature should not be visible or accessible. # Development Source: https://docs.vibetunnel.sh/docs/guides/development # Development Guide ## Setup ### Prerequisites * macOS 14.0+ * Xcode 16.0+ * Node.js 22.12 through 24.x * Bun 1.0+ * Rustup (`native/vt-fwd/rust-toolchain.toml` pins the forwarder toolchain) ### Clone & Build ```bash theme={null} # Clone repository git clone https://github.com/amantus-ai/vibetunnel.git cd vibetunnel # Install dependencies (cd web && pnpm install) # Build the macOS app, including the web assets and Rust forwarder (cd mac && ./scripts/build.sh --configuration Debug) # Build the iOS app separately (cd ios && xcodebuild -project VibeTunnel-iOS.xcodeproj \ -scheme VibeTunnel-iOS -configuration Debug \ -destination 'generic/platform=iOS Simulator' build) ``` ## Project Structure ``` vibetunnel/ ├── mac/ # macOS app │ ├── VibeTunnel/ # Swift sources │ │ ├── Core/ # Business logic │ │ └── Presentation/ # UI layer │ └── scripts/ # Build scripts ├── ios/ # iOS app │ └── VibeTunnel/ # Swift sources ├── native/vt-fwd/ # Rust terminal forwarder └── web/ # Server & frontend ├── src/ │ ├── server/ # Node.js server │ └── client/ # Web UI └── scripts/ # Utilities ``` ## Code Patterns ### Swift (macOS/iOS) **Observable Pattern** ```swift theme={null} // mac/VibeTunnel/Core/Services/ServerManager.swift @MainActor @Observable class ServerManager { private(set) var isRunning = false private(set) var error: Error? } ``` **Protocol-Oriented Design** ```swift theme={null} // mac/VibeTunnel/Core/Protocols/VibeTunnelServer.swift @MainActor protocol VibeTunnelServer: AnyObject { var isRunning: Bool { get } func start() async throws func stop() async } ``` **Error Handling** ```swift theme={null} enum ServerError: LocalizedError { case portInUse(Int) case binaryNotFound(String) var errorDescription: String? { switch self { case .portInUse(let port): return "Port \(port) is already in use" case .binaryNotFound(let path): return "Server binary not found at \(path)" } } } ``` ### TypeScript (Web) **Service Classes** ```typescript theme={null} // web/src/server/services/terminal-manager.ts export class TerminalManager { private sessions = new Map(); async createSession(options: SessionOptions): Promise { const session = new Session(options); this.sessions.set(session.id, session); return session; } } ``` **Lit Components** ```typescript theme={null} // web/src/client/components/terminal-view.ts @customElement('terminal-view') export class TerminalView extends LitElement { @property({ type: String }) sessionId = ''; @state() private connected = false; createRenderRoot() { return this; // No shadow DOM for Tailwind } } ``` ## Development Workflow ### Hot Reload Setup **Web Development** ```bash theme={null} # Terminal 1: Run dev server cd web && pnpm dev # Terminal 2: Enable in Mac app # Settings → Debug → Use Development Server ``` **Swift Development with Poltergeist** ```bash theme={null} # Install Poltergeist if available poltergeist # Auto-rebuilds on file changes # Check menu bar for build status ``` ### Testing **Unit Tests** ```bash theme={null} # macOS cd mac && xcodebuild test # iOS cd ios && ./scripts/test-with-coverage.sh # Web cd web && pnpm test ``` **E2E Tests** ```bash theme={null} cd web && pnpm test:e2e ``` ### Debugging **View Logs** ```bash theme={null} ./scripts/vtlog.sh -n 100 # Last 100 lines ./scripts/vtlog.sh -e # Errors only ./scripts/vtlog.sh -c Server # Component filter ``` **Debug Server** ```bash theme={null} # Run server directly cd web && pnpm dev:server # With inspector node --inspect dist/server/server.js ``` ## Common Tasks ### Add New API Endpoint 1. Define in `web/src/server/routes/api.ts` 2. Add types in `web/src/shared/types.ts` 3. Update client in `web/src/client/services/api.ts` 4. Add tests in `web/tests/api.test.ts` ### Add New Menu Item 1. Update `mac/VibeTunnel/Presentation/MenuBarView.swift` 2. Add action in `mac/VibeTunnel/Core/Actions/` 3. Update settings if needed ### Modify Terminal Protocol 1. Update framing/types in `web/src/shared/ws-v3.ts` 2. Update server routing in `web/src/server/services/ws-v3-hub.ts` 3. Update clients: * Web: `web/src/client/services/terminal-socket-client.ts` * iOS: `ios/VibeTunnel/Services/BufferWebSocketClient.swift` 4. Add/adjust tests: `web/src/test/e2e/websocket-v3.e2e.test.ts` ## Build System ### macOS Build ```bash theme={null} cd mac ./scripts/build.sh # Release build ./scripts/build.sh --configuration Debug ./scripts/build.sh --no-sign # Disable code signing ``` ### Web Build ```bash theme={null} cd web pnpm build # Production build, including the Rust forwarder node scripts/build-fwd-rust.js # Rust forwarder only ``` ### Release Build ```bash theme={null} cd mac ./scripts/release.sh stable # Full stable release ``` ## Code Quality ### Linting ```bash theme={null} # Swift cd mac && ./scripts/lint.sh # TypeScript cd web && pnpm lint cd web && pnpm check:fix ``` ### Formatting ```bash theme={null} # Swift (SwiftFormat) swiftformat mac/ ios/ # TypeScript (Prettier) cd web && pnpm format ``` ## Performance ### Profiling ```bash theme={null} # Server performance node --prof dist/server/server.js node --prof-process isolate-*.log # Client performance # Use Chrome DevTools Performance tab ``` ### Optimization Tips * Use binary protocol for terminal data * Batch WebSocket messages (16ms intervals) * Lazy load terminal sessions * Cache static assets with service worker ## Troubleshooting | Issue | Solution | | ----------------- | --------------------------------- | | Port in use | `lsof -i :4020` then kill process | | Build fails | Clean: `rm -rf node_modules dist` | | Tests fail | Check Node/Bun version | | Hot reload broken | Restart dev server | ## Contributing 1. Fork repository 2. Create feature branch 3. Follow code style 4. Add tests 5. Update documentation 6. Submit PR ## See Also * [Architecture](../core/architecture.md) * [API Reference](../core/api-reference.md) * [Testing Guide](testing.md) * [Release Process](../reference/release-process.md) # Quickstart Source: https://docs.vibetunnel.sh/docs/guides/quickstart # Quickstart Guide ## Installation ### Download & Install 1. Download VibeTunnel.dmg from [Releases](https://github.com/steipete/vibetunnel/releases) 2. Open DMG and drag VibeTunnel to Applications 3. Launch VibeTunnel from Applications 4. Grant accessibility permissions when prompted ### First Terminal ```bash theme={null} # Open a terminal session in your browser vt # Named session vt --name "Project Build" # Custom command vt --command "htop" ``` The browser opens automatically at `http://localhost:4020` ## Essential Commands | Command | Purpose | | -------------- | -------------------------- | | `vt` | Start new terminal session | | `vt list` | Show active sessions | | `vt kill ` | Terminate session | | `vt logs` | View server logs | | `vt --help` | Show all options | ## Configuration ### Settings Location ``` ~/Library/Preferences/com.steipete.VibeTunnel.plist ``` ### Key Settings | Setting | Default | Options | | -------------- | --------- | ------------------ | | Port | 4020 | Any available port | | Authentication | None | Password, Token | | Network | Localhost | LAN, Tailscale | | Auto-start | Disabled | Enable at login | ### Enable LAN Access 1. Click VibeTunnel menu bar icon 2. Select Preferences 3. Toggle "Allow LAN Connections" 4. Set password for security ## Development Mode ### Using Development Server ```bash theme={null} # Enable in VibeTunnel settings Settings → Debug → Use Development Server # Or run manually cd web pnpm install pnpm dev ``` Benefits: * Hot reload for web changes * No Mac app rebuild needed * Faster iteration ## Common Workflows ### Monitor AI Agents ```bash theme={null} # Start Claude Code in VibeTunnel vt --name "Claude Code" claude # Access from another device http://your-mac-ip:4020 ``` ### Remote Development ```bash theme={null} # With Tailscale vt --tailscale # With ngrok vt --ngrok ``` ### Multiple Sessions ```bash theme={null} # Start multiple named sessions vt --name "Frontend" --command "cd ~/frontend && npm run dev" vt --name "Backend" --command "cd ~/backend && npm start" vt --name "Database" --command "docker-compose up" ``` ## Keyboard Shortcuts ### Terminal | Shortcut | Action | | -------- | -------------- | | `Cmd+C` | Copy selection | | `Cmd+V` | Paste | | `Cmd+K` | Clear terminal | | `Cmd+T` | New session | | `Cmd+W` | Close session | ### Web Interface | Shortcut | Action | | -------------- | ------------ | | `Ctrl+Shift+C` | Copy | | `Ctrl+Shift+V` | Paste | | `Alt+1-9` | Switch tabs | | `Ctrl+Alt+T` | New terminal | ## Troubleshooting Quick Fixes ### Server Won't Start ```bash theme={null} # Check if port is in use lsof -i :4020 # Kill existing process killall node # Restart VibeTunnel osascript -e 'quit app "VibeTunnel"' open -a VibeTunnel ``` ### Can't Connect ```bash theme={null} # Check server status curl http://localhost:4020/api/health # View logs ./scripts/vtlog.sh -e ``` ### Permission Issues 1. System Preferences → Security & Privacy 2. Privacy → Accessibility 3. Add VibeTunnel.app 4. Restart VibeTunnel ## Next Steps * [Development Setup](development.md) - Build from source * [API Reference](../core/api-reference.md) - Integrate with VibeTunnel * [iOS App Setup](../platform/ios.md) - Mobile access * [Security Guide](../features/authentication.md) - Secure your sessions ## Quick Tips 1. **Auto-start**: Enable "Launch at Login" in preferences 2. **Custom port**: Set `VT_PORT=8080` environment variable 3. **Debug mode**: Hold Option while clicking menu bar icon 4. **Force quit session**: `vt kill --force ` 5. **Export recordings**: Sessions saved in `~/.vibetunnel/recordings/` # Testing Source: https://docs.vibetunnel.sh/docs/guides/testing # Testing Guide ## Quick Commands ```bash theme={null} # Run all tests ./scripts/test-all.sh # Platform-specific cd mac && xcodebuild test cd ios && ./scripts/test-with-coverage.sh cd web && pnpm test # With coverage cd web && pnpm test:coverage ``` ## Test Structure ``` tests/ ├── unit/ # Unit tests ├── integration/ # Integration tests ├── e2e/ # End-to-end tests └── fixtures/ # Test data ``` ## Unit Testing ### Swift (XCTest) ```swift theme={null} // mac/VibeTunnelTests/ServerManagerTests.swift import XCTest @testable import VibeTunnel class ServerManagerTests: XCTestCase { func testServerStart() async throws { let manager = ServerManager() try await manager.start() XCTAssertTrue(manager.isRunning) XCTAssertEqual(manager.port, "4020") } func testPortValidation() { XCTAssertThrowsError(try validatePort("abc")) XCTAssertNoThrow(try validatePort("8080")) } } ``` ### TypeScript (Vitest) ```typescript theme={null} // web/tests/session-manager.test.ts import { describe, it, expect, beforeEach } from 'vitest'; import { SessionManager } from '../src/server/services/session-manager'; describe('SessionManager', () => { let manager: SessionManager; beforeEach(() => { manager = new SessionManager(); }); it('creates session', async () => { const session = await manager.create({ shell: '/bin/bash', cols: 80, rows: 24 }); expect(session.id).toBeDefined(); expect(session.status).toBe('running'); }); }); ``` ## Integration Testing ### API Testing ```typescript theme={null} // web/tests/integration/api.test.ts import request from 'supertest'; import { app } from '../../src/server/app'; describe('API Integration', () => { it('creates session via API', async () => { const response = await request(app) .post('/api/sessions') .send({ shell: '/bin/bash' }) .expect(201); expect(response.body).toHaveProperty('id'); expect(response.body.status).toBe('running'); }); }); ``` ### WebSocket Testing ```typescript theme={null} // web/tests/integration/websocket.test.ts import { WebSocket } from 'ws'; // Helpers live in `web/src/shared/ws-v3.ts`. import { encodeWsV3Frame, encodeWsV3SubscribePayload, WsV3MessageType, WsV3SubscribeFlags } from './ws-v3'; describe('WebSocket Integration', () => { it('connects to session', async () => { const ws = new WebSocket('ws://localhost:4020/ws?token=JWT_TOKEN'); await new Promise((resolve) => { ws.on('open', resolve); }); ws.send( encodeWsV3Frame({ type: WsV3MessageType.SUBSCRIBE, sessionId: 'test', payload: encodeWsV3SubscribePayload({ flags: WsV3SubscribeFlags.Stdout }), }) ); const message = await new Promise((resolve) => { ws.on('message', resolve); }); expect(message.toString()).toContain('test'); }); }); ``` ## E2E Testing ### Playwright Setup ```typescript theme={null} // web/playwright.config.ts import { defineConfig } from '@playwright/test'; export default defineConfig({ testDir: './tests/e2e', use: { baseURL: 'http://localhost:4020', trace: 'on-first-retry', }, webServer: { command: 'pnpm dev', port: 4020, reuseExistingServer: !process.env.CI, }, }); ``` ### E2E Tests ```typescript theme={null} // web/tests/e2e/terminal.test.ts import { test, expect } from '@playwright/test'; test('complete terminal workflow', async ({ page }) => { // Navigate to app await page.goto('/'); // Create new terminal await page.click('button:has-text("New Terminal")'); // Wait for terminal to load const terminal = page.locator('.terminal'); await expect(terminal).toBeVisible(); // Type command await page.keyboard.type('echo "Hello, VibeTunnel"'); await page.keyboard.press('Enter'); // Verify output await expect(terminal).toContainText('Hello, VibeTunnel'); // Close session await page.click('button[aria-label="Close terminal"]'); await expect(terminal).not.toBeVisible(); }); ``` ## Performance Testing ### Load Testing ```javascript theme={null} // tests/performance/load.js import { check } from 'k6'; import ws from 'k6/ws'; export default function() { const url = 'ws://localhost:4020/ws?token=JWT_TOKEN'; ws.connect(url, {}, function(socket) { socket.on('open', () => { // WebSocket v3 is binary-framed; send `SUBSCRIBE` + `INPUT_TEXT` frames. // (Encode using the layout in `docs/websocket.md`.) // // socket.sendBinary(); }); socket.on('message', (data) => { check(data, { 'received response': (d) => d.length > 0, }); }); }); } export const options = { vus: 100, // 100 virtual users duration: '30s', // 30 second test }; ``` ### Benchmark Suite ```typescript theme={null} // tests/performance/benchmark.ts import { bench, describe } from 'vitest'; describe('Buffer encoding performance', () => { bench('encode 1KB', () => { encodeBuffer('x'.repeat(1024)); }); bench('encode 10KB', () => { encodeBuffer('x'.repeat(10240)); }); }); ``` ## Test Coverage ### Coverage Requirements | Component | Target | Current | | --------- | ------ | ------- | | Server | 80% | 85% | | Client | 70% | 72% | | Mac App | 60% | 65% | | iOS App | 75% | 78% | ### Generate Reports ```bash theme={null} # Web coverage cd web && pnpm test:coverage # iOS coverage cd ios && ./scripts/test-with-coverage.sh # View HTML report open coverage/index.html ``` ## Testing External Devices ### iPad/iPhone Testing ```bash theme={null} # 1. Start dev server on all interfaces cd web && pnpm dev --host 0.0.0.0 # 2. Get Mac IP ifconfig | grep inet # 3. Access from device # http://192.168.1.100:4021 ``` ### Cross-Browser Testing ```typescript theme={null} // playwright.config.ts projects: [ { name: 'chromium', use: { ...devices['Desktop Chrome'] } }, { name: 'firefox', use: { ...devices['Desktop Firefox'] } }, { name: 'webkit', use: { ...devices['Desktop Safari'] } }, { name: 'Mobile Safari', use: { ...devices['iPhone 13'] } }, ] ``` ## Mocking & Fixtures ### Mock PTY ```typescript theme={null} // tests/mocks/pty.ts export class MockPTY { write(data: string) { this.emit('data', `mock: ${data}`); } resize(cols: number, rows: number) { this.cols = cols; this.rows = rows; } } ``` ### Test Fixtures ```typescript theme={null} // tests/fixtures/sessions.ts export const mockSession = { id: 'test-session-123', name: 'Test Session', status: 'running', created: new Date(), pid: 12345, }; ``` ## CI/CD Testing ### GitHub Actions ```yaml theme={null} # .github/workflows/test.yml name: Tests on: [push, pull_request] jobs: test: runs-on: macos-14 steps: - uses: actions/checkout@v4 - name: Setup Node uses: actions/setup-node@v4 with: node-version: 22 - name: Install dependencies run: | cd web && pnpm install - name: Run tests run: ./scripts/test-all.sh - name: Upload coverage uses: codecov/codecov-action@v3 ``` ## Debugging Tests ### Debug Swift Tests ```bash theme={null} # Run with verbose output xcodebuild test -verbose # Debug specific test xcodebuild test -only-testing:VibeTunnelTests/ServerManagerTests/testServerStart ``` ### Debug TypeScript Tests ```bash theme={null} # Run with inspector node --inspect-brk ./node_modules/.bin/vitest # Run single test file pnpm test session-manager.test.ts # Watch mode pnpm test --watch ``` ## Best Practices 1. **Test naming**: Use descriptive names like `shouldCreateSessionWithCustomShell` 2. **Isolation**: Each test should be independent 3. **Cleanup**: Always cleanup resources (sessions, files, connections) 4. **Assertions**: Test both success and error cases 5. **Speed**: Keep unit tests under 100ms each 6. **Flakiness**: Retry flaky tests, investigate root cause ## Common Issues | Issue | Solution | | --------------- | ----------------------------- | | Tests timeout | Increase timeout, check async | | Port conflicts | Use random ports in tests | | Flaky WebSocket | Add connection retry logic | | Coverage gaps | Add tests for error paths | ## See Also * [Development Guide](development.md) * [CI/CD Setup](../reference/release-process.md#cicd-pipeline) * [Troubleshooting](../reference/troubleshooting.md) # Hq Source: https://docs.vibetunnel.sh/docs/hq # HQ Mode Documentation HQ (Headquarters) mode allows multiple VibeTunnel servers to work together in a distributed setup, where one server acts as the central HQ and others register as remote servers. ## Overview In HQ mode: * **HQ Server**: Acts as a central aggregator and router * **Remote Servers**: Individual VibeTunnel servers that register with the HQ * **Clients**: Connect to the HQ server and can create/manage sessions on any remote ## How It Works ### 1. Registration Flow When a remote server starts with HQ configuration: 1. It generates a unique bearer token 2. Registers itself with the HQ using Basic Auth (HQ credentials) 3. Provides its ID, name, URL, and bearer token 4. HQ stores this information in its `RemoteRegistry` ### 2. Session Management **Creating Sessions:** * Clients must specify a `remoteId` when creating sessions through HQ * HQ forwards the request to the specified remote using the bearer token * The remote creates the session locally * HQ tracks which sessions belong to which remote The web New Session form lists registered machines and requires one as the target. Working-directory paths are interpreted on that selected machine. Because repository discovery, directory browsing, and Git worktree helpers are local filesystem operations, the form hides those HQ-router helpers in distributed mode; enter the machine's path directly. **Session Operations:** * All session operations (get info, send input, kill, etc.) are proxied through HQ * HQ checks its registry to find which remote owns the session * Requests are forwarded with bearer token authentication ### 3. Health Monitoring * HQ performs health checks every 15 seconds on all registered remotes * Health check: `GET /api/health` with 5-second timeout * Failed remotes are automatically unregistered * Remotes refresh their registration every 60 seconds and automatically rejoin after sleep or a network interruption ### 4. Session Discovery * Remote servers watch their control directory for new sessions * When sessions are created/deleted, remotes notify HQ via `/api/remotes/{name}/refresh-sessions` * HQ fetches the latest session list from the remote and updates its registry ## Setup ### Running an HQ Server ```bash theme={null} # Basic HQ server vibetunnel-server --hq --username admin --password secret # HQ server on custom port vibetunnel-server --hq --port 8080 --username admin --password secret ``` ### Running Remote Servers ```bash theme={null} # Remote server registering with HQ vibetunnel-server \ --username local-user \ --password local-pass \ --hq-url https://hq.example.com \ --hq-username admin \ --hq-password secret \ --name production-1 # For local development (allow HTTP) vibetunnel-server \ --hq-url http://localhost:4020 \ --hq-username admin \ --hq-password secret \ --name dev-remote \ --allow-insecure-hq # Advertise an address that the HQ can resolve and reach vibetunnel-server \ --hq-url https://hq.example.com \ --hq-username admin \ --hq-password secret \ --name tailnet-remote \ --remote-url http://100.64.0.5:4020 ``` ### Command-Line Options **HQ Server Options:** * `--hq` - Enable HQ mode * `--username` - Admin username for HQ access * `--password` - Admin password for HQ access **Remote Server Options:** * `--hq-url` - URL of the HQ server * `--hq-username` - Username to authenticate with HQ * `--hq-password` - Password to authenticate with HQ * `--name` - Unique name for this remote server * `--remote-url` - HTTP(S) origin the HQ uses to connect back to this remote; defaults to the bind address or hostname * `--allow-insecure-hq` - Allow HTTP connections to HQ (dev only) * `--no-hq-auth` - Disable HQ authentication (testing only) ## API Endpoints ### HQ-Specific Endpoints **List Remotes:** ```http theme={null} GET /api/remotes Authorization: Basic Response: [ { "id": "uuid", "name": "production-1", "url": "http://remote1:4020", "registeredAt": "2025-01-17T10:00:00.000Z", "lastHeartbeat": "2025-01-17T10:15:00.000Z", "sessionIds": ["session1", "session2"] } ] ``` **Register Remote (called by remotes):** ```http theme={null} POST /api/remotes/register Authorization: Basic Content-Type: application/json { "id": "unique-id", "name": "remote-name", "url": "http://remote:4020", "token": "bearer-token-for-hq-to-use" } ``` **Refresh Sessions (called by remotes):** ```http theme={null} POST /api/remotes/{remoteName}/refresh-sessions Authorization: Basic Content-Type: application/json { "action": "created" | "deleted", "sessionId": "session-id" } ``` ### Session Management Through HQ **Create Session on Remote:** ```http theme={null} POST /api/sessions Content-Type: application/json { "command": ["bash"], "remoteId": "remote-uuid", // Specify which remote "name": "My Session" } ``` **All Standard Endpoints Work Transparently:** * `GET /api/sessions` - Aggregates from all remotes * `GET /api/sessions/:id` - Proxied to owning remote * `POST /api/sessions/:id/input` - Proxied to owning remote * `DELETE /api/sessions/:id` - Proxied to owning remote * `GET /ws` (WebSocket) - Unified v3 terminal stream proxied/fanned-out via HQ ## Authentication Flow 1. **Client → HQ**: Standard authentication (Basic Auth or JWT) 2. **HQ → Remote**: Bearer token (provided by remote during registration) 3. **Remote → HQ**: Basic Auth (HQ credentials) ## WebSocket Support * Unified terminal transport: `/ws` (WebSocket v3 framing) * HQ keeps one upstream `/ws` connection per remote and fans out frames to clients * Subscription flags are aggregated per session (stdout/snapshots/events) ## Implementation Details ### Key Components **RemoteRegistry** (`src/server/services/remote-registry.ts`): * Maintains map of registered remotes * Tracks session ownership (which sessions belong to which remote) * Performs periodic health checks * Handles registration/unregistration **HQClient** (`src/server/services/hq-client.ts`): * Used by remote servers to register with HQ * Handles registration and cleanup * Manages bearer token generation **Session Routes** (`src/server/routes/sessions.ts`): * Checks if running in HQ mode * For remote sessions, forwards requests using bearer token * For local sessions, handles normally **Control Directory Watcher** (`src/server/services/control-dir-watcher.ts`): * Watches for new/deleted sessions * Notifies HQ about session changes * Triggers session list refresh on HQ ### Session Tracking * Each remote maintains its own session IDs * HQ tracks which sessions belong to which remote * Session IDs are not namespaced - they remain unchanged * The `source` field in session objects indicates the remote name ## Testing The e2e tests in `src/test/e2e/hq-mode.e2e.test.ts` demonstrate: 1. Starting HQ server and multiple remotes 2. Remote registration 3. Creating sessions on specific remotes 4. Proxying session operations 5. WebSocket buffer aggregation 6. Cleanup and unregistration ## Limitations * Remotes must be network-accessible from the HQ server * If the default hostname is not resolvable from HQ, set `--remote-url` to a reachable origin such as a tailnet address * Health checks use a fixed 15-second interval * No built-in load balancing (clients must specify remoteId) * Bearer tokens are generated per server startup (not persistent) ## Security Considerations * Always use HTTPS in production (use `--allow-insecure-hq` only for local dev) * Use HTTPS for `--remote-url` on untrusted networks because HQ sends the remote bearer token on callback requests * Bearer tokens are sensitive - they allow HQ to execute commands on remotes * Remote listing and registration responses expose only public machine metadata, never callback bearer tokens * HQ credentials should be strong and kept secure * Consider network isolation between HQ and remotes * Remotes should not be directly accessible from the internet # Introduction Source: https://docs.vibetunnel.sh/docs/introduction # Welcome to VibeTunnel Documentation VibeTunnel is a powerful terminal sharing application that allows you to access your terminal sessions through any web browser. Whether you're on macOS, Linux, iPad, iPhone, or any other device with a web browser, VibeTunnel makes your terminal accessible from anywhere. ## Key Features * **Remote Terminal Access**: Access your terminal from any web browser * **Cross-Platform**: Native macOS app with Swift/SwiftUI, plus Linux support via npm package * **Secure Connection**: End-to-end encrypted terminal sessions * **Multi-Device**: Works on iOS, iPadOS, and any modern web browser * **Real-time Sync**: Changes sync instantly across all connected devices * **Headless Mode**: Run on servers and cloud instances without a GUI ## Quick Start ### For macOS 1. [Download VibeTunnel](https://vibetunnel.com/download) for macOS 2. Launch the app and create your first terminal session 3. Access your terminal from any device at your unique VibeTunnel URL ### For Linux & Headless Environments 1. Install via npm: `npm install -g vibetunnel` 2. Run the server: `vibetunnel-server` 3. Access your terminal at `http://localhost:4020` ## Documentation Overview Installation guides and your first steps with VibeTunnel Learn about VibeTunnel's system design and technical details Build, test, and contribute to VibeTunnel Complete API documentation and specifications ## Join Our Community * [GitHub](https://github.com/amantus-ai/vibetunnel) - Star us on GitHub * [Discord](https://discord.gg/vibetunnel) - Join our Discord community * [Twitter](https://twitter.com/vibetunnel) - Follow us for updates Ready to get started? Check out our [installation guide](/mac/README) or dive into the [architecture overview](/docs/ARCHITECTURE). # Ios spec Source: https://docs.vibetunnel.sh/docs/ios-spec # VibeTunnel iOS/iPadOS Native App Specification ## Overview VibeTunnel iOS is a native SwiftUI application that provides a beautiful, native interface to the VibeTunnel terminal multiplexer backend. The app allows users to create, manage, and interact with terminal sessions on their Mac from their iOS/iPadOS devices. ## Target Platform * **iOS/iPadOS**: 18.0+ * **Universal App**: Single app that adapts to iPhone and iPad * **SwiftUI**: Modern declarative UI framework * **Terminal Engine**: ghostty-web (WASM + canvas) ## Core Features ### 1. Connection Management * **Initial Setup Dialog**: * Server URL/IP input field * Port configuration (default: 3000) * Connection testing with status feedback * Saved connections list (stored in UserDefaults/KeyChain) * Auto-reconnection on app launch ### 2. Session Management * **Session List View**: * Display all active and exited sessions * Real-time status updates (auto-refresh every 3 seconds) * Session cards showing: * Session name/command * Working directory * Status (running/exited) * PID (if running) * Exit code (if exited) * Started time * Last modified time * Pull-to-refresh functionality * Search/filter capabilities * **Session Actions**: * Create new session * Kill running session * Clean up exited session files * Clean up all exited sessions * Tap to view terminal ### 3. Terminal View * **ghostty-web Integration**: * Full terminal emulation using ghostty-web * Support for ANSI escape sequences * 256-color and true color support * VS Code dark theme colors * Font: SF Mono or custom monospace fonts * Adjustable font size * **Input/Output**: * Native iOS keyboard integration * Special keys toolbar (arrows, escape, tab, ctrl) * Optional OpenAI BYOK voice transcription that inserts text without executing it * Copy/paste support * URL detection and tap-to-open * Smooth scrolling with momentum * Pinch-to-zoom font sizing * **Real-time Updates**: * WebSocket buffer streaming for terminal output * Efficient buffer management * Auto-scroll to bottom on new output * Scroll position indicator ### 4. Session Creation * **New Session Form**: * Command input (default: zsh) * Working directory picker * Session name (optional) * Terminal dimensions (auto-calculated based on device) * Recent commands/directories ### 5. iPad-Specific Features * **Split View Support**: * Session list in sidebar * Terminal in main view * Multiple terminal tabs * Drag and drop support * **Keyboard Support**: * Hardware keyboard shortcuts * Command+T for new session * Command+W to close session * Command+K to clear terminal * **Multitasking**: * Slide Over support * Split View with other apps * Stage Manager compatibility ## Technical Architecture ### 1. Project Structure ``` ios/ ├── VibeTunnel/ │ ├── App/ │ │ ├── VibeTunnelApp.swift │ │ └── ContentView.swift │ ├── Models/ │ │ ├── Session.swift │ │ ├── ServerConfig.swift │ │ └── TerminalData.swift │ ├── Views/ │ │ ├── Connection/ │ │ │ ├── ConnectionView.swift │ │ │ └── ServerConfigForm.swift │ │ ├── Sessions/ │ │ │ ├── SessionListView.swift │ │ │ ├── SessionCardView.swift │ │ │ └── SessionCreateView.swift │ │ ├── Terminal/ │ │ │ ├── TerminalView.swift │ │ │ ├── GhosttyWebView.swift │ │ │ ├── TerminalBufferRenderer.swift │ │ │ └── TerminalToolbar.swift │ │ └── Common/ │ │ ├── LoadingView.swift │ │ └── ErrorView.swift │ ├── Services/ │ │ ├── APIClient.swift │ │ ├── SessionService.swift │ │ ├── TerminalService.swift │ │ └── BufferWebSocketClient.swift │ ├── Utils/ │ │ ├── KeychainHelper.swift │ │ ├── ColorTheme.swift │ │ └── Extensions/ │ └── Resources/ │ ├── Assets.xcassets │ └── Info.plist └── VibeTunnel.xcodeproj ``` ### 2. Data Models ```swift theme={null} // Session Model struct Session: Codable, Identifiable { let id: String let command: String let workingDir: String let name: String? let status: SessionStatus let exitCode: Int? let startedAt: Date let lastModified: Date let pid: Int? let waiting: Bool? let width: Int? let height: Int? } enum SessionStatus: String, Codable { case running case exited } // Server Configuration struct ServerConfig: Codable { let host: String let port: Int let name: String? var baseURL: URL { URL(string: "http://\(host):\(port)")! } } // Terminal Event struct TerminalEvent { let timestamp: Double let type: EventType let data: String enum EventType: String { case output = "o" case input = "i" case resize = "r" case marker = "m" } } ``` ### 3. API Integration ```swift theme={null} // API Client Protocol protocol APIClientProtocol { func getSessions() async throws -> [Session] func createSession(_ data: SessionCreateData) async throws -> String func killSession(_ sessionId: String) async throws func cleanupSession(_ sessionId: String) async throws func cleanupAllExitedSessions() async throws -> [String] func sendInput(sessionId: String, text: String) async throws func resizeTerminal(sessionId: String, cols: Int, rows: Int) async throws } // Buffer WebSocket client for terminal streaming class BufferWebSocketClient: NSObject { func connect() func subscribe(to sessionId: String, handler: @escaping (TerminalWebSocketEvent) -> Void) func disconnect() } ``` ### 4. ghostty-web Integration ```swift theme={null} // Terminal View Controller class TerminalViewController: UIViewController { private let terminal: GhosttyWebView private let sessionId: String private let bufferClient: BufferWebSocketClient // Configure terminal with VS Code theme // Handle input/output // Manage resize events // Stream terminal data via WebSocket buffer updates } ``` ### 5. State Management * Use SwiftUI's `@StateObject` and `@ObservedObject` for view models * Combine framework for reactive updates * AsyncStream/Task for WebSocket handling * UserDefaults for connection preferences * Keychain for secure credential storage ## UI/UX Design ### 1. Design System * **Colors**: Match VS Code dark theme * Background: #1e1e1e * Foreground: #d4d4d4 * Accent: System blue * Success: System green * Error: System red * **Typography**: * System fonts for UI * SF Mono for terminal * Dynamic Type support * **Components**: * Native SwiftUI components * Consistent padding and spacing * Smooth animations and transitions ### 2. Navigation Flow ``` ConnectionView (if not connected) ↓ SessionListView (main screen) ├→ SessionCreateView (modal) └→ TerminalView (push/detail) └→ TerminalToolbar (overlay) ``` ### 3. Responsive Design * Adaptive layouts for different device sizes * Compact/Regular size class handling * Landscape optimization * Dynamic terminal sizing ## Implementation Phases ### Phase 1: Foundation (Tasks 2-4) * Set up Xcode project with SwiftUI * Create basic navigation structure * Implement connection dialog * Build data models and API client * Store server configuration ### Phase 2: Session Management (Task 5, 8) * Session list view with real-time updates * Session card components * Create new session form * Kill and cleanup actions * Pull-to-refresh ### Phase 3: Terminal Integration (Tasks 6-7, 9) * Integrate ghostty-web resources * Terminal view wrapper * WebSocket buffer streaming client * Input handling * Resize support ### Phase 4: Polish & iPad (Task 10) * iPad-specific layouts * Keyboard shortcuts * Settings view * Connection management * Performance optimization ## Testing Strategy ### Unit Tests * API client methods * Data model parsing * Session state management * URL construction ### UI Tests * Connection flow * Session creation * Terminal interaction * Error handling ### Integration Tests * End-to-end session lifecycle * WebSocket streaming reliability * Terminal command execution ## Security Considerations * Use HTTPS when possible (with option for HTTP in local network) * Store credentials in Keychain * Validate server certificates * Sanitize terminal output * Handle authentication if backend requires it ## Performance Optimization * Lazy loading of session list * Efficient terminal buffer management * Debounced resize events * Background session updates * Memory-efficient buffer streaming ## Future Enhancements 1. **Multiple Connections**: Support multiple VibeTunnel servers 2. **Session Sharing**: Share terminal sessions with others 3. **Recording**: Record and playback terminal sessions 4. **Themes**: Additional color themes beyond VS Code 5. **Shortcuts**: Customizable keyboard shortcuts 6. **File Transfer**: Upload/download files through the app 7. **Notifications**: Background notifications for session events ## Dependencies * **ghostty-web**: Terminal emulator engine * **Alamofire** (optional): For networking (or use URLSession) * **KeychainSwift**: Secure credential storage ## App Store Considerations * Ensure compliance with App Store guidelines * Proper error handling and user feedback * Privacy policy for network usage * Export compliance for encryption ## Conclusion This specification outlines a comprehensive native iOS/iPadOS client for VibeTunnel that leverages SwiftUI and ghostty-web to provide a superior terminal experience compared to the web interface. The app will be fast, responsive, and take full advantage of native iOS features while maintaining feature parity with the web frontend. # Keyboard shortcuts Source: https://docs.vibetunnel.sh/docs/keyboard-shortcuts # VibeTunnel Keyboard Shortcuts VibeTunnel provides a comprehensive set of keyboard shortcuts for efficient terminal session management. The app intelligently handles keyboard input to balance browser functionality with terminal control. ## Keyboard Capture Modes VibeTunnel operates in two keyboard capture modes: ### 1. **Capture Active** (Default) * Most keyboard shortcuts are sent to the terminal * Critical browser shortcuts remain functional * Indicated by the keyboard icon in the session header ### 2. **Capture Disabled** * Browser shortcuts take precedence * Terminal receives only typed text * Toggle with double-press Escape ## Toggling Keyboard Capture | Action | Description | | ----------------- | ------------------------------ | | **Double Escape** | Toggle keyboard capture on/off | Press Escape twice within 500ms to toggle between capture modes. ## Mobile Quick Keys On touch devices, open **Settings → Mobile Quick Keys → Customize** to reorder or hide the terminal shortcut buttons. VibeTunnel includes Default and Compact layouts, stores the selected layout in the current browser, and keeps the Done button fixed so the software keyboard can always be dismissed. ## Critical Browser Shortcuts (Always Available) These shortcuts always work, regardless of keyboard capture state: ### Tab Management | macOS | Windows/Linux | Action | | ----- | ------------- | -------------------- | | ⌘T | Ctrl+T | New tab | | ⌘W | Ctrl+W | Close tab | | ⌘⇧T | Ctrl+Shift+T | Reopen closed tab | | ⌘1-9 | Ctrl+1-9 | Switch to tab 1-9\* | | ⌘0 | Ctrl+0 | Switch to last tab\* | \*When keyboard capture is active in session view, these shortcuts switch between VibeTunnel sessions instead of browser tabs ### Window Management | macOS | Windows/Linux | Action | | ----- | ------------- | ---------------------- | | ⌘N | Ctrl+N | New window | | ⌘⇧N | Ctrl+Shift+N | New incognito window | | ⌘Q | Ctrl+Q | Quit browser | | ⌘H | - | Hide window (macOS) | | - | Alt+F4 | Close window (Windows) | ### Essential Operations | macOS | Windows/Linux | Action | | ----- | ------------- | ------------------- | | ⌘C | Ctrl+C | Copy | | ⌘V | Ctrl+V | Paste | | ⌘A | Ctrl+A | Select all\* | | ⌘, | - | Preferences (macOS) | \*When capture is active, ⌘A/Ctrl+A goes to terminal (moves cursor to line start) ### Developer Tools | macOS | Windows/Linux | Action | | ----- | ------------- | ------------- | | F12 | F12 | Open DevTools | | ⌘⌥I | Ctrl+Shift+I | Open DevTools | ## VibeTunnel-Specific Shortcuts ### Navigation | macOS | Windows/Linux | Action | Context | | ------ | ------------- | ------------------ | -------------------- | | ⌘K | Ctrl+K | Create new session | Any view | | ⌘O | Ctrl+O | Browse files | List view | | ⌘B | Ctrl+B | Toggle sidebar | Any view | | Escape | Escape | Return to list | Session/File browser | ### Session Switching (When Keyboard Capture Active) | macOS | Windows/Linux | Action | Context | | ------ | ------------- | ------------------------ | ---------------------------- | | ⌘1...9 | Ctrl+1...9 | Switch to session 1 to 9 | Session view with capture ON | | ⌘0 | Ctrl+0 | Switch to session 10 | Session view with capture ON | **Note**: When keyboard capture is active in session view, number shortcuts switch between VibeTunnel sessions instead of browser tabs. The session numbers correspond to the numbers shown in the session list. This allows quick navigation between active sessions without leaving the keyboard. ## Terminal Shortcuts (When Capture Active) When keyboard capture is active, these shortcuts are sent to the terminal: ### Cursor Movement | macOS | Windows/Linux | Terminal Action | | ----- | ------------- | ------------------ | | ⌘A | Ctrl+A | Move to line start | | ⌘E | Ctrl+E | Move to line end | | ⌥← | Alt+Left | Move word backward | | ⌥→ | Alt+Right | Move word forward | ### Text Editing | macOS | Windows/Linux | Terminal Action | | ----- | ------------- | -------------------- | | ⌘W | Ctrl+W | Delete word backward | | ⌘U | Ctrl+U | Delete to line start | | ⌘K | Ctrl+K | Delete to line end | | ⌥⌫ | Alt+Backspace | Delete word backward | | ⌥D | Alt+D | Delete word forward | ### History & Search | macOS | Windows/Linux | Terminal Action | | ----- | ------------- | ---------------------- | | ⌘R | Ctrl+R | Reverse history search | | ⌘P | Ctrl+P | Previous command | | ⌘N | Ctrl+N | Next command | ### Terminal Control | macOS | Windows/Linux | Terminal Action | | ----- | ------------- | ----------------- | | ⌘L | Ctrl+L | Clear screen | | ⌘D | Ctrl+D | EOF/Exit | | ⌘C | Ctrl+C | Interrupt process | | ⌘Z | Ctrl+Z | Suspend process | ## Shortcuts Behavior by Capture State ### When Capture is Active ✅ These shortcuts go to the terminal: * Text editing (⌘A, ⌘E, ⌘W, ⌘K, ⌘U) * Navigation (⌘F, ⌘B for forward/backward char) * Terminal control (⌘L, ⌘D, ⌘R) ### When Capture is Disabled ❌ These shortcuts perform browser actions: * ⌘F/Ctrl+F - Find in page * ⌘L/Ctrl+L - Focus address bar * ⌘D/Ctrl+D - Bookmark page * ⌘P/Ctrl+P - Print * ⌘S/Ctrl+S - Save page ## Special Key Handling ### Modified Enter Key | Combination | Terminal Receives | | ----------- | ------------------------ | | Enter | Standard return | | Ctrl+Enter | Special control sequence | | Shift+Enter | Special shift sequence | ### Function Keys * F1-F12 are sent to the terminal when capture is active * F5 (Refresh) works in browser when capture is disabled * F11 (Fullscreen) always works ## Platform Differences ### macOS * Uses ⌘ (Command) as primary modifier * ⌥ (Option) for word navigation * Additional shortcuts like ⌘H (Hide), ⌘M (Minimize) ### Windows/Linux * Uses Ctrl as primary modifier * Alt for word navigation * Alt+F4 closes windows ## Tips 1. **Double-tap Escape** to quickly toggle between terminal and browser shortcuts 2. **Critical shortcuts** (new tab, close tab, copy/paste) always work 3. **Session switching** (⌘1-9, ⌘0) - When keyboard capture is ON in session view, quickly switch between active sessions 4. **Tab switching** (⌘1-9, ⌘0) - When keyboard capture is OFF, switch browser tabs as usual 5. When unsure, check the keyboard icon in the session header to see capture state ## Troubleshooting ### Shortcuts not working as expected? 1. **Check keyboard capture state** - Look for the keyboard icon in the session header 2. **Try double-escape** - Toggle capture mode on/off 3. **Browser shortcuts in terminal?** - Ensure keyboard capture is active 4. **Terminal shortcuts in browser?** - Disable keyboard capture with double-escape ### Copy/Paste issues? * Standard copy/paste (⌘C/⌘V or Ctrl+C/Ctrl+V) always works * For terminal copy mode, use the terminal's built-in shortcuts * Right-click context menu is always available ## Implementation Details The keyboard shortcut system is implemented in: * `web/src/client/utils/browser-shortcuts.ts` - Centralized shortcut detection * `web/src/client/components/session-view/input-manager.ts` - Terminal input handling * `web/src/client/app.ts` - Application-level shortcut handling The system uses a priority-based approach: 1. Critical browser shortcuts (highest priority) 2. VibeTunnel app shortcuts 3. Terminal shortcuts (when capture active) 4. Browser defaults (when capture disabled) # Linux Source: https://docs.vibetunnel.sh/docs/linux # Linux (Ubuntu) Development + NPM Usage ## Goals * One command to bootstrap Linux dev * npm install/build works without pnpm * Avoid SEA on Linux by default (Node CLI path is more reliable) ## Quickstart (Ubuntu 24.04) ```bash theme={null} web/scripts/linux-bootstrap.sh cd web npm install npm run build ``` ## What the bootstrap does * Installs system deps: `curl`, `ca-certificates`, `xz-utils`, `python3`, `make`, `g++`, `git` * Installs `libpam0g-dev` for PAM auth native module * Installs checksum-verified Node.js 24.16.0 if missing or too old * Installs rustup and the Rust 1.97.0 toolchain pinned by `native/vt-fwd/rust-toolchain.toml` ## SEA on Linux (disabled by default) SEA builds are skipped on Linux unless explicitly enabled. Enable if you want to test SEA (not recommended on Linux): ```bash theme={null} VIBETUNNEL_BUILD_SEA=1 npm run build # or npm run build -- --build-sea ``` ## PAM Authentication (optional) * `authenticate-pam` is an optional dependency. * If `libpam0g-dev` is present during install, PAM auth will be built and used. * If it’s missing, VibeTunnel still runs; auth falls back to env/SSH methods. To force PAM after installing deps: ```bash theme={null} cd web npm rebuild authenticate-pam ``` ## npmjs (global install) ```bash theme={null} npm install -g vibetunnel@beta vibetunnel --help ``` Linux npm package runs the Node CLI wrapper (no SEA). Systemd support is available: ```bash theme={null} vibetunnel systemd install systemctl --user start vibetunnel systemctl --user status vibetunnel ``` ## Troubleshooting * `pnpm` missing during build: use npm (`npm install && npm run build`) or install pnpm. * `cargo` or `rustc` missing: rerun `web/scripts/linux-bootstrap.sh`. * `pam_appl.h` missing: `sudo apt-get install -y libpam0g-dev`. # Logging style guide Source: https://docs.vibetunnel.sh/docs/logging-style-guide # VibeTunnel Logging Style Guide ## Logging style ### 1. No Colors in Error/Warn ```typescript theme={null} // ❌ BAD logger.error(chalk.red('Failed to connect')); logger.warn(chalk.yellow('Missing config')); // ✅ GOOD logger.error('Failed to connect'); logger.warn('Missing config'); ``` ### 2. Use Colors in logger.log Only ```typescript theme={null} // Success = green logger.log(chalk.green('Session created')); logger.log(chalk.green(`Connected to ${server}`)); // Warning/Neutral = yellow logger.log(chalk.yellow('Shutting down...')); logger.log(chalk.yellow(`Client disconnected`)); // Info = blue logger.log(chalk.blue('New client connected')); // Metadata = gray logger.log(chalk.gray('Debug mode enabled')); ``` ### 3. Always Include Error Object ```typescript theme={null} // ❌ BAD logger.error(`Failed: ${error.message}`); // ✅ GOOD logger.error('Failed to connect:', error); ``` ### 4. Message Format * Start with lowercase (except acronyms) * No periods at end * Be concise * Include relevant IDs ```typescript theme={null} // ❌ BAD logger.log('The session has been created successfully.'); logger.error('ERROR: Failed to connect to server!'); // ✅ GOOD logger.log(`Session ${id} created`); logger.error('Failed to connect to server'); ``` ### 5. No Prefixes or Tags ```typescript theme={null} // ❌ BAD logger.log('[STREAM] Client connected'); logger.error('ERROR: Connection failed'); logger.warn('WARNING: Low memory'); // ✅ GOOD logger.log('Client connected to stream'); logger.error('Connection failed'); logger.warn('Low memory'); ``` ## Common Patterns ### Lifecycle Events ```typescript theme={null} // Starting logger.log(chalk.green('Server started')); logger.log(chalk.green(`Session ${id} created`)); // Stopping logger.log(chalk.yellow('Shutting down...')); logger.log(chalk.yellow(`Session ${id} terminated`)); // Connections logger.log(chalk.blue('Client connected')); logger.log(chalk.yellow('Client disconnected')); ``` ### Operations ```typescript theme={null} // Success logger.log(chalk.green(`File uploaded: ${filename}`)); // In Progress logger.log(`Processing ${count} items`); // Failure logger.error('Upload failed:', error); ``` ### Debug (no colors needed) ```typescript theme={null} logger.debug(`Buffer size: ${size}`); logger.debug(`Request headers: ${JSON.stringify(headers)}`); ``` ## Quick Reference | Event Type | Log Level | Color | | ---------- | --------- | ------------ | | Success | log | chalk.green | | Connection | log | chalk.blue | | Disconnect | log | chalk.yellow | | Shutdown | log | chalk.yellow | | Error | error | none | | Warning | warn | none | | Debug info | debug | none | | Metadata | log | chalk.gray | # Npm release Source: https://docs.vibetunnel.sh/docs/npm-release # NPM Release Checklist This checklist ensures a smooth and error-free npm release process for VibeTunnel. ## Pre-Release Checklist ### 1. Code Quality * [ ] Run all tests: `pnpm test` * [ ] Run linting: `pnpm run lint` * [ ] Run type checking: `pnpm run typecheck` * [ ] Run format check: `pnpm run format:check` * [ ] Fix any issues found: `pnpm run check:fix` ### 2. Dependency Updates * [ ] Update all dependencies to latest versions * [ ] Run `pnpm update --interactive --latest` * [ ] Test thoroughly after updates * [ ] Check for security vulnerabilities: `pnpm audit` ### 3. Version Updates (CRITICAL - Must be synchronized!) * [ ] Update version in `web/package.json` * [ ] Update version in `web/package.npm.json` (must match!) * [ ] Update version in `mac/VibeTunnel/version.xcconfig` (MARKETING\_VERSION) * [ ] Update version in `ios/VibeTunnel/version.xcconfig` (if applicable) * [ ] Ensure all versions match exactly ### 4. Changelog * [ ] Update CHANGELOG.md with new features, fixes, and breaking changes * [ ] Include migration guide for breaking changes * [ ] Credit contributors ## Build Process ### 5. Clean Build * [ ] Clean previous builds: `rm -rf dist-npm/ vibetunnel-*.tgz` * [ ] Run the complete build on an Apple Silicon Mac with Docker available * [ ] Run build: `pnpm run build:npm` * [ ] Verify build output shows all platforms built successfully * [ ] Check for "✅ authenticate-pam listed as optional dependency" in output ### 6. Package Verification (CRITICAL) * [ ] Verify tarball exists: `ls -la vibetunnel-*.tgz` * [ ] Extract package.json: `tar -xf vibetunnel-*.tgz package/package.json` * [ ] Verify authenticate-pam is OPTIONAL: ```bash theme={null} grep -A5 -B5 authenticate-pam package/package.json # Must show under "optionalDependencies", NOT "dependencies" ``` * [ ] Clean up: `rm -rf package/` * [ ] Check package size is reasonable (\~8-15 MB) ### 7. Package Contents Verification * [ ] List package contents: `tar -tzf vibetunnel-*.tgz | head -50` * [ ] Verify critical files are included: * [ ] `package/lib/vibetunnel-cli` * [ ] `package/lib/cli.js` * [ ] `package/bin/vibetunnel` * [ ] `package/bin/vt` * [ ] `package/scripts/postinstall.js` * [ ] `package/scripts/install-vt-command.js` * [ ] `package/node-pty/` directory * [ ] `package/prebuilds/` directory with .tar.gz files * [ ] `package/public/` directory ## Testing ### 8. Local Installation Test * [ ] Test installation: `npm install -g ./vibetunnel-*.tgz` * [ ] Verify version: `vibetunnel --version` * [ ] Start server: `vibetunnel` * [ ] Access web UI: [http://localhost:4020](http://localhost:4020) * [ ] Test vt command: `vt echo "test"` * [ ] Uninstall: `npm uninstall -g vibetunnel` ### 9. Docker Test (Linux Compatibility) * [ ] Create test Dockerfile: ```dockerfile theme={null} FROM node:22-slim COPY vibetunnel-*.tgz /tmp/ RUN npm install -g /tmp/vibetunnel-*.tgz CMD ["vibetunnel", "--version"] ``` * [ ] Build: `docker build -t vt-test .` * [ ] Run: `docker run --rm vt-test` * [ ] Test without PAM headers (should succeed) * [ ] Test with PAM: Add `RUN apt-get update && apt-get install -y libpam0g-dev` before install ### 10. Cross-Platform Testing * [ ] Test on macOS (if available) * [ ] Test on Linux x64 * [ ] Test on Linux ARM64 (if available) * [ ] Verify prebuilds are used (no compilation during install) ## Publishing ### 11. Pre-Publish Checks * [ ] Ensure you're logged in to npm: `npm whoami` * [ ] Check current tags: `npm dist-tag ls vibetunnel` * [ ] Verify no uncommitted changes: `git status` * [ ] Create git tag: `git tag v1.0.0-beta.X` ### 12. Publish (CRITICAL - Use tarball filename!) * [ ] Publish beta: `npm publish vibetunnel-*.tgz --tag beta` * [ ] Verify on npm: [https://www.npmjs.com/package/vibetunnel](https://www.npmjs.com/package/vibetunnel) * [ ] Test installation from npm: `npm install -g vibetunnel@beta` ### 13. Post-Publish Verification * [ ] Check package page shows correct version * [ ] Verify optional dependencies are displayed correctly * [ ] Test installation on clean system * [ ] Monitor npm downloads and issues ### 14. Promotion to Latest (if stable) * [ ] Wait for user feedback (at least 24 hours) * [ ] If stable, promote: `npm dist-tag add vibetunnel@VERSION latest` * [ ] Update documentation to reference new version ## Post-Release ### 15. Documentation Updates * [ ] Update README.md with new version info * [ ] Update installation instructions if needed * [ ] Update web/docs/npm.md release history * [ ] Create GitHub release with changelog ### 16. Communication * [ ] Announce release on relevant channels * [ ] Notify users of breaking changes * [ ] Thank contributors ## Emergency Procedures ### If Wrong package.json Was Used 1. **DO NOT PANIC** 2. Check if authenticate-pam is a regular dependency (bad) or optional (good) 3. If bad, deprecate immediately: ```bash theme={null} npm deprecate vibetunnel@VERSION "Installation issues on Linux. Use next version." ``` 4. Increment version and republish following this checklist ### If Build Failed to Include Files 1. Check build logs for errors 2. Verify all copy operations in build-npm.js succeeded 3. Ensure no .gitignore or .npmignore is excluding files 4. Rebuild with verbose logging if needed ## Common Issues to Avoid 1. **NEVER use `npm publish` without tarball filename** - it may rebuild with wrong config 2. **ALWAYS verify authenticate-pam is optional** before publishing 3. **ALWAYS sync versions** across all config files 4. **NEVER skip the Docker test** - it catches Linux issues 5. **ALWAYS use beta tag first** - easier to fix issues before promoting to latest ## Version Numbering * Beta releases: `1.0.0-beta.X` where X increments * Patch releases: `1.0.0-beta.X.Y` where Y is patch number * Stable releases: `1.0.0` (no beta suffix) ## Quick Commands Reference ```bash theme={null} # Update dependencies pnpm update --interactive --latest # Complete multi-platform build (macOS only) pnpm run build:npm # Verify tar -xf vibetunnel-*.tgz package/package.json && \ grep -A5 optionalDependencies package/package.json && \ rm -rf package/ # Publish npm publish vibetunnel-*.tgz --tag beta # Promote to latest npm dist-tag add vibetunnel@VERSION latest # Check tags npm dist-tag ls vibetunnel ``` ## Release Frequency * Beta releases: As needed for testing new features * Stable releases: After thorough testing and user feedback * Security patches: ASAP after discovery Remember: It's better to delay a release than to publish a broken package! # Openapi Source: https://docs.vibetunnel.sh/docs/openapi # OpenAPI Migration Plan for VibeTunnel ## Overview This document outlines the plan to adopt OpenAPI 3.1 for VibeTunnel's REST API to achieve type safety and consistency between the TypeScript server and Swift clients. ## Goals 1. **Single source of truth** - Define API contracts once in OpenAPI spec 2. **Type safety** - Generate TypeScript and Swift types from the spec 3. **Eliminate inconsistencies** - Fix type mismatches between platforms 4. **API documentation** - Auto-generate API docs from the spec 5. **Gradual adoption** - Migrate endpoint by endpoint without breaking changes ## Current Issues * Session types differ completely between Mac app and server * Git repository types have different field names and optional/required mismatches * No standardized error response format * Manual type definitions duplicated across platforms * Runtime parsing errors due to type mismatches ## Implementation Plan ### Phase 1: Setup and Infrastructure (Week 1) #### 1.1 Install Dependencies ```bash theme={null} # In web directory pnpm add -D @hey-api/openapi-ts @apidevtools/swagger-cli @stoplight/spectral-cli ``` #### 1.2 Create Initial OpenAPI Spec Create `web/openapi/openapi.yaml`: ```yaml theme={null} openapi: 3.1.0 info: title: VibeTunnel API version: 1.0.0 description: Terminal sharing and remote access API servers: - url: http://localhost:4020 description: Local development server ``` #### 1.3 Setup Code Generation **TypeScript Generation** (`web/package.json`): ```json theme={null} { "scripts": { "generate:api": "openapi-ts -i openapi/openapi.yaml -o src/generated/api", "validate:api": "spectral lint openapi/openapi.yaml", "prebuild": "npm run generate:api" } } ``` **Swift Generation** (Xcode Build Phase): 1. Add `swift-openapi-generator` to Package.swift 2. Add build phase to run before compilation: ```bash theme={null} cd "$SRCROOT/../web" && \ swift-openapi-generator generate \ openapi/openapi.yaml \ --mode types \ --mode client \ --output-directory "$SRCROOT/Generated/OpenAPI" ``` #### 1.4 Create Shared Components Define reusable schemas in `web/openapi/components/`: ```yaml theme={null} # components/errors.yaml ErrorResponse: type: object required: [error, timestamp] properties: error: type: string description: Human-readable error message code: type: string description: Machine-readable error code enum: [ 'INVALID_REQUEST', 'NOT_FOUND', 'UNAUTHORIZED', 'SERVER_ERROR' ] timestamp: type: string format: date-time ``` ### Phase 2: Migrate Git Endpoints (Week 2) Start with Git endpoints as they're well-defined and isolated. #### 2.1 Define Git Schemas ```yaml theme={null} # openapi/paths/git.yaml /api/git/repository-info: get: operationId: getRepositoryInfo tags: [git] parameters: - name: path in: query required: true schema: type: string responses: '200': description: Repository information content: application/json: schema: $ref: '../components/schemas.yaml#/GitRepositoryInfo' # components/schemas.yaml GitRepositoryInfo: type: object required: [isGitRepo, hasChanges, modifiedCount, untrackedCount, stagedCount, addedCount, deletedCount, aheadCount, behindCount, hasUpstream] properties: isGitRepo: type: boolean repoPath: type: string currentBranch: type: string nullable: true remoteUrl: type: string nullable: true githubUrl: type: string nullable: true hasChanges: type: boolean modifiedCount: type: integer minimum: 0 untrackedCount: type: integer minimum: 0 stagedCount: type: integer minimum: 0 addedCount: type: integer minimum: 0 deletedCount: type: integer minimum: 0 aheadCount: type: integer minimum: 0 behindCount: type: integer minimum: 0 hasUpstream: type: boolean ``` #### 2.2 Update Server Implementation ```typescript theme={null} // src/server/routes/git.ts import { paths } from '../../generated/api'; type GitRepositoryInfo = paths['/api/git/repository-info']['get']['responses']['200']['content']['application/json']; router.get('/git/repository-info', async (req, res) => { const response: GitRepositoryInfo = { isGitRepo: true, repoPath: result.repoPath, // ... ensure all required fields are included }; res.json(response); }); ``` #### 2.3 Update Mac Client ```swift theme={null} // Use generated types import OpenAPIGenerated let response = try await client.getRepositoryInfo(path: filePath) let info = response.body.json // Fully typed! ``` ### Phase 3: Migrate Session Endpoints (Week 3) Session endpoints are more complex due to WebSocket integration. #### 3.1 Standardize Session Types ```yaml theme={null} SessionInfo: type: object required: [id, name, workingDir, status, createdAt, pid] properties: id: type: string format: uuid name: type: string workingDir: type: string status: type: string enum: [starting, running, exited] exitCode: type: integer nullable: true createdAt: type: string format: date-time lastActivity: type: string format: date-time pid: type: integer nullable: true command: type: array items: type: string ``` #### 3.2 Create Session Operations ```yaml theme={null} /api/sessions: get: operationId: listSessions responses: '200': content: application/json: schema: type: array items: $ref: '#/components/schemas/SessionInfo' post: operationId: createSession requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/CreateSessionRequest' responses: '201': content: application/json: schema: $ref: '#/components/schemas/SessionInfo' ``` ### Phase 4: Runtime Validation (Week 4) #### 4.1 Add Request Validation Middleware ```typescript theme={null} // src/server/middleware/openapi-validator.ts import { OpenAPIValidator } from 'express-openapi-validator'; export const openapiValidator = OpenAPIValidator.middleware({ apiSpec: './openapi/openapi.yaml', validateRequests: true, validateResponses: true, }); // Apply to routes app.use('/api', openapiValidator); ``` #### 4.2 Add Response Validation in Development ```typescript theme={null} // src/server/utils/validated-response.ts export function validatedJson(res: Response, data: T): void { if (process.env.NODE_ENV === 'development') { // Validate against OpenAPI schema validateResponse(res.req, data); } res.json(data); } ``` ### Phase 5: Documentation and Testing (Week 5) #### 5.1 Generate API Documentation ```bash theme={null} # Add to package.json "docs:api": "npx @redocly/cli build-docs openapi/openapi.yaml -o dist/api-docs.html" ``` #### 5.2 Add Contract Tests ```typescript theme={null} // src/test/contract/git-api.test.ts import { matchesSchema } from './schema-matcher'; test('GET /api/git/repository-info matches schema', async () => { const response = await request(app) .get('/api/git/repository-info') .query({ path: '/test/repo' }); expect(response.body).toMatchSchema('GitRepositoryInfo'); }); ``` ## Migration Checklist ### Endpoints to Migrate * [ ] **Git APIs** (Phase 2) * [ ] GET /api/git/repo-info * [ ] GET /api/git/repository-info * [ ] GET /api/git/remote * [ ] GET /api/git/status * [ ] POST /api/git/event * [ ] GET /api/git/follow * [ ] **Session APIs** (Phase 3) * [ ] GET /api/sessions * [ ] POST /api/sessions * [ ] GET /api/sessions/:id * [ ] DELETE /api/sessions/:id * [ ] POST /api/sessions/:id/resize * [ ] POST /api/sessions/:id/input * [ ] WS /ws (v3 framing; not OpenAPI) * [ ] **Repository APIs** (Phase 4) * [ ] GET /api/repositories/discover * [ ] GET /api/repositories/branches * [ ] **Worktree APIs** (Phase 4) * [ ] GET /api/worktrees * [ ] POST /api/worktrees * [ ] DELETE /api/worktrees/:branch * [ ] POST /api/worktrees/switch ## Success Metrics 1. **Zero runtime type errors** between Mac app and server 2. **100% API documentation** coverage 3. **Contract tests** for all endpoints 4. **Reduced code** - Remove manual type definitions 5. **Developer velocity** - Faster API development with code generation ## Long-term Considerations ### Future Enhancements 1. **GraphQL Gateway** - Add GraphQL layer on top of REST for complex queries 2. **API Versioning** - Use OpenAPI to manage v1/v2 migrations 3. **Client SDKs** - Generate SDKs for other platforms (iOS, CLI tools) 4. **Mock Server** - Use OpenAPI spec to run mock server for testing ### Breaking Changes When making breaking changes: 1. Version the API (e.g., /api/v2/) 2. Deprecate old endpoints with sunset dates 3. Generate migration guides from schema differences ## Resources * [OpenAPI 3.1 Specification](https://spec.openapis.org/oas/v3.1.0) * [OpenAPI TypeScript Generator](https://github.com/hey-api/openapi-ts) * [Swift OpenAPI Generator](https://github.com/apple/swift-openapi-generator) * [Spectral Linting](https://stoplight.io/open-source/spectral) * [ReDoc Documentation](https://redocly.com/docs/redoc) # Org migrate Source: https://docs.vibetunnel.sh/docs/org-migrate # GitHub Organization Migration Plan This document outlines the migration process for moving VibeTunnel from `amantus-ai/vibetunnel` to `vibetunnel/vibetunnel`. **Status: TODO** - This migration has not been completed yet. ## Migration Options ### Option 1: Simple Transfer (GitHub Built-in) The simplest approach using GitHub's native transfer feature. #### What Transfers Automatically ✅ **Code & History** * All branches and commit history * Git tags and annotated tags ✅ **Project Management** * Issues and pull requests (with all comments) * Projects (classic and new) * Releases and release assets * Milestones and labels ✅ **Community Features** * Stars and watchers * Wiki content * Fork relationships ✅ **Security & Integration** * Webhooks configurations * Deploy keys * Repository-level secrets * GitHub Actions workflows * Git LFS objects (copied in background) #### What Needs Manual Updates ⚠️ **Organization-level Settings** * Branch protection rules (inherits new org defaults - review carefully) * Organization-level secrets (must recreate in new org) * Environment-level secrets (if used outside repo scope) * Team permissions (reassign in new org structure) ⚠️ **External Integrations** * CI/CD systems with hardcoded URLs * Documentation with repository links * Package registries (npm, etc.) * External webhooks * Status badges in README ### Option 2: Migration with History Cleanup Since `https://github.com/vibetunnel/vibetunnel` may already exist, we can perform a clean migration that: 1. Removes large files from history 2. Cleans up accidental commits 3. Preserves important history 4. Maintains all issues, PRs, and project management features Use BFG Repo-Cleaner or git-filter-repo to create a cleaned version of the repository. ## Pre-Migration Checklist ### Preparation (1-2 days before) * [ ] **Prepare Target Organization** * Create `vibetunnel` organization if not exists * Set up teams and permissions structure * Configure organization-level settings * Review default branch protection rules * [ ] **Audit Current Setup** * Document all webhooks and integrations * List organization/environment secrets * Note branch protection rules * Identify external services using the repo * [ ] **Analyze Repository for Cleanup (if using Option 2)** ```bash theme={null} # Find large files in history git rev-list --objects --all | \ git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' | \ awk '/^blob/ && $3 > 10485760 {print $3, $4}' | \ sort -rn | \ numfmt --field=1 --to=iec ``` * [ ] **Notify Stakeholders** * Team members about the migration * Users via issue/discussion if needed * Update any public documentation ## Migration Process ### Option 1: Simple Transfer Steps 1. Navigate to **Settings → General → Danger Zone → Transfer** 2. Enter the new owner: `vibetunnel` 3. Type the repository name to confirm 4. Accept the invite from the destination org 5. Done! ✅ ### Option 2: Clean Migration Script Save this as `migrate-clean.sh`: ```bash theme={null} #!/bin/bash set -euo pipefail # Configuration OLD_REPO="git@github.com:amantus-ai/vibetunnel.git" NEW_REPO="git@github.com:vibetunnel/vibetunnel.git" TEMP_DIR="vibetunnel-migration-$(date +%Y%m%d-%H%M%S)" SIZE_THRESHOLD="10M" # Files larger than this will be removed echo "🚀 Starting VibeTunnel repository migration with cleanup..." # Create temporary directory mkdir -p "$TEMP_DIR" cd "$TEMP_DIR" # Clone the repository (all branches and tags) echo "📥 Cloning repository with all history..." git clone --mirror "$OLD_REPO" vibetunnel-mirror cd vibetunnel-mirror # Create a backup first echo "💾 Creating backup..." cp -r . ../vibetunnel-backup # Analyze repository for large files echo "🔍 Analyzing repository for large files..." git rev-list --objects --all | \ git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' | \ awk '/^blob/ {print substr($0,6)}' | \ sort --numeric-sort --key=2 --reverse | \ awk '$2 >= 10485760 {print $1, $2, $3}' > ../large-files.txt echo "📊 Large files found:" cat ../large-files.txt | while read hash size path; do echo " - $path ($(numfmt --to=iec $size))" done # Download BFG Repo-Cleaner if not available if ! command -v bfg &> /dev/null && [ ! -f ../bfg.jar ]; then echo "📦 Downloading BFG Repo-Cleaner..." curl -L -o ../bfg.jar https://repo1.maven.org/maven2/com/madgag/bfg/1.14.0/bfg-1.14.0.jar fi # Clean large files using BFG echo "🧹 Removing large files from history..." java -jar ../bfg.jar --strip-blobs-bigger-than "$SIZE_THRESHOLD" --no-blob-protection # Clean specific file patterns (customize as needed) echo "🗑️ Removing unwanted file patterns..." java -jar ../bfg.jar --delete-files '*.{log,tmp,cache}' --no-blob-protection java -jar ../bfg.jar --delete-folders '{node_modules,dist,build}' --no-blob-protection # Clean up the repository echo "♻️ Cleaning up repository..." git reflog expire --expire=now --all git gc --prune=now --aggressive # Show size comparison echo "📏 Size comparison:" cd .. ORIGINAL_SIZE=$(du -sh vibetunnel-backup | cut -f1) CLEANED_SIZE=$(du -sh vibetunnel-mirror | cut -f1) echo " Original: $ORIGINAL_SIZE" echo " Cleaned: $CLEANED_SIZE" # Update remote URL and push cd vibetunnel-mirror git remote set-url origin "$NEW_REPO" # Interactive confirmation echo "⚠️ Ready to push to $NEW_REPO" read -p "Continue? (y/N) " -n 1 -r echo if [[ ! $REPLY =~ ^[Yy]$ ]]; then echo "❌ Aborted" exit 1 fi # Push to new repository echo "📤 Pushing to new repository..." git push --mirror "$NEW_REPO" echo "✅ Migration complete!" ``` ## Post-Migration Updates ### Update Git Remotes ```bash theme={null} # For all local clones git remote set-url origin git@github.com:vibetunnel/vibetunnel.git # Verify the change git remote -v ``` ### Update VibeTunnel Code * [ ] Update `GITHUB_URL` in `mac/VibeTunnel/version.xcconfig` * [ ] Update repository URLs in all `package.json` files: ```json theme={null} { "repository": { "type": "git", "url": "git+https://github.com/vibetunnel/vibetunnel.git" }, "bugs": { "url": "https://github.com/vibetunnel/vibetunnel/issues" }, "homepage": "https://github.com/vibetunnel/vibetunnel#readme" } ``` * [ ] Update any hardcoded GitHub URLs in documentation * [ ] Update CLAUDE.md references * [ ] Update docs.json if it contains repository URLs ### Update External Services * [ ] CI/CD configurations * [ ] npm package registry URLs * [ ] Monitoring services * [ ] Documentation sites * [ ] README badges and links * [ ] Installation instructions * [ ] Contributing guidelines ### Build & Release * [ ] Update GitHub Actions secrets if needed * [ ] Verify macOS notarization still works * [ ] Test release workflow with new repo URL * [ ] Update Sparkle appcast URLs if applicable * [ ] Consider publishing a patch version with updated URLs ## Redirect Behavior GitHub automatically sets up redirects: * `https://github.com/amantus-ai/vibetunnel` → `https://github.com/vibetunnel/vibetunnel` * Git operations: `git clone git@github.com:amantus-ai/vibetunnel.git` still works * API calls to old URL redirect automatically ⚠️ **Redirect Limitations**: * Redirects break if someone creates a new repo at `amantus-ai/vibetunnel` * Some tools may not follow redirects properly * Best practice: Update all references ASAP ## Timeline **Day 1**: Preparation * Set up new organization * Audit current configuration * Notify team **Day 2**: Migration * Morning: Final preparations * Midday: Execute transfer * Afternoon: Update configurations **Day 3**: Verification * Test all integrations * Monitor for issues * Complete documentation updates ## Important Notes * GitHub's transfer process is well-tested and reliable * The automatic redirects provide good backward compatibility * If using history cleanup (Option 2): * This process rewrites history - all commit SHAs will change * Contributors will need to re-clone or rebase their work * Keep the cleaned backup for a few weeks * Consider doing this during a low-activity period ## Rollback Plan If issues arise: 1. GitHub Support can reverse transfers within a short window 2. Keep the migration backup (if using Option 2) 3. Document any issues for future reference ## References * [GitHub Docs: Transferring a repository](https://docs.github.com/en/repositories/creating-and-managing-repositories/transferring-a-repository) * [BFG Repo-Cleaner](https://rtyley.github.io/bfg-repo-cleaner/) (for Option 2) * [git-filter-repo](https://github.com/newren/git-filter-repo) (alternative to BFG) * [GitHub: Removing sensitive data](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/removing-sensitive-data-from-a-repository) # Macos Source: https://docs.vibetunnel.sh/docs/platform/macos # macOS Development ## Project Setup ### Requirements * macOS 14.0+ * Xcode 16.0+ * Swift 6.0 ### Build & Run ```bash theme={null} cd mac # Debug build xcodebuild -project VibeTunnel.xcodeproj -scheme VibeTunnel # Release build ./scripts/build.sh # With code signing ./scripts/build.sh --sign # Run directly open build/Release/VibeTunnel.app ``` ## Architecture ### Core Components | Component | Location | Purpose | | ----------------- | ------------------------------------------------ | ---------------- | | ServerManager | `Core/Services/ServerManager.swift` | Server lifecycle | | SessionMonitor | `Core/Services/SessionMonitor.swift` | Track sessions | | TTYForwardManager | `Core/Services/TTYForwardManager.swift` | CLI integration | | MenuBarViewModel | `Presentation/ViewModels/MenuBarViewModel.swift` | UI state | ### Key Patterns **Observable State** ```swift theme={null} @MainActor @Observable class ServerManager { private(set) var isRunning = false private(set) var sessions: [Session] = [] } ``` **Protocol-Based Services** ```swift theme={null} @MainActor protocol VibeTunnelServer: AnyObject { var isRunning: Bool { get } func start() async throws func stop() async } ``` **SwiftUI Menu Bar** ```swift theme={null} struct MenuBarView: View { @StateObject private var viewModel = MenuBarViewModel() var body: some View { Menu("VT", systemImage: "terminal") { ForEach(viewModel.sessions) { session in SessionRow(session: session) } } } } ``` ## Server Integration ### Embedded Server ``` VibeTunnel.app/ └── Contents/ ├── MacOS/ │ └── VibeTunnel # Main executable └── Resources/ └── server/ └── bun-server # Embedded Bun binary ``` ### Server Launch ```swift theme={null} // ServerManager.swift func start() async throws { let serverPath = Bundle.main.resourcePath! + "/server/bun-server" process = Process() process.executableURL = URL(fileURLWithPath: serverPath) process.arguments = ["--port", port] try process.run() } ``` ## Settings Management ### UserDefaults Keys | Key | Type | Default | Description | | ------------ | ------ | ------- | ---------------- | | serverPort | String | "4020" | Server port | | autostart | Bool | false | Launch at login | | allowLAN | Bool | false | LAN connections | | useDevServer | Bool | false | Development mode | ### Settings Window ```swift theme={null} struct SettingsView: View { @AppStorage("serverPort") private var port = "4020" var body: some View { Form { TextField("Port:", text: $port) } } } ``` ## Menu Bar App ### App Lifecycle ```swift theme={null} @main struct VibeTunnelApp: App { @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate var body: some Scene { MenuBarExtra("VibeTunnel", systemImage: "terminal") { MenuBarView() } .menuBarExtraStyle(.menu) } } ``` ### Status Updates ```swift theme={null} // Update menu bar icon based on state func updateStatusItem() { if serverManager.isRunning { statusItem.button?.image = NSImage(systemSymbolName: "terminal.fill") } else { statusItem.button?.image = NSImage(systemSymbolName: "terminal") } } ``` ## Code Signing ### Entitlements ```xml theme={null} com.apple.security.network.client com.apple.security.network.server com.apple.security.files.user-selected.read-write ``` ### Build Settings ``` # version.xcconfig MARKETING_VERSION = 1.0.0 CURRENT_PROJECT_VERSION = 100 # Shared.xcconfig CODE_SIGN_IDENTITY = Developer ID Application DEVELOPMENT_TEAM = TEAMID ``` ## Sparkle Updates ### Integration ```swift theme={null} import Sparkle class UpdateManager { let updater = SPUStandardUpdaterController( startingUpdater: true, updaterDelegate: nil, userDriverDelegate: nil ) func checkForUpdates() { updater.checkForUpdates() } } ``` ### Configuration ```xml theme={null} SUFeedURL https://vibetunnel.com/appcast.xml SUEnableAutomaticChecks ``` ## Debugging ### Console Logs ```swift theme={null} os_log(.debug, log: .server, "Starting server on port %{public}@", port) ``` ### View Logs ```bash theme={null} # In Console.app # Filter: subsystem:com.steipete.VibeTunnel # Or via script ./scripts/vtlog.sh -c ServerManager ``` ## Testing ### Unit Tests ```bash theme={null} xcodebuild test \ -project VibeTunnel.xcodeproj \ -scheme VibeTunnel \ -destination 'platform=macOS' ``` ### UI Tests ```swift theme={null} class VibeTunnelUITests: XCTestCase { func testServerStart() throws { let app = XCUIApplication() app.launch() app.menuBarItems["VibeTunnel"].click() app.menuItems["Start Server"].click() XCTAssertTrue(app.menuItems["Stop Server"].exists) } } ``` ## Common Issues | Issue | Solution | | -------------------- | ------------------------------- | | Server won't start | Check port availability | | Menu bar not showing | Check LSUIElement in Info.plist | | Updates not working | Verify Sparkle feed URL | | Permissions denied | Add entitlements | ## See Also * [Architecture](../core/architecture.md) * [Development Guide](../guides/development.md) * [iOS Companion](ios.md) # Web Source: https://docs.vibetunnel.sh/docs/platform/web # Web Development ## Setup ### Prerequisites * Node.js 22.12 through 24.x * Bun 1.0+ * pnpm 8+ ### Install & Run ```bash theme={null} cd web pnpm install pnpm dev # Development server pnpm build # Production build pnpm test # Run tests ``` ## Project Structure ``` web/ ├── src/ │ ├── server/ # Node.js backend │ │ ├── server.ts # HTTP/WebSocket server │ │ ├── pty/ # Terminal management │ │ ├── services/ # Business logic │ │ └── routes/ # API endpoints │ ├── client/ # Web frontend │ │ ├── app.ts # Main application │ │ ├── components/ # Lit components │ │ └── services/ # Client services │ └── shared/ # Shared types ├── dist/ # Build output └── tests/ # Test files ``` ## Server Development ### Core Services | Service | File | Purpose | | --------------- | ------------------------------------- | ---------------------------------------------- | | PtyManager | `server/pty/pty-manager.ts` | PTY lifecycle + input/resize | | SessionManager | `server/pty/session-manager.ts` | On-disk session metadata + stdout/stderr paths | | TerminalManager | `server/services/terminal-manager.ts` | Server-side terminal state + VT snapshots | | CastOutputHub | `server/services/cast-output-hub.ts` | Stdout tail + pruning (`lastClearOffset`) | | GitStatusHub | `server/services/git-status-hub.ts` | Git status updates for sessions | | WsV3Hub | `server/services/ws-v3-hub.ts` | Unified WebSocket v3 transport (`/ws`) | ### API Routes * HTTP: `/api/...` (sessions, git, config, worktrees) * WebSocket: `/ws` (binary v3 framing; see `docs/websocket.md`) ### PTY Management ```typescript theme={null} // pty/pty-manager.ts import * as pty from 'node-pty'; export class PTYManager { create(options: PTYOptions): IPty { return pty.spawn(options.shell || '/bin/zsh', options.args, { cols: options.cols || 80, rows: options.rows || 24, cwd: options.cwd || process.env.HOME, env: { ...process.env, ...options.env } }); } } ``` ## Client Development ### Lit Components ```typescript theme={null} // components/terminal-view.ts @customElement('terminal-view') export class TerminalView extends LitElement { @property({ type: String }) sessionId = ''; private terminal?: Terminal; private ws?: WebSocket; createRenderRoot() { return this; // No shadow DOM for Tailwind } firstUpdated() { this.initTerminal(); this.connectWebSocket(); } render() { return html`
`; } } ``` ### WebSocket Client ```typescript theme={null} // services/terminal-socket-client.ts // // Single `/ws` WebSocket (v3 framing). Multiplexes sessions via `sessionId`. import { terminalSocketClient } from './services/terminal-socket-client.js'; terminalSocketClient.initialize(); const unsubscribe = terminalSocketClient.subscribe(sessionId, { stdout: true, snapshots: true, events: true, onStdout: (bytes) => { // forward bytes to Ghostty renderer }, onSnapshot: (snapshot) => { // update preview / hard resync }, onEvent: (event) => { // handle exit, git-status, etc }, }); ``` ### Terminal Integration ```typescript theme={null} // services/terminal-service.ts import { Ghostty, Terminal, FitAddon } from 'ghostty-web'; export class TerminalService { private terminal: Terminal; private fitAddon: FitAddon; async initialize(container: HTMLElement): Promise { const ghostty = await Ghostty.load('/ghostty-vt.wasm'); this.terminal = new Terminal({ ghostty, theme: { background: '#1e1e1e', foreground: '#ffffff' } }); this.fitAddon = new FitAddon(); this.terminal.loadAddon(this.fitAddon); this.terminal.open(container); this.fitAddon.fit(); } } ``` ## Build System ### Development Build ```json theme={null} // package.json scripts { "dev": "concurrently \"npm:dev:*\"", "dev:server": "tsx watch src/server/server.ts", "dev:client": "vite", "dev:tailwind": "tailwindcss -w" } ``` ### Production Build ```bash theme={null} # Build everything pnpm build # Outputs: # dist/server/ - Compiled server # dist/client/ - Static web assets # dist/bun - Standalone executable ``` ### Bun Compilation ```typescript theme={null} // scripts/build-bun.ts await Bun.build({ entrypoints: ['src/server/server.ts'], outdir: 'dist', target: 'bun', minify: true, sourcemap: 'external' }); ``` ## Testing ### Unit Tests ```typescript theme={null} // tests/terminal-manager.test.ts describe('TerminalManager', () => { it('creates session', async () => { const manager = new TerminalManager(); const session = await manager.create({ shell: '/bin/bash' }); expect(session.id).toBeDefined(); }); }); ``` ### E2E Tests ```typescript theme={null} // tests/e2e/session.test.ts test('create and connect to session', async ({ page }) => { await page.goto('http://localhost:4020'); await page.click('button:text("New Terminal")'); await expect(page.locator('.terminal')).toBeVisible(); }); ``` ## Performance ### Optimization Techniques | Technique | Implementation | Impact | | --------------------- | ------------------------------ | --------------------------- | | Multiplexed transport | `/ws` WebSocket v3 framing | One socket for all sessions | | Snapshot previews | VT snapshot v1 (`SNAPSHOT_VT`) | Fast previews / hard resync | | Virtual scrolling | ghostty-web scrollback | Handles 100K+ lines | | Service worker | Cache static assets | Instant load | ### Benchmarks ```typescript theme={null} // Measure WebSocket throughput const start = performance.now(); let bytes = 0; ws.onmessage = (event) => { bytes += event.data.byteLength; if (performance.now() - start > 1000) { console.log(`Throughput: ${bytes / 1024}KB/s`); } }; ``` ## Debugging ### Server Debugging ```bash theme={null} # Run with inspector node --inspect dist/server/server.js # With source maps NODE_OPTIONS='--enable-source-maps' node dist/server/server.js # Verbose logging DEBUG=vt:* pnpm dev:server ``` ### Client Debugging ```javascript theme={null} // Terminal debugging const terminalEl = document.querySelector('vibe-terminal'); console.log(terminalEl?.getDebugText?.({ maxLines: 50 })); // WebSocket debugging ws.addEventListener('message', (e) => { console.log('WS received:', e.data); }); ``` ## Common Issues | Issue | Solution | | ---------------- | ------------------------ | | CORS errors | Check server CORS config | | WebSocket fails | Verify port/firewall | | Terminal garbled | Check encoding (UTF-8) | | Build fails | Clear node\_modules | ## See Also * [API Reference](../core/api-reference.md) * [Protocol Specs](../core/protocols.md) * [Development Guide](../guides/development.md) # Push impl Source: https://docs.vibetunnel.sh/docs/push-impl # Push Notification Implementation Plan This document outlines the comprehensive plan for improving VibeTunnel's notification system through two major initiatives: 1. Creating a dedicated Notifications tab in macOS settings 2. Migrating SessionMonitor from the Mac app to the server for unified notifications ## Overview Currently, VibeTunnel has inconsistent notification implementations between the Mac and web clients. The Mac app has its own SessionMonitor while the web relies on server events. This leads to: * Different notification behaviors between platforms * Missing features (e.g., notification settings parity between platforms) * Duplicate code and maintenance burden * Inconsistent descriptions and thresholds ## Part 1: macOS Settings Redesign ### Current State * Notification settings are cramped in the General tab * No room for descriptive text explaining each notification type * Settings are already at 710px height (quite tall) * Missing helpful context that exists in the web UI ### Proposed Solution: Dedicated Notifications Tab #### 1. Add Notifications Tab to SettingsTab enum ```swift theme={null} // SettingsTab.swift enum SettingsTab: String, CaseIterable { case general case notifications // NEW case quickStart case dashboard // ... rest of tabs } // Add display name and icon var displayName: String { switch self { case .notifications: "Notifications" // ... rest } } var icon: String { switch self { case .notifications: "bell.badge" // ... rest } } ``` #### 2. Create NotificationSettingsView\.swift ```swift theme={null} struct NotificationSettingsView: View { @ObservedObject private var configManager = ConfigManager.shared @ObservedObject private var notificationService = NotificationService.shared var body: some View { Form { // Master toggle section Section { VStack(alignment: .leading, spacing: 8) { Toggle("Show Session Notifications", isOn: $showNotifications) Text("Display native macOS notifications for session and command events") .font(.caption) .foregroundStyle(.secondary) } } // Notification types section Section { NotificationToggleRow( title: "Session starts", description: "When a new session starts (useful for shared terminals)", isOn: $configManager.notificationSessionStart, helpText: NotificationHelp.sessionStart ) NotificationToggleRow( title: "Session ends", description: "When a session terminates or crashes (shows exit code)", isOn: $configManager.notificationSessionExit, helpText: NotificationHelp.sessionExit ) // ... other notification types } header: { Text("Notification Types") } // Behavior section Section { Toggle("Play sound", isOn: $configManager.notificationSoundEnabled) Toggle("Show in Notification Center", isOn: $configManager.showInNotificationCenter) } header: { Text("Notification Behavior") } // Test section Section { Button("Test Notification") { notificationService.sendTestNotification() } } } } } ``` #### 3. Create Reusable NotificationToggleRow Component ```swift theme={null} struct NotificationToggleRow: View { let title: String let description: String @Binding var isOn: Bool let helpText: String var body: some View { HStack(alignment: .top, spacing: 12) { VStack(alignment: .leading, spacing: 4) { HStack { Toggle(title, isOn: $isOn) .toggleStyle(.checkbox) HelpTooltip(text: helpText) } Text(description) .font(.caption) .foregroundStyle(.secondary) } } .padding(.vertical, 4) } } ``` #### 4. Update SettingsView\.swift ```swift theme={null} // Add the new tab NotificationSettingsView() .tabItem { Label(SettingsTab.notifications.displayName, systemImage: SettingsTab.notifications.icon) } .tag(SettingsTab.notifications) ``` #### 5. Update GeneralSettingsView\.swift Remove all notification-related settings to free up space. ### Standardized Notification Descriptions Use these descriptions consistently across Mac and web: | Type | Title | Description | | ------------------ | ------------------------------- | ------------------------------------------------------------ | | Session Start | Session starts | When a new session starts (useful for shared terminals) | | Session Exit | Session ends | When a session terminates or crashes (shows exit code) | | Command Error | Commands fail | When commands fail with non-zero exit codes | | Command Completion | Commands complete (> 3 seconds) | When commands taking >3 seconds finish (builds, tests, etc.) | | Terminal Bell | Terminal bell (🔔) | Terminal bell (^G) from vim, IRC mentions, completion sounds | ## Part 2: Server-Side SessionMonitor Migration ### Current Architecture ``` Mac App: SessionMonitor (Swift) → NotificationService → macOS notifications Server: PtyManager → Basic events → WS v3 → Web notifications ``` ### Proposed Architecture ``` Server: PtyManager → SessionMonitor (TypeScript) → Enhanced events → WS v3 ↓ Mac & Web clients ``` ### Implementation Steps #### 1. Create Server-Side SessionMonitor ```typescript theme={null} // web/src/server/services/session-monitor.ts export interface SessionState { id: string; name: string; command: string[]; isRunning: boolean; commandStartTime?: Date; lastCommand?: string; } export class SessionMonitor { private sessions = new Map(); private commandThresholdMs = 3000; // 3 seconds constructor( private ptyManager: PtyManager, private eventBus: EventEmitter ) { this.setupEventListeners(); } // ... other monitoring methods } ``` #### 2. Enhance Event Types ```typescript theme={null} // web/src/shared/types.ts export enum ServerEventType { SessionStart = 'session-start', SessionExit = 'session-exit', CommandFinished = 'command-finished', CommandError = 'command-error', // NEW - separate from finished Bell = 'bell', // NEW Connected = 'connected' } export interface ServerEvent { type: ServerEventType; timestamp: string; sessionId: string; sessionName?: string; // Event-specific data exitCode?: number; command?: string; duration?: number; message?: string; } ``` #### 3. Integrate with PtyManager ```typescript theme={null} // web/src/server/pty/pty-manager.ts class PtyManager { private sessionMonitor: SessionMonitor; constructor() { this.sessionMonitor = new SessionMonitor(this, serverEventBus); } // Feed data to SessionMonitor private handlePtyData(sessionId: string, data: string) { // Existing data handling... // Detect bell character if (data.includes('\x07')) { serverEventBus.emit('notification', { type: ServerEventType.Bell, sessionId, sessionName: this.sessions.get(sessionId)?.name }); } } } ``` #### 4. Update Server WS v3 Hub ```typescript theme={null} // web/src/server/services/ws-v3-hub.ts // SessionMonitor emits ServerEvent objects. // WsV3Hub broadcasts EVENT frames to clients that SUBSCRIBE with: // sessionId == "" and flags include WsV3SubscribeFlags.Events ``` #### 5. Update Mac NotificationService ```swift theme={null} // NotificationService.swift class NotificationService { // Remove local SessionMonitor dependency // Subscribe to server WS v3 events instead private func connectToServerEvents() { // WS v3: connect `/ws`, send SUBSCRIBE(sessionId:"", flags: Events) } private func handleServerEvent(_ event: ServerEvent) { // Map server events to notifications switch event.type { case .sessionStart: if preferences.sessionStart { sendNotification(for: event) } // ... handle other event types } } } ``` #### 6. Update Web Notification Service ```typescript theme={null} // web/src/client/services/push-notification-service.ts private handleServerEvent(event: ServerEvent) { if (!this.preferences[this.mapEventTypeToPreference(event.type)]) { return; } // Send browser notification this.showNotification(event); } private mapEventTypeToPreference(type: ServerEventType): keyof NotificationPreferences { const mapping = { [ServerEventType.SessionStart]: 'sessionStart', [ServerEventType.SessionExit]: 'sessionExit', [ServerEventType.CommandFinished]: 'commandCompletion', [ServerEventType.CommandError]: 'commandError', [ServerEventType.Bell]: 'bell' }; return mapping[type]; } ``` ## Migration Strategy ### Phase 1: Preparation (Non-breaking) 1. Implement server-side SessionMonitor alongside existing system 2. Add new event types to shared types ### Phase 2: Server Enhancement (Non-breaking) 1. Deploy enhanced server with SessionMonitor 2. Server emits both old and new event formats 3. Test with web client to ensure compatibility ### Phase 3: Mac App Migration 1. Update Mac app to consume server events 2. Keep fallback to local monitoring if server unavailable 3. Remove local SessionMonitor once stable ### Phase 4: Cleanup 1. Remove old event formats from server 2. Remove local SessionMonitor code from Mac 3. Document new architecture ## Testing Plan ### Unit Tests * Event threshold calculations * Activity state transitions ### Integration Tests * Server events reach both Mac and web clients * Notification preferences are respected * Bell character detection ### Manual Testing * Test each notification type on both platforms * Verify descriptions match * Test with multiple clients connected * Test offline Mac app behavior ## Success Metrics 1. **Consistency**: Same notifications appear on Mac and web for same events 2. **Performance**: No noticeable lag in notifications 3. **Reliability**: No missed notifications 4. **Maintainability**: Single codebase for monitoring logic ## Timeline Estimate * **Week 1**: Implement macOS Notifications tab * **Week 2**: Create server-side SessionMonitor * **Week 3**: Integrate and test with web client * **Week 4**: Migrate Mac app and testing * **Week 5**: Polish, documentation, and deployment ## Risks and Mitigations | Risk | Impact | Mitigation | | ------------------------------- | ------ | ------------------------------------------------ | | Breaking existing notifications | High | Phased rollout, maintain backwards compatibility | | Performance impact on server | Medium | Efficient event handling, consider debouncing | | Mac app offline mode | Medium | Keep local fallback for critical notifications | | Complex migration | Medium | Detailed testing plan, feature flags | ## Conclusion This two-part implementation will: 1. Provide a better UI for notification settings on macOS 2. Create a unified notification system across all platforms 3. Reduce code duplication and maintenance burden 4. Ensure consistent behavior for all users The migration is designed to be non-breaking with careful phases to minimize risk. # Push notification Source: https://docs.vibetunnel.sh/docs/push-notification # Push Notifications in VibeTunnel VibeTunnel provides real-time alerts for terminal events via native macOS notifications and web push notifications. The system is primarily driven by the **Session Monitor**, which tracks terminal activity and triggers alerts. ## How It Works The **Session Monitor** is the core of the notification system. It observes terminal sessions for key events and dispatches them to the appropriate notification service (macOS or web). ### Key Monitored Events * **Session Start/Exit**: Get notified when a terminal session begins or ends. * **Command Completion**: Alerts for long-running commands. * **Errors**: Notifications for commands that fail. * **Terminal Bell**: Triggered by programs sending a bell character (`^G`). * **Claude "Your Turn"**: A special notification when Claude AI finishes a response and is awaiting your input. ## Native macOS Notifications The VibeTunnel macOS app provides the most reliable and feature-rich notification experience. * **Enable**: Go to `VibeTunnel Settings > General` and toggle **Show Session Notifications**. * **Features**: Uses the native `UserNotifications` framework, respects Focus Modes, and works in the background. ## Web Push Notifications For non-macOS clients or remote access, VibeTunnel supports web push notifications. * **Enable**: Click the notification icon in the web UI and grant browser permission. * **Technology**: Uses Service Workers and the Web Push API. ## Troubleshooting * **No Notifications**: Ensure they are enabled in both VibeTunnel settings and your OS/browser settings. * **Duplicate Notifications**: You can clear old or duplicate subscriptions by deleting `~/.vibetunnel/notifications/subscriptions.json`. * **Claude Notifications**: If Claude's "Your Turn" notifications aren't working, you can try forcing it to use the terminal bell: ```bash claude config set --global preferredNotifChannel terminal_bell ``` # Release process Source: https://docs.vibetunnel.sh/docs/reference/release-process # Release Process ## Quick Checklist ```bash theme={null} # 1. Update version ./scripts/update-version.sh 1.0.0 # 2. Run tests ./scripts/test-all.sh # 3. Build release ./scripts/release.sh 1.0.0 # 4. Create GitHub release gh release create v1.0.0 dist/VibeTunnel-1.0.0.dmg # 5. Update Sparkle feed ./scripts/update-sparkle.sh ``` ## Detailed Steps ### 1. Pre-Release **Version Update** ```bash theme={null} # Updates all version files ./scripts/update-version.sh NEW_VERSION # Files modified: # - mac/VibeTunnel/version.xcconfig # - web/package.json # - ios/VibeTunnel/Info.plist ``` **Changelog** ```markdown theme={null} ## [1.0.0] - 2024-01-01 ### Added - New feature X - Support for Y ### Fixed - Bug Z ### Changed - Improved performance ``` ### 2. Testing **Run Test Suite** ```bash theme={null} # All platforms ./scripts/test-all.sh # Individual cd mac && xcodebuild test cd ios && ./scripts/test-with-coverage.sh cd web && pnpm test ``` **Manual Testing** * [ ] Fresh install on clean macOS * [ ] Upgrade from previous version * [ ] Test on minimum macOS version * [ ] iOS app connectivity * [ ] Web UI on Safari/Chrome/Firefox ### 3. Build **Release Build** ```bash theme={null} # Complete release ./scripts/release.sh VERSION # Steps performed: # 1. Clean build directories # 2. Build web assets # 3. Build Mac app (signed) # 4. Create DMG # 5. Notarize with Apple # 6. Generate Sparkle appcast ``` **Verification** ```bash theme={null} # Check signature codesign -dv --verbose=4 dist/VibeTunnel.app # Verify notarization spctl -a -v dist/VibeTunnel.app ``` ### 4. Distribution **GitHub Release** ```bash theme={null} # Create release gh release create v$VERSION \ --title "VibeTunnel $VERSION" \ --notes-file RELEASE_NOTES.md \ dist/VibeTunnel-$VERSION.dmg # Upload additional assets gh release upload v$VERSION dist/checksums.txt ``` **Sparkle Update** ```xml theme={null} Version 1.0.0 Mon, 01 Jan 2024 00:00:00 +0000 1.0.0 1.0.0 14.0 ``` ### 5. Post-Release **Documentation** * [ ] Update README with new version * [ ] Update docs with new features * [ ] Post release notes **Monitoring** * [ ] Check Sparkle update stats * [ ] Monitor crash reports * [ ] Review user feedback ## Version Scheme ``` MAJOR.MINOR.PATCH[-PRERELEASE] 1.0.0 - Stable release 1.0.0-beta.1 - Beta release 1.0.0-rc.1 - Release candidate ``` ## Build Configurations | Config | Use Case | Signing | | -------- | ------------- | ------- | | Debug | Development | No | | Release | Distribution | Yes | | AppStore | Mac App Store | Yes | ## Code Signing **Requirements** * Apple Developer account * Developer ID certificate * Notarization credentials **Setup** ```bash theme={null} # Store credentials xcrun notarytool store-credentials "VT_NOTARY" \ --apple-id "your@email.com" \ --team-id "TEAMID" \ --password "app-specific-password" ``` ## Troubleshooting | Issue | Solution | | -------------------- | ------------------------------ | | Notarization fails | Check entitlements, wait 5 min | | Sparkle not updating | Verify appcast URL, signature | | DMG corrupted | Re-run with clean build | | Version mismatch | Run update-version.sh | ## Rollback ```bash theme={null} # Revert release gh release delete v$VERSION git revert git tag -d v$VERSION git push origin :refs/tags/v$VERSION # Update Sparkle feed to previous version ./scripts/rollback-sparkle.sh $PREVIOUS_VERSION ``` ## CI/CD Pipeline ```yaml theme={null} # .github/workflows/release.yml on: push: tags: - 'v*' jobs: release: runs-on: macos-14 steps: - uses: actions/checkout@v4 - run: ./scripts/test-all.sh - run: ./scripts/release.sh ${{ github.ref_name }} - uses: softprops/action-gh-release@v1 with: files: dist/*.dmg ``` ## See Also * [Build System](../guides/development.md#build-system) * [Testing Guide](../guides/testing.md) * [Changelog](../../CHANGELOG.md) # Repoprompt Source: https://docs.vibetunnel.sh/docs/repoprompt # RepoPrompt Pair Programming Guide RepoPrompt is a powerful MCP (Model Context Protocol) server that enables sophisticated AI pair programming workflows. This guide explains how to use RepoPrompt with Claude Code for complex development tasks. ## Overview The pair programming mode allows Claude Code to collaborate with other AI models (like OpenAI's O1/O3 or Google's Gemini) by: * Claude Code acts as a **context manager** - gathering files, managing tokens, and handling tools * Another AI model acts as the **planner/executor** - providing deep reasoning and implementation * Both models work together continuously throughout the task ## Key Benefits 1. **Leverages Model Strengths**: Claude excels at tool use and navigation; reasoning models excel at complex problem-solving 2. **Optimal Context Management**: Reasoning models work best with complete context upfront rather than incremental discovery 3. **Continuous Collaboration**: Maintains conversation state and file context between messages 4. **Higher Quality Output**: More considered implementations for complex tasks ## Installation ### Prerequisites * Claude Code with MCP support * RepoPrompt MCP server installed ### Quick Setup ```bash theme={null} # Install RepoPrompt if not already installed claude mcp add RepoPrompt -- /path/to/repoprompt_cli # Verify installation claude mcp list # Restart Claude Code to load the server # Use Cmd+R or restart from command line ``` ## Basic Workflow ### 1. Select Your Working Files First, identify and select files relevant to your task: ``` # List current selection manage_selection action="list" include_stats=true # Add specific files manage_selection action="add" paths=["src/main.ts", "src/utils.ts", "tests/main.test.ts"] # Or replace entire selection manage_selection action="replace" paths=["src/auth/login.ts", "src/auth/session.ts"] # Clear selection to start fresh manage_selection action="clear" ``` **Tips:** * Keep total context under 100K tokens (ideally \~60K) * Select only directly relevant files * Use `get_code_structure` to preview large files efficiently ### 2. Set Your Task Description Write a clear prompt describing what you want to accomplish: ``` set_prompt_state prompt="Implement user authentication with JWT tokens, including login, logout, and session management" ``` ### 3. Start the Pair Programming Session Begin with planning mode to have the AI create a detailed implementation plan: ``` chat_send mode="plan" message="Let's implement the authentication system as described" ``` ### 4. Execute the Plan Switch to edit mode to implement the changes: ``` chat_send mode="edit" message="Now let's implement the authentication module" ``` ## Advanced Features ### File Discovery Workflow Before starting, use these tools to find relevant files: ``` # Get project structure get_file_tree type="files" # Search for specific patterns search pattern="authentication" mode="both" # Preview file structure without full content get_code_structure paths=["src/auth.ts", "src/server.ts"] # Read specific files read_file path="src/config/auth.config.ts" ``` ### Context Management Strategy 1. **Start Minimal**: Begin with core files only 2. **Add Dependencies**: Include related files as needed 3. **Monitor Token Count**: Check with `manage_selection action="list" include_stats=true` 4. **Update Between Tasks**: Use `replace` when switching focus ### Working with Multiple Models RepoPrompt supports various AI models. List available presets: ``` list_models ``` Choose a specific model for your task: ``` chat_send mode="plan" model="DeepAnalysis" message="Analyze our authentication architecture" ``` ## Practical Examples ### Example 1: Adding a New Feature ``` # 1. Clear previous context manage_selection action="clear" # 2. Find relevant files search pattern="session manager" mode="both" # 3. Select core files manage_selection action="add" paths=["src/session-manager.ts", "src/types/session.ts", "tests/session.test.ts"] # 4. Set the task set_prompt_state prompt="Add session expiration and automatic cleanup features" # 5. Plan the implementation chat_send mode="plan" message="Design session expiration system with configurable timeouts" # 6. Execute the plan chat_send mode="edit" message="Implement the session expiration features" ``` ### Example 2: Debugging Complex Issues ``` # 1. Gather error context search pattern="error.*websocket" mode="content" # 2. Select relevant files manage_selection action="replace" paths=["src/websocket.ts", "src/error-handler.ts", "logs/recent-errors.log"] # 3. Describe the issue set_prompt_state prompt="WebSocket connections dropping intermittently under high load" # 4. Analyze with reasoning model chat_send mode="plan" message="Investigate and fix WebSocket stability issues" ``` ### Example 3: Refactoring ``` # 1. Select files to refactor manage_selection action="add" paths=["src/old-api.ts", "src/handlers/*.ts"] # 2. Set refactoring goals set_prompt_state prompt="Refactor API to use async/await instead of callbacks, maintain backward compatibility" # 3. Plan the refactor chat_send mode="plan" message="Create refactoring plan preserving all existing functionality" # 4. Execute incrementally chat_send mode="edit" message="Start refactoring the authentication handlers" ``` ## Best Practices ### 1. Context Selection * Start with minimal context and add as needed * Include test files when modifying code * Add configuration files for system-wide changes * Remove files that are no longer relevant ### 2. Prompt Writing * Be specific about requirements * Include constraints (e.g., "maintain backward compatibility") * Mention relevant technologies and patterns * Specify testing requirements ### 3. Mode Selection * Use `plan` mode for: * Complex architectural decisions * Multi-file refactoring * New feature design * Use `edit` mode for: * Direct implementation * Bug fixes with clear solutions * Following an established plan * Use `chat` mode for: * General discussions * Code exploration * Understanding existing code ### 4. Session Management * Continue existing chats for related work: ``` chat_tools action="list" chat_send chat_id="existing-id" message="Continue the refactoring" ``` * Start new chats for unrelated tasks * Name your chats for easy identification: ``` chat_send new_chat=true chat_name="Auth System Refactor" message="Starting auth refactor" ``` ## Troubleshooting ### Common Issues 1. **Token Limit Exceeded** * Check current usage: `manage_selection action="list" include_stats=true` * Remove unnecessary files: `manage_selection action="remove" paths=["large-file.ts"]` * Use code structure instead of full content for large files 2. **Model Not Responding** * Reasoning models can be slow, especially on complex tasks * Wait for completion before sending follow-up messages * Consider breaking large tasks into smaller chunks 3. **Context Lost Between Messages** * Ensure you're continuing the same chat session * Verify file selection hasn't changed: `manage_selection action="list"` * Check chat history: `chat_tools action="log"` 4. **Edit Mode Not Working** * Ensure you have write permissions for target files * Verify files exist and are selected * Check for syntax errors in previous edits ### Getting Help * Use `chat_tools action="log"` to review conversation history * Check file selection state with `manage_selection action="list"` * Verify model availability with `list_models` * Ensure RepoPrompt is running: `claude mcp list` ## Advanced Workflows ### Multi-Stage Development For complex features spanning multiple components: 1. **Stage 1: Architecture Planning** ``` manage_selection action="replace" paths=["docs/architecture.md", "src/index.ts"] chat_send mode="plan" message="Design microservice architecture" ``` 2. **Stage 2: Core Implementation** ``` manage_selection action="replace" paths=["src/core/*.ts", "src/types/*.ts"] chat_send mode="edit" message="Implement core service logic" ``` 3. **Stage 3: Integration** ``` manage_selection action="add" paths=["src/api/*.ts", "tests/integration/*.ts"] chat_send mode="edit" message="Add API endpoints and integration tests" ``` ### Collaborative Review Use RepoPrompt for code review and improvements: ``` # Select files for review manage_selection action="replace" paths=["src/new-feature/*.ts"] # Set review criteria set_prompt_state prompt="Review for security vulnerabilities, performance issues, and code style" # Get detailed analysis chat_send mode="plan" message="Perform comprehensive code review" ``` ## Tips for Success 1. **Let Models Play to Their Strengths** * Claude: File navigation, tool use, quick edits * O1/O3: Deep reasoning, complex algorithms, architecture * Gemini: Large context analysis, pattern recognition 2. **Maintain Clear Separation** * Planning: High-level design and approach * Execution: Actual code changes * Verification: Testing and validation 3. **Use Appropriate Context** * Include enough context for understanding * But not so much that it overwhelms the model * \~60K tokens is often the sweet spot 4. **Iterate Thoughtfully** * Start with planning before jumping to implementation * Review plans before execution * Test incrementally ## Conclusion RepoPrompt's pair programming mode enables sophisticated AI collaboration for complex development tasks. By leveraging multiple models' strengths and maintaining careful context management, you can tackle challenging problems more effectively than with a single model alone. Remember: The key is using Claude Code as an intelligent context manager while letting reasoning models handle complex problem-solving. This division of labor produces higher quality results than either model working alone. # Security Source: https://docs.vibetunnel.sh/docs/security # VibeTunnel Server Security Configuration ## Authentication Options VibeTunnel Server provides several authentication mechanisms to secure terminal access: ### 1. Standard Authentication **System User Password** (default) * Uses the operating system's user authentication * Validates against local user accounts * Supports optional SSH key authentication with `--enable-ssh-keys` **No Authentication Mode** * Enabled with `--no-auth` flag * Automatically logs in as the current user * **WARNING**: Anyone with network access can use the terminal ### 2. Local Bypass Authentication The `--allow-local-bypass` flag enables a special authentication mode that allows localhost connections to bypass normal authentication requirements. #### Configuration Options **Basic Local Bypass** ```bash theme={null} vibetunnel-server --allow-local-bypass ``` * Allows any connection from localhost (127.0.0.1, ::1) to access without authentication * No token required **Secured Local Bypass** ```bash theme={null} vibetunnel-server --allow-local-bypass --local-auth-token ``` * Localhost connections must provide token via `X-VibeTunnel-Local` header * Adds an additional security layer for local connections #### Security Implementation The local bypass feature implements several security checks to prevent spoofing: 1. **IP Address Validation** (`web/src/server/middleware/auth.ts:24-48`) * Verifies connection originates from localhost IPs (127.0.0.1, ::1, ::ffff:127.0.0.1) * Checks both `req.ip` and `req.socket.remoteAddress` 2. **Header Verification** * Ensures no forwarding headers are present (`X-Forwarded-For`, `X-Real-IP`, `X-Forwarded-Host`) * Prevents proxy spoofing attacks 3. **Hostname Validation** * Confirms request hostname is localhost, 127.0.0.1, or \[::1] * Additional layer of verification 4. **Token Authentication** (when configured) * Requires `X-VibeTunnel-Local` header to match configured token * Provides shared secret authentication for local tools #### Security Implications **Benefits:** * Enables automated tools and scripts on the same machine to access terminals * Useful for development workflows and CI/CD pipelines * Allows local monitoring tools without exposing credentials **Risks:** * Any process on the local machine can access terminals (without token) * Malicious local software could exploit this access * Token-based mode mitigates but doesn't eliminate local access risks **Recommended Usage:** 1. **Development Environments**: Safe for local development machines 2. **CI/CD Servers**: Use with token authentication for build scripts 3. **Production Servers**: NOT recommended unless: * Combined with token authentication * Server has strict local access controls * Used only for specific automation needs #### Example Use Cases **Local Development Tools** ```javascript theme={null} // Local tool accessing VibeTunnel without authentication const response = await fetch('http://localhost:4020/api/sessions', { headers: { 'X-VibeTunnel-Local': 'my-secret-token' // Only if token configured } }); ``` **Automated Testing** ```bash theme={null} # Start server with local bypass for tests vibetunnel-server --allow-local-bypass --local-auth-token test-token # Test script can now access without password curl -H "X-VibeTunnel-Local: test-token" http://localhost:4020/api/sessions ``` ## Additional Security Considerations ### Network Binding * Default: Binds to all interfaces (0.0.0.0) * Use `--bind 127.0.0.1` to restrict to localhost only * Combine with `--allow-local-bypass` for local-only access ### SSH Key Authentication * Enable with `--enable-ssh-keys` * Disable passwords with `--disallow-user-password` * More secure than password authentication ### HTTPS/TLS * VibeTunnel does not provide built-in TLS * Use a reverse proxy (nginx, Caddy) for HTTPS * Or use secure tunnels (Tailscale, ngrok) ### Best Practices 1. Always use authentication in production 2. Restrict network binding when possible 3. Use token authentication with local bypass 4. Monitor access logs for suspicious activity 5. Keep the server updated for security patches # Spec Source: https://docs.vibetunnel.sh/docs/spec # VibeTunnel Technical Specification ## Table of Contents 1. [Executive Summary](#executive-summary) 2. [System Architecture](#system-architecture) 3. [Data Flows](#data-flows) 4. [Core Components](#core-components) 5. [Server Implementation](#server-implementation) 6. [Web Frontend](#web-frontend) 7. [iOS Application](#ios-application) 8. [Security Model](#security-model) 9. [Session Management](#session-management) 10. [CLI Integration](#cli-integration) 11. [API Specifications](#api-specifications) 12. [Binary Buffer Protocol](#binary-buffer-protocol) 13. [User Interface](#user-interface) 14. [Configuration System](#configuration-system) 15. [Build and Release](#build-and-release) 16. [Testing Strategy](#testing-strategy) 17. [Performance Requirements](#performance-requirements) 18. [Error Handling](#error-handling) 19. [Update System](#update-system) 20. [Platform Integration](#platform-integration) 21. [Data Formats](#data-formats) ## Executive Summary ### Project Overview 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. ### Key Features * **Zero-Configuration Terminal Access**: Launch terminals with a simple `vt` command * **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 ### Technical Stack * **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 ## System Architecture ### High-Level Architecture ``` ┌─────────────────────────────────────────────────────────────┐ │ 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 │ │ │ └──────────────┘ └──────────────┘ │ └─────────────────────────────────────────────────────────────┘ ``` ### Component Interaction Flow 1. **Terminal Launch**: User executes `vt` command 2. **Server Check**: ServerManager ensures Bun server is running 3. **Session Creation**: HTTP POST to create new terminal session 4. **PTY Allocation**: Server allocates pseudo-terminal via node-pty 5. **WebSocket Upgrade**: Client establishes `/ws` connection (v3 framing) 6. **Terminal Transport**: Multiplexed binary frames (`STDOUT` + `SNAPSHOT_VT`) 7. **Recording**: Session data recorded in asciinema format 8. **Session Cleanup**: Resources freed on terminal exit ### Design Principles * **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 ## Data Flows ### Terminal Session Lifecycle 1. User launches the `vt` command or selects **New Session** from the UI. 2. `ServerManager` verifies that the Bun server is running and starts it if needed. 3. A `POST /api/sessions` request triggers `TerminalManager.createTerminal()` on the server. 4. `PtyManager.spawn()` allocates a new PTY process and stores session metadata. 5. The server responds with the session ID and WebSocket URL. 6. Clients connect to `/ws` and `SUBSCRIBE(sessionId, flags)` using WS v3 framing. 7. Terminal output and input are recorded in asciinema format when recording is enabled. 8. On process exit, resources are cleaned up and the client is notified. ### Terminal I/O Flow 1. Keyboard input from the browser or iOS app is sent as WS v3 frames (`INPUT_TEXT` / `INPUT_KEY`). 2. `WsV3Hub` routes input to `PtyManager`. 3. PTY output is tailed and sent back as `STDOUT` frames. 4. Server-side VT snapshots (`SNAPSHOT_VT`) are streamed for previews/resync. 5. The client updates its terminal display accordingly. ### Server Lifecycle Flow 1. Starting the macOS app or running `vt` launches `ServerManager`. 2. `BunServer` spawns the Bun-based HTTP/WebSocket server process. 3. Health checks hit `/api/health` to verify the server is alive. 4. On stop or crash, `ServerManager` gracefully shuts down or restarts the process. ### Remote Access Flow 1. When network mode is enabled, the server binds to `0.0.0.0` for remote access. 2. `NgrokService` or Tailscale can expose a secure public URL. 3. Remote clients reach the server through the tunnel and communicate over HTTPS. ### Authentication Flow 1. Clients request the dashboard or a session endpoint. 2. Basic Auth middleware checks credentials stored via `DashboardKeychain`. 3. Local bypass or token-based headers are honored if configured. 4. Successful authentication allows API and WebSocket communication. ## Core Components ### ServerManager **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**: ```swift theme={null} func start() async func stop() async func restart() async func clearAuthCache() async ``` **State Management**: * Uses `@Observable` for SwiftUI integration * `@MainActor` ensures UI thread safety * Publishes server state changes * Maintains server configuration in UserDefaults ### BunServer **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 ### SessionMonitor **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 ### TerminalManager **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 ### NgrokService **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 ## Server Implementation ### Node.js/Bun Server **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 lifecycle * `app.ts` - Express application setup and middleware * `native/vt-fwd` - External terminal forwarder (Rust, built as `vibetunnel-fwd`) * `pty/pty-manager.ts` - Native PTY process management * `pty/session-manager.ts` - Terminal session lifecycle * `services/terminal-manager.ts` - High-level terminal operations * `services/ws-v3-hub.ts` - Unified `/ws` WebSocket v3 hub * `services/cast-output-hub.ts` - Cast tailing → v3 `STDOUT` * `services/git-status-hub.ts` - Git status updates → v3 `EVENT` * `routes/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**: ```bash theme={null} # Build standalone executable cd web && node build-native.js # Creates web/native/vibetunnel (60MB Bun executable) ``` ## Web Frontend ### Technology Stack **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) ### Component Architecture ``` 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 ``` ### Key Features **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 (`VT` magic, multiplexed sessions) * Snapshot cadence control (previews vs interactive) * Asciicast tailing with pruning detection * WebSocket reconnection and resubscribe logic * Lazy loading of terminal sessions ## iOS Application ### Overview **Location**: `ios/VibeTunnel/` directory **Purpose**: Native iOS companion app for mobile terminal access ### Architecture **Key Components**: * `VibeTunnelApp.swift` - Main app entry and lifecycle * `BufferWebSocketClient.swift` - WebSocket client with binary protocol * `TerminalView.swift` - Native terminal rendering * `GhosttyWebView.swift` - Ghostty web renderer (WKWebView) * `TerminalBufferRenderer.swift` - Buffer snapshot → ANSI conversion * `SessionService.swift` - Session management API client ### Features * Native SwiftUI interface * Server connection management * Terminal rendering with gesture support * Session listing and management * Recording export functionality * Advanced keyboard support ### Binary Buffer Protocol Support The iOS app speaks the same terminal transport as the web client: * Single `/ws` WebSocket (v3 framing) * Multiplexed sessions (`sessionId` in each frame) * VT snapshot v1 payloads for previews/resync ## Security Model ### Authentication **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.md` for 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` ### Network Security **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 ### System Security **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 ## Session Management ### Session Lifecycle ``` ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ Created │ --> │ Active │ --> │ Exited │ --> │ Cleaned │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ ``` ### Session Model **TypeScript Definition** (`web/src/server/pty/types.ts`): ```typescript theme={null} 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; } ``` ### Session Operations **Creation**: 1. Generate unique session ID (UUID) 2. Spawn PTY process with command 3. Initialize asciinema recording 4. Register with SessionManager 5. 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 ## CLI Integration ### vt Command **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` ```bash theme={null} #!/bin/bash # VibeTunnel CLI wrapper for Bun server exec /usr/local/bin/vibetunnel fwd "$@" ``` ### vibetunnel Binary **Location**: Embedded in app bundle, copied to `/usr/local/bin/vibetunnel` **Commands**: * `vibetunnel serve` - Start server (used internally) * `vibetunnel fwd [command]` - Forward terminal session * `vibetunnel version` - Show version information ### CLI Features **Command Parsing**: * Automatic 'fwd' prepending for vt wrapper * Shell detection and setup * Working directory preservation * Environment variable handling **Session Creation Flow**: 1. Parse command-line arguments 2. Ensure server is running 3. Create session via API 4. Open browser to session URL 5. Return session information ## API Specifications ### RESTful API **Base URL**: `http://localhost:4020` (default) **Authentication**: Optional HTTP Basic Auth #### Core Endpoints **GET /api/health** ```json theme={null} { "status": "ok", "version": "1.0.0" } ``` **GET /api/sessions** ```json theme={null} { "sessions": [ { "id": "550e8400-e29b-41d4-a716-446655440000", "command": "zsh", "args": [], "cwd": "/Users/username", "startTime": 1704060000000, "status": "running", "cols": 80, "rows": 24 } ] } ``` **POST /api/sessions** ```json theme={null} // 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** ```json theme={null} { "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** ```json theme={null} { "cols": 120, "rows": 40 } ``` ### WebSocket Protocol **Endpoint**: `GET /ws` (WebSocket upgrade) Terminal transport uses binary WebSocket v3 framing (multiplexed sessions). Details: `docs/websocket.md`. ## Binary Buffer Protocol ### Overview 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) ### Message Format See `docs/websocket.md` (frame layout + message types). ### Protocol Flow 1. Client connects to `/ws` (one socket). 2. Client sends `SUBSCRIBE(sessionId, flags)` for each interested session. 3. Server streams `STDOUT`/`SNAPSHOT_VT`/`EVENT` frames per subscription. 4. Client sends `INPUT_TEXT`/`INPUT_KEY`/`RESIZE` frames back. ### Implementation Details **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 `STDOUT` and `SNAPSHOT_VT` via `web/src/client/services/terminal-socket-client.ts` **iOS Client** (`ios/VibeTunnel/Services/BufferWebSocketClient.swift`): * Same `/ws` v3 framing + VT snapshot v1 decoding ## User Interface ### Menu Bar Application **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 ### Settings Window **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 ## Configuration System ### User Defaults **Storage**: `UserDefaults.standard` **Key Settings**: ```swift theme={null} serverPort: String = "4020" dashboardAccessMode: String = "localhost" dashboardPasswordEnabled: Bool = false launchAtLogin: Bool = false showDockIcon: Bool = false cleanupOnStartup: Bool = true ``` ### Keychain Integration **DashboardKeychain Service**: * Stores dashboard password securely * Uses kSecClassInternetPassword * Server and port-specific storage * Handles password updates/deletion ### Configuration Flow 1. **App Launch**: Load settings from UserDefaults 2. **Server Start**: Pass configuration via CLI arguments 3. **Runtime Changes**: Update server without restart where possible 4. **Password Changes**: Clear server auth cache ## Build and Release ### Build System **Requirements**: * Xcode 16.0+ * macOS 14.0+ SDK * Node.js 22.12 through 24.x * Bun runtime * Rustup; the forwarder toolchain is pinned by `native/vt-fwd/rust-toolchain.toml` **Build Process**: ```bash theme={null} # 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 build ``` **Build Phases**: 1. Build Bun executable from web sources 2. Compile Swift application 3. Copy resources (Bun binary, web assets) 4. Code sign application 5. Create DMG for distribution ### Code Signing **Entitlements** (`mac/VibeTunnel/VibeTunnel.entitlements`): ```xml theme={null} com.apple.security.cs.allow-jit com.apple.security.cs.allow-unsigned-executable-memory com.apple.security.cs.allow-dyld-environment-variables com.apple.security.cs.disable-library-validation ``` ### Distribution **Release Process**: 1. Build and sign application 2. Create notarized DMG 3. Generate Sparkle appcast 4. Upload to GitHub releases 5. Update appcast XML **Package Contents**: * ARM64-only binary (Apple Silicon required) * Embedded Bun server executable * Web assets and resources * Sparkle update framework ## Testing Strategy ### macOS Tests **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 ### Node.js Tests **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 ## Performance Requirements ### Latency Targets **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 ### Resource Usage **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 ### Scalability **Concurrent Sessions**: * Target: 50 simultaneous sessions * Tested: 100+ sessions * Graceful degradation * Buffer pooling for efficiency ## Error Handling ### Error Categories **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 ### Error Recovery **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 ## Update System ### Sparkle Integration **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 ### Update Process 1. Check appcast.xml for updates 2. Download update package 3. Verify EdDSA signature 4. Prompt user for installation 5. Install and restart application ## Platform Integration ### macOS Integration **System Features**: * Launch at login via SMAppService * Menu bar and Dock modes * Notification Center support * Keyboard shortcuts * AppleScript support ### Terminal Integration **Supported Terminals**: * Terminal.app (default) * iTerm2 * Warp * Alacritty * Hyper * kitty **Detection Method**: * Check bundle identifiers * Verify app existence * User preference storage ## Data Formats ### Asciinema Recording **Format**: Asciinema v2 **Header**: ```json theme={null} { "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"] ``` ### Session Storage 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. ## Conclusion 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. # Tailscale ios Source: https://docs.vibetunnel.sh/docs/tailscale_ios # VibeTunnel Architecture Deep Dive ## 🏗️ High-Level Architecture VibeTunnel is a sophisticated terminal multiplexer ecosystem with native macOS/iOS apps and a powerful web interface. Here's the complete architectural breakdown: ``` ┌─────────────────────────────────────────────────────────────────────────┐ │ USER INTERFACES │ ├─────────────────┬──────────────────────┬─────────────────────────────────┤ │ macOS Menu │ iOS App │ Web Browser │ │ Bar App │ (SwiftUI) │ (TypeScript/LitElement) │ │ │ │ │ │ ┌───────────┐ │ ┌──────────────┐ │ ┌──────────────────────────┐ │ │ │ServerMgr │ │ │SessionService│ │ │ xterm.js Terminal │ │ │ │TTYFwd │ │ │BufferWS │ │ │ Session Management UI │ │ │ │Monitor │ │ │APIClient │ │ │ File Browser │ │ │ └─────┬─────┘ │ └──────┬───────┘ │ └──────────┬───────────────┘ │ └────────┼────────┴─────────┼───────────┴─────────────┼─────────────────┘ │ │ │ │ Spawns & │ REST/WS │ HTTP/WS │ Manages │ │ ▼ └──────────┬───────────────┘ ┌─────────────────────────────────────▼──────────────────────────────────┐ │ NODE.JS/BUN SERVER (Port 4020) │ │ │ │ ┌──────────────────────────────────────────────────────────────────┐ │ │ │ EXPRESS APP + MIDDLEWARE │ │ │ │ Auth (JWT/SSH) │ CORS │ Compression │ Static Files │ Logging │ │ │ └──────────────────────────────────────────────────────────────────┘ │ │ │ │ ┌─────────────────┐ ┌──────────────────┐ ┌────────────────────┐ │ │ │ PTY Manager │ │ Terminal Manager │ │ Buffer Aggregator │ │ │ │ - Spawn PTY │ │ - Session Logic │ │ - Binary Protocol │ │ │ │ - Process I/O │ │ - Lifecycle Mgmt │ │ - Snapshot/Delta │ │ │ └─────────────────┘ └──────────────────┘ └────────────────────┘ │ │ │ │ ┌─────────────────┐ ┌──────────────────┐ ┌────────────────────┐ │ │ │ Session Monitor │ │ Stream Watcher │ │ Activity Monitor │ │ │ │ - Cleanup │ │ - Log Tailing │ │ - Idle Detection │ │ │ │ - Zombie detect │ │ - File Watch │ │ - Resource Track │ │ │ └─────────────────┘ └──────────────────┘ └────────────────────┘ │ └─────────────────────────────────────────────────────────────────────────┘ │ System Resources ▼ ┌─────────────────────────────────────────────────────────────────────────┐ │ SYSTEM LAYER │ │ ┌──────────────┐ ┌─────────────────┐ ┌─────────────────────────┐ │ │ │ PTY Processes│ │ File System │ │ Unix Sockets │ │ │ │ (bash/zsh) │ │ (~/.vibetunnel) │ │ (IPC Communication) │ │ │ └──────────────┘ └─────────────────┘ └─────────────────────────┘ │ └─────────────────────────────────────────────────────────────────────────┘ ``` ## 🎯 Core Components Breakdown ### 1. **macOS Application (Swift/SwiftUI)** The native macOS app serves as the system orchestrator: ``` mac/VibeTunnel/ ├── Core/ │ ├── Services/ │ │ ├── ServerManager.swift # Central orchestrator │ │ ├── BunServer.swift # Bun runtime integration │ │ ├── SessionMonitor.swift # Session tracking │ │ ├── TTYForwardManager.swift # Terminal forwarding │ │ ├── UnixSocketConnection.swift # IPC communication │ │ └── TailscaleServeService.swift # Remote access │ ├── Models/ │ │ ├── TunnelSession.swift # Session data model │ │ └── AppConstants.swift # Configuration │ └── Protocols/ │ └── VibeTunnelServer.swift # Server interface └── Presentation/ ├── Views/ # SwiftUI views └── Components/ # UI components ``` **Key Responsibilities:** * **Process Management**: Spawns and monitors the Bun/Node.js server * **Log Aggregation**: Captures all logs from server and frontend * **System Integration**: Menu bar UI, notifications, keychain * **Remote Access**: Tailscale/Ngrok tunnel management ### 2. **Web Server (Node.js/Bun)** The TypeScript server handles all terminal operations: ``` web/src/server/ ├── server.ts # Main server entry (912 lines) ├── pty/ │ ├── pty-manager.ts # Native PTY management │ ├── session-manager.ts # Session lifecycle │ └── types.ts # TypeScript definitions ├── services/ │ ├── terminal-manager.ts # High-level terminal ops │ ├── buffer-aggregator.ts # Binary buffer protocol │ ├── auth-service.ts # SSH key authentication │ ├── activity-monitor.ts # Resource tracking │ └── hq-client.ts # Multi-server mode ├── routes/ │ ├── sessions.ts # Session REST API │ ├── websocket-input.ts # WebSocket handlers │ ├── auth.ts # Authentication endpoints │ └── control.ts # Unix socket control └── middleware/ └── auth.ts # JWT validation ``` **Key Features:** * **Session Management**: Full PTY lifecycle (create/resize/kill) * **Binary Buffer Protocol**: Optimized terminal streaming * **Authentication**: JWT + SSH keys + PAM * **Distributed Mode**: HQ server for multi-machine setups ### 3. **iOS Application (Swift/SwiftUI)** Mobile terminal client with full feature parity: ``` ios/VibeTunnel/ ├── Services/ │ ├── BufferWebSocketClient.swift # Binary protocol client │ ├── SessionService.swift # Session management │ ├── ConnectionManager.swift # Server connections │ └── BonjourDiscoveryService.swift # Local discovery ├── Views/ │ ├── Terminal/ │ │ ├── TerminalView.swift # Main terminal UI │ │ ├── XtermWebView.swift # xterm.js wrapper │ │ └── TerminalHostingView.swift # UIKit bridge │ └── Sessions/ │ └── SessionListView.swift # Session browser └── Models/ ├── TerminalSnapshot.swift # Buffer state └── Session.swift # Session model ``` ### 4. **Web Frontend (TypeScript/LitElement)** Browser-based terminal interface: ``` web/src/client/ ├── app.ts # Main LitElement app ├── components/ │ ├── terminal.ts # xterm.js wrapper │ ├── session-list.ts # Session management │ └── file-browser.ts # File navigation └── services/ ├── websocket.ts # WebSocket client └── api-client.ts # REST client ``` ## 📡 Communication Protocols ### **1. Binary Buffer Protocol (0xBF Magic Byte)** Optimized terminal streaming protocol: ```typescript theme={null} // Message Format [Magic Byte: 0xBF] [Type: 1 byte] [Length: 4 bytes] [Payload: N bytes] // Types: 0x01: Full buffer snapshot 0x02: Delta update 0x03: Cursor position 0x04: Terminal resize ``` **Flow:** ``` Terminal Output → BufferAggregator → Binary Encode → WebSocket → Client Decode → xterm.js ``` ### **2. REST API Endpoints** ``` POST /api/sessions # Create session GET /api/sessions # List sessions DELETE /api/sessions/:id # Kill session POST /api/sessions/:id/resize # Resize terminal WS /api/sessions/:id/ws # Terminal I/O stream GET /api/auth/challenge # SSH key challenge POST /api/auth/ssh-key # SSH key verify ``` ### **3. Unix Socket IPC** Mac app ↔ Server communication: ```swift theme={null} // Control Protocol { "type": "session.create", "payload": { "cols": 80, "rows": 24, "cwd": "/Users/chris" } } ``` ## 🔄 Key Data Flows ### **Session Creation Flow** ``` User Request │ ▼ [macOS App] ServerManager.createSession() │ ├─→ [IPC] Unix Socket Message │ ▼ [Server] POST /api/sessions │ ├─→ TerminalManager.createTerminal() ├─→ PtyManager.spawn() → node-pty ├─→ Create ~/.vibetunnel/control/[sessionId]/ ├─→ Start BufferAggregator │ ▼ [Response] { sessionId, wsUrl } │ ▼ [Client] Connect WebSocket │ ▼ [Bidirectional Terminal I/O] ``` ### **Log Aggregation Pipeline** ``` ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ Frontend │────▶│ Server │────▶│ Mac App │────▶│ macOS Log │ │ console.log │HTTP │ [CLIENT:*] │Pipe │ ServerOutput │ │ Unified │ └──────────────┘ └──────────────┘ └──────────────┘ └──────────────┘ [component] msg → POST /api/logs → [CLIENT:component] → Logger category → vtlog ``` ## 🔐 Security Architecture ### **Authentication Layers** ``` ┌─────────────────────────────────────────────────────┐ │ Client Request │ └────────────────────────┬────────────────────────────┘ ▼ ┌─────────────────────────────────────────────────────┐ │ Authentication Middleware │ ├─────────────────────────────────────────────────────┤ │ 1. Local Bypass (localhost + token) │ │ 2. JWT Token (from previous auth) │ │ 3. SSH Key Challenge/Response │ │ 4. Password (PAM or env var) │ │ 5. Bearer Token (HQ mode) │ └─────────────────────────────────────────────────────┘ │ Authenticated ▼ ┌─────────────────────────────────────────────────────┐ │ Protected Routes │ └─────────────────────────────────────────────────────┘ ``` ## 🚀 Advanced Features ### **1. Distributed Mode (HQ)** ``` ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ Machine A │────▶│ HQ Server │◀────│ Machine B │ │ (Remote) │ │ (Central) │ │ (Remote) │ └──────────────┘ └──────────────┘ └──────────────┘ Registration → Session Discovery → Proxied Access ``` ### **2. Remote Access (Tailscale/Ngrok)** ``` Internet → Tailscale Funnel/Ngrok → localhost:4020 → VibeTunnel ├─ HTTPS termination ├─ Authentication └─ Traffic routing ``` ### **3. Activity Monitoring** ```typescript theme={null} // Idle detection and resource management ActivityMonitor → Session idle > 5min → Mark inactive → System resources → Auto-cleanup → WebSocket ping/pong → Connection health ``` ## 📁 File System Structure ``` ~/.vibetunnel/ ├── control/ # Session control files │ └── [sessionId]/ │ ├── session.json # Metadata │ ├── stdout # Output log │ ├── stdin # Input log │ ├── activity.json # Activity status │ └── ipc.sock # Unix socket ├── logs/ # Application logs ├── keys/ # VAPID keys └── config/ # Server config ``` ## 🎨 Technology Stack * **macOS/iOS**: Swift 6, SwiftUI, Combine, os.log * **Server**: Node.js/Bun, TypeScript, Express, node-pty * **Frontend**: TypeScript, LitElement, xterm.js, Web Components * **Protocols**: WebSocket, Unix Sockets, REST, Binary Buffer * **Security**: JWT, Ed25519 SSH keys, PAM, Keychain * **Build**: Xcode, Swift Package Manager, pnpm, esbuild ## 🔧 Key Implementation Details ### **Server Lifecycle** 1. **Startup**: Mac app spawns Bun process with embedded server 2. **Health Check**: Polls /health endpoint until ready 3. **Operation**: Handles sessions, forwards logs to Mac app 4. **Shutdown**: Graceful termination on SIGTERM ### **Session Persistence** * Sessions survive server restarts via control directory * Reconnection supported through session ID * Automatic cleanup of orphaned sessions ### **Performance Optimizations** * **Binary Protocol**: 10x smaller than JSON for terminal data * **Buffer Aggregation**: Batches updates to reduce WebSocket messages * **Delta Updates**: Only sends changes, not full buffer * **Lazy Loading**: Sessions load on-demand ### **Development vs Production** * **Development**: Hot reload, verbose logging, dev server mode * **Production**: Embedded server, optimized builds, minimal logging * **No Backwards Compatibility**: Everything ships together as one unit # Unified notifications test plan Source: https://docs.vibetunnel.sh/docs/unified-notifications-test-plan # Unified Notification System Test Plan ## Overview Test the new unified notification system that sends all notifications from the server to Mac via Unix socket. ## Architecture Changes * Server SessionMonitor detects all notification events * Events sent to Mac via Unix socket (session-monitor category) * Mac NotificationControlHandler processes and displays notifications * No more polling or duplicate detection logic ## Test Scenarios ### 1. Bell Notification Test ```bash theme={null} # In any VibeTunnel session echo -e '\a' # or printf '\007' ``` **Expected**: Bell notification appears on both Mac and Web ### 2. Command Completion Test (>3 seconds) ```bash theme={null} # Run a command that takes more than 3 seconds sleep 4 # or find / -name "*.txt" 2>/dev/null | head -100 ``` **Expected**: Command completion notification after command finishes ### 3. Command Error Test ```bash theme={null} # Run a command that fails ls /nonexistent/directory # or false ``` **Expected**: Command error notification with exit code ### 4. Session Start/Exit Test ```bash theme={null} # From another terminal or web UI # Create new session # Exit session with 'exit' command ``` **Expected**: Session start and exit notifications ## Verification Steps 1. **Enable all notifications in Mac Settings**: * Open VibeTunnel → Settings → Notifications * Enable "Show Session Notifications" * Enable all notification types * Enable sound if desired 2. **Monitor Unix socket traffic** (optional): ```bash theme={null} # In a separate terminal, monitor the control socket sudo dtrace -n 'syscall::write:entry /execname == "VibeTunnel" || execname == "node"/ { printf("%d: %s", pid, copyinstr(arg1, 200)); }' ``` 3. **Check logs**: ```bash theme={null} # Monitor VibeTunnel logs ./scripts/vtlog.sh -f -c NotificationControl # Check for session-monitor events ./scripts/vtlog.sh -f | grep "session-monitor" ``` ## Success Criteria 1. ✅ All notification types work on Mac via Unix socket 2. ✅ No duplicate notifications 3. ✅ Notifications respect user preferences (on/off toggles) 4. ✅ No more 3-second polling from Mac SessionMonitor 5. ✅ Single source of truth (server) for all notification events ## Troubleshooting * If no notifications appear, check: * Mac app is connected to server (check Unix socket connection) * Notifications are enabled in settings * Check vtlog for any errors * If notifications are delayed: * Check if bell detection is working (should be instant) * If getting duplicate notifications: * Ensure only one VibeTunnel instance is running * Check that old SessionMonitor code is not running # Websocket Source: https://docs.vibetunnel.sh/docs/websocket # WebSocket v3 (Unified Ghostty Transport) Single WebSocket. Multiplexed sessions. Binary framing. Same protocol for web + iOS. ## Endpoint * `GET /ws` (WebSocket upgrade) * Auth: * normal: `?token=...` * `--no-auth`: token optional ## Framing (binary) ``` u16 magic = 0x5654 // "VT" LE u8 version = 3 u8 type u32 sessionIdLenLE u8[] sessionId (UTF-8, may be empty) u32 payloadLenLE u8[] payload ``` Source of truth: `web/src/shared/ws-v3.ts`. ## Message types (v3) IDs: `WsV3MessageType` in `web/src/shared/ws-v3.ts`. Client → Server: * `SUBSCRIBE` payload = `encodeWsV3SubscribePayload({ flags, snapshotMinIntervalMs, snapshotMaxIntervalMs })` * `sessionId` may be empty (`""`) to subscribe to global `EVENT` frames (no per-session STDOUT/snapshots). * `UNSUBSCRIBE` payload empty * `INPUT_TEXT` payload = UTF-8 text bytes (includes escape sequences when needed) * `INPUT_KEY` payload = UTF-8 key name (`SpecialKey`) * `RESIZE` payload = `u32 colsLE` + `u32 rowsLE` * `KILL` payload = UTF-8 signal (default `SIGTERM`) * `RESET_SIZE` payload empty * `PING` payload optional Server → Client: * `WELCOME` payload = JSON `{ ok: true, version: 3 }` * `STDOUT` payload = UTF-8 bytes from PTY (asciinema “o” frames’ data) * `SNAPSHOT_VT` payload = VT snapshot bytes (see next section) * `EVENT` payload = JSON * per-session: `exit`, `git-status-update`, … * global (`sessionId == ""`): `connected`, `test-notification`, … * `ERROR` payload = JSON `{ message: string }` * `PONG` payload optional ## Subscribe flags `WsV3SubscribeFlags` in `web/src/shared/ws-v3.ts`: * `Stdout` (bit 0) * `Snapshots` (bit 1) * `Events` (bit 2) ## Snapshot payload (`SNAPSHOT_VT`) Payload is the existing **VT snapshot v1** byte format (magic `VT`, version `1`). * Used for: * session list previews/thumbnails (server-rendered) * optional “hard resync” for interactive clients * Encoder: `TerminalManager` (server-side Ghostty emulation) ## Implementation map * Server hub: `web/src/server/services/ws-v3-hub.ts` * Stdout source: `web/src/server/services/cast-output-hub.ts` (tails cast + pruning via `lastClearOffset`) * Git events: `web/src/server/services/git-status-hub.ts` * Web client transport: `web/src/client/services/terminal-socket-client.ts` * iOS transport: `ios/VibeTunnel/Services/BufferWebSocketClient.swift` ## HQ mode HQ uses the same `/ws` v3 frames. * HQ keeps one upstream WS per remote. * Downstream subscriptions aggregate flags per session and fan out frames to clients. ## Removed legacy transports * `/buffers` (v2 `0xBF` framing) * `/ws/input` Still available: * `/api/sessions/:id/text` (plain-text rendering of the current terminal buffer) # Worktree Source: https://docs.vibetunnel.sh/docs/worktree # Git Worktree Management in VibeTunnel VibeTunnel provides comprehensive Git worktree support, allowing you to work on multiple branches simultaneously without the overhead of cloning repositories multiple times. This guide covers everything you need to know about using worktrees effectively in VibeTunnel. ## Table of Contents * [What are Git Worktrees?](#what-are-git-worktrees) * [VibeTunnel's Worktree Features](#vibetunnels-worktree-features) * [Creating Sessions with Worktrees](#creating-sessions-with-worktrees) * [Branch Management](#branch-management) * [Worktree Operations](#worktree-operations) * [Follow Mode](#follow-mode) * [Best Practices](#best-practices) * [Common Workflows](#common-workflows) * [Troubleshooting](#troubleshooting) ## What are Git Worktrees? Git worktrees allow you to have multiple working trees attached to the same repository, each checked out to a different branch. This means you can: * Work on multiple features simultaneously * Keep a clean main branch while experimenting * Quickly switch between tasks without stashing changes * Run tests on one branch while developing on another ## VibeTunnel's Worktree Features VibeTunnel enhances Git worktrees with: 1. **Visual Worktree Management**: See all worktrees at a glance in the session list 2. **Smart Branch Switching**: Automatically handle branch conflicts and uncommitted changes 3. **Follow Mode**: Keep multiple worktrees in sync when switching branches 4. **Integrated Session Creation**: Create new sessions directly in worktrees 5. **Worktree-aware Terminal Titles**: See which worktree you're working in ## Creating Sessions with Worktrees ### Using the New Session Dialog When creating a new session in a Git repository, VibeTunnel provides intelligent branch and worktree selection: 1. **Base Branch Selection** * When no worktree is selected: "Switch to Branch" - attempts to switch the main repository to the selected branch * When creating a worktree: "Base Branch for Worktree" - uses this as the source branch 2. **Worktree Selection** * Choose "No worktree (use main repository)" to work in the main checkout * Select an existing worktree to create a session there * Click "Create new worktree" to create a new worktree on-the-fly ### Smart Branch Switching When you select a different branch without choosing a worktree: ``` Selected: feature/new-ui Current: main Action: Attempts to switch from main to feature/new-ui ``` If the switch fails (e.g., due to uncommitted changes): * A warning is displayed * The session is created on the current branch * No work is lost ### Creating New Worktrees To create a new worktree from the session dialog: 1. Select your base branch (e.g., `main` or `develop`) 2. Click "Create new worktree" 3. Enter the new branch name 4. Click "Create" The worktree will be created at: `{repo-path}-{branch-name}` Example: `/Users/you/project` → `/Users/you/project-feature-awesome` ## Branch Management ### Branch States in VibeTunnel VibeTunnel shows rich Git information for each session: * **Branch Name**: Current branch with worktree indicator * **Ahead/Behind**: Commits ahead/behind the upstream branch * **Changes**: Uncommitted changes indicator * **Worktree Status**: Main worktree vs feature worktrees ### Switching Branches There are several ways to switch branches: 1. **In Main Repository**: Use the branch selector in the new session dialog 2. **In Worktrees**: Each worktree maintains its own branch 3. **With Follow Mode**: Automatically sync the main repository when switching in a worktree ## Worktree Operations ### Listing Worktrees View all worktrees for a repository: * In the session list, worktrees are marked with a special indicator * The autocomplete dropdown shows worktree paths with their branches * Use the Git app launcher to see a dedicated worktree view ### Creating Worktrees via API ```bash theme={null} # Using VibeTunnel's API curl -X POST http://localhost:4020/api/worktrees \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "repoPath": "/path/to/repo", "branch": "feature/new-feature", "path": "/path/to/repo-new-feature", "baseBranch": "main" }' ``` ### Deleting Worktrees Remove worktrees when no longer needed: ```bash theme={null} # Via API curl -X DELETE "http://localhost:4020/api/worktrees/feature-branch?repoPath=/path/to/repo" \ -H "Authorization: Bearer YOUR_TOKEN" # With force option for worktrees with uncommitted changes curl -X DELETE "http://localhost:4020/api/worktrees/feature-branch?repoPath=/path/to/repo&force=true" \ -H "Authorization: Bearer YOUR_TOKEN" ``` ## Follow Mode Follow mode keeps your main repository synchronized with a specific worktree. This allows agents to work in worktrees while your IDE, Xcode, and servers stay open on the main repository - they'll automatically update when the worktree changes. ### How It Works 1. Enable follow mode from either the main repo or a worktree 2. Git hooks in both locations detect changes (commits, branch switches, checkouts) 3. Changes in the worktree sync to the main repository 4. Commits in the main repository sync to the worktree 5. Branch switches in the main repository auto-disable follow mode Follow mode state is stored in the main repository's git config: ```bash theme={null} # Check which worktree is being followed git config vibetunnel.followWorktree # Returns the path to the followed worktree when active ``` ### Using Follow Mode with vt From a worktree: ```bash theme={null} # Enable follow mode for this worktree vt follow # Output: Enabling follow mode for worktree: ~/project-feature # Main repository (~/project) will track this worktree ``` From main repository: ```bash theme={null} # Follow current branch's worktree (if it exists) vt follow # Follow a specific branch's worktree vt follow feature/new-feature # Follow a worktree by path vt follow ~/project-feature # Disable follow mode vt unfollow ``` The `vt follow` command is smart: * From worktree: Always follows the current worktree * From main repo without args: Follows current branch's worktree if it exists * From main repo with args: Can specify branch name or worktree path ### Checking Follow Mode Status ```bash theme={null} # Check current follow mode in git config git config vibetunnel.followBranch # If output shows a branch name, follow mode is enabled for that branch # If no output, follow mode is disabled ``` ### Use Cases * **Agent Development**: Agents work in worktrees while your IDE/Xcode stays on main repo * **Continuous Development**: Keep servers running without restarts when switching features * **Testing**: Make changes in worktree, test immediately in main repo environment * **Parallel Work**: Multiple agents in different worktrees, switch follow mode as needed * **Zero Disruption**: Never close your IDE or restart servers when context switching ## Best Practices ### 1. Naming Conventions Use descriptive branch names that work well as directory names: * ✅ `feature/user-authentication` * ✅ `bugfix/memory-leak` * ❌ `fix/issue#123` (special characters) ### 2. Worktree Organization Keep worktrees organized: ``` ~/projects/ myapp/ # Main repository myapp-feature-auth/ # Feature worktree myapp-bugfix-api/ # Bugfix worktree myapp-release-2.0/ # Release worktree ``` ### 3. Cleanup Regularly clean up unused worktrees: * Remove merged feature branches * Prune worktrees for deleted remote branches * Use `git worktree prune` to clean up references ### 4. Performance * Limit active worktrees to what you're actively working on * Use follow mode judiciously (it triggers branch switches) * Close sessions in unused worktrees to free resources ## Common Workflows ### Quick Start with Follow Mode ```bash theme={null} # Create a worktree for agent development git worktree add ../myproject-feature feature/awesome # From the worktree, enable follow mode cd ../myproject-feature vt follow # Main repo will now track this worktree # Or from the main repo cd ../myproject vt follow ../myproject-feature # Same effect ``` ### Feature Development 1. Create a worktree for your feature branch ```bash theme={null} git worktree add ../project-feature feature/new-ui ``` 2. Enable follow mode ```bash theme={null} # From the worktree cd ../project-feature vt follow # Or from main repo cd ../project vt follow feature/new-ui ``` 3. Agent develops in worktree while you stay in main repo 4. Your IDE and servers automatically see updates 5. Merge and remove worktree when done ### Agent-Assisted Development ```bash theme={null} # Create worktree for agent git worktree add ../project-agent feature/ai-feature # Enable follow mode from main repo vt follow ../project-agent # Agent works in worktree, your main repo stays in sync # Switch branches in worktree? Main repo follows # Commit in worktree? Main repo updates # When done vt unfollow ``` ### Bug Fixes 1. Create worktree from production branch ```bash theme={null} git worktree add ../project-hotfix hotfix/critical-bug ``` 2. Switch to it with follow mode ```bash theme={null} vt follow hotfix/critical-bug ``` 3. Fix the bug and test 4. Cherry-pick to other branches if needed 5. Clean up worktree after merge ### Parallel Development 1. Keep main repo on stable branch with IDE/servers running 2. Create worktrees for different features 3. Use `vt follow ~/project-feature1` to track first feature 4. Switch to `vt follow ~/project-feature2` for second feature 5. Main repo instantly syncs without restarting anything ## Troubleshooting ### "Cannot switch branches due to uncommitted changes" **Problem**: Trying to switch branches with uncommitted work **Solution**: * Commit or stash your changes first * Use a worktree to work on the other branch * VibeTunnel will show a warning and stay on current branch ### "Worktree path already exists" **Problem**: Directory already exists when creating worktree **Solution**: * Choose a different name for your branch * Manually remove the existing directory * Use the `-force` option if appropriate ### "Branch already checked out in another worktree" **Problem**: Git prevents checking out the same branch in multiple worktrees **Solution**: * Use the existing worktree for that branch * Create a new branch from the desired branch * Remove the other worktree if no longer needed ### Worktree Not Showing in List **Problem**: Created worktree doesn't appear in VibeTunnel **Solution**: * Ensure the worktree is within a discoverable path * Check that Git recognizes it: `git worktree list` * Refresh the repository discovery in VibeTunnel ### Follow Mode Not Working **Problem**: Main repository doesn't follow worktree changes **Solution**: * Ensure you enabled follow mode: `git config vibetunnel.followWorktree` * Check hooks are installed in both repos: `ls -la .git/hooks/post-*` * Verify worktree path is correct: `vt status` * Check for uncommitted changes in main repo blocking sync * If you switched branches in main repo, follow mode auto-disabled ## Advanced Topics ### Custom Worktree Locations You can create worktrees in custom locations: ```bash theme={null} # Create in a specific directory git worktree add /custom/path/feature-branch feature/branch # VibeTunnel will still discover and manage it ``` ### Bare Repositories For maximum flexibility, use a bare repository with worktrees: ```bash theme={null} # Clone as bare git clone --bare https://github.com/user/repo.git repo.git # Create worktrees from bare repo git -C repo.git worktree add ../repo-main main git -C repo.git worktree add ../repo-feature feature/branch ``` ### Integration with CI/CD Use worktrees for CI/CD workflows: * Keep a clean worktree for builds * Test multiple branches simultaneously * Isolate deployment branches ## Command Reference ### vt Commands * `vt follow` - Enable follow mode for current branch * `vt follow ` - Switch to branch and enable follow mode * `vt unfollow` - Disable follow mode * `vt git event` - Used internally by Git hooks ### Git Commands * `git worktree add ` - Create a new worktree * `git worktree list` - List all worktrees * `git worktree remove ` - Remove a worktree ### API Reference For detailed API documentation, see the main [API specification](./spec.md#worktree-endpoints). Key endpoints: * `GET /api/worktrees` - List worktrees with current follow mode status * `POST /api/worktrees/follow` - Enable/disable follow mode for a branch * `GET /api/git/follow` - Check follow mode status for a repository * `POST /api/git/event` - Internal endpoint used by git hooks ## Conclusion Git worktrees in VibeTunnel provide a powerful way to manage multiple branches and development tasks. By understanding the branch switching behavior, follow mode, and best practices, you can significantly improve your development workflow. For implementation details and architecture, see the [Worktree Implementation Spec](./worktree-spec.md). # Worktree spec Source: https://docs.vibetunnel.sh/docs/worktree-spec # Git Worktree Implementation Specification This document describes the technical implementation of Git worktree support in VibeTunnel. ## Architecture Overview VibeTunnel's worktree support is built on three main components: 1. **Backend API** - Git operations and worktree management 2. **Frontend UI** - Session creation and worktree visualization 3. **Git Hooks** - Automatic synchronization and follow mode ## Backend Implementation ### Core Services **GitService** (`web/src/server/services/git-service.ts`) * Not implemented as a service, Git operations are embedded in routes * Client-side GitService exists at `web/src/client/services/git-service.ts` **Worktree Routes** (`web/src/server/routes/worktrees.ts`) * `GET /api/worktrees` - List all worktrees with stats and follow mode status * `POST /api/worktrees` - Create new worktree * `DELETE /api/worktrees/:branch` - Remove worktree * `POST /api/worktrees/switch` - Switch branch and enable follow mode * `POST /api/worktrees/follow` - Enable/disable follow mode for a branch **Git Routes** (`web/src/server/routes/git.ts`) * `GET /api/git/repo-info` - Get repository information * `POST /api/git/event` - Process git hook events (internal use) * `GET /api/git/follow` - Check follow mode status for a repository * `GET /api/git/notifications` - Get pending notifications ### Key Functions ```typescript theme={null} // List worktrees with extended information async function listWorktreesWithStats(repoPath: string): Promise // Create worktree with automatic path generation async function createWorktree( repoPath: string, branch: string, path: string, baseBranch?: string ): Promise // Handle branch switching with safety checks async function switchBranch( repoPath: string, branch: string ): Promise ``` ### Git Operations All Git operations use Node.js `child_process.execFile` for security: ```typescript theme={null} import { execFile } from 'node:child_process'; import { promisify } from 'node:util'; const execFileAsync = promisify(execFile); // Execute git commands safely async function execGit(args: string[], options?: { cwd?: string }) { return execFileAsync('git', args, { ...options, timeout: 30000, maxBuffer: 10 * 1024 * 1024, // 10MB }); } ``` ### Follow Mode Implementation Follow mode uses Git hooks and git config for state management: 1. **State Storage**: Git config `vibetunnel.followWorktree` ```bash theme={null} # Follow mode stores the worktree path in main repository git config vibetunnel.followWorktree "/path/to/worktree" # Check follow mode status git config vibetunnel.followWorktree # Disable follow mode git config --unset vibetunnel.followWorktree ``` 2. **Git Hooks**: Installed in BOTH main repo and worktree * `post-checkout`: Detects branch switches * `post-commit`: Detects new commits * `post-merge`: Detects merge operations 3. **Event Processing**: Hooks execute `vt git event` command 4. **Synchronization Logic**: * Worktree events → Main repo syncs (branch, commits, checkouts) * Main repo commits → Worktree syncs (commits only) * Main repo branch switch → Auto-unfollow ## Frontend Implementation ### Components **SessionCreateForm** (`web/src/client/components/session-create-form.ts`) * Branch/worktree selection UI * Smart branch switching logic * Warning displays for conflicts **WorktreeManager** (`web/src/client/components/worktree-manager.ts`) * Dedicated worktree management UI * Follow mode controls * Worktree deletion and branch switching * **Note**: Does not include UI for creating new worktrees ### State Management ```typescript theme={null} // Session creation state @state() private currentBranch: string = ''; @state() private selectedBaseBranch: string = ''; @state() private selectedWorktree?: string; @state() private availableWorktrees: Worktree[] = []; // Branch switching state @state() private branchSwitchWarning?: string; @state() private isLoadingBranches = false; @state() private isLoadingWorktrees = false; ``` ### Branch Selection Logic The new session dialog implements smart branch handling: 1. **No Worktree Selected**: ```typescript theme={null} if (selectedBaseBranch !== currentBranch) { try { await gitService.switchBranch(repoPath, selectedBaseBranch); effectiveBranch = selectedBaseBranch; } catch (error) { // Show warning, use current branch this.branchSwitchWarning = "Cannot switch due to uncommitted changes"; effectiveBranch = currentBranch; } } ``` 2. **Worktree Selected**: ```typescript theme={null} // Use worktree's path and branch effectiveWorkingDir = worktreeInfo.path; effectiveBranch = selectedWorktree; // No branch switching occurs ``` ### UI Updates Dynamic labels based on context: ```typescript theme={null} ${this.selectedWorktree ? 'Base Branch for Worktree:' : 'Switch to Branch:'} ``` Help text explaining behavior: ```typescript theme={null} ${this.selectedWorktree ? 'New worktree branch will be created from this branch' : this.selectedBaseBranch !== this.currentBranch ? `Session will start on ${this.selectedBaseBranch} (currently on ${this.currentBranch})` : `Current branch: ${this.currentBranch}` } ``` ## Git Hook Integration ### Hook Installation Automatic hook installation on repository access: ```typescript theme={null} // Install hooks when checking Git repository async function installGitHooks(repoPath: string): Promise { const hooks = ['post-commit', 'post-checkout']; for (const hook of hooks) { await installHook(repoPath, hook); } } ``` ### Hook Script The hook implementation uses the `vt` command: ```bash theme={null} #!/bin/sh # VibeTunnel Git hook - post-checkout # This hook notifies VibeTunnel when Git events occur # Check if vt command is available if command -v vt >/dev/null 2>&1; then # Run in background to avoid blocking Git operations vt git event & fi # Always exit successfully exit 0 ``` The `vt git event` command: * Sends the repository path to the server via `POST /api/git/event` * Server determines what changed by examining current git state * Triggers branch synchronization if follow mode is enabled * Sends notifications to connected sessions * Runs in background to avoid blocking git operations ### Follow Mode Logic The git event handler determines sync behavior based on event source: ```typescript theme={null} // Get follow mode configuration const followWorktree = await getGitConfig(mainRepoPath, 'vibetunnel.followWorktree'); if (!followWorktree) return; // Follow mode not enabled // Determine if event is from main repo or worktree const eventPath = req.body.repoPath; const isFromWorktree = eventPath === followWorktree; const isFromMain = eventPath === mainRepoPath; if (isFromWorktree) { // Worktree → Main sync switch (event) { case 'checkout': // Sync branch or commit to main const target = req.body.branch || req.body.commit; await execGit(['checkout', target], { cwd: mainRepoPath }); break; case 'commit': case 'merge': // Pull changes to main await execGit(['fetch'], { cwd: mainRepoPath }); await execGit(['merge', 'FETCH_HEAD'], { cwd: mainRepoPath }); break; } } else if (isFromMain) { // Main → Worktree sync switch (event) { case 'checkout': // Branch switch in main = stop following await unsetGitConfig(mainRepoPath, 'vibetunnel.followWorktree'); sendNotification('Follow mode disabled - switched branches in main repository'); break; case 'commit': // Sync commit to worktree await execGit(['fetch'], { cwd: followWorktree }); await execGit(['merge', 'FETCH_HEAD'], { cwd: followWorktree }); break; } } ``` ## Data Models ### Worktree The Worktree interface differs between backend and frontend: **Backend** (`web/src/server/routes/worktrees.ts`): ```typescript theme={null} interface Worktree { path: string; branch: string; HEAD: string; detached: boolean; prunable?: boolean; locked?: boolean; lockedReason?: string; // Extended stats commitsAhead?: number; filesChanged?: number; insertions?: number; deletions?: number; hasUncommittedChanges?: boolean; } ``` **Frontend** (`web/src/client/services/git-service.ts`): ```typescript theme={null} interface Worktree extends BackendWorktree { // UI helpers - added dynamically by routes isMainWorktree?: boolean; isCurrentWorktree?: boolean; } ``` The UI helper fields are computed dynamically in the worktree routes based on the current repository path and are not stored in the backend data model. ### Session with Git Info ```typescript theme={null} interface Session { id: string; name: string; command: string[]; workingDir: string; // Git information (from shared/types.ts) gitRepoPath?: string; gitBranch?: string; gitAheadCount?: number; gitBehindCount?: number; gitHasChanges?: boolean; gitIsWorktree?: boolean; gitMainRepoPath?: string; } ``` ## Error Handling ### Common Errors 1. **Uncommitted Changes** ```typescript theme={null} if (hasUncommittedChanges) { throw new Error('Cannot switch branches with uncommitted changes'); } ``` 2. **Branch Already Checked Out** ```typescript theme={null} // Git automatically prevents this // Error: "fatal: 'branch' is already checked out at '/path/to/worktree'" ``` 3. **Worktree Path Exists** ```typescript theme={null} if (await pathExists(worktreePath)) { throw new Error(`Path already exists: ${worktreePath}`); } ``` ### Error Recovery * Show user-friendly warnings * Fallback to safe defaults * Never lose user work * Log detailed errors for debugging ## Performance Considerations ### Caching * Worktree list cached for 5 seconds * Branch list cached per repository * Git status cached with debouncing ### Optimization ```typescript theme={null} // Parallel operations where possible const [branches, worktrees] = await Promise.all([ loadBranches(repoPath), loadWorktrees(repoPath) ]); // Debounced Git checks this.gitCheckDebounceTimer = setTimeout(() => { this.checkGitRepository(); }, 500); ``` ## Security ### Command Injection Prevention All Git commands use array arguments: ```typescript theme={null} // Safe execFile('git', ['checkout', branchName]) // Never use string concatenation // execFile('git checkout ' + branchName) // DANGEROUS ``` ### Path Validation ```typescript theme={null} // Resolve and validate paths const absolutePath = path.resolve(repoPath); if (!absolutePath.startsWith(allowedBasePath)) { throw new Error('Invalid repository path'); } ``` ## Worktree Creation Currently, worktree creation is handled through terminal commands rather than UI: ```bash theme={null} # Create a new worktree for an existing branch git worktree add ../feature-branch feature-branch # Create a new worktree with a new branch git worktree add -b new-feature ../new-feature main ``` ### UI Support Status 1. **WorktreeManager** (`web/src/client/components/worktree-manager.ts`) * No creation UI, only management of existing worktrees * Provides worktree switching, deletion, and follow mode controls * Shows worktree status (commits ahead, uncommitted changes) 2. **SessionCreateForm** (`web/src/client/components/session-create-form.ts`) * Has worktree creation support through the git-branch-selector component * ✅ Creates worktrees and updates UI state properly * ✅ Selects newly created worktree after creation * ✅ Clears loading states and resets form on completion * ✅ Comprehensive branch name validation * ✅ Specific error messages for common failures * ⚠️ Uses simplistic path generation (repo path + branch slug) * ❌ No path customization UI * ❌ No option to create from specific base branch in UI 3. **Path Generation** (`web/src/client/components/session-create-form/git-utils.ts:100-103`) * Simple approach: `${repoPath}-${branchSlug}` * Branch names sanitized to alphanumeric + hyphens/underscores * No user customization of worktree location ### Missing Features from Spec 1. **Worktree Path Customization** * Current: Auto-generated paths only * Spec: Should allow custom path input * Impact: Users cannot organize worktrees in custom locations 2. **Base Branch Selection in UI** * Current: Uses selected base branch from dropdown * Missing: No explicit UI to choose base branch during worktree creation * Workaround: Select base branch first, then create worktree 3. **Comprehensive E2E Tests** * Unit tests exist: `worktrees.test.ts`, `git-hooks.test.ts` * Integration tests exist: `worktree-workflows.test.ts` * Missing: Full E2E tests for UI worktree creation flow ## Testing ### Unit Tests * `worktrees.test.ts` - Route handlers * `git-hooks.test.ts` - Hook installation * `session-create-form.test.ts` - UI logic ### Integration Tests * `worktree-workflows.test.ts` - Full workflows * `follow-mode.test.ts` - Follow mode scenarios ### E2E Tests * Create worktree via UI * Switch branches with warnings * Follow mode synchronization ## Implementation Summary ### ✅ Fully Implemented 1. **Backend API** - All planned endpoints functional * List, create, delete, switch, follow mode operations * Git hook integration for automatic branch following * Proper error handling and validation 2. **Follow Mode** - Complete implementation * Git config storage (`vibetunnel.followBranch`) * Automatic branch synchronization via hooks * UI controls in WorktreeManager and SessionCreateForm 3. **Basic Worktree Creation** - Functional with recent fixes * Create new worktrees from SessionCreateForm * Branch name validation * UI state management * Error handling with specific messages ### ⚠️ Partially Implemented 1. **Path Generation** - Simplified version only * Auto-generates paths as `${repoPath}-${branchSlug}` * No user customization option * Works for basic use cases 2. **Testing** - Good coverage but missing E2E * Unit tests for routes and utilities * Integration tests for workflows * Missing: Full E2E tests with UI interactions ### ❌ Not Implemented 1. **Advanced Worktree Creation UI** * Custom path input field * Path validation and suggestions * Preview of final worktree location 2. **WorktreeManager Creation UI** * No worktree creation in management view * Must use SessionCreateForm or terminal 3. **Worktree Templates/Presets** * No saved worktree configurations * No quick-create from templates # CLAUDE Source: https://docs.vibetunnel.sh/ios/CLAUDE # CLAUDE.md - iOS App This file provides guidance to Claude Code when working with the iOS companion app for VibeTunnel. ## Project Overview The iOS app is a companion application to VibeTunnel that allows viewing and managing terminal sessions from iOS devices. ## Development Setup 1. Open the project in Xcode: ```bash theme={null} open ios/VibeTunnel-iOS.xcodeproj ``` 2. Select your development team in project settings 3. Build and run on simulator or device ## Architecture * SwiftUI for the user interface * WebSocket client for real-time terminal data * Shared protocol definitions with macOS app ## Key Files * `VibeTunnelApp.swift` - Main app entry point * `ContentView.swift` - Primary UI * `TerminalView.swift` - Terminal display component * `WebSocketClient.swift` - Server communication ## Building ```bash theme={null} # Build for simulator xcodebuild -project VibeTunnel-iOS.xcodeproj -scheme VibeTunnel -sdk iphonesimulator # Build for device xcodebuild -project VibeTunnel-iOS.xcodeproj -scheme VibeTunnel -sdk iphoneos ``` ## Testing ```bash theme={null} # Run tests xcodebuild test -project VibeTunnel-iOS.xcodeproj -scheme VibeTunnel -destination 'platform=iOS Simulator,name=iPhone 15' ``` ## Viewing Logs Use the provided script to view iOS app logs with unredacted private data: ```bash theme={null} # View all logs ./ios/scripts/vtlog.sh # Filter by category ./ios/scripts/vtlog.sh -c NetworkManager # Follow logs in real-time ./ios/scripts/vtlog.sh -f # Search for specific terms ./ios/scripts/vtlog.sh -s "connection" ``` If prompted for password when viewing logs, see [apple/docs/logging-private-fix.md](../apple/docs/logging-private-fix.md) for setup instructions. ## Common Issues ### Simulator Connection Issues * Ensure the Mac app server is running * Check that simulator can reach localhost:4020 * Verify no firewall blocking connections ### Device Testing * Device must be on same network as Mac * Use Mac's IP address instead of localhost * Check network permissions in iOS settings # TestCoverage Source: https://docs.vibetunnel.sh/ios/VibeTunnelTests/TestCoverage # VibeTunnel iOS Test Coverage ## Test Suite Summary The VibeTunnel iOS test suite now includes 93 comprehensive tests covering all critical aspects of the application. ### Test Categories #### 1. **API Error Handling Tests** (✓ All Passing) * Network timeout and connection errors * HTTP status code handling (4xx, 5xx) * Malformed response handling * Unicode and special character support * Retry logic and exponential backoff * Concurrent error scenarios #### 2. **WebSocket Reconnection Tests** (✓ All Passing) * Exponential backoff calculations * Connection state transitions * Message queuing during disconnection * Reconnection with authentication * Circuit breaker pattern * Health monitoring and ping/pong #### 3. **Authentication & Security Tests** (✓ All Passing) * Password validation and hashing * Basic and Bearer token authentication * Session management and timeouts * URL sanitization and validation * Certificate pinning logic * Command injection prevention * Path traversal prevention * Rate limiting implementation * CORS validation #### 4. **File System Operation Tests** (✓ All Passing) * Path normalization and resolution * File permissions handling * Directory traversal and listing * Atomic file writing * File change detection * Sandbox path validation * MIME type detection * Text encoding detection #### 5. **Terminal Data Parsing Tests** (✓ All Passing) * ANSI escape sequence parsing * Color code parsing (16, 256, RGB) * Control character handling * Terminal buffer management * UTF-8 and Unicode handling * Emoji and grapheme clusters * Terminal mode parsing * Binary protocol parsing * Incremental parsing state #### 6. **Edge Case & Boundary Tests** (✓ All Passing) * Empty and nil string handling * Integer overflow/underflow * Floating point edge cases * Empty collections * Large collection performance * Date boundary conditions * URL edge cases * Thread safety boundaries * Memory allocation limits * Character encoding boundaries * JSON encoding special cases #### 7. **Performance & Stress Tests** (✓ All Passing) * String concatenation performance * Collection lookup optimization * Memory allocation stress * Concurrent queue operations * Lock contention scenarios * File I/O stress testing * Sorting algorithm performance * Hash table resize performance * Binary message parsing performance ## Test Infrastructure ### Mock Objects * `MockURLProtocol` - Network request interception * `MockAPIClient` - API client behavior simulation * `MockWebSocketTask` - WebSocket connection mocking ### Test Utilities * `TestFixtures` - Common test data * `TestTags` - Test categorization and filtering ### Test Execution Run all tests: ```bash theme={null} swift test ``` Run specific test categories: ```bash theme={null} swift test --filter .critical swift test --filter .security swift test --filter .performance ``` ## Coverage Highlights * **Network Layer**: Complete coverage of all API endpoints, error scenarios, and edge cases * **WebSocket Protocol**: Full binary protocol parsing and reconnection logic * **Security**: Comprehensive input validation, authentication, and authorization tests * **Performance**: Stress tests ensure the app handles high load scenarios * **Edge Cases**: Extensive boundary testing for all data types and operations ## CI Integration Tests are automatically run on every push via GitHub Actions: * iOS Simulator (iPhone 15, iOS 18.0) * Parallel test execution enabled * Test results uploaded as artifacts on failure ## Future Improvements 1. Add UI snapshot tests (once UI components are implemented) 2. Add integration tests with real server 3. Add fuzz testing for protocol parsing 4. Add memory leak detection tests 5. Add accessibility tests # TestingApproach Source: https://docs.vibetunnel.sh/ios/VibeTunnelTests/TestingApproach # VibeTunnel iOS Testing Approach ## Overview The VibeTunnel iOS project uses a hybrid testing approach due to the separation between the Xcode project (for the app) and Swift Package Manager (for dependencies and tests). ## Test Structure ### 1. Standalone Tests (`StandaloneTests.swift`) These tests verify core concepts and logic without importing the actual app module: * API endpoint construction * JSON encoding/decoding * WebSocket binary protocol * Model validation * Data persistence patterns ### 2. Mock-Based Tests (Future Implementation) The test infrastructure includes comprehensive mocks for when the app code can be properly tested: * `MockAPIClient` - Full API client mock with response configuration * `MockURLProtocol` - Network request interception * `MockWebSocketTask` - WebSocket connection mocking ## Running Tests ### Command Line ```bash theme={null} cd ios swift test # Run all tests swift test --parallel # Run tests in parallel swift test --filter Standalone # Run specific test suite ``` ### CI/CD Tests run automatically in GitHub Actions: 1. Swift tests run using `swift test` 2. iOS app builds separately to ensure compilation ## Test Categories ### Critical Tests (`.tags(.critical)`) * Core API functionality * Connection management * Essential data models ### Networking Tests (`.tags(.networking)`) * HTTP request/response handling * Error scenarios * URL construction ### WebSocket Tests (`.tags(.websocket)`) * Binary protocol parsing * Message handling * Connection lifecycle ### Model Tests (`.tags(.models)`) * Data encoding/decoding * Model validation * Computed properties ### Persistence Tests (`.tags(.persistence)`) * UserDefaults storage * Connection state restoration * Data migration ## Why This Approach? 1. **Xcode Project Limitations**: The iOS app uses an Xcode project which doesn't easily integrate with Swift Testing when running via SPM. 2. **Swift Testing Benefits**: Using the modern Swift Testing framework provides: * Better async/await support * Parallel test execution * Expressive assertions with `#expect` * Tag-based organization 3. **Standalone Tests**: By testing concepts rather than importing the app module directly, we can: * Run tests via SPM * Verify core logic independently * Maintain fast test execution ## Future Improvements 1. **Xcode Test Target**: Add a proper test target to the Xcode project to enable testing of actual app code. 2. **Integration Tests**: Create integration tests that run against a mock server. 3. **UI Tests**: Add XCUITest target for end-to-end testing. 4. **Code Coverage**: Enable coverage reporting once tests can import the app module. ## Adding New Tests 1. Add test functions to `StandaloneTests.swift` or create new test files 2. Use appropriate tags for organization 3. Follow the pattern: ```swift theme={null} @Test("Description of what is being tested") func testFeature() { // Arrange let input = ... // Act let result = ... // Assert #expect(result == expected) } ``` 4. Run tests locally before committing 5. Ensure CI passes # Tailscale guide Source: https://docs.vibetunnel.sh/ios/docs/tailscale-guide # Tailscale Integration Guide for VibeTunnel iOS ## Overview Tailscale integration allows you to securely connect to your VibeTunnel servers from anywhere without complex network configuration. This guide explains how to set up and use Tailscale with the VibeTunnel iOS app. ## What is Tailscale? Tailscale creates a secure, private network (called a tailnet) between your devices using WireGuard encryption. With VibeTunnel's Tailscale integration, you can: * Access your Mac's terminal sessions from anywhere * No port forwarding or firewall configuration needed * Automatic secure connections between devices * Seamless switching between local and remote access ## Prerequisites Before setting up Tailscale in VibeTunnel iOS: 1. **Tailscale Account**: Create a free account at [tailscale.com](https://tailscale.com) 2. **Tailscale on Mac**: Install Tailscale on your Mac running VibeTunnel server 3. **OAuth Credentials**: You'll need to create OAuth client credentials (instructions below) ## Setting Up OAuth Client Credentials VibeTunnel iOS uses OAuth to securely access your Tailscale network. Here's how to create the required credentials: ### Step 1: Access Tailscale Admin Console 1. Sign in to [Tailscale Admin Console](https://login.tailscale.com/admin) 2. Navigate to **Settings** → **OAuth clients** ### Step 2: Generate OAuth Client 1. Click **"Generate OAuth client"** 2. Configure the client: * **Description**: Enter "VibeTunnel iOS" (or any name you prefer) * **Scopes**: Add `devices` scope with **Read** access * This allows the app to discover VibeTunnel servers on your network ### Step 3: Save Your Credentials After creating the client, you'll receive: * **Client ID**: Starts with `k` (e.g., `k4cdcxxxxxxxx`) * **Client Secret**: Starts with `tskey-client-` (e.g., `tskey-client-xxxxxx`) ⚠️ **Important**: Save the Client Secret immediately - it's only shown once! ## Configuring Tailscale in VibeTunnel iOS ### Initial Setup 1. Open VibeTunnel iOS app 2. Go to **Settings** → **Tailscale** 3. Tap **"Configure Tailscale"** 4. Enter your credentials: * Paste your **Client ID** * Paste your **Client Secret** 5. Tap **Save** The app will verify your credentials and begin discovering VibeTunnel servers on your tailnet. ### Connection Status Indicators The Tailscale settings page shows: * 🟢 **Connected**: Successfully connected to Tailscale * 🟠 **Not Connected**: Credentials configured but connection failed * 🔴 **Not Configured**: No credentials set up yet ## Understanding Server Connection Modes VibeTunnel servers can operate in two modes when accessed via Tailscale: ### Public Mode (HTTPS with Tailscale Funnel) When your Mac has Tailscale Funnel enabled: * Server is accessible from the internet * Uses HTTPS with valid SSL certificates * Shows 🔒 **lock icon** next to server URL * Ideal for accessing from anywhere ### Private Mode (HTTP with Tailscale Serve) When using standard Tailscale networking: * Server only accessible within your tailnet * Uses HTTP (HTTPS certificates don't work on mobile) * Shows 🔓 **unlock icon** next to server URL * Perfect for private, secure access ### Automatic Mode Switching The iOS app intelligently handles mode transitions: 1. **On App Launch**: Checks all saved Tailscale servers for current status 2. **Connection Attempts**: Automatically falls back from HTTPS to HTTP if needed 3. **Visual Updates**: Lock/unlock icons update to reflect current connection type 4. **Seamless Experience**: You don't need to manually reconfigure when server modes change ## Using Tailscale Servers ### Discovering Servers With Tailscale configured: 1. The app automatically discovers VibeTunnel servers on your tailnet 2. Found servers appear in the **Discovered Servers** section 3. Tap **Add** to save a server for quick access ### Connecting to Servers 1. Saved Tailscale servers appear in your server list 2. Tap any server card to connect 3. The app will: * Check server availability * Determine best connection method (HTTPS/HTTP) * Establish secure connection * Show authentication prompt if needed ### Connection Features * **Entire Card Tappable**: Tap anywhere on the server card to connect * **Status Indicators**: Visual feedback for connection state * **Error Alerts**: Clear messages if connection fails * **Automatic Retry**: Falls back to HTTP if HTTPS fails ## Settings and Preferences ### Understanding the Three Key Switches #### 1. Auto-Discover Servers (Default: ON) * **What it does**: Enables automatic discovery of VibeTunnel servers on your Tailscale network * **When ON**: The app uses Tailscale API to find servers running VibeTunnel * **When OFF**: Tailscale discovery is disabled, but you can still manually add Tailscale servers * **Important**: This does NOT affect Bonjour discovery - local network discovery continues working #### 2. Prefer Tailscale Connections (Default: OFF) * **What it does**: Chooses Tailscale connection when both local and Tailscale are available * **When ON**: Always uses Tailscale connection if available (useful for consistent remote access) * **When OFF**: Uses the best available connection (typically local when on same network) * **Example**: If you're at home with your Mac, OFF uses local network (faster), ON uses Tailscale (consistent) * **Note**: Does NOT disable Bonjour or local connections - just changes preference #### 3. Auto-Refresh Discovery (Default: ON) * **What it does**: Automatically checks for new/changed servers every 30 seconds * **When ON**: Continuously monitors for new VibeTunnel servers joining your tailnet * **When OFF**: Only discovers servers when you manually refresh or open the app * **Requirement**: Auto-Discover Servers must be ON for this to work * **Battery Impact**: Minimal - uses efficient API polling ### Recommended Settings **For Most Users:** * ✅ Auto-Discover Servers: ON * ❌ Prefer Tailscale Connections: OFF (use local when available, Tailscale when remote) * ✅ Auto-Refresh Discovery: ON **For Always-Remote Access:** * ✅ Auto-Discover Servers: ON * ✅ Prefer Tailscale Connections: ON (consistent experience everywhere) * ✅ Auto-Refresh Discovery: ON **For Battery Saving:** * ✅ Auto-Discover Servers: ON * ❌ Prefer Tailscale Connections: OFF * ❌ Auto-Refresh Discovery: OFF (manually refresh when needed) ### How Discovery Works The app uses **two independent discovery methods**: 1. **Bonjour/mDNS** (Always Active) * Discovers servers on your local network * Works without any configuration * Cannot be disabled (and shouldn't be!) * Shows servers with network icon 2. **Tailscale Discovery** (Configurable) * Discovers servers through Tailscale API * Requires OAuth credentials * Controlled by the three switches above * Shows servers with Tailscale badge Both methods work simultaneously, giving you the best of both worlds! ### Managing Discovered Servers * Discovered servers can be added to your saved servers list * The app remembers which servers you've already added * Servers show their Tailscale hostname and IP address ## Troubleshooting ### Connection Issues **Problem**: Can't connect to server * Verify server is running on your Mac * Check Tailscale is connected on both devices * Ensure OAuth token hasn't expired (auto-refreshes after 1 hour) * Try **Retry Connection** button **Problem**: Authentication errors * The app will prompt for credentials when needed * Credentials are securely stored in iOS Keychain * Re-enter credentials if authentication fails persistently ### Mode Switching Issues **Problem**: Server shows wrong lock icon * The app updates on launch and connection * Pull down to refresh the server list * Icons reflect actual connection capability, not preference **Problem**: HTTPS connection fails * This is normal for private mode * App automatically falls back to HTTP * No action needed - this is handled automatically ### Discovery Problems **Problem**: No servers found * Ensure VibeTunnel server is running on your Mac * Verify Mac has Tailscale installed and connected * Check OAuth client has `devices:read` permission * Tap **Refresh Servers** to manually scan ### Credential Issues **Problem**: "Invalid credentials" error * Client ID must start with `k` * Client Secret must start with `tskey-client-` * Regenerate OAuth client if credentials are lost * Use **Reset Configuration** to start fresh ## Security Considerations ### OAuth Token Management * Access tokens expire after 1 hour * App automatically refreshes tokens using stored credentials * Credentials stored securely in iOS Keychain * Tokens never leave your device ### Connection Security * **Tailscale Funnel (Public)**: End-to-end HTTPS encryption * **Tailscale Network (Private)**: WireGuard VPN encryption * All connections authenticated before establishing * No passwords transmitted over network ### Best Practices 1. **Protect OAuth Credentials**: Never share Client Secret 2. **Regular Updates**: Keep VibeTunnel and Tailscale updated 3. **Monitor Access**: Review connected devices in Tailscale admin 4. **Use Strong Authentication**: Enable 2FA on Tailscale account ## Advanced Features ### Health Checks The app performs automatic health checks: * On app startup for all Tailscale servers * Before each connection attempt * Updates server profiles with current capabilities * Only applies to Tailscale-discovered servers ### Fallback Logic Smart connection fallback: 1. Try HTTPS if server reports it's available 2. Fall back to HTTP if HTTPS fails 3. Show appropriate visual indicators 4. Remember successful connection method ### Server Profile Management * Tailscale servers marked with special flag * Health checks only run for Tailscale servers * Bonjour and manually added servers unaffected * Profiles automatically update when server capabilities change ## Resetting Tailscale Configuration If you need to start over: 1. Go to **Settings** → **Tailscale** 2. Scroll to **Danger Zone** 3. Tap **Reset Tailscale Configuration** 4. Confirm the reset This will: * Remove stored OAuth credentials * Clear all discovered servers * Reset preferences to defaults * Require re-entering credentials ## Frequently Asked Questions **Q: Why does my server sometimes show a lock and sometimes not?** A: The lock icon indicates HTTPS availability. It changes based on whether Tailscale Funnel is enabled on your Mac. **Q: Do I need the Tailscale app on my iPhone?** A: No, VibeTunnel iOS handles everything through the OAuth API. The Tailscale iOS app is not required. **Q: Can I use Tailscale and local network discovery together?** A: Yes! The app supports both Tailscale and Bonjour discovery simultaneously. **Q: Is my terminal data encrypted?** A: Yes, all connections use either HTTPS (Funnel) or WireGuard VPN encryption (Tailscale network). **Q: What happens if my OAuth token expires?** A: The app automatically refreshes tokens using your stored credentials. You'll only need to re-enter credentials if they become invalid. ## Getting Help If you encounter issues not covered in this guide: 1. Check the VibeTunnel Mac app is running 2. Verify Tailscale status on both devices 3. Review error messages in the app 4. Check server logs using `vtlog.sh` on your Mac For additional support: * VibeTunnel Issues: [GitHub Issues](https://github.com/anthropics/vibetunnel/issues) * Tailscale Documentation: [tailscale.com/kb](https://tailscale.com/kb) ## Summary Tailscale integration makes VibeTunnel incredibly powerful for remote access: * **Simple Setup**: Just OAuth credentials, no network configuration * **Automatic Discovery**: Finds your servers instantly * **Smart Connections**: Handles HTTPS/HTTP automatically * **Secure Access**: Enterprise-grade encryption * **Seamless Experience**: Works like magic With this setup, your terminal sessions are securely accessible from anywhere, whether you're on your local network, at a coffee shop, or traveling abroad. The app handles all the complexity, so you can focus on your work. # CLAUDE Source: https://docs.vibetunnel.sh/mac/CLAUDE # CLAUDE.md for macOS Development ## SwiftUI Development Guidelines * Aim to build all functionality using SwiftUI unless there is a feature that is only supported in AppKit. * Design UI in a way that is idiomatic for the macOS platform and follows Apple Human Interface Guidelines. * Use SF Symbols for iconography. * Use the most modern macOS APIs. Since there is no backward compatibility constraint, this app can target the latest macOS version with the newest APIs. * Use the most modern Swift language features and conventions. Target Swift 6 and use Swift concurrency (async/await, actors) and Swift macros where applicable. ## Logging Guidelines **IMPORTANT**: Never use `print()` statements in production code. Always use the unified logging system with proper Logger instances. ### Setting up Loggers Each Swift file should declare its own logger at the top of the file: ```swift theme={null} import os.log private let logger = Logger(subsystem: "sh.vibetunnel.vibetunnel", category: "CategoryName") ``` ### Log Levels Choose the appropriate log level based on context: * **`.debug`** - Detailed information useful only during development/debugging ```swift theme={null} logger.debug("Detailed state: \(internalState)") ``` * **`.info`** - General informational messages about normal app flow ```swift theme={null} logger.info("Session created with ID: \(sessionID)") ``` * **`.notice`** - Important events that are part of normal operation ```swift theme={null} logger.notice("User authenticated successfully") ``` * **`.warning`** - Warnings about potential issues that don't prevent operation ```swift theme={null} logger.warning("Failed to cache data, continuing without cache") ``` * **`.error`** - Errors that indicate failure but app can continue ```swift theme={null} logger.error("Failed to load preferences: \(error)") ``` * **`.fault`** - Critical errors that indicate programming mistakes or system failures ```swift theme={null} logger.fault("Unexpected nil value in required configuration") ``` ### Common Patterns ```swift theme={null} // Instead of: print("🔍 [GitRepositoryMonitor] findRepository called for: \(filePath)") // Use: logger.info("🔍 findRepository called for: \(filePath)") // Instead of: print("❌ [GitRepositoryMonitor] Failed to get git status: \(error)") // Use: logger.error("❌ Failed to get git status: \(error)") ``` ### Benefits * Logs are automatically categorized and searchable with `vtlog` * Performance optimized (debug logs compiled out in release builds) * Privacy-aware (use `\(value, privacy: .public)` when needed) * Integrates with Console.app and system log tools * Consistent format across the entire codebase ## Important Build Instructions ### Xcode Build Process **CRITICAL**: When you build the Mac app with Xcode (using XcodeBuildMCP or manually), it automatically builds the web server as part of the build process. The Xcode build scripts handle: * Building the TypeScript/Node.js server * Bundling all web assets * Creating the native executable * Embedding everything into the Mac app bundle **DO NOT manually run `pnpm run build` in the web directory when building the Mac app** - this is redundant and wastes time. ### Always Use Subtasks **IMPORTANT**: Always use the Task tool for operations, not just when hitting context limits: * For ANY command that might generate output (builds, logs, file reads) * For parallel operations (checking multiple files, running searches) * For exploratory work (finding implementations, debugging) * This keeps the main context clean and allows better organization Examples: ``` # Instead of: pnpm run build Task(description="Build web bundle", prompt="Run pnpm run build in the web directory and report if it succeeded or any errors") # Instead of: ./scripts/vtlog.sh -n 100 Task(description="Check VibeTunnel logs", prompt="Run ./scripts/vtlog.sh -n 100 and summarize any errors or warnings") # Instead of: multiple file reads Task(description="Analyze WebRTC implementation", prompt="Read WebRTCManager.swift and webrtc-handler.ts, then explain the offer/answer flow") ``` ## VibeTunnel Architecture Overview VibeTunnel is a macOS application that provides terminal access through web browsers. It consists of three main components: ### 1. Mac App (Swift/SwiftUI) * Native macOS application that manages the entire system * Spawns and manages the Bun/Node.js server process * Handles terminal creation and management * Provides system tray UI and settings ### 2. Web Server (Node.js) * Runs on **localhost:4020** by default * Serves the web frontend * Manages WebSocket connections for terminal I/O * Handles API requests and session management * Routes logs from the frontend to the Mac app ### 3. Web Frontend (TypeScript/LitElement) * Browser-based terminal interface * Connects to the server via WebSocket * Uses ghostty-web for terminal rendering * Sends logs back to server for centralized logging ## Logging Architecture VibeTunnel has a sophisticated logging system that aggregates logs from all components: ### Log Flow ``` Frontend (Browser) → Server (Bun) → Mac App → macOS Unified Logging [module] [CLIENT:module] ServerOutput category ``` ### Log Prefixing System To help identify where logs originate, the system uses these prefixes: 1. **Frontend Logs**: * Browser console: `[module-name] message` * When forwarded to server: `[CLIENT:module-name] message` 2. **Server Logs**: * Direct server logs: `[module-name] message` * No additional prefix needed 3. **Mac App Logs**: * Native Swift logs: Use specific categories (ServerManager, SessionService, etc.) * Server output: All captured under "ServerOutput" category ### Understanding Log Sources When viewing logs with `vtlog`, you can identify the source: * `[CLIENT:*]` - Originated from web frontend * `[server]`, `[api]`, etc. - Server-side modules * Category-based logs - Native Mac app components ## Debugging and Logging The VibeTunnel Mac app uses the unified logging system with the subsystem `sh.vibetunnel.vibetunnel`. We provide a convenient `vtlog` script to simplify log access. ### Quick Start with vtlog The `vtlog` script is located at `scripts/vtlog.sh`. It's designed to be context-friendly by default. **Default behavior: Shows last 50 lines from the past 5 minutes** ```bash theme={null} # Show recent logs (default: last 50 lines from past 5 minutes) ./scripts/vtlog.sh # Stream logs continuously (like tail -f) ./scripts/vtlog.sh -f # Show only errors ./scripts/vtlog.sh -e # Show more lines ./scripts/vtlog.sh -n 100 # View logs from different time range ./scripts/vtlog.sh -l 30m # Filter by category ./scripts/vtlog.sh -c ServerManager # Search for specific text ./scripts/vtlog.sh -s "connection failed" ``` ### Common Use Cases ```bash theme={null} # Quick check for recent errors (context-friendly) ./scripts/vtlog.sh -e # Debug server issues ./scripts/vtlog.sh --server -e # Watch logs in real-time ./scripts/vtlog.sh -f # Debug screen capture with more context ./scripts/vtlog.sh -c ScreencapService -n 100 # Find authentication problems in last 2 hours ./scripts/vtlog.sh -s "auth" -l 2h # Export comprehensive debug logs ./scripts/vtlog.sh -d -l 1h --all -o ~/Desktop/debug.log # Get all logs without tail limit ./scripts/vtlog.sh --all ``` ### Available Categories * **ServerManager** - Server lifecycle and configuration * **SessionService** - Terminal session management * **TerminalManager** - Terminal spawning and control * **GitRepository** - Git integration features * **ScreencapService** - Screen capture functionality * **WebRTCManager** - WebRTC connections * **UnixSocket** - Unix socket communication * **WindowTracker** - Window tracking and focus * **NgrokService** - Ngrok tunnel management * **ServerOutput** - Node.js server output (includes frontend logs) ### Manual Log Commands If you prefer using the native `log` command directly: ```bash theme={null} # Stream logs log stream --predicate 'subsystem == "sh.vibetunnel.vibetunnel"' --level info # Show historical logs log show --predicate 'subsystem == "sh.vibetunnel.vibetunnel"' --info --last 30m # Filter by category log stream --predicate 'subsystem == "sh.vibetunnel.vibetunnel" AND category == "ServerManager"' ``` ### Tips * Run `./scripts/vtlog.sh --help` for full documentation * Use `-d` flag for debug-level logs during development * The app logs persist after the app quits, useful for crash debugging * Add `--json` for machine-readable output * Server logs (Node.js output) are under the "ServerOutput" category * Look for `[CLIENT:*]` prefix to identify frontend-originated logs ## XcodeBuildMCP Usage Guide XcodeBuildMCP is an MCP (Model Context Protocol) server that provides comprehensive Xcode build and automation capabilities. It's the recommended way to build, test, and manage the VibeTunnel macOS project. ### Installation If XcodeBuildMCP is not already installed, add it to Claude Code: ```bash theme={null} claude mcp add XcodeBuildMCP -- npx -y xcodebuildmcp@latest ``` ### Common XcodeBuildMCP Commands for VibeTunnel #### Project Discovery ``` # Find Xcode projects in the repository discover_projs(workspaceRoot: "/Users/steipete/Projects/vibetunnel") # List available schemes list_schems_proj(projectPath: "/Users/steipete/Projects/vibetunnel/mac/VibeTunnel-Mac.xcodeproj") ``` #### Building the Mac App ``` # Build for Debug configuration build_mac_proj( projectPath: "/Users/steipete/Projects/vibetunnel/mac/VibeTunnel-Mac.xcodeproj", scheme: "VibeTunnel-Mac", configuration: "Debug" ) # Build for Release configuration build_mac_proj( projectPath: "/Users/steipete/Projects/vibetunnel/mac/VibeTunnel-Mac.xcodeproj", scheme: "VibeTunnel-Mac", configuration: "Release" ) # Build with code signing build_mac_proj( projectPath: "/Users/steipete/Projects/vibetunnel/mac/VibeTunnel-Mac.xcodeproj", scheme: "VibeTunnel-Mac", configuration: "Release", extraArgs: ["CODE_SIGN_IDENTITY=Developer ID Application"] ) ``` #### Running the App ``` # Build and run in one step build_run_mac_proj( projectPath: "/Users/steipete/Projects/vibetunnel/mac/VibeTunnel-Mac.xcodeproj", scheme: "VibeTunnel-Mac" ) # Get the app bundle path after building get_mac_app_path_proj( projectPath: "/Users/steipete/Projects/vibetunnel/mac/VibeTunnel-Mac.xcodeproj", scheme: "VibeTunnel-Mac" ) # Get bundle identifier get_mac_bundle_id(appPath: "/path/to/VibeTunnel.app") # Launch the app launch_mac_app(appPath: "/path/to/VibeTunnel.app") ``` #### Testing ``` # Run all tests test_macos_proj( projectPath: "/Users/steipete/Projects/vibetunnel/mac/VibeTunnel-Mac.xcodeproj", scheme: "VibeTunnel-Mac" ) # Run tests with specific configuration test_macos_proj( projectPath: "/Users/steipete/Projects/vibetunnel/mac/VibeTunnel-Mac.xcodeproj", scheme: "VibeTunnel-Mac", configuration: "Debug" ) ``` #### Cleaning ``` # Clean build artifacts clean_proj( projectPath: "/Users/steipete/Projects/vibetunnel/mac/VibeTunnel-Mac.xcodeproj", scheme: "VibeTunnel-Mac" ) ``` #### Build Settings ``` # Show build settings show_build_set_proj( projectPath: "/Users/steipete/Projects/vibetunnel/mac/VibeTunnel-Mac.xcodeproj", scheme: "VibeTunnel-Mac" ) ``` ### Tips for Using XcodeBuildMCP 1. **Always use full paths**: XcodeBuildMCP requires absolute paths for project files 2. **Check schemes first**: Use `list_schems_proj` to verify available schemes 3. **Use proper configuration**: Debug for development, Release for distribution 4. **Handle build failures**: If builds fail, check the error output and use `clean_proj` if needed 5. **Incremental builds**: XcodeBuildMCP supports incremental builds by default for faster iteration ### Common Workflows #### Development Build & Run ``` # Clean, build, and run for development clean_proj(projectPath: "/Users/steipete/Projects/vibetunnel/mac/VibeTunnel-Mac.xcodeproj", scheme: "VibeTunnel-Mac") build_run_mac_proj(projectPath: "/Users/steipete/Projects/vibetunnel/mac/VibeTunnel-Mac.xcodeproj", scheme: "VibeTunnel-Mac") ``` #### Release Build ``` # Build optimized release version build_mac_proj( projectPath: "/Users/steipete/Projects/vibetunnel/mac/VibeTunnel-Mac.xcodeproj", scheme: "VibeTunnel-Mac", configuration: "Release", extraArgs: ["ENABLE_HARDENED_RUNTIME=YES"] ) ``` #### CI Build ``` # Build with derived data path for CI build_mac_proj( projectPath: "/Users/steipete/Projects/vibetunnel/mac/VibeTunnel-Mac.xcodeproj", scheme: "VibeTunnel-Mac", configuration: "Release", derivedDataPath: "/tmp/VibeTunnel-DerivedData" ) ``` ## Testing ### Running macOS Tests **IMPORTANT**: macOS tests MUST be run using XcodeBuildMCP commands or xcodebuild, NOT with `swift test`: #### Why `swift test` Fails 1. **Missing Server Binary**: The `vibetunnel` SEA (Single Executable Application) binary is only created during the full Xcode build process. Tests expect this binary to be embedded in the app bundle's Resources folder. 2. **No Proper App Bundle**: `swift test` runs in a minimal test bundle environment without the full app structure 3. **UserNotifications Framework**: Tests using UserNotifications will crash due to missing bundle configuration 4. **Missing Build Phases**: The web frontend build and SEA creation only happen during Xcode builds #### Correct Testing Methods **Using XcodeBuildMCP (Recommended):** ``` test_macos_proj( projectPath: "/Users/steipete/Projects/vibetunnel/mac/VibeTunnel-Mac.xcodeproj", scheme: "VibeTunnel-Mac" ) ``` **Using xcodebuild directly:** ```bash theme={null} cd mac xcodebuild test \ -project VibeTunnel-Mac.xcodeproj \ -scheme VibeTunnel-Mac \ -destination 'platform=macOS' ``` **NEVER use:** ```bash theme={null} swift test # This will fail with missing server binary and framework issues ``` The test suite includes checks for the embedded server binary (`ServerBinaryAvailableCondition`) which correctly fail when the binary isn't present, preventing tests from running in an incomplete environment. ### Testing the Web Interface The VibeTunnel server runs on localhost:4020 by default. To test the web interface: 1. Ensure the Mac app is running. The user does that. Do not start the mac app yourself! 2. Access [http://localhost:4020](http://localhost:4020) in your browser 3. Use Playwright MCP for automated testing: ``` # Example: Navigate to the interface # Take screenshots # Interact with terminal sessions ``` ## Key Implementation Details ### Server Process Management * The Mac app spawns the Bun server using `BunServer.swift` * Server logs are captured and forwarded to macOS logging system * Process lifecycle is tied to the Mac app lifecycle ### Log Aggregation * All logs flow through the Mac app for centralized access * Use `vtlog` to see logs from all components in one place * Frontend errors are particularly useful for debugging UI issues ### Development Workflow 1. Use XcodeBuildMCP for Swift changes 2. The web frontend auto-reloads on changes (when `pnpm run dev` is running) 3. Use Playwright MCP to test integration between components 4. Monitor all logs with `vtlog -f` during development ## Tailscale Integration Notes ### Known Issue: Tailscale IP Command The `tailscale ip -4` command may return error messages instead of an IP address when the Tailscale GUI has issues starting. Always validate the output is a valid IPv4 address (4 numbers between 0-255 separated by dots) before using it. The TailscaleURLHelper includes validation and fallback to hostname when IP retrieval fails. ## Unix Socket Communication Protocol ### Type Synchronization Between Mac and Web When implementing new Unix socket message types between the Mac app and web server, it's essential to maintain type safety on both sides: 1. **Mac Side**: Define message types in Swift (typically in `ControlProtocol.swift` or related files) 2. **Web Side**: Create corresponding TypeScript interfaces in `web/src/shared/types.ts` 3. **Keep Types in Sync**: Whenever you add or modify Unix socket messages, update the types on both platforms to ensure type safety and prevent runtime errors Example workflow: * Add new message type to `ControlProtocol.swift` (Mac) * Add corresponding interface to `types.ts` (Web) * Update handlers on both sides to use the typed messages * This prevents bugs from mismatched message formats and makes the protocol self-documenting # BuildArchitectures Source: https://docs.vibetunnel.sh/mac/docs/BuildArchitectures # Building VibeTunnel for Different Architectures ## Overview VibeTunnel now supports building separate binaries for arm64 (Apple Silicon) and x86\_64 (Intel) architectures. This allows for optimized builds for each platform while maintaining smaller download sizes compared to universal binaries. ## Local Development ### Building for a Specific Architecture ```bash theme={null} # Build for arm64 (Apple Silicon) ./scripts/build.sh --configuration Release --arch arm64 # Build for Intel ./scripts/build.sh --configuration Release --arch x86_64 # Build for native architecture (default) ./scripts/build.sh --configuration Release ``` ### Creating Distribution Packages The packaging scripts automatically detect the architecture from the built app: ```bash theme={null} # Create DMG (architecture is auto-detected) ./scripts/create-dmg.sh build/Build/Products/Release/VibeTunnel.app # Create ZIP (architecture is auto-detected) ./scripts/create-zip.sh build/Build/Products/Release/VibeTunnel.app ``` ## Release Builds The release workflow (`release.yml`) automatically: 1. Builds separate binaries for arm64 and x86\_64 2. Creates DMG and ZIP files for each architecture 3. Names files according to the pattern: `VibeTunnel--.` ### Release Artifacts Each release produces 4 distribution files: * `VibeTunnel--arm64.dmg` - Apple Silicon DMG installer * `VibeTunnel--arm64.zip` - Apple Silicon ZIP archive * `VibeTunnel--intel.dmg` - Intel DMG installer * `VibeTunnel--intel.zip` - Intel ZIP archive ## Architecture Detection The packaging scripts use `lipo -info` to detect the architecture of the built binary and automatically append the appropriate suffix to the filename. ## Bun Executable The Bun executable is also built architecture-specifically: ```bash theme={null} # Build for arm64 cd web node build-native.js --arch arm64 # Build for x64 (Intel) node build-native.js --arch x64 ``` The build process automatically passes the correct Bun target: * arm64 → `bun-darwin-aarch64` * x64 → `bun-darwin-x64` # BuildRequirements Source: https://docs.vibetunnel.sh/mac/docs/BuildRequirements # Build Requirements The macOS build combines the Swift app, web server, and Rust native forwarder. ## Requirements * **macOS**: 14.0 or later on Apple Silicon * **Xcode**: 16.0 or later with command line tools * **Node.js**: 22.12 through 24.x * **pnpm**: Use the repository-pinned version through Corepack * **Rustup**: `native/vt-fwd/rust-toolchain.toml` pins Rust 1.97.0 plus rustfmt and Clippy * **Internet connection**: Required for the first dependency and toolchain download ## Build Process When you build VibeTunnel in Xcode for the first time: 1. **Install Build Dependencies** prepares the supported Node.js and repository-pinned pnpm environment. 2. **Build Web Frontend** installs the pinned JavaScript dependencies and runs `pnpm build`. That build invokes Cargo in `native/vt-fwd`, then copies the server, browser assets, and `vibetunnel-fwd` into the app resources. 3. Xcode compiles the Swift application and packages the resources produced by the web build phase. There is no separate native-forwarder build phase. ## Benefits * **Pinned native toolchain** - Rust version and components come from `native/vt-fwd/rust-toolchain.toml` * **Integrated packaging** - Xcode receives the server and forwarder through one build pipeline * **Cached builds** - JavaScript, Cargo, and Xcode reuse their normal build caches ## Troubleshooting If the build fails: 1. Check internet connection (required for first build) 2. Run `rustup show` from `native/vt-fwd` if Cargo cannot select the pinned toolchain 3. Verify the repository-pinned pnpm is active with `pnpm --version` 4. Check Console.app for detailed error messages ## Clean Build To perform a completely clean build: ```bash theme={null} cd mac rm -rf .build-tools/ rm -rf ../web/node_modules/ rm -rf ../native/vt-fwd/target/ # Then build in Xcode ``` # RELEASE GUIDE Source: https://docs.vibetunnel.sh/mac/docs/RELEASE_GUIDE # VibeTunnel Release Guide - Quick Reference This guide provides a streamlined release process based on lessons learned from beta.13. ## 🚀 Quick Release Commands ```bash theme={null} # 1. Pre-release health check ./scripts/release-health-check.sh # 2. Set environment variables export SPARKLE_ACCOUNT="VibeTunnel" export CI=false export SKIP_NODE_CHECK=false # 3. Run the release ./scripts/release.sh beta 14 # For next beta # 4. Monitor progress in another terminal ./scripts/release-progress.sh # 5. If interrupted, resume ./scripts/release.sh --resume ``` ## 📋 Pre-Release Checklist Before starting any release: ### 1. Version Numbers * [ ] Update `mac/VibeTunnel/version.xcconfig`: * `MARKETING_VERSION = 1.0.0-beta.14` * `CURRENT_PROJECT_VERSION = 203` (increment from last) * [ ] Update `web/package.json` to match * [ ] Update `web/package.npm.json` to match * [ ] Update `CHANGELOG.md` with release notes ### 2. Environment Setup * Install rustup; `native/vt-fwd/rust-toolchain.toml` pins the native forwarder toolchain used by release builds. ```bash theme={null} # Required environment variables export SPARKLE_ACCOUNT="VibeTunnel" export CI=false export SKIP_NODE_CHECK=false # Notarization credentials (if not already set) export APP_STORE_CONNECT_KEY_ID="your_key_id" export APP_STORE_CONNECT_ISSUER_ID="your_issuer_id" export APP_STORE_CONNECT_API_KEY_P8="-----BEGIN PRIVATE KEY----- your_private_key_content -----END PRIVATE KEY-----" ``` ### 3. Clean State ```bash theme={null} # Ensure clean git state git status # Should be clean git pull --rebase origin main # Run health check ./scripts/release-health-check.sh ``` ## 🔧 Troubleshooting ### Node.js Detection Issues If the build fails with "Node.js is required": ```bash theme={null} # Test Node.js detection ./scripts/check-node-simple.sh # If using nvm, ensure it's loaded source ~/.nvm/nvm.sh nvm use 20 # or your version # Create symlinks if needed (requires sudo) sudo ln -s $(which node) /usr/local/bin/node sudo ln -s $(which pnpm) /usr/local/bin/pnpm ``` ### Release Script Timeouts The release process can take 20-30 minutes: * Build: 2-5 minutes * Notarization: 5-15 minutes * DMG creation: 1-2 minutes If it times out: ```bash theme={null} # Check current status ./scripts/release-progress.sh # Resume from last step ./scripts/release.sh --resume ``` ### Manual Recovery If the release script fails after notarization: ```bash theme={null} # 1. Create GitHub release manually ./scripts/generate-release-notes.sh 1.0.0-beta.14 > notes.md gh release create "v1.0.0-beta.14" \ --title "VibeTunnel 1.0.0-beta.14" \ --notes-file notes.md \ --prerelease \ build/VibeTunnel-*.dmg \ build/VibeTunnel-*.zip # 2. Sign DMG for Sparkle sign_update -f private/sparkle_ed_private_key \ build/VibeTunnel-1.0.0-beta.14.dmg \ --account VibeTunnel # 3. Update appcast manually # Add the signature to appcast-prerelease.xml # Then commit and push git add ../appcast-prerelease.xml git commit -m "Update appcast for v1.0.0-beta.14" git push ``` ## 📊 New Tools ### Release Progress Monitor Shows real-time release progress with visual indicators: ```bash theme={null} ./scripts/release-progress.sh ``` Features: * Step-by-step progress tracking * Duration for each step * Idle time warnings * Artifact status ### Release Health Check Comprehensive pre-release validation: ```bash theme={null} ./scripts/release-health-check.sh ``` Checks: * Git status and branch * Environment variables * Build tools availability * Code signing certificates * Version synchronization * Disk space * Appcast validity ### Simplified Node.js Check More robust Node.js detection: ```bash theme={null} ./scripts/check-node-simple.sh ``` ## 🎯 Best Practices 1. **Always run health check first** - Catches issues before they cause failures 2. **Use progress monitor** - Keep track of long-running operations 3. **Set all environment variables** - Prevents mid-release failures 4. **Keep versions synchronized** - Update all version files before starting 5. **Document in CHANGELOG.md** - Required for release notes generation 6. **Don't run in background** - Use screen/tmux if needed, but keep foreground ## 📝 Version Management ### Version Files to Update 1. `mac/VibeTunnel/version.xcconfig` - Source of truth 2. `web/package.json` - Must match macOS version 3. `web/package.npm.json` - For npm package release 4. `CHANGELOG.md` - Release notes ### Build Number Rules * Must increment for EVERY release * Must be unique across all releases * Sparkle uses build numbers, not version strings * Check existing: `grep '' ../appcast*.xml` ## 🚨 Common Issues ### "Uncommitted changes detected" * Commit all changes before releasing * Or stash temporarily: `git stash` ### "Build number already exists" * Increment CURRENT\_PROJECT\_VERSION in version.xcconfig * Must be higher than all previous releases ### "Version mismatch" * Ensure web/package.json matches mac version * Run health check to verify ### DMG stuck volumes ```bash theme={null} # List stuck volumes ls /Volumes/VibeTunnel* # Force unmount for vol in /Volumes/VibeTunnel*; do hdiutil detach "$vol" -force done ``` ## 📚 Summary The improved release process provides: * Better error handling with environment variable defaults * Visual progress tracking * Comprehensive pre-flight validation * Clear recovery procedures * Simplified troubleshooting For the smoothest release experience: 1. Run health check 2. Set environment variables 3. Use the standard release script 4. Monitor with progress tool 5. Resume if interrupted The release should complete in 20-30 minutes with clear visibility into each step. # Code signing Source: https://docs.vibetunnel.sh/mac/docs/code-signing # Code Signing Guide for VibeTunnel This comprehensive guide covers all aspects of code signing for VibeTunnel, from local development setup to release distribution. ## Table of Contents 1. [Development Setup](#development-setup) 2. [Release Signing & Notarization](#release-signing--notarization) 3. [Troubleshooting](#troubleshooting) 4. [Reference](#reference) ## Development Setup ### Initial Team Configuration VibeTunnel uses xcconfig files to manage developer team settings, allowing multiple developers to work without code signing conflicts. 1. **Copy the template file to create your local configuration:** ```bash theme={null} cp ../apple/Local.xcconfig.template ../apple/Local.xcconfig ``` 2. **Edit `../apple/Local.xcconfig` and add your development team ID:** ``` DEVELOPMENT_TEAM = YOUR_TEAM_ID_HERE ``` **Finding your team ID in Xcode:** * Open Xcode → Settings (or Preferences) * Go to Accounts tab * Select your Apple ID * Look for your Team ID in the team details 3. **Open the project in Xcode** - it will now use your personal development team automatically. ### How xcconfig Works * `VibeTunnel/Shared.xcconfig` - Contains shared configuration and includes local settings * `../apple/Local.xcconfig` - Your personal settings (ignored by git) * `../apple/Local.xcconfig.template` - Template for new developers ### Avoiding Keychain Dialogs During Development VibeTunnel stores dashboard passwords in the keychain, which can trigger repeated authorization dialogs during development. #### Debug Mode Behavior In DEBUG builds, the app automatically skips keychain reads to avoid dialogs: * **Password Setting**: You can set passwords during the current session * **Session Persistence**: Passwords work normally until app restart * **No Persistence**: Passwords are "forgotten" on restart (not read from keychain) * **No Dialogs**: Prevents keychain authorization dialogs during development When setting a password in debug mode, you'll see: ``` Debug mode: Password saved to keychain but will not persist across app restarts. The password will only be available during this session to avoid keychain authorization dialogs during development. ``` #### Testing Password Persistence To test actual password persistence: 1. **Build in Release mode**: * Product → Scheme → Edit Scheme → Run → Build Configuration → Release 2. **Use Archive build**: * Product → Archive (always uses Release configuration) ## Release Signing & Notarization ### Prerequisites 1. **Apple Developer Program membership** (\$99/year) 2. **Developer ID Application certificate** in your Keychain 3. **App Store Connect API key** for notarization ### Setting Up Developer ID Certificate 1. Go to [Apple Developer Portal](https://developer.apple.com/account/resources/certificates/list) 2. Create a new certificate → Developer ID → Developer ID Application 3. Download and install the certificate in your Keychain ### Environment Variables Create a `.env` file in the project root (gitignored): ```bash theme={null} # Optional: Specify signing identity (otherwise uses first Developer ID found) SIGN_IDENTITY="Developer ID Application: Your Name (TEAM123456)" # App Store Connect API Key for notarization APP_STORE_CONNECT_API_KEY_P8="-----BEGIN PRIVATE KEY----- MIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQg... -----END PRIVATE KEY-----" APP_STORE_CONNECT_KEY_ID="ABC123DEF4" APP_STORE_CONNECT_ISSUER_ID="12345678-1234-1234-1234-123456789012" ``` ### Creating App Store Connect API Key 1. Go to [App Store Connect](https://appstoreconnect.apple.com/access/api) 2. Click "Generate API Key" 3. Set role to "Developer" 4. Download the `.p8` file 5. Note the Key ID and Issuer ID ### Usage #### Sign Only (for development) ```bash theme={null} ./scripts/sign-and-notarize.sh --sign-only ``` #### Sign and Notarize (for distribution) ```bash theme={null} ./scripts/sign-and-notarize.sh --sign-and-notarize ``` #### Individual Scripts ```bash theme={null} # Just code signing ./scripts/codesign-app.sh build/Build/Products/Release/VibeTunnel.app # Just notarization (requires signed app) ./scripts/notarize-app.sh build/Build/Products/Release/VibeTunnel.app ``` ### Script Options ```bash theme={null} # Show help ./scripts/sign-and-notarize.sh --help # Sign and notarize with custom app path ./scripts/sign-and-notarize.sh --app-path path/to/VibeTunnel.app --sign-and-notarize # Skip stapling (for CI environments) ./scripts/sign-and-notarize.sh --sign-and-notarize --skip-staple # Don't create ZIP archive ./scripts/sign-and-notarize.sh --sign-and-notarize --no-zip # Verbose output for debugging ./scripts/sign-and-notarize.sh --sign-and-notarize --verbose ``` ### CI/CD Setup (GitHub Actions) Add these secrets to your GitHub repository: 1. `APP_STORE_CONNECT_API_KEY_P8` - The complete .p8 key content 2. `APP_STORE_CONNECT_KEY_ID` - The Key ID 3. `APP_STORE_CONNECT_ISSUER_ID` - The Issuer ID The CI workflow automatically uses these for notarization when building on the main branch. ## Troubleshooting ### Development Issues #### xcconfig Not Working * Ensure `../apple/Local.xcconfig` exists * Check that the file isn't committed to git * Verify the DEVELOPMENT\_TEAM value is correct #### Keychain Dialogs Still Appearing * Verify you're running in Debug configuration * Check `DashboardKeychain.swift` implementation * Ensure you're not in Release mode ### Code Signing Issues #### "No signing identity found" * Install Developer ID Application certificate * Check with: `security find-identity -v -p codesigning` #### "User interaction is not allowed" * Unlock keychain: `security unlock-keychain` * Or use: `security unlock-keychain -p login.keychain` ### Notarization Issues #### "Invalid API key" * Verify API key content, ID, and Issuer ID * Ensure .p8 key includes BEGIN/END lines #### "App bundle not eligible for notarization" * Ensure proper code signing with hardened runtime * Check entitlements configuration #### "Notarization failed" * Script shows detailed error messages * Common issues: unsigned binaries, invalid entitlements, prohibited code ### Verification Commands ```bash theme={null} # Verify code signature codesign --verify --verbose=2 VibeTunnel.app # Test with Gatekeeper (should pass for notarized apps) spctl -a -t exec -vv VibeTunnel.app # Check if notarization ticket is stapled stapler validate VibeTunnel.app ``` ## Reference ### Build Configurations * **Debug builds**: Use personal development certificate * **Release builds**: Use Developer ID for distribution * **CI builds**: Use ad-hoc signing ### File Structure After Signing ``` build/ ├── Build/Products/Release/VibeTunnel.app # Signed and notarized app ├── VibeTunnel-notarized.zip # Distributable archive └── VibeTunnel-1.0.0.dmg # DMG (if created) ``` ### Security Notes * Never commit signing certificates or API keys * Use environment variables or secure CI/CD secrets * The `.env` file is gitignored for security * API keys should have minimal permissions (Developer role) ### Implementation Details Debug keychain behavior is in `DashboardKeychain.swift`: * `getPassword()` returns `nil` in DEBUG builds * `setPassword()` saves but logs non-persistence * `hasPassword()` works normally ### External Resources * [Apple Code Signing Guide](https://developer.apple.com/documentation/security/notarizing_macos_software_before_distribution) * [App Store Connect API](https://developer.apple.com/documentation/appstoreconnectapi) * [notarytool Documentation](https://developer.apple.com/documentation/security/notarizing_macos_software_before_distribution/customizing_the_notarization_workflow) # Sparkle keys Source: https://docs.vibetunnel.sh/mac/docs/sparkle-keys # Sparkle Key Management Guide This guide covers the management of EdDSA keys used for signing VibeTunnel updates with the Sparkle framework. ## Overview VibeTunnel uses Sparkle's EdDSA (Ed25519) signatures for secure software updates. This system requires: * A **private key** (kept secret) for signing updates * A **public key** (distributed with the app) for verifying signatures ## Key Locations ### Public Key * **Location**: `VibeTunnel/sparkle-public-ed-key.txt` * **Status**: Committed to repository * **Usage**: Embedded in app via `SUPublicEDKey` in Info.plist * **Current Value**: `AGCY8w5vHirVfGGDGc8Szc5iuOqupZSh9pMj/Qs67XI=` ### Private Key * **Location**: `private/sparkle_private_key` * **Status**: NOT in version control (in .gitignore) * **Usage**: Required for signing updates during release * **Format**: Base64-encoded key data (no comments or headers) ## Initial Setup ### For New Team Members 1. **Request Access** ```bash theme={null} # Contact team lead for secure key transfer # Keys are stored in: Dropbox/Backup/Sparkle-VibeTunnel/ ``` 2. **Install Private Key** ```bash theme={null} # Create private directory mkdir -p private # Add key file (get content from secure backup) echo "BASE64_PRIVATE_KEY_HERE" > private/sparkle_private_key # Verify it's ignored by git git status # Should not show private/ ``` 3. **Verify Setup** ```bash theme={null} # Test signing with your key ./build/SourcePackages/artifacts/sparkle/Sparkle/bin/sign_update \ any_file.dmg \ -f private/sparkle_private_key ``` ### For New Projects 1. **Generate New Keys** ```bash theme={null} # Build Sparkle tools first ./scripts/build.sh # Generate new key pair ./build/SourcePackages/artifacts/sparkle/Sparkle/bin/generate_keys ``` 2. **Save Keys** ```bash theme={null} # Copy displayed keys: # Private key: [base64 string] # Public key: [base64 string] # Save private key mkdir -p private echo "PRIVATE_KEY_BASE64" > private/sparkle_private_key # Save public key echo "PUBLIC_KEY_BASE64" > VibeTunnel/sparkle-public-ed-key.txt ``` 3. **Update App Configuration** * Add public key to Info.plist under `SUPublicEDKey` * Commit public key file to repository ## Key Security ### Best Practices 1. **Never Commit Private Keys** * Private directory is in .gitignore * Double-check before committing 2. **Secure Backup** * Store in encrypted location * Use password manager or secure cloud storage * Keep multiple secure backups 3. **Limited Access** * Only release managers need private key * Use secure channels for key transfer * Rotate keys if compromised 4. **Key Format** * Private key file must contain ONLY the base64 key * No comments, headers, or extra whitespace * Single line of base64 data ### Example Private Key Format ``` SMYPxE98bJ5iLdHTLHTqGKZNFcZLgrT5Hyjh79h3TaU= ``` ## Troubleshooting ### "EdDSA signature does not match" Error **Cause**: Wrong private key or key format issues **Solution**: 1. Verify private key matches public key 2. Check key file has no extra characters 3. Regenerate appcast with correct key ### "Failed to decode base64 encoded key data" **Cause**: Private key file contains comments or headers **Solution**: ```bash theme={null} # Extract just the key grep -v '^#' your_key_backup.txt | grep -v '^$' > private/sparkle_private_key ``` ### Testing Key Pair Match ```bash theme={null} # Sign a test file echo "test" > test.txt ./build/SourcePackages/artifacts/sparkle/Sparkle/bin/sign_update \ test.txt \ -f private/sparkle_private_key # The signature should generate successfully # Compare with production signatures to verify ``` ## Key Rotation If keys need to be rotated: 1. **Generate New Keys** ```bash theme={null} ./build/SourcePackages/artifacts/sparkle/Sparkle/bin/generate_keys ``` 2. **Update App** * Change `SUPublicEDKey` in Info.plist * Update `sparkle-public-ed-key.txt` * Release new version with new public key 3. **Transition Period** * Keep old private key for emergency updates * Sign new updates with new key * After all users update, retire old key ## Integration with Release Process The release scripts automatically use the private key: 1. **generate-appcast.sh** * Expects key at `private/sparkle_private_key` * Fails if key missing or invalid * Signs all DMG files in releases 2. **release.sh** * Calls generate-appcast.sh after creating DMG * Ensures signatures are created before pushing ## Recovery Procedures ### Lost Private Key If private key is lost: 1. Generate new key pair 2. Update app with new public key 3. Release update signed with old key (if possible) 4. All future updates use new key ### Compromised Private Key If private key is compromised: 1. Generate new key pair immediately 2. Release security update with new public key 3. Notify users of security update 4. Revoke compromised key (document publicly) ## Verification Commands ### Verify Current Setup ```bash theme={null} # Check public key in app /usr/libexec/PlistBuddy -c "Print :SUPublicEDKey" \ build/Build/Products/Release/VibeTunnel.app/Contents/Info.plist # Check private key exists ls -la private/sparkle_private_key # Test signing ./scripts/generate-appcast.sh --dry-run ``` ### Verify Release Signatures ```bash theme={null} # Check signature in appcast grep "sparkle:edSignature" appcast-prerelease.xml # Manually verify a DMG ./build/SourcePackages/artifacts/sparkle/Sparkle/bin/sign_update \ build/VibeTunnel-1.0.0.dmg \ -f private/sparkle_private_key ``` ## Additional Resources * [Sparkle Documentation](https://sparkle-project.org/documentation/) * [EdDSA on Wikipedia](https://en.wikipedia.org/wiki/EdDSA) * [Ed25519 Key Security](https://ed25519.cr.yp.to/) *** For questions about key management, contact the release team lead. # Sparkle stats store Source: https://docs.vibetunnel.sh/mac/docs/sparkle-stats-store # Sparkle Updates with Stats.store Integration This document provides comprehensive documentation for VibeTunnel's automatic update system using Sparkle framework and Stats.store. ## Overview VibeTunnel uses a sophisticated update system that combines: * **Sparkle Framework** - Industry-standard macOS update framework for automatic updates * **Stats.store** - Privacy-first analytics backend that proxies appcast requests * **GitHub Releases** - Hosts the actual DMG files and appcast XML files ## System Architecture ### Update Check Flow ``` VibeTunnel App → Stats.store (Proxy) → GitHub (appcast.xml) → Stats.store → VibeTunnel App ↓ GitHub (DMG download) ``` 1. **App initiates update check**: VibeTunnel queries Stats.store endpoint 2. **Stats.store logs analytics**: Records anonymous data (OS version, CPU type, daily unique users) 3. **Stats.store proxies request**: Fetches appcast.xml from GitHub 4. **Appcast returned**: Stats.store returns the appcast to the app 5. **Signature verification**: Sparkle verifies the EdDSA signature 6. **Direct download**: If valid, app downloads DMG directly from GitHub (not through Stats.store) ### Update Endpoints * **Stable channel**: `https://stats.store/api/v1/appcast/appcast.xml` * **Pre-release channel**: `https://stats.store/api/v1/appcast/appcast-prerelease.xml` These endpoints proxy to the actual appcast files hosted on GitHub. ## Initial Setup and Registration ### Prerequisites Before Stats.store can serve your appcast files, you need to: 1. **Register your application** with Stats.store 2. **Configure your app** to use Stats.store endpoints 3. **Ensure proper User-Agent** headers are sent ### Stats.store Registration **Important**: As of the beta 9 release, VibeTunnel shows "Application not found" when querying Stats.store, indicating the app may not be properly registered or configured. To register your app with Stats.store: 1. Visit [stats.store](https://stats.store) and create an account 2. Add your application with: * App name: `VibeTunnel` * Bundle ID: `sh.vibetunnel.vibetunnel` * GitHub repository: `amantus-ai/vibetunnel` 3. Configure the appcast URLs to proxy to: * Stable: `https://raw.githubusercontent.com/amantus-ai/vibetunnel/main/appcast.xml` * Pre-release: `https://raw.githubusercontent.com/amantus-ai/vibetunnel/main/appcast-prerelease.xml` ### Verifying Configuration Check if your app is properly configured: ```bash theme={null} # This will fail with "Application not found" if not registered curl -H "User-Agent: VibeTunnel/1.0.0-beta.9 Sparkle/2.7.1" \ https://stats.store/api/v1/appcast/appcast-prerelease.xml # Check your app's stats page (replace with your app ID) # https://stats.store/app/YOUR_APP_ID ``` ## Configuration Details ### App Configuration (Info.plist) ```xml theme={null} SUPublicEDKey AGCY8w5vHirVfGGDGc8Szc5iuOqupZSh9pMj/Qs67XI= SUFeedURL https://stats.store/api/v1/appcast/appcast-prerelease.xml ``` ### HTTP Requirements Stats.store requires proper app identification via User-Agent header: ``` User-Agent: VibeTunnel/1.0.0-beta.8 Sparkle/2.7.1 ``` Without this header, Stats.store returns: ```json theme={null} {"error":"Application not found"} ``` ## Key Management and Signatures ### Public Key * **Location**: Info.plist (`SUPublicEDKey`) * **Value**: `AGCY8w5vHirVfGGDGc8Szc5iuOqupZSh9pMj/Qs67XI=` * **Purpose**: Verifies EdDSA signatures of updates ### Private Key * **Location**: `private/sparkle_private_key` * **Purpose**: Signs DMG files for appcast entries * **Critical**: Must match the public key in Info.plist ### Signature Generation To generate a signature for a DMG file: ```bash theme={null} # ALWAYS use the -f flag with the correct private key file sign_update -f /path/to/private/sparkle_private_key /path/to/VibeTunnel-1.0.0-beta.8.dmg # Output format: # sparkle:edSignature="..." length="44748347" ``` **⚠️ Important**: Never use `sign_update` without the `-f` flag as it may use a different key from the keychain. ## Appcast XML Format ### Structure ```xml theme={null} VibeTunnel VibeTunnel 1.0.0-beta.8 172 1.0.0-beta.8 Release Notes ]]> Tue, 08 Jul 2025 10:18:00 +0100 ``` ### Critical Fields * **sparkle:version**: Build number (must be incrementing) * **sparkle:shortVersionString**: Human-readable version * **length**: Exact file size in bytes * **sparkle:edSignature**: EdDSA signature of the DMG file * **url**: Direct download link to GitHub release ## Testing and Verification ### Manual Testing ```bash theme={null} # Test Stats.store endpoint (will fail without User-Agent) curl https://stats.store/api/v1/appcast/appcast-prerelease.xml # Test with proper User-Agent (should return XML) curl -H "User-Agent: VibeTunnel/1.0.0-beta.8 Sparkle/2.7.1" \ https://stats.store/api/v1/appcast/appcast-prerelease.xml # Verify a specific signature sign_update -f private/sparkle_private_key ~/Downloads/VibeTunnel-1.0.0-beta.8.dmg ``` ### Stats.store Caching ⚠️ **Important**: Stats.store has a **1-minute cache** for appcast files. After updating the appcast on GitHub: * Wait at least 1 minute before testing * The old version may be served during this cache period * Force-refresh won't bypass this server-side cache ## Current Release Signatures Complete signature reference for all VibeTunnel beta releases: | Version | Build | File Size | Signature | | ------------ | ----- | ---------- | ------------------------------------------------------------------------------------------ | | 1.0.0-beta.1 | 121 | 39,418,009 | `lm3eCKxuykGYj1oRG3uRm3QB+3azo7EGGeuP2SzZHsobnKGBxq48H21rN9WDi2mry8NbGM9YwjdjfzS56h7GDA==` | | 1.0.0-beta.2 | 133 | 40,511,292 | `VcPuSbUbcqhwrqongx9+mLhVAuHWlCw+xzIvsvqYKEv6W8UWtUPlPkYCgvoLuNRrJMnEOFcX/eJJv5RQl9/qAQ==` | | 1.0.0-beta.3 | 140 | 43,073,375 | `kY87vo1HXpFx6aKb9LDXbe/AmQND5iH+W7a3qpf2AejmEl+i7wKch/JY3zhBHrmWIuksiKOwFIIklT4sQFMjDw==` | | 1.0.0-beta.4 | 151 | 43,169,474 | `QXjzgcZXuF4zAy1AeYXAS2+WXLYWmMQYcm46isVO3WRp3I3IPHrXLOmWlVFixsFMM3JCKRmOnYsftEAyWjGbAA==` | | 1.0.0-beta.5 | 157 | 43,227,774 | `wAhA+mtSpcXd4f62yyF4bzSt/IG9ynPPVIRmIwcMCBgCZh0mavixiEPUHxYMGlukVuC+TXLJfqXowiCwMH8tBQ==` | | 1.0.0-beta.6 | 159 | 43,312,816 | `g84r8XLzvfeVHccjULfpjRGClf9Wll14PVLXCktUBkc+TRA312troC8dw1+bEn/ta5itW7nErwOCCIGD8U21DA==` | | 1.0.0-beta.7 | 165 | 43,383,612 | `vdcImChUp1qKY3V/8CTnyxq0TXkQjPXnEbEvks0xwWbzqvSP1xe3MBr/5kalilFpC9dH7wMxO9ohoNhHTjOvBQ==` | | 1.0.0-beta.8 | 172 | 44,748,347 | `/538z6L/qhhnHkfWU1hVoqeKvFdHubFRobfq6Vfmwz4UCpDVhJrqG+W28xW1wU4W9+xt41NMgei+DLJr1JV8Cg==` | | 1.0.0-beta.9 | 173 | 44,748,582 | `xAzHFZ1FYtncpZx1xKMAIMT9kDkEiZfH1uuY80weKzi7JE8Yd673/7919f3D3g4j/B7fTMs88TVlTxocL9zRCw==` | All signatures above are generated with the correct file-based private key and verified to work with the public key in Info.plist. ## Fallback Options Without Stats.store If Stats.store is not configured or you need to release before registration is complete, you can use direct GitHub URLs: ### Temporary Direct Configuration Update `UpdateChannel.swift` to use GitHub directly: ```swift theme={null} // Stable channel case .stable: return URL(string: "https://raw.githubusercontent.com/amantus-ai/vibetunnel/main/appcast.xml")! // Pre-release channel case .preRelease: return URL(string: "https://raw.githubusercontent.com/amantus-ai/vibetunnel/main/appcast-prerelease.xml")! ``` ### Implications of Direct URLs **Pros:** * Works immediately without registration * No dependency on third-party service * Updates still function normally **Cons:** * No analytics or usage statistics * No geographic CDN benefits * No A/B testing capabilities * Missing crash/update correlation data ### Migration Path 1. **Release with direct URLs** if Stats.store isn't ready 2. **Register with Stats.store** when convenient 3. **Update app in next release** to use Stats.store endpoints 4. **Existing users will update** and start using Stats.store ## Benefits of Stats.store 1. **Privacy-First Analytics**: * Track update adoption rates without collecting personal data * Monitor OS version distribution and hardware stats * Daily unique users via salted IP hashes (change daily) * No IP tracking, device IDs, or fingerprinting 2. **Anonymous Data Collection**: * macOS version and CPU architecture * App version numbers * Hardware info (RAM, Mac model, core count) * System language (no location data) 3. **Technical Benefits**: * Transparent proxy (doesn't host files) * 1-minute appcast caching * GitHub outage protection * Free for open source projects 4. **Future Features** (Planned): * A/B testing for gradual rollouts * Custom update channels * Geographic CDN capabilities ## Troubleshooting Guide ### Common Issues #### "Application not found" Error This is the most common Stats.store integration issue. There are several potential causes: 1. **App not registered with Stats.store** * **Solution**: Register at [stats.store](https://stats.store) * Create account and add VibeTunnel as an application * Configure GitHub repository URLs for appcast proxying 2. **Incorrect User-Agent header** * **Required format**: `VibeTunnel/VERSION Sparkle/VERSION` * **Example**: `VibeTunnel/1.0.0-beta.9 Sparkle/2.7.1` * **Fix**: Ensure Sparkle framework is properly integrated 3. **Bundle ID mismatch** * **Expected**: `sh.vibetunnel.vibetunnel` * **Check**: Verify in Info.plist that CFBundleIdentifier matches 4. **Testing before registration** * **Note**: You can still release without Stats.store * **Alternative**: Use direct GitHub URLs in Info.plist temporarily * **Update later**: Once registered, update SUFeedURL to Stats.store endpoints #### Signature Verification Failed * **Cause**: Wrong private key used for signing * **Fix**: Use `sign_update -f private/sparkle_private_key` #### Updates Not Detected * **Cause**: Stats.store cache or incorrect version numbers * **Fix**: Wait 1 minute after updating appcast, verify version increments #### File Size Mismatch * **Cause**: DMG was modified after signing * **Fix**: Re-download and re-sign the DMG ### Debugging Commands ```bash theme={null} # Check current appcast curl -H "User-Agent: VibeTunnel/1.0.0-beta.8 Sparkle/2.7.1" \ https://stats.store/api/v1/appcast/appcast-prerelease.xml | xmllint --format - # Verify DMG signature sign_update -f private/sparkle_private_key downloaded.dmg # Compare with appcast signature grep "sparkle:edSignature" appcast-prerelease.xml ``` ## The Beta 8 Update Incident (July 2025) ### Timeline of Events 1. **Initial Success**: Updates from beta 1 through beta 7 worked correctly 2. **Problem Detected**: Users updating from beta 7 to beta 8 received error: > "The update is improperly signed and could not be validated" 3. **Investigation**: Discovered multiple Sparkle private keys on the system 4. **Root Cause**: Wrong private key used to generate appcast signatures 5. **Resolution**: Updated appcast with correct signature ### Technical Details #### The Problem Two different Sparkle private keys existed: 1. **File-based key** (`private/sparkle_private_key`) * Base64: `SMYPxE98bJ5iLdHTLHTqGKZNFcZLgrT5Hyjh79h3TaU=` * Matches public key: `AGCY8w5vHirVfGGDGc8Szc5iuOqupZSh9pMj/Qs67XI=` * **This is the correct key** 2. **Keychain key** (accessed without `-f` flag) * Different key stored in macOS keychain * Produces incompatible signatures * **This was incorrectly used** #### The Investigation Used `sign_update` to test both keys: ```bash theme={null} # Wrong way (uses keychain) sign_update VibeTunnel-1.0.0-beta.8.dmg # Result: XcdsjTw01IMbHGVnRVAq1cZ4ii4bY69CE+xqRHO/XXHP+05xzqndwlQ3cv22Ju083zbU2eu8W1J5AoCa75jLBw== # Correct way (uses file) sign_update -f private/sparkle_private_key VibeTunnel-1.0.0-beta.8.dmg # Result: /538z6L/qhhnHkfWU1hVoqeKvFdHubFRobfq6Vfmwz4UCpDVhJrqG+W28xW1wU4W9+xt41NMgei+DLJr1JV8Cg== ``` #### The Mystery Why did beta 1-7 updates work despite incorrect signatures? Theories: * DMGs might have been originally signed with the keychain key * The keychain key might have changed between releases * There could have been a different issue masking the problem #### The Solution 1. **Identified** the correct private key file 2. **Generated** the correct signature using `-f` flag 3. **Updated** only the appcast XML (no DMG changes needed) 4. **Waited** for Stats.store cache to expire (1 minute) 5. **Verified** updates now work correctly ### Key Lessons Learned 1. **Always use `-f` flag**: `sign_update -f private/sparkle_private_key` 2. **Document key locations**: Keep clear records of which keys to use 3. **Understand the architecture**: Signatures live in appcast, not DMG files 4. **Remember caching**: Stats.store has a 1-minute cache 5. **Test thoroughly**: Verify signatures match before releasing ### Prevention Measures * Created this comprehensive documentation * Added warnings about multiple keys * Established correct signing procedure * Documented all historical signatures for reference The incident was resolved quickly once the root cause was identified, demonstrating the importance of understanding the complete update pipeline from app to Stats.store to GitHub. # CLAUDE Source: https://docs.vibetunnel.sh/web/CLAUDE # Claude Development Notes ## Build Process * **Never run build commands** * the user has `pnpm run dev` running which handles automatic rebuilds, either directly or via the mac app * Never manually run the server. The user does that * Changes to TypeScript files are automatically compiled and watched * Do not run `pnpm run build` or similar build commands ## Development Workflow * Make changes to source files in `src/` * **ALWAYS run code quality checks before committing:** * `pnpm run check` - Run all checks (format, lint, typecheck) in parallel * This is the ONLY command you need to run for checking * It runs everything concurrently for maximum speed * **If there are issues to fix:** * `pnpm run check:fix` - Auto-fix formatting and linting issues (runs sequentially to avoid conflicts) * **Individual commands (rarely needed):** * `pnpm run format` / `pnpm run format:check` * `pnpm run lint` / `pnpm run lint:fix` * `pnpm run typecheck` * Always fix all linting and type checking errors, including in unrelated code * Never run the tests, unless explicitly asked to. `pnpm run test` ## Code References **THIS IS OF UTTER IMPORTANCE THE USERS HAPPINESS DEPENDS ON IT!** When referencing code locations, you MUST use clickable format that VS Code recognizes: * `path/to/file.ts:123` format (file:line) * `path/to/file.ts:123-456` (ranges) * Always use relative paths from the project root * Examples: * `src/cli.ts:92` - single line reference * `src/server/pty/pty-manager.ts:274-280` - line range * `web/src/client/app.ts:15` - when in parent directory NEVER give a code reference or location in any other format. ## Git Commands When asked to "commit and push", "commit + push", "/cp", or "c+p", use a single command: ```bash theme={null} git add -A && git commit -m "commit message" && git push ``` Do NOT use three separate commands (add, commit, push) as this is slow. ## Refactoring Philosophy * We do not care about deprecation - remove old code completely * Always prefer clean refactoring over gradual migration * Delete unused functions and code paths immediately * **We do not care about backwards compatibility** - Everything is shipped together * No need to support "older UI versions" - the web UI and server are always deployed as a unit ## Best Practices * ALWAYS use `Z_INDEX` constants in `src/client/utils/constants.ts` instead of setting z-index properties using primitives / magic numbers * Add ids to web elements whenever needed to make testing simpler. This helps avoid complex selectors that search by text content or traverse the DOM * Use descriptive IDs like `session-kill-button`, `show-exited-button`, `file-picker-choose-button` * Prefer ID selectors (`#element-id`) over complex queries in tests * When adding interactive elements (buttons, inputs), always consider adding an ID for testability ## CRITICAL: Package Installation Policy **NEVER install packages without explicit user approval!** * Do NOT run `pnpm add`, `npm install`, or any package installation commands * Do NOT modify `package.json` or `pnpm-lock.yaml` unless explicitly requested * Always ask for permission before suggesting new dependencies * Understand and work with the existing codebase architecture first * This project has custom implementations - don't assume we need standard packages ## CRITICAL: vt Command in package.json **IMPORTANT: DO NOT add "vt": "./bin/vt" to the bin section of package.json or package.npm.json!** * The vt command must NOT be registered as a global binary in package.json * This is because it conflicts with other tools that use 'vt' (there are many) * Instead, vt is conditionally installed via postinstall script only if available * The postinstall script checks if vt already exists before creating a symlink ## CRITICAL: Playwright Test UI Changes **IMPORTANT: When tests fail looking for UI elements, investigate the actual UI structure!** ### Best Practices for Test Stability 1. **Always use semantic IDs and data-testid attributes** - These are more stable than CSS selectors 2. **Understand the UI structure** - Don't just increase timeouts, investigate why elements aren't found 3. **Check for collapsible/expandable sections** - Many elements are now hidden by default 4. **Wait for animations** - After expanding sections, wait briefly for animations to complete 5. **Use proper element states** - Wait for 'visible' not just 'attached' for interactive elements # NPM PUBLISH READY Source: https://docs.vibetunnel.sh/web/NPM_PUBLISH_READY # ✅ VibeTunnel npm Package - Ready to Publish The standalone VibeTunnel server is now fully prepared for npm publishing! ## What's Been Prepared ### 📦 Package Configuration (`package.npm.json`) * **Package name**: `vibetunnel` (unscoped package) * **Version**: 1.0.0-beta.16 (ready to increment) * **Entry point**: `lib/cli.js` * **Binary**: `vibetunnel` command * **Keywords**: Added relevant keywords for discoverability * **Files**: Configured to include only necessary files ### 🚀 New Features for Standalone Mode 1. **Ngrok Integration** * `NgrokService` class for tunnel management * CLI flags: `--ngrok`, `--ngrok-auth`, `--ngrok-domain`, `--ngrok-region` * Automatic binary detection * Proper cleanup on shutdown 2. **Enhanced CLI** * Works with `npx vibetunnel` out of the box * All server configuration options preserved * Help text with examples 3. **Docker Support** * `Dockerfile.standalone` for containerized deployment * Includes ngrok in the image * Ready for Kubernetes/Docker Compose ### 📚 Documentation * **README.npm.md**: Optimized README for npm listing * **README.standalone.md**: Comprehensive standalone usage guide * **PUBLISHING.md**: Step-by-step publishing instructions * **Dockerfile.standalone**: Docker deployment ### 🛠️ Build System * Updated `build-npm.js` to copy all necessary files * Handles README files appropriately * Creates proper npm package structure ## Quick Publishing Steps ```bash theme={null} # 1. Build the package cd web/ pnpm run build:npm # 2. Test locally cd dist-npm/ npm pack npm install -g vibetunnel-*.tgz vibetunnel --version npm uninstall -g vibetunnel # 3. Publish npm login # If not logged in npm publish ``` ## Testing Commands After publishing, users can: ```bash theme={null} # Quick start - no installation npx vibetunnel --no-auth # With ngrok tunnel npx vibetunnel --no-auth --ngrok # Docker docker run -p 4020:4020 vibetunnel --no-auth --ngrok ``` ## Key Benefits for Users 1. **Zero Installation** - Works instantly with npx 2. **Remote Access** - Built-in ngrok support 3. **Docker Ready** - Includes Dockerfile 4. **Cross-Platform** - Works on Linux, macOS, WSL 5. **Flexible Auth** - From no-auth demos to SSH keys 6. **Production Ready** - Proper security options ## Version Note Currently at `1.0.0-beta.16`. When ready for stable release, bump to `1.0.0`. ## Repository Owner Action Items 1. ✅ Review package.npm.json configuration 2. ✅ Test the build locally with `pnpm run build:npm` 3. ✅ Decide on versioning (keep beta or go to 1.0.0) 4. ✅ Run `npm publish` when ready 5. ✅ Consider setting up GitHub Actions for automated publishing ## Support Files All necessary files are in place: * `/web/package.npm.json` - npm package configuration * `/web/README.npm.md` - npm README * `/web/README.standalone.md` - Usage documentation * `/web/Dockerfile.standalone` - Docker support * `/web/PUBLISHING.md` - Publishing guide * `/web/.npmignore.standalone` - Files to exclude * `/web/src/server/services/ngrok-service.ts` - Ngrok integration The package is ready to publish! 🎉 # PHASE1 IMPLEMENTATION SUMMARY Source: https://docs.vibetunnel.sh/web/PHASE1_IMPLEMENTATION_SUMMARY # Phase 1 Mobile UI/UX Redesign - Implementation Summary ## Overview Successfully implemented Phase 1 of the mobile terminal UI/UX redesign for VibeTunnel, focusing on eliminating viewport overlap issues by removing CSS transforms and implementing a flexbox-based layout. ## Key Changes Implemented ### 1. New CSS Classes Added (`styles.css`) * **`.mobile-terminal-container`**: Flexbox container using `100dvh` for dynamic viewport height * **`.mobile-terminal-header`**: Sticky header with safe area support and minimum height of 44px * **`.mobile-terminal-content`**: Flexible content area that takes remaining space * **`.mobile-action-bar`**: Sticky bottom bar for special keys with safe area padding * **`.mobile-action-key`**: Touch-optimized action buttons with proper tap handling * **`.mobile-action-keys`**: Horizontally scrollable container for action keys ### 2. Session View Component Updates (`session-view.ts`) #### Layout Changes * Added responsive styles that use flexbox on mobile (≤768px) and grid on desktop * Removed all CSS transforms on mobile devices * Disabled the `translateY(-110px)` terminal transform on mobile * Reset terminal height/margin/padding to natural values on mobile #### New Method * Added `renderQuickKeysContent()` method to handle different quick key layouts: * Mobile direct keyboard mode: Scrollable action bar with special keys * Mobile non-direct keyboard mode: Similar but with ABC button instead of Done * Desktop: Maintains existing two-row layout #### Updated Transform Logic * Modified `updateTerminalTransform()` to skip transforms on mobile * Mobile now only triggers terminal resize without transforms * Desktop maintains existing transform behavior ### 3. Overlays Container Update (`overlays-container.ts`) * Modified to not render `terminal-quick-keys` component on mobile * Mobile quick keys are now handled by the main layout's action bar ## Benefits Achieved 1. **No More Viewport Overlap**: Header remains visible at all times, even with keyboard open 2. **Simplified Layout**: Flexbox provides stable, predictable behavior 3. **Better Touch Handling**: All interactive elements optimized for touch 4. **Safe Area Support**: Proper handling of device notches and home indicators 5. **Smooth Transitions**: Action bar slides in/out smoothly 6. **Responsive Design**: Different layouts for mobile vs desktop ## Testing Created `test-mobile-layout.html` to verify the implementation works correctly in isolation. ## Next Steps (Future Phases) * Phase 2: Simplify keyboard input to single system * Phase 3: Optimize header for mobile (compact mode) * Phase 4: Polish animations and performance ## Technical Notes * Used `100dvh` for dynamic viewport height that adjusts with keyboard * Maintained backward compatibility for desktop users * All changes are CSS-based with minimal JavaScript modifications * No breaking changes to existing functionality # PUBLISHING Source: https://docs.vibetunnel.sh/web/PUBLISHING # Publishing VibeTunnel to npm This guide helps the repository owner publish VibeTunnel to npm as a standalone package. ## Prerequisites 1. **npm account** with publish access to `vibetunnel` 2. **Node.js 22.12 through 24.x** installed 3. **Apple Silicon Mac** for the complete multi-platform package 4. **Docker** installed (for Linux builds) 5. **Rustup** installed; `native/vt-fwd/rust-toolchain.toml` pins the forwarder toolchain ## Publishing Checklist ### 1. Update Version ```bash theme={null} # Update version in both package files cd web/ # Edit version in package.json and package.npm.json vim package.json package.npm.json ``` ### 2. Build for npm ```bash theme={null} # Clean and build for all platforms on macOS pnpm run clean PACKAGE_TARBALL="./vibetunnel-$(node -p "require('./package.json').version").tgz" rm -f "$PACKAGE_TARBALL" pnpm run build:npm test -f "$PACKAGE_TARBALL" # This creates dist-npm/ and vibetunnel-.tgz with: # - Compiled JavaScript (lib/) # - Static files (public/) # - Native-module prebuilds (prebuilds/) # - Rust forwarders (forwarders/-/vibetunnel-fwd) # - Package.json ready for publishing ``` ### 3. Test Locally ```bash theme={null} # build:npm runs npm pack in dist-npm/ and moves the archive here, to web/ pnpm run test:npm-package "$PACKAGE_TARBALL" npm install -g "$PACKAGE_TARBALL" # Test basic functionality vibetunnel --version vibetunnel --help vibetunnel --no-auth # Test server starts # Test with ngrok vibetunnel --no-auth --ngrok # Cleanup npm uninstall -g vibetunnel ``` ### 4. Publish to npm ```bash theme={null} # Login to npm (first time only) npm login # Username: [your-username] # Password: [your-password] # Email: [your-email] # OTP: [if 2FA enabled] # Publish the exact archive tested above. Use one command, matching the version: npm publish "$PACKAGE_TARBALL" --tag beta # prerelease, e.g. 1.0.0-beta.18 npm publish "$PACKAGE_TARBALL" --tag latest # stable, e.g. 1.0.0 ``` ### 5. Verify Publication ```bash theme={null} # Check it's published npm view vibetunnel # Test installation npx vibetunnel --version # Test in a fresh directory cd /tmp npx vibetunnel --no-auth ``` ## Package Configuration The package is configured with: * **Name**: `vibetunnel` (unscoped) * **Main**: `lib/cli.js` (entry point) * **Bin**: `vibetunnel` command * **Platforms**: macOS (x64, arm64) and Linux (x64, arm64) * **Node**: Requires Node.js 22+ ## Rust Forwarder Outputs `pnpm run build` builds the host forwarder and installs it at `native/vibetunnel-fwd` and `bin/vibetunnel-fwd` under `web/`. `pnpm run build:npm` additionally stages the selected package targets at: ```text theme={null} web/forwarders/darwin-arm64/vibetunnel-fwd web/forwarders/darwin-x64/vibetunnel-fwd web/forwarders/linux-arm64/vibetunnel-fwd web/forwarders/linux-x64/vibetunnel-fwd ``` The selected directories are copied unchanged to `web/dist-npm/forwarders/` and then into the package archive. At runtime the CLI selects `forwarders/-/vibetunnel-fwd`; the postinstall script makes that binary executable. A complete macOS build creates all four targets. Filtered and `--current-only` builds include only their selected targets. ## What Gets Published The npm package includes: * ✅ Compiled JavaScript (`lib/`) * ✅ Web UI files (`public/`) * ✅ CLI binary (`bin/vibetunnel`) * ✅ Rust forwarders (`forwarders/-/vibetunnel-fwd`) * ✅ Prebuilt native binaries (`prebuilds/`) * ✅ README files (README.md, README.npm.md, README.standalone.md) * ✅ Runtime-only Dockerfile for building directly from extracted package contents * ✅ Postinstall scripts Not included: * ❌ Source TypeScript files * ❌ Test files * ❌ Development configs * ❌ Mac/iOS app code ## Version Management Follow semantic versioning: * **Patch** (1.0.x): Bug fixes, small improvements * **Minor** (1.x.0): New features, backward compatible * **Major** (x.0.0): Breaking changes Current version scheme: * `1.0.0-beta.X` for beta releases * `1.0.0` for first stable release ## Troubleshooting ### Build Fails ```bash theme={null} # Clean everything and retry pnpm run clean rm -rf dist-npm/ pnpm install pnpm run build:npm ``` Complete multi-platform builds require macOS. On Linux, use `pnpm run build:npm -- --current-only` or `pnpm run build:npm -- --platform linux`. ### Missing Prebuilds ```bash theme={null} # Build for specific platform pnpm run build:npm -- --platform darwin --arch arm64 ``` ### Permission Denied ```bash theme={null} # Ensure you're logged in with correct account npm whoami npm access ls-packages # If using npm org teams, ensure team access for the package ``` ### Already Published Version ```bash theme={null} # Bump version first npm version patch # or minor/major # Then rebuild and republish ``` ## Post-Publishing After successful publication: 1. **Test with npx**: `npx vibetunnel --version` 2. **Update documentation**: Add npm badge to main README 3. **Create GitHub release**: Tag the version 4. **Announce**: Twitter, Discord, etc. ## Automation (Future) Consider setting up GitHub Actions: ```yaml theme={null} # .github/workflows/npm-publish.yml name: Publish to npm on: release: types: [created] jobs: publish: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: node-version: '24' registry-url: 'https://registry.npmjs.org' - run: cd web && pnpm install - run: cd web && pnpm run build:npm - run: cd web && npm publish ./vibetunnel-*.tgz env: NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}} ``` ## Support For issues with publishing, check: * [npm documentation](https://docs.npmjs.com/cli/v10/commands/npm-publish) * [GitHub issues](https://github.com/amantus-ai/vibetunnel/issues) * npm support: [support@npmjs.com](mailto:support@npmjs.com) # README.docker Source: https://docs.vibetunnel.sh/web/README.docker # VibeTunnel Docker Usage Perfect for containerized development and instant terminal access to your code. ## 🚀 Quick Start ```bash theme={null} # Build the image docker build -f Dockerfile.standalone -t vibetunnel \ --build-arg VT_FWD_COMMIT="$(git rev-parse HEAD)" . # Mount your code and get instant tunnel access docker run -v $(pwd):/workspace -p 4020:4020 vibetunnel --ngrok ``` ## 📂 How It Works 1. **Your code** gets mounted to `/workspace` in the container 2. **VibeTunnel** starts with full dev tools (git, vim, nano, htop, etc.) 3. **Terminal access** via web browser at the tunnel URL 4. **All changes** persist to your local filesystem ## 🌐 Tunnel Options ### Ngrok (Most Popular) ```bash theme={null} # Basic ngrok tunnel docker run -v $(pwd):/workspace -p 4020:4020 vibetunnel --ngrok # With auth token for reliability docker run -v $(pwd):/workspace -p 4020:4020 vibetunnel --ngrok --ngrok-auth YOUR_TOKEN # Custom domain (paid plan) docker run -v $(pwd):/workspace -p 4020:4020 vibetunnel --ngrok --ngrok-domain custom.ngrok.io ``` ### Cloudflare Quick Tunnel (No Auth Required) ```bash theme={null} # Free Cloudflare tunnel - no signup needed! docker run -v $(pwd):/workspace -p 4020:4020 vibetunnel --cloudflare ``` ### Local Development (No Tunnel) ```bash theme={null} # Local only - no internet access docker run -v $(pwd):/workspace -p 4020:4020 vibetunnel --no-auth # Access at http://localhost:4020 ``` ## 💡 Use Cases ### Remote Development Work on any machine and access via tunnel: ```bash theme={null} # On any server docker run -v /home/user/project:/workspace -p 4020:4020 vibetunnel --ngrok # Share URL with team for collaboration ``` ### Docker Compose Development ```yaml theme={null} version: '3' services: app: image: your-app:latest volumes: - ./:/app terminal: image: vibetunnel:latest command: ["--cloudflare"] ports: - "4020:4020" volumes: - ./:/workspace ``` ### Kubernetes Debugging ```yaml theme={null} apiVersion: v1 kind: Pod metadata: name: debug-pod spec: containers: - name: app image: your-app:latest volumeMounts: - name: code mountPath: /app - name: terminal image: vibetunnel:latest args: ["--cloudflare"] ports: - containerPort: 4020 volumeMounts: - name: code mountPath: /workspace volumes: - name: code configMap: name: app-source ``` ### Teaching & Workshops ```bash theme={null} # Instructor shares live coding environment docker run -v $(pwd):/workspace -p 4020:4020 vibetunnel --ngrok # Students access via shared URL - no setup required! ``` ## 🔧 Container Features ### Pre-installed Tools * **Languages**: Node.js 22, Python 3 * **Editors**: vim, nano * **Utils**: git, curl, wget, htop, tree, jq * **Tunnels**: ngrok, cloudflared * **Dev Tools**: pnpm, typescript, ts-node, nodemon ### Smart Entrypoint * Shows helpful usage if no args provided * Passes all arguments to VibeTunnel * Automatically binds to 0.0.0.0 for container access ### Environment * Working directory: `/workspace` (mount your code here) * PATH includes `/workspace/node_modules/.bin` * Node.js tools available globally ## 🛡️ Security Notes * `--no-auth` disables authentication (only use for development) * For production, use proper authentication methods * Tunnel URLs are public - be careful with sensitive data * Consider using `--enable-ssh-keys` for better security ## 🎯 Perfect For * **Remote pair programming** * **Code reviews in real-time** * **Teaching programming** * **Debugging in containers** * **Quick server access** * **Team collaboration** * **Live demonstrations** Your code stays local, but terminal access is global! 🌍 # README.npm Source: https://docs.vibetunnel.sh/web/README.npm # VibeTunnel - Web Terminal Server Run terminal sessions in your browser. Perfect for remote access, Docker containers, and quick terminal sharing via ngrok. [![npm version](https://img.shields.io/npm/v/vibetunnel.svg)](https://www.npmjs.com/package/vibetunnel) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) ## 🚀 Quick Start No installation needed - run instantly with npx: ```bash theme={null} # Start local server (no auth) npx vibetunnel --no-auth # Start with ngrok tunnel for remote access npx vibetunnel --no-auth --ngrok # Custom port npx vibetunnel --port 8080 --no-auth ``` Then open [http://localhost:4020](http://localhost:4020) in your browser. ## 📦 Installation ### Global Install ```bash theme={null} npm install -g vibetunnel # Run the server vibetunnel --no-auth ``` ### Docker ```bash theme={null} # Extract the published package and build its runtime-only image npm pack vibetunnel mkdir vibetunnel-docker tar -xzf vibetunnel-*.tgz --strip-components=1 -C vibetunnel-docker docker build -f vibetunnel-docker/Dockerfile.standalone -t vibetunnel vibetunnel-docker # Mount your code and run with tunnel docker run -v $(pwd):/workspace -p 4020:4020 vibetunnel --ngrok # Or with Cloudflare tunnel docker run -v $(pwd):/workspace -p 4020:4020 vibetunnel --cloudflare ``` The packaged Dockerfile consumes only published runtime files. Repository builds continue to use the source-oriented `web/Dockerfile.standalone`. ## 🌐 Remote Access with Ngrok Share your terminal with anyone on the internet: ```bash theme={null} # With built-in Tailscale Serve npx vibetunnel --no-auth --enable-tailscale-serve # With external ngrok (run separately) npx vibetunnel --no-auth & ngrok http 4020 # With Cloudflare tunnel npx vibetunnel --no-auth & cloudflared tunnel --url localhost:4020 ``` ## 🔧 CLI Options ### Server Options * `--port ` - Server port (default: 4020) * `--bind
` - Bind address (default: 0.0.0.0) * `--debug` - Enable debug logging ### Authentication * `--no-auth` - Disable authentication (⚠️ use only for testing) * `--enable-ssh-keys` - Enable SSH key authentication * `--disallow-user-password` - SSH keys only, no passwords ### Tunnel Options * `--ngrok` - Enable ngrok tunnel * `--ngrok-auth ` - Ngrok auth token * `--ngrok-domain ` - Custom domain * `--ngrok-region ` - Region: us, eu, ap, au, sa, jp, in * `--cloudflare` - Enable Cloudflare Quick Tunnel (no auth) ## 💡 Use Cases ### Remote Server Management Access any server's terminal through a browser: ```bash theme={null} ssh remote-server npx vibetunnel --no-auth --ngrok # Share the ngrok URL with your team ``` ### Docker Development Add terminal access to any container: ```yaml theme={null} version: '3' services: app: image: vibetunnel:latest command: ["--ngrok"] ports: - "4020:4020" volumes: - "./:/workspace" ``` ### Kubernetes Debugging Deploy as a sidecar for pod debugging: ```yaml theme={null} containers: - name: main-app image: your-app:latest - name: terminal image: node:24-trixie-slim command: ["npx", "vibetunnel", "--no-auth"] ports: - containerPort: 4020 ``` ### Teaching & Demos Share your terminal for live coding sessions: ```bash theme={null} npx vibetunnel --no-auth --ngrok # Share URL with students ``` ## 🔒 Security ⚠️ **Important Security Notes:** 1. **Never use `--no-auth` in production** - it disables all authentication 2. **Always use HTTPS in production** - either via ngrok or a reverse proxy 3. **Consider SSH key authentication** for better security 4. **Use environment variables** for sensitive configuration ### Production Setup ```bash theme={null} # With system authentication (uses PAM) vibetunnel # With SSH keys only vibetunnel --enable-ssh-keys --disallow-user-password # Behind reverse proxy (nginx/caddy) vibetunnel --bind 127.0.0.1 ``` ## 🛠️ Advanced Configuration ### Environment Variables * `PORT` - Default port (overrides 4020) * `VIBETUNNEL_DEBUG` - Enable debug logging * `NGROK_AUTHTOKEN` - Ngrok auth token ### Custom Configuration Create `~/.vibetunnel/config.json`: ```json theme={null} { "port": 8080, "authentication": { "sshKeysEnabled": true }, "remoteAccess": { "ngrokEnabled": true } } ``` ## 📚 Full Documentation * [Standalone Usage Guide](https://github.com/amantus-ai/vibetunnel/blob/main/web/README.standalone.md) * [Main Repository](https://github.com/amantus-ai/vibetunnel) * [Report Issues](https://github.com/amantus-ai/vibetunnel/issues) ## 🤝 Contributing Contributions welcome! Please check the [main repository](https://github.com/amantus-ai/vibetunnel) for guidelines. ## 📄 License MIT - See [LICENSE](https://github.com/amantus-ai/vibetunnel/blob/main/LICENSE) for details. *** **Note:** This is the standalone web server version of VibeTunnel. For the full macOS app experience with menu bar integration, see the [main project](https://github.com/amantus-ai/vibetunnel). # README.standalone Source: https://docs.vibetunnel.sh/web/README.standalone # VibeTunnel Standalone Server Run VibeTunnel as a standalone web terminal server without the macOS app. Perfect for remote machines, Docker containers, and quick terminal sharing. ## Quick Start ### Using npx (no installation) ```bash theme={null} # Run with no authentication (demo/testing) npx vibetunnel --no-auth # Run with ngrok tunnel for instant sharing npx vibetunnel --no-auth --ngrok # Run with Cloudflare tunnel (no auth needed) npx vibetunnel --no-auth --cloudflare # Run with Tailscale tunnel npx vibetunnel --no-auth --enable-tailscale-serve # Run with custom port npx vibetunnel --port 8080 --no-auth ``` ### Global Installation ```bash theme={null} # Install globally npm install -g vibetunnel # Run the server vibetunnel --no-auth ``` ### Docker ```bash theme={null} # Released npm package: build directly from the extracted package contents docker build -f Dockerfile.standalone -t vibetunnel . # Repository checkout: the source Dockerfile builds the matching forwarder docker build -f Dockerfile.standalone -t vibetunnel \ --build-arg VT_FWD_COMMIT="$(git rev-parse HEAD)" . # Mount your code and run with ngrok tunnel docker run -v $(pwd):/workspace -p 4020:4020 vibetunnel --ngrok # With Cloudflare tunnel (no auth needed) docker run -v $(pwd):/workspace -p 4020:4020 vibetunnel --cloudflare # Local development (no tunnel) docker run -v $(pwd):/workspace -p 4020:4020 vibetunnel --no-auth # With ngrok auth token for custom domain docker run -v $(pwd):/workspace -p 4020:4020 vibetunnel --ngrok --ngrok-auth YOUR_TOKEN ``` ## CLI Options ### Basic Server Options * `--port ` - Server port (default: 4020) * `--bind
` - Bind address (default: 0.0.0.0) * `--no-auth` - Disable authentication (for testing) * `--debug` - Enable debug logging ### Tunnel Options (for remote access) * `--ngrok` - Enable ngrok tunnel for instant sharing * `--ngrok-auth ` - Ngrok authentication token * `--ngrok-domain ` - Custom ngrok domain (paid plan) * `--ngrok-region ` - Ngrok region (us, eu, ap, au, sa, jp, in) * `--cloudflare` - Enable Cloudflare tunnel (Quick Tunnel, no auth) * `--enable-tailscale-serve` - Enable Tailscale Serve integration ### Authentication Options * `--enable-ssh-keys` - Enable SSH key authentication * `--disallow-user-password` - Disable password auth, SSH keys only * `--allow-local-bypass` - Allow localhost connections to bypass auth * `--local-auth-token ` - Token for localhost auth bypass ### Network Discovery * `--no-mdns` - Disable mDNS/Bonjour advertisement ## Use Cases ### Remote Server Access Access a remote server's terminal through a web browser: ```bash theme={null} # Method 1: Built-in ngrok (easiest!) npx vibetunnel --no-auth --ngrok # Output: Public URL: https://abc123.ngrok.io # Method 2: Built-in Cloudflare (no auth needed) npx vibetunnel --no-auth --cloudflare # Output: Public URL: https://random-words.trycloudflare.com # Method 3: With Tailscale (if configured) npx vibetunnel --no-auth --enable-tailscale-serve # Method 4: With ngrok auth for custom domain npx vibetunnel --no-auth --ngrok --ngrok-auth YOUR_TOKEN --ngrok-domain custom.ngrok.io ``` ### Docker Development Environment Mount your project and get instant web terminal access: ```bash theme={null} # Quick development container with tunnel docker run -v $(pwd):/workspace -p 4020:4020 vibetunnel --ngrok # Or for team development docker run -v /path/to/project:/workspace -p 4020:4020 vibetunnel --cloudflare # Your code is available at /workspace in the web terminal # Access via the tunnel URL from anywhere ``` ### Quick Terminal Sharing Share your terminal session in one command: ```bash theme={null} # Instant sharing with ngrok npx vibetunnel --no-auth --ngrok # Or with Cloudflare (no signup needed) npx vibetunnel --no-auth --cloudflare # With Tailscale (if configured) npx vibetunnel --no-auth --enable-tailscale-serve ``` ### Kubernetes Pod Access Deploy VibeTunnel as a sidecar container for web-based pod access: ```yaml theme={null} apiVersion: v1 kind: Pod metadata: name: app-with-terminal spec: containers: - name: main-app image: your-app:latest - name: vibetunnel image: vibetunnel:latest ports: - containerPort: 4020 env: - name: VIBETUNNEL_NO_AUTH value: "true" ``` ## Security Considerations ⚠️ **Warning**: The `--no-auth` flag disables all authentication. Only use this for: * Local development * Isolated Docker containers * Networks you fully trust For production use: 1. Always enable authentication 2. Use HTTPS/TLS (via ngrok or reverse proxy) 3. Consider SSH key authentication with `--enable-ssh-keys` 4. Use environment variables for sensitive configuration ## Environment Variables * `PORT` - Default port if --port not specified * `VIBETUNNEL_DEBUG` - Enable debug logging * `VIBETUNNEL_CONTROL_DIR` - Control directory for session data * `NGROK_AUTHTOKEN` - Ngrok auth token (alternative to --ngrok-auth) ## Building from Source ```bash theme={null} # Clone the repository git clone https://github.com/amantus-ai/vibetunnel.git cd vibetunnel/web # Install dependencies pnpm install # Build pnpm run build # Run node dist/cli.js --no-auth ``` ## Differences from Mac App Version The standalone server: * ✅ Runs on any platform (Linux, macOS, Windows via WSL) * ✅ Works in Docker containers * ✅ Can be deployed via npx without installation * ✅ Includes built-in ngrok support * ❌ No menu bar integration * ❌ No automatic server management * ❌ No macOS-specific features (Keychain, etc.) ## Troubleshooting ### Ngrok not starting * Ensure ngrok is installed: `which ngrok` * Check if you need an auth token for your use case * Verify the port is not already in use ### Permission denied errors * The server needs to spawn PTY processes * In Docker, you may need `--cap-add SYS_ADMIN` * Check file permissions in mounted volumes ### Connection refused * Verify the bind address (use 0.0.0.0 for all interfaces) * Check firewall rules * Ensure the port is exposed in Docker ## License MIT - See LICENSE file for details # VT INSTALLATION Source: https://docs.vibetunnel.sh/web/docs/VT_INSTALLATION # VT Command Installation Guide The `vt` command is VibeTunnel's convenient wrapper that allows you to run any command with terminal sharing enabled. This guide explains how the installation works and how to manage it. ## Installation Behavior When you install VibeTunnel via npm, the `vt` command installation follows these rules: ### Global Installation (`npm install -g vibetunnel`) * **Checks for existing `vt` command** to avoid conflicts with other tools * If no `vt` command exists, creates it globally * If `vt` already exists, skips installation and shows a warning * You can still use `npx vt` or `vibetunnel fwd` as alternatives ### Local Installation (`npm install vibetunnel`) * Configures `vt` for local use only * Access via `npx vt` within your project ## Platform Support ### macOS and Linux * Creates a symlink to the `vt` script * Falls back to copying if symlink creation fails * Script is made executable automatically ### Windows * Creates a `.cmd` wrapper for proper command execution * Copies the actual script alongside the wrapper * Works with Command Prompt, PowerShell, and Git Bash ## Common Scenarios ### Existing VT Command If you already have a `vt` command from another tool: ```bash theme={null} # You'll see this warning during installation: ⚠️ A "vt" command already exists in your system VibeTunnel's vt wrapper was not installed to avoid conflicts You can still use "npx vt" or the full path to run VibeTunnel's vt ``` **Alternatives:** * Use `npx vt` (works globally if installed with -g) * Use `vibetunnel fwd` directly * Manually install to a different name (see below) ### Manual Installation If automatic installation fails or you want to customize: ```bash theme={null} # Find where npm installs global packages npm config get prefix # On macOS/Linux, create symlink manually ln -s $(npm root -g)/vibetunnel/bin/vt /usr/local/bin/vt # Or copy and rename to avoid conflicts cp $(npm root -g)/vibetunnel/bin/vt /usr/local/bin/vibetunnel-vt chmod +x /usr/local/bin/vibetunnel-vt ``` ### Force Reinstallation To force VibeTunnel to overwrite an existing `vt` command: ```bash theme={null} # Remove existing vt first rm -f $(which vt) # Then reinstall VibeTunnel npm install -g vibetunnel ``` ## Troubleshooting ### Permission Denied If you get permission errors during global installation: ```bash theme={null} # Option 1: Use a Node version manager (recommended) # With nvm: https://github.com/nvm-sh/nvm # With fnm: https://github.com/Schniz/fnm # Option 2: Change npm's default directory # See: https://docs.npmjs.com/resolving-eacces-permissions-errors ``` ### Command Not Found If `vt` is installed but not found: ```bash theme={null} # Check if npm bin directory is in PATH echo $PATH npm config get prefix # Add to your shell profile (.bashrc, .zshrc, etc.) export PATH="$(npm config get prefix)/bin:$PATH" ``` ### Windows Specific Issues * Ensure Node.js is in your system PATH * Restart your terminal after installation * Try using `vt.cmd` explicitly if `vt` doesn't work ## Uninstallation The `vt` command is removed automatically when you uninstall VibeTunnel: ```bash theme={null} npm uninstall -g vibetunnel ``` If it persists, remove manually: ```bash theme={null} rm -f $(which vt) # On Windows: del "%APPDATA%\npm\vt.cmd" ``` # Asciicast pruning Source: https://docs.vibetunnel.sh/web/docs/asciicast-pruning # Asciicast Pruning in VibeTunnel ## Overview VibeTunnel implements an intelligent pruning system to prevent session recordings from growing indefinitely. This is critical for long-running terminal sessions (like Claude Code sessions) that can generate gigabytes of output over time. The pruning system detects terminal clear operations and uses them as safe points to discard old content. ## The Problem Terminal sessions can run for hours or days, generating massive amounts of output: * A typical Claude Code session can produce 100MB+ of output per hour * Without pruning, session files can grow to several gigabytes * Large files cause performance issues for streaming and playback * Most of the old content is no longer relevant after screen clears ## How Pruning Works ### 1. Real-time Detection During Recording When a PTY session is created (in the forwarder process), the `AsciinemaWriter` monitors all terminal output for pruning sequences: ```typescript theme={null} // In AsciinemaWriter.writeOutput() const detection = detectLastPruningSequence(processedData); if (detection) { const exactPosition = calculateSequenceBytePosition(...); this.pruningCallback({ sequence: detection.sequence, position: exactPosition, timestamp: time }); } ``` ### 2. Pruning Sequences The system recognizes these ANSI escape sequences as safe pruning points: * `\x1b[3J` - Clear scrollback buffer (most common in modern terminals) * `\x1bc` - Terminal reset (RIS - Reset to Initial State) * `\x1b[2J` - Clear screen * `\x1b[H\x1b[J` - Home cursor + clear (older pattern) * `\x1b[H\x1b[2J` - Home cursor + clear screen variant * `\x1b[?1049h` - Enter alternate screen (vim, less, etc) * `\x1b[?1049l` - Exit alternate screen * `\x1b[?47h` - Save screen and enter alternate screen (legacy) * `\x1b[?47l` - Restore screen and exit alternate screen (legacy) ### 3. Byte Position Tracking The `AsciinemaWriter` maintains precise byte position tracking: ```typescript theme={null} private bytesWritten: number = 0; // Bytes actually written to disk private pendingBytes: number = 0; // Bytes queued but not yet written getPosition(): { written: number; pending: number; total: number } { return { written: this.bytesWritten, pending: this.pendingBytes, total: this.bytesWritten + this.pendingBytes }; } ``` This is crucial because: * Asciinema files use JSON encoding, which changes byte counts * UTF-8 encoding means character count ≠ byte count * We need exact byte positions to safely resume streaming ### 4. Position Calculation When a pruning sequence is detected, we calculate its exact byte position in the file: ```typescript theme={null} function calculateSequenceBytePosition( eventStartPos: number, // Where this event starts in the file timestamp: number, // Event timestamp fullData: string, // Complete output data sequenceIndex: number, // Character index of sequence in data sequenceLength: number // Length of the sequence ): number { // Calculate data up to sequence end const dataUpToSequenceEnd = fullData.substring(0, sequenceIndex + sequenceLength); // Create event prefix: [timestamp,"o"," const eventPrefix = JSON.stringify([timestamp, 'o', '']).slice(0, -1); const prefixBytes = Buffer.from(eventPrefix, 'utf8').length; // Calculate bytes for data portion const sequenceBytesInData = Buffer.from(dataUpToSequenceEnd, 'utf8').length; return eventStartPos + prefixBytes + sequenceBytesInData; } ``` ### 5. Storing Pruning Information When a pruning sequence is detected, the `PtyManager` updates the session info: ```typescript theme={null} asciinemaWriter.onPruningSequence(async ({ sequence, position }) => { const sessionInfo = this.sessionManager.loadSessionInfo(sessionId); if (sessionInfo) { sessionInfo.lastClearOffset = position; await this.sessionManager.saveSessionInfo(sessionId, sessionInfo); } }); ``` ### 6. Using Pruning During Playback When a client connects to follow a session, the `CastOutputHub`: 1. Reads the stored `lastClearOffset` from session info 2. Starts reading the asciicast file from that position instead of the beginning 3. This skips all the old content before the last clear ```typescript theme={null} // In CastOutputHub.sendExistingContent() const sessionInfo = this.sessionManager.loadSessionInfo(sessionId); let startOffset = sessionInfo?.lastClearOffset ?? 0; const analysisStream = fs.createReadStream(streamPath, { encoding: 'utf8', start: startOffset, // Start from last clear position }); ``` ### 7. Retroactive Pruning Detection The `CastOutputHub` also scans for pruning sequences when analyzing existing content: ```typescript theme={null} if (isOutputEvent(event) && containsPruningSequence(event[2])) { const clearResult = this.processClearSequence( event as AsciinemaOutputEvent, events.length, fileOffset, currentResize, line ); if (clearResult) { lastClearIndex = clearResult.lastClearIndex; lastClearOffset = clearResult.lastClearOffset; } } ``` This handles cases where: * A session was recorded without pruning detection * Multiple clear sequences exist in the buffered content * We need to find the most recent clear point ## Architecture ### Component Responsibilities 1. **PruningDetector** (`utils/pruning-detector.ts`) * Single source of truth for pruning sequences * Provides detection and position calculation functions * Ensures consistency between components 2. **AsciinemaWriter** (`pty/asciinema-writer.ts`) * Real-time detection during recording * Precise byte position tracking * Invokes callbacks when sequences detected 3. **PtyManager** (`pty/pty-manager.ts`) * Registers pruning callbacks * Updates session info with clear offsets * Coordinates between writer and session manager 4. **CastOutputHub** (`services/cast-output-hub.ts`) * Uses stored pruning offsets for efficient follow/initial replay * Performs retroactive detection on existing content * Handles replay from pruning points ### Data Flow ``` Terminal Output ↓ AsciinemaWriter (in forwarder process) ├─→ Writes to .cast file └─→ Detects pruning sequences ↓ PtyManager ├─→ Updates session.json with lastClearOffset └─→ Logs detection When client connects: ↓ CastOutputHub (in server process) ├─→ Reads lastClearOffset from session.json └─→ Starts streaming from that position ``` ## Benefits 1. **Prevents Unbounded Growth**: Session files stay manageable even for long-running sessions 2. **Improves Performance**: Clients don't need to download/process gigabytes of old data 3. **Preserves User Experience**: Users see current terminal state, not irrelevant history 4. **Automatic**: Works transparently without user intervention 5. **Safe**: Only prunes at explicit clear points, never loses important data ## Testing The pruning system includes comprehensive tests: 1. **Unit Tests** (`test/unit/pruning-detector.test.ts`) * Sequence detection accuracy * Byte position calculation * UTF-8 handling 2. **Integration Tests** (`test/unit/asciinema-writer.test.ts`) * Real-time detection during writes * Callback timing and accuracy * File position validation ## Debugging To debug pruning: 1. Check for pruning detection in logs: ```bash theme={null} grep -i "pruning" ~/.vibetunnel/log.txt ``` 2. Verify session info: ```bash theme={null} cat ~/.vibetunnel/sessions/*/session.json | jq .lastClearOffset ``` 3. Enable debug logging to see detailed pruning calculations: ```bash theme={null} export VIBETUNNEL_VERBOSITY=debug ``` ## Limitations 1. **Requires Forwarder Restart**: Pruning runs in the forwarder process, so existing sessions won't benefit until restarted 2. **Clear Sequence Dependent**: Only prunes when terminal is explicitly cleared 3. **No Manual Pruning**: Currently no way to manually trigger pruning 4. **Single Pruning Point**: Only tracks the most recent clear, not multiple checkpoints ## Future Improvements 1. **Multiple Checkpoints**: Track several pruning points for more granular history 2. **Time-based Pruning**: Prune content older than X hours 3. **Size-based Pruning**: Trigger pruning when file exceeds certain size 4. **Compression**: Compress old segments instead of discarding 5. **Manual Pruning API**: Allow users to explicitly mark pruning points ## Performance Analysis: Old vs New Pruning Logic (2025-07-27) ### Old Implementation (Before commit 627309ebf) **Architecture:** * Pruning detection was **duplicated** in 3 places: 1. `pty-manager.ts` - During data write (imprecise) 2. `stream-watcher.ts` - During playback (retroactive) 3. Inline sequence definitions in multiple files **Performance Issues:** 1. **Double Processing**: Data was scanned for pruning sequences twice: * Once in pty-manager during write (but couldn't calculate accurate positions) * Again in stream-watcher during playback 2. **Inefficient String Searching**: Multiple `lastIndexOf()` calls on potentially large strings 3. **Imprecise Byte Calculations**: PTY manager couldn't track exact byte positions 4. **Memory Overhead**: Entire file had to be re-read and parsed during playback ### New Implementation (After commit 627309ebf) **Architecture:** * Centralized pruning detection in `pruning-detector.ts` * Real-time detection in `asciinema-writer.ts` * Precise byte position tracking **Performance Improvements:** 1. **Single-Pass Detection**: * Pruning sequences detected **once** during write * Exact byte positions calculated and potentially stored * No need to re-scan during playback 2. **Optimized Detection**: ```typescript theme={null} // New centralized detection export function detectLastPruningSequence(data: string): PruningDetectionResult | null { let lastIndex = -1; let lastSequence = ''; for (const sequence of PRUNE_SEQUENCES) { const index = data.lastIndexOf(sequence); if (index > lastIndex) { lastIndex = index; lastSequence = sequence; } } // Single pass through sequences } ``` 3. **Precise Byte Tracking**: ```typescript theme={null} // New precise calculation export function calculateSequenceBytePosition( eventStartPos: number, timestamp: number, fullData: string, sequenceIndex: number, sequenceLength: number ): number { // Exact byte-level calculation } ``` ### Performance Comparison | Aspect | Old Logic | New Logic | Improvement | | --------------------- | --------------------------- | -------------------- | ------------------- | | **Detection Timing** | Retroactive (on playback) | Real-time (on write) | ✅ No playback delay | | **Processing Passes** | 2 (write + read) | 1 (write only) | ✅ 50% reduction | | **Byte Accuracy** | Approximate | Exact | ✅ Precise pruning | | **Memory Usage** | Re-read entire file | Stream processing | ✅ Lower memory | | **CPU Usage** | O(n) on each client connect | O(1) lookup | ✅ Much faster | | **Code Duplication** | 3 implementations | 1 centralized | ✅ Maintainable | ### Real-World Impact For a session with 18MB of data (like the example log showing offset 18,223,170): **Old System:** * Client connects → Read 18MB file → Scan for pruning sequences → Skip 20k events * Time: \~100-500ms depending on disk speed **New System:** * Client connects → Read pre-calculated offset → Start streaming from position * Time: \~1-10ms ### Conclusion The new pruning logic is **significantly faster** because: 1. **Eliminates redundant processing** - Detection happens once, not on every playback 2. **Reduces I/O** - No need to read/parse the entire file to find prune points 3. **Improves scalability** - O(1) vs O(n) for client connections 4. **Better accuracy** - Exact byte positions prevent edge cases The performance improvement is especially noticeable for: * Large session files (10MB+) * Multiple concurrent viewers * Sessions with many clear operations # Multiplexer integration Source: https://docs.vibetunnel.sh/web/docs/multiplexer-integration # Terminal Multiplexer Integration VibeTunnel supports seamless integration with terminal multiplexers like tmux, Zellij, and GNU Screen, allowing you to attach to existing sessions and manage them through the web interface. ## Overview The multiplexer integration allows you to: * List and attach to existing tmux/Zellij/Screen sessions * Navigate between windows and panes (tmux) * Create new sessions * Kill sessions, windows (tmux), and panes (tmux) * Maintain persistent terminal sessions across connections ## Supported Multiplexers ### tmux * Full support for sessions, windows, and panes * Shows session details including creation time, attached status, and window count * Navigate to specific windows and panes * Create sessions with optional initial commands * Kill individual panes, windows, or entire sessions ### Zellij * Session management with creation time tracking * Automatic session creation on first attach * Layout support for new sessions * ANSI color code handling in session names * Proper cleanup of exited sessions ### GNU Screen * Session listing and management * Shows session status (attached/detached) * Create new sessions with optional commands * Attach to existing sessions * Kill sessions ## Usage ### Accessing Multiplexer Sessions 1. Click the terminal icon in the session list 2. The multiplexer modal will open showing available sessions 3. For tmux sessions, expand to see windows and panes 4. Click "Attach" to connect to any session, window, or pane ### Creating New Sessions #### tmux ```bash theme={null} # Create a new session POST /api/multiplexer/sessions { "type": "tmux", "name": "dev-session", "command": "vim" // optional initial command } ``` #### Zellij ```bash theme={null} # Create a new session (created on first attach) POST /api/multiplexer/sessions { "type": "zellij", "name": "dev-session", "layout": "compact" // optional layout } ``` #### GNU Screen ```bash theme={null} # Create a new session POST /api/multiplexer/sessions { "type": "screen", "name": "dev-session", "command": "vim" // optional initial command } ``` ### API Endpoints #### Get Multiplexer Status ```bash theme={null} GET /api/multiplexer/status ``` Returns the availability and session list for all multiplexers. #### Get tmux Windows ```bash theme={null} GET /api/multiplexer/tmux/sessions/:session/windows ``` Returns all windows in a tmux session. #### Get tmux Panes ```bash theme={null} GET /api/multiplexer/tmux/sessions/:session/panes?window=:windowIndex ``` Returns panes in a session or specific window. #### Attach to Session ```bash theme={null} POST /api/multiplexer/attach { "type": "tmux|zellij|screen", "sessionName": "main", "windowIndex": 0, // tmux only, optional "paneIndex": 1, // tmux only, optional "cols": 120, // optional terminal dimensions "rows": 40 } ``` #### Kill Session ```bash theme={null} DELETE /api/multiplexer/:type/sessions/:sessionName ``` #### Kill Window (tmux only) ```bash theme={null} DELETE /api/multiplexer/tmux/sessions/:sessionName/windows/:windowIndex ``` #### Kill Pane (tmux only) ```bash theme={null} DELETE /api/multiplexer/tmux/sessions/:sessionName/panes/:paneId ``` ### Legacy tmux API Compatibility The following legacy endpoints are maintained for backward compatibility: * `GET /api/tmux/sessions` - List tmux sessions * `POST /api/tmux/attach` - Attach to tmux session ## Implementation Details ### Architecture The multiplexer integration consists of: * `MultiplexerManager` - Unified interface for all multiplexers * `TmuxManager` - tmux-specific implementation * `ZellijManager` - Zellij-specific implementation * `ScreenManager` - GNU Screen-specific implementation * `multiplexer-modal` - LitElement component for the UI ### Session Attachment When attaching to a multiplexer session: 1. A new VibeTunnel PTY session is created 2. The session runs the appropriate attach command: * tmux: `tmux attach-session -t main` * Zellij: `zellij attach main` * Screen: `screen -r 12345.main` 3. The multiplexer takes over the terminal, providing its own UI 4. Users can navigate within the multiplexer using native keybindings ### Key Features #### Automatic Detection The system automatically detects installed multiplexers and only shows available options. #### Session Persistence Multiplexer sessions persist even when VibeTunnel is restarted, allowing you to maintain long-running processes. #### Native Experience Once attached, you interact with the multiplexer using its native keybindings: * tmux: `Ctrl-B` (default prefix) * Zellij: `Ctrl-G` (default prefix) * Screen: `Ctrl-A` (default prefix) #### Clean Session Names Zellij session names are automatically cleaned of ANSI escape codes for better display. #### Kill Confirmation All destructive actions (killing sessions, windows, panes) require confirmation to prevent accidental data loss. ## Best Practices 1. **Use descriptive session names** - Makes it easier to identify sessions later 2. **Organize with windows** (tmux) - Group related tasks in different windows 3. **Leverage layouts** (Zellij) - Use predefined layouts for common workflows 4. **Clean up old sessions** - Kill sessions you're no longer using to free resources ## Troubleshooting ### Sessions Not Showing * Ensure tmux/Zellij/Screen is installed on the system * Check that sessions exist by running: * tmux: `tmux ls` * Zellij: `zellij list-sessions` * Screen: `screen -ls` ### Cannot Attach to Session * Verify the session name is correct * Check if the session is already attached elsewhere (some configurations prevent multiple attachments) ### Display Issues * Ensure terminal dimensions match between client and server * Try resizing the browser window to trigger a resize event ### Screen-Specific Issues * Screen returns exit code 1 when sessions exist (this is normal behavior) * Session names include PID prefix (e.g., `12345.session-name`) * Use `screen -R` instead of `screen -r` for more forgiving reattachment # Npm Source: https://docs.vibetunnel.sh/web/docs/npm # VibeTunnel NPM Package Distribution This document explains the npm package build process, native module handling, and prebuild system for VibeTunnel. ## Overview VibeTunnel is distributed as an npm package that includes: * Full web server with terminal sharing capabilities * Native modules for terminal (PTY) and authentication (PAM) support * Cross-platform prebuilt binaries to avoid requiring build tools * Command-line tools (`vibetunnel` and `vt`) ## Package Structure ``` vibetunnel/ ├── dist/ # Compiled server code ├── public/ # Web interface assets ├── bin/ # CLI entry points │ ├── vibetunnel # Main server executable │ └── vt # Terminal wrapper command ├── node-pty/ # Vendored PTY implementation │ ├── lib/ # TypeScript compiled code │ └── package.json # PTY package configuration ├── prebuilds/ # Native module prebuilt binaries │ ├── node-pty-* # PTY binaries for all platforms/Node versions │ └── authenticate-pam-* # PAM binaries for all platforms/Node versions └── README.md # Package documentation ``` ## Native Modules VibeTunnel requires two native modules: ### 1. node-pty (Terminal Support) * **Purpose**: Provides pseudo-terminal (PTY) functionality * **Components**: * `pty.node`: Main Node.js addon for terminal operations * `spawn-helper`: macOS-only C helper binary for process spawning * **Platforms**: All (macOS, Linux) * **Dependencies**: None (vendored implementation) ### 2. authenticate-pam (Authentication) * **Purpose**: PAM (Pluggable Authentication Modules) integration for system authentication * **Components**: * `authenticate_pam.node`: Node.js addon for system authentication * `node_modules/authenticate-pam/`: Full module with package.json, binding.gyp, and source files * **Platforms**: Both macOS and Linux * **Dependencies**: System PAM libraries * **Note**: While macOS uses different authentication mechanisms internally (OpenDirectory), VibeTunnel attempts PAM authentication on both platforms as a fallback after SSH key authentication * **Critical**: The entire authenticate-pam module directory must be included in the npm package, not just the prebuilds ## Prebuild System ### Overview We use `prebuild` and `prebuild-install` to provide precompiled native modules, eliminating the need for users to have build tools installed. ### Coverage * **Node.js versions**: 22, 24 * **Platforms**: macOS (x64, arm64), Linux (x64, arm64) * **Total prebuilds**: 12 binaries * node-pty: 8 binaries (macOS and Linux, all architectures) * authenticate-pam: 4 binaries (Linux only - macOS builds may fail due to PAM differences) ### Prebuild Files ``` prebuilds/ ├── node-pty-v1.0.0-node-v127-darwin-arm64.tar.gz ├── node-pty-v1.0.0-node-v127-darwin-x64.tar.gz ├── node-pty-v1.0.0-node-v127-linux-arm64.tar.gz ├── node-pty-v1.0.0-node-v127-linux-x64.tar.gz ├── authenticate-pam-v1.0.5-node-v127-linux-arm64.tar.gz ├── authenticate-pam-v1.0.5-node-v127-linux-x64.tar.gz └── ... (similar for Node.js 24) ``` Note: Node version numbers map to internal versions (v127=Node 22, v137=Node 24) ## Build Process Repository builds require rustup. The native forwarder uses the Rust toolchain pinned by `native/vt-fwd/rust-toolchain.toml`; packaged users receive a prebuilt binary. ### Clean Build Approach The npm build process uses a clean distribution directory approach that follows npm best practices: 1. **Creates dist-npm/ directory** - Separate from source files 2. **Generates clean package.json** - Only production fields, no dev dependencies 3. **Bundles dependencies** - node-pty is bundled directly, no symlinks needed 4. **Preserves source integrity** - Never modifies source package.json ### Unified Build (Multi-Platform by Default on macOS) ```bash theme={null} npm run build:npm ``` * Compiles TypeScript and bundles client code * Builds native modules for all supported platforms (macOS x64/arm64, Linux x64/arm64) * Requires macOS because Darwin forwarders must be built and verified natively * Creates comprehensive prebuilds for zero-dependency installation * Generates npm README optimized for package distribution * Creates clean dist-npm/ directory for packaging ### Build Options The unified build script supports flexible targeting: ```bash theme={null} # Default on macOS: All platforms npm run build:npm # Current platform only (use this on Linux for local validation) node scripts/build-npm.js --current-only # Specific platform/architecture node scripts/build-npm.js --platform darwin --arch arm64 node scripts/build-npm.js --platform linux # Skip Docker (Linux builds will be skipped) node scripts/build-npm.js --no-docker ``` ### Docker Requirements For Linux builds, Docker is required: * **Recommended**: [OrbStack](https://orbstack.dev/) * **Alternative**: [Docker Desktop](https://www.docker.com/products/docker-desktop/) The build will fail with helpful error messages if Docker is not available. ## Installation Process ### For End Users 1. **Install package**: `npm install -g vibetunnel` 2. **Postinstall script runs**: Extracts appropriate prebuilt binaries 3. **No compilation needed**: Prebuilds included for all supported platforms 4. **Result**: Working VibeTunnel installation without build tools ### Key Improvements * **No symlinks**: node-pty is bundled directly, avoiding postinstall symlink issues * **Clean package structure**: Only production files in the npm package * **Reliable installation**: Works in restricted environments (Docker, CI) ### Installation Scripts The package uses a simplified postinstall approach: ```json theme={null} { "scripts": { "postinstall": "node scripts/postinstall.js" } } ``` #### Postinstall Process * **Prebuild extraction**: Extracts the appropriate prebuild for the current platform * **No downloads**: All prebuilds are included in the package * **No compilation**: Everything is pre-built, no build tools required * **Platform detection**: Automatically selects correct binary based on OS and architecture ## Platform-Specific Details ### macOS * **spawn-helper**: Additional C binary needed for proper PTY operations (now prebuilt as universal binary) * **Authentication**: Attempts PAM authentication but may fall back to environment variables or SSH keys * **Architecture**: Supports both Intel (x64) and Apple Silicon (arm64) * **Build tools**: Not required with prebuilds; Xcode Command Line Tools only needed for source compilation fallback ### Linux * **PAM authentication**: Full support via authenticate-pam module * **PAM libraries**: Requires `libpam0g-dev` for authenticate-pam compilation from source * **spawn-helper**: Not used on Linux (macOS-only) * **Build tools**: Not required with prebuilds; `build-essential` only needed for source compilation fallback ### Docker Build Environment Linux prebuilds are created using Docker with: * **Base image**: `node:22-bookworm` * **Dependencies**: `python3 make g++ git libpam0g-dev` * **Package manager**: pnpm (more reliable than npm in Docker) * **Environment**: `CI=true` to avoid interactive prompts ## spawn-helper Binary ### What is spawn-helper? `spawn-helper` is a small C helper binary used by node-pty for proper terminal process spawning on macOS. ### Key Facts * **Size**: \~70KB pure C binary * **Platform**: macOS only (Linux doesn't use it) * **Purpose**: Handles terminal device attachment for spawned processes * **Dependencies**: None (pure C, no Node.js dependencies) * **Architecture**: Platform-specific (x64 vs arm64) ### Source Code ```c theme={null} // Simplified version of spawn-helper functionality int main (int argc, char** argv) { char *slave_path = ttyname(STDIN_FILENO); close(open(slave_path, O_RDWR)); // Attach to terminal char *cwd = argv[1]; char *file = argv[2]; argv = &argv[2]; if (strlen(cwd) && chdir(cwd) == -1) { _exit(1); } execvp(file, argv); // Execute the target command return 1; } ``` ### Installation Handling * **Current approach**: Universal spawn-helper binary included in prebuilds (macOS only) * **Benefits**: No compilation needed, faster installation, works without build tools * **Fallback path**: If prebuild fails, compilation happens automatically via node-gyp * **Error handling**: Non-fatal if missing (warns but continues) ### Universal Binary Implementation spawn-helper is now shipped as a prebuilt universal binary in all macOS prebuilds: **Implementation**: * Built for both x64 and arm64 architectures using clang++ * Combined into universal binary with `lipo` * Included in every macOS node-pty prebuild automatically **Benefits**: * ✅ Faster installation (no compilation needed) * ✅ Works without build tools (Xcode Command Line Tools) * ✅ Universal compatibility across Intel and Apple Silicon Macs * ✅ Smaller download than compiling during install **Build process**: ```bash theme={null} # Build for both architectures clang++ -arch x86_64 -o spawn-helper-x64 spawn-helper.cc clang++ -arch arm64 -o spawn-helper-arm64 spawn-helper.cc # Create universal binary lipo -create spawn-helper-x64 spawn-helper-arm64 -output spawn-helper-universal # Include in all macOS prebuilds ``` ## Package Optimization ### File Exclusions Development artifacts are excluded from the final package: * Test files (`public/bundle/test.js`, `public/test/` directory) * Recording files (`*.cast` prevented by .gitignore) * Build artifacts (`dist/` selectively included via package.json `files` field) ### Size Optimization * **Final size**: \~8.5 MB * **File count**: \~275 files * **Prebuilds**: Included for zero-build installation experience * **Source code**: Minimal, compiled assets only ## Development Commands ### Local Development ```bash theme={null} # Multi-platform build with prebuilds (macOS) npm run build:npm # Single-platform build for local testing node scripts/build-npm.js --current-only # Test package locally npm pack # Verify package contents tar -tzf vibetunnel-*.tgz | head -20 ``` ### Quality Checks Always run before publishing: ```bash theme={null} pnpm run lint # Check code style pnpm run typecheck # Verify TypeScript ``` ## Publishing ### Prerequisites 1. Update version in `package.json` 2. Run multi-platform build 3. Test package locally 4. Verify all prebuilds are included ### Publish Command ```bash theme={null} npm publish ``` ## Usage After Installation ### Installation ```bash theme={null} # Install globally npm install -g vibetunnel ``` ### Starting the Server ```bash theme={null} # Start with default settings (port 4020) vibetunnel # Start with custom port vibetunnel --port 8080 # Start without authentication vibetunnel --no-auth ``` Then open [http://localhost:4020](http://localhost:4020) in your browser to access the web interface. ### Using the vt Command ```bash theme={null} # Run Claude with the vt wrapper vt claude vt claude --dangerously-skip-permissions # Run commands with output visible in VibeTunnel vt npm test vt python script.py vt top # Launch interactive shell vt --shell vt -i # Update session title (inside a session) vt title "My Project" ``` ### Command Forwarding ```bash theme={null} # Basic usage vibetunnel fwd [args...] # Examples vibetunnel fwd --session-id abc123 ls -la vibetunnel fwd --session-id abc123 npm test vibetunnel fwd --session-id abc123 python script.py ``` ## Coexistence with Mac App The npm package works seamlessly alongside the Mac app: ### Command Routing * The `vt` command from npm automatically detects if the Mac app is installed * If Mac app found at `/Applications/VibeTunnel.app`, npm `vt` defers to it * Ensures you always get the best available implementation ### Installation Behavior * Won't overwrite existing `/usr/local/bin/vt` from other tools * Provides helpful warnings if conflicts exist * Installation always succeeds, even if `vt` symlink can't be created * Use `vibetunnel` or `npx vt` as alternatives ## Build Process Validation ### Ensuring authenticate-pam Module is Included **Critical**: The authenticate-pam module must be copied to the npm package during build. This was missing in beta 14 due to a hardcoded pnpm path issue. #### What to Check Before Publishing 1. **Verify authenticate-pam is installed**: ```bash theme={null} # Check if the module exists (may be a symlink) ls -la node_modules/authenticate-pam/ ``` 2. **Run the build and check output**: ```bash theme={null} npm run build:npm # Look for: "✅ authenticate-pam module copied to dist-npm for Linux PAM auth" # If you see: "⚠️ authenticate-pam source not found", the module won't be included ``` 3. **Verify in the built package**: ```bash theme={null} # Check if authenticate-pam was copied ls -la dist-npm/node_modules/authenticate-pam/ # Should contain: package.json, binding.gyp, authenticate_pam.cc, etc. ``` 4. **Test the package contents**: ```bash theme={null} # Create package and verify cd dist-npm && npm pack tar -tzf vibetunnel-*.tgz | grep authenticate-pam # Should show: package/node_modules/authenticate-pam/... ``` #### How the Build Script Works The `copyAuthenticatePam()` function in `scripts/build-npm.js`: * Searches multiple possible locations for the module (direct node\_modules, pnpm structures) * Uses `fs.statSync()` to properly follow symlinks * Logs which path was found or lists all searched paths if not found * Copies the entire module directory to `dist-npm/node_modules/authenticate-pam/` #### If authenticate-pam is Missing 1. **Ensure it's installed**: Run `pnpm install` to install all dependencies 2. **Check for optional dependency issues**: authenticate-pam is listed as a regular dependency, not optional 3. **Verify pnpm didn't clean it**: Sometimes pnpm removes unused modules during cleanup 4. **Force reinstall if needed**: `pnpm install authenticate-pam` ## Troubleshooting ### Common Issues #### Missing Build Tools **Error**: `gyp ERR! stack Error: not found: make` **Solution**: Install build tools: * **macOS**: `xcode-select --install` * **Linux**: `apt-get install build-essential` #### Missing PAM Development Libraries **Error**: `fatal error: security/pam_appl.h: No such file or directory` **Solution**: Install PAM development libraries: * **Linux**: `apt-get install libpam0g-dev` * **macOS**: Usually available by default #### Docker Not Available **Error**: `Docker is required for multi-platform builds` **Solution**: Install Docker using OrbStack or Docker Desktop #### Prebuild Download Failures **Error**: `prebuild-install warn install No prebuilt binaries found` **Cause**: Network issues or unsupported platform/Node version **Result**: Automatic fallback to source compilation #### npm\_config\_prefix Conflict with NVM **Error**: Global npm installs fail or install to wrong location when using NVM **Symptoms**: * `npm install -g` installs packages to system location instead of NVM directory * Command not found errors after global install * Permission errors during global installation **Cause**: The `npm_config_prefix` environment variable overrides NVM's per-version npm configuration **Detection**: VibeTunnel's postinstall script will warn if this conflict is detected: ``` ⚠️ Detected npm_config_prefix conflict with NVM npm_config_prefix: /usr/local NVM Node path: /home/user/.nvm/versions/node/v20.19.4/bin/node This may cause npm global installs to fail or install in wrong location. ``` **Solution**: Unset the conflicting environment variable: ```bash theme={null} unset npm_config_prefix ``` **Permanent fix**: Remove or comment out `npm_config_prefix` settings in: * `~/.bashrc` * `~/.bash_profile` * `~/.profile` * `/etc/profile` * CI/CD environment configurations **Common sources of this issue**: * Previous system-wide npm installations * Docker containers with npm pre-installed * CI/CD environments with global npm configuration * Package managers that set global npm prefix ### Debugging Installation ```bash theme={null} # Verbose npm install npm install -g vibetunnel --verbose # Check prebuild availability npx prebuild-install --list # Force source compilation npm install -g vibetunnel --build-from-source ``` ## Architecture Decisions ### Why Prebuilds? * **User experience**: No build tools required for most users * **Installation speed**: Pre-compiled binaries install much faster * **Reliability**: Eliminates compilation errors in user environments * **Cross-platform**: Supports all target platforms without user setup ### Why Docker for Linux Builds? * **Cross-compilation**: Build Linux binaries from macOS development machine * **Consistency**: Reproducible build environment * **Dependencies**: Proper PAM library versions for Linux ### Why Vendored node-pty? * **Control**: Custom modifications for VibeTunnel's needs * **Reliability**: Avoid external dependency issues * **Optimization**: Minimal implementation without unnecessary features ## Related Files * `scripts/build-npm.js` - Unified npm build process with multi-platform support * `scripts/postinstall-npm.js` - Fallback compilation logic * `.prebuildrc` - Prebuild configuration for target platforms * `package.json` - Package configuration and file inclusions ## Release Notes ### Version 1.0.0-beta.14.1 (2025-07-21) **Published to npm**: Successfully published as both `vibetunnel@beta` and `vibetunnel@latest` **Critical Fix**: * Fixed missing authenticate-pam module that was excluded from the npm package in beta.14 * The build script now properly detects authenticate-pam in various pnpm directory structures **Package Details**: * Package size: 14.8 MB (34.4 MB unpacked) * Contains 234 files including all prebuilds and web assets * Includes all 24 prebuilds (16 node-pty + 8 authenticate-pam) * authenticate-pam module is now properly bundled in `node_modules/authenticate-pam/` **Installation**: ```bash theme={null} # Install latest (now 1.0.0-beta.14.1 with the fix) npm install -g vibetunnel # Or install beta specifically npm install -g vibetunnel@beta # Or install specific version npm install -g vibetunnel@1.0.0-beta.14.1 ``` **Build Script Improvements**: * Enhanced `copyAuthenticatePam()` function to search multiple pnpm locations * Added comprehensive logging to track which paths are searched * Properly follows symlinks with `fs.statSync()` for accurate module detection **Verification**: The build now includes clear output confirming authenticate-pam inclusion: ``` ✅ authenticate-pam module copied to dist-npm for Linux PAM auth ``` ### Version 1.0.0-beta.13 (2025-07-19) **Published to npm**: Successfully published as both `vibetunnel@beta` and `vibetunnel@latest` **Key Features**: * All features from previous releases maintained * Updated to match macOS app version 1.0.0-beta.13 * Full cross-platform support with prebuilt binaries * Zero-dependency installation experience **Package Details**: * Package size: 15.5 MB (37.1 MB unpacked) * Contains 235 files including all prebuilds and web assets * Includes prebuilds for Node.js 22 and 24 **Installation**: ```bash theme={null} # Install latest (now 1.0.0-beta.13) npm install -g vibetunnel # Or install beta specifically npm install -g vibetunnel@beta ``` ### Version 1.0.0-beta.11 (2025-07-16) **Published to npm**: Successfully published as `vibetunnel@beta` **Key Features**: * Cross-platform support for macOS (x64, arm64) and Linux (x64, arm64) * Pre-built native binaries for Node.js versions 20, 22, 23, and 24 * Zero-dependency installation experience (no build tools required) * Comprehensive prebuild system with 24 total binaries included **Release Process Learnings**: 1. **Version Synchronization**: * Must update version in both `web/package.json` and `mac/VibeTunnel/version.xcconfig` * Build process validates version sync to prevent mismatches * Version mismatch will cause build failure with clear error message 2. **NPM Publishing Requirements**: * Beta versions require `--tag beta` flag when publishing * Previously published versions cannot be overwritten (must increment version) * Use `--access public` flag for public package publishing 3. **Package Build Process**: * `pnpm run build:npm` creates the complete package with all prebuilds * Build output filename may show older version in logs but creates correct package * Always verify package version in `dist-npm/package.json` before publishing 4. **Docker Testing Verification**: * Successfully tested on Ubuntu 22.04 (both ARM64 and x64 architectures) * Installation works without any build tools installed * Server starts correctly with all expected functionality * HTTP endpoints respond properly 5. **Package Structure**: * Final package size: 8.3 MB (24.9 MB unpacked) * Contains 198 files including all prebuilds and web assets * Proper postinstall script ensures seamless installation **Installation**: ```bash theme={null} npm install -g vibetunnel@beta ``` **Testing Commands Used**: ```bash theme={null} # Build the package cd web && pnpm run build:npm # Verify package contents tar -tzf vibetunnel-1.0.0-beta.11.tgz | head -50 # Test with Docker docker build -t vibetunnel-test . docker run --rm vibetunnel-test # Test cross-platform docker run --rm --platform linux/amd64 vibetunnel-test ``` ### Version History * **1.0.0-beta.14.1** (2025-07-21): Fixed authenticate-pam module missing from npm package (patch release) * **1.0.0-beta.14** (2025-07-21): macOS app release (npm package had missing authenticate-pam module) * **1.0.0-beta.13** (2025-07-19): Synchronized with macOS app version * **1.0.0-beta.12.1** (2025-07-17): Minor updates and fixes * **1.0.0-beta.12** (2025-07-17): Package structure improvements * **1.0.0-beta.11.1** (2025-07-16): Fixed npm installation issues * **1.0.0-beta.11** (2025-07-16): Initial release with full prebuild system * **1.0.0-beta.10** (2025-07-14): Previous version (unpublished) ## NPM Distribution Tags VibeTunnel uses npm dist-tags to manage different release channels: ### Current Tags * **latest**: Points to the most stable release (currently 1.0.0-beta.14.1) * **beta**: Points to the latest beta release (currently 1.0.0-beta.14.1) ### Managing Tags ```bash theme={null} # View current tags npm dist-tag ls vibetunnel # Set a version as latest npm dist-tag add vibetunnel@1.0.0-beta.13 latest # Add a new tag npm dist-tag add vibetunnel@1.0.0-beta.14 next # Remove a tag npm dist-tag rm vibetunnel next ``` ### Installation by Tag ```bash theme={null} # Install latest stable (default) npm install -g vibetunnel # Install specific tag npm install -g vibetunnel@beta npm install -g vibetunnel@latest # Install specific version npm install -g vibetunnel@1.0.0-beta.11.1 ``` ### Best Practices * Always tag beta releases with `beta` tag * Only promote to `latest` after testing confirms stability * Use semantic versioning for beta iterations (e.g., 1.0.0-beta.11.1, 1.0.0-beta.11.2) # Performance Source: https://docs.vibetunnel.sh/web/docs/performance # Performance Architecture ## Session Management Models VibeTunnel supports two distinct session management approaches, each with different performance characteristics: ### 1. Server-Managed Sessions (API-initiated) Sessions created via `POST /api/sessions` are spawned directly within the server's Node.js process. These sessions benefit from: * **Direct PTY communication**: Input and resize commands bypass the command pipe system * **Reduced latency**: No inter-process communication overhead for terminal interactions * **Immediate responsiveness**: Direct memory access to PTY stdout/stdin ### 2. External Sessions (vt-initiated) Sessions started via the `vt` command run in a separate Node.js process with: * **File-based communication**: PTY stdout is written to disk files * **Command pipe interface**: Resize and input commands are passed through IPC * **Additional latency**: File I/O and IPC overhead for all terminal operations ## Server Architecture ### Session Discovery and Management The server performs two primary management tasks: 1. **External Session Monitoring** * Watches control directory for new external sessions * Automatically registers discovered sessions with the terminal manager * Maintains in-memory terminal buffer for text export + VT snapshots (WS v3 `SNAPSHOT_VT`) 2. **Client Connection Handling** * WebSocket connections trigger file watchers on session stdout files * File watchers stream new output to connected clients in real-time * Multiple clients can connect to the same session simultaneously ### Memory Management * **Buffer caching**: Last visible scrollbuffer (terminal dimensions) kept in memory * **Efficient retrieval**: `/api/sessions/:id/text` and WS v3 `SNAPSHOT_VT` serve from memory cache * **File streaming**: WebSocket clients receive updates via file watchers ## Known Performance Issues ### Session Creation Blocking **Symptom**: All sessions freeze temporarily when creating a new session **Cause**: Synchronous operations during session creation * Session creation endpoint waits for process spawn completion * PTY initialization must complete before returning * Any synchronous operation blocks the entire Node.js event loop **Impact**: All active sessions become unresponsive during new session initialization ### Potential Solutions 1. **Async session creation**: Move blocking operations to worker threads 2. **Pre-spawn PTY pool**: Maintain ready PTYs to reduce creation time 3. **Event loop monitoring**: Identify and eliminate synchronous operations 4. **Progressive initialization**: Return session ID immediately, initialize asynchronously ## Performance Optimization Strategies ### For Server-Managed Sessions * Minimize synchronous operations in session creation * Use Node.js worker threads for CPU-intensive tasks * Implement connection pooling for database operations ### For External Sessions * Consider memory-mapped files for stdout communication * Implement file change batching to reduce watcher overhead * Use efficient file formats (binary vs text) where appropriate ### General Optimizations * Profile event loop blocking with tools like `clinic.js` * Implement request queuing for session creation * Add performance metrics and monitoring * Consider horizontal scaling with session affinity # Plan Source: https://docs.vibetunnel.sh/web/docs/plan # VibeTunnel Mobile Terminal UI/UX Redesign Plan ## Executive Summary The current VibeTunnel mobile terminal experience suffers from viewport overlap issues, complex keyboard handling, and poor space utilization. This plan outlines a comprehensive redesign to create a mobile-first terminal experience that is intuitive, efficient, and respects device constraints. ### Key Goals * Eliminate all viewport overlaps on mobile devices * Simplify keyboard input to a single, coherent system * Ensure navigation remains accessible at all times * Optimize screen real estate for terminal content * Provide seamless transition between keyboard and non-keyboard states ## Current Architecture Analysis ### Component Hierarchy ``` SessionView (Grid Container) ├── SessionHeader (Navigation/Controls) ├── Terminal Area │ ├── TerminalRenderer │ └── VibeTunnel Terminal (xterm.js) └── QuickKeys Area (Conditional) ``` ### Mobile Input Systems 1. **Direct Keyboard Mode** (Default) * Hidden input element for focus * Quick keys toolbar above keyboard * Complex focus retention logic 2. **Mobile Input Mode** (Legacy) * Full-screen textarea overlay * Manual toggle button * Less efficient screen usage ### State Management * `UIStateManager` handles multiple overlapping states * Complex boolean flags: `showQuickKeys`, `showCtrlAlpha`, `showFileBrowser` * Race conditions between keyboard animations and state updates ## Identified Problems ### 1. Viewport Overlap Issues * Fixed positioning elements don't respect safe areas consistently * Terminal transform (`translateY(-110px)`) causes content to move off-screen * Header can be obscured when keyboard is active * Z-index layering conflicts between overlays ### 2. Complex Keyboard Handling * Two separate keyboard systems confuse users * Focus management requires intervals and timeouts * Keyboard height calculations are inconsistent * Quick keys toolbar adds unnecessary complexity ### 3. Poor Space Utilization * Multiple toolbars and overlays reduce terminal visibility * Redundant UI elements (custom keyboard + native keyboard) * Inefficient use of limited mobile screen space ### 4. Navigation Accessibility * Header can become inaccessible when keyboard is active * No clear way to dismiss keyboard in some states * Sidebar toggle conflicts with keyboard overlays ## Design Principles ### 1. Mobile-First Approach * Design for smallest screens first * Progressive enhancement for larger devices * Touch-optimized interactions ### 2. Native Platform Integration * Leverage native keyboard capabilities * Respect platform conventions (iOS/Android) * Use system UI where possible ### 3. Simplified State Management * Single source of truth for keyboard state * Clear, predictable transitions * No overlapping or conflicting states ### 4. Maximum Content Visibility * Terminal content is primary focus * Minimal chrome and overlays * Smart hiding of non-essential UI ## Proposed Solutions ### 1. New Layout System #### Portrait Mode Layout ``` ┌─────────────────────────┐ │ Compact Header │ <- Always visible, 44px ├─────────────────────────┤ │ │ │ │ │ Terminal Content │ <- Flexible height │ │ │ │ ├─────────────────────────┤ │ Action Bar (48px) │ <- Context-sensitive └─────────────────────────┘ With Keyboard: ┌─────────────────────────┐ │ Compact Header │ ├─────────────────────────┤ │ Terminal Content │ <- Scrollable ├─────────────────────────┤ │ Action Bar │ ├─────────────────────────┤ │ Native Keyboard │ └─────────────────────────┘ ``` #### Landscape Mode Layout ``` ┌─────────────────────────────────────┐ │ Header │ Terminal Content │ │ │ │ │ Sidebar │ │ │ Toggle │ │ └─────────┴───────────────────────────┘ ``` ### 2. Simplified Keyboard System #### Single Input Method * Use native keyboard exclusively * Remove custom keyboard overlay * Integrate special keys into action bar #### Smart Action Bar ``` ┌─────────────────────────────────┐ │ [Esc] [Tab] [↑][↓][←][→] [Ctrl] │ <- Scrollable └─────────────────────────────────┘ ``` * Only shows when keyboard is active * Horizontally scrollable for more keys * Sticky positioned above keyboard ### 3. Responsive Header #### Compact Mobile Header * Reduce height to 44px (iOS standard) * Show only essential info: session name + menu * Move detailed info to collapsible drawer #### Header States 1. **Default**: Full info display 2. **Keyboard Active**: Minimal mode with just title 3. **Scrolling**: Auto-hide with scroll, show on scroll up ### 4. Improved Focus Management #### Native Focus Handling ```typescript theme={null} // Simplified focus management class MobileTerminalInput { private input: HTMLInputElement; focus() { // Direct focus, no timeouts this.input.focus(); this.input.click(); // Trigger keyboard on iOS } blur() { this.input.blur(); // Let native behavior handle keyboard dismissal } } ``` ### 5. Viewport Management #### CSS-Only Solution ```css theme={null} .mobile-terminal-container { height: 100vh; height: 100dvh; /* Dynamic viewport height */ display: flex; flex-direction: column; } .terminal-header { flex-shrink: 0; position: sticky; top: 0; z-index: 10; } .terminal-content { flex: 1; overflow-y: auto; -webkit-overflow-scrolling: touch; } .action-bar { flex-shrink: 0; position: sticky; bottom: 0; } /* No transforms, just natural flow */ ``` ## Implementation Phases ### Phase 1: Foundation (Week 1) 1. Create new mobile layout components 2. Implement CSS-only viewport solution 3. Remove terminal transform logic 4. Ensure header always visible ### Phase 2: Keyboard Simplification (Week 2) 1. Remove custom keyboard overlay 2. Implement native-only input 3. Create smart action bar 4. Simplify focus management ### Phase 3: Header Optimization (Week 3) 1. Design compact mobile header 2. Implement collapsible info drawer 3. Add scroll-based auto-hide 4. Test on various devices ### Phase 4: Polish & Testing (Week 4) 1. Fine-tune animations 2. Optimize performance 3. Cross-device testing 4. User feedback integration ## Technical Requirements ### 1. Component Changes #### New Components * `MobileTerminalLayout` - Manages mobile-specific layout * `SmartActionBar` - Context-sensitive action buttons * `CompactSessionHeader` - Mobile-optimized header #### Modified Components * `SessionView` - Use mobile layout on small screens * `TerminalRenderer` - Remove transform logic * `UIStateManager` - Simplify state management ### 2. CSS Architecture #### Remove * Terminal transforms * Complex height calculations * Fixed positioning for overlays #### Add * Flexbox/Grid mobile layouts * Sticky positioning for header/action bar * CSS containment for performance ### 3. State Simplification #### Before (Complex) ```typescript theme={null} interface UIState { showQuickKeys: boolean; keyboardHeight: number; hiddenInputFocused: boolean; showCtrlAlpha: boolean; // ... many more } ``` #### After (Simple) ```typescript theme={null} interface MobileUIState { keyboardVisible: boolean; actionBarExpanded: boolean; headerCompact: boolean; } ``` ## Testing Strategy ### 1. Device Testing Matrix * iPhone 13/14/15 (Standard) * iPhone 13/14 Pro Max (Large) * iPhone SE (Small) * iPad (Tablet) * Android devices (Various) ### 2. Scenario Testing * [ ] Keyboard appears without overlap * [ ] Header remains accessible * [ ] Terminal content scrolls properly * [ ] Action bar functions correctly * [ ] Landscape/portrait transitions smooth * [ ] Safe areas respected on all devices ### 3. Performance Metrics * Layout shift score \< 0.1 * No janky animations (60fps) * Keyboard appear/dismiss \< 300ms * Memory usage stable ## Risk Mitigation ### 1. Browser Compatibility * **Risk**: Visual Viewport API not supported * **Mitigation**: Fallback to resize events ### 2. iOS Keyboard Quirks * **Risk**: Keyboard doesn't appear on focus * **Mitigation**: Use click event as backup ### 3. Performance Issues * **Risk**: Scroll performance degrades * **Mitigation**: Use CSS containment, minimize reflows ### 4. User Adoption * **Risk**: Users confused by changes * **Mitigation**: Gradual rollout, feature flags ## Success Metrics 1. **Zero viewport overlaps** on all tested devices 2. **50% reduction** in keyboard-related bug reports 3. **Improved usability scores** in user testing 4. **Faster keyboard interactions** (\< 300ms response) 5. **Increased mobile session duration** by 25% ## Conclusion This redesign prioritizes simplicity, native platform integration, and maximum content visibility. By removing complexity and focusing on core functionality, we can deliver a superior mobile terminal experience that just works. # Playwright testing Source: https://docs.vibetunnel.sh/web/docs/playwright-testing # Playwright Testing Best Practices for VibeTunnel ## Overview This guide documents best practices for writing reliable, non-flaky Playwright tests for VibeTunnel, based on official Playwright documentation and community best practices. ## Core Principles ### 1. Use Auto-Waiting Instead of Arbitrary Delays **❌ Bad: Arbitrary timeouts** ```typescript theme={null} await page.waitForTimeout(1000); // Don't do this! ``` **✅ Good: Wait for specific conditions** ```typescript theme={null} // Wait for element to be visible await page.waitForSelector('vibe-terminal', { state: 'visible' }); // Wait for loading indicator to disappear await page.locator('.loading-spinner').waitFor({ state: 'hidden' }); // Wait for specific text to appear await page.getByText('Session created').waitFor(); ``` ### 2. Use Web-First Assertions Web-first assertions automatically wait and retry until the condition is met: ```typescript theme={null} // These assertions auto-wait await expect(page.locator('session-card')).toBeVisible(); await expect(page).toHaveURL(/\?session=/); await expect(sessionCard).toContainText('RUNNING'); ``` ### 3. Prefer User-Facing Locators **Locator Priority (best to worst):** 1. `getByRole()` - semantic HTML roles 2. `getByText()` - visible text content 3. `getByTestId()` - explicit test IDs 4. `locator()` with CSS - last resort ```typescript theme={null} // Good examples await page.getByRole('button', { name: 'Create Session' }).click(); await page.getByText('Session Name').fill('My Session'); await page.getByTestId('terminal-output').waitFor(); ``` ## VibeTunnel-Specific Patterns ### Waiting for Terminal Ready Instead of arbitrary delays, wait for terminal indicators: ```typescript theme={null} // Wait for terminal component to be visible await page.waitForSelector('vibe-terminal', { state: 'visible' }); // Wait for terminal to have content or structure await page.waitForFunction(() => { const terminal = document.querySelector('vibe-terminal'); return terminal && ( terminal.textContent?.trim().length > 0 || !!terminal.shadowRoot || !!terminal.querySelector('vibe-terminal') ); }); ``` ### Handling Session Creation ```typescript theme={null} // Wait for navigation after session creation await expect(page).toHaveURL(/\?session=/, { timeout: 2000 }); // Wait for terminal to be ready await page.locator('vibe-terminal').waitFor({ state: 'visible' }); ``` ### Managing Modal Animations Instead of waiting for animations, wait for the modal state: ```typescript theme={null} // Wait for modal to be fully visible await page.locator('[role="dialog"]').waitFor({ state: 'visible' }); // Wait for modal to be completely gone await page.locator('[role="dialog"]').waitFor({ state: 'hidden' }); ``` ### Session List Updates ```typescript theme={null} // Wait for session cards to update await page.locator('session-card').first().waitFor(); // Wait for specific session by name await page.locator(`session-card:has-text("${sessionName}")`).waitFor(); ``` ## Common Anti-Patterns to Avoid ### 1. Storing Element References ```typescript theme={null} // ❌ Bad: Element reference can become stale const button = await page.$('button'); await doSomething(); await button.click(); // May fail! // ✅ Good: Re-query element when needed await doSomething(); await page.locator('button').click(); ``` ### 2. Assuming Immediate Availability ```typescript theme={null} // ❌ Bad: No waiting await page.goto('/'); await page.click('session-card'); // May not exist yet! // ✅ Good: Wait for element await page.goto('/'); await page.locator('session-card').waitFor(); await page.locator('session-card').click(); ``` ### 3. Fixed Sleep for Dynamic Content ```typescript theme={null} // ❌ Bad: Arbitrary wait for data load await page.click('#load-data'); await page.waitForTimeout(3000); // ✅ Good: Wait for loading state await page.click('#load-data'); await page.locator('.loading').waitFor({ state: 'hidden' }); // Or wait for results await page.locator('[data-testid="results"]').waitFor(); ``` ## Test Configuration ### Timeouts Configure appropriate timeouts in `playwright.config.ts`: ```typescript theme={null} use: { // Global timeout for assertions expect: { timeout: 5000 }, // Action timeout (click, fill, etc.) actionTimeout: 10000, // Navigation timeout navigationTimeout: 10000, } ``` ### Test Isolation Each test should be independent: ```typescript theme={null} test.beforeEach(async ({ page }) => { // Fresh start for each test await page.goto('/'); await page.waitForSelector('vibetunnel-app', { state: 'attached' }); }); ``` ## Debugging Flaky Tests ### 1. Enable Trace Recording ```typescript theme={null} // In playwright.config.ts use: { trace: 'on-first-retry', } ``` ### 2. Use Debug Mode ```bash theme={null} # Run with headed browser and inspector pnpm exec playwright test --debug ``` ### 3. Add Strategic Logging ```typescript theme={null} console.log('Waiting for terminal to be ready...'); await page.locator('vibe-terminal').waitFor(); console.log('Terminal is ready'); ``` ## Terminal-Specific Patterns ### Waiting for Terminal Output ```typescript theme={null} // Wait for specific text in terminal await page.waitForFunction( (searchText) => { const terminal = document.querySelector('vibe-terminal'); return terminal?.textContent?.includes(searchText); }, 'Expected output' ); ``` ### Waiting for Shell Prompt ```typescript theme={null} // Wait for prompt patterns await page.waitForFunction(() => { const terminal = document.querySelector('vibe-terminal'); const content = terminal?.textContent || ''; return /[$>#%❯]\s*$/.test(content); }); ``` ### Handling Server-Side Terminals When `spawnWindow` is false, terminals run server-side: ```typescript theme={null} // Create session with server-side terminal await sessionListPage.createNewSession(sessionName, false); // Wait for WebSocket v3 connection await page.locator('vibe-terminal').waitFor({ state: 'visible' }); // Terminal content comes through WebSocket - no need for complex waits ``` ## Summary 1. **Never use `waitForTimeout()`** - always wait for specific conditions 2. **Use web-first assertions** that auto-wait 3. **Prefer semantic locators** over CSS selectors 4. **Wait for observable conditions** not arbitrary time 5. **Configure appropriate timeouts** for your application 6. **Keep tests isolated** and independent 7. **Use Playwright's built-in debugging tools** for flaky tests By following these practices, tests will be more reliable, faster, and easier to maintain. # Socket protocol Source: https://docs.vibetunnel.sh/web/docs/socket-protocol # VibeTunnel Socket Protocol ## Overview VibeTunnel uses a binary framed message protocol over Unix domain sockets for all inter-process communication (IPC). This protocol replaces the previous file-based IPC system, providing better performance, real-time updates, and cleaner architecture. ## Architecture ### Components 1. **PTY Manager** (Server) * Creates Unix domain socket at `{session_dir}/ipc.sock` * Handles multiple client connections * Manages PTY process I/O * Tracks session state 2. **Socket Client** (`vibetunnel-fwd` and other clients) * Connects to session's Unix socket * Sends stdin data and control commands * Receives errors and server responses * Supports auto-reconnection ### Socket Path * Location: `{control_dir}/{session_id}/ipc.sock` * Example: `/tmp/vt-1234567890/a1b2c3d4-e5f6-g7h8-i9j0-k1l2m3n4o5p6/ipc.sock` **Important**: macOS has a 104 character limit for Unix socket paths (103 usable). Keep control directories short to avoid EINVAL errors. ## Message Format ### Frame Structure ``` +--------+--------+--------+--------+--------+----------------+ | Type | Length | Payload | | 1 byte | 4 bytes (big-endian uint32) | Length bytes | +--------+--------+--------+--------+--------+----------------+ ``` * **Type**: Single byte indicating message type * **Length**: 32-bit unsigned integer in big-endian format * **Payload**: Variable-length data (format depends on message type) ### Message Types | Type | Value | Direction | Description | | -------------- | ----- | --------------- | ------------------------------- | | STDIN\_DATA | 0x01 | Client → Server | Terminal input data | | CONTROL\_CMD | 0x02 | Client → Server | Control commands (resize, kill) | | STATUS\_UPDATE | 0x03 | Both | Legacy status updates (ignored) | | HEARTBEAT | 0x04 | Both | Connection health check | | ERROR | 0x05 | Server → Client | Error messages | ## Message Payloads ### STDIN\_DATA (0x01) * **Payload**: UTF-8 encoded string * **Example**: `"ls -la\n"` ### CONTROL\_CMD (0x02) * **Payload**: JSON object * **Commands**: ```json theme={null} // Resize terminal { "cmd": "resize", "cols": 120, "rows": 40 } // Kill process { "cmd": "kill", "signal": "SIGTERM" } // Reset terminal size { "cmd": "reset-size" } ``` ### STATUS\_UPDATE (0x03) * **Payload**: JSON object * **Status**: Reserved for legacy clients; server ignores these messages. ### HEARTBEAT (0x04) * **Payload**: Empty (0 bytes) * **Behavior**: * Clients can send periodic heartbeats * Server echoes heartbeats back * Used to detect connection health ### ERROR (0x05) * **Payload**: JSON object * **Format**: ```json theme={null} { "code": "SESSION_NOT_FOUND", "message": "Session does not exist", "details": { /* optional */ } } ``` #### Error Codes | Code | Description | Details | | -------------------------- | ------------------------------------ | ------------------------------------------------------------- | | `SESSION_NOT_FOUND` | The requested session does not exist | Session ID is invalid or session has been terminated | | `MESSAGE_PROCESSING_ERROR` | Failed to process incoming message | Malformed message, invalid JSON, or internal processing error | | `INVALID_OPERATION` | Operation not valid for session type | e.g., reset-size on in-memory session | | `CONTROL_MESSAGE_FAILED` | Failed to send control message | Unable to communicate with PTY process | | `RESET_SIZE_FAILED` | Failed to reset terminal size | Error during size reset operation | | `CONNECTION_LIMIT` | Too many concurrent connections | Server connection limit reached | | `PAYLOAD_TOO_LARGE` | Message payload exceeds size limit | Payload larger than maximum allowed size | | `INVALID_MESSAGE_TYPE` | Unknown or unsupported message type | Client sent unrecognized message type | | `MALFORMED_FRAME` | Invalid message frame structure | Message framing protocol violation | **Example Error Response**: ```json theme={null} { "code": "MESSAGE_PROCESSING_ERROR", "message": "Failed to parse control command", "details": { "error": "Unexpected token } in JSON at position 42", "messageType": 2 } } ``` ## Client Implementation ### Connection Flow 1. Connect to Unix socket at `{session_dir}/ipc.sock` 2. Send messages as needed 3. Handle incoming messages asynchronously 4. Reconnect automatically on disconnection (optional) ### Example Usage ```typescript theme={null} import { VibeTunnelSocketClient } from './socket-client.js'; // Connect to session const client = new VibeTunnelSocketClient('/path/to/session/ipc.sock', { autoReconnect: true, heartbeatInterval: 30000 // 30 seconds }); // Listen for events client.on('connect', () => console.log('Connected')); client.on('error', (err) => console.error('Error:', err)); // Connect and use await client.connect(); // Send terminal input client.sendStdin('echo "Hello, World!"\n'); // Resize terminal client.resize(120, 40); // Disconnect when done client.disconnect(); ``` ## Server Implementation ### Socket Server Setup The PTY manager creates a Unix domain socket for each session: ```typescript theme={null} // Create socket server const server = net.createServer((client) => { const parser = new MessageParser(); // Handle incoming messages client.on('data', (chunk) => { parser.addData(chunk); for (const { type, payload } of parser.parseMessages()) { handleMessage(type, payload, client); } }); }); // Listen on socket server.listen(socketPath); ``` ### Message Handling The server processes messages based on type: * **STDIN\_DATA**: Write to PTY process * **CONTROL\_CMD**: Handle resize/kill commands * **STATUS\_UPDATE**: Ignored (legacy) * **HEARTBEAT**: Echo back to sender ## Protocol Features ### Message Framing The protocol handles: * **Partial messages**: TCP may split messages across packets * **Multiple messages**: TCP may combine messages in one packet * **Large payloads**: No practical size limit (up to 4GB per message) * **Binary safety**: Handles null bytes and non-UTF8 data ### Connection Management * **Multiple clients**: Server supports multiple simultaneous connections * **Auto-reconnection**: Clients can automatically reconnect on failure * **Heartbeats**: Optional periodic heartbeats for connection health * **Graceful shutdown**: Proper cleanup of resources ## Migration from File-Based IPC ### Previous System * Control commands via `{session_dir}/control-pipe` file * Required file watching and polling ### New System * All communication through single Unix socket * Real-time bidirectional messaging * No file watching or polling needed * Better performance and cleaner architecture ## Error Handling ### Connection Errors * **ENOENT**: Socket file doesn't exist (session not found) * **ECONNREFUSED**: Server not listening (session crashed) * **EINVAL**: Socket path too long (macOS limit) ### Protocol Errors * Malformed messages are logged and ignored * Server sends ERROR message for processing failures * Clients should handle disconnections gracefully ## Performance Considerations 1. **Message Size**: Keep messages reasonably sized (\< 1MB) 2. **Heartbeat Interval**: 30-60 seconds is typical 3. **Reconnect Delay**: 1-5 seconds between attempts 4. **Socket Backlog**: Default is sufficient for typical usage ## Security Notes * Sockets are created with 0666 permissions (world-writable) * Rely on directory permissions for access control * No authentication or encryption (local use only) * Validate all JSON payloads before processing ## Implementation Files * **Protocol**: `src/server/pty/socket-protocol.ts` * **Client**: `src/server/pty/socket-client.ts` * **Server**: `src/server/pty/pty-manager.ts` (setupIPCSocket method) * **Tests**: `src/test/unit/socket-*.test.ts`, `src/test/integration/socket-*.test.ts` ## Future Enhancements Potential improvements to consider: * Message compression for large payloads * Authentication for multi-user systems * Encryption for sensitive data * Request/response correlation IDs * Batch message support # Spec Source: https://docs.vibetunnel.sh/web/docs/spec # VibeTunnel Web Architecture Specification This document provides a comprehensive map of the VibeTunnel web application architecture, including server components, client structure, API specifications, and protocol details. Updated: 2025-07-01 ## Key Files Quick Reference ### Server Core * **Entry Point**: `src/server/server.ts:912` - `startVibeTunnelServer()` * **App Creation**: `src/server/server.ts:330` - `createApp()` * **Configuration**: `src/server/server.ts:57` - `Config` interface * **CLI Entry**: `src/server/cli.ts:51-56` - `vibetunnel fwd` command ### Authentication * **Service**: `src/server/services/auth-service.ts:144-271` - SSH key verification * **Middleware**: `src/server/middleware/auth.ts:20-105` - JWT validation * **Routes**: `src/server/routes/auth.ts:20-178` - Auth endpoints ### Session Management * **PTY Manager**: `src/server/pty/pty-manager.ts:57` - Session Map * **Session Manager**: `src/server/pty/session-manager.ts:40-141` - Session lifecycle * **Routes**: `src/server/routes/sessions.ts:134-1252` - Session API ### Real-time Communication * **VT Snapshot v1**: `src/server/services/terminal-manager.ts:378-574` - Snapshot encoding * **WebSocket v3 Hub**: `src/server/services/ws-v3-hub.ts` - Multiplexed stdout/snapshots/events + input ### Client Core * **Entry Point**: `src/client/app-entry.ts:1-28` - App initialization * **Main Component**: `src/client/app.ts:44-1355` - `` * **Terminal**: `src/client/components/terminal.ts:23-1567` - ghostty-web wrapper ## Server Architecture ### Main Server (`src/server/server.ts`) The server provides a comprehensive API for terminal session management with support for distributed deployments. **Configuration Options**: * `port`: Server port (default: 4020) * `bind`: Bind address (default: 127.0.0.1) * `isHQMode`: Run as headquarters server * `hqUrl/hqUsername/hqPassword`: Remote server registration * `enableSSHKeys`: Enable SSH key authentication * `noAuth`: Disable all authentication **Key Services**: * Authentication (JWT + SSH keys) * Session management (PTY processes) * WebSocket communication (binary buffers + input) * File system operations * Push notifications * Activity monitoring ### Authentication System **Supported Methods**: 1. **SSH Key Authentication** (`src/server/routes/auth.ts:52`) * Challenge-response with Ed25519 signatures * Verifies against `~/.ssh/authorized_keys` 2. **Password Authentication** (`src/server/routes/auth.ts:101`) * PAM authentication or environment variables 3. **Bearer Token** (HQ mode) * For server-to-server communication 4. **Local Bypass** (optional) * Localhost connections with optional token **JWT Token Flow**: 1. Client requests challenge from `/api/auth/challenge` 2. Server generates random challenge 3. Client signs challenge and sends to `/api/auth/ssh-key` 4. Server verifies signature and returns JWT token ### Session Management **Session Lifecycle**: 1. **Creation** (`src/server/routes/sessions.ts:134`): * Spawns PTY process using node-pty * Creates session directory in `~/.vibetunnel/control/` * Saves metadata to `session.json` 2. **Tracking**: * In-memory: `PtyManager.sessions` Map * On-disk: Session directories with stdout/stdin files 3. **Cleanup** (`src/server/pty/session-manager.ts:297`): * Automatic cleanup of exited sessions * 5-minute cleanup interval * Zombie process detection **Control Directory Structure**: ``` ~/.vibetunnel/control/ ├── [sessionId]/ │ ├── session.json # Session metadata │ ├── stdout # Terminal output │ ├── stdin # Terminal input log │ └── ipc.sock # Unix socket for IPC ``` ## Client Architecture ### Component Hierarchy ``` # Main app orchestrator ├── # Login form ├── # Session listing │ └── # Individual session ├── # Full-screen terminal │ ├── # ghostty-web wrapper │ └── # Binary buffer renderer └── # Settings panel ``` ### State Management * Component-level state using LitElement's `@state()` decorator * localStorage for persistent data (auth tokens, preferences) * Event-driven communication between components * No global state management library ### Services **AuthClient** (`src/client/services/auth-client.ts`): * Manages authentication state * Handles SSH key and password auth * Stores tokens in localStorage **TerminalSocketClient** (`src/client/services/terminal-socket-client.ts`): * Single WebSocket to `/ws` (v3 framing) * Multiplexed subscriptions per session (stdout/snapshots/events) * Input + resize on the same socket ## API Specification ### REST Endpoints #### Sessions * `GET /api/sessions` - List all sessions * `POST /api/sessions` - Create new session * `GET /api/sessions/:id` - Get session info * `DELETE /api/sessions/:id` - Kill session * `POST /api/sessions/:id/input` - Send input * `POST /api/sessions/:id/resize` - Resize terminal * `GET /api/sessions/:id/text` - Get text output #### Authentication * `POST /api/auth/challenge` - Request challenge * `POST /api/auth/ssh-key` - SSH key auth * `POST /api/auth/password` - Password auth * `GET /api/auth/verify` - Verify token * `GET /api/auth/config` - Get auth config #### HQ Mode (Distributed) * `GET /api/remotes` - List remote servers * `POST /api/remotes/register` - Register remote * `DELETE /api/remotes/:id` - Unregister remote #### Git Integration * `GET /api/worktrees` - List worktrees * `POST /api/worktrees` - Create worktree * `POST /api/worktrees/follow` - Enable/disable follow mode * `GET /api/worktrees/follow` - Get follow mode status * `POST /api/git/events` - Git hook notifications ### WebSocket Protocols #### Terminal Transport (`/ws`, v3) Single WebSocket. Multiplexed sessions. Binary framing. See `docs/websocket.md` for framing and message types. **VT Snapshot v1 Format** (`SNAPSHOT_VT` payload): ``` Header (32 bytes): ├── Magic: 0x5654 "VT" (2 bytes) ├── Version: 0x01 (1 byte) ├── Flags: reserved (1 byte) ├── Columns (4 bytes) ├── Rows (4 bytes) ├── ViewportY (4 bytes) ├── CursorX (4 bytes) ├── CursorY (4 bytes) └── Reserved (4 bytes) Row Encoding: ├── Empty rows: [0xFE][count] └── Content rows: [0xFD][cell count (2 bytes)][cells...] Cell Type Byte: ├── Bit 7: Has extended data ├── Bit 6: Is Unicode ├── Bit 5: Has foreground color ├── Bit 4: Has background color ├── Bit 3: Is RGB foreground ├── Bit 2: Is RGB background └── Bits 1-0: Character type (00=space, 01=ASCII, 10=Unicode) ``` ## vibetunnel-fwd (Rust forwarder) The `vibetunnel-fwd` binary (`native/vt-fwd`) wraps any command in a VibeTunnel session: **Usage**: `vibetunnel-fwd [--session-id ] [--title-mode ] [--verbosity ] [args...]` **Options**: * `--session-id `: Use a pre-generated session ID * `--title-mode `: none|filter|static * `--update-title `: Update existing session title and exit (requires --session-id) **Artifacts**: * `{control_dir}/{session_id}/session.json` * `{control_dir}/{session_id}/stdout` (asciinema v2) * `{control_dir}/{session_id}/ipc.sock` (binary framed) See `socket-protocol.md` for IPC framing and message types. ## HQ Mode & Distributed Architecture ### Remote Registration 1. Remote servers register with HQ using bearer tokens 2. HQ maintains registry of all remote servers 3. Health checks every 15 seconds 4. Automatic session discovery ### Request Routing * HQ checks session ownership via registry * Forwards API requests to appropriate remote * Proxies WS v3 streams transparently * Multiplexes WebSocket connections ### High Availability * Graceful degradation on remote failure * Continues serving local sessions * Automatic reconnection for WebSocket streams * Session ownership tracking for reliability ## Additional Features ### Push Notifications * Web Push API with VAPID authentication * Bell event notifications from terminal * Service worker for offline support * Process context in notifications ### File Browser * Full filesystem browsing with Git status * Monaco Editor for code preview * Git diff visualization * Image preview support ### SSH Key Management * Browser-based Ed25519 key generation * Import/export functionality * Password-protected key support * Web Crypto API integration ### Native Terminal Spawning (macOS) * Unix socket at `/tmp/vibetunnel-terminal.sock` * Requests native Terminal.app windows * Falls back to web terminal ### Performance Optimizations * Binary buffer compression (empty row encoding) * Fire-and-forget input protocol * Debounced buffer notifications (50ms) * Efficient cell encoding with bit-packing ## Development Commands ```bash theme={null} # Web directory commands cd web/ # Development (auto-rebuild) pnpm run dev # Code quality (must run before commit) pnpm run check # Run all checks in parallel pnpm run check:fix # Auto-fix issues # Individual commands pnpm run lint # ESLint pnpm run format # Prettier pnpm run typecheck # TypeScript ``` ## Git Follow Mode Git follow mode creates an intelligent synchronization between a main repository and a specific worktree, enabling seamless development workflows where agents work in worktrees while developers maintain their IDE and server setups in the main repository. **Key Components**: * **Git Hooks** (`src/server/utils/git-hooks.ts`): Manages post-commit, post-checkout, post-merge hooks * **Git Event Handler** (`src/server/routes/git.ts:186-482`): Processes git events and handles synchronization * **Socket API** (`src/server/api-socket-server.ts:217-267`): Socket-based follow mode control * **CLI Integration** (`web/bin/vt`): Smart command handling with path/branch detection **Configuration**: * Single config option: `vibetunnel.followWorktree` stores the worktree path being followed * Config is stored in the main repository's `.git/config` * Follow mode is active when this config contains a valid worktree path **Synchronization Behavior**: 1. **Worktree → Main** (Primary): Branch switches, commits, and checkouts sync to main repo 2. **Main → Worktree** (Limited): Only commits sync; branch switches auto-unfollow 3. **Auto-unfollow**: Switching branches in main repo disables follow mode **Command Usage**: ```bash theme={null} # From worktree - follow this worktree vt follow # From main repo - smart detection vt follow # Follow current branch's worktree (if exists) vt follow feature/new-api # Follow worktree for this branch vt follow ~/project-feature # Follow worktree by path ``` **Hook Installation**: * Hooks installed in BOTH main repository and worktree * Hooks execute `vt git event` which notifies server via socket API * Server processes events based on source (main vs worktree) * Existing hooks are preserved with `.vtbak` extension **Event Flow**: 1. Git event occurs (checkout, commit, merge) 2. Hook executes `vt git event` 3. CLI sends event via socket to server 4. Server determines sync action based on event source 5. Appropriate git commands executed to maintain sync ## Architecture Principles 1. **Modular Design**: Clear separation between auth, sessions, and real-time communication 2. **Scalability**: Horizontal scaling via HQ mode and remote servers 3. **Reliability**: Automatic reconnection, health checks, graceful degradation 4. **Performance**: Binary protocols, compression, minimal latency 5. **Security**: Multiple auth methods, JWT tokens, secure WebSocket connections For implementation details, refer to the line numbers provided in the Key Files Quick Reference section. # Systemd Source: https://docs.vibetunnel.sh/web/docs/systemd # VibeTunnel Systemd Service Guide This guide covers installing and managing VibeTunnel as a systemd service on Linux systems. ## Overview VibeTunnel includes built-in systemd integration that allows you to run it as a persistent service on Linux. The service runs as a **user-level systemd service** under your account (not system-wide), providing automatic startup, restart on failure, and proper resource management. ## Quick Start ```bash theme={null} # Install the systemd service (run as regular user, NOT root) vibetunnel systemd # Start the service systemctl --user start vibetunnel # Enable auto-start on boot systemctl --user enable vibetunnel # Check status systemctl --user status vibetunnel ``` ## Installation ### Prerequisites * Linux system with systemd (most modern distributions) * VibeTunnel installed globally via npm (`npm install -g vibetunnel`) * Regular user account (do not run as root) ### Install Command ```bash theme={null} vibetunnel systemd ``` This command will: 1. Verify VibeTunnel is installed and accessible 2. Create a wrapper script at `~/.local/bin/vibetunnel-systemd` 3. Install the service file at `~/.config/systemd/user/vibetunnel.service` 4. Enable the service for automatic startup 5. Configure user lingering for boot startup ## Service Management ### Basic Commands ```bash theme={null} # Start the service systemctl --user start vibetunnel # Stop the service systemctl --user stop vibetunnel # Restart the service systemctl --user restart vibetunnel # Check service status systemctl --user status vibetunnel # Enable auto-start systemctl --user enable vibetunnel # Disable auto-start systemctl --user disable vibetunnel # Check VibeTunnel's systemd status vibetunnel systemd status ``` ### Viewing Logs ```bash theme={null} # Follow logs in real-time journalctl --user -u vibetunnel -f # View all logs journalctl --user -u vibetunnel # View logs from the last hour journalctl --user -u vibetunnel --since "1 hour ago" # View only error messages journalctl --user -u vibetunnel -p err ``` ## Configuration ### Default Settings The service runs with these defaults: * **Port**: 4020 * **Bind Address**: 0.0.0.0 (all interfaces) * **Working Directory**: Your home directory * **Restart Policy**: Always restart on failure * **Restart Delay**: 10 seconds * **Memory Limit**: 512MB soft, 1GB hard * **File Descriptor Limit**: 65536 * **Environment**: `NODE_ENV=production`, `VIBETUNNEL_LOG_LEVEL=info` ### Service File Location The service configuration is stored at: ``` ~/.config/systemd/user/vibetunnel.service ``` ### Customizing the Service To modify service settings: 1. Edit the service file: ```bash theme={null} nano ~/.config/systemd/user/vibetunnel.service ``` 2. Common customizations: ```ini theme={null} # Change port ExecStart=/home/user/.local/bin/vibetunnel-systemd --port 8080 --bind 0.0.0.0 # Add authentication ExecStart=/home/user/.local/bin/vibetunnel-systemd --port 4020 --bind 0.0.0.0 --auth system # Change log level Environment=VIBETUNNEL_LOG_LEVEL=debug # Adjust memory limits MemoryHigh=1G MemoryMax=2G # Add custom environment variables Environment=MY_CUSTOM_VAR=value ``` 3. Reload and restart: ```bash theme={null} systemctl --user daemon-reload systemctl --user restart vibetunnel ``` ## Architecture ### Why User-Level Service? VibeTunnel uses user-level systemd services for several reasons: 1. **Security**: Runs with user privileges, not root 2. **Node.js Compatibility**: Works with user-installed Node.js version managers (nvm, fnm) 3. **User Data Access**: Natural access to your projects and Git repositories 4. **Simplicity**: No sudo required for management 5. **Isolation**: Each user can run their own instance ### The Wrapper Script The installer creates a wrapper script at `~/.local/bin/vibetunnel-systemd` that: * Searches for VibeTunnel in multiple locations * Handles nvm and fnm installations * Falls back to system-wide Node.js if needed * Provides detailed logging for troubleshooting ### User Lingering The installer enables "user lingering" which allows your user services to run even when you're not logged in: ```bash theme={null} # This is done automatically during installation loginctl enable-linger $USER # To check lingering status loginctl show-user $USER | grep Linger # To disable lingering (if desired) loginctl disable-linger $USER ``` ## Troubleshooting ### Service Won't Start 1. Check if VibeTunnel is installed: ```bash theme={null} which vibetunnel ``` 2. Check service logs: ```bash theme={null} journalctl --user -u vibetunnel -n 50 ``` 3. Verify the wrapper script exists: ```bash theme={null} ls -la ~/.local/bin/vibetunnel-systemd ``` 4. Test the wrapper script directly: ```bash theme={null} ~/.local/bin/vibetunnel-systemd --version ``` ### Port Already in Use If port 4020 is already in use: 1. Find what's using the port: ```bash theme={null} lsof -i :4020 ``` 2. Either stop the conflicting service or change VibeTunnel's port in the service file ### Node.js Version Manager Issues If using nvm or fnm, ensure they're properly initialized: 1. Check your shell configuration: ```bash theme={null} # For nvm echo $NVM_DIR # For fnm echo $FNM_DIR ``` 2. The wrapper script searches these locations: * nvm: `~/.nvm` * fnm: `~/.local/share/fnm` * Global npm: `/usr/local/bin/npm`, `/usr/bin/npm` ### Permission Denied If you get permission errors: 1. Ensure you're NOT running as root 2. Check file permissions: ```bash theme={null} ls -la ~/.config/systemd/user/ ls -la ~/.local/bin/vibetunnel-systemd ``` 3. Fix permissions if needed: ```bash theme={null} chmod 755 ~/.local/bin/vibetunnel-systemd chmod 644 ~/.config/systemd/user/vibetunnel.service ``` ## Uninstallation To completely remove the systemd service: ```bash theme={null} # Stop and disable the service systemctl --user stop vibetunnel systemctl --user disable vibetunnel # Remove service files vibetunnel systemd uninstall # Optional: Disable user lingering loginctl disable-linger $USER ``` This will: * Stop the running service * Disable automatic startup * Remove the service file * Remove the wrapper script * Reload systemd configuration ## Advanced Usage ### Multiple Instances To run multiple VibeTunnel instances: 1. Copy the service file with a new name: ```bash theme={null} cp ~/.config/systemd/user/vibetunnel.service ~/.config/systemd/user/vibetunnel-dev.service ``` 2. Edit the new service file to use a different port: ```ini theme={null} ExecStart=/home/user/.local/bin/vibetunnel-systemd --port 4021 --bind 0.0.0.0 ``` 3. Manage the new instance: ```bash theme={null} systemctl --user daemon-reload systemctl --user start vibetunnel-dev ``` ### Environment-Specific Configuration Create environment-specific service overrides: ```bash theme={null} # Create override directory mkdir -p ~/.config/systemd/user/vibetunnel.service.d/ # Create override file cat > ~/.config/systemd/user/vibetunnel.service.d/override.conf << EOF [Service] Environment=NODE_ENV=development Environment=VIBETUNNEL_LOG_LEVEL=debug ExecStart= ExecStart=/home/user/.local/bin/vibetunnel-systemd --port 4020 --bind 127.0.0.1 EOF # Reload and restart systemctl --user daemon-reload systemctl --user restart vibetunnel ``` ### Integration with Other Services To make VibeTunnel depend on other services: ```ini theme={null} [Unit] After=network-online.target postgresql.service Wants=network-online.target [Service] # ... rest of configuration ``` ## Security Considerations ### Firewall Configuration If binding to 0.0.0.0, ensure your firewall is properly configured: ```bash theme={null} # UFW example sudo ufw allow 4020/tcp # firewalld example sudo firewall-cmd --add-port=4020/tcp --permanent sudo firewall-cmd --reload ``` ### Restricting Access To limit access to localhost only, modify the service: ```ini theme={null} ExecStart=/home/user/.local/bin/vibetunnel-systemd --port 4020 --bind 127.0.0.1 ``` ### Resource Limits The service includes resource limits for stability: * Memory: 512MB soft limit, 1GB hard limit * File descriptors: 65536 * Automatic restart with 10-second delay Adjust these based on your needs and system resources. ## FAQ **Q: Why doesn't the service run as root?** A: VibeTunnel doesn't require root privileges and running as a regular user is more secure. It also ensures compatibility with user-installed Node.js version managers. **Q: Can I run this on a server without a GUI?** A: Yes, the systemd service works perfectly on headless servers. User lingering ensures it starts at boot. **Q: How do I run VibeTunnel on a different port?** A: Edit the service file and change the `--port` parameter in the `ExecStart` line, then reload and restart. **Q: What if I use a custom Node.js installation?** A: The wrapper script searches common locations. If your installation isn't found, you can modify the wrapper script at `~/.local/bin/vibetunnel-systemd`. **Q: Can multiple users run VibeTunnel on the same system?** A: Yes, each user can install their own service. Just ensure they use different ports. ## Support For issues specific to the systemd service: 1. Check the logs with `journalctl --user -u vibetunnel` 2. Verify the installation with `vibetunnel systemd status` 3. Report issues at [https://github.com/amantus-ai/vibetunnel/issues](https://github.com/amantus-ai/vibetunnel/issues) # Npm Source: https://docs.vibetunnel.sh/web/npm # NPM Publishing Guide for VibeTunnel ## Installation Guide ### Installing VibeTunnel from NPM VibeTunnel is published as an npm package that works on macOS and Linux. The package includes prebuilt binaries for common platforms to avoid compilation. #### Basic Installation ```bash theme={null} # Install globally (recommended) npm install -g vibetunnel # Or install locally in a project npm install vibetunnel ``` #### Platform-Specific Notes **macOS**: * Works out of the box * PAM authentication supported natively **Linux**: * Works without additional dependencies * PAM authentication is optional - installs only if PAM headers are available * If you need PAM authentication, install development headers first: ```bash theme={null} # Ubuntu/Debian sudo apt-get install libpam0g-dev # RHEL/CentOS/Fedora sudo yum install pam-devel ``` #### Verifying Installation ```bash theme={null} # Check version vibetunnel --version # Run the server vibetunnel # The server will start on http://localhost:4020 ``` #### Docker Installation For containerized environments: ```dockerfile theme={null} FROM node:22-slim # Optional: Install PAM headers for authentication support # RUN apt-get update && apt-get install -y libpam0g-dev # Install VibeTunnel RUN npm install -g vibetunnel # Expose the default port EXPOSE 4020 # Run VibeTunnel CMD ["vibetunnel"] ``` #### Troubleshooting Installation 1. **"Cannot find module '../build/Release/pty.node'"** * The package includes prebuilds, this shouldn't happen * Try reinstalling: `npm uninstall -g vibetunnel && npm install -g vibetunnel` 2. **PAM authentication not working on Linux** * Install PAM headers: `sudo apt-get install libpam0g-dev` * Reinstall VibeTunnel to compile the PAM module 3. **Permission errors during installation** * Use a Node.js version manager (nvm, fnm) instead of system Node.js * Or fix npm permissions: [https://docs.npmjs.com/resolving-eacces-permissions-errors](https://docs.npmjs.com/resolving-eacces-permissions-errors) ## Quick Release Checklist 1. **Update versions** in all 3 files: * `package.json` * `package.npm.json` * `../mac/VibeTunnel/version.xcconfig` 2. **Build**: `pnpm run build:npm` 3. **Verify**: ```bash theme={null} tar -xf vibetunnel-*.tgz package/package.json grep optionalDependencies package/package.json # Must show authenticate-pam rm -rf package/ ``` 4. **Publish tarball**: ```bash theme={null} npm publish vibetunnel-*.tgz --tag beta npm dist-tag add vibetunnel@VERSION latest ``` ⚠️ **NEVER** use `npm publish` without the tarball filename! ## Critical Issue History: Wrong package.json Used in Releases ### The Problem We've repeatedly published npm packages with the wrong configuration: * **Version 11.2**: Used main `package.json` instead of `package.npm.json` * **Version 11.3**: Also used main `package.json` despite having the correct `package.npm.json` * **Both versions had to be unpublished** due to Linux installation failures This causes installation failures on Linux systems because: * Main `package.json` has `authenticate-pam` as a **regular dependency** * `package.npm.json` has `authenticate-pam` as an **optional dependency** When `authenticate-pam` is a regular dependency, npm fails the entire installation if PAM headers (libpam0g-dev) aren't available. ### Root Cause The build script (`scripts/build-npm.js`) checks for `package.npm.json` and uses it if available, BUT: * During `npm publish`, npm runs the `prepublishOnly` script which triggers `build:npm` * This rebuilds the package, potentially overwriting the correct configuration * The timing and execution context can cause the wrong package.json to be used ### The Solution **NEVER use `npm publish` directly!** Instead: 1. Build the package explicitly: ```bash theme={null} pnpm run build:npm ``` 2. Verify the package has the correct configuration: ```bash theme={null} # Extract and check package.json from the tarball tar -xf vibetunnel-*.tgz package/package.json cat package/package.json | grep -A5 -B5 authenticate-pam ``` 3. Ensure `authenticate-pam` is under `optionalDependencies`: ```json theme={null} "optionalDependencies": { "authenticate-pam": "^1.0.5" } ``` 4. Publish the pre-built tarball: ```bash theme={null} npm publish vibetunnel-*.tgz --tag beta npm dist-tag add vibetunnel@VERSION latest # if needed ``` ## Correct Release Process ### 1. Update Version Numbers ```bash theme={null} # Update all three version files - MUST keep in sync! vim package.json # Update version vim package.npm.json # Update version to match vim ../mac/VibeTunnel/version.xcconfig # Update MARKETING_VERSION ``` ### 2. Build the Package ```bash theme={null} pnpm run build:npm ``` ### 3. Verify the Build ```bash theme={null} # Check the tarball exists (look in parent directory!) ls -la *.tgz # Extract and verify authenticate-pam is optional tar -xf vibetunnel-*.tgz package/package.json cat package/package.json | grep -A5 -B5 authenticate-pam # Should show: # "optionalDependencies": { # "authenticate-pam": "^1.0.5" # } # Clean up rm -rf package/ ``` ### 4. Test Installation Locally ```bash theme={null} # Test on a system without PAM headers docker run --rm -it node:22 bash npm install /path/to/vibetunnel-*.tgz # Should succeed even without libpam0g-dev ``` ### 5. Publish ```bash theme={null} # Publish the pre-built tarball with beta tag npm publish vibetunnel-*.tgz --tag beta # Also tag as latest if stable npm dist-tag add vibetunnel@VERSION latest ``` ## Package Configuration Files ### package.json (Main Development) * Used for development environment * Has ALL dependencies including devDependencies * `authenticate-pam` is a regular dependency (for development) * **DO NOT USE FOR NPM PUBLISHING** ### package.npm.json (NPM Distribution) * Used for npm package distribution * Has only runtime dependencies * `authenticate-pam` is an **optional dependency** * **ALWAYS USE THIS FOR NPM PUBLISHING** ## Common Mistakes 1. **Running `npm publish` without arguments** * This triggers rebuild and may use wrong package.json * Always publish pre-built tarball 2. **Not verifying the package before publishing** * Always check that authenticate-pam is optional * Test installation on Linux without PAM headers 3. **Version mismatch** * Keep package.json, package.npm.json, and version.xcconfig in sync ## Testing npm Package ### Quick Docker Test ```bash theme={null} # Test on Ubuntu without PAM headers docker run --rm -it ubuntu:22.04 bash apt update && apt install -y nodejs npm npm install vibetunnel@VERSION # Should succeed without libpam0g-dev # Test with PAM headers apt install -y libpam0g-dev npm install vibetunnel@VERSION # Should also succeed and include authenticate-pam ``` ### Verify Installation ```bash theme={null} # Check if vibetunnel works npx vibetunnel --version # On Linux, check if PAM module loaded (optional) node -e "try { require('authenticate-pam'); console.log('PAM available'); } catch { console.log('PAM not available'); }" ``` ## Emergency Fixes If you accidentally published with wrong configuration: 1. **Unpublish if within 72 hours** (not recommended): ```bash theme={null} npm unpublish vibetunnel@VERSION ``` 2. **Publish a fix version**: * Increment version (e.g., 11.3 → 11.4) * Follow correct process above * Deprecate the broken version: ```bash theme={null} npm deprecate vibetunnel@BROKEN_VERSION "Has installation issues on Linux. Please use VERSION or later." ``` ## Release History & Lessons Learned ### Version 11.1 (Good) * Used `package.npm.json` correctly * `authenticate-pam` was an optional dependency * Linux installations worked without PAM headers ### Version 11.2 (Bad - Unpublished) * Accidentally used main `package.json` * `authenticate-pam` was a required dependency * Failed on Linux without libpam0g-dev * **Issue**: Wrong package.json configuration ### Version 11.3 (Bad - Unpublished) * Also used main `package.json` despite fix attempts * Same Linux installation failures * **Issue**: npm publish process overwrote correct configuration ### Version 11.4 (Good) * Built with explicit `pnpm run build:npm` * Published pre-built tarball * `authenticate-pam` correctly optional * Linux installations work properly ### Version 11.5 (Good) * Published December 2024 * Built with explicit `pnpm run build:npm` * Published pre-built tarball: `vibetunnel-1.0.0-beta.11.5.tgz` * Verified `authenticate-pam` as optional dependency before publishing * Tagged as both `beta` and `latest` * **Process followed correctly**: All three version files updated, tarball verified, published with explicit filename ### Version 12.1 (Good) * Published July 2025 * Built with explicit `pnpm run build:npm` * Published pre-built tarball * `authenticate-pam` correctly optional * Linux installations work properly ### Version 12.2 (Good - Latest) * Published July 17, 2025 * Built with explicit `pnpm run build:npm` * Published pre-built tarball: `vibetunnel-1.0.0-beta.12.2.tgz` * Verified `authenticate-pam` as optional dependency before publishing * Tagged with `beta` tag * **Process followed correctly**: All three version files updated (from beta.13 to beta.12.2), tarball verified, published with explicit filename ## Summary The critical lesson: **package.npm.json must be used for npm distribution**, not package.json. The build script supports this, but you must publish the pre-built tarball, not rely on npm's prepublish hooks. **Golden Rule**: Always build first, verify the package configuration, then publish the tarball. Never use `npm publish` without arguments. # Terminal quick keys chord implementation Source: https://docs.vibetunnel.sh/web/src/client/components/terminal-quick-keys-chord-implementation # Mobile Chord System Implementation ## Overview Implemented a chord system that allows mobile users to use Option+Arrow key combinations for word navigation: * Option key acts as a toggle/modifier * When Option is pressed, it activates and waits for an arrow key * When an arrow key is pressed while Option is active, it sends the combination * Visual feedback shows when Option modifier is active ## Implementation Details ### 1. State Management * Added `activeModifiers` Set to track which modifiers are currently active * Option key toggles its state in the Set rather than sending immediately ### 2. Chord Detection * When Option is pressed, it's added to activeModifiers * When an arrow key is pressed with Option active: * Clears the Option modifier * Sends Option (ESC) first * Then sends the arrow key * This creates the Option+Arrow combination ### 3. Visual Feedback * Added CSS class `.modifier-key.active` with blue background * Option button shows active state when pressed * State clears after arrow key press or when pressing non-arrow keys ### 4. Key Mappings * Option sends ESC (`\x1b`) - already implemented in direct-keyboard-manager.ts * Arrow keys send their normal codes * The combination results in ESC+arrow sequences for word navigation: * Option+Left = ESC+b (word backward) * Option+Right = ESC+f (word forward) ## Testing Added comprehensive tests in `terminal-quick-keys.test.ts` covering: * Toggle behavior of Option key * Chord detection for all arrow keys * Clearing of modifier state * Visual update requests * Multiple chord sequences ## Usage 1. Tap Option key (⌥) - it highlights in blue 2. Tap any arrow key - sends Option+Arrow combination 3. Option automatically deactivates after use 4. Can tap Option again to cancel without sending This provides an intuitive way for mobile users to access word navigation without requiring a physical keyboard. # SEQUENTIAL OPTIMIZATIONS Source: https://docs.vibetunnel.sh/web/src/test/playwright/SEQUENTIAL_OPTIMIZATIONS # Playwright Sequential Test Optimizations This document outlines the optimizations made for VibeTunnel's Playwright tests, designed to work efficiently with the single-server, system-wide session architecture. ## Architecture Constraints VibeTunnel's architecture requires sequential test execution because: * Sessions are stored system-wide in `~/.vibetunnel/control/` * Server maintains shared in-memory state for all sessions * PTY processes and Unix sockets can conflict between parallel tests * No session isolation or namespacing mechanism exists ## Optimization Strategies ### 1. Server Reuse (High Impact) * **File**: `playwright.config.ts` * **Change**: `reuseExistingServer: !process.env.CI` * **Impact**: Saves 10-30 seconds per test run locally * **How**: Keeps server running between test executions ### 2. Smart Session Cleanup * **File**: `helpers/session-cleanup.helper.ts` * **Features**: * Pattern-based cleanup (e.g., test-*, pool-*) * Age-based cleanup (remove sessions older than X minutes) * Status-based cleanup (remove only exited sessions) * Batch API operations for efficiency * **Impact**: Prevents session accumulation, faster cleanup ### 3. Session Pooling * **File**: `helpers/session-pool.helper.ts` * **Features**: * Pre-create sessions for test reuse * Acquire/release pattern * Automatic session verification * Clear terminal between uses * **Impact**: Reduces session creation overhead by \~70% ### 4. Batch API Operations * **File**: `helpers/batch-operations.helper.ts` * **Features**: * Create/delete multiple sessions in one call * Parallel promise execution * Status filtering and verification * Batch input/resize operations * **Impact**: 5-10x faster for multi-session operations ### 5. Optimized Wait Strategies * **File**: `utils/optimized-wait.utils.ts` * **Features**: * Reduced default timeouts (3s vs 5s) * Early exit conditions * Parallel wait operations * Smart network idle detection * **Impact**: 30-50% reduction in wait times ### 6. Test Organization * **File**: `fixtures/sequential-test.fixture.ts` * **Features**: * Test groups by resource usage (light/heavy/critical) * Global setup/teardown hooks * Automatic cleanup fixtures * Lazy-loaded utilities * **Impact**: Better test prioritization and resource management ## Usage Examples ### Basic Test with Optimizations ```typescript theme={null} import { test, expect } from '../fixtures/sequential-test.fixture'; test('optimized test example', async ({ page, batchOps, waitUtils, cleanupHelper }) => { // Fast app initialization check await page.goto('/'); await waitUtils.waitForAppReady(page); // Efficient session creation const sessions = await batchOps.createSessions([ { name: 'test-1' }, { name: 'test-2' } ]); // Automatic cleanup via fixture }); ``` ### Using Session Pool ```typescript theme={null} test('reuse sessions from pool', async ({ sessionPool, page }) => { // Get pre-created session const session = await sessionPool.acquire(); // Use session for testing await page.goto(`/sessions/${session.id}`); // Return to pool for next test await sessionPool.release(session.id); }); ``` ### Batch Operations ```typescript theme={null} test('batch operations example', async ({ batchOps }) => { // Create 10 sessions at once const sessions = await batchOps.createSessions( Array(10).fill(0).map((_, i) => ({ name: `batch-${i}` })) ); // Delete all at once const ids = sessions.map(s => s.id); await batchOps.deleteSessions(ids); }); ``` ## Performance Metrics ### Before Optimizations * Server startup: 10-30s per run * Session creation: 500-1000ms each * Session cleanup: 200-500ms each * Wait operations: 5000ms timeouts * Total test suite: \~5-10 minutes ### After Optimizations * Server startup: 0s (reused locally) * Session creation: 100-200ms (pooled), 200-300ms (batch) * Session cleanup: 50-100ms (batch API) * Wait operations: 1000-3000ms timeouts * Total test suite: \~2-3 minutes ### Net Improvement * **Local development**: 50-70% faster * **CI pipeline**: 30-40% faster * **Reduced flakiness**: Smarter waits and cleanup * **Resource usage**: Lower with session pooling ## Best Practices 1. **Use Batch Operations**: When creating/deleting multiple sessions 2. **Leverage Session Pool**: For tests that don't need fresh sessions 3. **Smart Cleanup**: Use pattern-based cleanup instead of individual 4. **Reduced Timeouts**: Use OptimizedWaitUtils for faster waits 5. **Test Grouping**: Organize tests by resource usage ## Running Tests ```bash theme={null} # Run all tests (sequential) pnpm test:e2e # Run specific test group pnpm test:e2e --grep "light" # Run with detailed timing pnpm test:e2e --reporter=list # Debug slow tests PWDEBUG=1 pnpm test:e2e ``` ## Future Improvements 1. **Test-specific control directories**: Isolate session storage per test 2. **In-memory session mode**: Skip file system for test sessions 3. **WebSocket connection pooling**: Reuse connections across tests 4. **Snapshot testing**: Reduce terminal interaction tests 5. **API-only test mode**: Skip UI for pure API tests