use clap::Args;
use std::path::{Path, PathBuf};
use std::sync::Arc;
use auv_cli_invoke::{InvokeCliParse, InvokeRequest, command::TargetPolicy};
#[derive(Clone, Debug, Args)]
#[command(disable_help_flag = true)]
pub struct InvokeArgs {
#[arg(trailing_var_arg = true, allow_hyphen_values = true)]
pub arguments: Vec<String>,
}
pub async fn run(args: InvokeArgs, selection: &auv::selection::RootSelection, project_root: &Path) -> Result<i32, String> {
let mut arguments = vec!["invoke".to_string()];
arguments.extend(args.arguments);
let parsed = auv_cli_invoke::parse_invoke_args(&arguments)?;
match parsed {
InvokeCliParse::Help { command_id } => {
let registry = auv_cli_invoke::default_registry();
if let Some(command_id) = command_id {
let command = registry
.resolve(&command_id)
.ok_or_else(|| format!("unknown command {command_id}; use `auv invoke --help` to inspect available entries"))?;
print!("{}", auv_cli_invoke::render_command_help(command));
} else {
print!("{}", auv_cli_invoke::render_help_index(®istry));
}
Ok(0)
}
InvokeCliParse::Invoke {
command_id,
target,
inputs,
typed_args,
store_root,
dry_run,
output,
} => {
execute(
InvokeRequest {
command_id,
target,
inputs,
dry_run,
},
typed_args,
store_root,
output,
selection,
project_root,
)
.await
}
}
}
async fn execute(
request: InvokeRequest,
typed_args: auv_cli_invoke::TypedInvokeArgs,
store_root: Option<PathBuf>,
output: auv_cli_invoke::InvokeOutputOptions,
selection: &auv::selection::RootSelection,
project_root: &Path,
) -> Result<i32, String> {
let authority = build_tracing(project_root, store_root.as_ref())?;
let registry = auv_cli_invoke::default_registry();
let command = registry.resolve(&request.command_id).cloned().ok_or_else(|| format!("unknown invoke command: {}", request.command_id))?;
let selected_context = if !selection.is_empty() && (!request.dry_run || command.target == TargetPolicy::OptionalKeyboard) {
Some(crate::commands::plugin::resolve_invoke_context(selection).await?)
} else {
None
};
let remote_context = selected_context.as_ref().map(|resolved| resolved.context.clone());
let input = auv_cli_invoke::InvokeCommandInput {
command_id: request.command_id.clone(),
target: request.target,
inputs: request.inputs,
typed_args: Some(typed_args),
dry_run: request.dry_run,
cancellation: auv_cli_invoke::InvokeCancellation::new(),
};
let invoked_command = command.clone();
let run_id = tracing_run_id(selected_context.as_ref())?;
let root = auv_tracing::dispatcher::with_default(&authority.dispatch, || auv_tracing::Context::root(run_id));
let future = root.in_scope(|| async move {
auv_tracing::emit_event!(InvokeFrontendLifecycle { frontend: "cli" });
match remote_context {
Some(context) => Box::pin(auv_cli_invoke::runner::invoke(input, context)).await,
None => invoked_command.invoke(input).await,
}
});
let mut direct_result = root.instrument(future).await;
if let Some(context) = selected_context
&& let Err(error) = context.finish(direct_result.is_ok()).await
{
if direct_result.is_ok() {
direct_result = Err(error.into());
} else {
eprintln!("warning: failed to finalize the selected invoke Run: {error}");
}
}
if let Some(failure) = authority.dispatch.flush().await.err().map(|error| error.to_string()) {
eprintln!("warning: invoke recording failure for run {run_id}: {failure}");
}
let artifact_paths = direct_result
.as_ref()
.ok()
.into_iter()
.flat_map(auv_cli_invoke::InvokeCommandOutput::artifacts)
.map(|metadata| (metadata.uri().clone(), authority.store.artifact_path(metadata)))
.collect::<Vec<_>>();
let result = auv_cli_invoke::InvokeResult::from_command_result(run_id, &command, direct_result).with_artifact_paths(artifact_paths);
Ok(auv_cli_invoke::render_invoke_result(&result, output)?.exit_code)
}
#[derive(Clone)]
struct CliTracing {
dispatch: auv_tracing::Dispatch,
store: Arc<auv_tracing::FileTracingStore>,
}
fn build_tracing(project_root: &Path, explicit: Option<&PathBuf>) -> Result<CliTracing, String> {
let root = explicit.cloned().unwrap_or_else(|| project_root.join(".auv").join("store"));
let store = Arc::new(
auv_tracing::FileTracingStore::open(&root).map_err(|error| format!("failed to open tracing store {}: {error}", root.display()))?,
);
let dispatch =
auv_tracing::configure().tracing_store(store.clone()).build().map_err(|error| format!("failed to configure invoke tracing: {error}"))?;
Ok(CliTracing { dispatch, store })
}
#[derive(serde::Serialize)]
struct InvokeFrontendLifecycle {
frontend: &'static str,
}
impl auv_tracing::EventPayload for InvokeFrontendLifecycle {
const NAME: &'static str = "auv.frontend.lifecycle";
const VERSION: u32 = 1;
}
fn tracing_run_id(selected: Option<&crate::commands::plugin::ResolvedExecutionContext>) -> Result<auv_tracing::RunId, String> {
let Some(control_run_id) = selected.and_then(|selected| selected.context.run_id.as_deref()) else {
return Ok(auv_tracing::RunId::new());
};
let value = control_run_id.strip_prefix("run_").unwrap_or(control_run_id);
let uuid = uuid::Uuid::parse_str(value)
.map_err(|error| format!("selected Run ID {control_run_id:?} cannot be projected into the tracing Run identity: {error}"))?;
uuid
.to_string()
.parse()
.map_err(|error| format!("selected Run ID {control_run_id:?} cannot be projected into the tracing Run identity: {error}"))
}