skanda_engine 0.1.0

A zero-dependency, ultra-high-performance retrieval engine designed for the next generation of RAG.
Documentation
use skanda_engine::{Indexer, Searcher, Bridge};
use std::env;
use std::time::Instant;

fn main() {
    let args: Vec<String> = env::args().collect();
    if args.len() < 2 {
        print_usage();
        return;
    }

    let command = &args[1];

    match command.as_str() {
        "index" => {
            if args.len() != 4 {
                println!("Usage: skanda index <dir> <index_path.bin>");
                return;
            }
            let dir = &args[2];
            let index_path = &args[3];

            println!("Indexing directory: {}", dir);
            let start = Instant::now();
            let mut idx = Indexer::new();
            idx.index_directory(dir);
            
            if let Err(e) = idx.save_to_disk(index_path) {
                eprintln!("Failed to save index: {}", e);
            } else {
                println!("Indexing completed in {:?}", start.elapsed());
            }
        }
        "search" => {
            if args.len() < 4 {
                println!("Usage: skanda search <index_path.bin> <query...> [--json] [--fuzzy]");
                return;
            }
            let index_path = &args[2];
            let is_json = args.contains(&String::from("--json"));
            let is_fuzzy = args.contains(&String::from("--fuzzy"));
            
            let query_parts: Vec<String> = args[3..].iter()
                .filter(|a| !a.starts_with("--"))
                .cloned()
                .collect();
            let query = query_parts.join(" ");

            match Searcher::load_from_disk(index_path) {
                Ok(s) => {
                    let results = s.search(&query, is_fuzzy);
                    
                    if is_json {
                        println!("{}", format_results_json(&results));
                    } else {
                        println!("Found {} results", results.len());
                        for (i, res) in results.iter().enumerate() {
                            println!("--- Result {} ---", i + 1);
                            println!("File: {}", res.file_path);
                            println!("Snippet:\n{}\n", res.snippet);
                        }
                    }
                }
                Err(e) => eprintln!("Failed to load index: {}", e),
            }
        }
        "serve" => {
            if args.len() != 4 {
                println!("Usage: skanda serve <index_path.bin> <port>");
                return;
            }
            let index_path = &args[2];
            let port = args[3].parse::<u16>().expect("Invalid port");

            match Searcher::load_from_disk(index_path) {
                Ok(s) => {
                    let b = Bridge::new(s);
                    if let Err(e) = b.listen(port) {
                        eprintln!("Bridge error: {}", e);
                    }
                }
                Err(e) => eprintln!("Failed to load index: {}", e),
            }
        }
        "status" => {
            if args.len() != 3 {
                println!("Usage: skanda status <index_path.bin>");
                return;
            }
            let index_path = &args[2];
            match Searcher::load_from_disk(index_path) {
                Ok(s) => {
                    s.print_status();
                }
                Err(e) => eprintln!("Failed to load index: {}", e),
            }
        }
        _ => {
            println!("Unknown command: {}", command);
            print_usage();
        }
    }
}

fn print_usage() {
    println!("Skanda Engine");
    println!("Usage: skanda <command> [args]");
    println!("Commands:");
    println!("  index <dir> <index.bin>      Index a directory of text files");
    println!("  search <index.bin> <query>   Search footprints (use --json for machine output, --fuzzy for typos)");
    println!("  serve <index.bin> <port>     Start the JSON HTTP bridge");
    println!("  status <index.bin>           Show index metadata");
}

fn format_results_json(results: &[skanda_engine::SearchResult]) -> String {
    let mut json = String::from("[\n");
    for (i, res) in results.iter().enumerate() {
        json.push_str("  {\n");
        json.push_str(&format!("    \"file\": \"{}\",\n", res.file_path.replace('\\', "/")));
        let escaped = res.snippet.replace('\\', "\\\\").replace('"', "\\\"").replace('\n', "\\n").replace('\r', "\\r");
        json.push_str(&format!("    \"snippet\": \"{}\"\n", escaped));
        json.push_str("  }");
        if i < results.len() - 1 { json.push(','); }
        json.push('\n');
    }
    json.push(']');
    json
}