1use anyhow::Result;
2use std::sync::Arc;
3use tokio::sync::mpsc;
4
5use crate::config::types::UiSurfacePreference;
6
7pub mod alternate_screen;
8pub mod app;
9pub(crate) mod language_badge;
10pub mod log;
11pub mod panic_hook;
12pub mod runner;
13pub mod session;
14pub mod style;
15pub mod theme_parser;
16pub mod types;
17pub mod widgets;
18
19pub use style::{convert_style, theme_from_styles};
20pub use theme_parser::ThemeConfigParser;
21pub use types::{
22 ContentPart, EditingMode, FocusChangeCallback, InlineCommand, InlineEvent, InlineEventCallback,
23 InlineHandle, InlineHeaderContext, InlineHeaderHighlight, InlineHeaderStatusBadge,
24 InlineHeaderStatusTone, InlineLinkRange, InlineLinkTarget, InlineListItem,
25 InlineListSearchConfig, InlineListSelection, InlineMessageKind, InlineSegment, InlineSession,
26 InlineTextStyle, InlineTheme, ListOverlayRequest, ModalOverlayRequest, OverlayEvent,
27 OverlayHotkey, OverlayHotkeyAction, OverlayHotkeyKey, OverlayRequest, OverlaySelectionChange,
28 OverlaySubmission, RewindAction, SecurePromptConfig, WizardModalMode, WizardOverlayRequest,
29 WizardStep,
30};
31
32use runner::{TuiOptions, run_tui};
33
34pub fn spawn_session(
35 theme: InlineTheme,
36 placeholder: Option<String>,
37 surface_preference: UiSurfacePreference,
38 inline_rows: u16,
39 event_callback: Option<InlineEventCallback>,
40 active_pty_sessions: Option<Arc<std::sync::atomic::AtomicUsize>>,
41 workspace_root: Option<std::path::PathBuf>,
42) -> Result<InlineSession> {
43 spawn_session_with_prompts(
44 theme,
45 placeholder,
46 surface_preference,
47 inline_rows,
48 event_callback,
49 active_pty_sessions,
50 None,
51 crate::config::KeyboardProtocolConfig::default(),
52 workspace_root,
53 )
54}
55
56#[allow(clippy::too_many_arguments)]
58pub fn spawn_session_with_prompts(
59 theme: InlineTheme,
60 placeholder: Option<String>,
61 surface_preference: UiSurfacePreference,
62 inline_rows: u16,
63 event_callback: Option<InlineEventCallback>,
64 active_pty_sessions: Option<Arc<std::sync::atomic::AtomicUsize>>,
65 input_activity_counter: Option<Arc<std::sync::atomic::AtomicU64>>,
66 keyboard_protocol: crate::config::KeyboardProtocolConfig,
67 workspace_root: Option<std::path::PathBuf>,
68) -> Result<InlineSession> {
69 spawn_session_with_prompts_and_options(
70 theme,
71 placeholder,
72 surface_preference,
73 inline_rows,
74 event_callback,
75 None,
76 active_pty_sessions,
77 input_activity_counter,
78 keyboard_protocol,
79 workspace_root,
80 None,
81 "Agent TUI".to_string(),
82 None,
83 )
84}
85
86#[allow(clippy::too_many_arguments)]
88pub fn spawn_session_with_prompts_and_options(
89 theme: InlineTheme,
90 placeholder: Option<String>,
91 surface_preference: UiSurfacePreference,
92 inline_rows: u16,
93 event_callback: Option<InlineEventCallback>,
94 focus_callback: Option<FocusChangeCallback>,
95 active_pty_sessions: Option<Arc<std::sync::atomic::AtomicUsize>>,
96 input_activity_counter: Option<Arc<std::sync::atomic::AtomicU64>>,
97 keyboard_protocol: crate::config::KeyboardProtocolConfig,
98 workspace_root: Option<std::path::PathBuf>,
99 appearance: Option<session::config::AppearanceConfig>,
100 app_name: String,
101 non_interactive_hint: Option<String>,
102) -> Result<InlineSession> {
103 use crossterm::tty::IsTty;
104
105 if !std::io::stdin().is_tty() {
107 return Err(anyhow::anyhow!(
108 "cannot run interactive TUI: stdin is not a terminal (must be run in an interactive terminal)"
109 ));
110 }
111
112 let (command_tx, command_rx) = mpsc::unbounded_channel();
113 let (event_tx, event_rx) = mpsc::unbounded_channel();
114 let show_logs = log::is_tui_log_capture_enabled();
115
116 tokio::spawn(async move {
117 if let Err(error) = run_tui(
118 command_rx,
119 event_tx,
120 TuiOptions {
121 surface_preference,
122 inline_rows,
123 show_logs,
124 log_theme: None,
125 event_callback,
126 focus_callback,
127 active_pty_sessions,
128 input_activity_counter,
129 keyboard_protocol,
130 workspace_root,
131 },
132 move |rows| {
133 session::Session::new_with_logs(
134 theme,
135 placeholder,
136 rows,
137 show_logs,
138 appearance,
139 app_name,
140 )
141 },
142 )
143 .await
144 {
145 let error_msg = error.to_string();
146 if error_msg.contains("stdin is not a terminal") {
147 eprintln!("Error: Interactive TUI requires a proper terminal.");
148 if let Some(hint) = non_interactive_hint.as_deref() {
149 eprintln!("{}", hint);
150 } else {
151 eprintln!("Use a non-interactive mode in your host app for piped input.");
152 }
153 } else {
154 eprintln!("Error: TUI startup failed: {:#}", error);
155 }
156 tracing::error!(%error, "inline session terminated unexpectedly");
157 }
158 });
159
160 Ok(InlineSession {
161 handle: InlineHandle { sender: command_tx },
162 events: event_rx,
163 })
164}