use cached_context::cache::CacheStore;
use cached_context::types::CacheConfig;
use clap::{Parser, Subcommand};
use std::path::PathBuf;
use tracing::info;
#[derive(Subcommand)]
enum Commands {
Serve {
#[arg(short, long)]
db_path: Option<PathBuf>,
#[arg(short, long)]
workdir: Option<PathBuf>,
},
Status {
#[arg(short, long)]
db_path: Option<PathBuf>,
#[arg(short, long)]
workdir: Option<PathBuf>,
},
Clear {
#[arg(short, long)]
db_path: Option<PathBuf>,
#[arg(short, long)]
workdir: Option<PathBuf>,
},
}
#[derive(Parser)]
#[command(name = "cached-context")]
#[command(about = "cached-context - File cache with diff tracking cache management service", long_about = None)]
struct Args {
#[command(subcommand)]
command: Option<Commands>,
#[arg(short, long, global = true)]
verbose: bool,
}
fn default_db_path() -> PathBuf {
std::env::var("CACHEBRO_DIR")
.map(|dir| PathBuf::from(dir).join("cache.db"))
.unwrap_or_else(|_| PathBuf::from(".cached-context/cache.db"))
}
fn init_logging(verbose: bool) {
let filter = std::env::var("RUST_LOG")
.ok()
.and_then(|v| v.parse::<tracing_subscriber::EnvFilter>().ok())
.unwrap_or_else(|| {
tracing_subscriber::EnvFilter::new(if verbose { "debug" } else { "info" })
});
tracing_subscriber::fmt()
.with_env_filter(filter)
.with_target(false)
.with_thread_ids(false)
.with_file(true)
.with_line_number(true)
.with_writer(std::io::stderr)
.init();
}
async fn create_cache(db_path: PathBuf, workdir: Option<PathBuf>) -> Result<CacheStore, String> {
let workdir = workdir.unwrap_or_else(|| std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")));
let config = CacheConfig {
db_path,
session_id: uuid::Uuid::new_v4().to_string(),
workdir,
};
let store = CacheStore::new(config).map_err(|e| format!("Failed to create cache: {}", e))?;
store.init().await.map_err(|e| format!("Failed to initialize cache: {}", e))?;
Ok(store)
}
async fn handle_status(db_path: PathBuf, workdir: Option<PathBuf>) -> Result<(), String> {
let store = create_cache(db_path, workdir).await?;
let stats = store.get_stats().await.map_err(|e| format!("Failed to get stats: {}", e))?;
println!("Cache Statistics:");
println!(" Files tracked: {}", stats.files_tracked);
println!(" Total tokens saved: {}", stats.tokens_saved);
println!(" Session tokens saved: {}", stats.session_tokens_saved);
Ok(())
}
async fn handle_clear(db_path: PathBuf, workdir: Option<PathBuf>) -> Result<(), String> {
let store = create_cache(db_path, workdir).await?;
store.clear().await.map_err(|e| format!("Failed to clear cache: {}", e))?;
println!("Cache cleared successfully");
Ok(())
}
async fn run_server(db_path: PathBuf, workdir: Option<PathBuf>) -> Result<(), String> {
let store = create_cache(db_path, workdir).await?;
info!("Starting MCP server...");
cached_context::mcp::start_mcp_server_with_store(store)
.await
.map_err(|e| format!("MCP server error: {}", e))?;
Ok(())
}
#[tokio::main]
async fn main() -> Result<(), String> {
let args = Args::parse();
init_logging(args.verbose);
let command = args.command.unwrap_or(Commands::Serve {
db_path: None,
workdir: None,
});
match command {
Commands::Serve { db_path, workdir } => {
run_server(db_path.unwrap_or_else(default_db_path), workdir).await?;
}
Commands::Status { db_path, workdir } => {
handle_status(db_path.unwrap_or_else(default_db_path), workdir).await?;
}
Commands::Clear { db_path, workdir } => {
handle_clear(db_path.unwrap_or_else(default_db_path), workdir).await?;
}
}
Ok(())
}