Skip to content
Merged

V2 #46

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 4 additions & 6 deletions cortex-mem-cli/src/commands/add.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,13 @@ pub async fn execute(
println!("{} Adding message to session: {}", "📝".bold(), thread.cyan());

// Add message using MemoryOperations
let message_id = operations.add_message(thread, role, content).await?;
// Note: add_message returns the full URI of the message file
let message_uri = operations.add_message(thread, role, content).await?;

println!("{} Message added successfully", "✓".green().bold());
println!(" {}: {}", "Thread".cyan(), thread);
println!(" {}: {}", "Role".cyan(), role);
println!(" {}: {}", "ID".cyan(), message_id);

let uri = format!("cortex://session/{}/timeline/{}.md", thread, message_id);
println!(" {}: {}", "URI".cyan(), uri.bright_blue());
println!(" {}: {}", "URI".cyan(), message_uri.bright_blue());

Ok(())
}
}
62 changes: 31 additions & 31 deletions cortex-mem-cli/src/commands/layers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,48 +3,48 @@ use cortex_mem_core::automation::{LayerGenerator, LayerGenerationConfig};
use cortex_mem_tools::MemoryOperations;
use std::sync::Arc;

/// 确保所有目录拥有 L0/L1 文件
/// Ensure all directories have L0/L1 files
pub async fn ensure_all(operations: Arc<MemoryOperations>) -> Result<()> {
println!("🔍 扫描文件系统,检查缺失的 .abstract.md .overview.md 文件...\n");
println!("🔍 Scanning filesystem for missing .abstract.md and .overview.md files...\n");

// 从 session_manager 中获取 LLM client
// Get LLM client from session_manager
let llm_client = {
let sm = operations.session_manager().read().await;
sm.llm_client()
.ok_or_else(|| anyhow::anyhow!("LLM client not available"))?
.clone()
};

// 创建 LayerGenerator
// Create LayerGenerator
let config = LayerGenerationConfig::default();
let generator = LayerGenerator::new(
operations.filesystem().clone(),
llm_client,
config,
);

// 执行扫描和生成
// Execute scan and generation
let stats = generator.ensure_all_layers().await?;

// 显示结果
println!("\n✅ 生成完成!");
// Display results
println!("\n✅ Generation complete!");
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
println!("📊 统计信息:");
println!(" • 总计发现缺失: {} 个目录", stats.total);
println!(" • 成功生成: {} 个", stats.generated);
println!(" • 失败: {} 个", stats.failed);
println!("📊 Statistics:");
println!(" • Total missing: {} directories", stats.total);
println!(" • Generated: {}", stats.generated);
println!(" • Failed: {}", stats.failed);
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");

if stats.failed > 0 {
println!("\n⚠️ 部分目录生成失败,请检查日志获取详细信息");
println!("\n⚠️ Some directories failed to generate. Check logs for details.");
}

Ok(())
}

/// 显示层级文件状态
/// Display layer file status
pub async fn status(operations: Arc<MemoryOperations>) -> Result<()> {
println!("📊 层级文件状态检查\n");
println!("📊 Layer file status check\n");

let llm_client = {
let sm = operations.session_manager().read().await;
Expand All @@ -60,11 +60,11 @@ pub async fn status(operations: Arc<MemoryOperations>) -> Result<()> {
config,
);

// 扫描所有目录
// Scan all directories
let directories = generator.scan_all_directories().await?;
println!("🗂️ 总计目录数: {}\n", directories.len());
println!("🗂️ Total directories: {}\n", directories.len());

// 检测缺失的目录
// Detect missing directories
let missing = generator.filter_missing_layers(&directories).await?;

let complete = directories.len() - missing.len();
Expand All @@ -75,33 +75,33 @@ pub async fn status(operations: Arc<MemoryOperations>) -> Result<()> {
};

println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
println!("✅ 完整 (有 L0/L1): {} ({:.0}%)", complete, complete_percent);
println!("❌ 缺失 (无 L0/L1): {} ({:.0}%)", missing.len(), 100 - complete_percent);
println!("✅ Complete (has L0/L1): {} ({:.0}%)", complete, complete_percent);
println!("❌ Missing (no L0/L1): {} ({:.0}%)", missing.len(), 100 - complete_percent);
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");

if missing.len() > 0 {
println!("\n💡 提示: 运行 `cortex-mem-cli layers ensure-all` 来生成缺失的文件");
println!("\n💡 Tip: Run `cortex-mem layers ensure-all` to generate missing files");

if missing.len() <= 10 {
println!("\n缺失的目录:");
println!("\nMissing directories:");
for dir in &missing {
println!(" • {}", dir);
}
} else {
println!("\n缺失的目录 (显示前 10 个):");
println!("\nMissing directories (showing first 10):");
for dir in missing.iter().take(10) {
println!(" • {}", dir);
}
println!(" ... 还有 {} ", missing.len() - 10);
println!(" ... and {} more", missing.len() - 10);
}
}

Ok(())
}

/// 重新生成超大的 .abstract 文件
/// Regenerate oversized .abstract files
pub async fn regenerate_oversized(operations: Arc<MemoryOperations>) -> Result<()> {
println!("🔍 扫描超大的 .abstract.md 文件...\n");
println!("🔍 Scanning for oversized .abstract.md files...\n");

let llm_client = {
let sm = operations.session_manager().read().await;
Expand All @@ -119,16 +119,16 @@ pub async fn regenerate_oversized(operations: Arc<MemoryOperations>) -> Result<(

let stats = generator.regenerate_oversized_abstracts().await?;

println!("\n✅ 重新生成完成!");
println!("\n✅ Regeneration complete!");
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
println!("📊 统计信息:");
println!(" • 发现超大文件: {} 个", stats.total);
println!(" • 成功重新生成: {}", stats.regenerated);
println!(" • 失败: {} 个", stats.failed);
println!("📊 Statistics:");
println!(" • Oversized files found: {}", stats.total);
println!(" • Successfully regenerated: {}", stats.regenerated);
println!(" • Failed: {}", stats.failed);
println!("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");

if stats.total == 0 {
println!("\n✨ 所有 .abstract 文件大小都在限制范围内!");
println!("\n✨ All .abstract files are within size limits!");
}

Ok(())
Expand Down
1 change: 1 addition & 0 deletions cortex-mem-cli/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ pub mod list;
pub mod search;
pub mod session;
pub mod stats;
pub mod tenant;
13 changes: 13 additions & 0 deletions cortex-mem-cli/src/commands/search.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,19 @@ pub async fn execute(
min_score: f32,
scope: &str,
) -> Result<()> {
// Validate min_score parameter
if min_score < 0.0 || min_score > 1.0 {
return Err(anyhow::anyhow!(
"min_score must be between 0.0 and 1.0, got {:.2}",
min_score
));
}

// Validate limit parameter
if limit == 0 {
return Err(anyhow::anyhow!("limit must be greater than 0"));
}

println!("{} Searching for: {}", "🔍".bold(), query.yellow());

// Build search scope URI
Expand Down
51 changes: 51 additions & 0 deletions cortex-mem-cli/src/commands/tenant.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
use anyhow::Result;
use colored::Colorize;
use std::path::Path;

/// List all available tenants
pub async fn list(data_dir: &str) -> Result<()> {
println!("{} Listing all available tenants", "📋".bold());

let tenants_dir = Path::new(data_dir).join("tenants");

if !tenants_dir.exists() {
println!("\n{} No tenants directory found at {}", "ℹ".yellow().bold(), tenants_dir.display());
return Ok(());
}

let mut tenants = Vec::new();

if let Ok(entries) = std::fs::read_dir(&tenants_dir) {
for entry in entries.flatten() {
if entry.path().is_dir() {
if let Some(name) = entry.file_name().to_str() {
// Skip hidden directories
if !name.starts_with('.') {
tenants.push(name.to_string());
}
}
}
}
}

if tenants.is_empty() {
println!("\n{} No tenants found", "ℹ".yellow().bold());
println!("\n Data directory: {}", tenants_dir.display().to_string().dimmed());
return Ok(());
}

// Sort tenants alphabetically
tenants.sort();

println!("\n{} Found {} tenant(s):", "✓".green().bold(), tenants.len());
println!();

for tenant in tenants {
println!("• {}", tenant.bright_blue().bold());
}

println!("\n {} Use --tenant <id> to specify which tenant to operate on", "💡".dimmed());
println!(" {} Data directory: {}", "📁".dimmed(), tenants_dir.display().to_string().dimmed());

Ok(())
}
37 changes: 32 additions & 5 deletions cortex-mem-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use std::path::PathBuf;
use std::sync::Arc;

mod commands;
use commands::{add, delete, get, layers, list, search, session, stats};
use commands::{add, delete, get, layers, list, search, session, stats, tenant};

/// Cortex-Mem CLI - File-based memory management for AI Agents
#[derive(Parser)]
Expand All @@ -18,7 +18,7 @@ struct Cli {
#[arg(short, long, default_value = "config.toml")]
config: PathBuf,

/// Tenant identifier
/// Tenant identifier (use 'cortex-mem tenant list' to see available tenants)
#[arg(long, default_value = "default")]
tenant: String,

Expand Down Expand Up @@ -109,6 +109,12 @@ enum Commands {
#[command(subcommand)]
action: LayersAction,
},

/// Tenant management
Tenant {
#[command(subcommand)]
action: TenantAction,
},
}

#[derive(Subcommand)]
Expand Down Expand Up @@ -139,6 +145,12 @@ enum LayersAction {
RegenerateOversized,
}

#[derive(Subcommand)]
enum TenantAction {
/// List all available tenants
List,
}

#[tokio::main]
async fn main() -> Result<()> {
let cli = Cli::parse();
Expand All @@ -160,6 +172,19 @@ async fn main() -> Result<()> {
)
})?;

// Determine data directory
let data_dir = config.cortex.data_dir();

// Handle tenant list command early (doesn't need MemoryOperations)
if let Commands::Tenant { action } = cli.command {
match action {
TenantAction::List => {
tenant::list(&data_dir).await?;
}
}
return Ok(());
}

// Initialize LLM client
let model_name = config.llm.model_efficient.clone();
let llm_config = cortex_mem_core::llm::LLMConfig {
Expand All @@ -171,16 +196,14 @@ async fn main() -> Result<()> {
};
let llm_client = Arc::new(LLMClientImpl::new(llm_config)?);

// Determine data directory
let data_dir = config.cortex.data_dir();

// Initialize MemoryOperations with vector search
let operations = MemoryOperations::new(
&data_dir,
&cli.tenant,
llm_client,
&config.qdrant.url,
&config.qdrant.collection_name,
config.qdrant.api_key.as_deref(),
&config.embedding.api_base_url,
&config.embedding.api_key,
&config.embedding.model_name,
Expand All @@ -192,6 +215,7 @@ async fn main() -> Result<()> {
if cli.verbose {
eprintln!("LLM model: {}", model_name);
eprintln!("Data directory: {}", data_dir);
eprintln!("Tenant: {}", cli.tenant);
}

let operations = Arc::new(operations);
Expand Down Expand Up @@ -256,6 +280,9 @@ async fn main() -> Result<()> {
layers::regenerate_oversized(operations).await?;
}
},
Commands::Tenant { .. } => {
// Already handled above
}
}

Ok(())
Expand Down
Loading