tools: add Python support; document tools in system prompt

- Detect language from shebang (bash default, python3 if shebang contains "python")
- Inject args language-appropriately: set -- for bash, sys.argv for Python
- Add python3 to executor language switch
- Handle Python steps in execute_pipe using bash $'...' embedding
- Update execute_tool description to mention supported languages
- Add Tools section to SYSTEM_PROMPT.md pointing to $OLLIE_9MOUNT/t/

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Levi Neely 2026-04-11 16:33:57 +02:00
parent 9775380532
commit 3d02de4a71
6 changed files with 93 additions and 24 deletions

View File

@ -22,7 +22,7 @@ func ExecuteDefs(toolsPath string) []ToolInfo {
},
{
Name: "execute_tool",
Description: "Run a named tool script from " + toolsPath + " in a sandboxed environment. Use this only for named scripts, not for inline shell commands.",
Description: "Run a named tool script from " + toolsPath + " in a sandboxed environment. Supported languages: bash, python3 (detected from shebang). Use this only for named scripts, not for inline shell commands.",
InputSchema: json.RawMessage(`{
"type": "object",
"required": ["tool"],

View File

@ -81,13 +81,10 @@ func dispatchExecuteTool(ctx context.Context, e *Server, args json.RawMessage) (
if err != nil {
return "", err
}
language := detectLanguage(toolCode)
code := toolCode
if len(a.Args) > 0 {
var escaped []string
for _, arg := range a.Args {
escaped = append(escaped, "'"+strings.ReplaceAll(arg, "'", "'\\''")+"'")
}
code = fmt.Sprintf("set -- %s\n%s", strings.Join(escaped, " "), code)
code = injectArgs(language, a.Tool, a.Args, toolCode)
}
timeout := a.Timeout
if timeout <= 0 {
@ -97,7 +94,7 @@ func dispatchExecuteTool(ctx context.Context, e *Server, args json.RawMessage) (
if sandbox == "" {
sandbox = "default"
}
return e.Execute(ctx, code, "bash", timeout, sandbox, true)
return e.Execute(ctx, code, language, timeout, sandbox, true)
}
func dispatchExecutePipe(ctx context.Context, e *Server, args json.RawMessage) (string, error) {

View File

@ -181,20 +181,24 @@ func (e *Server) Execute(ctx context.Context, code, language string, timeout int
defer cancel()
var cmd *exec.Cmd
var interpreter []string
switch language {
case "bash", "":
wrapped := sandbox.WrapCommand(cfg, []string{"bash", "-c", code}, workDir)
cmd = exec.CommandContext(ctx, wrapped[0], wrapped[1:]...)
cmd.Dir = workDir
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
cmd.Cancel = func() error {
if cmd.Process != nil {
syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
}
return nil
}
interpreter = []string{"bash", "-c", code}
case "python3", "python":
interpreter = []string{"python3", "-c", code}
default:
return "", fmt.Errorf("unsupported language: %s (supported: bash)", language)
return "", fmt.Errorf("unsupported language: %s (supported: bash, python3)", language)
}
wrapped := sandbox.WrapCommand(cfg, interpreter, workDir)
cmd = exec.CommandContext(ctx, wrapped[0], wrapped[1:]...)
cmd.Dir = workDir
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
cmd.Cancel = func() error {
if cmd.Process != nil {
syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
}
return nil
}
var outputBuf bytes.Buffer

View File

@ -38,15 +38,20 @@ func BuildPipeline(steps []PipeStep) (string, bool, error) {
} else {
return "", false, fmt.Errorf("each pipe step requires either 'tool' or 'code'")
}
language := "bash"
if step.Tool != "" {
language = detectLanguage(code)
}
if len(step.Args) > 0 {
var escaped []string
for _, arg := range step.Args {
escaped = append(escaped, "'"+strings.ReplaceAll(arg, "'", "'\\''")+"'")
}
parts = append(parts, fmt.Sprintf("( set -- %s\n%s )", strings.Join(escaped, " "), code))
} else {
code = injectArgs(language, step.Tool, step.Args, code)
}
switch language {
case "python3":
parts = append(parts, fmt.Sprintf("( python3 -c $'%s' )", ansiCEscape(code)))
default:
parts = append(parts, fmt.Sprintf("(\n%s\n)", code))
}
}
return strings.Join(parts, " |\n"), true, nil
}

View File

@ -17,6 +17,57 @@ func ToolsPath() string {
return filepath.Join(home, ".local", "share", "ollie", "tools")
}
// detectLanguage infers the script language from the shebang line.
// Returns "python3" for Python scripts, "bash" for everything else.
func detectLanguage(code string) string {
line, _, _ := strings.Cut(code, "\n")
line = strings.TrimSpace(line)
if strings.HasPrefix(line, "#!") && strings.Contains(line, "python") {
return "python3"
}
return "bash"
}
// injectArgs prepends language-appropriate argument binding to code.
func injectArgs(language, name string, args []string, code string) string {
switch language {
case "python3":
quoted := make([]string, len(args))
for i, a := range args {
quoted[i] = fmt.Sprintf("%q", a)
}
return fmt.Sprintf("import sys\nsys.argv = [%q, %s]\n%s", name, strings.Join(quoted, ", "), code)
default: // bash
escaped := make([]string, len(args))
for i, a := range args {
escaped[i] = "'" + strings.ReplaceAll(a, "'", "'\\''") + "'"
}
return fmt.Sprintf("set -- %s\n%s", strings.Join(escaped, " "), code)
}
}
// ansiCEscape escapes a string for embedding in a bash $'...' literal.
func ansiCEscape(s string) string {
var b strings.Builder
for _, r := range s {
switch r {
case '\\':
b.WriteString(`\\`)
case '\'':
b.WriteString(`\'`)
case '\n':
b.WriteString(`\n`)
case '\r':
b.WriteString(`\r`)
case '\t':
b.WriteString(`\t`)
default:
b.WriteRune(r)
}
}
return b.String()
}
// ReadTool reads a named tool script from the tools directory.
func ReadTool(name string) (string, error) {
if strings.Contains(name, "/") || strings.Contains(name, "..") {

View File

@ -17,6 +17,18 @@ Be truthful. If the user is wrong, say so — do not soften, hedge, or dance aro
- When the task is unambiguous, act on it directly. Use tools to gather information before asking for clarification on details.
- Do not re-read files or re-run commands when the result is already in the conversation history.
# Tools
Tool scripts live at `${OLLIE_9MOUNT:-$HOME/mnt/ollie}/t/`. Run them with `execute_tool` or as steps in an `execute_pipe` pipeline.
```sh
ls ${OLLIE_9MOUNT:-$HOME/mnt/ollie}/t/ # list available tools
```
Supported languages are detected from the script's shebang line:
- `#!/usr/bin/env bash` (or any bash shebang) — runs with `bash -c`
- `#!/usr/bin/env python3` (or any python shebang) — runs with `python3 -c`; `sys.argv` is set automatically when args are provided
# Skills
Skills are domain-specific knowledge files. Discover and load them before starting non-trivial tasks.