use std::io::{BufRead, Write};
use cap_rs::core::{AgentEvent, ClientFrame, Content, TextChannel};
use cap_rs::driver::Driver;
use cap_rs::driver::stream_json::ClaudeCodeDriver;
use tokio::sync::mpsc;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
tracing_subscriber::fmt()
.with_env_filter(
tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| "info".into()),
)
.with_target(false)
.with_writer(std::io::stderr)
.init();
let cwd = std::env::current_dir()?;
eprintln!("│ cap-rs · ClaudeCodeDriver · session mode");
eprintln!("│ cwd: {}", cwd.display());
eprintln!("│ type 'exit' or '/quit' or Ctrl+D to end");
eprintln!();
let mut driver = ClaudeCodeDriver::builder(&cwd).spawn().await?;
let (stdin_tx, mut stdin_rx) = mpsc::channel::<String>(8);
std::thread::spawn(move || {
let stdin = std::io::stdin();
let mut handle = stdin.lock();
let mut line = String::new();
loop {
line.clear();
match handle.read_line(&mut line) {
Ok(0) => {
drop(stdin_tx);
return;
}
Ok(_) => {
if stdin_tx.blocking_send(line.trim_end().to_string()).is_err() {
return;
}
}
Err(_) => return,
}
}
});
let mut turn = 0u32;
let mut pending_turns: u32 = 0;
let mut want_exit = false;
let mut stdin_open = true;
let mut waiting_for_prompt = true;
let mut current_msg_id = String::new();
let mut session_id = String::new();
print_prompt(turn);
loop {
let read_stdin = stdin_open && !want_exit;
tokio::select! {
event = driver.next_event() => {
match event {
Some(AgentEvent::Ready { session_id: sid, model }) => {
session_id = sid.clone();
eprintln!("\n● ready session={} model={}",
short(&sid, 8),
model.as_deref().unwrap_or("?"));
}
Some(AgentEvent::TextChunk { msg_id, text, channel }) => {
if waiting_for_prompt {
print!("\n│ ");
waiting_for_prompt = false;
}
if msg_id != current_msg_id && !current_msg_id.is_empty() {
print!("\n│ ");
}
current_msg_id = msg_id;
match channel {
TextChannel::Assistant => print!("{text}"),
TextChannel::System => print!("[sys: {text}]"),
_ => print!("{text}"),
}
std::io::stdout().flush().ok();
}
Some(AgentEvent::ToolCallStart { name, input, .. }) => {
print!("\n⚙ {} {}", name, short(&input.to_string(), 60));
std::io::stdout().flush().ok();
}
Some(AgentEvent::ToolCallEnd { output, is_error, .. }) => {
let status = if is_error { "ERR" } else { "ok" };
print!("\n → {status}: {}", short(&output, 60));
std::io::stdout().flush().ok();
}
Some(AgentEvent::Done { stop_reason, usage }) => {
println!();
println!(
"● turn {} done · stop={:?} · in/out {}/{} · ${:.6}",
turn,
stop_reason,
usage.input_tokens,
usage.output_tokens,
usage.cost_usd_estimate.unwrap_or(0.0),
);
println!();
turn += 1;
pending_turns = pending_turns.saturating_sub(1);
current_msg_id.clear();
waiting_for_prompt = true;
if want_exit && pending_turns == 0 {
driver.finish_input();
} else if !want_exit {
print_prompt(turn);
}
}
Some(AgentEvent::Error { code, message }) => {
eprintln!("\n✗ {code}: {message}");
}
Some(other) => {
eprintln!("· {other:?}");
}
None => {
println!("● claude exited (session={})", short(&session_id, 8));
break;
}
}
}
line = stdin_rx.recv(), if read_stdin => {
match line {
Some(prompt) if prompt == "exit" || prompt == "/quit" || prompt == "quit" => {
want_exit = true;
if pending_turns == 0 {
eprintln!("[bye] closing session…");
driver.finish_input();
} else {
eprintln!("[bye] waiting for {} pending turn(s)…", pending_turns);
}
}
Some(prompt) if prompt.is_empty() => {
print_prompt(turn);
}
Some(prompt) => {
pending_turns += 1;
driver.send(ClientFrame::Prompt {
content: vec![Content::Text(prompt)],
}).await?;
}
None => {
stdin_open = false;
want_exit = true;
if pending_turns == 0 {
eprintln!("[stdin closed] closing session…");
driver.finish_input();
} else {
eprintln!("[stdin closed] waiting for {} pending turn(s)…", pending_turns);
}
}
}
}
}
}
driver.shutdown().await?;
Ok(())
}
fn print_prompt(turn: u32) {
print!("you ({turn})> ");
std::io::stdout().flush().ok();
}
fn short(s: &str, n: usize) -> String {
let s = s.trim().replace('\n', " ");
if s.chars().count() <= n {
s
} else {
let truncated: String = s.chars().take(n).collect();
format!("{truncated}…")
}
}