103f2cd33e
LaunchOpts gains LaunchDir. session.Run resolves it via workspace.ResolveLaunch, prints any fallback warning, and passes the absolute path as the child process's working directory. Security violations (absolute paths, .. escape) abort the session. The banner gains a 'project dir: <name>/' line when launch_dir is set. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
112 lines
2.8 KiB
Go
112 lines
2.8 KiB
Go
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.
|
|
// Adds a "project dir:" line when launchDir is non-empty.
|
|
func BannerLines(mode, slug, wsPath, launchDir string) []string {
|
|
lines := []string{
|
|
fmt.Sprintf("[ctask] %s :: %s", mode, slug),
|
|
fmt.Sprintf("[ctask] %s", wsPath),
|
|
}
|
|
if launchDir != "" {
|
|
lines = append(lines, fmt.Sprintf("[ctask] project dir: %s/", launchDir))
|
|
}
|
|
return lines
|
|
}
|
|
|
|
// 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()
|
|
}
|