package projecttype // prompter.go is the Layer-3 interactive selection: when the deterministic // scan is ambiguous and no --type/--ai flag forced the outcome, eeco asks the // operator to pick a category, describe the project (routing to Layer 4), or // fall back to generic. It is AI-free and reads a single readline. import ( "bufio" "fmt" "io" "strconv" "strings" ) // stdinPrompter is the deterministic, AI-free layer-3 prompter: a single // readline question with numbered candidates and two escapes — describe // the project freely (routes to the AI layer) or pick the generic // fallback. type stdinPrompter struct { in *bufio.Scanner out io.Writer } // NewStdinPrompter builds a Prompter that reads operator choices from in // and writes its question to out. It is the prompter `eeco init` wires // to os.Stdin / os.Stderr; tests inject buffers. func NewStdinPrompter(in io.Reader, out io.Writer) Prompter { return &stdinPrompter{in: bufio.NewScanner(in), out: out} } const maxPromptRetries = 5 func (p *stdinPrompter) Pick(candidates []Category, cat *Catalog) (Category, bool, string, error) { fmt.Fprintln(p.out, "eeco init: project type is ambiguous.") for i, c := range candidates { desc := "" if e, ok := cat.Get(c); ok { desc = e.Description } fmt.Fprintf(p.out, " %d) %s — %s\n", i+1, c, desc) } fmt.Fprintln(p.out, " d) describe the project in your own words") fmt.Fprintln(p.out, " g) generic (no project-specific scaffold)") for range maxPromptRetries { if len(candidates) > 0 { fmt.Fprintf(p.out, "pick [1-%d/d/g]: ", len(candidates)) } else { fmt.Fprint(p.out, "pick [d/g]: ") } if !p.in.Scan() { // No more input (EOF / closed stdin): degrade to generic so a // piped, non-interactive `eeco init` still completes. return Generic, false, "", nil } line := strings.TrimSpace(p.in.Text()) switch strings.ToLower(line) { case "d": fmt.Fprint(p.out, "describe the project: ") free := "" if p.in.Scan() { free = strings.TrimSpace(p.in.Text()) } return "", true, free, nil case "g", "": if line == "" { continue } return Generic, false, "", nil } n, err := strconv.Atoi(line) if err == nil && n >= 1 && n <= len(candidates) { return candidates[n-1], false, "", nil } fmt.Fprintln(p.out, " unrecognised choice; try again.") } return Generic, false, "", nil }