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, exibir_json_resultados, exibir_texto_plano,
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 #[command(alias = "completion")]
86 Completions {
87 shell: clap_complete::Shell,
89 },
90}
91
92#[derive(Debug, Subcommand)]
94pub enum OperacaoKeys {
95 Add {
97 key: String,
99 },
100 List,
102 Remove {
104 index: usize,
106 },
107 Clear {
109 #[arg(long)]
111 yes: bool,
112 },
113 Path,
115 Import {
117 file: std::path::PathBuf,
119 },
120 Export,
122}
123
124pub fn executar_keys(operacao: OperacaoKeys, json: bool) -> Result<()> {
128 match operacao {
129 OperacaoKeys::Add { key } => cmd_keys_add(&key),
130 OperacaoKeys::List => cmd_keys_list(json),
131 OperacaoKeys::Remove { index } => cmd_keys_remove(index),
132 OperacaoKeys::Clear { yes } => cmd_keys_clear(yes),
133 OperacaoKeys::Path => cmd_keys_path(),
134 OperacaoKeys::Import { file } => cmd_keys_import(&file),
135 OperacaoKeys::Export => cmd_keys_export(),
136 }
137}
138
139pub async fn executar_library(name: String, query: Option<String>, json: bool) -> Result<()> {
141 info!("Buscando biblioteca: {}", name);
142
143 let chaves = carregar_chaves_api()?;
144 let cliente = criar_cliente_http()?;
145
146 info!(
147 "Iniciando context7 com {} chaves de API disponíveis",
148 chaves.len()
149 );
150
151 let query_contexto = query.as_deref().unwrap_or(&name).to_string();
153
154 let cliente_arc = std::sync::Arc::new(cliente);
155 let name_clone = name.clone();
156 let query_clone = query_contexto.clone();
157 let resultado = executar_com_retry(&chaves, move |chave| {
158 let c = std::sync::Arc::clone(&cliente_arc);
159 let n = name_clone.clone();
160 let q = query_clone.clone();
161 async move { buscar_biblioteca(&c, &chave, &n, &q).await }
162 })
163 .await;
164
165 if let Err(ref e) = resultado {
167 if let Some(ErroContext7::BibliotecaNaoEncontrada { .. }) = e.downcast_ref::<ErroContext7>()
168 {
169 exibir_dica_biblioteca_nao_encontrada();
170 }
171 }
172
173 let resultado =
174 resultado.with_context(|| format!("{} '{}'", t(Mensagem::FalhaBuscarBiblioteca), name))?;
175
176 if json {
177 exibir_json_resultados(
178 &serde_json::to_string_pretty(&resultado.results)
179 .with_context(|| t(Mensagem::FalhaSerializarJson))?,
180 );
181 } else {
182 exibir_bibliotecas_formatado(&resultado.results);
183 }
184 Ok(())
185}
186
187pub async fn executar_docs(
189 library_id: String,
190 query: Option<String>,
191 text: bool,
192 json: bool,
193) -> Result<()> {
194 info!("Buscando documentação para: {}", library_id);
195
196 let chaves = carregar_chaves_api()?;
197 let cliente = criar_cliente_http()?;
198
199 info!(
200 "Iniciando context7 com {} chaves de API disponíveis",
201 chaves.len()
202 );
203
204 let cliente_arc = std::sync::Arc::new(cliente);
205 let id_clone = library_id.clone();
206 let query_clone = query.clone();
207
208 if text {
209 let texto = executar_com_retry(&chaves, move |chave| {
211 let c = std::sync::Arc::clone(&cliente_arc);
212 let id = id_clone.clone();
213 let q = query_clone.clone();
214 async move { buscar_documentacao_texto(&c, &chave, &id, q.as_deref()).await }
215 })
216 .await;
217
218 if let Err(ref e) = texto {
220 if let Some(ErroContext7::BibliotecaNaoEncontrada { .. }) =
221 e.downcast_ref::<ErroContext7>()
222 {
223 exibir_dica_biblioteca_nao_encontrada();
224 }
225 }
226
227 let texto = texto.with_context(|| {
228 format!("{} '{}'", t(Mensagem::FalhaBuscarDocumentacao), library_id)
229 })?;
230
231 exibir_texto_plano(&texto);
232 return Ok(());
233 }
234
235 let resultado = executar_com_retry(&chaves, move |chave| {
237 let c = std::sync::Arc::clone(&cliente_arc);
238 let id = id_clone.clone();
239 let q = query_clone.clone();
240 async move { buscar_documentacao(&c, &chave, &id, q.as_deref()).await }
241 })
242 .await;
243
244 if let Err(ref e) = resultado {
246 if let Some(ErroContext7::BibliotecaNaoEncontrada { .. }) = e.downcast_ref::<ErroContext7>()
247 {
248 exibir_dica_biblioteca_nao_encontrada();
249 }
250 }
251
252 let resultado = resultado
253 .with_context(|| format!("{} '{}'", t(Mensagem::FalhaBuscarDocumentacao), library_id))?;
254
255 if json {
256 exibir_json_resultados(
257 &serde_json::to_string_pretty(&resultado)
258 .with_context(|| t(Mensagem::FalhaSerializarDocs))?,
259 );
260 } else {
261 exibir_documentacao_formatada(&resultado);
262 }
263 Ok(())
264}