Skip to main content

qualia_cli/
resources.rs

1//! `qualia-cli resources` — discover, inspect, download, and import catalog resources.
2//!
3//! # Pipeline (download)
4//!
5//! ```text
6//! resources/llms.yaml
7//!   └─ LLMResource::download_info.resolved_url()
8//!        └─ reqwest stream → ~/.qualia/models/<filename>
9//!             └─ GGufSharder::generate_bidx_pointer_map()   ← tensor pointer Quins
10//!                  └─ WriteAheadLog::append_mutation()       ← pointer + provenance Quins
11//!                       └─ LLMResource::to_capability_profile() → CapabilityProfile
12//! ```
13//!
14//! # Pipeline (ontology import)
15//!
16//! ```text
17//! resources/ontologies.yaml
18//!   └─ OntologyResource::download_info.resolved_url()
19//!        └─ reqwest stream → /tmp/<filename>
20//!             └─ qualia_core_db::ingest::streaming_import_rdf()   ← builds .q42
21//!                  └─ WriteAheadLog::append_mutation()             ← provenance Quin
22//! ```
23
24use qualia_core_db::resource_catalog::{self, ResourceCatalog};
25use std::path::{Path, PathBuf};
26
27/// Load the full `ResourceCatalog` from the YAML files under `catalog_dir`.
28pub fn load_catalog(catalog_dir: &Path) -> Result<ResourceCatalog, String> {
29    resource_catalog::load_from_dir(catalog_dir).map_err(|e| e.to_string())
30}
31
32// ─── CLI entry point ─────────────────────────────────────────────────────────
33
34pub async fn handle(subcommand: &str, arg: Option<&str>) {
35    let catalog_dir = resource_catalog::resolve_resources_dir();
36    let catalog = match load_catalog(&catalog_dir) {
37        Ok(c) => c,
38        Err(e) => {
39            eprintln!("Error loading catalog: {}", e);
40            return;
41        }
42    };
43
44    match subcommand {
45        "list" => cmd_list(&catalog, arg),
46        "show" => cmd_show(&catalog, arg),
47        "download" => {
48            if let Some(id) = arg {
49                cmd_download(&catalog, id).await;
50            } else {
51                eprintln!("Usage: qualia resources download <llm-id>");
52            }
53        }
54        "import-ontology" => {
55            if let Some(id) = arg {
56                cmd_import_ontology(&catalog, id).await;
57            } else {
58                eprintln!("Usage: qualia resources import-ontology <ontology-id>");
59            }
60        }
61        _ => print_help(),
62    }
63}
64
65// ─── list ─────────────────────────────────────────────────────────────────────
66
67fn cmd_list(catalog: &ResourceCatalog, filter: Option<&str>) {
68    match filter.unwrap_or("all") {
69        "llms" | "llm" => list_llms(catalog),
70        "ontologies" | "ont" => list_ontologies(catalog),
71        "sparql" => list_sparql(catalog),
72        _ => {
73            list_llms(catalog);
74            list_ontologies(catalog);
75            list_sparql(catalog);
76        }
77    }
78}
79
80fn list_llms(catalog: &ResourceCatalog) {
81    println!("\nLLMs ({}):", catalog.llms.len());
82    for m in &catalog.llms {
83        println!(
84            "  {:40} {:8}  {:7}  {}MB",
85            m.id,
86            m.format,
87            m.quantization.as_deref().unwrap_or("—"),
88            m.size_mb.unwrap_or(0)
89        );
90    }
91}
92
93fn list_ontologies(catalog: &ResourceCatalog) {
94    println!("\nOntologies ({}):", catalog.ontologies.len());
95    for o in &catalog.ontologies {
96        println!(
97            "  {:40} {:6}  ~{}MB  {}",
98            o.id,
99            o.format,
100            o.size_estimate_mb.unwrap_or(0.0),
101            o.domain.as_deref().unwrap_or("—")
102        );
103    }
104}
105
106fn list_sparql(catalog: &ResourceCatalog) {
107    println!("\nSPARQL endpoints ({}):", catalog.sparql_endpoints.len());
108    for s in &catalog.sparql_endpoints {
109        println!("  {:40} {}", s.id, s.endpoint);
110    }
111}
112
113// ─── show ─────────────────────────────────────────────────────────────────────
114
115fn cmd_show(catalog: &ResourceCatalog, id: Option<&str>) {
116    let id = match id {
117        Some(i) => i,
118        None => {
119            eprintln!("Usage: qualia resources show <id>");
120            return;
121        }
122    };
123
124    if let Some(m) = catalog.find_llm(id) {
125        println!("LLM: {}", m.name);
126        println!("  id           : {}", m.id);
127        println!("  format       : {}", m.format);
128        println!(
129            "  quantization : {}",
130            m.quantization.as_deref().unwrap_or("—")
131        );
132        println!("  size         : {}MB", m.size_mb.unwrap_or(0));
133        println!("  RAM estimate : {}MB", m.ram_estimate_mb.unwrap_or(0));
134        println!("  license      : {}", m.license.as_deref().unwrap_or("—"));
135        if let Some(url) = m.download.resolved_url() {
136            println!("  download url : {}", url);
137        }
138        if let Some(ref notes) = m.notes {
139            println!("  notes        : {}", notes);
140        }
141        println!(
142            "  profile_id   : 0x{:016x}  (q_hash(\"profile:{}\"))",
143            qualia_core_db::q_hash(&format!("profile:{}", m.id)),
144            m.id
145        );
146        return;
147    }
148
149    if let Some(o) = catalog.find_ontology(id) {
150        println!("Ontology: {}", o.name);
151        println!("  id       : {}", o.id);
152        println!("  format   : {}", o.format);
153        println!("  domain   : {}", o.domain.as_deref().unwrap_or("—"));
154        println!("  size     : ~{}MB", o.size_estimate_mb.unwrap_or(0.0));
155        println!("  license  : {}", o.license.as_deref().unwrap_or("—"));
156        if let Some(url) = o.download.resolved_url() {
157            println!("  download : {}", url);
158        }
159        return;
160    }
161
162    if let Some(s) = catalog.find_sparql(id) {
163        println!("SPARQL: {}", s.name);
164        println!("  endpoint     : {}", s.endpoint);
165        println!(
166            "  maintainer   : {}",
167            s.maintainer.as_deref().unwrap_or("—")
168        );
169        println!(
170            "  reliability  : {}",
171            s.reliability.as_deref().unwrap_or("—")
172        );
173        return;
174    }
175
176    eprintln!("Not found: {}", id);
177}
178
179// ─── download ─────────────────────────────────────────────────────────────────
180
181fn default_storage_root() -> PathBuf {
182    std::env::var("QUALIA_STORAGE")
183        .map(PathBuf::from)
184        .unwrap_or_else(|_| {
185            std::env::var("HOME")
186                .or_else(|_| std::env::var("USERPROFILE"))
187                .map(PathBuf::from)
188                .unwrap_or_else(|_| PathBuf::from("."))
189                .join(".qualia")
190        })
191}
192
193async fn cmd_download(catalog: &ResourceCatalog, id: &str) {
194    let storage = default_storage_root();
195
196    match qualia_client_core::model_lifecycle::install_catalog_llm(catalog, id, &storage).await {
197        Ok(result) => {
198            println!("Install complete: {}", result.gguf_path);
199            println!("  profile_id : 0x{:016x}", result.profile_id);
200            println!("  pointers   : {}", result.pointer_quin_count);
201            println!("  WAL        : {}", result.wal_path);
202            println!("  lifecycle  : {}", result.lifecycle_state);
203            println!(
204                "\nActivate in LLM Hub, then chat with profile_id 0x{:016x}",
205                result.profile_id
206            );
207        }
208        Err(e) => eprintln!("Download failed: {e}"),
209    }
210}
211
212// ─── import-ontology ──────────────────────────────────────────────────────────
213
214async fn cmd_import_ontology(catalog: &ResourceCatalog, id: &str) {
215    let storage = default_storage_root();
216
217    match qualia_client_core::resource_import::import_catalog_ontology(catalog, id, &storage).await
218    {
219        Ok(result) => {
220            println!("Import complete: {}", result.q42_path);
221            println!("  Quins: {}", result.quin_count);
222            println!("  WAL:   {}", result.wal_path);
223            println!("  SHA256: {}", result.sha256);
224        }
225        Err(e) => eprintln!("Import failed: {e}"),
226    }
227}
228
229fn print_help() {
230    println!("qualia resources <subcommand> [arg]");
231    println!("  list [llms|ontologies|sparql]    List catalog entries");
232    println!("  show <id>                        Show details for a resource");
233    println!("  download <llm-id>                Download GGUF → WAL + CapabilityProfile");
234    println!("  import-ontology <ont-id>         Download + ingest ontology → .q42 + WAL");
235}