#!/bin/sh # Jessup installer - Kestrel version manager # # Usage: # curl --proto '=https' --tlsv1.2 -sSf https://kestrel-lang.com/install | sh # curl --proto '=https' --tlsv1.2 -sSf https://kestrel-lang.com/install | sh -s -- -y # # Options: # -y Skip the confirmation prompt # JESSUP_HOME Override install directory (default: ~/.jessup) # GITHUB_TOKEN Authenticate release downloads (private repos / rate limits) set -eu # ============================================================================ # CONFIGURATION # ============================================================================ JESSUP_HOME="${JESSUP_HOME:-$HOME/.jessup}" JESSUP_BIN="$JESSUP_HOME/bin" REPO="kestrellang/kestrel" SKIP_CONFIRM=no for _arg in "$@"; do case "$_arg" in -y) SKIP_CONFIRM=yes ;; *) printf "warning: unknown option '%s' ignored\n" "$_arg" >&2 ;; esac done # ============================================================================ # COLORS # ============================================================================ if [ -t 1 ]; then BOLD='\033[1m' GREEN='\033[0;32m' YELLOW='\033[0;33m' RED='\033[0;31m' CYAN='\033[0;36m' RESET='\033[0m' else BOLD='' GREEN='' YELLOW='' RED='' CYAN='' RESET='' fi # ============================================================================ # HELPERS # ============================================================================ info() { printf "${GREEN}info:${RESET} %s\n" "$1" } warn() { printf "${YELLOW}warn:${RESET} %s\n" "$1" } err() { printf "${RED}error:${RESET} %s\n" "$1" >&2 exit 1 } need_cmd() { if ! command -v "$1" > /dev/null 2>&1; then err "need '$1' (command not found)" fi } # download URL DEST — HTTPS-only, TLS 1.2+, fails on HTTP errors (a 404 page # must never be saved as the archive), retries transient failures. An optional # GITHUB_TOKEN is passed as a real argv entry so the shell never re-parses it. download() { if [ -n "${GITHUB_TOKEN:-}" ]; then set -- -H "Authorization: Bearer $GITHUB_TOKEN" -o "$2" "$1" else set -- -o "$2" "$1" fi curl --proto '=https' --tlsv1.2 -fsSL --retry 3 "$@" } # ============================================================================ # PLATFORM DETECTION # ============================================================================ detect_platform() { local _os _arch _target _os="$(uname -s)" _arch="$(uname -m)" case "$_os" in Darwin) _os="apple-darwin" ;; Linux) _os="unknown-linux" ;; *) err "unsupported operating system: $_os" ;; esac case "$_arch" in arm64 | aarch64) _arch="aarch64" ;; x86_64) _arch="x86_64" ;; *) err "unsupported architecture: $_arch" ;; esac PLATFORM="${_arch}-${_os}" } # ============================================================================ # MAIN # ============================================================================ main() { need_cmd curl need_cmd tar need_cmd uname need_cmd mkdir need_cmd chmod detect_platform printf "\n" printf "${BOLD}${CYAN} Jessup - Kestrel Version Manager${RESET}\n" printf "\n" printf " This will install jessup and the latest preview kestrel toolchain.\n" printf "\n" printf " Install location: ${BOLD}%s${RESET}\n" "$JESSUP_HOME" printf " Platform: ${BOLD}%s${RESET}\n" "$PLATFORM" printf "\n" # Confirmation prompt. Under `curl | sh` stdin is the script itself, so # read from the terminal; with no terminal (CI), proceed unprompted. if [ "$SKIP_CONFIRM" != "yes" ] && [ -r /dev/tty ]; then printf " Proceed with installation? [Y/n] " if read -r _confirm < /dev/tty; then case "$_confirm" in [nN]*) info "Installation cancelled" exit 0 ;; esac fi printf "\n" fi # Create directories info "Creating $JESSUP_HOME..." mkdir -p "$JESSUP_BIN" mkdir -p "$JESSUP_HOME/toolchains" # Download jessup binary info "Downloading jessup for $PLATFORM..." local _url _archive _tmpdir="$(mktemp -d)" _archive="$_tmpdir/jessup.tar.gz" trap 'rm -rf "$_tmpdir"' EXIT # Get the latest release info and find the jessup asset _url="https://github.com/$REPO/releases/latest/download/jessup-${PLATFORM}.tar.gz" if ! download "$_url" "$_archive"; then err "failed to download jessup from $_url" fi # Extract jessup binary (strip the top-level directory) tar xzf "$_archive" -C "$_tmpdir" --strip-components=1 2>/dev/null || { err "failed to extract jessup archive" } # Install jessup binary if [ -f "$_tmpdir/jessup" ]; then mv "$_tmpdir/jessup" "$JESSUP_BIN/jessup" else err "jessup binary not found in archive" fi chmod +x "$JESSUP_BIN/jessup" info "Installed jessup to $JESSUP_BIN/jessup" # Add to PATH add_to_path # Install latest preview toolchain (the default channel during 0.x; # switch to `stable` once 1.0 ships). info "Installing latest preview toolchain..." "$JESSUP_BIN/jessup" install preview # Optional integrations (editor extension, AI agent plugins). Printed # before the success banner so the banner's "listed above" is accurate. print_integrations printf "\n" printf "${BOLD}${GREEN} Jessup installed successfully!${RESET}\n" printf "\n" printf " To get started, either restart your shell or run:\n" printf "\n" printf " ${BOLD}export PATH=\"%s:\$PATH\"${RESET}\n" "$JESSUP_BIN" printf "\n" printf " Then:\n" printf "\n" printf " ${BOLD}kestrel --help${RESET} Run the Kestrel compiler\n" printf " ${BOLD}jessup list${RESET} Show installed toolchains\n" printf " ${BOLD}jessup --help${RESET} Show jessup commands\n" printf "\n" printf " ${BOLD}Optional integrations${RESET} (editor extension, Claude Code / Codex\n" printf " plugin) were listed above with their install commands.\n" printf "\n" } # ============================================================================ # OPTIONAL INTEGRATIONS # ============================================================================ # Print install commands for the editor extension and AI agent plugins. # Commands are always shown; when a host tool is on PATH we mark it "detected" # so the user knows which one to run, but we never assume what they want. print_integrations() { printf "\n" printf "${BOLD}${CYAN} Optional integrations${RESET}\n" printf "\n" # --- Claude Code plugin (skill + kestrel-lsp config) --- if command -v claude > /dev/null 2>&1; then printf " ${BOLD}Claude Code plugin${RESET} ${GREEN}(claude detected)${RESET}\n" else printf " ${BOLD}Claude Code plugin${RESET}\n" fi printf " ${BOLD}claude plugin marketplace add kestrellang/kestrel-plugin${RESET}\n" printf " ${BOLD}claude plugin install kestrel-plugin@kestrel${RESET}\n" printf "\n" # --- Codex plugin --- if command -v codex > /dev/null 2>&1; then printf " ${BOLD}Codex plugin${RESET} ${GREEN}(codex detected)${RESET}\n" else printf " ${BOLD}Codex plugin${RESET}\n" fi printf " ${BOLD}codex plugin marketplace add kestrellang/kestrel-plugin${RESET}\n" printf " ${BOLD}codex plugin add kestrel-plugin@kestrel${RESET}\n" printf "\n" # --- Editor extension (VS Code / Cursor) --- # Ships as a .vsix on the kestrel-vscode releases page; download the build # for your platform first, then install it with your editor's CLI. if command -v code > /dev/null 2>&1; then printf " ${BOLD}VS Code / Cursor extension${RESET} ${GREEN}(code detected)${RESET}\n" else printf " ${BOLD}VS Code / Cursor extension${RESET}\n" fi printf " Download the .vsix for your platform from:\n" printf " ${BOLD}https://github.com/kestrellang/kestrel-vscode/releases/latest${RESET}\n" printf " then install it:\n" printf " ${BOLD}code --install-extension path/to/kestrel-.vsix${RESET}\n" printf "\n" } # ============================================================================ # PATH SETUP # ============================================================================ add_to_path() { local _path_entry _path_entry="export PATH=\"$JESSUP_BIN:\$PATH\"" # NOTE: do NOT early-return when $JESSUP_BIN is already on the current # $PATH. Being on PATH for *this* shell (a reinstall, or a parent process # that exported it) does not mean the entry is persisted in a profile — # skipping the write here is exactly how a fresh terminal ends up with the # binaries installed but reporting "command not found". The per-profile # grep below keeps appends idempotent, so writing unconditionally is safe. # Try to add to shell profile local _added=false for _profile in "$HOME/.zshrc" "$HOME/.bashrc" "$HOME/.profile"; do if [ -f "$_profile" ]; then # Check if already added if ! grep -q "jessup" "$_profile" 2>/dev/null; then printf "\n# Jessup (Kestrel version manager)\n%s\n" "$_path_entry" >> "$_profile" info "Added jessup to PATH in $_profile" _added=true fi fi done if [ "$_added" = "false" ]; then # Create .profile if nothing exists if [ ! -f "$HOME/.profile" ] && [ ! -f "$HOME/.bashrc" ] && [ ! -f "$HOME/.zshrc" ]; then printf "# Jessup (Kestrel version manager)\n%s\n" "$_path_entry" >> "$HOME/.profile" info "Added jessup to PATH in $HOME/.profile" fi fi } main "$@"