#!/usr/bin/env bash # Post-deploy smoke harness for the muse CLI. # # Installs the published tarball into a throwaway venv and runs 18 checks # against the installed binary. The dev editable install is never touched. # # Usage: # bash deploy/smoke_muse.sh # staging, auto version # bash deploy/smoke_muse.sh --url https://musehub.ai # prod # bash deploy/smoke_muse.sh --version 0.2.0rc9 # explicit version # # Exit codes: # 0 All checks passed. # 1 One or more checks failed. # 2 Setup error (tarball not found, venv creation failed, etc.). set -euo pipefail MUSE_REPO="${MUSE_REPO:-$HOME/ecosystem/muse}" # ── Defaults ────────────────────────────────────────────────────────────────── HUB_URL="https://staging.musehub.ai" MUSE_VERSION="" # ── Arg parse ───────────────────────────────────────────────────────────────── while [[ $# -gt 0 ]]; do case "$1" in --url) HUB_URL="$2"; shift 2 ;; --version) MUSE_VERSION="$2"; shift 2 ;; *) echo "Unknown argument: $1" >&2; exit 2 ;; esac done # ── Resolve version ─────────────────────────────────────────────────────────── if [[ -z "$MUSE_VERSION" ]]; then MUSE_VERSION=$(python3 -c " import re, pathlib t = pathlib.Path('$MUSE_REPO/pyproject.toml').read_text() m = re.search(r'^version\s*=\s*\"([^\"]+)\"', t, re.MULTILINE) print(m.group(1)) ") fi TARBALL_URL="${HUB_URL}/releases/muse-${MUSE_VERSION}.tar.gz" # ── Colours ─────────────────────────────────────────────────────────────────── GREEN='\033[0;32m' RED='\033[0;31m' BOLD='\033[1m' DIM='\033[2m' RESET='\033[0m' pass() { printf " ${GREEN}✅${RESET} %-22s %s\n" "$1" "$2"; } fail() { printf " ${RED}❌${RESET} %-22s %s\n" "$1" "$2"; } info() { printf " ${DIM}%s${RESET}\n" "$1"; } # ── Shared state ────────────────────────────────────────────────────────────── PASSED=0 FAILED=0 SMOKE_VENV="" SMOKE_REPO="" SMOKE_TARBALL="" # ── Cleanup (always runs) ───────────────────────────────────────────────────── cleanup() { [[ -n "$SMOKE_REPO" ]] && rm -rf "$SMOKE_REPO" [[ -n "$SMOKE_TARBALL" ]] && rm -f "$SMOKE_TARBALL" [[ -n "$SMOKE_VENV" ]] && rm -rf "$SMOKE_VENV" } trap cleanup EXIT # ── Check helpers ───────────────────────────────────────────────────────────── _ts() { python3 -c "import time; print(time.monotonic())"; } _ms() { python3 -c "print(f'{($2-$1)*1000:.0f}ms')"; } check_exit() { local name="$1"; shift local t0 t1 ms rc _out _err _tmp_o _tmp_e _tmp_o=$(mktemp); _tmp_e=$(mktemp) t0=$(_ts) "$@" > "$_tmp_o" 2> "$_tmp_e" && rc=0 || rc=$? t1=$(_ts); ms=$(_ms "$t0" "$t1") _out=$(cat "$_tmp_o"); _err=$(cat "$_tmp_e") rm -f "$_tmp_o" "$_tmp_e" if [[ $rc -eq 0 ]]; then pass "$name" "$ms"; (( PASSED++ )) || true else fail "$name" "exit $rc — $ms — ${_err:0:120}${_out:0:80}" (( FAILED++ )) || true fi } check_json() { local name="$1" expr="$2" expected="$3"; shift 3 local t0 t1 ms rc actual _out _err _tmp_o _tmp_e _tmp_o=$(mktemp); _tmp_e=$(mktemp) t0=$(_ts) "$@" > "$_tmp_o" 2> "$_tmp_e" && rc=0 || rc=$? t1=$(_ts); ms=$(_ms "$t0" "$t1") _out=$(cat "$_tmp_o"); _err=$(cat "$_tmp_e") rm -f "$_tmp_o" "$_tmp_e" if [[ $rc -ne 0 ]]; then fail "$name" "exit $rc — $ms — ${_err:0:120}" (( FAILED++ )) || true; return fi actual=$(echo "$_out" | python3 -c " import sys, json d = json.load(sys.stdin) result = $expr print(str(result).lower() if isinstance(result, bool) else result) " 2>&1) && true if [[ "$actual" == "$expected" ]]; then pass "$name" "$ms"; (( PASSED++ )) || true else fail "$name" "expected '$expected' got '${actual:0:100}' — $ms" (( FAILED++ )) || true fi } check_contains() { local name="$1" needle="$2"; shift 2 local t0 t1 ms rc _out _err _tmp_o _tmp_e _tmp_o=$(mktemp); _tmp_e=$(mktemp) t0=$(_ts) "$@" > "$_tmp_o" 2> "$_tmp_e" && rc=0 || rc=$? t1=$(_ts); ms=$(_ms "$t0" "$t1") _out=$(cat "$_tmp_o"); _err=$(cat "$_tmp_e") rm -f "$_tmp_o" "$_tmp_e" if [[ $rc -ne 0 ]]; then fail "$name" "exit $rc — $ms — ${_err:0:120}" (( FAILED++ )) || true; return fi if echo "$_out" | grep -qF "$needle"; then pass "$name" "$ms"; (( PASSED++ )) || true else fail "$name" "needle '$needle' not found — $ms" (( FAILED++ )) || true fi } check_json_contains() { local name="$1" key="$2" needle="$3"; shift 3 local t0 t1 ms rc actual _out _err _tmp_o _tmp_e _tmp_o=$(mktemp); _tmp_e=$(mktemp) t0=$(_ts) "$@" > "$_tmp_o" 2> "$_tmp_e" && rc=0 || rc=$? t1=$(_ts); ms=$(_ms "$t0" "$t1") _out=$(cat "$_tmp_o"); _err=$(cat "$_tmp_e") rm -f "$_tmp_o" "$_tmp_e" if [[ $rc -ne 0 ]]; then fail "$name" "exit $rc — $ms — ${_err:0:120}" (( FAILED++ )) || true; return fi actual=$(echo "$_out" | python3 -c " import sys, json d = json.load(sys.stdin) items = d.get('$key', d) if isinstance(d, dict) else d needle = '$needle' found = any(needle in str(item) for item in (items if isinstance(items, list) else [items])) print('true' if found else 'false') " 2>&1) if [[ "$actual" == "true" ]]; then pass "$name" "$ms"; (( PASSED++ )) || true else fail "$name" "needle '$needle' not found in .$key — $ms" (( FAILED++ )) || true fi } # ── Setup ───────────────────────────────────────────────────────────────────── printf "\n${BOLD}[smoke] muse ${MUSE_VERSION} url=${HUB_URL}${RESET}\n\n" printf " Downloading %s …\n" "$TARBALL_URL" SMOKE_TARBALL=$(mktemp /tmp/muse-smoke-XXXXXX.tar.gz) HTTP=$(curl -fsSL --write-out "%{http_code}" -o "$SMOKE_TARBALL" "$TARBALL_URL" 2>&1) && true if [[ "$HTTP" != "200" ]]; then printf "${RED}ERROR:${RESET} tarball download failed (HTTP %s) — %s\n" "$HTTP" "$TARBALL_URL" >&2 exit 2 fi printf " Downloaded %s\n" "$(du -sh "$SMOKE_TARBALL" | cut -f1)" printf " Installing into throwaway venv …\n" SMOKE_VENV=$(mktemp -d /tmp/muse-smoke-venv-XXXXXX) python3 -m venv "$SMOKE_VENV" "$SMOKE_VENV/bin/pip" install --quiet --disable-pip-version-check "$SMOKE_TARBALL" 2>/dev/null MUSE="$SMOKE_VENV/bin/muse" SMOKE_REPO=$(mktemp -d /tmp/muse-smoke-repo-XXXXXX) printf "\n" # ── Check corpus ────────────────────────────────────────────────────────────── printf "${BOLD}local CLI checks${RESET}\n\n" TOTAL_START=$(_ts) check_contains "version" "$MUSE_VERSION" "$MUSE" --version check_exit "init" "$MUSE" init --json "$SMOKE_REPO" M() { "$MUSE" -C "$SMOKE_REPO" "$@"; } check_exit "add_empty" M code add . echo "muse smoke test file — $(date -u)" > "$SMOKE_REPO/smoke.txt" check_exit "add_file" M code add smoke.txt check_exit "commit" M commit -m "smoke" --agent-id smoke --model-id smoke check_json "status_clean" 'd["clean"]' "true" M status --json check_json "log_count" 'len(d["commits"])' "1" M log --json check_json "read_message" 'd["message"]' "smoke" M read --json check_json_contains "ls_files" "files" "smoke.txt" M ls-files --json check_json "branch_count" 'len(d)' "1" M branch --json check_json "branch_current" 'd[0]["current"]' "true" M branch --json check_contains "diff_clean" "" M diff check_exit "checkout_b" M checkout -b smoke/branch check_exit "checkout_main" M checkout main check_exit "branch_delete" M branch -d smoke/branch check_exit "tag_add" M tag add "smoke:v1" check_json_contains "tag_list" "tags" "smoke:v1" M tag list --json check_json "verify" 'd["all_ok"]' "true" M verify --json # ── Summary ─────────────────────────────────────────────────────────────────── TOTAL_END=$(_ts) TOTAL_MS=$(_ms "$TOTAL_START" "$TOTAL_END") TOTAL=$(( PASSED + FAILED )) printf "\n" if [[ $FAILED -eq 0 ]]; then printf "${GREEN}${BOLD}[smoke] PASSED %d/%d total=%s${RESET}\n\n" "$PASSED" "$TOTAL" "$TOTAL_MS" exit 0 else printf "${RED}${BOLD}[smoke] FAILED %d/%d total=%s${RESET}\n\n" "$PASSED" "$TOTAL" "$TOTAL_MS" exit 1 fi