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