mirror of
https://github.com/katanemo/plano.git
synced 2026-04-25 00:36:34 +02:00
add install.sh script and self-update command
This commit is contained in:
parent
1e6f38f772
commit
eb30c65796
3 changed files with 281 additions and 0 deletions
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
139
crates/plano-cli/src/commands/self_update.rs
Normal file
139
crates/plano-cli/src/commands/self_update.rs
Normal 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}"),
|
||||
}
|
||||
}
|
||||
132
install.sh
Executable file
132
install.sh
Executable file
|
|
@ -0,0 +1,132 @@
|
|||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# Plano CLI installer
|
||||
# Usage: curl -fsSL https://raw.githubusercontent.com/katanemo/plano/main/install.sh | bash
|
||||
|
||||
REPO="katanemo/archgw"
|
||||
BINARY_NAME="planoai"
|
||||
INSTALL_DIR="${PLANO_INSTALL_DIR:-$HOME/.plano/bin}"
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
CYAN='\033[0;36m'
|
||||
DIM='\033[2m'
|
||||
BOLD='\033[1m'
|
||||
RESET='\033[0m'
|
||||
|
||||
info() { echo -e "${GREEN}✓${RESET} $*"; }
|
||||
error() { echo -e "${RED}✗${RESET} $*" >&2; }
|
||||
dim() { echo -e "${DIM}$*${RESET}"; }
|
||||
|
||||
# Detect platform
|
||||
detect_platform() {
|
||||
local os arch
|
||||
os="$(uname -s)"
|
||||
arch="$(uname -m)"
|
||||
|
||||
case "$os" in
|
||||
Linux) os="linux" ;;
|
||||
Darwin) os="darwin" ;;
|
||||
*) error "Unsupported OS: $os"; exit 1 ;;
|
||||
esac
|
||||
|
||||
case "$arch" in
|
||||
x86_64) arch="amd64" ;;
|
||||
aarch64) arch="arm64" ;;
|
||||
arm64) arch="arm64" ;;
|
||||
*) error "Unsupported architecture: $arch"; exit 1 ;;
|
||||
esac
|
||||
|
||||
if [ "$os" = "darwin" ] && [ "$arch" = "amd64" ]; then
|
||||
error "macOS x86_64 (Intel) is not supported. Pre-built binaries are only available for Apple Silicon (arm64)."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "${os}-${arch}"
|
||||
}
|
||||
|
||||
# Get latest version from GitHub releases
|
||||
get_latest_version() {
|
||||
local version
|
||||
version=$(curl -fsSL "https://api.github.com/repos/${REPO}/releases/latest" \
|
||||
| grep '"tag_name"' \
|
||||
| sed -E 's/.*"([^"]+)".*/\1/')
|
||||
echo "$version"
|
||||
}
|
||||
|
||||
main() {
|
||||
echo -e "\n${BOLD}Plano CLI Installer${RESET}\n"
|
||||
|
||||
# Detect platform
|
||||
local platform
|
||||
platform="$(detect_platform)"
|
||||
dim " Platform: $platform"
|
||||
|
||||
# Get version
|
||||
local version="${PLANO_VERSION:-}"
|
||||
if [ -z "$version" ]; then
|
||||
dim " Fetching latest version..."
|
||||
version="$(get_latest_version)"
|
||||
fi
|
||||
if [ -z "$version" ]; then
|
||||
error "Could not determine version. Set PLANO_VERSION or check your internet connection."
|
||||
exit 1
|
||||
fi
|
||||
dim " Version: $version"
|
||||
|
||||
# Download URL
|
||||
local url="https://github.com/${REPO}/releases/download/${version}/planoai-${platform}.gz"
|
||||
dim " URL: $url"
|
||||
echo ""
|
||||
|
||||
# Create install directory
|
||||
mkdir -p "$INSTALL_DIR"
|
||||
|
||||
# Download and extract
|
||||
local tmp_gz
|
||||
tmp_gz="$(mktemp)"
|
||||
echo -e " ${DIM}Downloading planoai ${version}...${RESET}"
|
||||
|
||||
if ! curl -fSL --progress-bar "$url" -o "$tmp_gz"; then
|
||||
error "Download failed. Check that version $version exists for platform $platform."
|
||||
rm -f "$tmp_gz"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Decompress
|
||||
echo -e " ${DIM}Installing to ${INSTALL_DIR}/${BINARY_NAME}...${RESET}"
|
||||
gzip -d -c "$tmp_gz" > "${INSTALL_DIR}/${BINARY_NAME}"
|
||||
chmod +x "${INSTALL_DIR}/${BINARY_NAME}"
|
||||
rm -f "$tmp_gz"
|
||||
|
||||
info "Installed planoai ${version} to ${INSTALL_DIR}/${BINARY_NAME}"
|
||||
|
||||
# Check if install dir is in PATH
|
||||
if ! echo "$PATH" | tr ':' '\n' | grep -qx "$INSTALL_DIR"; then
|
||||
echo ""
|
||||
echo -e " ${CYAN}Add to your PATH:${RESET}"
|
||||
local shell_name
|
||||
shell_name="$(basename "${SHELL:-/bin/bash}")"
|
||||
local rc_file
|
||||
case "$shell_name" in
|
||||
zsh) rc_file="$HOME/.zshrc" ;;
|
||||
fish) rc_file="$HOME/.config/fish/config.fish" ;;
|
||||
*) rc_file="$HOME/.bashrc" ;;
|
||||
esac
|
||||
|
||||
if [ "$shell_name" = "fish" ]; then
|
||||
echo -e " ${BOLD}set -gx PATH ${INSTALL_DIR} \$PATH${RESET}"
|
||||
else
|
||||
echo -e " ${BOLD}export PATH=\"${INSTALL_DIR}:\$PATH\"${RESET}"
|
||||
fi
|
||||
echo -e " ${DIM}Add this line to ${rc_file} to make it permanent.${RESET}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
info "Run ${BOLD}planoai --help${RESET} to get started."
|
||||
echo ""
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Loading…
Add table
Add a link
Reference in a new issue