From b65fc0fdf7c09fea7fc8b2ef0e101bbff6c8f938 Mon Sep 17 00:00:00 2001 From: Levi Neely Date: Tue, 14 Apr 2026 13:25:43 +0200 Subject: [PATCH] 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 --- pkg/tools/execute/pipe.go | 4 ++++ pkg/tools/execute/server.go | 11 ++++++++--- pkg/tools/execute/tool.go | 19 +++++++++++++++++++ 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/pkg/tools/execute/pipe.go b/pkg/tools/execute/pipe.go index 2b1f442..70adbb7 100644 --- a/pkg/tools/execute/pipe.go +++ b/pkg/tools/execute/pipe.go @@ -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)) } diff --git a/pkg/tools/execute/server.go b/pkg/tools/execute/server.go index 9d9139b..1d3396a 100644 --- a/pkg/tools/execute/server.go +++ b/pkg/tools/execute/server.go @@ -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:]...) diff --git a/pkg/tools/execute/tool.go b/pkg/tools/execute/tool.go index af22910..6439672 100644 --- a/pkg/tools/execute/tool.go +++ b/pkg/tools/execute/tool.go @@ -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 {