execute: add jq, expect, bc support

jq: filter as positional arg, file args appended, pipeline-safe.
expect: pipes script via printf '%s' $'...' | expect -.
bc: pipes via printf '%s' $'...' | bc -ql (quiet + math library).
All three use ANSI-C quoting consistently.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Levi Neely 2026-04-14 13:25:43 +02:00
parent 9ec58b0ae8
commit b65fc0fdf7
3 changed files with 31 additions and 3 deletions

View File

@ -59,6 +59,10 @@ func BuildPipeline(steps []PipeStep) (string, bool, error) {
parts = append(parts, fmt.Sprintf("( gawk -e $'%s' )", ansiCEscape(code)))
case "sed":
parts = append(parts, fmt.Sprintf("( sed -e $'%s' )", ansiCEscape(code)))
case "jq":
parts = append(parts, fmt.Sprintf("( jq $'%s' )", ansiCEscape(code)))
case "bc":
parts = append(parts, fmt.Sprintf("( bc -ql <<< $'%s' )", ansiCEscape(code)))
default:
parts = append(parts, fmt.Sprintf("(\n%s\n)", code))
}

View File

@ -82,7 +82,7 @@ Examples:
Usage:
- Scripts located in: ` + ToolsPath() + `
- Supported languages: bash, python3, perl, awk, sed, ed (detected from shebang)
- Supported languages: bash, python3, perl, awk, sed, ed, jq, expect, bc (detected from shebang)
- Use for named scripts, not inline shell commands
- Default timeout: 30 seconds
@ -256,10 +256,15 @@ func (e *Server) Execute(ctx context.Context, code, language string, timeout int
case "sed":
interpreter = []string{"bash", "-c", fmt.Sprintf("sed -e $'%s'", ansiCEscape(code))}
case "ed":
// ed reads commands from stdin; no filename = edit new buffer.
interpreter = []string{"bash", "-c", fmt.Sprintf("printf '%%s' $'%s' | ed -s", ansiCEscape(code))}
case "jq":
interpreter = []string{"bash", "-c", fmt.Sprintf("jq $'%s'", ansiCEscape(code))}
case "expect":
interpreter = []string{"bash", "-c", fmt.Sprintf("printf '%%s' $'%s' | expect -", ansiCEscape(code))}
case "bc":
interpreter = []string{"bash", "-c", fmt.Sprintf("printf '%%s' $'%s' | bc -ql", ansiCEscape(code))}
default:
return "", fmt.Errorf("unsupported language: %s (supported: bash, python3, perl, awk, sed, ed)", language)
return "", fmt.Errorf("unsupported language: %s (supported: bash, python3, perl, awk, sed, ed, jq, expect, bc)", language)
}
wrapped := sandbox.WrapCommand(cfg, interpreter, workDir)
cmd = exec.CommandContext(ctx, wrapped[0], wrapped[1:]...)

View File

@ -50,6 +50,12 @@ func detectLanguage(code string) string {
return "sed"
case "ed":
return "ed"
case "jq":
return "jq"
case "expect":
return "expect"
case "bc":
return "bc"
}
return "bash"
}
@ -90,6 +96,19 @@ func injectArgs(language, name string, args []string, code string) string {
file = " '" + strings.ReplaceAll(args[0], "'", "'\\''") + "'"
}
return fmt.Sprintf("printf '%%s' $'%s' | ed -s%s", ansiCEscape(code), file)
case "jq":
// jq args are JSON input files; filter is the program.
fileArgs := make([]string, len(args))
for i, a := range args {
fileArgs[i] = "'" + strings.ReplaceAll(a, "'", "'\\''") + "'"
}
return fmt.Sprintf("jq $'%s' %s", ansiCEscape(code), strings.Join(fileArgs, " "))
case "expect":
// expect reads script from stdin via expect -.
return fmt.Sprintf("printf '%%s' $'%s' | expect -", ansiCEscape(code))
case "bc":
// bc reads from stdin; -ql for quiet mode + math library.
return fmt.Sprintf("printf '%%s' $'%s' | bc -ql", ansiCEscape(code))
default: // bash
escaped := make([]string, len(args))
for i, a := range args {