package toolsrv import ( "context" "fmt" ) // Process represents a running toolsrv subprocess. // The session owns this and calls Close() when the session ends. type Process struct { // Socket is the Unix socket path clients should Dial(). Socket string // Info holds host environment details (platform, git repo status). Info HostInfo // cleanup shuts down the subprocess and removes the socket. cleanup func() } // Close shuts down the toolsrv subprocess and removes the socket file. func (p *Process) Close() { if p.cleanup != nil { p.cleanup() } } // Spawn starts a local ollie-remote subprocess listening on a Unix socket. // The returned Process owns the subprocess lifecycle. Callers connect // via Dial(LocalAddr(proc.Socket)). func Spawn(ctx context.Context, cwd string, opts ...Option) (*Process, error) { StreamOutput(ctx, "[toolsrv] spawning local toolsrv...\n") t, err := localDial(ctx, cwd, opts...) if err != nil { return nil, err } // Verify the server is responsive via the initial connection. conn := NewConnSplit(t.r, t.w, func() {}) var info HostInfo if hi, err := conn.FetchHostInfo(); err == nil { info = hi } StreamOutput(ctx, fmt.Sprintf("[toolsrv] local ready: %s (socket %s)\n", cwd, t.sockPath)) return &Process{ Socket: t.sockPath, Info: info, cleanup: t.cleanup, }, nil } // SpawnRemote starts ollie-remote on a remote host via SSH, sets up Unix // socket forwarding, and returns a Process with the local forwarded socket. func SpawnRemote(ctx context.Context, cfg RemoteConfig) (*Process, error) { StreamOutput(ctx, fmt.Sprintf("[toolsrv] dialing %s (cwd: %s)...\n", cfg.SSHTarget, cfg.CWD)) t, err := sshDial(ctx, cfg) if err != nil { return nil, fmt.Errorf("spawn remote: %w", err) } conn := NewConnSplit(t.r, t.w, func() {}) var info HostInfo if hi, err := conn.FetchHostInfo(); err == nil { info = hi } StreamOutput(ctx, fmt.Sprintf("[toolsrv] remote ready: %s → %s (%s/%s)\n", cfg.SSHTarget, t.sockPath, info.Platform, info.Arch)) return &Process{ Socket: t.sockPath, Info: info, cleanup: t.cleanup, }, nil }