use std::path::PathBuf;
const PERSONA_MAX_CHARS: usize = 2000;
pub fn persona_path() -> Option<PathBuf> {
if let Some(p) = std::env::var_os("MARS_PERSONA") {
return Some(PathBuf::from(p));
}
std::env::var_os("HOME").map(|h| PathBuf::from(h).join(".mars").join("persona.md"))
}
pub fn load() -> Option<String> {
let text = match persona_path().map(|p| std::fs::read_to_string(p)) {
Some(Ok(s)) => {
if s.trim().is_empty() {
return None; }
s.lines()
.filter(|l| !l.trim_start().starts_with('#')) .map(crate::retrieval::redact)
.collect::<Vec<_>>()
.join("\n")
}
_ => crate::prompts::PERSONA_DEFAULT.to_string(), };
let text = text.trim().to_string();
if text.is_empty() {
return None;
}
Some(if text.chars().count() > PERSONA_MAX_CHARS {
let mut t: String = text.chars().take(PERSONA_MAX_CHARS).collect();
t.push('…');
t
} else {
text
})
}
pub fn system_message() -> Option<serde_json::Value> {
let text = load()?;
Some(serde_json::json!({
"role": "system",
"content": format!("{}\n{}", crate::prompts::PERSONA_PREAMBLE.trim_end(), text),
}))
}
pub fn seed_if_missing() -> Option<PathBuf> {
let p = persona_path()?;
if !p.exists() {
if let Some(dir) = p.parent() {
let _ = std::fs::create_dir_all(dir);
}
let _ = std::fs::write(
&p,
format!(
"# Your assistant's voice — style notes applied to prose replies (ask, watch).\n\
# Style only: this file can never change what the agent does, run commands,\n\
# or alter directive/output formats. Empty file = persona off. Lines starting\n\
# with # are ignored. Delete the file to restore this default.\n\n{}",
crate::prompts::PERSONA_DEFAULT.trim_end()
),
);
}
Some(p)
}