fix: treat meta cmd as shell command string, prepend tools dir to PATH

- ResolveTool now returns cmd field as-is instead of trying to resolve
  it as a binary path. This allows meta-only tools to use full shell
  command strings with env vars, pipes, and subshells.

- Prepend ~/.config/ollie/tools to PATH when executing tools so cmd
  fields can reference other tools by name.
This commit is contained in:
Levi Neely 2026-08-12 18:44:16 +02:00
parent 92d9fec51a
commit 9b98a958cc
2 changed files with 13 additions and 16 deletions

View File

@ -143,6 +143,14 @@ func executeSandboxed(ctx context.Context, toolPath, stdinData, cwd string, envE
for k, v := range envExtra { for k, v := range envExtra {
envMap[k] = v envMap[k] = v
} }
// Prepend tools directory to PATH so meta-only cmd fields can reference other tools
if toolsDir := toolsrv.ToolsPath(); toolsDir != "" {
if existing := envMap["PATH"]; existing != "" {
envMap["PATH"] = toolsDir + ":" + existing
} else {
envMap["PATH"] = toolsDir
}
}
// Compute NAMESPACE if not set // Compute NAMESPACE if not set
if _, ok := envMap["NAMESPACE"]; !ok { if _, ok := envMap["NAMESPACE"]; !ok {
if ns := Plan9Namespace(envMap); ns != "" { if ns := Plan9Namespace(envMap); ns != "" {

View File

@ -187,10 +187,10 @@ func ToolInfoFromMeta(name string, m *MetaFile) ToolInfo {
} }
} }
// ResolveTool returns the executable path for a named tool. // ResolveTool returns the command string for a named tool.
// Resolution order: // Resolution order:
// 1. ~/.config/ollie/tools/<name> (local wrapper wins over system binary) // 1. ~/.config/ollie/tools/<name> (local script/binary)
// 2. .meta "cmd" field (after variant resolution; absolute path or bare name via $PATH) // 2. .meta "cmd" field (returned as-is; it's a shell command string)
// 3. exec.LookPath(<name>) as final fallback // 3. exec.LookPath(<name>) as final fallback
func ResolveTool(name string) (string, error) { func ResolveTool(name string) (string, error) {
// Local tools path always wins — allows wrappers to shadow system binaries. // Local tools path always wins — allows wrappers to shadow system binaries.
@ -199,22 +199,11 @@ func ResolveTool(name string) (string, error) {
return local, nil return local, nil
} }
// Check .meta cmd field (after variant resolution). // Check .meta cmd field — return as-is (shell command string, not a path to resolve).
if m, err := LoadMetaFile(name); err == nil && m != nil { if m, err := LoadMetaFile(name); err == nil && m != nil {
resolved := m.Resolve() resolved := m.Resolve()
if resolved != nil && resolved.Cmd != "" { if resolved != nil && resolved.Cmd != "" {
// Check tools path for the cmd name first return resolved.Cmd, nil
cmdLocal := filepath.Join(ToolsPath(), resolved.Cmd)
if info, err := os.Stat(cmdLocal); err == nil && !info.IsDir() {
return cmdLocal, nil
}
if filepath.IsAbs(resolved.Cmd) {
return resolved.Cmd, nil
}
if p, err := exec.LookPath(resolved.Cmd); err == nil {
return p, nil
}
return "", fmt.Errorf("tool %s: cmd %q not found in PATH", name, resolved.Cmd)
} }
} }