1use anyhow::{Context, Result};
7use clap::{Parser, Subcommand};
8use tracing::info;
9
10use crate::api::{buscar_biblioteca, buscar_documentacao, criar_cliente_http, executar_com_retry};
11use crate::output::{exibir_bibliotecas_formatado, exibir_documentacao_formatada};
12use crate::storage::{
13 carregar_chaves_api, cmd_keys_add, cmd_keys_clear, cmd_keys_export, cmd_keys_import,
14 cmd_keys_list, cmd_keys_path, cmd_keys_remove,
15};
16
17#[derive(Debug, Parser)]
21#[command(
22 name = "context7",
23 version,
24 about = "CLI client for the Context7 API (bilingual EN/PT)",
25 long_about = None,
26)]
27pub struct Cli {
28 #[arg(long, global = true, env = "CONTEXT7_LANG")]
30 pub lang: Option<String>,
31
32 #[arg(long, global = true)]
34 pub json: bool,
35
36 #[command(subcommand)]
38 pub comando: Comando,
39}
40
41#[derive(Debug, Subcommand)]
43pub enum Comando {
44 #[command(alias = "lib", alias = "search")]
46 Library {
47 nome: String,
49 query: Option<String>,
51 },
52
53 #[command(alias = "doc", alias = "context")]
55 Docs {
56 library_id: String,
58
59 #[arg(long)]
61 query: Option<String>,
62
63 #[arg(long, conflicts_with = "json")]
65 text: bool,
66 },
67
68 #[command(alias = "key")]
70 Keys {
71 #[command(subcommand)]
73 operacao: OperacaoKeys,
74 },
75}
76
77#[derive(Debug, Subcommand)]
79pub enum OperacaoKeys {
80 Add {
82 chave: String,
84 },
85 List,
87 Remove {
89 indice: usize,
91 },
92 Clear {
94 #[arg(long)]
96 yes: bool,
97 },
98 Path,
100 Import {
102 arquivo: std::path::PathBuf,
104 },
105 Export,
107}
108
109pub fn executar_keys(operacao: OperacaoKeys) -> Result<()> {
113 match operacao {
114 OperacaoKeys::Add { chave } => cmd_keys_add(&chave),
115 OperacaoKeys::List => cmd_keys_list(),
116 OperacaoKeys::Remove { indice } => cmd_keys_remove(indice),
117 OperacaoKeys::Clear { yes } => cmd_keys_clear(yes),
118 OperacaoKeys::Path => cmd_keys_path(),
119 OperacaoKeys::Import { arquivo } => cmd_keys_import(&arquivo),
120 OperacaoKeys::Export => cmd_keys_export(),
121 }
122}
123
124pub async fn executar_library(nome: String, query: Option<String>, json: bool) -> Result<()> {
126 info!("Buscando biblioteca: {}", nome);
127
128 let chaves = carregar_chaves_api()?;
129 let cliente = criar_cliente_http()?;
130
131 info!(
132 "Iniciando context7 com {} chaves de API disponíveis",
133 chaves.len()
134 );
135
136 let query_contexto = query.as_deref().unwrap_or(&nome).to_string();
138
139 let cliente_arc = std::sync::Arc::new(cliente);
140 let nome_clone = nome.clone();
141 let query_clone = query_contexto.clone();
142 let resultado = executar_com_retry(&chaves, move |chave| {
143 let c = std::sync::Arc::clone(&cliente_arc);
144 let n = nome_clone.clone();
145 let q = query_clone.clone();
146 async move { buscar_biblioteca(&c, &chave, &n, &q).await }
147 })
148 .await
149 .with_context(|| format!("Falha ao buscar biblioteca '{}'", nome))?;
150
151 if json {
152 println!(
153 "{}",
154 serde_json::to_string_pretty(&resultado.results)
155 .context("Falha ao serializar resultados para JSON")?
156 );
157 } else {
158 exibir_bibliotecas_formatado(&resultado.results);
159 }
160 Ok(())
161}
162
163pub async fn executar_docs(
165 library_id: String,
166 query: Option<String>,
167 text: bool,
168 json: bool,
169) -> Result<()> {
170 info!("Buscando documentação para: {}", library_id);
171
172 let chaves = carregar_chaves_api()?;
173 let cliente = criar_cliente_http()?;
174
175 info!(
176 "Iniciando context7 com {} chaves de API disponíveis",
177 chaves.len()
178 );
179
180 let cliente_arc = std::sync::Arc::new(cliente);
181 let id_clone = library_id.clone();
182 let query_clone = query.clone();
183 let resultado = executar_com_retry(&chaves, move |chave| {
184 let c = std::sync::Arc::clone(&cliente_arc);
185 let id = id_clone.clone();
186 let q = query_clone.clone();
187 async move { buscar_documentacao(&c, &chave, &id, q.as_deref(), text).await }
188 })
189 .await
190 .with_context(|| format!("Falha ao buscar documentação para '{}'", library_id))?;
191
192 if json {
193 println!(
194 "{}",
195 serde_json::to_string_pretty(&resultado)
196 .context("Falha ao serializar documentação para JSON")?
197 );
198 } else if text {
199 if let Some(conteudo) = &resultado.content {
200 println!("{}", conteudo);
201 } else if let Some(snippets) = &resultado.snippets {
202 for s in snippets {
203 println!("{}", s.content);
204 }
205 }
206 } else {
207 exibir_documentacao_formatada(&resultado);
208 }
209 Ok(())
210}