runLaunchWizard validates pinned values, skips phases that are already pinned, and runs a bubbletea program for whatever remains. Returns cancelled=true when the user aborts via q/Ctrl+C. When all three fields are pinned and valid, it returns immediately without starting bubbletea.
(out io.Writer, in wizardInput)
| 39 | // When all three fields are pinned and valid, it returns immediately |
| 40 | // without starting bubbletea. |
| 41 | func runLaunchWizard(out io.Writer, in wizardInput) (launchSelection, bool, error) { |
| 42 | resolved, needTool, needEndpoint, needModel, err := validatePinned(in) |
| 43 | if err != nil { |
| 44 | return launchSelection{}, false, err |
| 45 | } |
| 46 | if !needTool && !needEndpoint && !needModel { |
| 47 | return resolved, false, nil |
| 48 | } |
| 49 | |
| 50 | model := newLaunchWizardModel(resolved, in, needTool, needEndpoint, needModel) |
| 51 | |
| 52 | file, ok := out.(*os.File) |
| 53 | if !ok || !isTerminal(file) { |
| 54 | // Non-TTY callers should never reach here — launch.go calls |
| 55 | // autoResolve instead. Defensive: render the first phase to |
| 56 | // help debugging and return cancelled. |
| 57 | _, _ = fmt.Fprint(out, model.View()) |
| 58 | return resolved, true, nil |
| 59 | } |
| 60 | |
| 61 | program := tea.NewProgram(model, tea.WithOutput(out)) |
| 62 | final, err := program.Run() |
| 63 | if err != nil { |
| 64 | return launchSelection{}, false, err |
| 65 | } |
| 66 | wizard, ok := final.(launchWizardModel) |
| 67 | if !ok { |
| 68 | return launchSelection{}, true, nil |
| 69 | } |
| 70 | if wizard.aborted { |
| 71 | return launchSelection{}, true, nil |
| 72 | } |
| 73 | return wizard.sel, false, nil |
| 74 | } |
| 75 | |
| 76 | // validatePinned looks up pinned tool/endpoint values up-front so the |
| 77 | // caller never enters the TUI with garbage. Returns the partially- |