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 InlineLinkRange, InlineLinkTarget, InlineListItem, InlineListSearchConfig, InlineListSelection,
25 InlineMessageKind, InlineSegment, InlineSession, InlineTextStyle, InlineTheme,
26 ListOverlayRequest, ModalOverlayRequest, OverlayEvent, OverlayHotkey, OverlayHotkeyAction,
27 OverlayHotkeyKey, OverlayRequest, OverlaySelectionChange, OverlaySubmission, PlanContent,
28 PlanPhase, PlanStep, RewindAction, SecurePromptConfig, SlashCommandItem, TrustMode,
29 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::config::KeyboardProtocolConfig::default(),
52 workspace_root,
53 Vec::new(),
54 )
55}
56
57#[allow(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 workspace_root: Option<std::path::PathBuf>,
69 slash_commands: Vec<SlashCommandItem>,
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 workspace_root,
82 slash_commands,
83 None,
84 "Agent TUI".to_string(),
85 None,
86 )
87}
88
89#[allow(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 workspace_root: Option<std::path::PathBuf>,
102 slash_commands: Vec<SlashCommandItem>,
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
119 tokio::spawn(async move {
120 if let Err(error) = run_tui(
121 command_rx,
122 event_tx,
123 TuiOptions {
124 theme,
125 placeholder,
126 surface_preference,
127 inline_rows,
128 show_logs: log::is_tui_log_capture_enabled(),
129 log_theme: None,
130 event_callback,
131 focus_callback,
132 active_pty_sessions,
133 input_activity_counter,
134 keyboard_protocol,
135 workspace_root,
136 slash_commands,
137 appearance,
138 app_name: app_name.clone(),
139 },
140 )
141 .await
142 {
143 let error_msg = error.to_string();
144 if error_msg.contains("stdin is not a terminal") {
145 eprintln!("Error: Interactive TUI requires a proper terminal.");
146 if let Some(hint) = non_interactive_hint.as_deref() {
147 eprintln!("{}", hint);
148 } else {
149 eprintln!("Use a non-interactive mode in your host app for piped input.");
150 }
151 } else {
152 eprintln!("Error: TUI startup failed: {:#}", error);
153 }
154 tracing::error!(%error, "inline session terminated unexpectedly");
155 }
156 });
157
158 Ok(InlineSession {
159 handle: InlineHandle { sender: command_tx },
160 events: event_rx,
161 })
162}