use anyhow::Context;
use serde::Deserialize;
use std::{fs, path::PathBuf};
pub const DEFAULT_RESUME_COMMAND: &str = "opencode {directory} --session {session_id}";
#[derive(Clone, Debug, Deserialize)]
pub struct Config {
#[serde(default = "default_command")]
pub resume_command: String,
#[serde(default = "default_limit")]
pub limit: usize,
#[serde(default)]
pub paths: Paths,
#[serde(default)]
pub ui: Ui,
}
#[derive(Clone, Debug, Default, Deserialize)]
pub struct Paths {
#[serde(default)]
pub include: Vec<PathBuf>,
#[serde(default)]
pub exclude: Vec<PathBuf>,
}
#[derive(Clone, Debug, Deserialize)]
pub struct Ui {
#[serde(default = "default_true")]
pub show_directory: bool,
#[serde(default = "default_true")]
pub show_agent: bool,
#[serde(default)]
pub show_model: bool,
}
impl Default for Config {
fn default() -> Self {
Self {
resume_command: default_command(),
limit: default_limit(),
paths: Paths::default(),
ui: Ui::default(),
}
}
}
impl Default for Ui {
fn default() -> Self {
Self {
show_directory: true,
show_agent: true,
show_model: false,
}
}
}
impl Config {
pub fn load(path: Option<PathBuf>) -> anyhow::Result<Self> {
let Some(path) = path.or_else(default_path) else {
return Ok(Self::default());
};
if !path.exists() {
return Ok(Self::default());
}
toml::from_str(
&fs::read_to_string(&path).with_context(|| format!("read {}", path.display()))?,
)
.with_context(|| format!("parse {}", path.display()))
}
}
fn default_path() -> Option<PathBuf> {
dirs::config_dir().map(|dir| dir.join("oc-session").join("config.toml"))
}
fn default_command() -> String {
DEFAULT_RESUME_COMMAND.to_string()
}
fn default_limit() -> usize {
500
}
fn default_true() -> bool {
true
}