diff --git a/src/cli/admin.rs b/src/cli/admin.rs index de2edea..ddbaad1 100644 --- a/src/cli/admin.rs +++ b/src/cli/admin.rs @@ -479,3 +479,32 @@ pub fn cmd_export(files: &[String], export_all: bool) -> Result<(), String> { Ok(()) } +pub fn cmd_status() -> Result<(), String> { + // TUI moved to consciousness binary (F4 unconscious screen) + + let store = crate::store::Store::load()?; + let g = store.build_graph(); + + let mut type_counts = std::collections::HashMap::new(); + for node in store.nodes.values() { + *type_counts.entry(format!("{:?}", node.node_type)).or_insert(0usize) += 1; + } + let mut types: Vec<_> = type_counts.iter().collect(); + types.sort_by_key(|(_, c)| std::cmp::Reverse(**c)); + + println!("Nodes: {} Relations: {}", store.nodes.len(), store.relations.len()); + print!("Types:"); + for (t, c) in &types { + let label = match t.as_str() { + "Semantic" => "semantic", + "EpisodicSession" | "EpisodicDaily" | "EpisodicWeekly" | "EpisodicMonthly" + => "episodic", + _ => t, + }; + print!(" {}={}", label, c); + } + println!(); + println!("Graph edges: {} Communities: {}", + g.edge_count(), g.community_count()); + Ok(()) +} diff --git a/src/cli/misc.rs b/src/cli/misc.rs deleted file mode 100644 index fddf9b5..0000000 --- a/src/cli/misc.rs +++ /dev/null @@ -1,156 +0,0 @@ -// cli/misc.rs — misc subcommand handlers - - -pub fn cmd_search(keys: &[String]) -> Result<(), String> { - if keys.is_empty() { - return Err("search requires seed keys".into()); - } - let result = crate::mcp_server::memory_rpc( - "memory_search", - serde_json::json!({"keys": keys}), - ).map_err(|e| e.to_string())?; - print!("{}", result); - Ok(()) -} - -pub fn cmd_status() -> Result<(), String> { - // TUI moved to consciousness binary (F4 unconscious screen) - - let store = crate::store::Store::load()?; - let g = store.build_graph(); - - let mut type_counts = std::collections::HashMap::new(); - for node in store.nodes.values() { - *type_counts.entry(format!("{:?}", node.node_type)).or_insert(0usize) += 1; - } - let mut types: Vec<_> = type_counts.iter().collect(); - types.sort_by_key(|(_, c)| std::cmp::Reverse(**c)); - - println!("Nodes: {} Relations: {}", store.nodes.len(), store.relations.len()); - print!("Types:"); - for (t, c) in &types { - let label = match t.as_str() { - "Semantic" => "semantic", - "EpisodicSession" | "EpisodicDaily" | "EpisodicWeekly" | "EpisodicMonthly" - => "episodic", - _ => t, - }; - print!(" {}={}", label, c); - } - println!(); - println!("Graph edges: {} Communities: {}", - g.edge_count(), g.community_count()); - Ok(()) -} - -pub fn cmd_query(expr: &[String]) -> Result<(), String> { - if expr.is_empty() { - return Err("query requires an expression (try: poc-memory query --help)".into()); - } - - let query_str = expr.join(" "); - let result = crate::mcp_server::memory_rpc( - "memory_query", - serde_json::json!({"query": query_str}), - ).map_err(|e| e.to_string())?; - print!("{}", result); - Ok(()) -} - -/// Get group content via RPC (handles daemon or local fallback) -pub fn get_group_content(group: &crate::config::ContextGroup, cfg: &crate::config::Config) -> Vec<(String, String)> { - match group.source { - crate::config::ContextSource::Journal => { - // Query for recent journal entries - let window: i64 = cfg.journal_days as i64 * 24 * 3600; - let query = format!("all | type:episodic | age:<{} | sort:timestamp | limit:{}", - window, cfg.journal_max); - - let keys_str = match crate::mcp_server::memory_rpc( - "memory_query", - serde_json::json!({"query": query}), - ) { - Ok(s) => s, - Err(_) => return vec![], - }; - - // Parse keys (one per line) and render each - keys_str.lines() - .filter(|k| !k.is_empty() && *k != "no results") - .filter_map(|key| { - let content = crate::mcp_server::memory_rpc( - "memory_render", - serde_json::json!({"key": key, "raw": true}), - ).ok()?; - if content.trim().is_empty() { return None; } - Some((key.to_string(), content)) - }) - .collect() - } - crate::config::ContextSource::File => { - group.keys.iter().filter_map(|key| { - let content = std::fs::read_to_string(cfg.identity_dir.join(key)).ok()?; - if content.trim().is_empty() { return None; } - Some((key.clone(), content.trim().to_string())) - }).collect() - } - crate::config::ContextSource::Store => { - group.keys.iter().filter_map(|key| { - let content = crate::mcp_server::memory_rpc( - "memory_render", - serde_json::json!({"key": key, "raw": true}), - ).ok()?; - if content.trim().is_empty() { return None; } - Some((key.clone(), content.trim().to_string())) - }).collect() - } - } -} - -pub fn cmd_load_context(stats: bool) -> Result<(), String> { - let cfg = crate::config::get(); - - if stats { - let mut total_words = 0; - let mut total_entries = 0; - println!("{:<25} {:>6} {:>8}", "GROUP", "ITEMS", "WORDS"); - println!("{}", "-".repeat(42)); - - for group in &cfg.context_groups { - let entries = get_group_content(group, &cfg); - let words: usize = entries.iter() - .map(|(_, c)| c.split_whitespace().count()) - .sum(); - let count = entries.len(); - println!("{:<25} {:>6} {:>8}", group.label, count, words); - total_words += words; - total_entries += count; - } - - println!("{}", "-".repeat(42)); - println!("{:<25} {:>6} {:>8}", "TOTAL", total_entries, total_words); - return Ok(()); - } - - println!("=== MEMORY SYSTEM ({}) ===", cfg.assistant_name); - - for group in &cfg.context_groups { - let entries = get_group_content(group, &cfg); - if !entries.is_empty() && group.source == crate::config::ContextSource::Journal { - println!("--- recent journal entries ({}/{}) ---", - entries.len(), cfg.journal_max); - } - for (key, content) in entries { - if group.source == crate::config::ContextSource::Journal { - println!("## {}", key); - } else { - println!("--- {} ({}) ---", key, group.label); - } - println!("{}\n", content); - } - } - - println!("=== END MEMORY LOAD ==="); - Ok(()) -} - diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 98b89f6..4beda59 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -8,7 +8,6 @@ pub mod node; pub mod agent; pub mod admin; pub mod journal; -pub mod misc; /// Exit silently if POC_MEMORY_DRY_RUN=1. pub fn check_dry_run() { diff --git a/src/cli/node.rs b/src/cli/node.rs index dfa8506..ba01e11 100644 --- a/src/cli/node.rs +++ b/src/cli/node.rs @@ -156,3 +156,126 @@ pub fn cmd_edit(key: &[String]) -> Result<(), String> { println!("{}", result); Ok(()) } + +pub fn cmd_search(keys: &[String]) -> Result<(), String> { + if keys.is_empty() { + return Err("search requires seed keys".into()); + } + let result = crate::mcp_server::memory_rpc( + "memory_search", + serde_json::json!({"keys": keys}), + ).map_err(|e| e.to_string())?; + print!("{}", result); + Ok(()) +} + +pub fn cmd_query(expr: &[String]) -> Result<(), String> { + if expr.is_empty() { + return Err("query requires an expression (try: poc-memory query --help)".into()); + } + + let query_str = expr.join(" "); + let result = crate::mcp_server::memory_rpc( + "memory_query", + serde_json::json!({"query": query_str}), + ).map_err(|e| e.to_string())?; + print!("{}", result); + Ok(()) +} + +/// Get group content via RPC (handles daemon or local fallback) +pub fn get_group_content(group: &crate::config::ContextGroup, cfg: &crate::config::Config) -> Vec<(String, String)> { + match group.source { + crate::config::ContextSource::Journal => { + // Query for recent journal entries + let window: i64 = cfg.journal_days as i64 * 24 * 3600; + let query = format!("all | type:episodic | age:<{} | sort:timestamp | limit:{}", + window, cfg.journal_max); + + let keys_str = match crate::mcp_server::memory_rpc( + "memory_query", + serde_json::json!({"query": query}), + ) { + Ok(s) => s, + Err(_) => return vec![], + }; + + // Parse keys (one per line) and render each + keys_str.lines() + .filter(|k| !k.is_empty() && *k != "no results") + .filter_map(|key| { + let content = crate::mcp_server::memory_rpc( + "memory_render", + serde_json::json!({"key": key, "raw": true}), + ).ok()?; + if content.trim().is_empty() { return None; } + Some((key.to_string(), content)) + }) + .collect() + } + crate::config::ContextSource::File => { + group.keys.iter().filter_map(|key| { + let content = std::fs::read_to_string(cfg.identity_dir.join(key)).ok()?; + if content.trim().is_empty() { return None; } + Some((key.clone(), content.trim().to_string())) + }).collect() + } + crate::config::ContextSource::Store => { + group.keys.iter().filter_map(|key| { + let content = crate::mcp_server::memory_rpc( + "memory_render", + serde_json::json!({"key": key, "raw": true}), + ).ok()?; + if content.trim().is_empty() { return None; } + Some((key.clone(), content.trim().to_string())) + }).collect() + } + } +} + +pub fn cmd_load_context(stats: bool) -> Result<(), String> { + let cfg = crate::config::get(); + + if stats { + let mut total_words = 0; + let mut total_entries = 0; + println!("{:<25} {:>6} {:>8}", "GROUP", "ITEMS", "WORDS"); + println!("{}", "-".repeat(42)); + + for group in &cfg.context_groups { + let entries = get_group_content(group, &cfg); + let words: usize = entries.iter() + .map(|(_, c)| c.split_whitespace().count()) + .sum(); + let count = entries.len(); + println!("{:<25} {:>6} {:>8}", group.label, count, words); + total_words += words; + total_entries += count; + } + + println!("{}", "-".repeat(42)); + println!("{:<25} {:>6} {:>8}", "TOTAL", total_entries, total_words); + return Ok(()); + } + + println!("=== MEMORY SYSTEM ({}) ===", cfg.assistant_name); + + for group in &cfg.context_groups { + let entries = get_group_content(group, &cfg); + if !entries.is_empty() && group.source == crate::config::ContextSource::Journal { + println!("--- recent journal entries ({}/{}) ---", + entries.len(), cfg.journal_max); + } + for (key, content) in entries { + if group.source == crate::config::ContextSource::Journal { + println!("## {}", key); + } else { + println!("--- {} ({}) ---", key, group.label); + } + println!("{}\n", content); + } + } + + println!("=== END MEMORY LOAD ==="); + Ok(()) +} diff --git a/src/main.rs b/src/main.rs index 1460bc2..e5dad7d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -437,15 +437,15 @@ trait Run { impl Run for Command { fn run(self) -> Result<(), String> { match self { - Self::Search { keys } => cli::misc::cmd_search(&keys), + Self::Search { keys } => cli::node::cmd_search(&keys), Self::Render { key } => cli::node::cmd_render(&key), Self::Write { key } => cli::node::cmd_write(&key), Self::Edit { key } => cli::node::cmd_edit(&key), Self::History { full, key } => cli::node::cmd_history(&key, full), Self::Tail { n, full, provenance, all_versions } => cli::journal::cmd_tail(n, full, provenance.as_deref(), !all_versions), - Self::Status => cli::misc::cmd_status(), - Self::Query { expr } => cli::misc::cmd_query(&expr), + Self::Status => cli::admin::cmd_status(), + Self::Query { expr } => cli::node::cmd_query(&expr), Self::WeightSet { key, weight } => cli::node::cmd_weight_set(&key, weight), Self::Node(sub) => sub.run(), Self::Journal(sub) => sub.run(), @@ -518,7 +518,7 @@ impl Run for AdminCmd { Self::DailyCheck => cli::admin::cmd_daily_check(), Self::Import { files } => cli::admin::cmd_import(&files), Self::Export { files, all } => cli::admin::cmd_export(&files, all), - Self::LoadContext { stats } => cli::misc::cmd_load_context(stats), + Self::LoadContext { stats } => cli::node::cmd_load_context(stats), Self::MigrateTranscriptProgress => { let mut store = store::Store::load()?; let count = store.migrate_transcript_progress()?; diff --git a/src/subconscious/defs.rs b/src/subconscious/defs.rs index fce6bff..4f7fcd9 100644 --- a/src/subconscious/defs.rs +++ b/src/subconscious/defs.rs @@ -474,7 +474,7 @@ fn resolve( let mut keys = Vec::new(); for group in &cfg.context_groups { if !group.agent { continue; } - let entries = crate::cli::misc::get_group_content(group, &cfg); + let entries = crate::cli::node::get_group_content(group, &cfg); for (key, content) in entries { use std::fmt::Write; writeln!(text, "--- {} ({}) ---", key, group.label).ok();