use anyhow::{Context, Result, bail};
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
const INSTALL_SH: &str = "https://raw.githubusercontent.com/singudotdev/kanbrain/main/install.sh";
const INSTALL_PS1: &str = "https://raw.githubusercontent.com/singudotdev/kanbrain/main/install.ps1";
fn installed_app(dir: &Path) -> &'static str {
let has = |name: &str| dir.join(format!("{name}{}", std::env::consts::EXE_SUFFIX)).is_file();
match (has("kb"), has("kanbrain")) {
(true, true) => "both",
(false, true) => "gui",
_ => "cli",
}
}
fn installed_by_cargo(dir: &Path, cargo_home: Option<PathBuf>) -> bool {
let Some(home) = cargo_home.or_else(|| dirs::home_dir().map(|home| home.join(".cargo"))) else { return false };
let canonical = |path: &Path| path.canonicalize().unwrap_or_else(|_| path.to_path_buf());
canonical(dir) == canonical(&home.join("bin"))
}
pub fn update() -> Result<()> {
let exe = std::env::current_exe().context("finding this program's file")?;
let dir = exe.parent().context("finding this program's folder")?;
if installed_by_cargo(dir, std::env::var_os("CARGO_HOME").map(PathBuf::from)) {
println!(
"kb was installed with cargo: update it, and then every project's agent instructions, with\n\n \
cargo install kanbrain && kb init --all"
);
return Ok(());
}
let app = installed_app(dir);
if cfg!(windows) {
println!(
"Close kanbrain, then run in PowerShell:\n\n $env:KANBRAIN_APP = \"{app}\"; \
$env:KANBRAIN_INSTALL_DIR = \"{}\"; irm {INSTALL_PS1} | iex",
dir.display()
);
return Ok(());
}
let mut curl = Command::new("curl")
.args(["-fsSL", "--", INSTALL_SH])
.stdout(Stdio::piped())
.spawn()
.context("running curl, which downloads the installer")?;
let script = curl.stdout.take().context("reading curl's output")?;
let installed = Command::new("sh")
.args(["-s", "--", "--app", app, "--dir"])
.arg(dir)
.stdin(script)
.status()
.context("running the installer")?;
let downloaded = curl.wait().context("waiting for curl")?;
if !downloaded.success() {
bail!("couldn't download the installer from {INSTALL_SH}");
}
if !installed.success() {
bail!("the installer failed (its messages are above)");
}
Ok(())
}
#[cfg(test)]
#[path = "../../tests/cli/update.rs"]
mod tests;