use crate::cli::ContextCommands;
use crate::context::{ContextManager, ItemType};
use crate::db::Database;
use anyhow::Result;
use colored::Colorize;
use std::env;
pub fn run(command: ContextCommands) -> Result<()> {
let project_root = env::current_dir()?;
if !Database::minni_dir_exists(&project_root) {
println!(
"{} Minni not initialized. Run {} first.",
"Error:".red(),
"minni init".cyan()
);
return Ok(());
}
let db = Database::open(&project_root)?;
let ctx_mgr = ContextManager::new(&db);
match command {
ContextCommands::Save { name, description } => {
let context = ctx_mgr.save_context(name, description)?;
println!("{} Context saved!", "✓".green());
println!(" ID: {}", context.id.cyan());
println!(" Name: {}", context.name.cyan());
println!(" Project: {}", ctx_mgr.project_name().yellow());
}
ContextCommands::Load { id } => match ctx_mgr.load_context(&id)? {
Some(full_ctx) => {
println!("{}", full_ctx.to_markdown());
}
None => {
println!("{} Context not found: {}", "Error:".red(), id);
}
},
ContextCommands::List { verbose } => {
let contexts = ctx_mgr.list_contexts()?;
if contexts.is_empty() {
println!("{} No saved contexts.", "!".yellow());
return Ok(());
}
println!("{} Saved contexts:\n", "→".blue());
for ctx in contexts {
if verbose {
println!(
"{} {} ({})",
"•".cyan(),
ctx.name.bold(),
ctx.project_name.yellow()
);
println!(" ID: {}", ctx.id.dimmed());
if let Some(desc) = &ctx.description {
println!(" Description: {}", desc);
}
println!(" Items: {}", ctx.item_count);
println!(" Created: {}", ctx.created_at);
println!(" Updated: {}", ctx.updated_at);
println!();
} else {
let desc = ctx
.description
.as_deref()
.map(|d| format!(" - {}", d))
.unwrap_or_default();
println!(
"{} {} ({}){}",
"•".cyan(),
ctx.name.bold(),
ctx.project_name.yellow(),
desc.dimmed()
);
}
}
}
ContextCommands::Delete { id } => {
if ctx_mgr.delete_context(&id)? {
println!("{} Context deleted: {}", "✓".green(), id);
} else {
println!("{} Context not found: {}", "Error:".red(), id);
}
}
ContextCommands::Show { id } => {
match ctx_mgr.load_context(&id)? {
Some(full_ctx) => {
println!("{}", full_ctx.to_json()?);
}
None => {
println!("{} Context not found: {}", "Error:".red(), id);
}
}
}
ContextCommands::Add { key, value } => {
let contexts = ctx_mgr.list_contexts_by_project(ctx_mgr.project_name())?;
let context_name = if let Some(latest) = contexts.first() {
latest.name.clone()
} else {
let ctx = ctx_mgr.save_context(None, None)?;
ctx.name
};
let item_type = ItemType::from_str(&key);
ctx_mgr.add_item(&context_name, &key, &value, item_type)?;
println!(
"{} Added to context '{}':",
"✓".green(),
context_name.cyan()
);
println!(" {}: {}", key.yellow(), value);
}
ContextCommands::Snapshot {
name,
include_diff,
conversation,
} => {
println!("{} Creating session snapshot...", "→".blue());
let conv = if conversation {
println!("Reading conversation from stdin (Ctrl+D to finish)...");
use std::io::{self, Read};
let mut buffer = String::new();
io::stdin().read_to_string(&mut buffer)?;
Some(parse_conversation(&buffer))
} else {
None
};
let snapshot = ctx_mgr.create_snapshot(
name.unwrap_or_else(|| {
format!("snapshot-{}", chrono::Utc::now().format("%Y%m%d-%H%M%S"))
}),
Some("Session snapshot".to_string()),
include_diff,
conv,
)?;
let snapshot_dir = project_root.join(".minni").join("snapshots");
std::fs::create_dir_all(&snapshot_dir)?;
let filename = format!("{}.json", snapshot.name);
let output_path = snapshot_dir.join(&filename);
ctx_mgr.export_snapshot(&snapshot, &output_path.to_string_lossy())?;
println!("{} Snapshot created!", "✓".green());
println!(" Name: {}", snapshot.name.cyan());
println!(
" Files changed: {}",
snapshot.modified_files.len().to_string().yellow()
);
if let Some(branch) = &snapshot.git_branch {
println!(" Git branch: {}", branch.yellow());
}
println!(" Saved to: {}", output_path.display().to_string().cyan());
}
ContextCommands::Export { id, output } => {
match ctx_mgr.load_context(&id)? {
Some(full_ctx) => {
let output_path = output.unwrap_or_else(|| format!("{}.json", id));
let snapshot = crate::context::ContextSnapshot {
version: "1.0".to_string(),
created_at: full_ctx.context.created_at.clone(),
project_name: ctx_mgr.project_name().to_string(),
project_path: project_root.to_string_lossy().to_string(),
name: full_ctx.context.name.clone(),
description: full_ctx.context.description.clone(),
conversation: None,
modified_files: vec![],
git_diff: None,
git_branch: None,
tasks: vec![],
notes: vec![],
relevant_files: vec![],
code_snippets: vec![],
};
ctx_mgr.export_snapshot(&snapshot, &output_path)?;
println!("{} Context exported!", "✓".green());
println!(" File: {}", output_path.cyan());
}
None => {
println!("{} Context not found: {}", "Error:".red(), id);
}
}
}
ContextCommands::Import { file, name } => {
println!("{} Importing context from {}...", "→".blue(), file.cyan());
let snapshot = ctx_mgr.import_snapshot(&file)?;
let context_id = ctx_mgr.snapshot_to_context(snapshot.clone(), name)?;
println!("{} Context imported!", "✓".green());
println!(" ID: {}", context_id.cyan());
println!(" Name: {}", snapshot.name.cyan());
println!(" Original project: {}", snapshot.project_name.yellow());
}
}
Ok(())
}
fn parse_conversation(text: &str) -> Vec<crate::context::ConversationTurn> {
let mut turns = Vec::new();
let mut current_role = String::new();
let mut current_content = String::new();
for line in text.lines() {
if line.starts_with("USER:") {
if !current_role.is_empty() {
turns.push(crate::context::ConversationTurn {
role: current_role.clone(),
content: current_content.trim().to_string(),
timestamp: chrono::Utc::now().to_rfc3339(),
});
}
current_role = "user".to_string();
current_content = line[5..].to_string();
} else if line.starts_with("ASSISTANT:") {
if !current_role.is_empty() {
turns.push(crate::context::ConversationTurn {
role: current_role.clone(),
content: current_content.trim().to_string(),
timestamp: chrono::Utc::now().to_rfc3339(),
});
}
current_role = "assistant".to_string();
current_content = line[10..].to_string();
} else if !current_role.is_empty() {
current_content.push('\n');
current_content.push_str(line);
}
}
if !current_role.is_empty() {
turns.push(crate::context::ConversationTurn {
role: current_role,
content: current_content.trim().to_string(),
timestamp: chrono::Utc::now().to_rfc3339(),
});
}
turns
}