mod cli;
use std::path::PathBuf;
use anyhow::Result;
use clap::Parser;
use coven::commands;
use coven::vcr::{Io, VcrContext};
use cli::{Cli, Command};
#[tokio::main]
async fn main() -> Result<()> {
let default_hook = std::panic::take_hook();
std::panic::set_hook(Box::new(move |info| {
crossterm::terminal::disable_raw_mode().ok();
default_hook(info);
}));
let cli = Cli::parse();
match cli.command {
Some(Command::Init) => {
commands::init::init()?;
}
Some(Command::Status) => {
commands::status::status()?;
}
Some(Command::Gc) => {
commands::gc::gc()?;
}
Some(Command::Ralph {
prompt,
iterations,
break_tag,
no_break,
show_thinking,
fork,
claude_args,
}) => {
if no_break && iterations == 0 {
anyhow::bail!("--no-break requires --iterations to prevent infinite looping");
}
let (mut io, vcr) = create_live_io();
commands::ralph::ralph(
commands::ralph::RalphConfig {
prompt,
iterations,
break_tag,
no_break,
show_thinking,
fork,
extra_args: claude_args,
working_dir: None,
},
&mut io,
&vcr,
std::io::stdout(),
)
.await?;
}
Some(Command::Worker {
branch,
worktree_base,
show_thinking,
fork,
claude_args,
}) => {
let base = match worktree_base {
Some(b) => b,
None => default_worktree_base()?,
};
let (mut io, vcr) = create_live_io();
commands::worker::worker(
commands::worker::WorkerConfig {
show_thinking,
branch,
worktree_base: base,
extra_args: claude_args,
working_dir: None,
fork,
},
&mut io,
&vcr,
std::io::stdout(),
)
.await?;
}
None => {
let (mut io, vcr) = create_live_io();
commands::run::run(
commands::run::RunConfig {
prompt: cli.prompt,
extra_args: cli.claude_args,
show_thinking: cli.show_thinking,
fork: cli.fork,
working_dir: None,
},
&mut io,
&vcr,
std::io::stdout(),
)
.await?;
}
}
Ok(())
}
fn create_live_io() -> (Io, VcrContext) {
use crossterm::event::EventStream;
use futures::StreamExt;
use tokio::sync::mpsc;
let (term_tx, term_rx) = mpsc::unbounded_channel();
let (_event_tx, event_rx) = mpsc::unbounded_channel();
tokio::spawn(async move {
let mut stream = EventStream::new();
while let Some(Ok(event)) = stream.next().await {
if term_tx.send(event).is_err() {
break;
}
}
});
let io = Io::new(event_rx, term_rx);
let vcr = VcrContext::live();
(io, vcr)
}
fn default_worktree_base() -> Result<PathBuf> {
let home = std::env::var("HOME").map_err(|_| {
anyhow::anyhow!("HOME not set; use --worktree-base to specify worktree location")
})?;
Ok(PathBuf::from(home).join(".coven").join("worktrees"))
}