package ui import ( "bufio" "context" "fmt" "io" "strings" "harness/internal/approval" ) // Approver reads approval decisions from a terminal. type Approver struct { In *bufio.Scanner Out io.Writer } // Prompt implements approval.Prompt. func (a *Approver) Prompt(ctx context.Context, req approval.Request) (approval.Decision, error) { if ctx.Err() != nil { return approval.Deny, ctx.Err() } fmt.Fprintf(a.Out, "\n%s%s wants to %s%s\n", color.bold, req.Tool, req.Summary, color.reset) if req.Detail != "" { for _, line := range strings.Split(firstLines(strings.TrimRight(req.Detail, "\n"), 10), "\n") { fmt.Fprintf(a.Out, " %s%s%s\n", color.cyan, line, color.reset) } } options := "[y] yes [n] no [t] always this tool" if req.Rememberable { options = "[y] yes [n] no [a] always this exact one [t] always this tool" } for { fmt.Fprintf(a.Out, "%s%s%s\n> ", color.dim, options, color.reset) if !a.In.Scan() { // stdin closed: refusing is the only safe reading of silence. fmt.Fprintln(a.Out) return approval.Deny, nil } switch strings.ToLower(strings.TrimSpace(a.In.Text())) { case "y", "yes", "": return approval.Once, nil case "n", "no": return approval.Deny, nil case "t", "tool": return approval.AlwaysThisTool, nil case "a", "always": if req.Rememberable { return approval.AlwaysThisCommand, nil } fmt.Fprintf(a.Out, "%sthis one cannot be remembered: its text does not determine what it runs%s\n", color.red, color.reset) default: fmt.Fprintf(a.Out, "%sanswer y, n%s\n", color.dim, color.reset) } } }