add install.sh script and self-update command

This commit is contained in:
Adil Hafeez 2026-03-22 23:05:13 +00:00
parent 1e6f38f772
commit eb30c65796
3 changed files with 281 additions and 0 deletions

View file

@ -3,6 +3,7 @@ pub mod cli_agent;
pub mod down;
pub mod init;
pub mod logs;
pub mod self_update;
pub mod up;
use clap::{Parser, Subcommand};
@ -132,6 +133,14 @@ pub enum Command {
#[arg(long)]
list_templates: bool,
},
/// Update planoai to the latest version
#[command(name = "self-update")]
SelfUpdate {
/// Update to a specific version instead of latest
#[arg(long)]
version: Option<String>,
},
}
#[derive(Subcommand)]
@ -252,6 +261,7 @@ pub async fn run(cli: Cli) -> anyhow::Result<()> {
force,
list_templates,
}) => init::run(template, clean, output, force, list_templates).await,
Some(Command::SelfUpdate { version }) => self_update::run(version.as_deref()).await,
}
}

View file

@ -0,0 +1,139 @@
use std::fs;
use std::os::unix::fs::PermissionsExt;
use anyhow::{bail, Result};
use crate::consts::{PLANO_GITHUB_REPO, PLANO_VERSION};
pub async fn run(target_version: Option<&str>) -> Result<()> {
let green = console::Style::new().green();
let bold = console::Style::new().bold();
let dim = console::Style::new().dim();
let cyan = console::Style::new().cyan();
println!(
"\n{} {}",
bold.apply_to("planoai"),
dim.apply_to("self-update")
);
// Determine target version
let version = if let Some(v) = target_version {
v.to_string()
} else {
println!(" {}", dim.apply_to("Checking for latest version..."));
fetch_latest_version()
.await?
.ok_or_else(|| anyhow::anyhow!("Could not determine latest version"))?
};
let current = PLANO_VERSION;
if version == current && target_version.is_none() {
println!(
"\n {} Already up to date ({})",
green.apply_to(""),
cyan.apply_to(current)
);
return Ok(());
}
println!(
" {} → {}",
dim.apply_to(format!("Current: {current}")),
cyan.apply_to(&version)
);
// Detect platform
let platform = get_platform_slug()?;
// Download URL
let url = format!(
"https://github.com/{PLANO_GITHUB_REPO}/releases/download/{version}/planoai-{platform}.gz"
);
println!(" {}", dim.apply_to(format!("Downloading from {url}...")));
let client = reqwest::Client::new();
let resp = client.get(&url).send().await?;
if !resp.status().is_success() {
bail!(
"Download failed: HTTP {}. Version {} may not exist for platform {}.",
resp.status(),
version,
platform
);
}
let gz_bytes = resp.bytes().await?;
// Decompress
let mut decoder = flate2::read::GzDecoder::new(&gz_bytes[..]);
let mut binary_data = Vec::new();
std::io::copy(&mut decoder, &mut binary_data)?;
// Find current binary path
let current_exe = std::env::current_exe()?;
let exe_path = current_exe.canonicalize()?;
println!(
" {}",
dim.apply_to(format!("Installing to {}", exe_path.display()))
);
// Write to a temp file next to the binary, then atomically rename
let tmp_path = exe_path.with_extension("update-tmp");
fs::write(&tmp_path, &binary_data)?;
fs::set_permissions(&tmp_path, fs::Permissions::from_mode(0o755))?;
// Atomic replace
fs::rename(&tmp_path, &exe_path)?;
println!(
"\n {} Updated planoai to {}\n",
green.apply_to(""),
bold.apply_to(&version)
);
Ok(())
}
async fn fetch_latest_version() -> Result<Option<String>> {
let url = format!("https://api.github.com/repos/{PLANO_GITHUB_REPO}/releases/latest");
let client = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(5))
.build()?;
let resp = client
.get(&url)
.header("User-Agent", "planoai-cli")
.send()
.await?;
if !resp.status().is_success() {
return Ok(None);
}
let json: serde_json::Value = resp.json().await?;
let tag = json
.get("tag_name")
.and_then(|v| v.as_str())
.map(|s| s.strip_prefix('v').unwrap_or(s).to_string());
Ok(tag)
}
fn get_platform_slug() -> Result<&'static str> {
let os = std::env::consts::OS;
let arch = std::env::consts::ARCH;
match (os, arch) {
("linux", "x86_64") => Ok("linux-amd64"),
("linux", "aarch64") => Ok("linux-arm64"),
("macos", "aarch64") => Ok("darwin-arm64"),
("macos", "x86_64") => {
bail!("macOS x86_64 (Intel) is not supported.")
}
_ => bail!("Unsupported platform: {os}/{arch}"),
}
}