use std::path::PathBuf;
use anyhow::{Result, bail};
use clap::Args;
use codewhale_config::ConfigStore;
#[derive(Debug, Args)]
pub(crate) struct DispatchArgs {
#[arg(value_name = "PROMPT")]
prompt: Vec<String>,
#[arg(long, value_name = "ID")]
show: Option<String>,
#[arg(long, value_name = "ID")]
cancel: Option<String>,
#[arg(long)]
agent: Option<String>,
#[arg(long)]
seconds: Option<u64>,
#[arg(long, value_name = "PATH")]
context_file: Option<PathBuf>,
#[arg(long = "file", value_name = "FILE_ID@VERSION")]
file_refs: Vec<String>,
#[arg(long)]
yes: bool,
#[arg(long)]
confirm_eu_compute: bool,
#[arg(long)]
operation_key: Option<String>,
#[arg(long)]
message_id: Option<String>,
}
fn unknown_job(id: &str) -> anyhow::Error {
anyhow::anyhow!("Unknown job {id}: it is not a Codewhale account job ID.")
}
pub(crate) fn run(
args: DispatchArgs,
profile: Option<&str>,
config: &mut ConfigStore,
) -> Result<()> {
use crate::cloud::{AccountDispatch, run_dispatch};
let modes = [
args.show.is_some(),
args.cancel.is_some(),
!args.prompt.is_empty(),
];
if modes.iter().filter(|mode| **mode).count() > 1 {
bail!("Use one of: a prompt, --show <id>, or --cancel <id>.");
}
if let Some(id) = args.show.as_deref() {
if id.starts_with("cloud_") {
return Err(unknown_job(id));
}
return run_dispatch(AccountDispatch::Status(id.to_string()), profile, config);
}
if let Some(id) = args.cancel.as_deref() {
if id.starts_with("cloud_") {
return Err(unknown_job(id));
}
return run_dispatch(AccountDispatch::Cancel(id.to_string()), profile, config);
}
if args.prompt.is_empty() {
bail!("Give a task to dispatch, or use --show <id> or --cancel <id>.");
}
run_dispatch(
AccountDispatch::Run {
objective: args.prompt.join(" "),
agent: args.agent,
seconds: args.seconds,
context_file: args.context_file,
file_refs: args.file_refs,
operation_key: args.operation_key,
message_id: args.message_id,
yes: args.yes,
confirm_eu_compute: args.confirm_eu_compute,
},
profile,
config,
)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Cli, Commands};
use clap::Parser;
fn args(argv: &[&str]) -> DispatchArgs {
let cli = Cli::try_parse_from(argv).unwrap();
let Some(Commands::Dispatch(args)) = cli.command else {
panic!("expected dispatch command");
};
args
}
#[test]
fn parses_the_obvious_dispatch_command() {
let parsed = args(&[
"codewhale",
"dispatch",
"fix",
"the",
"flake",
"--seconds",
"600",
]);
assert_eq!(parsed.prompt, ["fix", "the", "flake"]);
assert_eq!(parsed.seconds, Some(600));
assert!(Cli::try_parse_from(["codewhale", "dispatch", "--remote", "github"]).is_err());
}
#[test]
fn rendered_help_carries_no_provider_brand() {
use clap::CommandFactory;
let help = Cli::command()
.find_subcommand_mut("dispatch")
.expect("dispatch subcommand exists")
.render_help()
.to_string();
assert!(help.contains("--show"), "{help}");
assert!(!help.contains("--own-sandbox"), "{help}");
for banned in ["Daytona", "daytona"] {
assert!(
!help.contains(banned),
"--help must not brand the operator: {banned}"
);
}
}
}