use anyhow::{anyhow, Result};
use clap::{Parser, Subcommand};
use safari::{backend, tui};
#[derive(Parser)]
#[command(name = "safari")]
#[command(about = "Capture and restore Safari sessions on macOS", long_about = None)]
struct Cli {
#[command(subcommand)]
command: Option<Commands>,
}
#[derive(Subcommand)]
enum Commands {
#[command(alias = "dump")]
Capture,
#[command(alias = "reopen")]
Restore {
#[arg(short, long)]
index: Option<usize>,
},
List,
Tui,
}
fn main() -> Result<()> {
let cli = Cli::parse();
match cli.command {
Some(Commands::Capture) => {
let session = backend::safari::get_current_session()?;
let path = backend::storage::save_session(&session)?;
println!(
"Saved {} window(s) and {} tab(s) to {}",
session.window_count(),
session.tab_count(),
path.display()
);
}
Some(Commands::Restore { index }) => {
let sessions = backend::storage::list_sessions()?;
let selected = resolve_session(index, &sessions)?;
let session = backend::storage::load_session(&selected.path)?;
backend::safari::restore_session(&session)?;
println!(
"Restored {} ({}, {} tabs)",
backend::utils::format_timestamp(&selected.captured_at),
selected.window_count,
selected.tab_count
);
}
Some(Commands::List) => {
let sessions = backend::storage::list_sessions()?;
if sessions.is_empty() {
println!(
"No backups found in {}",
backend::storage::storage_dir()?.display()
);
} else {
println!(
"{:<4} {:<16} {:<7} {:<6} File",
"#", "Captured", "Win", "Tabs"
);
for (index, session) in sessions.iter().enumerate() {
println!(
"{:<4} {:<16} {:<7} {:<6} {}",
index + 1,
backend::utils::format_timestamp(&session.captured_at),
session.window_count,
session.tab_count,
session.file_name
);
}
}
}
Some(Commands::Tui) | None => {
tui::run()?;
}
}
Ok(())
}
fn resolve_session<'a>(
index: Option<usize>,
sessions: &'a [backend::models::SessionSummary],
) -> Result<&'a backend::models::SessionSummary> {
if sessions.is_empty() {
return Err(anyhow!("No saved Safari sessions were found"));
}
match index {
Some(0) => Err(anyhow!("Session indexes start at 1")),
Some(index) => sessions
.get(index - 1)
.ok_or_else(|| anyhow!("Session {index} does not exist")),
None => Ok(&sessions[0]),
}
}