oc-session 0.1.0

Global OpenCode session browser and resume tool
use crate::{
    config::{Config, DEFAULT_RESUME_COMMAND},
    session::Session,
};
use anyhow::Context;
use std::process::Command;

pub fn command(cfg: &Config, session: &Session) -> String {
    cfg.resume_command
        .replace("{directory}", &quote(&session.directory.to_string_lossy()))
        .replace("{session_id}", &quote(&session.id))
}

pub fn run(cfg: &Config, session: &Session) -> anyhow::Result<()> {
    if cfg.resume_command == DEFAULT_RESUME_COMMAND {
        let status = Command::new("opencode")
            .arg(&session.directory)
            .arg("--session")
            .arg(&session.id)
            .status()
            .context("run opencode")?;
        if !status.success() {
            anyhow::bail!("opencode exited with {status}");
        }
        return Ok(());
    }

    let cmd = command(cfg, session);
    let status = if cfg!(target_os = "windows") {
        Command::new("cmd")
            .args(["/C", &cmd])
            .status()
            .with_context(|| cmd.clone())?
    } else {
        Command::new("sh")
            .args(["-lc", &cmd])
            .status()
            .with_context(|| cmd.clone())?
    };
    if !status.success() {
        anyhow::bail!("resume command exited with {status}: {cmd}");
    }
    Ok(())
}

fn quote(value: &str) -> String {
    if cfg!(target_os = "windows") {
        return format!("\"{}\"", value.replace('"', "\\\""));
    }
    format!("'{}'", value.replace('\'', "'\\''"))
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::session::Session;
    use std::path::PathBuf;

    #[test]
    fn command_shell_quotes_placeholders() {
        let session = Session {
            id: "ses_1".into(),
            title: "title".into(),
            directory: PathBuf::from("/tmp/a b's"),
            path: None,
            agent: None,
            model: None,
            project: None,
            worktree: None,
            updated: 0,
            created: 0,
            db: PathBuf::from("db"),
            preview: None,
        };

        let command = command(&Config::default(), &session);
        if cfg!(target_os = "windows") {
            assert!(command.contains("\"/tmp/a b's\""));
        } else {
            assert!(command.contains("'/tmp/a b'\\''s'"));
        }
    }
}