use std::path::PathBuf;
use anyhow::{Context, Result, bail};
use crate::backend::Registry;
const SKILL_MD: &str = include_str!("../../skills/SKILL.md");
const VERSION: &str = env!("CARGO_PKG_VERSION");
pub fn auto_update(registry: &Registry) {
let home = match std::env::var("HOME") {
Ok(h) => h,
Err(_) => return,
};
for config_dir in [".claude", ".codex"] {
let skill_dir = PathBuf::from(&home).join(config_dir).join("skills/dbg");
let version_file = skill_dir.join(".version");
if !version_file.exists() {
continue;
}
let installed = std::fs::read_to_string(&version_file).unwrap_or_default();
if installed.trim() == VERSION {
continue;
}
let agent_name = if config_dir == ".claude" { "Claude Code" } else { "Codex" };
let _ = init_agent(registry, config_dir, agent_name);
}
}
pub fn run_init(target: &str, registry: &Registry) -> Result<()> {
match target {
"claude" => init_agent(registry, ".claude", "Claude Code"),
"codex" => init_agent(registry, ".codex", "Codex"),
_ => bail!("unknown init target: {target} (use: claude, codex, agent-context)"),
}
}
pub fn emit_skill_yaml() {
if let Some(fm) = extract_frontmatter(SKILL_MD) {
println!("---\n{fm}\n---");
} else {
print!("{SKILL_MD}");
}
}
fn extract_frontmatter(doc: &str) -> Option<&str> {
let rest = doc.strip_prefix("---\n")?;
let end = rest.find("\n---\n")?;
Some(&rest[..end])
}
fn init_agent(registry: &Registry, config_dir: &str, agent_name: &str) -> Result<()> {
let home = std::env::var("HOME").context("HOME not set")?;
let skill_dir = PathBuf::from(&home).join(config_dir).join("skills/dbg");
let adapters_dir = skill_dir.join("references/adapters");
let adapters: Vec<(&str, &str)> = registry
.all_backends()
.iter()
.flat_map(|b| b.adapters())
.collect();
let version_file = skill_dir.join(".version");
#[cfg(not(debug_assertions))]
if version_file.exists() {
if let Ok(existing) = std::fs::read_to_string(&version_file) {
if existing.trim() == VERSION {
eprintln!("dbg v{VERSION} skill already up to date");
match ensure_on_path()? {
Some(path) => eprintln!("installed: {}", path.display()),
None => eprintln!("dbg already on PATH"),
}
return Ok(());
}
}
}
let _ = std::fs::remove_file(skill_dir.join(".hash"));
std::fs::create_dir_all(&adapters_dir)
.context("failed to create skill directory")?;
std::fs::write(skill_dir.join("SKILL.md"), SKILL_MD)?;
for (filename, content) in &adapters {
std::fs::write(adapters_dir.join(filename), content)?;
}
std::fs::write(&version_file, VERSION)?;
eprintln!("dbg v{VERSION} skill installed: {}", skill_dir.display());
match ensure_on_path()? {
Some(path) => eprintln!("installed: {}", path.display()),
None => eprintln!("dbg already on PATH"),
}
eprintln!("done — restart {agent_name} to activate");
Ok(())
}
pub fn ensure_on_path() -> Result<Option<PathBuf>> {
let bin_dir = dirs_home().join(".local/bin");
let dest = bin_dir.join("dbg");
let self_exe = std::env::current_exe()?;
let self_canon = std::fs::canonicalize(&self_exe)?;
if dest.exists() {
if let Ok(dest_canon) = std::fs::canonicalize(&dest) {
if dest_canon == self_canon {
return Ok(None);
}
}
}
if let Ok(found) = which::which("dbg") {
if let Ok(found_canon) = std::fs::canonicalize(&found) {
if found_canon == self_canon {
return Ok(None);
}
}
}
std::fs::create_dir_all(&bin_dir)?;
if dest.exists() || dest.is_symlink() {
std::fs::remove_file(&dest)?;
}
std::fs::copy(&self_exe, &dest)?;
#[cfg(unix)]
{
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(&dest, std::fs::Permissions::from_mode(0o755))?;
}
Ok(Some(dest))
}
fn dirs_home() -> PathBuf {
std::env::var("HOME")
.map(PathBuf::from)
.unwrap_or_else(|_| PathBuf::from("/tmp"))
}