promptForConfigurationFrom is the non-TTY fallback for promptForConfiguration. It prints numbered option lists for single-select fields and accepts comma-separated values for multi-select fields (tools and safe-outputs). Separated from promptForConfiguration so it can be exercised in unit tests with
(r io.Reader)
| 287 | // values for multi-select fields (tools and safe-outputs). Separated from |
| 288 | // promptForConfiguration so it can be exercised in unit tests without a real TTY. |
| 289 | func (b *InteractiveWorkflowBuilder) promptForConfigurationFrom(r io.Reader) error { |
| 290 | interactiveLog.Print("Non-TTY detected, using text prompts for configuration") |
| 291 | scanner := b.ensureNonTTYScanner(r) |
| 292 | |
| 293 | // --- Trigger (single-select) --- |
| 294 | triggerOptions := []struct{ label, value string }{ |
| 295 | {"Manual trigger (workflow_dispatch)", "workflow_dispatch"}, |
| 296 | {"Issue opened or reopened", "issues"}, |
| 297 | {"Pull request opened or synchronized", "pull_request"}, |
| 298 | {"Push to main branch", "push"}, |
| 299 | {"Issue comment created", "issue_comment"}, |
| 300 | {"Schedule (daily, scattered execution time)", "schedule_daily"}, |
| 301 | {"Schedule (weekly on Monday, scattered execution time)", "schedule_weekly"}, |
| 302 | {"Command trigger (/bot-name)", "command"}, |
| 303 | } |
| 304 | trigger, err := promptNonInteractiveSelect(scanner, "When should this workflow run?", triggerOptions) |
| 305 | if err != nil { |
| 306 | return fmt.Errorf("failed to select trigger: %w", err) |
| 307 | } |
| 308 | b.Trigger = trigger |
| 309 | |
| 310 | // --- Engine (single-select) --- |
| 311 | engineOptions := []struct{ label, value string }{ |
| 312 | {"copilot - GitHub Copilot CLI", "copilot"}, |
| 313 | {"claude - Anthropic Claude Code coding agent", "claude"}, |
| 314 | {"codex - OpenAI Codex engine", "codex"}, |
| 315 | {"gemini - Google Gemini CLI", "gemini"}, |
| 316 | } |
| 317 | engine, err := promptNonInteractiveSelect(scanner, "Which AI engine should process this workflow?", engineOptions) |
| 318 | if err != nil { |
| 319 | return fmt.Errorf("failed to select engine: %w", err) |
| 320 | } |
| 321 | b.Engine = engine |
| 322 | |
| 323 | // --- Tools (multi-select) --- |
| 324 | toolOptions := []struct{ label, value string }{ |
| 325 | {"github - GitHub API tools (issues, PRs, comments, repos)", "github"}, |
| 326 | {"edit - File editing tools", "edit"}, |
| 327 | {"bash - Shell command tools", "bash"}, |
| 328 | {"web-fetch - Web content fetching tools", "web-fetch"}, |
| 329 | {"web-search - Web search tools", "web-search"}, |
| 330 | {"playwright - Browser automation tools", "playwright"}, |
| 331 | } |
| 332 | tools, err := promptNonInteractiveMultiSelect(scanner, "Which tools should the AI have access to? (comma-separated values or numbers, or leave blank for none)", toolOptions) |
| 333 | if err != nil { |
| 334 | return fmt.Errorf("failed to select tools: %w", err) |
| 335 | } |
| 336 | b.Tools = tools |
| 337 | |
| 338 | // --- Safe outputs (multi-select) --- |
| 339 | outputOptions := buildSafeOutputOptions() |
| 340 | safeOutputItems := make([]struct{ label, value string }, len(outputOptions)) |
| 341 | for i, opt := range outputOptions { |
| 342 | safeOutputItems[i] = struct{ label, value string }{opt.Key, opt.Value} |
| 343 | } |
| 344 | safeOutputs, err := promptNonInteractiveMultiSelect(scanner, "What outputs should the AI be able to create? (comma-separated values or numbers, or leave blank for none)", safeOutputItems) |
| 345 | if err != nil { |
| 346 | return fmt.Errorf("failed to select safe outputs: %w", err) |