use super::{
DefaultRouteDispatch, Exit, GatesOnlyDispatch, dispatch_command, dispatch_default_route,
dispatch_do_workflow, dispatch_gates_only_route, finish_entrypoint, prepare_cli_output,
print_command_error,
};
use crate::cli::args::Cli;
use crate::cli::config_defaults::is_gates_only_route;
use crate::cli::entrypoint_checks::{
ensure_malvin_checks_for_command, ensure_malvin_checks_for_default_route,
ensure_malvin_checks_for_do_workflow, ensure_malvin_checks_for_gates_only_route,
};
use crate::do_flow::DoArgs;
fn parse_cli_args_or_exit(
args: impl IntoIterator<Item = impl Into<std::ffi::OsString> + Clone>,
) -> Result<(Cli, clap::ArgMatches), Exit> {
match crate::cli::config_defaults::parse_cli_with_config_defaults(args) {
Ok(parsed) => Ok(parsed),
Err(e) => {
use clap::error::ErrorKind;
let exit = match e.kind() {
ErrorKind::DisplayHelp | ErrorKind::DisplayVersion => Exit::Success,
_ => Exit::Failure,
};
let _ = e.print();
Err(exit)
}
}
}
fn entrypoint_doc_exit(cli: &Cli) -> Exit {
match crate::cli::command_docs::print_doc_for_cli(cli) {
Ok(()) => Exit::Success,
Err(e) => {
print_command_error(&e);
Exit::Failure
}
}
}
fn entrypoint_before_dispatch(cli: &Cli, matches: &clap::ArgMatches) -> Option<Exit> {
if cli.do_workflow && cli.command.is_some() {
print_command_error("`--do` cannot be combined with a subcommand");
return Some(Exit::Failure);
}
if let Some(msg) = reject_admin_with_workflow_only_flags(cli, matches) {
print_command_error(&msg);
return Some(Exit::Failure);
}
if cli.command.is_none()
&& cli.request.is_none()
&& !cli.shared.doc
&& !cli.do_workflow
&& !is_gates_only_route(cli)
{
let _ = crate::cli::commands_help::print_commands_only_help();
return Some(Exit::Success);
}
if let Some(exit) = super::entrypoint_short_help::entrypoint_request_missing_short_help(cli) {
return Some(exit);
}
if cli.shared.doc {
return Some(entrypoint_doc_exit(cli));
}
None
}
fn reject_admin_with_workflow_only_flags(cli: &Cli, matches: &clap::ArgMatches) -> Option<String> {
use crate::cli::config_defaults::global_flag_from_command_line;
const WORKFLOW_ONLY: &[(&str, &str)] = &[
("quiet", "--quiet / -q"),
("gates", "--gates / -g"),
("creative", "--creative"),
("no_kpop", "--no-kpop"),
("max_loops", "--max-loops"),
("max_hypotheses", "--max-hypotheses"),
("verbose", "--verbose / -v"),
("max_acp_retries", "--max-acp-retries"),
];
if !matches!(cli.command, Some(crate::cli::Commands::Admin(_))) {
return None;
}
for (id, flag) in WORKFLOW_ONLY {
if global_flag_from_command_line(matches, id) {
return Some(format!("{flag} cannot be combined with `admin`"));
}
}
None
}
fn entrypoint_preflight(cli: &Cli) -> Option<Exit> {
if cli.do_workflow {
return ensure_malvin_checks_for_do_workflow().err().map(|e| {
print_command_error(&e);
Exit::Failure
});
}
if let Some(command) = cli.command.as_ref() {
ensure_malvin_checks_for_command(command);
}
if is_gates_only_route(cli) {
return ensure_malvin_checks_for_gates_only_route().err().map(|e| {
print_command_error(&e);
Exit::Failure
});
}
if cli.request.is_some() {
return ensure_malvin_checks_for_default_route().err().map(|e| {
print_command_error(&e);
Exit::Failure
});
}
None
}
fn entrypoint_acquire_session() -> Result<(String, crate::SessionNameGuard), Exit> {
crate::acquire_session_name(None).map_err(|e| {
print_command_error(&e);
Exit::Failure
})
}
const fn default_route_needs_session_name(cli: &Cli) -> bool {
cli.command.is_none() && cli.request.is_some() && !cli.do_workflow
}
fn entrypoint_sweep_stale_acp_spawn_locks() {
let Ok(cwd) = std::env::current_dir() else {
return;
};
let chamber = crate::malvin_acp_spawn_chamber_dir(&cwd);
if !chamber.is_dir() {
return;
}
if let Err(e) = crate::acp_spawn_sweep::sweep_stale_acp_spawn_locks(&cwd) {
tracing::warn!(
target: "malvin::entrypoint",
error = %e,
"stale ACP spawn lock sweep failed; continuing"
);
}
}
fn run_entrypoint(cli: Cli, matches: clap::ArgMatches) -> Exit {
prepare_cli_output(&cli.shared);
if let Some(exit) = entrypoint_before_dispatch(&cli, &matches) {
return exit;
}
entrypoint_sweep_stale_acp_spawn_locks();
if let Some(exit) = entrypoint_preflight(&cli) {
return exit;
}
let needs_session =
cli.do_workflow || is_gates_only_route(&cli) || default_route_needs_session_name(&cli);
if needs_session {
let _session_name_guard = match entrypoint_acquire_session() {
Ok((session_name, guard)) => {
crate::set_active_acp_lock_slot(session_name);
guard
}
Err(exit) => return exit,
};
return dispatch_after_session(cli, matches);
}
dispatch_after_session(cli, matches)
}
fn dispatch_after_session(cli: Cli, matches: clap::ArgMatches) -> Exit {
use crate::cli::malvin_workflow::{MalvinWorkflow, malvin_workflow_from_cli};
let Some(workflow) = malvin_workflow_from_cli(cli) else {
return Exit::Success;
};
match workflow {
MalvinWorkflow::Do { request, shared } => {
finish_entrypoint(dispatch_do_workflow(DoArgs { request }, &shared))
}
MalvinWorkflow::Admin { admin, model } => finish_entrypoint(dispatch_command(
crate::cli::Commands::Admin(admin),
&model.canonical(),
&matches,
)),
MalvinWorkflow::DefaultRoute {
request,
mut shared,
mut router,
} => finish_entrypoint(dispatch_default_route(DefaultRouteDispatch {
request,
max_loops: router.max_loops,
max_hypotheses: router.max_hypotheses,
shared: &mut shared,
router: &mut router,
matches: &matches,
})),
MalvinWorkflow::GatesOnly {
mut shared,
mut router,
} => finish_entrypoint(dispatch_gates_only_route(GatesOnlyDispatch {
max_loops: router.max_loops,
max_hypotheses: router.max_hypotheses,
shared: &mut shared,
router: &mut router,
matches: &matches,
})),
}
}
pub fn entrypoint_from(
args: impl IntoIterator<Item = impl Into<std::ffi::OsString> + Clone>,
) -> Exit {
crate::init_from_env();
match parse_cli_args_or_exit(args) {
Ok((cli, matches)) => run_entrypoint(cli, matches),
Err(exit) => exit,
}
}