use std::path::Path;
pub(crate) struct WelcomeInfo {
pub model_id: String,
pub thinking_level: String,
#[allow(dead_code)]
pub tool_names: Vec<String>,
pub tool_labels: Vec<(String, String)>,
pub skill_names: Vec<String>,
pub agents_md_path: Option<String>,
#[allow(dead_code)]
pub session_type: &'static str,
pub git_branch: Option<String>,
pub project_name: String,
}
pub(crate) fn format_welcome(info: &WelcomeInfo) -> String {
let version = env!("CARGO_PKG_VERSION");
let mut md = String::new();
md.push_str(&format!("## oxi v{}\n", version));
md.push('\n');
if !info.model_id.is_empty() {
let model_display = info
.model_id
.split('/')
.next_back()
.unwrap_or(&info.model_id);
let provider = info.model_id.split('/').next().unwrap_or("");
md.push_str(&format!(
"**Model**: {} ({})",
model_display, provider
));
if !info.thinking_level.is_empty() {
md.push_str(&format!(" · Thinking: {}", info.thinking_level));
}
md.push('\n');
}
md.push_str(&format!("**Project**: {}", info.project_name));
if let Some(ref branch) = info.git_branch {
md.push_str(&format!(" (`{}`)", branch));
}
md.push('\n');
md.push('\n');
if let Some(ref path) = info.agents_md_path {
md.push_str(&format!("- **AGENTS.md**: {} found\n", shorten(path)));
} else {
md.push_str("- **AGENTS.md**: not found\n");
}
if !info.tool_labels.is_empty() {
md.push_str(&format!(
"- **Tools** ({}): ",
info.tool_labels.len()
));
let names: Vec<String> = info
.tool_labels
.iter()
.map(|(name, _label)| format!("`{}`", name))
.collect();
md.push_str(&names.join(" "));
md.push('\n');
}
if !info.skill_names.is_empty() {
md.push_str(&format!(
"- **Skills** ({}): ",
info.skill_names.len()
));
md.push_str(
&info
.skill_names
.iter()
.map(|s| format!("`{}`", s))
.collect::<Vec<_>>()
.join(" "),
);
md.push('\n');
}
md.push('\n');
md.push_str("Enter send · /help commands · Ctrl+C interrupt · Shift+Tab thinking\n");
md
}
fn shorten(path: &str) -> String {
if let Some(home) = dirs::home_dir() {
let home_str = home.to_string_lossy();
if let Some(rest) = path.strip_prefix(home_str.as_ref()) {
return format!("~{}", rest);
}
}
path.to_string()
}
pub(crate) fn detect_agents_md(cwd: &Path) -> Option<String> {
let candidates = [".oxi/AGENTS.md", "AGENTS.md"];
let mut dir = cwd;
loop {
for candidate in &candidates {
let path = dir.join(candidate);
if path.exists() {
return Some(path.to_string_lossy().into_owned());
}
}
dir = dir.parent()?;
}
}