use std::path::PathBuf;
use std::pin::Pin;
use std::future::Future;
use std::sync::Arc;
use std::task;
use anyhow::Result;
use clap::Parser;
use markhor::app::Markhor;
use markhor::cli::{Cli, Commands};
use markhor::commands;
use markhor_core::extension::{ActiveExtension, Extension};
use markhor_core::storage::{Storage, Workspace};
use markhor_extensions::chunking::Chunkers;
use markhor_extensions::cli::CliExtension;
use markhor_extensions::gemini::GeminiClientExtension;
use markhor_extensions::ocr::mistral::client::MistralClient;
use reqwest::Client;
use tracing::{debug, error, info, Level};
use tracing_subscriber::EnvFilter;
#[tokio::main]
async fn main() -> Result<()> {
let cli = Cli::parse();
setup_tracing(cli.verbose, cli.quiet);
tracing::debug!(args = ?cli, "Markhor CLI arguments parsed");
let mut extensions: Vec<ActiveExtension> = vec![
ActiveExtension::new(Chunkers, Default::default()),
];
dotenv::dotenv().ok();
match std::env::var("GOOGLE_API_KEY") {
Ok(key) => {
info!("Google API key loaded from environment variables");
match GeminiClientExtension::new(key) {
Ok(ext) => extensions.push(ActiveExtension::new(ext, Default::default())),
Err(e) => {
error!("Failed to construct Gemini extension: {}", e);
}
}
},
Err(_) => {
debug!("Google API key not found in environment variables");
}
};
match std::env::var("MISTRAL_API_KEY") {
Ok(key) => {
info!("Mistral API key loaded from environment variables");
extensions.push(ActiveExtension::new(
MistralClient::new(key),
Default::default(),
));
},
Err(_) => {
debug!("Mistral API key not found in environment variables");
}
};
let storage = Arc::new(Storage::new());
let workspace = get_workspace(&storage, cli.workspace.clone()).await;
let folder = match &workspace {
Ok(ws) => match std::env::current_dir() {
Ok(cwd) => ws.folder(&cwd).await.ok(),
Err(e) => {
error!("Could not get current directory: {}", e);
return Err(e.into());
}
}
Err(e) => None
};
extensions.push(ActiveExtension::new(
CliExtension::new(folder.clone()),
Default::default(),
));
let app = Markhor {
storage,
workspace,
folder,
extensions,
};
tracing::debug!(command = ?cli.command, "Dispatching command");
let command_result = match cli.command {
Commands::Import(args) => {
println!("Importing with args: {:?}", args);
commands::handle_import(args, app).await
}
Commands::Chat(args) => {
println!("Chatting with args: {:?}", args);
commands::handle_chat(args, app).await
}
Commands::Show(args) => {
println!("Showing info with args: {:?}", args);
commands::handle_show(args, app).await
}
Commands::Open(args) => {
println!("Opening document with args: {:?}", args);
commands::handle_open(args).await
}
Commands::Search(args) => {
println!("Searching with args: {:?}", args);
commands::handle_search(args, app).await
}
Commands::Install(args) => {
println!("Installing plugin with args: {:?}", args);
commands::handle_install(args).await
}
Commands::Config(args) => {
println!("Managing config with args: {:?}", args);
commands::handle_config(args).await
}
Commands::Workspace(args) => {
println!("Managing workspace with args: {:?}", args);
commands::handle_workspace(args, app).await
}
};
if let Err(e) = command_result {
tracing::error!(error = ?e, "Command failed");
return Err(e);
}
tracing::debug!("Command executed successfully");
Ok(())
}
fn setup_tracing(verbosity: u8, quiet: bool) {
let filter = match std::env::var("RUST_LOG") {
Ok(env_var) if !env_var.is_empty() => {
EnvFilter::new(env_var)
}
_ => {
let base_level = if quiet {
return;
} else {
match verbosity {
0 => Level::WARN, 1 => Level::INFO, 2 => Level::DEBUG, _ => Level::TRACE, }
};
let filter_directives = format!("{},hyper=warn,reqwest=warn", base_level);
EnvFilter::new(filter_directives)
}
};
let subscriber = tracing_subscriber::fmt()
.compact() .with_env_filter(filter)
.with_level(true) .with_target(true) .with_writer(std::io::stderr) .finish();
tracing::subscriber::set_global_default(subscriber)
.expect("Failed to set global default tracing subscriber");
tracing::debug!("Tracing initialized");
}
async fn get_workspace(storage: &Arc<Storage>, cli_ws_flag: Option<PathBuf>) -> Result<Arc<Workspace>> {
if let Some(ws_path) = cli_ws_flag {
debug!("Opening workspace at: {}", ws_path.display());
let ws = Workspace::open(storage, &ws_path).await;
if let Err(e) = ws {
error!("Could not open workspace specified in workspace flag: {}", e);
return Err(anyhow::anyhow!("Failed to open workspace at {}: {}", ws_path.display(), e));
}
return Ok(ws?);
} else {
debug!("Finding workspace in current directory or its parents");
let mut dir = std::env::current_dir()?;
while let Some(parent) = dir.parent() {
match Workspace::open(storage, &*dir).await {
Ok(ws) => {
info!("Found workspace at: {}", dir.display());
return Ok(ws);
}
Err(e) => {
debug!("No workspace found in: {}", dir.display());
dir = parent.to_path_buf();
}
}
}
};
info!("Could not find workspace in current directory or its parents");
Err(anyhow::anyhow!("No workspace found in current directory or its parents"))
}