1use std::io::Write;
7
8use clap::{ArgAction, CommandFactory, Parser, Subcommand};
9use clap_complete::Shell;
10
11use crate::commands;
12use crate::context::{Ctx, GlobalArgs};
13use crate::errors::CliError;
14use crate::output::Format;
15
16#[derive(Debug, Parser)]
18#[command(
19 name = "qn",
20 version,
21 about = "Command-line interface for the Quicknode API.",
22 long_about = "qn lets you manage Quicknode endpoints, streams, webhooks, and the KV store from the terminal.\n\n\
23 Use `qn <noun> --help` (e.g. `qn endpoint --help`) for command details.\n\n\
24 Authentication is resolved in this order: --api-key flag, then the config file\n\
25 (--config-file path if given, else ~/.config/qn/config.toml). Run `qn auth login`\n\
26 to save a key the first time.",
27 propagate_version = true,
28 disable_help_subcommand = true,
29 disable_help_flag = true,
32 disable_version_flag = true,
33 after_help = "Examples:\n \
34 qn auth login\n \
35 qn endpoint create --chain ethereum --network mainnet\n \
36 qn endpoint list -o json\n \
37 qn endpoint logs ep-1234 --from 1h\n \
38 qn chain list\n\n\
39 AI agents: run 'qn agent context' for a machine-readable usage guide.",
40 next_help_heading = "Global options"
43)]
44pub struct Cli {
45 #[arg(long, global = true)]
47 pub api_key: Option<String>,
48
49 #[arg(long, global = true, value_name = "PATH")]
51 pub config_file: Option<std::path::PathBuf>,
52
53 #[arg(short = 'o', long = "format", global = true, value_enum)]
58 pub format: Option<Format>,
59
60 #[arg(long, global = true)]
62 pub no_color: bool,
63
64 #[arg(short, long, global = true)]
66 pub quiet: bool,
67
68 #[arg(short = 'w', long = "wide", global = true)]
72 pub wide: bool,
73
74 #[arg(short, long, global = true)]
76 pub verbose: bool,
77
78 #[arg(long, global = true)]
80 pub no_input: bool,
81
82 #[arg(long, global = true, default_value_t = 3, value_name = "N")]
86 pub retries: u32,
87
88 #[arg(short = 'y', long = "yes", global = true, action = ArgAction::Count)]
90 pub yes: u8,
91
92 #[arg(long, global = true, hide = true)]
95 pub base_url: Option<String>,
96
97 #[arg(long, global = true, hide = true)]
101 pub base_prefix: Option<String>,
102
103 #[arg(short = 'h', long, global = true, action = ArgAction::Help)]
105 pub help: Option<bool>,
106
107 #[arg(short = 'V', long, global = true, action = ArgAction::Version)]
109 pub version: Option<bool>,
110
111 #[command(subcommand)]
112 pub command: Command,
113}
114
115#[derive(Debug, Subcommand)]
116pub enum Command {
117 Auth(commands::auth::Args),
119
120 Agent(commands::agent::Args),
122
123 #[command(visible_alias = "endpoints")]
125 Endpoint(commands::endpoint::Args),
126
127 #[command(visible_alias = "teams")]
129 Team(commands::team::Args),
130
131 Usage(commands::usage::Args),
133
134 Metrics(commands::metrics::Args),
136
137 #[command(visible_alias = "chains")]
139 Chain(commands::chain::Args),
140
141 Billing(commands::billing::Args),
143
144 #[command(visible_alias = "streams")]
146 Stream(commands::stream::Args),
147
148 #[command(visible_alias = "webhooks")]
150 Webhook(commands::webhook::Args),
151
152 Kv(commands::kv::Args),
154
155 Sql(commands::sql::Args),
157
158 Rpc(commands::rpc::Args),
160
161 #[command(visible_alias = "wallets")]
163 Wallet(commands::wallet::Args),
164
165 #[command(name = "tooling-access")]
167 ToolingAccess(commands::tooling_access::Args),
168
169 #[command(after_long_help = "### bash\n\n \
180 First, ensure that you install `bash-completion` using your package manager.\n\n \
181 After, add this to your `~/.bashrc`:\n\n \
182 eval \"$(qn completions bash)\"\n\n\
183 ### zsh\n\n \
184 Homebrew already creates this `_qn` file for you on `brew install`. To\n \
185 set it up manually, generate the script into a directory on your\n \
186 `$fpath` (Apple Silicon shown; Intel brew uses\n \
187 `/usr/local/share/zsh/site-functions`):\n\n \
188 qn completions zsh > /opt/homebrew/share/zsh/site-functions/_qn\n\n \
189 Ensure that the following is present in your `~/.zshrc`:\n\n \
190 autoload -U compinit\n \
191 compinit\n\n \
192 See the zsh manual for details:\n \
193 https://zsh.sourceforge.io/Doc/Release/Completion-System.html\n\n\
194 ### fish\n\n \
195 Generate a `qn.fish` completion script:\n\n \
196 qn completions fish > ~/.config/fish/completions/qn.fish\n\n\
197 ### PowerShell\n\n \
198 Add the following line to your profile script (`$PROFILE`):\n\n \
199 qn completions powershell | Out-String | Invoke-Expression\n\n \
200 Or append the generated script so it loads each session:\n\n \
201 qn completions powershell >> $PROFILE")]
202 Completions {
203 #[arg(value_enum)]
205 shell: Shell,
206 },
207}
208
209impl Cli {
210 pub fn global_args(&self) -> GlobalArgs {
212 GlobalArgs {
213 api_key: self.api_key.clone(),
214 config_file: self.config_file.clone(),
215 format: self.format,
216 wide: self.wide,
217 no_color: self.no_color,
219 quiet: self.quiet,
220 verbose: self.verbose,
221 no_input: self.no_input,
222 yes_count: self.yes,
223 retries: self.retries,
224 base_url: self.base_url.clone(),
225 base_prefix: self.base_prefix.clone(),
226 }
227 }
228
229 pub async fn run(self) -> Result<(), CliError> {
235 let global = self.global_args();
236 match self.command {
237 Command::Completions { shell } => {
238 let mut cmd = <Self as CommandFactory>::command();
239 let bin_name = cmd.get_name().to_string();
240 let mut out = std::io::stdout().lock();
241 clap_complete::generate(shell, &mut cmd, bin_name, &mut out);
242 out.flush()?;
243 Ok(())
244 }
245 Command::Auth(args) => commands::auth::run(args, global).await,
246 Command::Agent(args) => commands::agent::run(args, global).await,
247 Command::Endpoint(args) => {
248 commands::endpoint::run(args, Ctx::from_global(global)?).await
249 }
250 Command::Team(args) => commands::team::run(args, Ctx::from_global(global)?).await,
251 Command::Usage(args) => commands::usage::run(args, Ctx::from_global(global)?).await,
252 Command::Metrics(args) => commands::metrics::run(args, Ctx::from_global(global)?).await,
253 Command::Chain(args) => commands::chain::run(args, Ctx::from_global(global)?).await,
254 Command::Billing(args) => commands::billing::run(args, Ctx::from_global(global)?).await,
255 Command::Stream(args) => commands::stream::run(args, Ctx::from_global(global)?).await,
256 Command::Webhook(args) => commands::webhook::run(args, Ctx::from_global(global)?).await,
257 Command::Kv(args) => commands::kv::run(args, Ctx::from_global(global)?).await,
258 Command::Sql(args) => commands::sql::run(args, Ctx::from_global(global)?).await,
259 Command::Rpc(args) => commands::rpc::run(args, global).await,
261 Command::Wallet(args) => {
263 commands::wallet::run(args, Ctx::from_global_keyless(global)?).await
264 }
265 Command::ToolingAccess(args) => {
266 commands::tooling_access::run(args, Ctx::from_global(global)?).await
267 }
268 }
269 }
270}