Skip to main content

embeddenator_cli/commands/
ingest.rs

1//! Ingest command implementation
2
3use anyhow::Result;
4use embeddenator_fs::embrfs::EmbrFS;
5use embeddenator_vsa::ReversibleVSAConfig;
6use std::collections::HashMap;
7use std::env;
8use std::path::PathBuf;
9
10use crate::utils::logical_path_for_file_input;
11
12pub fn handle_ingest(
13    input: Vec<PathBuf>,
14    engram: PathBuf,
15    manifest: PathBuf,
16    verbose: bool,
17) -> Result<()> {
18    if verbose {
19        println!(
20            "Embeddenator v{} - Holographic Ingestion",
21            env!("CARGO_PKG_VERSION")
22        );
23        println!("=====================================");
24    }
25
26    let mut fs = EmbrFS::new();
27    let config = ReversibleVSAConfig::default();
28
29    // Backward-compatible behavior: a single directory input ingests with paths
30    // relative to that directory (no namespacing).
31    if input.len() == 1 && input[0].is_dir() {
32        fs.ingest_directory(&input[0], verbose, &config)?;
33    } else {
34        let cwd = env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
35
36        // Ensure deterministic and collision-resistant namespacing for multiple directory roots.
37        let mut dir_prefix_counts: HashMap<String, usize> = HashMap::new();
38
39        for p in &input {
40            if !p.exists() {
41                anyhow::bail!("Input path does not exist: {}", p.display());
42            }
43
44            if p.is_dir() {
45                let base = p
46                    .file_name()
47                    .and_then(|s| s.to_str())
48                    .filter(|s| !s.is_empty())
49                    .unwrap_or("input")
50                    .to_string();
51                let count = dir_prefix_counts.entry(base.clone()).or_insert(0);
52                *count += 1;
53                let prefix = if *count == 1 {
54                    base
55                } else {
56                    format!("{}_{}", base, count)
57                };
58
59                fs.ingest_directory_with_prefix(p, Some(&prefix), verbose, &config)?;
60            } else {
61                let logical = logical_path_for_file_input(p, &cwd);
62                fs.ingest_file(p, logical, verbose, &config)?;
63            }
64        }
65    }
66
67    fs.save_engram(&engram)?;
68    fs.save_manifest(&manifest)?;
69
70    if verbose {
71        println!("\nIngestion complete!");
72        println!("  Engram: {}", engram.display());
73        println!("  Manifest: {}", manifest.display());
74        println!("  Files: {}", fs.manifest.files.len());
75        println!("  Total chunks: {}", fs.manifest.total_chunks);
76    }
77
78    Ok(())
79}