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::i18n::{t, Mensagem};
15use crate::output::{exibir_bibliotecas_formatado, exibir_documentacao_formatada};
16use crate::storage::{
17 carregar_chaves_api, cmd_keys_add, cmd_keys_clear, cmd_keys_export, cmd_keys_import,
18 cmd_keys_list, cmd_keys_path, cmd_keys_remove,
19};
20
21#[derive(Debug, Parser)]
25#[command(
26 name = "context7",
27 version,
28 about = "CLI client for the Context7 API (bilingual EN/PT)",
29 long_about = None,
30)]
31pub struct Cli {
32 #[arg(long, global = true, env = "CONTEXT7_LANG")]
34 pub lang: Option<String>,
35
36 #[arg(long, global = true)]
38 pub json: bool,
39
40 #[command(subcommand)]
42 pub comando: Comando,
43}
44
45#[derive(Debug, Subcommand)]
47pub enum Comando {
48 #[command(alias = "lib", alias = "search")]
50 Library {
51 nome: String,
53 query: Option<String>,
55 },
56
57 #[command(alias = "doc", alias = "context")]
59 Docs {
60 library_id: String,
62
63 #[arg(long)]
65 query: Option<String>,
66
67 #[arg(long, conflicts_with = "json")]
69 text: bool,
70 },
71
72 #[command(alias = "key")]
74 Keys {
75 #[command(subcommand)]
77 operacao: OperacaoKeys,
78 },
79}
80
81#[derive(Debug, Subcommand)]
83pub enum OperacaoKeys {
84 Add {
86 chave: String,
88 },
89 List,
91 Remove {
93 indice: usize,
95 },
96 Clear {
98 #[arg(long)]
100 yes: bool,
101 },
102 Path,
104 Import {
106 arquivo: std::path::PathBuf,
108 },
109 Export,
111}
112
113pub fn executar_keys(operacao: OperacaoKeys) -> Result<()> {
117 match operacao {
118 OperacaoKeys::Add { chave } => cmd_keys_add(&chave),
119 OperacaoKeys::List => cmd_keys_list(),
120 OperacaoKeys::Remove { indice } => cmd_keys_remove(indice),
121 OperacaoKeys::Clear { yes } => cmd_keys_clear(yes),
122 OperacaoKeys::Path => cmd_keys_path(),
123 OperacaoKeys::Import { arquivo } => cmd_keys_import(&arquivo),
124 OperacaoKeys::Export => cmd_keys_export(),
125 }
126}
127
128pub async fn executar_library(nome: String, query: Option<String>, json: bool) -> Result<()> {
130 info!("Buscando biblioteca: {}", nome);
131
132 let chaves = carregar_chaves_api()?;
133 let cliente = criar_cliente_http()?;
134
135 info!(
136 "Iniciando context7 com {} chaves de API disponíveis",
137 chaves.len()
138 );
139
140 let query_contexto = query.as_deref().unwrap_or(&nome).to_string();
142
143 let cliente_arc = std::sync::Arc::new(cliente);
144 let nome_clone = nome.clone();
145 let query_clone = query_contexto.clone();
146 let resultado = executar_com_retry(&chaves, move |chave| {
147 let c = std::sync::Arc::clone(&cliente_arc);
148 let n = nome_clone.clone();
149 let q = query_clone.clone();
150 async move { buscar_biblioteca(&c, &chave, &n, &q).await }
151 })
152 .await
153 .with_context(|| format!("{} '{}'", t(Mensagem::FalhaBuscarBiblioteca), nome))?;
154
155 if json {
156 println!(
157 "{}",
158 serde_json::to_string_pretty(&resultado.results)
159 .with_context(|| t(Mensagem::FalhaSerializarJson))?
160 );
161 } else {
162 exibir_bibliotecas_formatado(&resultado.results);
163 }
164 Ok(())
165}
166
167pub async fn executar_docs(
169 library_id: String,
170 query: Option<String>,
171 text: bool,
172 json: bool,
173) -> Result<()> {
174 info!("Buscando documentação para: {}", library_id);
175
176 let chaves = carregar_chaves_api()?;
177 let cliente = criar_cliente_http()?;
178
179 info!(
180 "Iniciando context7 com {} chaves de API disponíveis",
181 chaves.len()
182 );
183
184 let cliente_arc = std::sync::Arc::new(cliente);
185 let id_clone = library_id.clone();
186 let query_clone = query.clone();
187
188 if text {
189 let texto = executar_com_retry(&chaves, move |chave| {
191 let c = std::sync::Arc::clone(&cliente_arc);
192 let id = id_clone.clone();
193 let q = query_clone.clone();
194 async move { buscar_documentacao_texto(&c, &chave, &id, q.as_deref()).await }
195 })
196 .await
197 .with_context(|| format!("{} '{}'", t(Mensagem::FalhaBuscarDocumentacao), library_id))?;
198
199 println!("{}", texto);
200 return Ok(());
201 }
202
203 let resultado = 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(&c, &chave, &id, q.as_deref()).await }
209 })
210 .await
211 .with_context(|| format!("{} '{}'", t(Mensagem::FalhaBuscarDocumentacao), library_id))?;
212
213 if json {
214 println!(
215 "{}",
216 serde_json::to_string_pretty(&resultado)
217 .with_context(|| t(Mensagem::FalhaSerializarDocs))?
218 );
219 } else {
220 exibir_documentacao_formatada(&resultado);
221 }
222 Ok(())
223}