use std::{env, time::Duration};
use basis::{ModelSelector, NullSink, RunOutcome, RunReport, RunSpec, Snapshot, Workspace};
const DEADLINE: Duration = Duration::from_secs(300);
const TOOL_BUDGET: usize = 40;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut args = env::args().skip(1);
let path = args.next().unwrap_or_else(|| ".".to_string());
let prompt = args
.next()
.unwrap_or_else(|| "Summarize what changed here, in one sentence.".to_string());
let interval = Duration::from_secs(
args.next()
.and_then(|raw| raw.parse().ok())
.unwrap_or(60_u64),
);
let workspace = Workspace::builder(&path)
.with_model(selected_model())
.open()
.await?;
let spec = RunSpec::new(prompt)
.with_deadline(DEADLINE)
.with_tool_budget(TOOL_BUDGET);
println!(
"watching {} every {interval:?} with {}",
workspace.root().display(),
workspace.model()
);
let mut baseline = None;
loop {
let snapshot = workspace.fingerprint();
if let Snapshot::Known(observed) = &snapshot
&& Some(*observed) == baseline
{
println!("unchanged at {} — skipping", observed.hex());
} else {
let report = workspace.prepare(spec.clone())?.execute(NullSink).await?;
println!("ran: {}", summarize(&report));
if report.succeeded() {
baseline = workspace.fingerprint().fingerprint();
}
}
tokio::time::sleep(interval).await;
}
}
fn selected_model() -> ModelSelector {
match env::var("BASIS_MODEL") {
Ok(id) => ModelSelector::Id(id),
Err(_) => ModelSelector::NewestAvailable,
}
}
fn summarize<S>(report: &RunReport<S>) -> String {
match (&report.outcome, report.stopped_by) {
(RunOutcome::Ok, _) => report
.final_message
.as_deref()
.unwrap_or("(no message)")
.trim()
.to_string(),
(RunOutcome::Error { .. }, Some(bound)) => format!("stopped by {bound:?}"),
(RunOutcome::Error { message }, None) => format!("failed: {message}"),
}
}