Skip to main content

talon_cli/command/
mod.rs

1//! Command dispatch for the Talon CLI scaffold.
2
3mod ask;
4mod ask_client;
5mod ask_sources;
6mod changes;
7mod init;
8mod inspect;
9mod meta;
10mod read;
11mod recall;
12mod related;
13mod search;
14pub(crate) mod secrets;
15mod status;
16mod sync;
17
18use crate::cli::{Cli, Commands};
19use crate::mcp::state::{ConfigState, McpServerState};
20use crate::mcp::transport::run_jsonrpc_loop_with_state;
21use crate::output::OutputMode;
22use eyre::{Result, bail};
23use std::io::{self, BufReader};
24use std::sync::Arc;
25
26/// Runs the selected command.
27///
28/// # Errors
29///
30/// Returns an error for invalid command input or not-yet-implemented behavior.
31pub async fn run(cli: &Cli) -> Result<()> {
32    if cli.skill {
33        use std::io::Write as _;
34        writeln!(io::stdout().lock(), "{}", crate::SKILL_MD)?;
35        return Ok(());
36    }
37
38    let Some(cmd) = &cli.command else {
39        bail!("missing command; try `talon --help`");
40    };
41
42    match cmd {
43        Commands::Mcp => {
44            crate::mcp::diagnostics::install_panic_hook();
45            let _ready_guard = crate::mcp::diagnostics::ready_guard();
46            let config = crate::config::load_config(cli.config_file.as_deref())?;
47            let vault_path = config.vault_path.clone();
48            let db_path = config.db_path.clone();
49            let config_path = config.config_file_path.clone();
50            let config_state = ConfigState {
51                config,
52                config_path,
53                vault_path,
54                db_path,
55            };
56            let state = McpServerState::new(config_state);
57            let vault_path_for_watcher = state.config.vault_path.clone();
58            crate::mcp::background::watcher::spawn_watcher(
59                vault_path_for_watcher,
60                Arc::clone(&state),
61            );
62            crate::mcp::background::embed::spawn_embed_ticker(Arc::clone(&state));
63            let stdin = io::stdin();
64            let stdout = io::stdout();
65            // block_in_place allows the synchronous JSON-RPC loop to drop
66            // tokio-backed resources (reqwest clients, inference pools) without
67            // panicking inside the outer async executor.
68            let outcome = tokio::task::block_in_place(|| {
69                run_jsonrpc_loop_with_state(BufReader::new(stdin.lock()), stdout.lock(), &state)
70            })?;
71            let _ = outcome;
72            crate::mcp::diagnostics::clear_ready();
73            Ok(())
74        }
75        Commands::Init(args) => init::emit(args),
76        Commands::Search(args) => search::emit(args, cli).await,
77        Commands::Ask(args) => ask::emit(args, cli).await,
78        Commands::Read(args) => read::emit(args, cli).await,
79        Commands::Sync(args) => sync::emit(args, cli).await,
80        Commands::Related(args) => related::emit(args, cli).await,
81        Commands::Status(args) => status::emit(args, cli),
82        Commands::Meta(args) => meta::emit(args, cli).await,
83        Commands::Changes(args) => changes::emit(args, cli).await,
84        Commands::Inspect(args) => inspect::emit(args, cli).await,
85        Commands::Recall(args) => recall::emit(args, cli).await,
86        Commands::Secrets(args) => secrets::emit(&args.subcommand),
87    }
88}
89
90pub(super) const fn output_mode(cli: &Cli) -> OutputMode {
91    if cli.agent {
92        OutputMode::Agent
93    } else if cli.json {
94        OutputMode::JsonPretty
95    } else {
96        OutputMode::Human
97    }
98}
99
100pub(super) fn should_spin(cli: &Cli) -> bool {
101    !cli.agent && !cli.json && crate::platform::stderr_is_tty()
102}