use crate::core::prelude::*;
use anyhow::Result;
use clap::{CommandFactory, FromArgMatches, Parser, Subcommand};
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
use crate::commands::{
api, ask, auth, build_auth, completions, gen_dispatch, generated, mcp, safety, skills,
transport,
};
use crate::logo;
use crate::schema;
use auth::AuthCommands;
#[derive(Parser)]
#[command(
name = "grr",
version = logo::version_banner(),
about = "Google tools from the terminal, at maximum performance"
)]
struct Cli {
#[command(subcommand)]
command: StaticCommands,
}
#[derive(Subcommand)]
enum StaticCommands {
#[command(subcommand, subcommand_required = true)]
Auth(auth::AuthCommands),
#[command(subcommand)]
Api(api::ApiCommands),
Mcp(mcp::McpArgs),
Transport(transport::TransportArgs),
Schema(schema::SchemaArgs),
Completions(completions::CompletionsArgs),
Ask(ask::AskArgs),
#[command(subcommand, subcommand_required = true)]
Skills(skills::SkillsCommands),
}
pub(crate) fn root_command() -> clap::Command {
Cli::command()
.subcommands(generated::tree::commands())
.args(safety::global_args())
}
pub async fn run() -> Result<()> {
tracing_subscriber::registry()
.with(tracing_subscriber::EnvFilter::new(
std::env::var("RUST_LOG").unwrap_or_else(|_| "info,quinn_udp=error".into()),
))
.with(tracing_subscriber::fmt::layer().with_writer(std::io::stderr))
.init();
let matches = root_command().get_matches();
let Some((name, sub)) = matches.subcommand() else {
anyhow::bail!("a subcommand is required; run `grr --help`");
};
if !matches!(name, "skills" | "completions")
&& let Some(summary) = skills::maybe_auto_migrate()
{
eprintln!("{summary}");
}
match name {
"schema" => {
let args = schema::SchemaArgs::from_arg_matches(sub)?;
schema::handle_schema_cmd(root_command(), args)?;
}
"completions" => {
let args = completions::CompletionsArgs::from_arg_matches(sub)?;
completions::handle_completions_cmd(root_command(), args)?;
}
"skills" => {
skills::handle_skills_cmd(skills::SkillsCommands::from_arg_matches(sub)?)?;
}
"auth" => {
let cmd = AuthCommands::from_arg_matches(sub)?;
if let AuthCommands::Setup(args) = &cmd {
return crate::commands::setup::handle_setup(args.clone()).await;
}
let config = ConfigLoader::load().await?;
let auth = build_auth(&config).await?;
auth::handle_auth_cmd(&auth, cmd).await?;
}
"transport" => {
let config = ConfigLoader::load().await?;
let auth = build_auth(&config).await?;
transport::handle_transport_cmd(auth, transport::TransportArgs::from_arg_matches(sub)?)
.await?;
}
"api" => {
let cmd = api::ApiCommands::from_arg_matches(sub)?;
if matches!(cmd, api::ApiCommands::Call(_)) {
let safety_args = safety::SafetyArgs::from_arg_matches(&matches)?;
let config = ConfigLoader::load().await?;
let auth = build_auth(&config).await.ok();
api::handle_api_cmd(
auth.as_ref(),
cmd,
&safety::SafetyProfile::from_args(&safety_args),
)
.await?;
} else {
api::handle_api_cmd(None, cmd, &safety::SafetyProfile::PERMISSIVE).await?;
}
}
"mcp" => {
let args = mcp::McpArgs::from_arg_matches(sub)?;
mcp::run_mcp(args).await?;
}
"ask" => {
let args = ask::AskArgs::from_arg_matches(sub)?;
let config = ConfigLoader::load().await?;
let auth = if args.run {
Some(build_auth(&config).await?)
} else {
None
};
let safety_args = safety::SafetyArgs::from_arg_matches(&matches)?;
ask::handle_ask(
&config,
auth.as_ref(),
args,
&safety::SafetyProfile::from_args(&safety_args),
)
.await?;
}
_ => {
let safety_args = safety::SafetyArgs::from_arg_matches(&matches)?;
let config = ConfigLoader::load().await?;
let auth = build_auth(&config).await.ok();
gen_dispatch::dispatch_with_profile(
&matches,
auth.as_ref(),
&safety::SafetyProfile::from_args(&safety_args),
)
.await?;
}
}
Ok(())
}