11"""Input handling, completers, and prompt session for the CLI."""
22
3+ import asyncio
34import os
45import re
6+ import time
57from collections .abc import Callable
68from pathlib import Path
79
2325AT_MENTION_RE = re .compile (r"@(?P<path>(?:[^\s@]|(?<=\\)\s)*)$" )
2426SLASH_COMMAND_RE = re .compile (r"^/(?P<command>[a-z]*)$" )
2527
28+ EXIT_CONFIRM_WINDOW = 3.0
29+
2630
2731class FilePathCompleter (Completer ):
2832 """Activate filesystem completion only when cursor is after '@'."""
@@ -154,6 +158,16 @@ def toolbar() -> list[tuple[str, str]]:
154158
155159 parts .append ((base_class , base_msg ))
156160
161+ # Show exit confirmation hint if active
162+ hint_until = session_state .exit_hint_until
163+ if hint_until is not None :
164+ now = time .monotonic ()
165+ if now < hint_until :
166+ parts .append (("" , " | " ))
167+ parts .append (("class:toolbar-exit" , " Ctrl+C again to exit " ))
168+ else :
169+ session_state .exit_hint_until = None
170+
157171 return parts
158172
159173 return toolbar
@@ -168,6 +182,44 @@ def create_prompt_session(assistant_id: str, session_state: SessionState) -> Pro
168182 # Create key bindings
169183 kb = KeyBindings ()
170184
185+ @kb .add ("c-c" )
186+ def _ (event ):
187+ """Require double Ctrl+C within a short window to exit."""
188+ app = event .app
189+ now = time .monotonic ()
190+
191+ if session_state .exit_hint_until is not None and now < session_state .exit_hint_until :
192+ handle = session_state .exit_hint_handle
193+ if handle :
194+ handle .cancel ()
195+ session_state .exit_hint_handle = None
196+ session_state .exit_hint_until = None
197+ app .invalidate ()
198+ app .exit (exception = KeyboardInterrupt ())
199+ return
200+
201+ session_state .exit_hint_until = now + EXIT_CONFIRM_WINDOW
202+
203+ handle = session_state .exit_hint_handle
204+ if handle :
205+ handle .cancel ()
206+
207+ loop = asyncio .get_running_loop ()
208+ app_ref = app
209+
210+ def clear_hint ():
211+ if (
212+ session_state .exit_hint_until is not None
213+ and time .monotonic () >= session_state .exit_hint_until
214+ ):
215+ session_state .exit_hint_until = None
216+ session_state .exit_hint_handle = None
217+ app_ref .invalidate ()
218+
219+ session_state .exit_hint_handle = loop .call_later (EXIT_CONFIRM_WINDOW , clear_hint )
220+
221+ app .invalidate ()
222+
171223 # Bind Ctrl+T to toggle auto-approve
172224 @kb .add ("c-t" )
173225 def _ (event ):
@@ -240,6 +292,7 @@ def _(event):
240292 "bottom-toolbar" : "noreverse" , # Disable default reverse video
241293 "toolbar-green" : "bg:#10b981 #000000" , # Green for auto-accept ON
242294 "toolbar-orange" : "bg:#f59e0b #000000" , # Orange for manual accept
295+ "toolbar-exit" : "bg:#2563eb #ffffff" , # Blue for exit hint
243296 }
244297 )
245298
0 commit comments