use std::io::Write;
use clap::{ArgAction, CommandFactory, Parser, Subcommand};
use clap_complete::Shell;
use crate::commands;
use crate::context::{Ctx, GlobalArgs};
use crate::errors::CliError;
use crate::output::Format;
#[derive(Debug, Parser)]
#[command(
name = "qn",
version,
about = "Command-line interface for the Quicknode API.",
long_about = "qn lets you manage Quicknode endpoints, streams, webhooks, and the KV store from the terminal.\n\n\
Use `qn <noun> --help` (e.g. `qn endpoint --help`) for command details.\n\n\
Authentication is resolved in this order: --api-key flag, then the config file\n\
(--config-file path if given, else ~/.config/qn/config.toml). Run `qn auth login`\n\
to save a key the first time.",
propagate_version = true,
disable_help_subcommand = true,
// The auto-generated -h/-V land under a separate "Options" heading; we
// re-declare them below so they group with the other global flags.
disable_help_flag = true,
disable_version_flag = true,
after_help = "Examples:\n \
qn auth login\n \
qn endpoint create --chain ethereum --network mainnet\n \
qn endpoint list -o json\n \
qn endpoint logs ep-1234 --from 1h\n \
qn chain list\n\n\
AI agents: run 'qn agent context' for a machine-readable usage guide.",
// Group the global flags under their own heading in every subcommand's
// --help, so command-specific flags surface first under "Options".
next_help_heading = "Global options"
)]
pub struct Cli {
#[arg(long, global = true)]
pub api_key: Option<String>,
#[arg(long, global = true, value_name = "PATH")]
pub config_file: Option<std::path::PathBuf>,
#[arg(short = 'o', long = "format", global = true, value_enum)]
pub format: Option<Format>,
#[arg(long, global = true)]
pub no_color: bool,
#[arg(short, long, global = true)]
pub quiet: bool,
#[arg(short = 'w', long = "wide", global = true)]
pub wide: bool,
#[arg(short, long, global = true)]
pub verbose: bool,
#[arg(long, global = true)]
pub no_input: bool,
#[arg(long, global = true, default_value_t = 3, value_name = "N")]
pub retries: u32,
#[arg(short = 'y', long = "yes", global = true, action = ArgAction::Count)]
pub yes: u8,
#[arg(long, global = true, hide = true)]
pub base_url: Option<String>,
#[arg(short = 'h', long, global = true, action = ArgAction::Help)]
pub help: Option<bool>,
#[arg(short = 'V', long, global = true, action = ArgAction::Version)]
pub version: Option<bool>,
#[command(subcommand)]
pub command: Command,
}
#[derive(Debug, Subcommand)]
pub enum Command {
Auth(commands::auth::Args),
Agent(commands::agent::Args),
#[command(visible_alias = "endpoints")]
Endpoint(commands::endpoint::Args),
#[command(visible_alias = "teams")]
Team(commands::team::Args),
Usage(commands::usage::Args),
Metrics(commands::metrics::Args),
#[command(visible_alias = "chains")]
Chain(commands::chain::Args),
Billing(commands::billing::Args),
#[command(visible_alias = "streams")]
Stream(commands::stream::Args),
#[command(visible_alias = "webhooks")]
Webhook(commands::webhook::Args),
Kv(commands::kv::Args),
#[command(after_long_help = "### bash\n\n \
First, ensure that you install `bash-completion` using your package manager.\n\n \
After, add this to your `~/.bashrc`:\n\n \
eval \"$(qn completions bash)\"\n\n\
### zsh\n\n \
Homebrew already creates this `_qn` file for you on `brew install`. To\n \
set it up manually, generate the script into a directory on your\n \
`$fpath` (Apple Silicon shown; Intel brew uses\n \
`/usr/local/share/zsh/site-functions`):\n\n \
qn completions zsh > /opt/homebrew/share/zsh/site-functions/_qn\n\n \
Ensure that the following is present in your `~/.zshrc`:\n\n \
autoload -U compinit\n \
compinit\n\n \
See the zsh manual for details:\n \
https://zsh.sourceforge.io/Doc/Release/Completion-System.html\n\n\
### fish\n\n \
Generate a `qn.fish` completion script:\n\n \
qn completions fish > ~/.config/fish/completions/qn.fish\n\n\
### PowerShell\n\n \
Add the following line to your profile script (`$PROFILE`):\n\n \
qn completions powershell | Out-String | Invoke-Expression\n\n \
Or append the generated script so it loads each session:\n\n \
qn completions powershell >> $PROFILE")]
Completions {
#[arg(value_enum)]
shell: Shell,
},
}
impl Cli {
pub fn global_args(&self) -> GlobalArgs {
GlobalArgs {
api_key: self.api_key.clone(),
config_file: self.config_file.clone(),
format: self.format,
wide: self.wide,
no_color: self.no_color,
quiet: self.quiet,
verbose: self.verbose,
no_input: self.no_input,
yes_count: self.yes,
retries: self.retries,
base_url: self.base_url.clone(),
}
}
pub async fn run(self) -> Result<(), CliError> {
let global = self.global_args();
match self.command {
Command::Completions { shell } => {
let mut cmd = <Self as CommandFactory>::command();
let bin_name = cmd.get_name().to_string();
let mut out = std::io::stdout().lock();
clap_complete::generate(shell, &mut cmd, bin_name, &mut out);
out.flush()?;
Ok(())
}
Command::Auth(args) => commands::auth::run(args, global).await,
Command::Agent(args) => commands::agent::run(args, global).await,
Command::Endpoint(args) => {
commands::endpoint::run(args, Ctx::from_global(global)?).await
}
Command::Team(args) => commands::team::run(args, Ctx::from_global(global)?).await,
Command::Usage(args) => commands::usage::run(args, Ctx::from_global(global)?).await,
Command::Metrics(args) => commands::metrics::run(args, Ctx::from_global(global)?).await,
Command::Chain(args) => commands::chain::run(args, Ctx::from_global(global)?).await,
Command::Billing(args) => commands::billing::run(args, Ctx::from_global(global)?).await,
Command::Stream(args) => commands::stream::run(args, Ctx::from_global(global)?).await,
Command::Webhook(args) => commands::webhook::run(args, Ctx::from_global(global)?).await,
Command::Kv(args) => commands::kv::run(args, Ctx::from_global(global)?).await,
}
}
}