Files
sherlock/internal/agent/pathaug.go
T
amacocian f5109abfad
CI / build (push) Successful in 19s
Gitea mcp pre-release
2026-05-25 15:22:30 +02:00

74 lines
1.9 KiB
Go

package agent
import (
"os"
"path/filepath"
"slices"
"strings"
)
// EnsurePathContainsSiblings prepends sherlock-adjacent directories to
// $PATH if they're not already there, then re-sets PATH in the
// current process's environment so syscall.Exec children inherit it.
//
// This is sherlock's "no manual setup" guarantee: dropping a binary
// at `go install`'s default destination (~/go/bin) is enough; the
// operator never has to edit a shell rc.
//
// Dirs added (in order, deduped):
//
// 1. Directory of os.Executable() — wherever this sherlock binary
// lives, all sibling MCPs live too if installed via `go install`.
// 2. $GOBIN.
// 3. $GOPATH/bin (resolved via `go env` defaults).
// 4. $HOME/go/bin (the canonical fallback when neither is set).
//
// Idempotent: re-runs are no-ops because each candidate is checked
// for presence in PATH before being prepended.
func EnsurePathContainsSiblings() {
current := os.Getenv("PATH")
sep := string(os.PathListSeparator)
existing := strings.Split(current, sep)
var toAdd []string
add := func(dir string) {
if dir == "" {
return
}
abs, err := filepath.Abs(dir)
if err != nil {
return
}
if slices.Contains(existing, abs) || slices.Contains(toAdd, abs) {
return
}
if fi, err := os.Stat(abs); err != nil || !fi.IsDir() {
return
}
toAdd = append(toAdd, abs)
}
if exe, err := os.Executable(); err == nil {
if real, err := filepath.EvalSymlinks(exe); err == nil {
exe = real
}
add(filepath.Dir(exe))
}
add(os.Getenv("GOBIN"))
if gopath := os.Getenv("GOPATH"); gopath != "" {
// GOPATH can be a list on some setups; canonical is one entry.
for _, p := range strings.Split(gopath, sep) {
add(filepath.Join(p, "bin"))
}
}
if home, err := os.UserHomeDir(); err == nil {
add(filepath.Join(home, "go", "bin"))
}
if len(toAdd) == 0 {
return
}
updated := strings.Join(append(toAdd, existing...), sep)
_ = os.Setenv("PATH", updated)
}