M1: composition root and harness run -p debug command

Adds the harness-app composition root wiring config, providers, tools, and the engine loop together, plus the harness run -p "<prompt>" debug command in harness-tui::main for driving a one-shot prompt end-to-end from the CLI.
This commit is contained in:
2026-07-10 16:19:38 +02:00
parent 9ed278bcb5
commit d83f8c84b7
5 changed files with 320 additions and 3 deletions
+2
View File
@@ -10,6 +10,8 @@ path = "src/main.rs"
[dependencies]
harness-app = { workspace = true }
harness-core = { workspace = true }
tokio = { workspace = true }
[lints]
workspace = true
+85 -2
View File
@@ -1,3 +1,86 @@
fn main() {
println!("harness {}", env!("CARGO_PKG_VERSION"));
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]");
}
fn parse_run_args(args: &[String]) -> Option<(String, Option<String>)> {
let mut prompt = None;
let mut model = None;
let mut i = 0;
while i < args.len() {
match args[i].as_str() {
"-p" | "--prompt" => {
prompt = args.get(i + 1).cloned();
i += 2;
}
"-m" | "--model" => {
model = args.get(i + 1).cloned();
i += 2;
}
_ => i += 1,
}
}
prompt.map(|p| (p, model))
}
async fn run(args: &[String]) -> i32 {
let Some((prompt, model_arg)) = parse_run_args(args) else {
print_usage();
return 2;
};
let cwd = match std::env::current_dir() {
Ok(cwd) => cwd,
Err(e) => {
eprintln!("error: could not read cwd: {e}");
return 1;
}
};
let app = match harness_app::App::init(cwd) {
Ok(app) => app,
Err(e) => {
eprintln!("error: {e}");
return 1;
}
};
let model_ref = model_arg
.or_else(|| app.config.model.clone())
.unwrap_or_else(|| DEFAULT_MODEL.to_string());
match app.run_prompt(prompt, &model_ref).await {
Ok((RunOutcome::Stopped, session_id)) => {
let text = app.final_text(&session_id).await.unwrap_or_default();
println!("{text}");
0
}
Ok((RunOutcome::Aborted, _)) => {
eprintln!("run aborted");
1
}
Ok((RunOutcome::Errored { message }, _)) => {
eprintln!("error: {message}");
1
}
Err(e) => {
eprintln!("error: {e}");
1
}
}
}
#[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
};
std::process::exit(exit_code);
}