Skip to main content

saya_cli/
app.rs

1use crate::{
2    cli::{Cli, Command, ConfigCommand, ConnectionCommand},
3    commands, config, interactive,
4};
5use std::{io::IsTerminal, path::Path};
6
7pub fn run(cli: Cli) -> i32 {
8    match dispatch(cli) {
9        Ok(code) => code,
10        Err(error) => {
11            eprintln!("Error: {error}");
12            2
13        }
14    }
15}
16
17fn dispatch(cli: Cli) -> Result<i32, Box<dyn std::error::Error>> {
18    let Some(command) = cli.command.clone() else {
19        if cli.options.non_interactive {
20            return Err("non-interactive mode requires a subcommand".into());
21        }
22        return interactive::run(cli);
23    };
24    if matches!(
25        &command,
26        Command::Config {
27            command: ConfigCommand::Init
28        }
29    ) {
30        return commands::run_config_init(cli.options.format.into());
31    }
32    // `--verbose` seeds the extraction-boundary trace before any turn runs.
33    // Until now the flag was declared and read nowhere, so passing it did
34    // nothing and said nothing — a small dishonesty in the one surface a user
35    // reaches for when memory "didn't record".
36    if cli.options.verbose {
37        crate::agent::extraction_trace::enable();
38    }
39    let options = command_options(&cli.options, &command);
40    let runtime = config::runtime::load(&options, Path::new("."))?;
41    let approval = config::runtime::approval_mode(&options)?;
42    let format = config::runtime::format_name(&options, &runtime.resolved);
43    let can_prompt = !options.non_interactive && std::io::stdin().is_terminal();
44    tokio::runtime::Builder::new_current_thread()
45        .enable_all()
46        .build()?
47        .block_on(commands::run(
48            command,
49            &runtime,
50            format,
51            approval,
52            can_prompt,
53            options.include_profiles.clone(),
54        ))
55}
56
57fn command_options(
58    options: &crate::cli::GlobalOptions,
59    command: &Command,
60) -> crate::cli::GlobalOptions {
61    let mut options = options.clone();
62    if options.profile.is_none() {
63        let profile = match command {
64            Command::Connection {
65                command:
66                    ConnectionCommand::Test { profile_name }
67                    | ConnectionCommand::Schema { profile_name, .. },
68            } => Some(profile_name.clone()),
69            _ => None,
70        };
71        options.profile = profile;
72    }
73    options
74}