use anyhow::{Context, Result};
use clap::{Parser, Subcommand};
use tracing::info;
use crate::api::{
buscar_biblioteca, buscar_documentacao, buscar_documentacao_texto, criar_cliente_http,
executar_com_retry,
};
use crate::errors::ErroContext7;
use crate::i18n::{t, Mensagem};
use crate::output::{
exibir_bibliotecas_formatado, exibir_dica_biblioteca_nao_encontrada,
exibir_documentacao_formatada, exibir_json_resultados, exibir_texto_plano,
};
use crate::storage::{
carregar_chaves_api, cmd_keys_add, cmd_keys_clear, cmd_keys_export, cmd_keys_import,
cmd_keys_list, cmd_keys_path, cmd_keys_remove,
};
#[derive(Debug, Parser)]
#[command(
name = "context7",
version,
about = "CLI client for the Context7 API (bilingual EN/PT)",
long_about = None,
)]
pub struct Cli {
#[arg(long, global = true, env = "CONTEXT7_LANG")]
pub lang: Option<String>,
#[arg(long, global = true)]
pub json: bool,
#[command(subcommand)]
pub comando: Comando,
}
#[derive(Debug, Subcommand)]
pub enum Comando {
#[command(alias = "lib", alias = "search")]
Library {
name: String,
query: Option<String>,
},
#[command(alias = "doc", alias = "context")]
Docs {
library_id: String,
#[arg(short = 'q', long)]
query: Option<String>,
#[arg(long, conflicts_with = "json")]
text: bool,
},
#[command(alias = "key")]
Keys {
#[command(subcommand)]
operacao: OperacaoKeys,
},
#[command(alias = "completion")]
Completions {
shell: clap_complete::Shell,
},
}
#[derive(Debug, Subcommand)]
pub enum OperacaoKeys {
Add {
key: String,
},
List,
Remove {
index: usize,
},
Clear {
#[arg(long)]
yes: bool,
},
Path,
Import {
file: std::path::PathBuf,
},
Export,
}
pub fn executar_keys(operacao: OperacaoKeys, json: bool) -> Result<()> {
match operacao {
OperacaoKeys::Add { key } => cmd_keys_add(&key),
OperacaoKeys::List => cmd_keys_list(json),
OperacaoKeys::Remove { index } => cmd_keys_remove(index),
OperacaoKeys::Clear { yes } => cmd_keys_clear(yes),
OperacaoKeys::Path => cmd_keys_path(),
OperacaoKeys::Import { file } => cmd_keys_import(&file),
OperacaoKeys::Export => cmd_keys_export(),
}
}
pub async fn executar_library(name: String, query: Option<String>, json: bool) -> Result<()> {
info!("Buscando biblioteca: {}", name);
let chaves = carregar_chaves_api()?;
let cliente = criar_cliente_http()?;
info!(
"Iniciando context7 com {} chaves de API disponíveis",
chaves.len()
);
let query_contexto = query.as_deref().unwrap_or(&name).to_string();
let cliente_arc = std::sync::Arc::new(cliente);
let name_clone = name.clone();
let query_clone = query_contexto.clone();
let resultado = executar_com_retry(&chaves, move |chave| {
let c = std::sync::Arc::clone(&cliente_arc);
let n = name_clone.clone();
let q = query_clone.clone();
async move { buscar_biblioteca(&c, &chave, &n, &q).await }
})
.await;
if let Err(ref e) = resultado {
if let Some(ErroContext7::BibliotecaNaoEncontrada { .. }) = e.downcast_ref::<ErroContext7>()
{
exibir_dica_biblioteca_nao_encontrada();
}
}
let resultado =
resultado.with_context(|| format!("{} '{}'", t(Mensagem::FalhaBuscarBiblioteca), name))?;
if json {
exibir_json_resultados(
&serde_json::to_string_pretty(&resultado.results)
.with_context(|| t(Mensagem::FalhaSerializarJson))?,
);
} else {
exibir_bibliotecas_formatado(&resultado.results);
}
Ok(())
}
pub async fn executar_docs(
library_id: String,
query: Option<String>,
text: bool,
json: bool,
) -> Result<()> {
info!("Buscando documentação para: {}", library_id);
let chaves = carregar_chaves_api()?;
let cliente = criar_cliente_http()?;
info!(
"Iniciando context7 com {} chaves de API disponíveis",
chaves.len()
);
let cliente_arc = std::sync::Arc::new(cliente);
let id_clone = library_id.clone();
let query_clone = query.clone();
if text {
let texto = executar_com_retry(&chaves, move |chave| {
let c = std::sync::Arc::clone(&cliente_arc);
let id = id_clone.clone();
let q = query_clone.clone();
async move { buscar_documentacao_texto(&c, &chave, &id, q.as_deref()).await }
})
.await;
if let Err(ref e) = texto {
if let Some(ErroContext7::BibliotecaNaoEncontrada { .. }) =
e.downcast_ref::<ErroContext7>()
{
exibir_dica_biblioteca_nao_encontrada();
}
}
let texto = texto.with_context(|| {
format!("{} '{}'", t(Mensagem::FalhaBuscarDocumentacao), library_id)
})?;
exibir_texto_plano(&texto);
return Ok(());
}
let resultado = executar_com_retry(&chaves, move |chave| {
let c = std::sync::Arc::clone(&cliente_arc);
let id = id_clone.clone();
let q = query_clone.clone();
async move { buscar_documentacao(&c, &chave, &id, q.as_deref()).await }
})
.await;
if let Err(ref e) = resultado {
if let Some(ErroContext7::BibliotecaNaoEncontrada { .. }) = e.downcast_ref::<ErroContext7>()
{
exibir_dica_biblioteca_nao_encontrada();
}
}
let resultado = resultado
.with_context(|| format!("{} '{}'", t(Mensagem::FalhaBuscarDocumentacao), library_id))?;
if json {
exibir_json_resultados(
&serde_json::to_string_pretty(&resultado)
.with_context(|| t(Mensagem::FalhaSerializarDocs))?,
);
} else {
exibir_documentacao_formatada(&resultado);
}
Ok(())
}