use anyhow::{bail, Result};
use chrono::Utc;
use colored::Colorize;
use uuid::Uuid;
use crate::storage::{Database, Summary};
use crate::summarize::{generate_summary, SummarizeError};
#[derive(clap::Args)]
#[command(after_help = "EXAMPLES:\n \
lore summarize abc123 \"Implemented auth feature\" Add summary to session\n \
lore summarize abc123 --show View existing summary\n \
lore summarize abc123 --generate Generate summary via LLM")]
pub struct Args {
#[arg(value_name = "SESSION")]
#[arg(
long_help = "The session ID prefix to summarize. Must uniquely identify a\n\
single session. Use 'lore sessions' to find session IDs."
)]
pub session: String,
#[arg(value_name = "SUMMARY")]
#[arg(
long_help = "The summary content describing what happened in the session.\n\
Omit this to use --show and view the existing summary."
)]
pub summary: Option<String>,
#[arg(long)]
#[arg(
long_help = "Display the existing summary for the session instead of\n\
adding or updating one."
)]
pub show: bool,
#[arg(long)]
#[arg(long_help = "Generate a summary using the configured LLM provider.\n\
Requires a summary provider to be configured via 'lore init --force'\n\
or 'lore config set'. Cannot be used with manual summary text.")]
pub generate: bool,
}
pub fn run(args: Args) -> Result<()> {
let db = Database::open_default()?;
let all_sessions = db.list_sessions(1000, None)?;
let matching: Vec<_> = all_sessions
.iter()
.filter(|s| s.id.to_string().starts_with(&args.session))
.collect();
if matching.is_empty() {
if all_sessions.is_empty() {
bail!(
"No session found matching '{}'. No sessions in database. \
Run 'lore import' to import sessions first.",
args.session
);
} else {
bail!(
"No session found matching '{}'. \
Run 'lore sessions' to list available sessions.",
args.session
);
}
}
if matching.len() > 1 {
println!("{}", "Multiple sessions match that prefix:".yellow());
for s in &matching {
let id_short = &s.id.to_string()[..8];
println!(
" {} - {}",
id_short.cyan(),
s.started_at.format("%Y-%m-%d %H:%M")
);
}
bail!(
"Multiple sessions match '{}'. Please use a more specific prefix from the list above.",
args.session
);
}
let session = matching[0];
let session_id = session.id;
let session_short = &session.id.to_string()[..8];
if args.show && args.generate {
bail!("Cannot use --show and --generate together.");
}
if args.show {
show_summary(&db, &session_id, session_short)?;
} else if args.generate && args.summary.is_some() {
bail!("Cannot use --generate with manual summary text.");
} else if args.generate {
let messages = db.get_messages(&session_id)?;
match generate_summary(&messages) {
Ok(summary_text) => {
add_or_update_summary(&db, &session_id, session_short, &summary_text)?;
println!(
"{} {}",
"Generated summary for session".green(),
session_short.cyan()
);
println!("{summary_text}");
}
Err(SummarizeError::NotConfigured) => {
bail!(
"Summary provider not configured.\n\n\
Configure a summary provider first:\n \
lore init --force (guided setup with hidden key input)\n \
lore config set summary_provider <anthropic|openai|openrouter>"
);
}
Err(SummarizeError::EmptySession) => {
bail!("Session has no messages to summarize.");
}
Err(e) => {
bail!("Failed to generate summary: {e}");
}
}
} else if let Some(summary_text) = args.summary {
add_or_update_summary(&db, &session_id, session_short, &summary_text)?;
} else {
bail!("Please provide a summary text, use --show to view, or --generate to auto-generate.");
}
Ok(())
}
fn show_summary(db: &Database, session_id: &Uuid, session_short: &str) -> Result<()> {
match db.get_summary(session_id)? {
Some(summary) => {
println!("{} {}", "Session".bold(), session_short.cyan());
println!();
println!("{}", "Summary:".bold());
println!("{}", summary.content);
println!();
println!(
"{}",
format!(
"Last updated: {}",
summary.generated_at.format("%Y-%m-%d %H:%M:%S")
)
.dimmed()
);
}
None => {
println!(
"{}",
format!("No summary exists for session {session_short}").dimmed()
);
println!();
println!(
"{}",
"Use 'lore summarize <session> \"<text>\"' to add one.".dimmed()
);
}
}
Ok(())
}
fn add_or_update_summary(
db: &Database,
session_id: &Uuid,
session_short: &str,
content: &str,
) -> Result<()> {
let existing = db.get_summary(session_id)?;
if existing.is_some() {
db.update_summary(session_id, content)?;
println!(
"{} session {}",
"Updated summary for".green(),
session_short.cyan()
);
} else {
let summary = Summary {
id: Uuid::new_v4(),
session_id: *session_id,
content: content.to_string(),
generated_at: Utc::now(),
};
db.insert_summary(&summary)?;
println!(
"{} session {}",
"Summary saved for".green(),
session_short.cyan()
);
}
Ok(())
}