#!/usr/bin/env bash set -euo pipefail SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" TARGET_DIR="${MOSAIC_HOME:-$HOME/.config/mosaic}" INSTALL_MODE="${MOSAIC_INSTALL_MODE:-prompt}" # prompt|keep|overwrite PRESERVE_PATHS=("SOUL.md" "USER.md" "TOOLS.md" "memory") # Colors (disabled if not a terminal) if [[ -t 1 ]]; then GREEN='\033[0;32m' YELLOW='\033[0;33m' RED='\033[0;31m' CYAN='\033[0;36m' BOLD='\033[1m' RESET='\033[0m' else GREEN='' YELLOW='' RED='' CYAN='' BOLD='' RESET='' fi ok() { echo -e " ${GREEN}✓${RESET} $1"; } warn() { echo -e " ${YELLOW}⚠${RESET} $1" >&2; } fail() { echo -e " ${RED}✗${RESET} $1" >&2; } step() { echo -e "\n${BOLD}$1${RESET}"; } is_existing_install() { [[ -d "$TARGET_DIR" ]] || return 1 [[ -f "$TARGET_DIR/bin/mosaic" || -f "$TARGET_DIR/AGENTS.md" || -f "$TARGET_DIR/SOUL.md" ]] } select_install_mode() { case "$INSTALL_MODE" in keep|overwrite|prompt) ;; *) fail "Invalid MOSAIC_INSTALL_MODE='$INSTALL_MODE'. Use: prompt, keep, overwrite." exit 1 ;; esac if ! is_existing_install; then INSTALL_MODE="overwrite" return fi case "$INSTALL_MODE" in keep|overwrite) ;; prompt) if [[ -t 0 ]]; then echo "" echo "Existing Mosaic install detected at: $TARGET_DIR" echo "Choose reinstall mode:" echo " 1) keep Keep local files (SOUL.md, USER.md, TOOLS.md, memory/) while updating framework" echo " 2) overwrite Replace everything in $TARGET_DIR" echo " 3) cancel Abort install" printf "Selection [1/2/3] (default: 1): " read -r selection case "${selection:-1}" in 1|k|K|keep|KEEP) INSTALL_MODE="keep" ;; 2|o|O|overwrite|OVERWRITE) INSTALL_MODE="overwrite" ;; 3|c|C|cancel|CANCEL|n|N|no|NO) fail "Install cancelled." exit 1 ;; *) warn "Unrecognized selection '$selection'; defaulting to keep." INSTALL_MODE="keep" ;; esac else warn "Existing install detected without interactive input; defaulting to keep local files." INSTALL_MODE="keep" fi ;; esac } sync_framework() { local source_real target_real source_real="$(cd "$SOURCE_DIR" && pwd -P)" target_real="$(mkdir -p "$TARGET_DIR" && cd "$TARGET_DIR" && pwd -P)" if [[ "$source_real" == "$target_real" ]]; then warn "Source and target are the same directory; skipping file sync." return fi if command -v rsync >/dev/null 2>&1; then local rsync_args=(-a --delete --exclude ".git") if [[ "$INSTALL_MODE" == "keep" ]]; then local path for path in "${PRESERVE_PATHS[@]}"; do rsync_args+=(--exclude "$path") done fi rsync "${rsync_args[@]}" "$SOURCE_DIR/" "$TARGET_DIR/" return fi local preserve_tmp="" if [[ "$INSTALL_MODE" == "keep" ]]; then preserve_tmp="$(mktemp -d "${TMPDIR:-/tmp}/mosaic-preserve-XXXXXX")" local path for path in "${PRESERVE_PATHS[@]}"; do if [[ -e "$TARGET_DIR/$path" ]]; then mkdir -p "$preserve_tmp/$(dirname "$path")" cp -R "$TARGET_DIR/$path" "$preserve_tmp/$path" fi done fi find "$TARGET_DIR" -mindepth 1 -maxdepth 1 ! -name ".git" -exec rm -rf {} + cp -R "$SOURCE_DIR"/. "$TARGET_DIR"/ rm -rf "$TARGET_DIR/.git" if [[ -n "$preserve_tmp" ]]; then local path for path in "${PRESERVE_PATHS[@]}"; do if [[ -e "$preserve_tmp/$path" ]]; then rm -rf "$TARGET_DIR/$path" mkdir -p "$TARGET_DIR/$(dirname "$path")" cp -R "$preserve_tmp/$path" "$TARGET_DIR/$path" fi done rm -rf "$preserve_tmp" fi } step "Installing Mosaic framework" mkdir -p "$TARGET_DIR" select_install_mode if [[ "$INSTALL_MODE" == "keep" ]]; then ok "Install mode: keep local SOUL.md/USER.md/TOOLS.md/memory while updating framework" else ok "Install mode: overwrite existing files" fi sync_framework # Ensure memory directory exists (preserved across upgrades, may not exist on fresh install) mkdir -p "$TARGET_DIR/memory" chmod +x "$TARGET_DIR"/bin/* chmod +x "$TARGET_DIR"/install.sh # Ensure tool scripts are executable find "$TARGET_DIR/tools" -name "*.sh" -exec chmod +x {} + 2>/dev/null || true # Create backward-compat symlink: rails/ → tools/ if [[ -d "$TARGET_DIR/tools" ]]; then if [[ -d "$TARGET_DIR/rails" ]] && [[ ! -L "$TARGET_DIR/rails" ]]; then rm -rf "$TARGET_DIR/rails" fi ln -sfn "tools" "$TARGET_DIR/rails" fi ok "Framework installed to $TARGET_DIR" step "Post-install tasks" if "$TARGET_DIR/bin/mosaic-link-runtime-assets" >/dev/null 2>&1; then ok "Runtime assets linked" else warn "Runtime asset linking failed (non-fatal)" fi if "$TARGET_DIR/bin/mosaic-ensure-sequential-thinking" >/dev/null 2>&1; then ok "sequential-thinking MCP configured" else if [[ "${MOSAIC_ALLOW_MISSING_SEQUENTIAL_THINKING:-0}" == "1" ]]; then warn "sequential-thinking MCP setup failed but bypassed (MOSAIC_ALLOW_MISSING_SEQUENTIAL_THINKING=1)" else fail "sequential-thinking MCP setup failed (hard requirement)." fail "Set MOSAIC_ALLOW_MISSING_SEQUENTIAL_THINKING=1 only for temporary bypass scenarios." exit 1 fi fi if [[ "${MOSAIC_SKIP_SKILLS_SYNC:-0}" == "1" ]]; then ok "Skills sync skipped (MOSAIC_SKIP_SKILLS_SYNC=1)" else if "$TARGET_DIR/bin/mosaic-sync-skills" >/dev/null 2>&1; then ok "Skills synced" else warn "Skills sync failed (non-fatal)" fi fi if "$TARGET_DIR/bin/mosaic-migrate-local-skills" --apply >/dev/null 2>&1; then ok "Local skills migrated" else warn "Local skill migration failed (non-fatal)" fi if "$TARGET_DIR/bin/mosaic-doctor" >/dev/null 2>&1; then ok "Health audit passed" else warn "Health audit reported issues — run 'mosaic doctor' for details" fi step "PATH configuration" PATH_LINE="export PATH=\"$TARGET_DIR/bin:\$PATH\"" # Find the right shell profile if [[ -n "${ZSH_VERSION:-}" ]] || [[ "$(basename "${SHELL:-}")" == "zsh" ]]; then SHELL_PROFILE="$HOME/.zshrc" elif [[ -f "$HOME/.bashrc" ]]; then SHELL_PROFILE="$HOME/.bashrc" elif [[ -f "$HOME/.profile" ]]; then SHELL_PROFILE="$HOME/.profile" else SHELL_PROFILE="$HOME/.profile" fi PATH_CHANGED=false if grep -qF "$TARGET_DIR/bin" "$SHELL_PROFILE" 2>/dev/null; then ok "Already in PATH via $SHELL_PROFILE" else { echo "" echo "# Mosaic agent framework" echo "$PATH_LINE" } >> "$SHELL_PROFILE" ok "Added to PATH in $SHELL_PROFILE" PATH_CHANGED=true fi # ── Summary ────────────────────────────────────────────────── echo "" echo -e "${GREEN}${BOLD} Mosaic installed successfully.${RESET}" echo "" # Collect next steps NEXT_STEPS=() if [[ "$PATH_CHANGED" == "true" ]]; then NEXT_STEPS+=("Run ${CYAN}source $SHELL_PROFILE${RESET} or log out and back in to activate PATH.") fi if [[ ! -f "$TARGET_DIR/SOUL.md" ]]; then NEXT_STEPS+=("Run ${CYAN}mosaic init${RESET} to set up your agent identity (SOUL.md), user profile (USER.md), and tool config (TOOLS.md).") elif grep -q "not configured" "$TARGET_DIR/USER.md" 2>/dev/null; then NEXT_STEPS+=("Run ${CYAN}mosaic init${RESET} to personalize your user profile (USER.md) and tool config (TOOLS.md).") fi if [[ ${#NEXT_STEPS[@]} -gt 0 ]]; then echo -e " ${BOLD}Next steps:${RESET}" for i in "${!NEXT_STEPS[@]}"; do echo -e " $((i+1)). ${NEXT_STEPS[$i]}" done echo "" fi