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, InlineHeaderBadge, InlineHeaderContext, InlineHeaderHighlight,
24 InlineHeaderStatusBadge, InlineHeaderStatusTone, InlineLinkRange, InlineLinkTarget,
25 InlineListItem, InlineListSearchConfig, InlineListSelection, InlineMessageKind, InlineSegment,
26 InlineSession, InlineTextStyle, InlineTheme, ListOverlayRequest, ModalOverlayRequest,
27 OverlayEvent, OverlayHotkey, OverlayHotkeyAction, OverlayHotkeyKey, OverlayRequest,
28 OverlaySelectionChange, OverlaySubmission, RewindAction, SecurePromptConfig, WizardModalMode,
29 WizardOverlayRequest, 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 crate::FullscreenInteractionSettings::default(),
53 workspace_root,
54 )
55}
56
57#[expect(clippy::too_many_arguments)]
59pub fn spawn_session_with_prompts(
60 theme: InlineTheme,
61 placeholder: Option<String>,
62 surface_preference: UiSurfacePreference,
63 inline_rows: u16,
64 event_callback: Option<InlineEventCallback>,
65 active_pty_sessions: Option<Arc<std::sync::atomic::AtomicUsize>>,
66 input_activity_counter: Option<Arc<std::sync::atomic::AtomicU64>>,
67 keyboard_protocol: crate::config::KeyboardProtocolConfig,
68 fullscreen: crate::FullscreenInteractionSettings,
69 workspace_root: Option<std::path::PathBuf>,
70) -> Result<InlineSession> {
71 spawn_session_with_prompts_and_options(
72 theme,
73 placeholder,
74 surface_preference,
75 inline_rows,
76 event_callback,
77 None,
78 active_pty_sessions,
79 input_activity_counter,
80 keyboard_protocol,
81 fullscreen,
82 workspace_root,
83 None,
84 "Agent TUI".to_string(),
85 None,
86 )
87}
88
89#[expect(clippy::too_many_arguments)]
91pub fn spawn_session_with_prompts_and_options(
92 theme: InlineTheme,
93 placeholder: Option<String>,
94 surface_preference: UiSurfacePreference,
95 inline_rows: u16,
96 event_callback: Option<InlineEventCallback>,
97 focus_callback: Option<FocusChangeCallback>,
98 active_pty_sessions: Option<Arc<std::sync::atomic::AtomicUsize>>,
99 input_activity_counter: Option<Arc<std::sync::atomic::AtomicU64>>,
100 keyboard_protocol: crate::config::KeyboardProtocolConfig,
101 fullscreen: crate::FullscreenInteractionSettings,
102 workspace_root: Option<std::path::PathBuf>,
103 appearance: Option<session::config::AppearanceConfig>,
104 app_name: String,
105 non_interactive_hint: Option<String>,
106) -> Result<InlineSession> {
107 use crossterm::tty::IsTty;
108
109 if !std::io::stdin().is_tty() {
111 return Err(anyhow::anyhow!(
112 "cannot run interactive TUI: stdin is not a terminal (must be run in an interactive terminal)"
113 ));
114 }
115
116 let (command_tx, command_rx) = mpsc::unbounded_channel();
117 let (event_tx, event_rx) = mpsc::unbounded_channel();
118 let show_logs = log::is_tui_log_capture_enabled();
119
120 tokio::spawn(async move {
121 if let Err(error) = run_tui(
122 command_rx,
123 event_tx,
124 TuiOptions {
125 surface_preference,
126 inline_rows,
127 show_logs,
128 log_theme: None,
129 event_callback,
130 focus_callback,
131 active_pty_sessions,
132 input_activity_counter,
133 keyboard_protocol,
134 fullscreen,
135 workspace_root,
136 },
137 move |rows| {
138 session::Session::new_with_logs(
139 theme,
140 placeholder,
141 rows,
142 show_logs,
143 appearance,
144 app_name,
145 )
146 },
147 )
148 .await
149 {
150 let error_msg = error.to_string();
151 if error_msg.contains("stdin is not a terminal") {
152 eprintln!("Error: Interactive TUI requires a proper terminal.");
153 if let Some(hint) = non_interactive_hint.as_deref() {
154 eprintln!("{}", hint);
155 } else {
156 eprintln!("Use a non-interactive mode in your host app for piped input.");
157 }
158 } else {
159 eprintln!("Error: TUI startup failed: {:#}", error);
160 }
161 tracing::error!(%error, "inline session terminated unexpectedly");
162 }
163 });
164
165 Ok(InlineSession {
166 handle: InlineHandle { sender: command_tx },
167 events: event_rx,
168 })
169}