mirror of
https://github.com/YspCoder/clawgo.git
synced 2026-04-15 00:27:29 +08:00
fix shell
This commit is contained in:
@@ -7,6 +7,9 @@ import (
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"clawgo/pkg/config"
|
||||
@@ -115,10 +118,46 @@ func (t *ExecTool) SetTimeout(timeout time.Duration) {
|
||||
}
|
||||
|
||||
func (t *ExecTool) executeCommand(ctx context.Context, command, cwd string) (string, error) {
|
||||
output, err, timedOut := t.runShellCommand(ctx, command, cwd)
|
||||
if timedOut {
|
||||
return fmt.Sprintf("Error: Command timed out after %v", t.timeout), nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if missingCmd := detectMissingCommandFromOutput(output); missingCmd != "" {
|
||||
if installLog, installed := t.tryAutoInstallMissingCommand(ctx, missingCmd, cwd); installed {
|
||||
output += "\n[AUTO-INSTALL]\n" + installLog
|
||||
retryOutput, retryErr, retryTimedOut := t.runShellCommand(ctx, command, cwd)
|
||||
if retryTimedOut {
|
||||
return fmt.Sprintf("Error: Command timed out after %v", t.timeout), nil
|
||||
}
|
||||
output += "\n[RETRY]\n" + retryOutput
|
||||
err = retryErr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
output += fmt.Sprintf("\nExit code: %v", err)
|
||||
}
|
||||
|
||||
if output == "" {
|
||||
output = "(no output)"
|
||||
}
|
||||
|
||||
maxLen := 10000
|
||||
if len(output) > maxLen {
|
||||
output = output[:maxLen] + fmt.Sprintf("\n... (truncated, %d more chars)", len(output)-maxLen)
|
||||
}
|
||||
return output, nil
|
||||
}
|
||||
|
||||
func (t *ExecTool) runShellCommand(ctx context.Context, command, cwd string) (string, error, bool) {
|
||||
cmdCtx, cancel := context.WithTimeout(ctx, t.timeout)
|
||||
defer cancel()
|
||||
|
||||
cmd := exec.CommandContext(cmdCtx, "sh", "-c", command)
|
||||
cmd.Env = buildExecEnv()
|
||||
if cwd != "" {
|
||||
cmd.Dir = cwd
|
||||
}
|
||||
@@ -135,18 +174,105 @@ func (t *ExecTool) executeCommand(ctx context.Context, command, cwd string) (str
|
||||
|
||||
if err != nil {
|
||||
if cmdCtx.Err() == context.DeadlineExceeded {
|
||||
return fmt.Sprintf("Error: Command timed out after %v", t.timeout), nil
|
||||
return output, err, true
|
||||
}
|
||||
output += fmt.Sprintf("\nExit code: %v", err)
|
||||
}
|
||||
|
||||
if output == "" {
|
||||
output = "(no output)"
|
||||
}
|
||||
|
||||
maxLen := 10000
|
||||
if len(output) > maxLen {
|
||||
output = output[:maxLen] + fmt.Sprintf("\n... (truncated, %d more chars)", len(output)-maxLen)
|
||||
}
|
||||
return output, nil
|
||||
return output, err, false
|
||||
}
|
||||
|
||||
func buildExecEnv() []string {
|
||||
env := os.Environ()
|
||||
current := os.Getenv("PATH")
|
||||
fallback := "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/opt/homebrew/bin:/opt/homebrew/sbin"
|
||||
if strings.TrimSpace(current) == "" {
|
||||
return append(env, "PATH="+fallback)
|
||||
}
|
||||
// Append common paths to reduce false "command not found" in service/daemon envs.
|
||||
return append(env, "PATH="+current+":"+fallback)
|
||||
}
|
||||
|
||||
func detectMissingCommandFromOutput(output string) string {
|
||||
patterns := []*regexp.Regexp{
|
||||
regexp.MustCompile(`(?m)(?:^|[:\s])([a-zA-Z0-9._+-]+): not found`),
|
||||
regexp.MustCompile(`(?m)(?:^|[:\s])([a-zA-Z0-9._+-]+): command not found`),
|
||||
}
|
||||
for _, p := range patterns {
|
||||
match := p.FindStringSubmatch(output)
|
||||
if len(match) >= 2 && strings.TrimSpace(match[1]) != "" {
|
||||
return strings.TrimSpace(match[1])
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func commandExists(name string) bool {
|
||||
if strings.TrimSpace(name) == "" {
|
||||
return false
|
||||
}
|
||||
_, err := exec.LookPath(name)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func buildInstallCommandCandidates(commandName string) []string {
|
||||
cmd := strings.TrimSpace(commandName)
|
||||
if cmd == "" {
|
||||
return nil
|
||||
}
|
||||
type pkgTool struct {
|
||||
bin string
|
||||
cmd string
|
||||
sudo bool
|
||||
}
|
||||
candidates := []pkgTool{
|
||||
{bin: "apt-get", cmd: "apt-get update && apt-get install -y %s", sudo: true},
|
||||
{bin: "dnf", cmd: "dnf install -y %s", sudo: true},
|
||||
{bin: "yum", cmd: "yum install -y %s", sudo: true},
|
||||
{bin: "apk", cmd: "apk add --no-cache %s", sudo: true},
|
||||
{bin: "pacman", cmd: "pacman -Sy --noconfirm %s", sudo: true},
|
||||
{bin: "zypper", cmd: "zypper --non-interactive install %s", sudo: true},
|
||||
{bin: "brew", cmd: "brew install %s", sudo: false},
|
||||
}
|
||||
|
||||
var out []string
|
||||
for _, c := range candidates {
|
||||
if !commandExists(c.bin) {
|
||||
continue
|
||||
}
|
||||
installCmd := fmt.Sprintf(c.cmd, cmd)
|
||||
if c.sudo && runtime.GOOS != "windows" && os.Geteuid() != 0 && commandExists("sudo") {
|
||||
installCmd = "sudo " + installCmd
|
||||
}
|
||||
out = append(out, installCmd)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (t *ExecTool) tryAutoInstallMissingCommand(ctx context.Context, commandName, cwd string) (string, bool) {
|
||||
name := strings.TrimSpace(commandName)
|
||||
if name == "" || commandExists(name) {
|
||||
return "", false
|
||||
}
|
||||
candidates := buildInstallCommandCandidates(name)
|
||||
if len(candidates) == 0 {
|
||||
return fmt.Sprintf("No supported package manager found to install missing command: %s", name), false
|
||||
}
|
||||
|
||||
timeout := 5 * time.Minute
|
||||
if t.timeout > 0 && t.timeout < timeout {
|
||||
timeout = t.timeout
|
||||
}
|
||||
|
||||
for _, installCmd := range candidates {
|
||||
installCtx, cancel := context.WithTimeout(ctx, timeout)
|
||||
output, err, timedOut := t.runShellCommand(installCtx, installCmd, cwd)
|
||||
cancel()
|
||||
|
||||
if timedOut {
|
||||
continue
|
||||
}
|
||||
if err == nil && commandExists(name) {
|
||||
return fmt.Sprintf("Installed %s using: %s\n%s", name, installCmd, output), true
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("Failed to auto-install missing command: %s", name), false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user