mod approver;
mod bridge;
mod cli;
mod duration_arg;
mod exit;
mod fingerprint;
mod local;
mod route;
mod run;
mod serve;
mod shorthand;
use std::process::ExitCode;
use clap::{CommandFactory, Parser};
use crate::{
cli::{Cli, Command},
exit::{EXIT_FAILED, EXIT_USAGE},
fingerprint::execute_fingerprint,
local::ClientError,
route::{Route, route},
run::execute_run,
serve::{serve_acp, serve_bridge},
shorthand::normalize,
};
#[tokio::main]
async fn main() -> ExitCode {
let cli = match Cli::try_parse_from(normalize(std::env::args_os())) {
Ok(cli) => cli,
Err(error) => {
let code = error.exit_code();
eprint!("{error}");
if code == i32::from(EXIT_USAGE) {
eprintln!(
"next: use `basis spawn <PROMPT>` for work or `basis serve --acp` for ACP"
);
}
return ExitCode::from(code as u8);
}
};
match cli.command {
Some(Command::Spawn(args)) => match route(&args, local::has_current_task()) {
Route::Attended => match execute_run(args).await {
Ok(code) => code,
Err(message) => {
eprintln!("basis: {message}");
eprintln!("next: retry with `basis spawn <PROMPT>` or use `basis --help`");
ExitCode::from(EXIT_FAILED)
}
},
route => {
let structured = args.json;
match local::spawn(args, route == Route::Attach).await {
Ok(code) => code,
Err(error) => error.render(structured, "basis spawn <PROMPT>"),
}
}
},
Some(Command::Send(args)) => {
let structured = args.json;
lifecycle_result(
local::send(args).await,
"basis send <ID> <MESSAGE>",
structured,
)
}
Some(Command::Ask(args)) => {
let structured = args.json;
lifecycle_result(
local::ask(args).await,
"basis ask <ID> <MESSAGE>",
structured,
)
}
Some(Command::Wait(args)) => {
let structured = args.json;
lifecycle_result(local::wait(args).await, "basis wait <ID>", structured)
}
Some(Command::Cancel(args)) => {
let structured = args.json;
lifecycle_result(local::cancel(args).await, "basis cancel <ID>", structured)
}
Some(Command::Watch(args)) => {
let structured = args.json;
lifecycle_result(local::watch(args).await, "basis watch <ID>", structured)
}
Some(Command::Inbox(args)) => {
let structured = args.json;
lifecycle_result(local::inbox(args).await, "basis inbox <ID>", structured)
}
Some(Command::Fingerprint(args)) => match execute_fingerprint(args) {
Ok(code) => code,
Err(message) => {
eprintln!("basis: {message}");
eprintln!(
"next: retry with `basis fingerprint -C <DIR>` after checking the workspace path"
);
ExitCode::from(EXIT_FAILED)
}
},
Some(Command::Serve(args)) if args.acp && args.has_bridge_options() => {
eprintln!("basis: bridge options cannot be used with `basis serve --acp`");
eprintln!("next: remove the bridge flags or use `basis serve --bridge`");
ExitCode::from(EXIT_USAGE)
}
Some(Command::Serve(args)) if args.acp => serve_acp(args.acp_args).await,
Some(Command::Serve(args)) => serve_bridge(args.acp_args, args.bridge_args).await,
None => usage(),
}
}
fn lifecycle_result(
result: Result<ExitCode, ClientError>,
command: &str,
structured: bool,
) -> ExitCode {
match result {
Ok(code) => code,
Err(error) => error.render(structured, command),
}
}
fn usage() -> ExitCode {
eprintln!(
"basis: a prompt or command is required; try `basis serve --acp` or `basis spawn <PROMPT>`"
);
eprintln!("{}", Cli::command().render_usage());
eprintln!(
"next: use `basis spawn <PROMPT>` for work, `basis fingerprint` for the workspace hash, or `basis serve --acp` for ACP"
);
ExitCode::from(EXIT_USAGE)
}