Files
yetone 9848a9062a library: RTK says when rtk isn't on the PATH the agents get, and puts it there when asked (#601, Moody-Sin: magpie好像没有正确安装RTK — Pi said "RTK disabled: rtk binary not found in PATH" while the Library showed RTK working) — rtk's hooks run it by name (Pi's extension probes rtk --version and turns itself off when that fails; Claude Code's and Codex's are rtk hook …; the commands they rewrite to are rtk git status and so on), but magpie looked for rtk in more places than an agent's PATH: ~/.local/bin and ~/.cargo/bin, which the app adds to its own PATH whether the user's shell has them or not (rtk's own install.sh puts it in ~/.local/bin and only warns when that isn't on PATH), and WinGet\Links on Windows, which winget doesn't always link into. So rtk was "installed", the agents were switched on with it by its whole path, and none of them could run it. Now the PATH a terminal opened now has is read — the login shell's (proc.LoginPath, the one UserPath already asks for, kept a minute) or on Windows the registry's (moved from provider's registryPath) — and when rtk isn't in it the RTK card says so, with what Put RTK on PATH will do, and each agent that has rtk is tagged Not on PATH (不在 PATH 中). Put RTK on PATH (magpie library rtk path) links rtk into the first of ~/.local/bin, ~/bin, /opt/homebrew/bin, /usr/local/bin, linuxbrew's bin, ~/.cargo/bin that is on that PATH and writable, or on Windows adds rtk's own folder to the user's PATH in the registry (a link needs Developer Mode, and a copy is left behind when winget upgrades rtk; winget's package folder doesn't change across upgrades) and tells Explorer; with no such folder it says which folder to add to PATH in the shell profile — shell profiles are never edited. rtk is also found in winget's package folder (WinGet\Packages\rtk-ai.rtk_*), and rtk's script upgrades a linked rtk where the link points, not over the link. TestRTKOffPath and library-rtk-path.test.cjs (en, zh) fail without the change. go vet ./internal/..., go build -tags nogui (darwin, GOOS=linux), GOOS=windows go build and vet, go test -tags nogui -count=1 ./... under a temp HOME pass but the known TestFXInState flake. The Windows registry path is built, not run on Windows.
2026-10-03 02:38:17 +08:00

97 lines
2.6 KiB
Go

package library
import (
"fmt"
"os"
"path/filepath"
"runtime"
"github.com/yetone/magpie/internal/proc"
)
// rtk's hooks run it by name — Pi's extension asks rtk --version and turns
// itself off ("RTK disabled: rtk binary not found in PATH") when that
// fails, Claude Code's and Codex's hooks are rtk hook …, and the commands
// they rewrite to are rtk git status and so on — so an agent needs rtk on
// its own PATH. magpie finds rtk in more places than that: the folders
// installers use, which the app adds to its PATH whether the user's shell
// has them or not (~/.local/bin, where rtk's own script puts it and only
// warns when it isn't on PATH), and winget's, whose link isn't always made.
// So magpie says when rtk isn't on the PATH a terminal opened now has, and
// puts it there when asked: a link in a folder on it, or on Windows its
// folder added to the user's PATH, as winget does when it can't link.
// Shell profiles are left alone.
// userPath is the PATH an agent started now gets (proc.LoginPath); nil
// when it can't be told. A test sets it.
var userPath = proc.LoginPath
func rtkName() string {
if runtime.GOOS == "windows" {
return "rtk.exe"
}
return "rtk"
}
// rtkReached says whether an agent started now finds rtk by name; true when
// the PATH it gets can't be told.
func rtkReached() bool {
dirs := userPath()
if dirs == nil {
return true
}
for _, d := range dirs {
if d != "" && runnable(filepath.Join(d, rtkName())) {
return true
}
}
return false
}
func runnable(p string) bool {
st, err := os.Stat(p)
return err == nil && !st.IsDir() && (runtime.GOOS == "windows" || st.Mode()&0o111 != 0)
}
// writable says whether a file can be made in dir.
func writable(dir string) bool {
f, err := os.CreateTemp(dir, ".magpie-")
if err != nil {
return false
}
f.Close()
os.Remove(f.Name())
return true
}
// PathRTK puts the rtk magpie found on the PATH agents get, when the user
// asks for it (see rtkOnPath).
func PathRTK() (*RTKView, error) {
rtkMu.Lock()
defer rtkMu.Unlock()
bin := rtkPath()
if bin == "" {
return nil, fmt.Errorf("rtk isn't installed — install it first (%s)", RTKURL)
}
if rtkReached() {
return ReadRTK(), nil
}
dir, link := rtkPathDir(bin)
if dir == "" {
return nil, fmt.Errorf("magpie found no folder on your PATH it can put rtk in — add %s to PATH in your shell profile", filepath.Dir(bin))
}
if err := rtkOnPath(bin, dir, link); err != nil {
return nil, err
}
v := ReadRTK()
if v.OffPath {
return nil, fmt.Errorf("rtk still isn't on your PATH after putting it in %s", dir)
}
for _, a := range v.Agents {
if a.On {
v.Restart = append(v.Restart, a.ID)
}
}
return v, nil
}