1use anyhow::Result;
2use std::sync::Arc;
3use tokio::sync::mpsc;
4
5use crate::config::types::UiSurfacePreference;
6
7pub mod alternate_screen;
8pub(crate) mod language_badge;
9pub mod log;
10pub mod panic_hook;
11pub mod runner;
12pub mod session;
13pub mod style;
14pub mod theme_parser;
15pub mod types;
16pub mod widgets;
17
18pub use style::{convert_style, theme_from_styles};
19pub use theme_parser::ThemeConfigParser;
20pub use types::{
21 ContentPart, DiffHunk, DiffOverlayRequest, DiffPreviewMode, DiffPreviewState, EditingMode,
22 FocusChangeCallback, InlineCommand, InlineEvent, InlineEventCallback, InlineHandle,
23 InlineHeaderContext, InlineHeaderHighlight, InlineHeaderStatusBadge, InlineHeaderStatusTone,
24 InlineListItem, InlineListSearchConfig, InlineListSelection, InlineMessageKind, InlineSegment,
25 InlineSession, InlineTextStyle, InlineTheme, ListOverlayRequest, ModalOverlayRequest,
26 OverlayEvent, OverlayHotkey, OverlayHotkeyAction, OverlayHotkeyKey, OverlayRequest,
27 OverlaySelectionChange, OverlaySubmission, PlanContent, PlanPhase, PlanStep,
28 SecurePromptConfig, SlashCommandItem, TrustMode, 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 crate::config::KeyboardProtocolConfig::default(),
51 workspace_root,
52 Vec::new(),
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 keyboard_protocol: crate::config::KeyboardProtocolConfig,
66 workspace_root: Option<std::path::PathBuf>,
67 slash_commands: Vec<SlashCommandItem>,
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 keyboard_protocol,
78 workspace_root,
79 slash_commands,
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 keyboard_protocol: crate::config::KeyboardProtocolConfig,
97 workspace_root: Option<std::path::PathBuf>,
98 slash_commands: Vec<SlashCommandItem>,
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
115 tokio::spawn(async move {
116 if let Err(error) = run_tui(
117 command_rx,
118 event_tx,
119 TuiOptions {
120 theme,
121 placeholder,
122 surface_preference,
123 inline_rows,
124 show_logs: log::is_tui_log_capture_enabled(),
125 log_theme: None,
126 event_callback,
127 focus_callback,
128 active_pty_sessions,
129 keyboard_protocol,
130 workspace_root,
131 slash_commands,
132 appearance,
133 app_name: app_name.clone(),
134 },
135 )
136 .await
137 {
138 let error_msg = error.to_string();
139 if error_msg.contains("stdin is not a terminal") {
140 eprintln!("Error: Interactive TUI requires a proper terminal.");
141 if let Some(hint) = non_interactive_hint.as_deref() {
142 eprintln!("{}", hint);
143 } else {
144 eprintln!("Use a non-interactive mode in your host app for piped input.");
145 }
146 } else {
147 eprintln!("Error: TUI startup failed: {:#}", error);
148 }
149 tracing::error!(%error, "inline session terminated unexpectedly");
150 }
151 });
152
153 Ok(InlineSession {
154 handle: InlineHandle { sender: command_tx },
155 events: event_rx,
156 })
157}