use anyhow::Result;
use colored::*;
use std::fs;
use std::time::Instant;
use crate::{init_telemetry, record_command_metric, Storage};
pub fn handle_version(
storage: &Storage,
name: &str,
tag: &str,
message: Option<&str>,
start: Instant,
) -> Result<()> {
let mut telemetry = Some(init_telemetry(storage.base_dir().clone()).ok()).flatten();
let prompt_path = storage.prompt_path(name);
if !prompt_path.exists() {
eprintln!("{}", "❌ Prompt not found".red());
std::process::exit(1);
}
let content = fs::read_to_string(&prompt_path)?;
let versions_dir = prompt_path
.parent()
.unwrap()
.join(format!("{}.versions", name));
fs::create_dir_all(&versions_dir)?;
let git_hash = format!("{:x}", md5::compute(&content));
let version_file = versions_dir.join(format!("{}.md", tag));
if version_file.exists() {
eprintln!("{}", format!("❌ Version '{}' already exists", tag).red());
std::process::exit(1);
}
let timestamp = chrono::Utc::now().to_rfc3339();
let version_content = format!(
"---\nversion: {}\ngit_hash: {}\ncreated_at: {}\nmessage: {}\n---\n\n{}",
tag,
git_hash,
timestamp,
message.unwrap_or(""),
content
);
fs::write(&version_file, version_content)?;
if let Ok(mut metadata) = storage.read_prompt_metadata(name) {
metadata.version = Some(tag.to_string());
metadata.git_hash = Some(git_hash.clone());
metadata.updated_at = Some(timestamp);
let _ = storage.write_prompt_metadata(name, &metadata);
}
println!(
"Created version '{}' for '{}' ({})",
tag,
name,
&git_hash[..8]
);
if let Some(msg) = message {
println!("Message: {}", msg);
}
record_command_metric(&mut telemetry, "version", start.elapsed(), true, None, None);
Ok(())
}
pub fn handle_versions(storage: &Storage, name: &str, verbose: bool, start: Instant) -> Result<()> {
let mut telemetry = Some(init_telemetry(storage.base_dir().clone()).ok()).flatten();
let prompt_path = storage.prompt_path(name);
if !prompt_path.exists() {
eprintln!("{}", "❌ Prompt not found".red());
std::process::exit(1);
}
let versions_dir = prompt_path
.parent()
.unwrap()
.join(format!("{}.versions", name));
if !versions_dir.exists() {
println!(
"{}",
format!("📝 No versions found for '{}'", name).yellow()
);
return Ok(());
}
let mut versions = Vec::new();
for entry in fs::read_dir(&versions_dir)? {
let entry = entry?;
if let Some(name) = entry.file_name().to_str() {
if name.ends_with(".md") {
let version_name = name.trim_end_matches(".md");
let content = fs::read_to_string(entry.path())?;
if let Some(metadata_end) = content.find("\n---\n") {
let metadata_section = &content[4..metadata_end]; let mut version_info = std::collections::HashMap::<String, String>::new();
for line in metadata_section.lines() {
if let Some((key, value)) = line.split_once(": ") {
version_info.insert(key.trim().to_string(), value.trim().to_string());
}
}
versions.push((version_name.to_string(), version_info));
}
}
}
}
if versions.is_empty() {
println!(
"{}",
format!("📝 No versions found for '{}'", name).yellow()
);
return Ok(());
}
versions.sort_by(|a, b| {
let a_date = a.1.get("created_at").map(|s| s.as_str()).unwrap_or("");
let b_date = b.1.get("created_at").map(|s| s.as_str()).unwrap_or("");
b_date.cmp(a_date)
});
println!("{}", format!("📚 Version history for '{}'", name).bold());
println!();
for (version, info) in &versions {
let hash = info
.get("git_hash")
.map(|s| s.as_str())
.unwrap_or("unknown");
let date = info
.get("created_at")
.map(|s| s.as_str())
.unwrap_or("unknown");
let message = info.get("message").map(|s| s.as_str()).unwrap_or("");
if verbose {
println!("{} {} ({})", "📌".cyan(), version.bold(), &hash[..8]);
println!(" 📅 {}", date.dimmed());
if !message.is_empty() {
println!(" 💬 {}", message);
}
println!();
} else {
let msg_display = if message.is_empty() {
String::new()
} else {
format!(" - {}", message)
};
println!(
"{} {} ({}){}",
"📌".cyan(),
version.bold(),
&hash[..8],
msg_display
);
}
}
record_command_metric(
&mut telemetry,
"versions",
start.elapsed(),
true,
None,
None,
);
Ok(())
}
pub fn handle_rollback(
storage: &Storage,
name: &str,
version: &str,
backup: bool,
start: Instant,
) -> Result<()> {
let mut telemetry = Some(init_telemetry(storage.base_dir().clone()).ok()).flatten();
let prompt_path = storage.prompt_path(name);
if !prompt_path.exists() {
eprintln!("{}", "❌ Prompt not found".red());
std::process::exit(1);
}
let versions_dir = prompt_path
.parent()
.unwrap()
.join(format!("{}.versions", name));
let version_file = versions_dir.join(format!("{}.md", version));
if !version_file.exists() {
eprintln!("{}", format!("❌ Version '{}' not found", version).red());
std::process::exit(1);
}
if backup {
let backup_tag = format!("backup-{}", chrono::Utc::now().timestamp());
handle_version(
storage,
name,
&backup_tag,
Some("Pre-rollback backup"),
start,
)?;
println!(
"{}",
format!("💾 Created backup version '{}'", backup_tag).blue()
);
}
let version_content = fs::read_to_string(&version_file)?;
if let Some(content_start) = version_content.find("\n---\n") {
let content = &version_content[content_start + 5..];
fs::write(&prompt_path, content)?;
if let Ok(mut metadata) = storage.read_prompt_metadata(name) {
metadata.version = Some(version.to_string());
metadata.updated_at = Some(chrono::Utc::now().to_rfc3339());
let _ = storage.write_prompt_metadata(name, &metadata);
}
println!("Rolled back '{}' to version '{}'", name, version);
} else {
eprintln!("Invalid version file format");
std::process::exit(1);
}
record_command_metric(
&mut telemetry,
"rollback",
start.elapsed(),
true,
None,
None,
);
Ok(())
}