mirror of
https://github.com/samsonjs/vibetunnel.git
synced 2026-04-27 15:17:38 +00:00
Work on new session logic
This commit is contained in:
parent
1519fbca16
commit
22eb5a2e3f
7 changed files with 128 additions and 40 deletions
|
|
@ -504,8 +504,22 @@ func main() {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attach to the session
|
// For spawned sessions, we need to execute the command and connect I/O
|
||||||
if err := sess.Attach(); err != nil {
|
// The session was already created by the server, we just need to run the command
|
||||||
|
info := sess.GetInfo()
|
||||||
|
if info == nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Error: Failed to get session info\n")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute the command that was stored in the session
|
||||||
|
if len(info.Args) == 0 {
|
||||||
|
fmt.Fprintf(os.Stderr, "Error: No command specified in session\n")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new PTY and attach it to the existing session
|
||||||
|
if err := sess.AttachSpawnedSession(); err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -380,11 +380,11 @@ func (s *Server) handleCreateSession(w http.ResponseWriter, r *http.Request) {
|
||||||
// Generate a session ID
|
// Generate a session ID
|
||||||
sessionID := session.GenerateID()
|
sessionID := session.GenerateID()
|
||||||
|
|
||||||
// Get vt binary path (not vibetunnel)
|
// Get vibetunnel binary path
|
||||||
vtPath := findVTBinary()
|
vtPath := findVTBinary()
|
||||||
if vtPath == "" {
|
if vtPath == "" {
|
||||||
log.Printf("[ERROR] vt binary not found")
|
log.Printf("[ERROR] vibetunnel binary not found")
|
||||||
http.Error(w, "vt binary not found", http.StatusInternalServerError)
|
http.Error(w, "vibetunnel binary not found", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -466,18 +466,38 @@ func (s *Server) handleCreateSession(w http.ResponseWriter, r *http.Request) {
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("[ERROR] Failed to create session: %v", err)
|
log.Printf("[ERROR] Failed to create session: %v", err)
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
||||||
|
// Return structured error response for frontends to parse
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
errorResponse := map[string]interface{}{
|
||||||
|
"success": false,
|
||||||
|
"error": err.Error(),
|
||||||
|
"details": fmt.Sprintf("Failed to create session with command '%s'", strings.Join(cmdline, " ")),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract more specific error information if available
|
||||||
|
if sessionErr, ok := err.(*session.SessionError); ok {
|
||||||
|
errorResponse["code"] = string(sessionErr.Code)
|
||||||
|
if sessionErr.Code == session.ErrPTYCreationFailed {
|
||||||
|
errorResponse["details"] = sessionErr.Message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.NewEncoder(w).Encode(errorResponse); err != nil {
|
||||||
|
log.Printf("Failed to encode error response: %v", err)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get vt binary path (not vibetunnel)
|
// Get vibetunnel binary path
|
||||||
vtPath := findVTBinary()
|
vtPath := findVTBinary()
|
||||||
if vtPath == "" {
|
if vtPath == "" {
|
||||||
log.Printf("[ERROR] vt binary not found for native terminal spawn")
|
log.Printf("[ERROR] vibetunnel binary not found for native terminal spawn")
|
||||||
if err := s.manager.RemoveSession(sess.ID); err != nil {
|
if err := s.manager.RemoveSession(sess.ID); err != nil {
|
||||||
log.Printf("Failed to remove session: %v", err)
|
log.Printf("Failed to remove session: %v", err)
|
||||||
}
|
}
|
||||||
http.Error(w, "vt binary not found", http.StatusInternalServerError)
|
http.Error(w, "vibetunnel binary not found", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -518,7 +538,28 @@ func (s *Server) handleCreateSession(w http.ResponseWriter, r *http.Request) {
|
||||||
IsSpawned: false, // This is not a spawned session (detached)
|
IsSpawned: false, // This is not a spawned session (detached)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
log.Printf("[ERROR] Failed to create session: %v", err)
|
||||||
|
|
||||||
|
// Return structured error response for frontends to parse
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
errorResponse := map[string]interface{}{
|
||||||
|
"success": false,
|
||||||
|
"error": err.Error(),
|
||||||
|
"details": fmt.Sprintf("Failed to create session with command '%s'", strings.Join(cmdline, " ")),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract more specific error information if available
|
||||||
|
if sessionErr, ok := err.(*session.SessionError); ok {
|
||||||
|
errorResponse["code"] = string(sessionErr.Code)
|
||||||
|
if sessionErr.Code == session.ErrPTYCreationFailed {
|
||||||
|
errorResponse["details"] = sessionErr.Message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.NewEncoder(w).Encode(errorResponse); err != nil {
|
||||||
|
log.Printf("Failed to encode error response: %v", err)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1028,29 +1069,27 @@ func (s *Server) GetNgrokStatus() ngrok.StatusResponse {
|
||||||
return s.ngrokService.GetStatus()
|
return s.ngrokService.GetStatus()
|
||||||
}
|
}
|
||||||
|
|
||||||
// findVTBinary locates the vt binary in common locations
|
// findVTBinary locates the vibetunnel Go binary in common locations
|
||||||
func findVTBinary() string {
|
func findVTBinary() string {
|
||||||
// Get the directory of the current executable (vibetunnel)
|
// Get the directory of the current executable (vibetunnel)
|
||||||
execPath, err := os.Executable()
|
execPath, err := os.Executable()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
execDir := filepath.Dir(execPath)
|
// Return the current executable path since we want to use vibetunnel itself
|
||||||
// Check if vt is in the same directory as vibetunnel
|
return execPath
|
||||||
vtPath := filepath.Join(execDir, "vt")
|
|
||||||
if _, err := os.Stat(vtPath); err == nil {
|
|
||||||
return vtPath
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check common locations
|
// Check common locations
|
||||||
paths := []string{
|
paths := []string{
|
||||||
// App bundle location
|
// App bundle location
|
||||||
"/Applications/VibeTunnel.app/Contents/Resources/vt",
|
"/Applications/VibeTunnel.app/Contents/Resources/vibetunnel",
|
||||||
// Development locations
|
// Development locations
|
||||||
"./vt",
|
"./linux/cmd/vibetunnel/vibetunnel",
|
||||||
"../vt",
|
"../linux/cmd/vibetunnel/vibetunnel",
|
||||||
"../../linux/vt",
|
"../../linux/cmd/vibetunnel/vibetunnel",
|
||||||
|
"./vibetunnel",
|
||||||
|
"../vibetunnel",
|
||||||
// Installed location
|
// Installed location
|
||||||
"/usr/local/bin/vt",
|
"/usr/local/bin/vibetunnel",
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, path := range paths {
|
for _, path := range paths {
|
||||||
|
|
@ -1061,10 +1100,11 @@ func findVTBinary() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to find in PATH
|
// Try to find in PATH
|
||||||
if path, err := exec.LookPath("vt"); err == nil {
|
if path, err := exec.LookPath("vibetunnel"); err == nil {
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// No binary found
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -53,12 +53,20 @@ func NewPTY(session *Session) (*PTY, error) {
|
||||||
// If just launching the shell itself, don't use -c
|
// If just launching the shell itself, don't use -c
|
||||||
cmd = exec.Command(shell)
|
cmd = exec.Command(shell)
|
||||||
} else {
|
} else {
|
||||||
// Execute command through interactive login shell for proper environment handling
|
// Execute command through login shell for proper environment handling
|
||||||
// This ensures aliases and functions from .zshrc/.bashrc are loaded
|
// This ensures aliases and functions from .zshrc/.bashrc are loaded
|
||||||
shellCmd := strings.Join(cmdline, " ")
|
shellCmd := strings.Join(cmdline, " ")
|
||||||
// Use interactive login shell to load user's configuration
|
|
||||||
cmd = exec.Command(shell, "-i", "-l", "-c", shellCmd)
|
// Try different approaches based on the shell
|
||||||
debugLog("[DEBUG] NewPTY: Executing through interactive login shell: %s -i -l -c %q", shell, shellCmd)
|
if strings.Contains(shell, "zsh") {
|
||||||
|
// For zsh, use login shell without -i flag as it can cause issues with PTY allocation
|
||||||
|
cmd = exec.Command(shell, "-l", "-c", shellCmd)
|
||||||
|
debugLog("[DEBUG] NewPTY: Executing through zsh login shell: %s -l -c %q", shell, shellCmd)
|
||||||
|
} else {
|
||||||
|
// For other shells (bash, sh), use interactive login
|
||||||
|
cmd = exec.Command(shell, "-i", "-l", "-c", shellCmd)
|
||||||
|
debugLog("[DEBUG] NewPTY: Executing through interactive login shell: %s -i -l -c %q", shell, shellCmd)
|
||||||
|
}
|
||||||
|
|
||||||
// Add some debugging to understand what's happening
|
// Add some debugging to understand what's happening
|
||||||
debugLog("[DEBUG] NewPTY: Shell: %s", shell)
|
debugLog("[DEBUG] NewPTY: Shell: %s", shell)
|
||||||
|
|
@ -85,6 +93,14 @@ func NewPTY(session *Session) (*PTY, error) {
|
||||||
// Pass all environment variables like Node.js implementation does
|
// Pass all environment variables like Node.js implementation does
|
||||||
// This ensures terminal features, locale settings, and shell prompts work correctly
|
// This ensures terminal features, locale settings, and shell prompts work correctly
|
||||||
env := os.Environ()
|
env := os.Environ()
|
||||||
|
|
||||||
|
// Log PATH for debugging
|
||||||
|
for _, e := range env {
|
||||||
|
if strings.HasPrefix(e, "PATH=") {
|
||||||
|
debugLog("[DEBUG] NewPTY: PATH=%s", e[5:])
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Override TERM if specified in session info
|
// Override TERM if specified in session info
|
||||||
termSet := false
|
termSet := false
|
||||||
|
|
|
||||||
|
|
@ -265,6 +265,25 @@ func (s *Session) Attach() error {
|
||||||
return s.pty.Attach()
|
return s.pty.Attach()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AttachSpawnedSession is used when a terminal is spawned with TTY_SESSION_ID
|
||||||
|
// It creates a new PTY for the spawned terminal and runs the command
|
||||||
|
func (s *Session) AttachSpawnedSession() error {
|
||||||
|
// Create a new PTY for this spawned session
|
||||||
|
pty, err := NewPTY(s)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create PTY: %w", err)
|
||||||
|
}
|
||||||
|
s.pty = pty
|
||||||
|
|
||||||
|
// Start the PTY with the command from session info
|
||||||
|
if err := s.pty.Start(); err != nil {
|
||||||
|
return fmt.Errorf("failed to start PTY: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attach to the PTY to connect stdin/stdout
|
||||||
|
return s.pty.Attach()
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Session) SendKey(key string) error {
|
func (s *Session) SendKey(key string) error {
|
||||||
return s.sendInput([]byte(key))
|
return s.sendInput([]byte(key))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,8 @@ import (
|
||||||
// This is used as a fallback when the Mac app's terminal service is not available
|
// This is used as a fallback when the Mac app's terminal service is not available
|
||||||
func SpawnInTerminal(sessionID, vtBinaryPath string, cmdline []string, workingDir string) error {
|
func SpawnInTerminal(sessionID, vtBinaryPath string, cmdline []string, workingDir string) error {
|
||||||
// Format the command to run in the terminal
|
// Format the command to run in the terminal
|
||||||
// This matches the format used by the Rust implementation
|
// Using the Go vibetunnel binary with TTY_SESSION_ID environment variable
|
||||||
vtCommand := fmt.Sprintf("TTY_SESSION_ID=\"%s\" \"%s\" -- %s",
|
vtCommand := fmt.Sprintf("TTY_SESSION_ID=\"%s\" \"%s\" %s",
|
||||||
sessionID, vtBinaryPath, shellQuoteArgs(cmdline))
|
sessionID, vtBinaryPath, shellQuoteArgs(cmdline))
|
||||||
|
|
||||||
switch runtime.GOOS {
|
switch runtime.GOOS {
|
||||||
|
|
|
||||||
|
|
@ -100,15 +100,14 @@ remove_item "build/SourcePackages" "Source packages"
|
||||||
remove_item "build/dmg-temp" "DMG temporary files"
|
remove_item "build/dmg-temp" "DMG temporary files"
|
||||||
remove_item "DerivedData" "DerivedData"
|
remove_item "DerivedData" "DerivedData"
|
||||||
|
|
||||||
# Clean tty-fwd Rust target (but keep the built binaries)
|
# Clean Go build artifacts
|
||||||
if [[ "$CLEAN_ALL" == "true" ]]; then
|
if [[ "$CLEAN_ALL" == "true" ]]; then
|
||||||
remove_item "tty-fwd/target" "Rust build artifacts"
|
remove_item "linux/build" "Go build artifacts"
|
||||||
else
|
else
|
||||||
# Keep the release binaries
|
# Keep the release binaries but clean intermediate files
|
||||||
find tty-fwd/target -type f -name "*.d" -delete 2>/dev/null || true
|
find linux/build -type f -name "*.a" -delete 2>/dev/null || true
|
||||||
find tty-fwd/target -type f -name "*.rmeta" -delete 2>/dev/null || true
|
find linux/build -type f -name "*.o" -delete 2>/dev/null || true
|
||||||
find tty-fwd/target -type d -name "incremental" -exec rm -rf {} + 2>/dev/null || true
|
[[ "$DRY_RUN" == "false" ]] && print_success "Cleaned Go intermediate files"
|
||||||
[[ "$DRY_RUN" == "false" ]] && print_success "Cleaned Rust intermediate files"
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Clean SPM build artifacts
|
# Clean SPM build artifacts
|
||||||
|
|
|
||||||
|
|
@ -93,10 +93,10 @@ if [ -d "$APP_BUNDLE/Contents/Frameworks" ]; then
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Sign embedded binaries (like tty-fwd)
|
# Sign embedded binaries (like vibetunnel)
|
||||||
if [ -f "$APP_BUNDLE/Contents/Resources/tty-fwd" ]; then
|
if [ -f "$APP_BUNDLE/Contents/Resources/vibetunnel" ]; then
|
||||||
log "Signing tty-fwd binary..."
|
log "Signing vibetunnel binary..."
|
||||||
codesign --force --options runtime --timestamp --sign "$SIGN_IDENTITY" $KEYCHAIN_OPTS "$APP_BUNDLE/Contents/Resources/tty-fwd" || log "Warning: Failed to sign tty-fwd"
|
codesign --force --options runtime --timestamp --sign "$SIGN_IDENTITY" $KEYCHAIN_OPTS "$APP_BUNDLE/Contents/Resources/vibetunnel" || log "Warning: Failed to sign vibetunnel"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Sign the main executable
|
# Sign the main executable
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue