1package chat
2
3import (
4 "encoding/json"
5 "strings"
6
7 "github.com/charmbracelet/crush/internal/agent/tools"
8 "github.com/charmbracelet/crush/internal/ui/styles"
9)
10
11// BashToolRenderer renders a bash tool call.
12func BashToolRenderer(sty *styles.Styles, width int, opts *ToolRenderOpts) string {
13 cappedWidth := cappedMessageWidth(width)
14 const toolName = "Bash"
15 if !opts.ToolCall.Finished && !opts.Canceled {
16 return pendingTool(sty, toolName, opts.Anim)
17 }
18
19 var params tools.BashParams
20 var cmd string
21 err := json.Unmarshal([]byte(opts.ToolCall.Input), ¶ms)
22
23 if err != nil {
24 cmd = "failed to parse command"
25 } else {
26 cmd = strings.ReplaceAll(params.Command, "\n", " ")
27 cmd = strings.ReplaceAll(cmd, "\t", " ")
28 }
29
30 // TODO: if the tool is being run in the background use the background job renderer
31
32 toolParams := []string{
33 cmd,
34 }
35
36 if params.RunInBackground {
37 toolParams = append(toolParams, "background", "true")
38 }
39
40 header := toolHeader(sty, opts.Status(), "Bash", cappedWidth, toolParams...)
41
42 if opts.Nested {
43 return header
44 }
45
46 earlyStateContent, ok := toolEarlyStateContent(sty, opts, cappedWidth)
47
48 // If this is OK that means that the tool is not done yet or it was canceled
49 if ok {
50 return strings.Join([]string{header, "", earlyStateContent}, "\n")
51 }
52
53 if opts.Result == nil {
54 // We should not get here!
55 return header
56 }
57
58 var meta tools.BashResponseMetadata
59 err = json.Unmarshal([]byte(opts.Result.Metadata), &meta)
60
61 var output string
62 if err != nil {
63 output = "failed to parse output"
64 }
65 output = meta.Output
66 if output == "" && opts.Result.Content != tools.BashNoOutput {
67 output = opts.Result.Content
68 }
69
70 if output == "" {
71 return header
72 }
73
74 bodyWidth := cappedWidth - toolBodyLeftPaddingTotal
75
76 output = sty.Tool.Body.Render(toolOutputPlainContent(sty, output, bodyWidth, opts.Expanded))
77
78 return strings.Join([]string{header, "", output}, "\n")
79}