1use std::io::{self, Write};
4
5use anyhow::Result;
6use clap::Parser;
7use crossterm::{
8 event::{self, Event, KeyCode, KeyModifiers},
9 terminal::{disable_raw_mode, enable_raw_mode},
10};
11
12#[derive(Parser)]
14pub struct ChatCommand {
15 #[arg(long)]
17 pub model: Option<String>,
18}
19
20impl ChatCommand {
21 pub async fn execute(self) -> Result<()> {
23 let ai_info = crate::utils::preflight::check_ai_credentials(self.model.as_deref())?;
24 eprintln!(
25 "Connected to {} (model: {})",
26 ai_info.provider, ai_info.model
27 );
28 eprintln!("Enter to send, Shift+Enter for newline, Ctrl+D to exit.\n");
29
30 let client = crate::claude::create_default_claude_client(self.model, None)?;
31
32 chat_loop(&client).await
33 }
34}
35
36async fn chat_loop(client: &crate::claude::client::ClaudeClient) -> Result<()> {
37 let system_prompt = "You are a helpful assistant.";
38
39 loop {
40 let input = match read_user_input() {
41 Ok(Some(text)) => text,
42 Ok(None) => {
43 eprintln!("\nGoodbye!");
44 break;
45 }
46 Err(e) => {
47 eprintln!("\nInput error: {e}");
48 break;
49 }
50 };
51
52 let trimmed = input.trim();
53 if trimmed.is_empty() {
54 continue;
55 }
56
57 let response = client.send_message(system_prompt, trimmed).await?;
58 println!("{response}\n");
59 }
60
61 Ok(())
62}
63
64struct RawModeGuard;
66
67impl Drop for RawModeGuard {
68 fn drop(&mut self) {
69 let _ = disable_raw_mode();
70 }
71}
72
73fn read_user_input() -> Result<Option<String>> {
77 eprint!("> ");
78 io::stderr().flush()?;
79
80 enable_raw_mode()?;
81 let _guard = RawModeGuard;
82
83 let mut buffer = String::new();
84
85 loop {
86 if let Event::Key(key_event) = event::read()? {
87 match key_event.code {
88 KeyCode::Enter => {
89 if key_event.modifiers.contains(KeyModifiers::SHIFT) {
90 buffer.push('\n');
91 eprint!("\r\n... ");
92 io::stderr().flush()?;
93 } else {
94 eprint!("\r\n");
95 io::stderr().flush()?;
96 return Ok(Some(buffer));
97 }
98 }
99 KeyCode::Char('d') if key_event.modifiers.contains(KeyModifiers::CONTROL) => {
100 if buffer.is_empty() {
101 return Ok(None);
102 }
103 eprint!("\r\n");
104 io::stderr().flush()?;
105 return Ok(Some(buffer));
106 }
107 KeyCode::Char('c') if key_event.modifiers.contains(KeyModifiers::CONTROL) => {
108 return Ok(None);
109 }
110 KeyCode::Char(c) => {
111 buffer.push(c);
112 eprint!("{c}");
113 io::stderr().flush()?;
114 }
115 KeyCode::Backspace if buffer.pop().is_some() => {
116 eprint!("\x08 \x08");
117 io::stderr().flush()?;
118 }
119 _ => {}
120 }
121 }
122 }
123}