docs: Add Linux setup instructions and authentication documentation (#344)

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Peter Steinberger 2025-07-15 02:47:25 +02:00 committed by GitHub
parent 2fc28b687f
commit 68e6456aef
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 3661 additions and 133 deletions

265
README.md
View file

@ -16,11 +16,27 @@
Ever wanted to check on your AI agents while you're away? Need to monitor that long-running build from your phone? Want to share a terminal session with a colleague without complex SSH setups? VibeTunnel makes it happen with zero friction.
## Installation Options
### macOS App (Recommended for Mac users)
The native macOS app provides the best experience with menu bar integration and automatic updates.
### npm Package (Linux & Headless Systems)
For Linux servers, Docker containers, or headless macOS systems, install via npm:
```bash
npm install -g vibetunnel
```
This gives you the full VibeTunnel server with web UI, just without the macOS menu bar app. See the [npm Package section](#npm-package) for detailed usage.
## Quick Start
### Requirements
**VibeTunnel requires an Apple Silicon Mac (M1+).** Intel Macs are not supported.
**macOS App**: Requires an Apple Silicon Mac (M1+). Intel Macs are not supported for the native app.
**npm Package**: Works on any system with Node.js 20+, including Intel Macs and Linux. Windows is not yet supported ([#252](https://github.com/amantus-ai/vibetunnel/issues/252)).
### 1. Download & Install
@ -38,6 +54,24 @@ VibeTunnel lives in your menu bar. Click the icon to start the server.
### 3. Use the `vt` Command
The `vt` command is a smart wrapper that forwards your terminal sessions through VibeTunnel:
**How it works**:
- `vt` is a bash script that internally calls `vibetunnel fwd` to forward terminal output
- It provides additional features like shell alias resolution and session title management
- Available from both the Mac app and npm package installations
**Installation sources**:
- **macOS App**: Creates `/usr/local/bin/vt` symlink during installation
- **npm Package**: Installs `vt` globally, with intelligent Mac app detection
**Smart detection**:
When you run `vt` from the npm package, it:
1. Checks if the Mac app is installed at `/Applications/VibeTunnel.app`
2. If found, forwards to the Mac app's `vt` for the best experience
3. If not found, uses the npm-installed `vibetunnel fwd`
4. This ensures `vt` always uses the best available implementation
```bash
# Run any command in the browser
vt pnpm run dev
@ -67,11 +101,11 @@ Visit [http://localhost:4020](http://localhost:4020) to see all your terminal se
- **🚀 Zero Configuration** - No SSH keys, no port forwarding, no complexity
- **🤖 AI Agent Friendly** - Perfect for monitoring Claude Code, ChatGPT, or any terminal-based AI tools
- **📊 Dynamic Terminal Titles** - Real-time activity tracking shows what's happening in each session
- **🔒 Secure by Design** - Password protection, localhost-only mode, or secure tunneling via Tailscale/ngrok
- **🔒 Secure by Design** - Multiple authentication modes, localhost-only mode, or secure tunneling via Tailscale/ngrok
- **📱 Mobile Ready** - Native iOS app and responsive web interface for phones and tablets
- **🎬 Session Recording** - All sessions recorded in asciinema format for later playback
- **⚡ High Performance** - Powered by Bun runtime for blazing-fast JavaScript execution
- **🍎 Apple Silicon Native** - Optimized for M1/M2/M3 Macs with ARM64-only binaries
- **⚡ High Performance** - Optimized Node.js server with minimal resource usage
- **🍎 Apple Silicon Native** - Optimized for Apple Silicon (M1+) Macs with ARM64-only binaries
- **🐚 Shell Alias Support** - Your custom aliases and shell functions work automatically
> **Note**: The iOS app and Tauri-based components are still work in progress and not recommended for production use yet.
@ -81,10 +115,10 @@ Visit [http://localhost:4020](http://localhost:4020) to see all your terminal se
VibeTunnel consists of three main components:
1. **macOS Menu Bar App** - Native Swift application that manages the server lifecycle
2. **Node.js/Bun Server** - High-performance TypeScript server handling terminal sessions
2. **Node.js Server** - High-performance TypeScript server handling terminal sessions
3. **Web Frontend** - Modern web interface using Lit components and xterm.js
The server runs as a standalone Bun executable with embedded Node.js modules, providing excellent performance and minimal resource usage.
The server runs as a standalone Node.js executable with embedded modules, providing excellent performance and minimal resource usage.
## Remote Access Options
@ -132,7 +166,7 @@ The server runs as a standalone Bun executable with embedded Node.js modules, pr
**Note**: Free ngrok URLs change each time you restart the tunnel. You can claim one free static domain per user, or upgrade to a paid plan for multiple domains.
### Option 3: Local Network
1. Set a dashboard password in settings
1. Configure authentication (see Authentication section)
2. Switch to "Network" mode
3. Access via `http://[your-mac-ip]:4020`
@ -165,13 +199,224 @@ Dynamic mode includes real-time activity detection:
- Shows `•` when there's terminal output within 5 seconds
- Claude commands show specific status (Crafting, Transitioning, etc.)
- Extensible system for future app-specific detectors
## Authentication
VibeTunnel provides multiple authentication modes to secure your terminal sessions:
### Authentication Modes
#### 1. System Authentication (Default)
Uses your operating system's native authentication:
- **macOS**: Authenticates against local user accounts
- **Linux**: Uses PAM (Pluggable Authentication Modules)
- Login with your system username and password
#### 2. Environment Variable Authentication
Simple authentication for deployments:
```bash
export VIBETUNNEL_USERNAME=admin
export VIBETUNNEL_PASSWORD=your-secure-password
npm run start
```
#### 3. SSH Key Authentication
Use Ed25519 SSH keys from `~/.ssh/authorized_keys`:
```bash
# Enable SSH key authentication
npm run start -- --enable-ssh-keys
# Make SSH keys mandatory (disable password auth)
npm run start -- --enable-ssh-keys --disallow-user-password
```
#### 4. No Authentication
For trusted environments only:
```bash
npm run start -- --no-auth
```
#### 5. Local Bypass (Development Only)
Allow localhost connections to bypass authentication:
```bash
# Basic local bypass (DEVELOPMENT ONLY - NOT FOR PRODUCTION)
npm run start -- --allow-local-bypass
# With token for additional security (minimum for production)
npm run start -- --allow-local-bypass --local-auth-token mytoken
```
**Security Note**: Local bypass uses `req.socket.remoteAddress` which cannot be spoofed remotely due to TCP's three-way handshake. The implementation also rejects requests with proxy headers (`X-Forwarded-For`, etc.) to prevent header injection attacks. However:
- **Development only**: Basic bypass without token should never be used in production
- **Local processes**: Any process on the same machine can access the API
- **Always use tokens**: In production, always require `--local-auth-token`
- **Consider alternatives**: For production, use proper authentication instead of local bypass
### macOS App Authentication
The macOS menu bar app supports these authentication modes:
- **No Authentication**: For trusted environments only
- **System Authentication**: Uses your macOS user account credentials
- **SSH Key Authentication**: Uses Ed25519 SSH keys from `~/.ssh/authorized_keys`
- Configure via Settings → Security when in "Network" mode
### Security Best Practices
1. **Always use authentication** when binding to network interfaces (`--bind 0.0.0.0`)
2. **Use HTTPS** in production with a reverse proxy (nginx, Caddy)
3. **Rotate credentials** regularly
4. **Consider SSH keys** for stronger security
5. **Never use local bypass without tokens** in production environments
6. **Monitor access logs** for suspicious authentication patterns
7. **Default to secure** - explicitly enable less secure options only when needed
## npm Package
The VibeTunnel npm package provides the full server functionality for Linux, Docker, CI/CD environments, and headless macOS systems.
### Installation
```bash
# Install globally via npm
npm install -g vibetunnel
# Or with yarn
yarn global add vibetunnel
# Or with pnpm
pnpm add -g vibetunnel
```
**Requirements**: Node.js 20.0.0 or higher
### Running the VibeTunnel Server
#### Basic Usage
```bash
# Start with default settings (localhost:4020)
vibetunnel
# Bind to all network interfaces
vibetunnel --bind 0.0.0.0
# Use a custom port
vibetunnel --port 8080
# With authentication
VIBETUNNEL_USERNAME=admin VIBETUNNEL_PASSWORD=secure vibetunnel --bind 0.0.0.0
# Enable debug logging
VIBETUNNEL_DEBUG=1 vibetunnel
# Run without authentication (trusted networks only!)
vibetunnel --no-auth
```
#### Using the `vt` Command
The `vt` command wrapper makes it easy to forward terminal sessions:
```bash
# Monitor AI agents with automatic activity tracking
vt claude
vt claude --dangerously-skip-permissions
vt --title-mode dynamic claude # See real-time Claude status
# Run any command and see it in the browser
vt npm test
vt python script.py
vt cargo build --release
# Open an interactive shell
vt --shell
vt -i # short form
# Control terminal titles
vt --title-mode static npm run dev # Shows path and command
vt --title-mode dynamic python app.py # Shows path, command, and activity
vt --title-mode filter vim # Blocks vim from changing title
# Shell aliases work automatically!
vt claude-danger # Your custom alias for claude --dangerously-skip-permissions
# Update session title (inside a VibeTunnel session)
vt title "My Project - Testing"
```
### Mac App Interoperability
The npm package is designed to work seamlessly alongside the Mac app:
#### Smart Command Routing
- The `vt` command automatically detects if the Mac app is installed
- If found at `/Applications/VibeTunnel.app`, it defers to the Mac app
- If not found, it uses the npm-installed server
- This ensures you always get the best available implementation
#### Installation Behavior
- If `/usr/local/bin/vt` already exists (from another tool), npm won't overwrite it
- You'll see a helpful warning with alternatives: `vibetunnel` or `npx vt`
- The installation always succeeds, even if the `vt` symlink can't be created
#### When to Use Each Version
- **Mac app only**: Best for macOS users who want menu bar integration
- **npm only**: Perfect for Linux, Docker, CI/CD, or headless servers
- **Both installed**: Mac app takes precedence, npm serves as fallback
- **Development**: npm package useful for testing without affecting Mac app
### Package Contents
The npm package includes:
- Full VibeTunnel server with web UI
- CLI tools (`vibetunnel` and `vt` commands)
- Native PTY support via node-pty
- Pre-built binaries for common platforms
- Complete feature parity with macOS app (minus menu bar)
### Building the npm Package
For maintainers who need to build the npm package:
#### Unified Build (Multi-Platform by Default)
```bash
# Build with prebuilt binaries for all platforms
# Requires Docker for Linux cross-compilation
npm run build:npm
```
This creates prebuilt binaries for:
- macOS (x64, arm64) - Node.js 20, 22, 23, 24
- Linux (x64, arm64) - Node.js 20, 22, 23, 24
#### Build Options
```bash
# Current platform only (faster for development)
node scripts/build-npm.js --current-only
# Specific platform/architecture
node scripts/build-npm.js --platform darwin --arch arm64
# Skip Docker builds
node scripts/build-npm.js --no-docker
```
#### Publishing
```bash
# Test the package locally
npm pack
# Publish to npm
npm publish
```
## Building from Source
### Prerequisites
- macOS 14.0+ (Sonoma) on Apple Silicon (M1/M2/M3)
- macOS 14.0+ (Sonoma) on Apple Silicon (M1+)
- Xcode 16.0+
- Node.js 20+
- Bun runtime
- Node.js 20+ (minimum supported version)
### Build Steps

View file

@ -282,7 +282,17 @@ else
echo "Warning: authenticate_pam.node not found. PAM authentication may not work."
fi
echo "✓ Native executable and modules copied successfully"
# Copy unified vt script
if [ -f "${WEB_DIR}/bin/vt" ]; then
echo "Copying unified vt script..."
cp "${WEB_DIR}/bin/vt" "${APP_RESOURCES}/"
chmod +x "${APP_RESOURCES}/vt"
else
echo "error: Unified vt script not found at ${WEB_DIR}/bin/vt"
exit 1
fi
echo "✓ Native executable, modules, and vt script copied successfully"
# Sanity check: Verify all required binaries are present in the app bundle
echo "Performing final sanity check..."
@ -314,6 +324,16 @@ if [ -f "${APP_RESOURCES}/spawn-helper" ] && [ ! -x "${APP_RESOURCES}/spawn-help
MISSING_FILES+=("spawn-helper is not executable")
fi
# Check for vt script
if [ ! -f "${APP_RESOURCES}/vt" ]; then
MISSING_FILES+=("vt script")
fi
# Check if vt script is executable
if [ -f "${APP_RESOURCES}/vt" ] && [ ! -x "${APP_RESOURCES}/vt" ]; then
MISSING_FILES+=("vt script is not executable")
fi
# If any files are missing, fail the build
if [ ${#MISSING_FILES[@]} -gt 0 ]; then
echo "error: Build sanity check failed! Missing required files:"
@ -323,7 +343,7 @@ if [ ${#MISSING_FILES[@]} -gt 0 ]; then
echo "Build artifacts in ${NATIVE_DIR}:"
ls -la "${NATIVE_DIR}" || echo " Directory does not exist"
echo "App resources in ${APP_RESOURCES}:"
ls -la "${APP_RESOURCES}/vibetunnel" "${APP_RESOURCES}/pty.node" "${APP_RESOURCES}/spawn-helper" 2>/dev/null || true
ls -la "${APP_RESOURCES}/vibetunnel" "${APP_RESOURCES}/pty.node" "${APP_RESOURCES}/spawn-helper" "${APP_RESOURCES}/vt" 2>/dev/null || true
exit 1
fi

18
web/.gitignore vendored
View file

@ -121,6 +121,10 @@ native/
# Custom Node.js builds
.node-builds/
# Prebuild binaries (generated)
prebuilds/
prebuilds-linux/
# Bun lockfile (generated during native build)
bun.lock
@ -137,3 +141,17 @@ coverage-summary.json
# Playwright traces and test data
data/
trace/
# Compiled binaries (should not be in git)
bin-macos/
*.dylib
*.so
*.exe
spawn-helper*
# Test recordings and artifacts
*.cast
test.cast
# Temporary build directories
temp-spawn-helper/

1
web/.npm-lock-notice Normal file
View file

@ -0,0 +1 @@
This package was built for npm distribution. Some dev files are excluded.

65
web/.npmignore Normal file
View file

@ -0,0 +1,65 @@
# Development and build files
src/
scripts/
docs/
test/
playwright/
coverage/
.husky/
# Config files not needed in production
.gitignore
.prettierignore
.prettierrc
biome.json
vitest.config.ts
playwright.config.ts
playwright.config.skip-failing.ts
tsconfig.*.json
tsconfig.json
postcss.config.js
tailwind.config.js
# Build artifacts and temp files
build/
*.log
*.tmp
.DS_Store
node_modules/
pnpm-lock.yaml
# Native build scripts - not needed for npm package
build-native.js
build-native-clean.sh
build-custom-node.js
custom-node.md
native/
# Development files
CLAUDE.md
DEVELOPMENT.md
LOGGING_STYLE_GUIDE.md
SECURITY.md
fwd-test.ts
spec.md
# Only include the built node-pty, not source
node-pty/src/
node-pty/tsconfig.json
node-pty/*.ts
node-pty/node_modules/
# Test files
public/bundle/test.js
public/bundle/screencap.js
public/test/
public/test.cast
# Keep:
# - dist/ (compiled server code)
# - public/ (web interface)
# - bin/ (CLI entry points)
# - node-pty/build/ (native module)
# - node-pty/lib/ (JS files)
# - package.json
# - README.md

View file

@ -3,6 +3,4 @@
# - enable-pre-post-scripts (pre/post scripts are enabled by default in npm 7+)
# - auto-install-peers (use --legacy-peer-deps if needed)
# - unsafe-perm (no longer needed in npm 7+)
# Approve builds for vendored packages
side-effects-cache-unsafe=@vibetunnel/vendored-pty
# - side-effects-cache-unsafe (deprecated in npm 9+)

35
web/.prebuildrc Normal file
View file

@ -0,0 +1,35 @@
{
"targets": [
{
"runtime": "node",
"target": "20.0.0"
},
{
"runtime": "node",
"target": "22.0.0"
},
{
"runtime": "node",
"target": "23.0.0"
},
{
"runtime": "node",
"target": "24.0.0"
}
],
"include": [
"node-pty/build/Release/pty.node"
],
"prebuild": [
{
"name": "node-pty",
"binary": {
"module_name": "pty",
"module_path": "./node-pty/build/Release/",
"remote_path": "{version}",
"package_name": "{module_name}-v{version}-{node_abi}-{platform}-{arch}.tar.gz",
"host": "https://github.com/amantus-ai/vibetunnel/releases/download/"
}
}
]
}

View file

@ -1,81 +1,99 @@
# VibeTunnel Web
# VibeTunnel CLI
Web terminal interface and server for VibeTunnel.
Full-featured terminal sharing server with web interface for macOS and Linux. Windows not yet supported.
## Quick Start
Production users: Use the pre-built VibeTunnel executable from the main app.
## Development
## Installation
```bash
pnpm install
pnpm run dev # Watch mode: server + client
pnpm run dev:client # Watch mode: client only (for debugging server)
npm install -g vibetunnel
```
Open http://localhost:3000
## Requirements
### Pre-commit Hooks
- Node.js >= 20.0.0
- macOS or Linux (Windows not yet supported)
- Build tools for native modules (Xcode on macOS, build-essential on Linux)
This project uses husky and lint-staged to enforce code quality standards. After running `pnpm install`, pre-commit hooks will automatically:
## Usage
- Format code with Biome
- Check for linting errors
- Run TypeScript type checking for all configs
If your commit fails due to linting or type errors, fix the issues and try again. Many formatting issues will be auto-fixed.
### Build Commands
### Start the server
```bash
pnpm run clean # Remove build artifacts
pnpm run build # Build everything (including native executable)
pnpm run lint # Check code style
pnpm run lint:fix # Fix code style
pnpm run typecheck # Type checking
pnpm run test # Run all tests (unit + e2e)
pnpm run format # Format code
# Start with default settings (port 4020)
vibetunnel
# Start with custom port
vibetunnel --port 8080
# Start without authentication
vibetunnel --no-auth
```
## Production Build
Then open http://localhost:4020 in your browser to access the web interface.
### Use the vt command wrapper
The `vt` command allows you to run commands with TTY forwarding:
```bash
pnpm run build # Creates Node.js SEA executable
./native/vibetunnel # Run standalone executable (no Node.js required)
# Monitor AI agents with automatic activity tracking
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"
```
## Architecture
### Forward commands to a session
See [spec.md](./spec.md) for detailed architecture documentation.
```bash
# Basic usage
vibetunnel fwd <session-id> <command> [args...]
## Key Features
# Examples
vibetunnel fwd --session-id abc123 ls -la
vibetunnel fwd --session-id abc123 npm test
vibetunnel fwd --session-id abc123 python script.py
```
- Terminal sessions via node-pty
- Real-time streaming (SSE + WebSocket)
- Binary-optimized buffer updates
- Multi-session support
- File browser integration
## Features
## Terminal Resizing Behavior
- **Web-based terminal interface** - Access terminals from any browser
- **Multiple concurrent sessions** - Run multiple terminals simultaneously
- **Real-time synchronization** - See output in real-time
- **TTY forwarding** - Full terminal emulation support
- **Session management** - Create, list, and manage sessions
- **Cross-platform** - Works on macOS and Linux
- **No dependencies** - Just Node.js required
VibeTunnel intelligently handles terminal width based on how the session was created:
## Package Contents
### Tunneled Sessions (via `vt` command)
- Sessions created by running `vt` in a native terminal window
- Terminal width is automatically limited to the native terminal's width to prevent text overflow
- Prevents flickering and display issues in the native terminal
- Shows "≤120" (or actual width) in the width selector when limited
- Users can manually override this limit using the width selector
This npm package includes:
- Full VibeTunnel server with web UI
- Command-line tools (vibetunnel, vt)
- Native PTY support for terminal emulation
- Web interface with xterm.js
- Session management and forwarding
### Frontend-Created Sessions
- Sessions created directly from the web interface (using the "New Session" button)
- No width restrictions by default - uses full browser width
- Perfect for web-only workflows where no native terminal is involved
- Shows "∞" in the width selector for unlimited width
## Platform Support
### Manual Width Control
- Click the width indicator in the session header to open the width selector
- Choose from common terminal widths (80, 120, 132, etc.) or unlimited
- Width preferences are saved per session and persist across reloads
- Selecting any width manually overrides automatic limitations
- macOS (Intel and Apple Silicon)
- Linux (x64 and ARM64)
- Windows: Not yet supported ([#252](https://github.com/amantus-ai/vibetunnel/issues/252))
## Documentation
See the main repository for complete documentation: https://github.com/amantus-ai/vibetunnel
## License
MIT

4
web/bin/vibetunnel Executable file
View file

@ -0,0 +1,4 @@
#!/usr/bin/env node
// Start the CLI - it handles all command routing including 'fwd'
require('../dist/cli.js');

View file

@ -1,47 +1,107 @@
#!/bin/bash
# VibeTunnel CLI wrapper
# Unified VibeTunnel CLI wrapper - compatible with both Mac app and npm installations
# First, find the VibeTunnel app location
# Try standard locations first, but verify the binary exists
APP_PATH=""
for TRY_PATH in "/Applications/VibeTunnel.app" "$HOME/Applications/VibeTunnel.app"; do
if [ -d "$TRY_PATH" ] && [ -f "$TRY_PATH/Contents/Resources/vibetunnel" ]; then
APP_PATH="$TRY_PATH"
break
fi
done
# Only check for Mac app on macOS
if [[ "$OSTYPE" == "darwin"* ]]; then
# macOS symlink resolution function using BSD readlink
resolve_symlink_macos() {
local target="$1"
local current="$target"
while [ -L "$current" ]; do
current="$(readlink "$current")"
# Handle relative symlinks
if [[ "$current" != /* ]]; then
current="$(dirname "$target")/$current"
fi
done
echo "$current"
}
# If not found in standard locations with valid binary, search for it
if [ -z "$APP_PATH" ]; then
# First try DerivedData (for development)
for CANDIDATE in $(find ~/Library/Developer/Xcode/DerivedData -name "VibeTunnel.app" -type d 2>/dev/null | grep -v "\.dSYM" | grep -v "Index\.noindex"); do
if [ -f "$CANDIDATE/Contents/Resources/vibetunnel" ]; then
APP_PATH="$CANDIDATE"
# Get the real path of this script to avoid infinite recursion
SCRIPT_REAL_PATH="$(resolve_symlink_macos "${BASH_SOURCE[0]}")"
# Comprehensive Mac app search - try standard locations first, then development locations
APP_PATH=""
# First try standard locations with valid binary check
for TRY_PATH in "/Applications/VibeTunnel.app" "$HOME/Applications/VibeTunnel.app"; do
if [ -d "$TRY_PATH" ] && [ -f "$TRY_PATH/Contents/Resources/vibetunnel" ]; then
VT_SCRIPT="$TRY_PATH/Contents/Resources/vt"
if [ -f "$VT_SCRIPT" ] && [ -x "$VT_SCRIPT" ]; then
# Avoid infinite recursion by checking if this is the same script
VT_REAL_PATH="$(resolve_symlink_macos "$VT_SCRIPT")"
if [ "$SCRIPT_REAL_PATH" != "$VT_REAL_PATH" ]; then
exec "$VT_SCRIPT" "$@"
fi
fi
APP_PATH="$TRY_PATH"
break
fi
done
# If still not found, use mdfind as last resort
# If not found in standard locations, search for development builds
if [ -z "$APP_PATH" ]; then
for CANDIDATE in $(mdfind -name "VibeTunnel.app" 2>/dev/null | grep -v "\.dSYM"); do
# First try DerivedData (for development)
for CANDIDATE in $(find ~/Library/Developer/Xcode/DerivedData -name "VibeTunnel.app" -type d 2>/dev/null | grep -v "\.dSYM" | grep -v "Index\.noindex"); do
if [ -f "$CANDIDATE/Contents/Resources/vibetunnel" ]; then
VT_SCRIPT="$CANDIDATE/Contents/Resources/vt"
if [ -f "$VT_SCRIPT" ] && [ -x "$VT_SCRIPT" ]; then
VT_REAL_PATH="$(resolve_symlink_macos "$VT_SCRIPT")"
if [ "$SCRIPT_REAL_PATH" != "$VT_REAL_PATH" ]; then
exec "$VT_SCRIPT" "$@"
fi
fi
APP_PATH="$CANDIDATE"
break
fi
done
# If still not found, use mdfind as last resort
if [ -z "$APP_PATH" ]; then
for CANDIDATE in $(mdfind -name "VibeTunnel.app" 2>/dev/null | grep -v "\.dSYM"); do
if [ -f "$CANDIDATE/Contents/Resources/vibetunnel" ]; then
VT_SCRIPT="$CANDIDATE/Contents/Resources/vt"
if [ -f "$VT_SCRIPT" ] && [ -x "$VT_SCRIPT" ]; then
VT_REAL_PATH="$(resolve_symlink_macos "$VT_SCRIPT")"
if [ "$SCRIPT_REAL_PATH" != "$VT_REAL_PATH" ]; then
exec "$VT_SCRIPT" "$@"
fi
fi
APP_PATH="$CANDIDATE"
break
fi
done
fi
fi
if [ -z "$APP_PATH" ]; then
echo "Error: VibeTunnel.app with vibetunnel binary not found anywhere on the system" >&2
exit 1
# If we found a Mac app but couldn't use its vt script, use its binary directly
if [ -n "$APP_PATH" ]; then
VIBETUNNEL_BIN="$APP_PATH/Contents/Resources/vibetunnel"
if [ -f "$VIBETUNNEL_BIN" ]; then
# Found Mac app bundle - will use this binary
echo "# Using VibeTunnel from Mac app bundle" >&2
fi
fi
fi
# Execute vibetunnel from app bundle
VIBETUNNEL_BIN="$APP_PATH/Contents/Resources/vibetunnel"
if [ ! -f "$VIBETUNNEL_BIN" ]; then
echo "Error: vibetunnel binary not found in app bundle at $VIBETUNNEL_BIN" >&2
exit 1
# If we get here without a Mac app, use the npm-installed vibetunnel
if [ -z "$VIBETUNNEL_BIN" ]; then
# First, try to find vibetunnel in the same directory as this script
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
if [ -f "$SCRIPT_DIR/vibetunnel" ]; then
VIBETUNNEL_BIN="$SCRIPT_DIR/vibetunnel"
else
# Try to find vibetunnel in PATH
if command -v vibetunnel >/dev/null 2>&1; then
VIBETUNNEL_BIN="$(command -v vibetunnel)"
fi
fi
if [ -z "$VIBETUNNEL_BIN" ] || [ ! -f "$VIBETUNNEL_BIN" ]; then
echo "Error: vibetunnel binary not found. Please ensure vibetunnel is installed." >&2
echo "Install with: npm install -g vibetunnel" >&2
exit 1
fi
fi
# Check if we're already inside a VibeTunnel session
@ -115,8 +175,9 @@ TITLE MODES:
dynamic Show directory, command, and live activity status (default for web UI)
NOTE:
This script automatically uses the vibetunnel executable bundled with
VibeTunnel from the app bundle.
This script automatically detects and uses the best available VibeTunnel installation:
- Mac app bundle (preferred on macOS)
- npm package installation (fallback)
EOF
# Show path and version info
@ -124,20 +185,39 @@ EOF
echo "VIBETUNNEL BINARY:"
echo " Path: $VIBETUNNEL_BIN"
if [ -f "$VIBETUNNEL_BIN" ]; then
# Extract version from the vibetunnel output
# Try to get version from binary output first (works for both Mac app and npm)
VERSION_INFO=$("$VIBETUNNEL_BIN" --version 2>&1 | grep "^VibeTunnel Server" | head -n 1)
BUILD_INFO=$("$VIBETUNNEL_BIN" --version 2>&1 | grep "^Built:" | head -n 1)
PLATFORM_INFO=$("$VIBETUNNEL_BIN" --version 2>&1 | grep "^Platform:" | head -n 1)
if [ -n "$VERSION_INFO" ]; then
echo " Version: ${VERSION_INFO#VibeTunnel Server }"
else
# Fallback to package.json for npm installations
PACKAGE_JSON="$(dirname "$(dirname "$VIBETUNNEL_BIN")")/package.json"
if [ -f "$PACKAGE_JSON" ]; then
VERSION=$(grep '"version"' "$PACKAGE_JSON" | head -1 | sed 's/.*"version".*:.*"\(.*\)".*/\1/')
echo " Version: $VERSION"
fi
fi
if [ -n "$BUILD_INFO" ]; then
echo " ${BUILD_INFO}"
fi
if [ -n "$PLATFORM_INFO" ]; then
echo " ${PLATFORM_INFO}"
fi
# Determine installation type
if [[ "$VIBETUNNEL_BIN" == */Applications/VibeTunnel.app/* ]]; then
echo " Status: Mac app bundle"
elif [[ "$VIBETUNNEL_BIN" == */DerivedData/* ]]; then
echo " Status: Development build"
elif [[ "$VIBETUNNEL_BIN" == *npm* ]] || [[ "$VIBETUNNEL_BIN" == */bin/vibetunnel ]]; then
echo " Status: Installed via npm"
else
echo " Status: Unknown installation"
fi
else
echo " Status: Not found"
fi
@ -156,15 +236,15 @@ resolve_command() {
case "$shell_name" in
zsh)
# For zsh, we need interactive mode to get aliases
exec "$VIBETUNNEL_BIN" fwd $TITLE_MODE_ARGS "$user_shell" -i -c "$(printf '%q ' "$cmd" "$@")"
exec "$VIBETUNNEL_BIN" fwd ${TITLE_MODE_ARGS:+"$TITLE_MODE_ARGS"} "$user_shell" -i -c "$(printf '%q ' "$cmd" "$@")"
;;
bash)
# For bash, expand aliases in non-interactive mode
exec "$VIBETUNNEL_BIN" fwd $TITLE_MODE_ARGS "$user_shell" -c "shopt -s expand_aliases; source ~/.bashrc 2>/dev/null || source ~/.bash_profile 2>/dev/null || true; $(printf '%q ' "$cmd" "$@")"
exec "$VIBETUNNEL_BIN" fwd ${TITLE_MODE_ARGS:+"$TITLE_MODE_ARGS"} "$user_shell" -c "shopt -s expand_aliases; source ~/.bashrc 2>/dev/null || source ~/.bash_profile 2>/dev/null || true; $(printf '%q ' "$cmd" "$@")"
;;
*)
# Generic shell handling
exec "$VIBETUNNEL_BIN" fwd $TITLE_MODE_ARGS "$user_shell" -c "$(printf '%q ' "$cmd" "$@")"
exec "$VIBETUNNEL_BIN" fwd ${TITLE_MODE_ARGS:+"$TITLE_MODE_ARGS"} "$user_shell" -c "$(printf '%q ' "$cmd" "$@")"
;;
esac
}
@ -207,12 +287,12 @@ fi
if [ $# -gt 0 ] && [[ "$1" != -* ]]; then
if [[ "$NO_SHELL_WRAP" == "true" ]]; then
# Execute directly without shell wrapper
exec "$VIBETUNNEL_BIN" fwd $TITLE_MODE_ARGS "$@"
exec "$VIBETUNNEL_BIN" fwd ${TITLE_MODE_ARGS:+"$TITLE_MODE_ARGS"} "$@"
else
# Check if the first argument is a real binary
if which "$1" >/dev/null 2>&1; then
# It's a real binary, execute directly
exec "$VIBETUNNEL_BIN" fwd $TITLE_MODE_ARGS "$@"
exec "$VIBETUNNEL_BIN" fwd ${TITLE_MODE_ARGS:+"$TITLE_MODE_ARGS"} "$@"
else
# Not a real binary, try alias resolution
resolve_command "$@"
@ -220,5 +300,5 @@ if [ $# -gt 0 ] && [[ "$1" != -* ]]; then
fi
else
# Run with fwd command (original behavior for options)
exec "$VIBETUNNEL_BIN" fwd $TITLE_MODE_ARGS "$@"
exec "$VIBETUNNEL_BIN" fwd ${TITLE_MODE_ARGS:+"$TITLE_MODE_ARGS"} "$@"
fi

406
web/docs/npm.md Normal file
View file

@ -0,0 +1,406 @@
# 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
- **Components**:
- `authenticate_pam.node`: Node.js addon for system authentication
- **Platforms**: Linux primarily, macOS for compatibility
- **Dependencies**: System PAM libraries
## 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**: 20, 22, 23, 24
- **Platforms**: macOS (x64, arm64), Linux (x64, arm64)
- **Total prebuilds**: 32 binaries (16 per native module)
### Prebuild Files
```
prebuilds/
├── node-pty-v1.0.0-node-v115-darwin-arm64.tar.gz
├── node-pty-v1.0.0-node-v115-darwin-x64.tar.gz
├── node-pty-v1.0.0-node-v115-linux-arm64.tar.gz
├── node-pty-v1.0.0-node-v115-linux-x64.tar.gz
├── authenticate-pam-v1.0.5-node-v115-darwin-arm64.tar.gz
├── authenticate-pam-v1.0.5-node-v115-darwin-x64.tar.gz
├── authenticate-pam-v1.0.5-node-v115-linux-arm64.tar.gz
├── authenticate-pam-v1.0.5-node-v115-linux-x64.tar.gz
└── ... (similar for node versions 22, 23, 24)
```
Note: Node version numbers map to internal versions (v115=Node 20, v127=Node 22, v131=Node 23, v134=Node 24)
## Build Process
### Unified Build (Multi-Platform by Default)
```bash
npm run build:npm
```
- Compiles TypeScript and bundles client code
- Builds native modules for all supported platforms (macOS x64/arm64, Linux x64/arm64)
- Creates comprehensive prebuilds for zero-dependency installation
- Generates npm README optimized for package distribution
### Build Options
The unified build script supports flexible targeting:
```bash
# Default: All platforms
npm run build:npm
# Current platform only (faster for development)
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. **Prebuild-install runs**: Attempts to download prebuilt binaries
3. **Fallback compilation**: If prebuilds fail, compiles from source
4. **Result**: Working VibeTunnel installation
### Installation Scripts
The package uses a multi-stage installation approach:
```json
{
"scripts": {
"install": "prebuild-install || node scripts/postinstall-npm.js"
}
}
```
#### Stage 1: prebuild-install
- Downloads appropriate prebuilt binary for current platform/Node version
- Installs to standard locations
- **Success**: Installation complete, no compilation needed
- **Failure**: Proceeds to Stage 2
#### Stage 2: postinstall-npm.js
- **node-pty**: Essential module, installation fails if build fails
- **authenticate-pam**: Optional module, warns if build fails
- Provides helpful error messages about required build tools
## Platform-Specific Details
### macOS
- **spawn-helper**: Additional C binary needed for proper PTY operations
- **Built during install**: spawn-helper compiles via node-gyp when needed
- **Architecture**: Supports both Intel (x64) and Apple Silicon (arm64)
- **Build tools**: Requires Xcode Command Line Tools for source compilation
### Linux
- **PAM libraries**: Requires `libpam0g-dev` for authenticate-pam compilation
- **spawn-helper**: Not used on Linux (macOS-only)
- **Build tools**: Requires `build-essential` package for source compilation
### 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
// 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
# 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)
**Note**: `screencap.js` is kept as it provides screen capture functionality for the web interface.
### 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
# Multi-platform build with prebuilds (default)
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
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
npm publish
```
## Usage After Installation
### Installation
```bash
# Install globally
npm install -g vibetunnel
```
### Starting the Server
```bash
# 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 in your browser to access the web interface.
### Using the vt Command
```bash
# Monitor AI agents with automatic activity tracking
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
# Basic usage
vibetunnel fwd <session-id> <command> [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
## 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
### Debugging Installation
```bash
# 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

View file

@ -15,6 +15,7 @@
"devDependencies": {
"@types/node": "^24.0.3",
"node-gyp": "^11.0.0",
"prebuild": "^13.0.1",
"rimraf": "^5.0.5",
"typescript": "^5.8.3"
},

File diff suppressed because it is too large Load diff

View file

@ -1,10 +1,35 @@
{
"name": "@vibetunnel/vibetunnel-cli",
"name": "vibetunnel",
"version": "1.0.0-beta.10",
"description": "Web frontend for terminal multiplexer",
"main": "dist/server.js",
"description": "Terminal sharing server with web interface - supports macOS, Linux, and headless environments",
"main": "dist/server/server.js",
"bin": {
"vibetunnel": "./dist/vibetunnel-cli"
"vibetunnel": "./bin/vibetunnel"
},
"files": [
"dist/",
"public/",
"bin/",
"node-pty/lib/",
"node-pty/package.json",
"prebuilds/",
"README.md"
],
"os": [
"darwin",
"linux"
],
"engines": {
"node": ">=20.0.0"
},
"repository": {
"type": "git",
"url": "https://github.com/amantus-ai/vibetunnel.git",
"directory": "web"
},
"homepage": "https://vibetunnel.sh",
"bugs": {
"url": "https://github.com/amantus-ai/vibetunnel/issues"
},
"scripts": {
"clean": "node scripts/clean.js",
@ -13,7 +38,11 @@
"dev:client": "node scripts/dev.js --client-only",
"build": "node scripts/build.js",
"build:ci": "node scripts/build-ci.js",
"build:npm": "node scripts/build-npm.js",
"prepublishOnly": "npm run build:npm",
"postinstall": "node scripts/ensure-native-modules.js",
"prebuild": "echo 'Skipping prebuild - handled by build-npm.js'",
"prebuild:upload": "echo 'Skipping prebuild:upload - not used'",
"lint": "concurrently -n biome,tsc-server,tsc-client,tsc-sw \"biome check src\" \"tsc --noEmit --project tsconfig.server.json\" \"tsc --noEmit --project tsconfig.client.json\" \"tsc --noEmit --project tsconfig.sw.json\"",
"lint:fix": "biome check src --write",
"lint:biome": "biome check src",

601
web/scripts/build-npm.js Executable file
View file

@ -0,0 +1,601 @@
#!/usr/bin/env node
/**
* Unified npm build script for VibeTunnel
* Builds for all platforms by default with complete prebuild support
*
* Options:
* --current-only Build for current platform/arch only (legacy mode)
* --no-docker Skip Docker builds (Linux builds will be skipped)
* --platform <os> Build for specific platform (darwin, linux)
* --arch <arch> Build for specific architecture (x64, arm64)
*/
const { execSync, spawn } = require('child_process');
const fs = require('fs');
const path = require('path');
const NODE_VERSIONS = ['20', '22', '23', '24'];
const ALL_PLATFORMS = {
darwin: ['x64', 'arm64'],
linux: ['x64', 'arm64']
};
// Map Node.js versions to ABI versions
function getNodeAbi(nodeVersion) {
const abiMap = {
'20': '115',
'22': '127',
'23': '131',
'24': '134'
};
return abiMap[nodeVersion];
}
// Parse command line arguments
const args = process.argv.slice(2);
const currentOnly = args.includes('--current-only');
const noDocker = args.includes('--no-docker');
const platformFilter = args.find(arg => arg.startsWith('--platform'))?.split('=')[1] ||
(args.includes('--platform') ? args[args.indexOf('--platform') + 1] : null);
const archFilter = args.find(arg => arg.startsWith('--arch'))?.split('=')[1] ||
(args.includes('--arch') ? args[args.indexOf('--arch') + 1] : null);
let PLATFORMS = ALL_PLATFORMS;
if (currentOnly) {
// Legacy mode: current platform/arch only
PLATFORMS = { [process.platform]: [process.arch] };
} else {
// Apply filters
if (platformFilter) {
PLATFORMS = { [platformFilter]: ALL_PLATFORMS[platformFilter] || [] };
}
if (archFilter) {
PLATFORMS = Object.fromEntries(
Object.entries(PLATFORMS).map(([platform, archs]) => [
platform,
archs.filter(arch => arch === archFilter)
])
);
}
}
console.log('🚀 Building VibeTunnel for npm distribution...\n');
if (currentOnly) {
console.log(`📦 Legacy mode: Building for ${process.platform}/${process.arch} only\n`);
} else {
console.log('🌐 Multi-platform mode: Building for all supported platforms\n');
console.log('Target platforms:', Object.entries(PLATFORMS)
.map(([platform, archs]) => `${platform}(${archs.join(',')})`)
.join(', '));
console.log('');
}
// Check if Docker is available for Linux builds
function checkDocker() {
try {
execSync('docker --version', { stdio: 'pipe' });
return true;
} catch (e) {
if (PLATFORMS.linux && !noDocker) {
console.error('❌ Docker is required for Linux builds but is not installed.');
console.error('Please install Docker using one of these options:');
console.error(' - OrbStack (recommended): https://orbstack.dev/');
console.error(' - Docker Desktop: https://www.docker.com/products/docker-desktop/');
console.error(' - Use --no-docker to skip Linux builds');
process.exit(1);
}
return false;
}
}
// Build for macOS locally
function buildMacOS() {
console.log('🍎 Building macOS binaries locally...\n');
// First ensure prebuild is available
try {
execSync('npx prebuild --version', { stdio: 'pipe' });
} catch (e) {
console.log(' Installing prebuild dependencies...');
execSync('npm install', { stdio: 'inherit' });
}
// Build node-pty
console.log(' Building node-pty...');
const nodePtyDir = path.join(__dirname, '..', 'node-pty');
for (const nodeVersion of NODE_VERSIONS) {
for (const arch of PLATFORMS.darwin || []) {
console.log(` → node-pty for Node.js ${nodeVersion} ${arch}`);
try {
execSync(`npx prebuild --runtime node --target ${nodeVersion}.0.0 --arch ${arch}`, {
cwd: nodePtyDir,
stdio: 'pipe'
});
} catch (error) {
console.error(` ❌ Failed to build node-pty for Node.js ${nodeVersion} ${arch}`);
console.error(` Error: ${error.message}`);
process.exit(1);
}
}
}
// Build universal spawn-helper binaries for macOS
console.log(' Building universal spawn-helper binaries...');
const spawnHelperSrc = path.join(nodePtyDir, 'src', 'unix', 'spawn-helper.cc');
const tempDir = path.join(__dirname, '..', 'temp-spawn-helper');
// Ensure temp directory exists
if (!fs.existsSync(tempDir)) {
fs.mkdirSync(tempDir, { recursive: true });
}
try {
// Build for x64
console.log(` → spawn-helper for x64`);
execSync(`clang++ -arch x86_64 -o ${tempDir}/spawn-helper-x64 ${spawnHelperSrc}`, {
stdio: 'pipe'
});
// Build for arm64
console.log(` → spawn-helper for arm64`);
execSync(`clang++ -arch arm64 -o ${tempDir}/spawn-helper-arm64 ${spawnHelperSrc}`, {
stdio: 'pipe'
});
// Create universal binary
console.log(` → Creating universal spawn-helper binary`);
execSync(`lipo -create ${tempDir}/spawn-helper-x64 ${tempDir}/spawn-helper-arm64 -output ${tempDir}/spawn-helper-universal`, {
stdio: 'pipe'
});
// Add universal spawn-helper to each macOS prebuild
for (const nodeVersion of NODE_VERSIONS) {
for (const arch of PLATFORMS.darwin || []) {
const prebuildFile = path.join(nodePtyDir, 'prebuilds', `node-pty-v1.0.0-node-v${getNodeAbi(nodeVersion)}-darwin-${arch}.tar.gz`);
if (fs.existsSync(prebuildFile)) {
console.log(` → Adding spawn-helper to ${path.basename(prebuildFile)}`);
// Extract existing prebuild
const extractDir = path.join(tempDir, `extract-${nodeVersion}-${arch}`);
if (fs.existsSync(extractDir)) {
fs.rmSync(extractDir, { recursive: true, force: true });
}
fs.mkdirSync(extractDir, { recursive: true });
execSync(`tar -xzf ${prebuildFile} -C ${extractDir}`, { stdio: 'pipe' });
// Copy universal spawn-helper
fs.copyFileSync(`${tempDir}/spawn-helper-universal`, `${extractDir}/build/Release/spawn-helper`);
fs.chmodSync(`${extractDir}/build/Release/spawn-helper`, '755');
// Repackage prebuild
execSync(`tar -czf ${prebuildFile} -C ${extractDir} .`, { stdio: 'pipe' });
// Clean up extract directory
fs.rmSync(extractDir, { recursive: true, force: true });
}
}
}
// Clean up temp directory
fs.rmSync(tempDir, { recursive: true, force: true });
console.log(' ✅ Universal spawn-helper binaries created and added to prebuilds');
} catch (error) {
console.error(` ❌ Failed to build universal spawn-helper: ${error.message}`);
// Clean up on error
if (fs.existsSync(tempDir)) {
fs.rmSync(tempDir, { recursive: true, force: true });
}
process.exit(1);
}
// Build authenticate-pam
console.log(' Building authenticate-pam...');
const authenticatePamDir = path.join(__dirname, '..', 'node_modules', '.pnpm', 'authenticate-pam@1.0.5', 'node_modules', 'authenticate-pam');
for (const nodeVersion of NODE_VERSIONS) {
for (const arch of PLATFORMS.darwin || []) {
console.log(` → authenticate-pam for Node.js ${nodeVersion} ${arch}`);
try {
execSync(`npx prebuild --runtime node --target ${nodeVersion}.0.0 --arch ${arch} --tag-prefix authenticate-pam-v`, {
cwd: authenticatePamDir,
stdio: 'pipe',
env: { ...process.env, npm_config_target_platform: 'darwin', npm_config_target_arch: arch }
});
} catch (error) {
console.error(` ❌ Failed to build authenticate-pam for Node.js ${nodeVersion} ${arch}`);
console.error(` Error: ${error.message}`);
process.exit(1);
}
}
}
console.log('✅ macOS builds completed\n');
}
// Build for Linux using Docker
function buildLinux() {
console.log('🐧 Building Linux binaries using Docker...\n');
const dockerScript = `
set -e
export CI=true
export DEBIAN_FRONTEND=noninteractive
# Install dependencies including cross-compilation tools
apt-get update -qq
apt-get install -y -qq python3 make g++ git libpam0g-dev gcc-aarch64-linux-gnu g++-aarch64-linux-gnu
# Add ARM64 architecture for cross-compilation
dpkg --add-architecture arm64
apt-get update -qq
apt-get install -y -qq libpam0g-dev:arm64
# Install pnpm
npm install -g pnpm --force --no-frozen-lockfile
# Install dependencies
cd /workspace
pnpm install --force --no-frozen-lockfile
# Build node-pty for Linux
cd /workspace/node-pty
for node_version in ${NODE_VERSIONS.join(' ')}; do
for arch in ${(PLATFORMS.linux || []).join(' ')}; do
echo "Building node-pty for Node.js \$node_version \$arch"
if [ "\$arch" = "arm64" ]; then
export CC=aarch64-linux-gnu-gcc
export CXX=aarch64-linux-gnu-g++
export AR=aarch64-linux-gnu-ar
export STRIP=aarch64-linux-gnu-strip
export LINK=aarch64-linux-gnu-g++
else
unset CC CXX AR STRIP LINK
fi
npm_config_target_platform=linux npm_config_target_arch=\$arch \\
npx prebuild --runtime node --target \$node_version.0.0 --arch \$arch || exit 1
done
done
# Build authenticate-pam for Linux
cd /workspace/node_modules/.pnpm/authenticate-pam@1.0.5/node_modules/authenticate-pam
for node_version in ${NODE_VERSIONS.join(' ')}; do
for arch in ${(PLATFORMS.linux || []).join(' ')}; do
echo "Building authenticate-pam for Node.js \$node_version \$arch"
if [ "\$arch" = "arm64" ]; then
export CC=aarch64-linux-gnu-gcc
export CXX=aarch64-linux-gnu-g++
export AR=aarch64-linux-gnu-ar
export STRIP=aarch64-linux-gnu-strip
export LINK=aarch64-linux-gnu-g++
else
unset CC CXX AR STRIP LINK
fi
npm_config_target_platform=linux npm_config_target_arch=\$arch \\
npx prebuild --runtime node --target \$node_version.0.0 --arch \$arch --tag-prefix authenticate-pam-v || exit 1
done
done
echo "Linux builds completed successfully"
`;
try {
execSync(`docker run --rm --platform linux/amd64 -v "\${PWD}:/workspace" -w /workspace node:22-bookworm bash -c '${dockerScript}'`, {
stdio: 'inherit',
cwd: path.join(__dirname, '..')
});
console.log('✅ Linux builds completed\n');
} catch (error) {
console.error('❌ Linux build failed:', error.message);
process.exit(1);
}
}
// Copy and merge all prebuilds
function mergePrebuilds() {
console.log('📦 Merging prebuilds...\n');
const rootPrebuildsDir = path.join(__dirname, '..', 'prebuilds');
const nodePtyPrebuildsDir = path.join(__dirname, '..', 'node-pty', 'prebuilds');
// Ensure root prebuilds directory exists
if (!fs.existsSync(rootPrebuildsDir)) {
fs.mkdirSync(rootPrebuildsDir, { recursive: true });
}
// Copy node-pty prebuilds
if (fs.existsSync(nodePtyPrebuildsDir)) {
console.log(' Copying node-pty prebuilds...');
const nodePtyFiles = fs.readdirSync(nodePtyPrebuildsDir);
for (const file of nodePtyFiles) {
const srcPath = path.join(nodePtyPrebuildsDir, file);
const destPath = path.join(rootPrebuildsDir, file);
if (fs.statSync(srcPath).isFile()) {
fs.copyFileSync(srcPath, destPath);
console.log(`${file}`);
}
}
}
// Copy authenticate-pam prebuilds
const authenticatePamPrebuildsDir = path.join(__dirname, '..', 'node_modules', '.pnpm', 'authenticate-pam@1.0.5', 'node_modules', 'authenticate-pam', 'prebuilds');
if (fs.existsSync(authenticatePamPrebuildsDir)) {
console.log(' Copying authenticate-pam prebuilds...');
const pamFiles = fs.readdirSync(authenticatePamPrebuildsDir);
for (const file of pamFiles) {
const srcPath = path.join(authenticatePamPrebuildsDir, file);
const destPath = path.join(rootPrebuildsDir, file);
if (fs.statSync(srcPath).isFile()) {
fs.copyFileSync(srcPath, destPath);
console.log(`${file}`);
}
}
}
// Count total prebuilds
const allPrebuilds = fs.readdirSync(rootPrebuildsDir).filter(f => f.endsWith('.tar.gz'));
const nodePtyCount = allPrebuilds.filter(f => f.startsWith('node-pty')).length;
const pamCount = allPrebuilds.filter(f => f.startsWith('authenticate-pam')).length;
console.log(`✅ Merged prebuilds: ${nodePtyCount} node-pty + ${pamCount} authenticate-pam = ${allPrebuilds.length} total\n`);
}
// Main build process
async function main() {
// Step 0: Temporarily modify package.json for npm packaging
const packageJsonPath = path.join(__dirname, '..', 'package.json');
const originalPackageJson = fs.readFileSync(packageJsonPath, 'utf8');
const packageJson = JSON.parse(originalPackageJson);
// Store original postinstall
const originalPostinstall = packageJson.scripts.postinstall;
// Set install script for npm package
packageJson.scripts.install = 'prebuild-install || node scripts/postinstall-npm.js';
delete packageJson.scripts.postinstall;
// Add prebuild dependencies for npm package only
packageJson.dependencies['prebuild-install'] = '^7.1.3';
// Write modified package.json
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
// Restore original package.json on exit
const restorePackageJson = () => {
fs.writeFileSync(packageJsonPath, originalPackageJson);
};
process.on('exit', restorePackageJson);
process.on('SIGINT', () => { restorePackageJson(); process.exit(1); });
process.on('SIGTERM', () => { restorePackageJson(); process.exit(1); });
// Step 1: Standard build process (includes spawn-helper)
console.log('1⃣ Running standard build process...\n');
try {
execSync('node scripts/build.js', { stdio: 'inherit' });
console.log('✅ Standard build completed\n');
} catch (error) {
console.error('❌ Standard build failed:', error.message);
process.exit(1);
}
// Step 2: Multi-platform native module builds (unless current-only)
if (!currentOnly) {
// Check Docker availability for Linux builds
const hasDocker = checkDocker();
// Build for macOS if included in targets
if (PLATFORMS.darwin && process.platform === 'darwin') {
buildMacOS();
} else if (PLATFORMS.darwin && process.platform !== 'darwin') {
console.log('⚠️ Skipping macOS builds (not running on macOS)\n');
}
// Build for Linux if included in targets
if (PLATFORMS.linux && hasDocker && !noDocker) {
buildLinux();
} else if (PLATFORMS.linux) {
console.log('⚠️ Skipping Linux builds (Docker not available or --no-docker specified)\n');
}
// Merge all prebuilds
mergePrebuilds();
}
// Step 3: Ensure node-pty is built for current platform
console.log('3⃣ Ensuring node-pty is built for current platform...\n');
const nodePtyBuild = path.join(__dirname, '..', 'node-pty', 'build', 'Release', 'pty.node');
if (!fs.existsSync(nodePtyBuild)) {
console.log(' Building node-pty for current platform...');
const nodePtyDir = path.join(__dirname, '..', 'node-pty');
try {
execSync('npm run install', { cwd: nodePtyDir, stdio: 'inherit' });
console.log('✅ node-pty built successfully');
} catch (error) {
console.error('❌ Failed to build node-pty:', error.message);
process.exit(1);
}
} else {
console.log('✅ node-pty already built');
}
// Step 4: Create package-specific README
console.log('\n4⃣ Creating npm package README...\n');
const readmeContent = `# VibeTunnel CLI
Full-featured terminal sharing server with web interface for macOS and Linux. Windows not yet supported.
## Installation
\`\`\`bash
npm install -g vibetunnel
\`\`\`
## Requirements
- Node.js >= 20.0.0
- macOS or Linux (Windows not yet supported)
- Build tools for native modules (Xcode on macOS, build-essential on Linux)
## Usage
### Start the server
\`\`\`bash
# 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 in your browser to access the web interface.
### Use the vt command wrapper
The \`vt\` command allows you to run commands with TTY forwarding:
\`\`\`bash
# Monitor AI agents with automatic activity tracking
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"
\`\`\`
### Forward commands to a session
\`\`\`bash
# Basic usage
vibetunnel fwd <session-id> <command> [args...]
# Examples
vibetunnel fwd --session-id abc123 ls -la
vibetunnel fwd --session-id abc123 npm test
vibetunnel fwd --session-id abc123 python script.py
\`\`\`
## Features
- **Web-based terminal interface** - Access terminals from any browser
- **Multiple concurrent sessions** - Run multiple terminals simultaneously
- **Real-time synchronization** - See output in real-time
- **TTY forwarding** - Full terminal emulation support
- **Session management** - Create, list, and manage sessions
- **Cross-platform** - Works on macOS and Linux
- **No dependencies** - Just Node.js required
## Package Contents
This npm package includes:
- Full VibeTunnel server with web UI
- Command-line tools (vibetunnel, vt)
- Native PTY support for terminal emulation
- Web interface with xterm.js
- Session management and forwarding
## Platform Support
- macOS (Intel and Apple Silicon)
- Linux (x64 and ARM64)
- Windows: Not yet supported ([#252](https://github.com/amantus-ai/vibetunnel/issues/252))
## Documentation
See the main repository for complete documentation: https://github.com/amantus-ai/vibetunnel
## License
MIT
`;
const readmePath = path.join(__dirname, '..', 'README.md');
fs.writeFileSync(readmePath, readmeContent);
console.log('✅ npm README created');
// Step 5: Clean up test files (keep screencap.js - it's needed)
console.log('\n5⃣ Cleaning up test files...\n');
const testFiles = [
'public/bundle/test.js',
'public/test' // Remove entire test directory
];
for (const file of testFiles) {
const filePath = path.join(__dirname, '..', file);
if (fs.existsSync(filePath)) {
if (fs.statSync(filePath).isDirectory()) {
fs.rmSync(filePath, { recursive: true, force: true });
console.log(` Removed directory: ${file}`);
} else {
fs.unlinkSync(filePath);
console.log(` Removed file: ${file}`);
}
}
}
// Step 6: Show final package info
console.log('\n6⃣ Package summary...\n');
// Calculate total size
function getDirectorySize(dirPath) {
let totalSize = 0;
const items = fs.readdirSync(dirPath);
for (const item of items) {
const itemPath = path.join(dirPath, item);
const stats = fs.statSync(itemPath);
if (stats.isFile()) {
totalSize += stats.size;
} else if (stats.isDirectory()) {
totalSize += getDirectorySize(itemPath);
}
}
return totalSize;
}
const packageRoot = path.join(__dirname, '..');
const totalSize = getDirectorySize(packageRoot);
const sizeMB = (totalSize / 1024 / 1024).toFixed(1);
console.log(`📦 Package size: ${sizeMB} MB`);
if (!currentOnly) {
const prebuildsDir = path.join(__dirname, '..', 'prebuilds');
if (fs.existsSync(prebuildsDir)) {
const prebuildFiles = fs.readdirSync(prebuildsDir).filter(f => f.endsWith('.tar.gz'));
console.log(`🔧 Prebuilds: ${prebuildFiles.length} binaries included`);
}
}
console.log('\n🎉 npm package build completed successfully!');
console.log('\nNext steps:');
console.log(' - Test locally: npm pack');
console.log(' - Publish: npm publish');
// Restore original package.json
restorePackageJson();
}
main().catch(error => {
console.error('❌ Build failed:', error);
process.exit(1);
});

View file

@ -64,7 +64,7 @@ async function build() {
// Build server TypeScript
console.log('Building server...');
execSync('tsc', { stdio: 'inherit' });
execSync('npx tsc', { stdio: 'inherit' });
// Bundle CLI
console.log('Bundling CLI...');

View file

@ -0,0 +1,34 @@
#!/bin/bash
set -e
echo "Installing build dependencies..."
apt-get update && apt-get install -y python3 make g++ git
echo "Setting up project..."
cd /workspace
# Fix npm permissions issue in Docker
mkdir -p ~/.npm
chown -R $(id -u):$(id -g) ~/.npm
# Install pnpm using corepack (more reliable)
corepack enable
corepack prepare pnpm@latest --activate
# Install dependencies
cd /workspace
pnpm install --ignore-scripts --no-frozen-lockfile
# Go to node-pty directory
cd node-pty
# Install prebuild locally in node-pty
pnpm add -D prebuild
# Build for Node.js 20
echo "Building for Node.js 20..."
./node_modules/.bin/prebuild --runtime node --target 20.0.0
# List results
echo "Build complete. Prebuilds:"
ls -la prebuilds/

144
web/scripts/postinstall-npm.js Executable file
View file

@ -0,0 +1,144 @@
#!/usr/bin/env node
/**
* Postinstall script for npm package
* Fallback build script when prebuild-install fails
*/
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');
console.log('Setting up native modules for VibeTunnel...');
// Check if we're in development (has src directory) or npm install
const isDevelopment = fs.existsSync(path.join(__dirname, '..', 'src'));
if (isDevelopment) {
// In development, run the existing ensure-native-modules script
require('./ensure-native-modules.js');
return;
}
// Try prebuild-install first for each module
const tryPrebuildInstall = (name, dir) => {
console.log(`Trying prebuild-install for ${name}...`);
try {
execSync('prebuild-install', {
cwd: dir,
stdio: 'inherit',
env: { ...process.env, npm_config_cache: path.join(require('os').homedir(), '.npm') }
});
console.log(`${name} prebuilt binary installed`);
return true;
} catch (error) {
console.log(` No prebuilt binary available for ${name}, will compile from source`);
return false;
}
};
// Handle both native modules with prebuild-install fallback
const modules = [
{
name: 'node-pty',
dir: path.join(__dirname, '..', 'node-pty'),
build: path.join(__dirname, '..', 'node-pty', 'build', 'Release', 'pty.node'),
essential: true
},
{
name: 'authenticate-pam',
dir: path.join(__dirname, '..', 'node_modules', 'authenticate-pam'),
build: path.join(__dirname, '..', 'node_modules', 'authenticate-pam', 'build', 'Release', 'authenticate_pam.node'),
essential: false
}
];
let hasErrors = false;
for (const module of modules) {
if (!fs.existsSync(module.build)) {
// First try prebuild-install
const prebuildSuccess = tryPrebuildInstall(module.name, module.dir);
if (!prebuildSuccess) {
// Fall back to compilation
console.log(`Building ${module.name} from source...`);
try {
execSync('node-gyp rebuild', {
cwd: module.dir,
stdio: 'inherit'
});
console.log(`${module.name} built successfully`);
} catch (error) {
console.error(`Failed to build ${module.name}:`, error.message);
if (module.essential) {
console.error(`${module.name} is required for VibeTunnel to function.`);
console.error('You may need to install build tools for your platform:');
console.error('- macOS: Install Xcode Command Line Tools');
console.error('- Linux: Install build-essential package');
hasErrors = true;
} else {
console.warn(`Warning: ${module.name} build failed. Some features may be limited.`);
}
}
}
} else {
console.log(`${module.name} already available`);
}
}
if (hasErrors) {
process.exit(1);
}
// Conditionally install vt symlink
if (!isDevelopment) {
try {
// Find npm's global bin directory
const npmBinDir = execSync('npm bin -g', { encoding: 'utf8' }).trim();
const vtTarget = path.join(npmBinDir, 'vt');
const vtSource = path.join(__dirname, '..', 'bin', 'vt');
// Check if vt already exists
if (fs.existsSync(vtTarget)) {
// Check if it's already our symlink
try {
const stats = fs.lstatSync(vtTarget);
if (stats.isSymbolicLink()) {
const linkTarget = fs.readlinkSync(vtTarget);
if (linkTarget.includes('vibetunnel')) {
console.log('✓ vt command already installed (VibeTunnel)');
} else {
console.log('⚠️ vt command already exists (different tool)');
console.log(' Use "vibetunnel" command or "npx vt" instead');
}
} else {
console.log('⚠️ vt command already exists (not a symlink)');
console.log(' Use "vibetunnel" command instead');
}
} catch (e) {
// Ignore errors checking the existing file
console.log('⚠️ vt command already exists');
console.log(' Use "vibetunnel" command instead');
}
} else {
// Create the symlink
try {
fs.symlinkSync(vtSource, vtTarget);
// Make it executable
fs.chmodSync(vtTarget, '755');
console.log('✓ vt command installed successfully');
} catch (error) {
console.warn('⚠️ Could not install vt command:', error.message);
console.log(' Use "vibetunnel" command instead');
}
}
} catch (error) {
// If we can't determine npm bin dir or create symlink, just warn
console.warn('⚠️ Could not install vt command:', error.message);
console.log(' Use "vibetunnel" command instead');
}
}
console.log('✓ VibeTunnel is ready to use');
console.log('Run "vibetunnel --help" for usage information');

View file

@ -0,0 +1,55 @@
#!/bin/bash
set -e
echo "Testing Docker build for Linux x64..."
# Create the build script
cat > docker-build-test.sh << 'EOF'
#!/bin/bash
set -e
echo "Installing build dependencies..."
apt-get update && apt-get install -y python3 make g++ git
echo "Setting up project..."
cd /workspace
# Fix npm permissions issue in Docker
mkdir -p ~/.npm
chown -R $(id -u):$(id -g) ~/.npm
# Install pnpm using corepack (more reliable)
corepack enable
corepack prepare pnpm@latest --activate
# Install dependencies
cd /workspace
pnpm install --ignore-scripts --no-frozen-lockfile
# Go to node-pty directory
cd node-pty
# Install prebuild locally in node-pty
pnpm add -D prebuild
# Build for Node.js 20
echo "Building for Node.js 20..."
./node_modules/.bin/prebuild --runtime node --target 20.0.0
# List results
echo "Build complete. Prebuilds:"
ls -la prebuilds/
EOF
chmod +x docker-build-test.sh
# Run the test
docker run --rm \
-v "$(pwd)":/workspace \
-w /workspace \
--platform linux/amd64 \
node:22-bookworm \
/workspace/docker-build-test.sh
# Clean up
rm docker-build-test.sh

View file

@ -1,20 +0,0 @@
{"version":2,"width":80,"height":24}
[0.353805917,"o","\u001b[1m\u001b[7m%\u001b[27m\u001b[1m\u001b[0m \r \r\u001b]2;badlogic@Marios-MacBook-Pro:~\u0007"]
[0.353852709,"o","\u001b]1;~\u0007"]
[0.367397334,"o","\r\u001b[0m\u001b[27m\u001b[24m\u001b[J\u001b[01;32m➜ \u001b[36m~\u001b[00m \u001b[K"]
[0.367477375,"o","\u001b[?1h\u001b="]
[0.367502292,"o","\u001b[?2004h"]
[3.3307017500000002,"o","l"]
[3.640558792,"o","\bls"]
[3.970247959,"o","\u001b[?1l\u001b>"]
[3.970351,"o","\u001b[?2004l\r\r\n"]
[3.971092792,"o","\u001b]2;ls -G\u0007\u001b]1;ls\u0007"]
[3.9785605840000002,"o","192.168.1.9\r\n\u001b[1m\u001b[36mApplications\u001b[39;49m\u001b[0m\r\n"]
[3.978659209,"o","\u001b[1m\u001b[36mDataGripProjects\u001b[39;49m\u001b[0m\r\n\u001b[1m\u001b[36mDesktop\u001b[39;49m\u001b[0m\r\n\u001b[1m\u001b[36mDocuments\u001b[39;49m\u001b[0m\r\n\u001b[1m\u001b[36mDownloads\u001b[39;49m\u001b[0m\r\n\u001b[1m\u001b[36mLibrary\u001b[39;49m\u001b[0m\r\n\u001b[1m\u001b[36mMovies\u001b[39;49m\u001b[0m\r\n\u001b[1m\u001b[36mMusic\u001b[39;49m\u001b[0m\r\n\u001b[1m\u001b[36mPictures\u001b[39;49m\u001b[0m\r\n\u001b[1m\u001b[36mPublic\u001b[39;49m\u001b[0m\r\nRankers_Toreview.7z\r\n\u001b[1m\u001b[36mSupport\u001b[39;49m\u001b[0m\r\n\u001b[1m\u001b[36mTemplates\u001b[39;49m\u001b[0m\r\n\u001b[1m\u001b[36mUnrealEngine\u001b[39;49m\u001b[0m\r\n\u001b[1m\u001b[36mVM.bundle\u001b[39;49m\u001b[0m\r\n\u001b[1m\u001b[36mVulkanSDK\u001b[39;49m\u001b[0m\r\nandroid.webm\r\ncookies.txt\r\n\u001b[1m\u001b[36mdotTraceSnapshots\u001b[39;49m\u001b[0m\r\n\u001b[1m\u001b[36mesp\u001b[39;49m\u001b[0m\r\n\u001b[1m\u001b[36mfacebook-political-ads\u001b[39;49m\u001b[0m\r\nfirebase-service-credentials.json\r\n\u001b[1m\u001b[36mgo\u001b[39;49m\u001b[0m\r\n\u001b[1m\u001b[36mgradle\u001b[39;49m\u001b[0m\r\n\u001b[1m\u001b[36mjan\u001b[39;49m\u001b[0m\r\njcef_51747.log\r\njcef_77101.log\r\njcef_81453.log\r\n\u001b[1m\u001b[36mlol\u001b[39;49m\u001b[0m\r\n\u001b[1m\u001b[36mnltk_data\u001b[39;49m\u001b[0m\r\noaJsApi.class.js.html\r\nout.mp4\r\noutput.html\r\npage.html\r\n"]
[3.978693917,"o","proxy.js\r\nscene.html\r\nsearch?query=babler&fd=2023-01-01&td=2024-05-30&s=date&p=1\r\nsegment_ctaudio_ridp0aa0br191998_cinit_mpd.m4s\r\nsegment_ctaudio_ridp0aa0br191998_cs0_mpd.m4s\r\nsegment_ctvideo_ridp0va0br801408_cinit_mpd.m4s\r\nsegment_ctvideo_ridp0va0br801408_cs0_mpd.m4s\r\nsynology\r\ntest.json\r\ntest.ts\r\ntest.txt\r\n\u001b[1m\u001b[36mtools\u001b[39;49m\u001b[0m\r\n\u001b[1m\u001b[36mtty-fwd-control\u001b[39;49m\u001b[0m\r\ntwitteria-001-2024-03-05.wav\r\n\u001b[1m\u001b[36mworkspaces\u001b[39;49m\u001b[0m\r\n\u001b[31mx.sh\u001b[39;49m\u001b[0m\r\n"]
[3.979061084,"o","\u001b[1m\u001b[7m%\u001b[27m\u001b[1m\u001b[0m \r \r"]
[3.979122292,"o","\u001b]2;badlogic@Marios-MacBook-Pro:~\u0007"]
[3.979145417,"o","\u001b]1;~\u0007"]
[3.996851709,"o","\r\u001b[0m\u001b[27m\u001b[24m\u001b[J\u001b[01;32m➜ \u001b[36m~\u001b[00m \u001b[K"]
[3.996903959,"o","\u001b[?1h\u001b="]
[3.996924625,"o","\u001b[?2004h"]