feat: platform-specific shell and agent launch helpers
DefaultShell, ExecAgent, ExecShell with prompt prefix. Banner, container notice. Tests for all helpers. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
package shell
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
// DefaultShell returns the platform-appropriate interactive shell.
|
||||
func DefaultShell() string {
|
||||
if runtime.GOOS == "windows" {
|
||||
if _, err := exec.LookPath("powershell.exe"); err == nil {
|
||||
return "powershell.exe"
|
||||
}
|
||||
return "cmd.exe"
|
||||
}
|
||||
shell := os.Getenv("SHELL")
|
||||
if shell != "" {
|
||||
return shell
|
||||
}
|
||||
return "bash"
|
||||
}
|
||||
|
||||
// BuildEnvList merges ctask env vars into the current process environment.
|
||||
func BuildEnvList(vars map[string]string) []string {
|
||||
env := os.Environ()
|
||||
for k, v := range vars {
|
||||
env = append(env, fmt.Sprintf("%s=%s", k, v))
|
||||
}
|
||||
return env
|
||||
}
|
||||
|
||||
// PromptPrefix returns the shell prompt prefix for --shell mode.
|
||||
func PromptPrefix(slug, mode string) string {
|
||||
return fmt.Sprintf("(ctask:%s|%s) ", slug, mode)
|
||||
}
|
||||
|
||||
// BannerLines returns the launch banner lines for agent mode.
|
||||
func BannerLines(mode, slug, wsPath string) []string {
|
||||
return []string{
|
||||
fmt.Sprintf("[ctask] %s :: %s", mode, slug),
|
||||
fmt.Sprintf("[ctask] %s", wsPath),
|
||||
}
|
||||
}
|
||||
|
||||
// ContainerNotice returns the v0.1 deferred container mode message.
|
||||
func ContainerNotice() string {
|
||||
return "[ctask] container mode is not available in v0.1. Use local mode or see docs for manual container setup."
|
||||
}
|
||||
|
||||
// ExecAgent launches the agent command in the workspace directory.
|
||||
func ExecAgent(agent string, wsDir string, envVars map[string]string) error {
|
||||
path, err := exec.LookPath(agent)
|
||||
if err != nil {
|
||||
return fmt.Errorf("agent command not found: %s", agent)
|
||||
}
|
||||
|
||||
cmd := exec.Command(path)
|
||||
cmd.Dir = wsDir
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
cmd.Env = BuildEnvList(envVars)
|
||||
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
// ExecShell launches an interactive shell in the workspace directory.
|
||||
func ExecShell(wsDir string, envVars map[string]string, slug, mode string) error {
|
||||
shellCmd := DefaultShell()
|
||||
prefix := PromptPrefix(slug, mode)
|
||||
|
||||
var cmd *exec.Cmd
|
||||
if runtime.GOOS == "windows" {
|
||||
if shellCmd == "powershell.exe" {
|
||||
cmd = exec.Command(shellCmd, "-NoExit", "-Command",
|
||||
fmt.Sprintf("function prompt { '%s' + (Get-Location).Path + '> ' }", prefix))
|
||||
} else {
|
||||
// cmd.exe
|
||||
cmd = exec.Command(shellCmd)
|
||||
}
|
||||
} else {
|
||||
cmd = exec.Command(shellCmd)
|
||||
}
|
||||
|
||||
cmd.Dir = wsDir
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
|
||||
env := BuildEnvList(envVars)
|
||||
|
||||
if runtime.GOOS == "windows" && shellCmd == "cmd.exe" {
|
||||
env = append(env, fmt.Sprintf("PROMPT=%s$P$G", prefix))
|
||||
} else if runtime.GOOS != "windows" {
|
||||
existingPS1 := os.Getenv("PS1")
|
||||
if existingPS1 == "" {
|
||||
existingPS1 = "\\u@\\h:\\w\\$ "
|
||||
}
|
||||
env = append(env, fmt.Sprintf("PS1=%s%s", prefix, existingPS1))
|
||||
}
|
||||
|
||||
cmd.Env = env
|
||||
return cmd.Run()
|
||||
}
|
||||
Reference in New Issue
Block a user