| 149 | ) |
| 150 | |
| 151 | func newHarness(runner AgentRunner, usageFunc UsageFunc, banner string) harness { |
| 152 | ti := textinput.New() |
| 153 | ti.Prompt = "❯ " |
| 154 | ti.PromptStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("36")).Bold(true) |
| 155 | ti.CharLimit = 0 |
| 156 | ti.Focus() |
| 157 | |
| 158 | vp := viewport.New(80, 20) |
| 159 | vp.SetContent(banner) |
| 160 | |
| 161 | dv := viewport.New(80, debugPanelHeight-2) |
| 162 | dv.SetContent(Dimmed("(no debug events yet)")) |
| 163 | |
| 164 | sp := spinner.New() |
| 165 | sp.Spinner = spinner.MiniDot |
| 166 | sp.Style = lipgloss.NewStyle().Foreground(lipgloss.Color("36")).Bold(true) |
| 167 | |
| 168 | m := harness{ |
| 169 | runner: runner, |
| 170 | input: ti, |
| 171 | viewport: vp, |
| 172 | debugView: dv, |
| 173 | spinner: sp, |
| 174 | followBottom: true, |
| 175 | // shineCol starts off-screen left so the very first frame shows |
| 176 | // the banner with no highlight visible — the streak slides in |
| 177 | // as ticks fire and the easing function ramps it across. |
| 178 | shineCol: -shineEdgePad, |
| 179 | output: &strings.Builder{}, |
| 180 | } |
| 181 | m.output.WriteString(banner) |
| 182 | m.bannerEnd = m.output.Len() |
| 183 | return m |
| 184 | } |
| 185 | |
| 186 | // NewProgram builds the Bubble Tea program. Wire the returned program to |
| 187 | // a Confirm callback and a stdout-pipe forwarder before calling Run. Pass |