use anyhow::Result;
use clap::Parser;
use std::path::PathBuf;
#[derive(Parser, Debug, Clone)]
pub struct Args {
#[arg(trailing_var_arg = true, value_name = "TASK")]
pub tasks: Vec<String>,
#[arg(short, long, default_value = ".")]
pub dir: PathBuf,
#[arg(short, long, default_value = "4")]
pub concurrency: usize,
#[arg(short, long)]
pub file: Option<PathBuf>,
#[arg(long)]
pub files: Option<String>,
#[arg(long)]
pub prompt: Option<String>,
#[arg(short, long)]
pub output: Option<PathBuf>,
#[arg(long)]
pub yolo: bool,
}
pub(crate) async fn run(args: Args) -> Result<()> {
let mut tasks = args.tasks;
if let Some(file) = args.file {
let content = tokio::fs::read_to_string(&file).await?;
for line in content.lines() {
let trimmed = line.trim();
if !trimmed.is_empty() && !trimmed.starts_with('#') {
tasks.push(trimmed.to_string());
}
}
}
if let Some(ref pattern) = args.files {
let template = args
.prompt
.as_deref()
.ok_or_else(|| anyhow::anyhow!("--prompt is required when using --files"))?;
let paths = glob::glob(pattern)?
.filter_map(Result::ok)
.filter(|p| p.is_file());
for path in paths {
let prompt = template.replace("{path}", &path.to_string_lossy());
tasks.push(prompt);
}
}
if tasks.is_empty() {
anyhow::bail!(
"No tasks provided. Pass tasks as arguments, use --file, or use --files with --prompt."
);
}
if !args.yolo {
let total = tasks.len();
println!();
println!(
"⚡ Ultrawork: about to run {total} tasks with concurrency {}",
args.concurrency
);
println!(" First 3 tasks:");
for (i, task) in tasks.iter().take(3).enumerate() {
println!(
" {}. {}",
i + 1,
task.chars().take(80).collect::<String>()
);
}
if total > 3 {
println!(" ... and {} more", total - 3);
}
println!();
println!("Press Enter to continue or Ctrl-C to cancel");
let mut buf = String::new();
std::io::stdin().read_line(&mut buf)?;
}
crate::runtime::ultrawork::run_ultrawork(tasks, &args.dir, args.concurrency, args.output)
.await?;
Ok(())
}