mirror of
https://github.com/samsonjs/vibetunnel.git
synced 2026-03-29 10:05:53 +00:00
Extend SUPPRESS_OUTPUT logic to include vt follow, vt unfollow, and vt git event commands when using custom VT folder, matching the behavior of vt status and vt title.
642 lines
No EOL
26 KiB
Bash
Executable file
642 lines
No EOL
26 KiB
Bash
Executable file
#!/bin/bash
|
|
# Unified VibeTunnel CLI wrapper - compatible with both Mac app and npm installations
|
|
|
|
# Function to convert absolute paths to use ~
|
|
prettify_path() {
|
|
local path="$1"
|
|
local home="$HOME"
|
|
if [[ "$path" == "$home"* ]]; then
|
|
echo "~${path#$home}"
|
|
else
|
|
echo "$path"
|
|
fi
|
|
}
|
|
|
|
# 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"
|
|
}
|
|
|
|
# Get the real path of this script to avoid infinite recursion
|
|
SCRIPT_REAL_PATH="$(resolve_symlink_macos "${BASH_SOURCE[0]}")"
|
|
|
|
# Comprehensive Mac app search - order depends on VIBETUNNEL_PREFER_DERIVED_DATA
|
|
APP_PATH=""
|
|
|
|
if [ -n "$VIBETUNNEL_PREFER_DERIVED_DATA" ]; then
|
|
# When preference is set, try DerivedData first
|
|
for CANDIDATE in $(find ~/Library/Developer/Xcode/DerivedData -name "VibeTunnel.app" -type d 2>/dev/null | grep -v "\.dSYM" | grep -v "Index\.noindex" | sort -r); 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
|
|
# Don't exec to the app bundle vt script - use the binary instead
|
|
# This allows our self-healing to work
|
|
# exec "$VT_SCRIPT" "$@"
|
|
true # Continue to use the binary
|
|
fi
|
|
fi
|
|
APP_PATH="$CANDIDATE"
|
|
break
|
|
fi
|
|
done
|
|
fi
|
|
|
|
# If not found yet, try standard locations
|
|
if [ -z "$APP_PATH" ]; then
|
|
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
|
|
fi
|
|
|
|
# If not found in standard locations and VIBETUNNEL_PREFER_DERIVED_DATA wasn't set, search development builds
|
|
if [ -z "$APP_PATH" ] && [ -z "$VIBETUNNEL_PREFER_DERIVED_DATA" ]; then
|
|
# 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
|
|
# Don't exec to the app bundle vt script - use the binary instead
|
|
# This allows our self-healing to work
|
|
# exec "$VT_SCRIPT" "$@"
|
|
true # Continue to use the binary
|
|
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 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
|
|
# Silent operation - no message printed
|
|
true # No-op command to fix syntax error
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# 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 this is a command that should suppress wrapper output
|
|
SUPPRESS_OUTPUT=false
|
|
# Debug: echo "First argument at line 139: '$1'" >&2
|
|
if [[ "$1" == "title" || "$1" == "status" || "$1" == "--version" || "$1" == "version" || "$1" == "follow" || "$1" == "unfollow" || ("$1" == "git" && "$2" == "event") ]]; then
|
|
SUPPRESS_OUTPUT=true
|
|
fi
|
|
|
|
# Log VibeTunnel binary info when VIBETUNNEL_PREFER_DERIVED_DATA is set
|
|
if [ -n "$VIBETUNNEL_PREFER_DERIVED_DATA" ] && [ -n "$VIBETUNNEL_BIN" ] && [ "$SUPPRESS_OUTPUT" != "true" ]; then
|
|
# Get version and build info
|
|
VERSION_OUTPUT=$("$VIBETUNNEL_BIN" --version 2>&1)
|
|
VERSION_LINE=$(echo "$VERSION_OUTPUT" | grep "^VibeTunnel Server" | head -n 1)
|
|
BUILD_LINE=$(echo "$VERSION_OUTPUT" | grep "^Built:" | head -n 1)
|
|
|
|
# Gray color (bright black)
|
|
GRAY='\033[90m'
|
|
RESET='\033[0m'
|
|
|
|
# Always log this info regardless of verbosity level
|
|
# Shorten path by removing /Contents/Resources/vibetunnel suffix
|
|
DISPLAY_PATH=$(prettify_path "$VIBETUNNEL_BIN")
|
|
DISPLAY_PATH=${DISPLAY_PATH%/Contents/Resources/vibetunnel}
|
|
echo -e "${GRAY}[VibeTunnel] ${DISPLAY_PATH}${RESET}"
|
|
if [ -n "$VERSION_LINE" ] && [ -n "$BUILD_LINE" ]; then
|
|
echo -e "${GRAY}[VibeTunnel] Version: ${VERSION_LINE#VibeTunnel Server } (${BUILD_LINE})${RESET}"
|
|
elif [ -n "$VERSION_LINE" ]; then
|
|
echo -e "${GRAY}[VibeTunnel] Version: ${VERSION_LINE#VibeTunnel Server }${RESET}"
|
|
fi
|
|
fi
|
|
|
|
# Handle safe commands first that work both inside and outside sessions
|
|
# This must come BEFORE the session check to avoid the recursive session error
|
|
if [[ "$1" == "status" || "$1" == "version" || "$1" == "--version" ]]; then
|
|
# These commands can run safely inside or outside a session
|
|
exec "$VIBETUNNEL_BIN" "$@"
|
|
fi
|
|
|
|
# Check if we're already inside a VibeTunnel session
|
|
if [ -n "$VIBETUNNEL_SESSION_ID" ]; then
|
|
# Special case: handle 'vt title' command inside a session
|
|
if [[ "$1" == "title" ]]; then
|
|
if [[ $# -lt 2 ]]; then
|
|
echo "Error: 'vt title' requires a title argument" >&2
|
|
echo "Usage: vt title <new title>" >&2
|
|
exit 1
|
|
fi
|
|
shift # Remove 'title' from arguments
|
|
TITLE="$*" # Get all remaining arguments as the title
|
|
|
|
# Use the vibetunnel binary's new --update-title flag
|
|
exec "$VIBETUNNEL_BIN" fwd --update-title "$TITLE" --session-id "$VIBETUNNEL_SESSION_ID"
|
|
# If exec fails, exit with error
|
|
exit 1
|
|
fi
|
|
|
|
# For all other commands, block recursive sessions
|
|
echo "Error: Already inside a VibeTunnel session (ID: $VIBETUNNEL_SESSION_ID). Recursive VibeTunnel sessions are not supported." >&2
|
|
echo "If you need to run commands, use them directly without the 'vt' prefix." >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Function to get git repository root
|
|
get_git_root() {
|
|
git rev-parse --show-toplevel 2>/dev/null
|
|
}
|
|
|
|
# Function to escape strings for JSON
|
|
json_escape() {
|
|
local str="$1"
|
|
# Escape backslashes first, then quotes, then other special characters
|
|
str="${str//\\/\\\\}"
|
|
str="${str//\"/\\\"}"
|
|
str="${str//$'\n'/\\n}"
|
|
str="${str//$'\r'/\\r}"
|
|
str="${str//$'\t'/\\t}"
|
|
printf '%s' "$str"
|
|
}
|
|
|
|
# Function to show help
|
|
show_help() {
|
|
cat << 'EOF'
|
|
vt - VibeTunnel TTY Forward Wrapper
|
|
|
|
USAGE:
|
|
vt [command] [args...]
|
|
vt --shell [args...]
|
|
vt -i [args...]
|
|
vt --no-shell-wrap [command] [args...]
|
|
vt -S [command] [args...]
|
|
vt title <new title> # Inside a VibeTunnel session only
|
|
vt status # Show server status and follow mode
|
|
vt follow [branch] # Enable follow mode for current or specified branch
|
|
vt unfollow # Disable follow mode
|
|
vt git event # Git hook notifications
|
|
vt --help
|
|
|
|
QUICK VERBOSITY:
|
|
-q (quiet), -v (verbose), -vv (extra), -vvv (debug)
|
|
|
|
DESCRIPTION:
|
|
This wrapper script allows VibeTunnel to see the output of commands by
|
|
forwarding TTY data through the vibetunnel utility. When you run commands
|
|
through 'vt', VibeTunnel can monitor and display the command's output
|
|
in real-time.
|
|
|
|
By default, commands are executed through your shell to resolve aliases,
|
|
functions, and builtins. Use --no-shell-wrap to execute commands directly.
|
|
|
|
Inside a VibeTunnel session, use 'vt title' to update the session name.
|
|
|
|
Follow mode automatically switches your VibeTunnel terminal to the Git
|
|
worktree that matches the branch you're working on in your editor/IDE.
|
|
When you switch branches in your editor, VibeTunnel follows along.
|
|
|
|
The 'vt git event' command is used by Git hooks to notify VibeTunnel
|
|
of repository changes for automatic worktree switching.
|
|
|
|
EXAMPLES:
|
|
vt top # Watch top with VibeTunnel monitoring
|
|
vt python script.py # Run Python script with output forwarding
|
|
vt npm test # Run tests with VibeTunnel visibility
|
|
vt --shell # Launch current shell (equivalent to vt $SHELL)
|
|
vt -i # Launch current shell (short form)
|
|
vt -S ls -la # List files without shell alias resolution
|
|
vt title "My Project" # Update session title (inside session only)
|
|
vt -q npm test # Run with minimal output (errors only)
|
|
vt -vv npm run dev # Run with verbose output
|
|
|
|
# Server status:
|
|
vt status # Check if server is running and follow mode status
|
|
|
|
# Git follow mode:
|
|
vt follow # Enable follow mode for current branch
|
|
vt follow main # Switch to main branch and enable follow mode
|
|
vt unfollow # Disable follow mode
|
|
|
|
# Git event command (typically called by Git hooks):
|
|
vt git event # Notify VibeTunnel of Git changes
|
|
|
|
OPTIONS:
|
|
--shell, -i Launch current shell (equivalent to vt $SHELL)
|
|
--no-shell-wrap, -S Execute command directly without shell wrapper
|
|
--title-mode <mode> Terminal title mode (none, filter, static, dynamic)
|
|
Default: none (dynamic for claude)
|
|
--quiet, -q Quiet mode - only show errors
|
|
--verbose, -v Verbose mode - show more information
|
|
-vv Extra verbose - show all except debug
|
|
-vvv Debug mode - show all messages
|
|
--help, -h Show this help message and exit
|
|
|
|
TITLE MODES:
|
|
none No title management - apps control their own titles
|
|
filter Block all title changes from applications
|
|
static Show working directory and command in title
|
|
dynamic Show directory, command, and live activity status (default for web UI)
|
|
|
|
VERBOSITY:
|
|
By default, only errors are shown. Use verbosity flags to control output:
|
|
-q/--quiet Suppress all output except critical errors
|
|
-v/--verbose Show errors, warnings, and informational messages
|
|
-vv Show everything except debug messages
|
|
-vvv Show all messages including debug
|
|
|
|
You can also set VIBETUNNEL_LOG_LEVEL environment variable:
|
|
export VIBETUNNEL_LOG_LEVEL=error # Default
|
|
export VIBETUNNEL_LOG_LEVEL=warn # Show errors and warnings
|
|
export VIBETUNNEL_LOG_LEVEL=info # Show errors, warnings, and info
|
|
export VIBETUNNEL_LOG_LEVEL=verbose # All except debug
|
|
export VIBETUNNEL_LOG_LEVEL=debug # Everything
|
|
|
|
NOTE:
|
|
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
|
|
echo
|
|
echo "VIBETUNNEL BINARY:"
|
|
echo " Path: $VIBETUNNEL_BIN"
|
|
if [ -f "$VIBETUNNEL_BIN" ]; then
|
|
# 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
|
|
}
|
|
|
|
# Function to execute with self-healing fallback
|
|
exec_with_fallback() {
|
|
# Save the command for potential fallback
|
|
local SAVED_CMD=("$@")
|
|
|
|
# Try running with VibeTunnel
|
|
local START_TIME=$(date +%s%N 2>/dev/null || echo "0") # nanoseconds if available
|
|
|
|
# Run the command (without exec to capture exit code)
|
|
"$@"
|
|
local EXIT_CODE=$?
|
|
|
|
local END_TIME=$(date +%s%N 2>/dev/null || echo "1000000000") # 1 second if date doesn't support nanoseconds
|
|
|
|
# Calculate duration in milliseconds
|
|
local DURATION=0
|
|
if [[ "$START_TIME" != "0" && "$END_TIME" != "1000000000" ]]; then
|
|
DURATION=$(( (END_TIME - START_TIME) / 1000000 ))
|
|
fi
|
|
|
|
# Check if it was killed by macOS (exit code 137 or very quick failure)
|
|
if [[ $EXIT_CODE -eq 137 ]] || ([[ $EXIT_CODE -ne 0 ]] && [[ $DURATION -lt 100 ]]); then
|
|
# Log the error
|
|
echo "[vt] VibeTunnel binary killed by macOS (exit code: $EXIT_CODE). Running command directly." >&2
|
|
|
|
# Extract the actual command (skip vibetunnel binary and fwd)
|
|
local FALLBACK_CMD=()
|
|
local SKIP_NEXT=0
|
|
local IN_COMMAND=0
|
|
|
|
for arg in "${SAVED_CMD[@]}"; do
|
|
# Skip the vibetunnel binary path
|
|
if [[ "$arg" == "$VIBETUNNEL_BIN" ]]; then
|
|
continue
|
|
fi
|
|
# Skip "fwd"
|
|
if [[ "$arg" == "fwd" ]]; then
|
|
IN_COMMAND=1
|
|
continue
|
|
fi
|
|
# Skip verbosity and title-mode args
|
|
if [[ "$arg" == "--verbosity" || "$arg" == "--title-mode" ]]; then
|
|
SKIP_NEXT=1
|
|
continue
|
|
fi
|
|
if [[ $SKIP_NEXT -eq 1 ]]; then
|
|
SKIP_NEXT=0
|
|
continue
|
|
fi
|
|
# Add to fallback command
|
|
if [[ $IN_COMMAND -eq 1 ]]; then
|
|
FALLBACK_CMD+=("$arg")
|
|
fi
|
|
done
|
|
|
|
# Execute the original command directly
|
|
exec "${FALLBACK_CMD[@]}"
|
|
else
|
|
# Normal exit - return the exit code
|
|
exit $EXIT_CODE
|
|
fi
|
|
}
|
|
|
|
# Function to resolve command through user's shell
|
|
resolve_command() {
|
|
local user_shell="${SHELL:-/bin/bash}"
|
|
local cmd="$1"
|
|
shift
|
|
|
|
local shell_name=$(basename "$user_shell")
|
|
|
|
# Always try through shell first to handle aliases, functions, and builtins
|
|
# The shell will fall back to PATH lookup if no alias/function exists
|
|
case "$shell_name" in
|
|
zsh)
|
|
# For zsh, we need interactive mode to get aliases
|
|
exec_with_fallback "$VIBETUNNEL_BIN" fwd ${VERBOSITY_ARGS:+$VERBOSITY_ARGS} ${TITLE_MODE_ARGS:+"$TITLE_MODE_ARGS"} "$user_shell" -i -c "$(printf '%q ' "$cmd" "$@")"
|
|
;;
|
|
bash)
|
|
# For bash, expand aliases in non-interactive mode
|
|
exec_with_fallback "$VIBETUNNEL_BIN" fwd ${VERBOSITY_ARGS:+$VERBOSITY_ARGS} ${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_with_fallback "$VIBETUNNEL_BIN" fwd ${VERBOSITY_ARGS:+$VERBOSITY_ARGS} ${TITLE_MODE_ARGS:+"$TITLE_MODE_ARGS"} "$user_shell" -c "$(printf '%q ' "$cmd" "$@")"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
# Handle --help or -h option, or no arguments (show help)
|
|
if [[ $# -eq 0 || "$1" == "--help" || "$1" == "-h" ]]; then
|
|
show_help
|
|
exit 0
|
|
fi
|
|
|
|
|
|
# Handle 'vt title' command when not inside a session
|
|
if [[ "$1" == "title" ]]; then
|
|
echo "Error: 'vt title' can only be used inside a VibeTunnel session." >&2
|
|
echo "Start a session first with 'vt' or 'vt <command>'" >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Handle 'vt follow' command
|
|
if [[ "$1" == "follow" ]]; then
|
|
# Detect if we're in a worktree
|
|
IS_WORKTREE=$(git rev-parse --is-inside-work-tree 2>/dev/null)
|
|
COMMON_DIR=$(git rev-parse --git-common-dir 2>/dev/null)
|
|
|
|
if [[ "$IS_WORKTREE" == "true" ]] && [[ "$COMMON_DIR" != ".git" ]]; then
|
|
# We're in a worktree
|
|
if [[ -n "$2" ]]; then
|
|
# Error if trying to specify path/branch from worktree
|
|
WORKTREE_PATH=$(git rev-parse --show-toplevel)
|
|
echo "Error: Cannot specify arguments when running from a worktree."
|
|
echo "To enable follow mode for this worktree ($(prettify_path "$WORKTREE_PATH")):"
|
|
echo " vt follow"
|
|
exit 1
|
|
fi
|
|
|
|
WORKTREE_PATH=$(git rev-parse --show-toplevel)
|
|
# Extract main repo path from git common dir
|
|
MAIN_REPO=$(dirname "$COMMON_DIR")
|
|
|
|
echo "Enabling follow mode for worktree: $(prettify_path "$WORKTREE_PATH")"
|
|
echo "Main repository ($(prettify_path "$MAIN_REPO")) will track this worktree"
|
|
|
|
# Use vibetunnel CLI with worktree context
|
|
exec "$VIBETUNNEL_BIN" follow --from-worktree --worktree-path "$WORKTREE_PATH" --main-repo "$MAIN_REPO"
|
|
else
|
|
# We're in main repo
|
|
MAIN_REPO=$(git rev-parse --show-toplevel 2>/dev/null)
|
|
if [[ -z "$MAIN_REPO" ]]; then
|
|
echo "Error: Not in a git repository" >&2
|
|
exit 1
|
|
fi
|
|
|
|
ARG="$2"
|
|
|
|
if [[ -z "$ARG" ]]; then
|
|
# No argument - try to be smart
|
|
CURRENT_BRANCH=$(git branch --show-current 2>/dev/null)
|
|
|
|
if [[ -z "$CURRENT_BRANCH" ]]; then
|
|
# Detached HEAD
|
|
echo "Error: Not on a branch (detached HEAD state)."
|
|
echo "Available worktrees:"
|
|
git worktree list | tail -n +2 | while read -r line; do
|
|
WPATH=$(echo "$line" | awk '{print $1}')
|
|
WBRANCH=$(echo "$line" | grep -oE '\[[^]]+\]' | tr -d '[]')
|
|
echo " $WBRANCH -> $(prettify_path "$WPATH")"
|
|
done
|
|
echo ""
|
|
echo "To follow a worktree, use one of:"
|
|
echo " vt follow <branch-name>"
|
|
echo " vt follow <worktree-path>"
|
|
exit 1
|
|
fi
|
|
|
|
# Check if current branch has a worktree
|
|
WORKTREE_PATH=$(git worktree list --porcelain | grep -B2 "branch refs/heads/$CURRENT_BRANCH" | grep "worktree" | cut -d' ' -f2 | grep -v "^$MAIN_REPO$" | head -n1)
|
|
|
|
if [[ -z "$WORKTREE_PATH" ]]; then
|
|
# No worktree for current branch
|
|
echo "Error: Current branch '$CURRENT_BRANCH' has no associated worktree."
|
|
echo "Available worktrees:"
|
|
git worktree list | tail -n +2 | while read -r line; do
|
|
WPATH=$(echo "$line" | awk '{print $1}')
|
|
WBRANCH=$(echo "$line" | grep -oE '\[[^]]+\]' | tr -d '[]')
|
|
echo " $WBRANCH -> $(prettify_path "$WPATH")"
|
|
done
|
|
echo ""
|
|
echo "To follow a worktree, use one of:"
|
|
echo " vt follow <branch-name>"
|
|
echo " vt follow <worktree-path>"
|
|
exit 1
|
|
fi
|
|
|
|
# Success - current branch has a worktree
|
|
echo "Enabling follow mode for branch: $CURRENT_BRANCH"
|
|
echo "Following worktree: $(prettify_path "$WORKTREE_PATH")"
|
|
echo "Main repository: $(prettify_path "$MAIN_REPO")"
|
|
exec "$VIBETUNNEL_BIN" follow --worktree-path "$WORKTREE_PATH" --main-repo "$MAIN_REPO"
|
|
|
|
elif [[ -d "$ARG" ]] || [[ "$ARG" == /* ]] || [[ "$ARG" == ../* ]]; then
|
|
# Path argument
|
|
WORKTREE_PATH=$(realpath "$ARG" 2>/dev/null)
|
|
if [[ -z "$WORKTREE_PATH" ]] || [[ ! -d "$WORKTREE_PATH" ]]; then
|
|
echo "Error: Invalid path: $ARG" >&2
|
|
exit 1
|
|
fi
|
|
echo "Enabling follow mode for worktree: $(prettify_path "$WORKTREE_PATH")"
|
|
echo "Main repository: $(prettify_path "$MAIN_REPO")"
|
|
exec "$VIBETUNNEL_BIN" follow --worktree-path "$WORKTREE_PATH" --main-repo "$MAIN_REPO"
|
|
else
|
|
# Branch argument
|
|
WORKTREE_PATH=$(git worktree list --porcelain | grep -B2 "branch refs/heads/$ARG" | grep "worktree" | cut -d' ' -f2 | grep -v "^$MAIN_REPO$" | head -n1)
|
|
|
|
if [[ -z "$WORKTREE_PATH" ]]; then
|
|
echo "Error: No worktree found for branch '$ARG'"
|
|
echo "Create a worktree first: git worktree add ../${ARG//\//-} $ARG"
|
|
exit 1
|
|
fi
|
|
|
|
echo "Enabling follow mode for branch: $ARG"
|
|
echo "Following worktree: $(prettify_path "$WORKTREE_PATH")"
|
|
echo "Main repository: $(prettify_path "$MAIN_REPO")"
|
|
exec "$VIBETUNNEL_BIN" follow --worktree-path "$WORKTREE_PATH" --main-repo "$MAIN_REPO"
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
# Handle 'vt unfollow' command
|
|
if [[ "$1" == "unfollow" ]]; then
|
|
# Use vibetunnel CLI to disable follow mode via socket
|
|
exec "$VIBETUNNEL_BIN" unfollow
|
|
fi
|
|
|
|
# Handle 'vt git event' command
|
|
if [[ "$1" == "git" && "$2" == "event" ]]; then
|
|
# Use vibetunnel CLI to send git event via socket
|
|
exec "$VIBETUNNEL_BIN" git-event
|
|
fi
|
|
|
|
# Handle verbosity flags
|
|
VERBOSITY_ARGS=""
|
|
if [[ "$1" == "--quiet" || "$1" == "-q" ]]; then
|
|
VERBOSITY_ARGS="--verbosity silent"
|
|
shift
|
|
elif [[ "$1" == "--verbose" || "$1" == "-v" ]]; then
|
|
VERBOSITY_ARGS="--verbosity info"
|
|
shift
|
|
elif [[ "$1" == "-vv" ]]; then
|
|
VERBOSITY_ARGS="--verbosity verbose"
|
|
shift
|
|
elif [[ "$1" == "-vvv" ]]; then
|
|
VERBOSITY_ARGS="--verbosity debug"
|
|
shift
|
|
fi
|
|
|
|
# Handle --shell or -i option (launch current shell)
|
|
if [[ "$1" == "--shell" || "$1" == "-i" ]]; then
|
|
shift
|
|
# Execute current shell through vibetunnel
|
|
exec "$0" ${VERBOSITY_ARGS:+$VERBOSITY_ARGS} "${SHELL:-/bin/bash}" "$@"
|
|
fi
|
|
|
|
# Handle --no-shell-wrap or -S option
|
|
NO_SHELL_WRAP=false
|
|
if [[ "$1" == "--no-shell-wrap" || "$1" == "-S" ]]; then
|
|
NO_SHELL_WRAP=true
|
|
shift
|
|
fi
|
|
|
|
# Handle --title-mode option
|
|
TITLE_MODE_ARGS=""
|
|
if [[ "$1" == "--title-mode" && $# -gt 1 ]]; then
|
|
TITLE_MODE_ARGS="--title-mode $2"
|
|
shift 2
|
|
fi
|
|
|
|
# Check if we have arguments and if the first argument is not an option
|
|
if [ $# -gt 0 ] && [[ "$1" != -* ]]; then
|
|
if [[ "$NO_SHELL_WRAP" == "true" ]]; then
|
|
# Execute directly without shell wrapper
|
|
exec_with_fallback "$VIBETUNNEL_BIN" fwd ${VERBOSITY_ARGS:+$VERBOSITY_ARGS} ${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_with_fallback "$VIBETUNNEL_BIN" fwd ${VERBOSITY_ARGS:+$VERBOSITY_ARGS} ${TITLE_MODE_ARGS:+"$TITLE_MODE_ARGS"} "$@"
|
|
else
|
|
# Not a real binary, try alias resolution
|
|
resolve_command "$@"
|
|
fi
|
|
fi
|
|
else
|
|
# Run with fwd command (original behavior for options)
|
|
exec_with_fallback "$VIBETUNNEL_BIN" fwd ${VERBOSITY_ARGS:+$VERBOSITY_ARGS} ${TITLE_MODE_ARGS:+"$TITLE_MODE_ARGS"} "$@"
|
|
fi |