mirror of
https://github.com/samsonjs/vibetunnel.git
synced 2026-04-02 10:45:57 +00:00
277 lines
6.1 KiB
Go
277 lines
6.1 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"syscall"
|
|
)
|
|
|
|
const Version = "1.0.2"
|
|
|
|
type Config struct {
|
|
Server string `json:"server,omitempty"`
|
|
}
|
|
|
|
func main() {
|
|
// Debug incoming args
|
|
if os.Getenv("VT_DEBUG") != "" {
|
|
fmt.Fprintf(os.Stderr, "VT Debug: args = %v\n", os.Args)
|
|
}
|
|
|
|
// Handle version flag only if it's the only argument
|
|
if len(os.Args) == 2 && (os.Args[1] == "--version" || os.Args[1] == "-v") {
|
|
fmt.Printf("vt version %s\n", Version)
|
|
os.Exit(0)
|
|
}
|
|
|
|
// Get preferred server
|
|
server := getPreferredServer()
|
|
|
|
// Forward to appropriate server
|
|
var err error
|
|
switch server {
|
|
case "rust":
|
|
err = forwardToRustServer(os.Args[1:])
|
|
case "go":
|
|
err = forwardToGoServer(os.Args[1:])
|
|
default:
|
|
err = forwardToGoServer(os.Args[1:])
|
|
}
|
|
|
|
if err != nil {
|
|
// If the command exited with a specific code, preserve it
|
|
if exitErr, ok := err.(*exec.ExitError); ok {
|
|
if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
|
|
os.Exit(status.ExitStatus())
|
|
}
|
|
os.Exit(1)
|
|
}
|
|
fmt.Fprintf(os.Stderr, "vt: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
func getPreferredServer() string {
|
|
// Check environment variable first
|
|
if server := os.Getenv("VT_SERVER"); server != "" {
|
|
if server == "rust" || server == "go" {
|
|
return server
|
|
}
|
|
}
|
|
|
|
// Read from config file
|
|
configPath := filepath.Join(os.Getenv("HOME"), ".vibetunnel", "config.json")
|
|
data, err := os.ReadFile(configPath)
|
|
if err != nil {
|
|
return "go" // default
|
|
}
|
|
|
|
var config Config
|
|
if err := json.Unmarshal(data, &config); err != nil {
|
|
return "go" // default on parse error
|
|
}
|
|
|
|
if config.Server == "rust" || config.Server == "go" {
|
|
return config.Server
|
|
}
|
|
return "go" // default for invalid values
|
|
}
|
|
|
|
func forwardToGoServer(args []string) error {
|
|
// Find vibetunnel binary
|
|
vibetunnelPath := findVibetunnelBinary()
|
|
if vibetunnelPath == "" {
|
|
return fmt.Errorf("vibetunnel binary not found")
|
|
}
|
|
|
|
// Check if this is a special VT command
|
|
var finalArgs []string
|
|
if len(args) > 0 && isVTSpecialCommand(args[0]) {
|
|
// Translate special VT commands
|
|
finalArgs = translateVTToGoArgs(args)
|
|
} else {
|
|
// For regular commands, just prepend -- to tell vibetunnel to stop parsing
|
|
finalArgs = append([]string{"--"}, args...)
|
|
}
|
|
|
|
// Debug: print what we're executing
|
|
if os.Getenv("VT_DEBUG") != "" {
|
|
fmt.Fprintf(os.Stderr, "VT Debug: executing %s with args: %v\n", vibetunnelPath, finalArgs)
|
|
}
|
|
|
|
// Create command
|
|
cmd := exec.Command(vibetunnelPath, finalArgs...)
|
|
cmd.Stdin = os.Stdin
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
|
|
// Run and return
|
|
return cmd.Run()
|
|
}
|
|
|
|
func isVTSpecialCommand(arg string) bool {
|
|
switch arg {
|
|
case "--claude", "--claude-yolo", "--shell", "-i",
|
|
"--no-shell-wrap", "-S", "--show-session-info", "--show-session-id":
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func forwardToRustServer(args []string) error {
|
|
// Find tty-fwd binary
|
|
ttyFwdPath := findTtyFwdBinary()
|
|
if ttyFwdPath == "" {
|
|
return fmt.Errorf("tty-fwd binary not found")
|
|
}
|
|
|
|
// Create command with original args (tty-fwd already understands vt args)
|
|
cmd := exec.Command(ttyFwdPath, args...)
|
|
cmd.Stdin = os.Stdin
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
|
|
// Run and return
|
|
return cmd.Run()
|
|
}
|
|
|
|
func translateVTToGoArgs(args []string) []string {
|
|
if len(args) == 0 {
|
|
return args
|
|
}
|
|
|
|
// Check for special vt-only flags
|
|
switch args[0] {
|
|
case "--claude":
|
|
// Find Claude and run it with any additional arguments
|
|
claudePath := findClaude()
|
|
if claudePath != "" {
|
|
// Pass all remaining args to claude
|
|
result := []string{"--", claudePath}
|
|
if len(args) > 1 {
|
|
result = append(result, args[1:]...)
|
|
}
|
|
return result
|
|
}
|
|
// Fallback
|
|
result := []string{"--", "claude"}
|
|
if len(args) > 1 {
|
|
result = append(result, args[1:]...)
|
|
}
|
|
return result
|
|
|
|
case "--claude-yolo":
|
|
// Find Claude and run with permissions skip
|
|
claudePath := findClaude()
|
|
if claudePath != "" {
|
|
return []string{"--", claudePath, "--dangerously-skip-permissions"}
|
|
}
|
|
return []string{"--", "claude", "--dangerously-skip-permissions"}
|
|
|
|
case "--shell", "-i":
|
|
// Launch interactive shell
|
|
shell := os.Getenv("SHELL")
|
|
if shell == "" {
|
|
shell = "/bin/bash"
|
|
}
|
|
return []string{"--", shell, "-i"}
|
|
|
|
case "--no-shell-wrap", "-S":
|
|
// Direct execution without shell - skip the flag and pass rest
|
|
if len(args) > 1 {
|
|
return append([]string{"--"}, args[1:]...)
|
|
}
|
|
return []string{}
|
|
|
|
case "--show-session-info":
|
|
return []string{"--list-sessions"}
|
|
|
|
case "--show-session-id":
|
|
// This needs special handling - just pass through for now
|
|
return args
|
|
|
|
default:
|
|
// This shouldn't happen since we check isVTSpecialCommand first
|
|
return args
|
|
}
|
|
}
|
|
|
|
func findVibetunnelBinary() string {
|
|
// Check common locations
|
|
paths := []string{
|
|
// App bundle location
|
|
"/Applications/VibeTunnel.app/Contents/Resources/vibetunnel",
|
|
// Development locations
|
|
"./vibetunnel",
|
|
"../vibetunnel",
|
|
"../../linux/vibetunnel",
|
|
// Installed location
|
|
"/usr/local/bin/vibetunnel",
|
|
}
|
|
|
|
for _, path := range paths {
|
|
if _, err := os.Stat(path); err == nil {
|
|
return path
|
|
}
|
|
}
|
|
|
|
// Try to find in PATH
|
|
if path, err := exec.LookPath("vibetunnel"); err == nil {
|
|
return path
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
func findTtyFwdBinary() string {
|
|
// Check common locations
|
|
paths := []string{
|
|
// App bundle location
|
|
"/Applications/VibeTunnel.app/Contents/Resources/tty-fwd",
|
|
// Development locations
|
|
"./tty-fwd",
|
|
"../tty-fwd",
|
|
"../../tty-fwd/target/release/tty-fwd",
|
|
// Installed location
|
|
"/usr/local/bin/tty-fwd",
|
|
}
|
|
|
|
for _, path := range paths {
|
|
if _, err := os.Stat(path); err == nil {
|
|
return path
|
|
}
|
|
}
|
|
|
|
// Try to find in PATH
|
|
if path, err := exec.LookPath("tty-fwd"); err == nil {
|
|
return path
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
func findClaude() string {
|
|
// Check common Claude installation paths
|
|
claudePaths := []string{
|
|
filepath.Join(os.Getenv("HOME"), ".claude", "local", "claude"),
|
|
"/opt/homebrew/bin/claude",
|
|
"/usr/local/bin/claude",
|
|
"/usr/bin/claude",
|
|
}
|
|
|
|
for _, path := range claudePaths {
|
|
if _, err := os.Stat(path); err == nil {
|
|
return path
|
|
}
|
|
}
|
|
|
|
// Try PATH
|
|
if path, err := exec.LookPath("claude"); err == nil {
|
|
return path
|
|
}
|
|
|
|
return ""
|
|
}
|