1use anyhow::{Context, Result};
7use clap::{Parser, Subcommand};
8use tracing::info;
9
10use crate::api::{
11 buscar_biblioteca, buscar_documentacao, buscar_documentacao_texto, criar_cliente_http,
12 executar_com_retry,
13};
14use crate::errors::ErroContext7;
15use crate::i18n::{t, Mensagem};
16use crate::output::{
17 exibir_bibliotecas_formatado, exibir_dica_biblioteca_nao_encontrada,
18 exibir_documentacao_formatada,
19};
20use crate::storage::{
21 carregar_chaves_api, cmd_keys_add, cmd_keys_clear, cmd_keys_export, cmd_keys_import,
22 cmd_keys_list, cmd_keys_path, cmd_keys_remove,
23};
24
25#[derive(Debug, Parser)]
29#[command(
30 name = "context7",
31 version,
32 about = "CLI client for the Context7 API (bilingual EN/PT)",
33 long_about = None,
34)]
35pub struct Cli {
36 #[arg(long, global = true, env = "CONTEXT7_LANG")]
38 pub lang: Option<String>,
39
40 #[arg(long, global = true)]
42 pub json: bool,
43
44 #[command(subcommand)]
46 pub comando: Comando,
47}
48
49#[derive(Debug, Subcommand)]
51pub enum Comando {
52 #[command(alias = "lib", alias = "search")]
54 Library {
55 name: String,
57 query: Option<String>,
59 },
60
61 #[command(alias = "doc", alias = "context")]
63 Docs {
64 library_id: String,
66
67 #[arg(short = 'q', long)]
69 query: Option<String>,
70
71 #[arg(long, conflicts_with = "json")]
73 text: bool,
74 },
75
76 #[command(alias = "key")]
78 Keys {
79 #[command(subcommand)]
81 operacao: OperacaoKeys,
82 },
83}
84
85#[derive(Debug, Subcommand)]
87pub enum OperacaoKeys {
88 Add {
90 key: String,
92 },
93 List,
95 Remove {
97 index: usize,
99 },
100 Clear {
102 #[arg(long)]
104 yes: bool,
105 },
106 Path,
108 Import {
110 file: std::path::PathBuf,
112 },
113 Export,
115}
116
117pub fn executar_keys(operacao: OperacaoKeys, json: bool) -> Result<()> {
121 match operacao {
122 OperacaoKeys::Add { key } => cmd_keys_add(&key),
123 OperacaoKeys::List => cmd_keys_list(json),
124 OperacaoKeys::Remove { index } => cmd_keys_remove(index),
125 OperacaoKeys::Clear { yes } => cmd_keys_clear(yes),
126 OperacaoKeys::Path => cmd_keys_path(),
127 OperacaoKeys::Import { file } => cmd_keys_import(&file),
128 OperacaoKeys::Export => cmd_keys_export(),
129 }
130}
131
132pub async fn executar_library(name: String, query: Option<String>, json: bool) -> Result<()> {
134 info!("Buscando biblioteca: {}", name);
135
136 let chaves = carregar_chaves_api()?;
137 let cliente = criar_cliente_http()?;
138
139 info!(
140 "Iniciando context7 com {} chaves de API disponíveis",
141 chaves.len()
142 );
143
144 let query_contexto = query.as_deref().unwrap_or(&name).to_string();
146
147 let cliente_arc = std::sync::Arc::new(cliente);
148 let name_clone = name.clone();
149 let query_clone = query_contexto.clone();
150 let resultado = executar_com_retry(&chaves, move |chave| {
151 let c = std::sync::Arc::clone(&cliente_arc);
152 let n = name_clone.clone();
153 let q = query_clone.clone();
154 async move { buscar_biblioteca(&c, &chave, &n, &q).await }
155 })
156 .await;
157
158 if let Err(ref e) = resultado {
160 if let Some(ErroContext7::BibliotecaNaoEncontrada { .. }) = e.downcast_ref::<ErroContext7>()
161 {
162 exibir_dica_biblioteca_nao_encontrada();
163 }
164 }
165
166 let resultado =
167 resultado.with_context(|| format!("{} '{}'", t(Mensagem::FalhaBuscarBiblioteca), name))?;
168
169 if json {
170 println!(
171 "{}",
172 serde_json::to_string_pretty(&resultado.results)
173 .with_context(|| t(Mensagem::FalhaSerializarJson))?
174 );
175 } else {
176 exibir_bibliotecas_formatado(&resultado.results);
177 }
178 Ok(())
179}
180
181pub async fn executar_docs(
183 library_id: String,
184 query: Option<String>,
185 text: bool,
186 json: bool,
187) -> Result<()> {
188 info!("Buscando documentação para: {}", library_id);
189
190 let chaves = carregar_chaves_api()?;
191 let cliente = criar_cliente_http()?;
192
193 info!(
194 "Iniciando context7 com {} chaves de API disponíveis",
195 chaves.len()
196 );
197
198 let cliente_arc = std::sync::Arc::new(cliente);
199 let id_clone = library_id.clone();
200 let query_clone = query.clone();
201
202 if text {
203 let texto = executar_com_retry(&chaves, move |chave| {
205 let c = std::sync::Arc::clone(&cliente_arc);
206 let id = id_clone.clone();
207 let q = query_clone.clone();
208 async move { buscar_documentacao_texto(&c, &chave, &id, q.as_deref()).await }
209 })
210 .await;
211
212 if let Err(ref e) = texto {
214 if let Some(ErroContext7::BibliotecaNaoEncontrada { .. }) =
215 e.downcast_ref::<ErroContext7>()
216 {
217 exibir_dica_biblioteca_nao_encontrada();
218 }
219 }
220
221 let texto = texto.with_context(|| {
222 format!("{} '{}'", t(Mensagem::FalhaBuscarDocumentacao), library_id)
223 })?;
224
225 println!("{}", texto);
226 return Ok(());
227 }
228
229 let resultado = executar_com_retry(&chaves, move |chave| {
231 let c = std::sync::Arc::clone(&cliente_arc);
232 let id = id_clone.clone();
233 let q = query_clone.clone();
234 async move { buscar_documentacao(&c, &chave, &id, q.as_deref()).await }
235 })
236 .await;
237
238 if let Err(ref e) = resultado {
240 if let Some(ErroContext7::BibliotecaNaoEncontrada { .. }) = e.downcast_ref::<ErroContext7>()
241 {
242 exibir_dica_biblioteca_nao_encontrada();
243 }
244 }
245
246 let resultado = resultado
247 .with_context(|| format!("{} '{}'", t(Mensagem::FalhaBuscarDocumentacao), library_id))?;
248
249 if json {
250 println!(
251 "{}",
252 serde_json::to_string_pretty(&resultado)
253 .with_context(|| t(Mensagem::FalhaSerializarDocs))?
254 );
255 } else {
256 exibir_documentacao_formatada(&resultado);
257 }
258 Ok(())
259}