Vectrum
Sign In
59 lines · 1.6 KB · Go
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)
		}
	}
}