feat: phase 2 quick wins (#65 #69 #72)

- #65: update policies + per-agent version pinning
  - add settings.update_policy (stable/latest/skip)
  - add AgentDef.pin_version
  - honor both in update_cmd
  - add update_policy tests

- #69: REPL color theme CLI command
  - add am theme [name] (show/switch)
  - persist selected theme to config

- #72: doctor checks local model runtimes
  - verify ollama and llama-server on PATH
  - for ollama, also check server responds (ollama ls)

Version 0.4.4
This commit is contained in:
2026-08-17 22:24:45 -04:00
parent 98c0b30f68
commit 890ad953e5
12 changed files with 229 additions and 10 deletions
+40 -2
View File
@@ -76,8 +76,7 @@ pub fn run(app: &App, runner: &dyn Runner) -> DoctorReport {
let empty = BTreeMap::new();
if let Ok(out) = runner.run(tool, &probe_args(tool), &empty, None, true) {
if out.code == 0 {
let combined = format!("{}
{}", out.stdout, out.stderr);
let combined = format!("{}\n{}", out.stdout, out.stderr);
if let Some(v) = crate::version::find_version(&combined) {
detail = format!("{} (v{v})", path.display());
}
@@ -113,6 +112,45 @@ pub fn run(app: &App, runner: &dyn Runner) -> DoctorReport {
}
}
// --- local model runtimes (issue #72) -------------------------------
for runtime in ["ollama", "llama-server"] {
if let Some(path) = runner.which(runtime) {
let mut detail = path.display().to_string();
let empty = BTreeMap::new();
if let Ok(out) = runner.run(runtime, &["--version".to_string()], &empty, None, true) {
if out.code == 0 {
let combined = format!("{}\n{}", out.stdout, out.stderr);
if let Some(v) = crate::version::find_version(&combined) {
detail = format!("{} (v{})", path.display(), v);
}
}
}
// For ollama, also check that the server answers.
let status = if runtime == "ollama" {
match runner.run(runtime, &["ls".to_string()], &empty, None, true) {
Ok(out) if out.code == 0 => Status::Ok,
_ => {
detail.push_str(" — server does not answer (ollama serve?)");
Status::Warn
}
}
} else {
Status::Ok
};
checks.push(Check {
name: runtime.to_string(),
status,
detail,
});
} else {
checks.push(Check {
name: runtime.to_string(),
status: Status::Ok,
detail: "not installed (optional)".to_string(),
});
}
}
// --- configuration ---------------------------------------------------
match &app.config_origin {
Some(path) => checks.push(Check {