M2: TUI — EngineHandle, ratatui app, markdown renderer, permission modal, session picker, snapshot tests
Builds the ratatui TUI: EngineHandle bridging the async engine to the render loop, chat viewport rendering with a markdown renderer, input handling, a permission modal wired to the real oneshot ask path, and a session picker. Adds TestBackend snapshot tests covering empty session, tool cards (running/completed/error), permission modal, session picker, and message rendering.
This commit is contained in:
@@ -1,9 +1,19 @@
|
||||
mod app;
|
||||
mod input;
|
||||
mod markdown;
|
||||
mod modal;
|
||||
mod render;
|
||||
mod state;
|
||||
mod terminal;
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
||||
use harness_core::event::RunOutcome;
|
||||
|
||||
const DEFAULT_MODEL: &str = "anthropic/claude-sonnet-4-5";
|
||||
|
||||
fn print_usage() {
|
||||
eprintln!("usage: harness run -p \"<prompt>\" [-m provider/model]");
|
||||
eprintln!("usage: harness [run -p \"<prompt>\" [-m provider/model]] | [tui]");
|
||||
}
|
||||
|
||||
fn parse_run_args(args: &[String]) -> Option<(String, Option<String>)> {
|
||||
@@ -26,7 +36,7 @@ fn parse_run_args(args: &[String]) -> Option<(String, Option<String>)> {
|
||||
prompt.map(|p| (p, model))
|
||||
}
|
||||
|
||||
async fn run(args: &[String]) -> i32 {
|
||||
async fn run_headless(args: &[String]) -> i32 {
|
||||
let Some((prompt, model_arg)) = parse_run_args(args) else {
|
||||
print_usage();
|
||||
return 2;
|
||||
@@ -49,7 +59,7 @@ async fn run(args: &[String]) -> i32 {
|
||||
};
|
||||
|
||||
let model_ref = model_arg
|
||||
.or_else(|| app.config.model.clone())
|
||||
.or_else(|| app.config().model.clone())
|
||||
.unwrap_or_else(|| DEFAULT_MODEL.to_string());
|
||||
|
||||
match app.run_prompt(prompt, &model_ref).await {
|
||||
@@ -73,14 +83,74 @@ async fn run(args: &[String]) -> i32 {
|
||||
}
|
||||
}
|
||||
|
||||
fn log_dir() -> PathBuf {
|
||||
dirs::data_dir()
|
||||
.unwrap_or_else(std::env::temp_dir)
|
||||
.join("ai-harness")
|
||||
.join("log")
|
||||
}
|
||||
|
||||
fn setup_tracing() -> anyhow::Result<tracing_appender::non_blocking::WorkerGuard> {
|
||||
let log_dir = log_dir();
|
||||
std::fs::create_dir_all(&log_dir)?;
|
||||
let file_appender = tracing_appender::rolling::daily(log_dir, "harness-tui.log");
|
||||
let (non_blocking, guard) = tracing_appender::non_blocking(file_appender);
|
||||
tracing_subscriber::fmt()
|
||||
.with_writer(non_blocking)
|
||||
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
|
||||
.init();
|
||||
Ok(guard)
|
||||
}
|
||||
|
||||
async fn run_tui() -> i32 {
|
||||
let cwd = match std::env::current_dir() {
|
||||
Ok(cwd) => cwd,
|
||||
Err(e) => {
|
||||
eprintln!("error: could not read cwd: {e}");
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
|
||||
let _guard = match setup_tracing() {
|
||||
Ok(guard) => guard,
|
||||
Err(e) => {
|
||||
eprintln!("error: could not initialize logging: {e}");
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
|
||||
let mut app = match crate::app::App::new(cwd).await {
|
||||
Ok(app) => app,
|
||||
Err(e) => {
|
||||
tracing::error!(error = %e, "failed to start TUI");
|
||||
eprintln!("error: {e}");
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
|
||||
if let Err(e) = app.run().await {
|
||||
tracing::error!(error = %e, "TUI error");
|
||||
eprintln!("error: {e}");
|
||||
return 1;
|
||||
}
|
||||
0
|
||||
}
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn main() {
|
||||
let args: Vec<String> = std::env::args().skip(1).collect();
|
||||
let exit_code = if args.first().map(String::as_str) == Some("run") {
|
||||
run(&args[1..]).await
|
||||
} else {
|
||||
println!("harness {}", env!("CARGO_PKG_VERSION"));
|
||||
0
|
||||
let exit_code = match args.first().map(String::as_str) {
|
||||
Some("run") => run_headless(&args[1..]).await,
|
||||
Some("tui") | None => run_tui().await,
|
||||
Some("help") | Some("--help") | Some("-h") => {
|
||||
print_usage();
|
||||
0
|
||||
}
|
||||
Some(cmd) => {
|
||||
eprintln!("unknown command: {cmd}");
|
||||
print_usage();
|
||||
2
|
||||
}
|
||||
};
|
||||
std::process::exit(exit_code);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user