#!/bin/sh # Basel installer: downloads the latest build, verifies it, installs it and opens it. # # curl -fsSL https://basel.stdio.art/install.sh | sh # # Options (environment): BASEL_DIR= installs elsewhere, BASEL_NO_OPEN=1 skips launching. # Everything runs inside main(), so a partly downloaded script does nothing. main() { set -eu BASE=${BASEL_BASE:-https://basel.stdio.art/download} T=${TMPDIR:-/tmp}; TMP=$(mktemp -d "${T%/}/basel-install.XXXXXX") trap 'rm -rf "$TMP"; printf "\033[?25h"' EXIT INT TERM if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then A=$(printf '\033[38;2;229;53;43m'); D=$(printf '\033[38;5;245m'); B=$(printf '\033[1m') R=$(printf '\033[0m'); TTY=1 else A=""; D=""; B=""; R=""; TTY=0 fi step() { printf " ${A}■${R} %-11s %s\n" "$1" "$2"; } ok() { printf " ${B}✓${R} %-11s %s\n" "$1" "$2"; } fail() { printf "\n ${A}✕${R} %s\n\n" "$1" >&2; exit 1; } printf "\n ${B}Basel${R} ${D}vector, image and motion for the mac${R}\n" printf " ${D}────────────────────────────────────────────${R}\n\n" [ "$(uname -s)" = "Darwin" ] || fail "Basel is a Mac app. This looks like $(uname -s)." OSV=$(sw_vers -productVersion) MAJOR=${OSV%%.*} [ "$MAJOR" -ge 15 ] || fail "Basel needs macOS 15 Sequoia or later (this Mac runs ${OSV})." case "$(uname -m)" in arm64) CHIP="Apple silicon" ;; *) CHIP="Intel" ;; esac [ "$(sysctl -n hw.optional.arm64 2>/dev/null || echo 0)" = "1" ] && CHIP="Apple silicon" step "mac" "macOS ${OSV} · ${CHIP}" curl -fsSL "$BASE/SHA256SUMS" -o "$TMP/SUMS" || fail "Couldn't reach basel.stdio.art. Check your connection." FILE=$(grep -Eo 'Basel-[0-9.]+\.zip' "$TMP/SUMS" | head -1) [ -n "$FILE" ] || fail "No build listed on the server. Try again in a minute." VERSION=$(printf '%s' "$FILE" | sed -E 's/Basel-([0-9.]+[0-9]).*/\1/') step "build" "Basel ${B}${VERSION}${R} ${D}(universal)${R}" SIZE=$(curl -fsSIL "$BASE/$FILE" | tr -d '\r' | awk 'tolower($1)=="content-length:"{n=$2} END{print n+0}') curl -fsSL "$BASE/$FILE" -o "$TMP/$FILE" & PID=$! if [ "$TTY" = 1 ] && [ "$SIZE" -gt 0 ]; then printf '\033[?25l' while kill -0 "$PID" 2>/dev/null; do bar "$TMP/$FILE" "$SIZE"; sleep 0.08; done bar "$TMP/$FILE" "$SIZE"; printf '\r\033[2K\033[?25h' fi wait "$PID" || fail "The download failed. Try again." ok "download" "$FILE ${D}$(mb "$(wc -c < "$TMP/$FILE")")${R}" WANT=$(grep " $FILE\$" "$TMP/SUMS" | awk '{print $1}') GOT=$(shasum -a 256 "$TMP/$FILE" | awk '{print $1}') [ -n "$WANT" ] && [ "$WANT" = "$GOT" ] || fail "Checksum mismatch: the download is damaged or was altered. Nothing was installed." ok "verified" "sha-256 ${D}$(printf '%s' "$GOT" | cut -c1-16)…${R}" DIR=${BASEL_DIR:-/Applications} if [ -z "${BASEL_DIR:-}" ] && ! [ -w /Applications ]; then DIR="$HOME/Applications"; fi mkdir -p "$DIR" ditto -x -k "$TMP/$FILE" "$TMP/app" || fail "Couldn't unpack the download." [ -d "$TMP/app/Basel.app" ] || fail "The download didn't contain Basel.app." if pgrep -xq Basel; then printf " ${A}!${R} %-11s %s" "running" "Basel is open. Quit it to update? [y/N] " ANSWER=n if [ -r /dev/tty ]; then read -r ANSWER < /dev/tty || ANSWER=n; else printf "\n"; fi case "$ANSWER" in y|Y|yes) osascript -e 'tell application "Basel" to quit' >/dev/null 2>&1 || true i=0; while pgrep -xq Basel && [ $i -lt 50 ]; do sleep 0.2; i=$((i+1)); done pgrep -xq Basel && fail "Basel is still open (unsaved changes?). Quit it and run the installer again." ;; *) fail "Left the running copy alone. Quit Basel and run the installer again." ;; esac fi [ -e "$DIR/Basel.app" ] && { rm -rf "$DIR/Basel.app.old"; mv "$DIR/Basel.app" "$DIR/Basel.app.old"; } mv "$TMP/app/Basel.app" "$DIR/Basel.app" || fail "Couldn't write to $DIR." rm -rf "$DIR/Basel.app.old" # Downloaded with curl, so macOS hasn't flagged it; clear the flag anyway in case of an old copy. xattr -dr com.apple.quarantine "$DIR/Basel.app" 2>/dev/null || true ok "installed" "$DIR/Basel.app" if [ -z "${BASEL_NO_OPEN:-}" ]; then open "$DIR/Basel.app" && ok "opened" "File ▸ New, or ask Claude (⌥⌘C)" fi printf "\n ${D}update any time by running the same command · agent server: http://127.0.0.1:4051/mcp${R}\n\n" } mb() { awk -v b="$1" 'BEGIN{printf "%.1f MB", b/1048576}'; } # Draws a 28-cell progress bar for the file being downloaded. bar() { have=0; [ -f "$1" ] && have=$(wc -c < "$1") awk -v h="$have" -v t="$2" -v a="$A" -v d="$D" -v r="$R" 'BEGIN{ p = t > 0 ? h / t : 0; if (p > 1) p = 1; w = 28; f = int(p * w + 0.5) s = ""; for (i = 0; i < w; i++) s = s (i < f ? a "█" r : d "░" r) printf "\r %s■%s %-11s %s %3d%% %s%.1f / %.1f MB%s", a, r, "download", s, p * 100, d, h/1048576, t/1048576, r }' } main "$@"