BREAKING CHANGE: ~/.config/mosaic/bin/ is removed entirely. The mosaic npm CLI is now the only executable. ## What changed - **bin/ → deleted**: All scripts moved to tools/_scripts/ (internal) - **mosaic-launch → deleted**: Launcher logic is native TypeScript in packages/cli/src/commands/launch.ts - **mosaic.ps1 → deleted**: PowerShell launcher removed - **Framework install.sh**: Complete rewrite with migration system - **Version tracking**: .framework-version file (schema v2) - **Migration v1→v2**: Auto-removes bin/, cleans old PATH entries from shell profiles ## Native TypeScript launcher (commands/launch.ts) All runtime launch logic ported from bash: - Runtime prompt builder (AGENTS.md + RUNTIME.md + USER.md + TOOLS.md) - Mission context injection (reads .mosaic/orchestrator/mission.json) - PRD status injection (scans docs/PRD.md) - Pre-flight checks (MOSAIC_HOME, AGENTS.md, SOUL.md, runtime binary) - Session lock management with signal cleanup - Per-runtime launch: Claude, Codex, OpenCode, Pi - Yolo mode flags per runtime - Pi skill discovery + extension loading - Framework management (init, doctor, sync, bootstrap) delegates to tools/_scripts/ bash implementations ## Installer - tools/install.sh: detects framework by .framework-version or AGENTS.md - Framework install.sh: migration system with schema versioning - Forward-compatible: add migrations as numbered blocks - No PATH manipulation for framework (npm bin is the only PATH entry)
285 lines
10 KiB
Bash
Executable File
285 lines
10 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
# ─── Mosaic Framework Installer ──────────────────────────────────────────────
|
|
#
|
|
# Installs/upgrades the framework DATA to ~/.config/mosaic/.
|
|
# No executables are placed on PATH — the mosaic npm CLI is the only binary.
|
|
#
|
|
# Called by tools/install.sh (the unified installer). Can also be run directly.
|
|
#
|
|
# Environment:
|
|
# MOSAIC_HOME — target directory (default: ~/.config/mosaic)
|
|
# MOSAIC_INSTALL_MODE — prompt|keep|overwrite (default: prompt)
|
|
# MOSAIC_ALLOW_MISSING_SEQUENTIAL_THINKING — 1 to bypass MCP check
|
|
# MOSAIC_SKIP_SKILLS_SYNC — 1 to skip skill sync
|
|
# ──────────────────────────────────────────────────────────────────────────────
|
|
|
|
SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
TARGET_DIR="${MOSAIC_HOME:-$HOME/.config/mosaic}"
|
|
INSTALL_MODE="${MOSAIC_INSTALL_MODE:-prompt}"
|
|
|
|
# Files preserved across upgrades (never overwritten)
|
|
PRESERVE_PATHS=("SOUL.md" "USER.md" "TOOLS.md" "memory" "sources")
|
|
|
|
# Current framework schema version — bump this when the layout changes.
|
|
# The migration system uses this to run upgrade steps.
|
|
FRAMEWORK_VERSION=2
|
|
|
|
# ─── colours ──────────────────────────────────────────────────────────────────
|
|
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}"; }
|
|
|
|
# ─── helpers ──────────────────────────────────────────────────────────────────
|
|
|
|
is_existing_install() {
|
|
[[ -d "$TARGET_DIR" ]] || return 1
|
|
[[ -f "$TARGET_DIR/AGENTS.md" || -f "$TARGET_DIR/SOUL.md" ]]
|
|
}
|
|
|
|
installed_framework_version() {
|
|
local vf="$TARGET_DIR/.framework-version"
|
|
if [[ -f "$vf" ]]; then
|
|
cat "$vf" 2>/dev/null || echo "0"
|
|
else
|
|
# No version file = legacy install (version 0 or 1)
|
|
if [[ -d "$TARGET_DIR/bin" ]]; then
|
|
echo "1" # Has bin/ → pre-migration legacy
|
|
else
|
|
echo "0" # Fresh or unknown
|
|
fi
|
|
fi
|
|
}
|
|
|
|
write_framework_version() {
|
|
echo "$FRAMEWORK_VERSION" > "$TARGET_DIR/.framework-version"
|
|
}
|
|
|
|
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 " 1) keep Update framework, preserve local files (SOUL.md, USER.md, etc.)"
|
|
echo " 2) overwrite Replace everything"
|
|
echo " 3) cancel Abort"
|
|
printf "Selection [1/2/3] (default: 1): "
|
|
read -r selection
|
|
case "${selection:-1}" in
|
|
1|k|K|keep) INSTALL_MODE="keep" ;;
|
|
2|o|O|overwrite) INSTALL_MODE="overwrite" ;;
|
|
*) fail "Install cancelled."; exit 1 ;;
|
|
esac
|
|
else
|
|
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" --exclude ".framework-version")
|
|
|
|
if [[ "$INSTALL_MODE" == "keep" ]]; then
|
|
for path in "${PRESERVE_PATHS[@]}"; do
|
|
rsync_args+=(--exclude "$path")
|
|
done
|
|
fi
|
|
|
|
rsync "${rsync_args[@]}" "$SOURCE_DIR/" "$TARGET_DIR/"
|
|
return
|
|
fi
|
|
|
|
# Fallback: cp-based sync
|
|
local preserve_tmp=""
|
|
if [[ "$INSTALL_MODE" == "keep" ]]; then
|
|
preserve_tmp="$(mktemp -d "${TMPDIR:-/tmp}/mosaic-preserve-XXXXXX")"
|
|
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" ! -name ".framework-version" -exec rm -rf {} +
|
|
cp -R "$SOURCE_DIR"/. "$TARGET_DIR"/
|
|
rm -rf "$TARGET_DIR/.git"
|
|
|
|
if [[ -n "$preserve_tmp" ]]; then
|
|
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
|
|
}
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
# Migrations — run sequentially from the installed version to FRAMEWORK_VERSION
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
run_migrations() {
|
|
local from_version
|
|
from_version="$(installed_framework_version)"
|
|
|
|
if [[ "$from_version" -ge "$FRAMEWORK_VERSION" ]]; then
|
|
return # Already current
|
|
fi
|
|
|
|
step "Running migrations (v${from_version} → v${FRAMEWORK_VERSION})"
|
|
|
|
# ── Migration: v0/v1 → v2 ─────────────────────────────────────────────────
|
|
# Remove bin/ directory — all executables now live in the npm CLI.
|
|
# Scripts that were in bin/ are now in tools/_scripts/.
|
|
if [[ "$from_version" -lt 2 ]]; then
|
|
if [[ -d "$TARGET_DIR/bin" ]]; then
|
|
ok "Removing legacy bin/ directory (executables now in npm CLI)"
|
|
rm -rf "$TARGET_DIR/bin"
|
|
fi
|
|
|
|
# Remove old mosaic PATH entry from shell profiles
|
|
for profile in "$HOME/.bashrc" "$HOME/.zshrc" "$HOME/.profile"; do
|
|
if [[ -f "$profile" ]] && grep -qF "$TARGET_DIR/bin" "$profile"; then
|
|
# Remove the PATH line and the comment above it
|
|
sed -i.mosaic-migration-bak \
|
|
-e "\|# Mosaic agent framework|d" \
|
|
-e "\|$TARGET_DIR/bin|d" \
|
|
"$profile"
|
|
ok "Cleaned up old PATH entry from $(basename "$profile")"
|
|
rm -f "${profile}.mosaic-migration-bak"
|
|
fi
|
|
done
|
|
|
|
# Remove stale rails/ symlink
|
|
if [[ -L "$TARGET_DIR/rails" ]]; then
|
|
rm -f "$TARGET_DIR/rails"
|
|
fi
|
|
fi
|
|
|
|
# ── Future migrations go here ──────────────────────────────────────────────
|
|
# if [[ "$from_version" -lt 3 ]]; then
|
|
# ...
|
|
# fi
|
|
}
|
|
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
# Main
|
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
|
|
step "Installing Mosaic framework"
|
|
|
|
mkdir -p "$TARGET_DIR"
|
|
select_install_mode
|
|
|
|
if [[ "$INSTALL_MODE" == "keep" ]]; then
|
|
ok "Install mode: keep local files (SOUL.md, USER.md, TOOLS.md, memory/)"
|
|
else
|
|
ok "Install mode: overwrite"
|
|
fi
|
|
|
|
sync_framework
|
|
|
|
# Ensure memory directory exists
|
|
mkdir -p "$TARGET_DIR/memory"
|
|
|
|
# Ensure tool scripts are executable
|
|
find "$TARGET_DIR/tools" -name "*.sh" -exec chmod +x {} + 2>/dev/null || true
|
|
find "$TARGET_DIR/tools/_scripts" -type f -exec chmod +x {} + 2>/dev/null || true
|
|
|
|
ok "Framework synced to $TARGET_DIR"
|
|
|
|
# Run migrations before post-install (migrations may remove old bin/ etc.)
|
|
run_migrations
|
|
|
|
step "Post-install tasks"
|
|
|
|
SCRIPTS="$TARGET_DIR/tools/_scripts"
|
|
|
|
if [[ -x "$SCRIPTS/mosaic-link-runtime-assets" ]]; then
|
|
if "$SCRIPTS/mosaic-link-runtime-assets" >/dev/null 2>&1; then
|
|
ok "Runtime assets linked"
|
|
else
|
|
warn "Runtime asset linking failed (non-fatal)"
|
|
fi
|
|
fi
|
|
|
|
if [[ -x "$SCRIPTS/mosaic-ensure-sequential-thinking" ]]; then
|
|
if "$SCRIPTS/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 bypassed (MOSAIC_ALLOW_MISSING_SEQUENTIAL_THINKING=1)"
|
|
else
|
|
fail "sequential-thinking MCP setup failed (hard requirement)."
|
|
exit 1
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
if [[ -x "$SCRIPTS/mosaic-ensure-excalidraw" ]]; then
|
|
"$SCRIPTS/mosaic-ensure-excalidraw" >/dev/null 2>&1 && ok "excalidraw MCP configured" || warn "excalidraw MCP setup failed (non-fatal)"
|
|
fi
|
|
|
|
if [[ "${MOSAIC_SKIP_SKILLS_SYNC:-0}" != "1" ]] && [[ -x "$SCRIPTS/mosaic-sync-skills" ]]; then
|
|
"$SCRIPTS/mosaic-sync-skills" >/dev/null 2>&1 && ok "Skills synced" || warn "Skills sync failed (non-fatal)"
|
|
fi
|
|
|
|
if [[ -x "$SCRIPTS/mosaic-migrate-local-skills" ]]; then
|
|
"$SCRIPTS/mosaic-migrate-local-skills" --apply >/dev/null 2>&1 && ok "Local skills migrated" || warn "Local skill migration failed (non-fatal)"
|
|
fi
|
|
|
|
if [[ -x "$SCRIPTS/mosaic-doctor" ]]; then
|
|
"$SCRIPTS/mosaic-doctor" >/dev/null 2>&1 && ok "Health audit passed" || warn "Health audit reported issues — run 'mosaic doctor' for details"
|
|
fi
|
|
|
|
# Write version stamp AFTER everything succeeds
|
|
write_framework_version
|
|
|
|
# ── Summary ──────────────────────────────────────────────────
|
|
echo ""
|
|
echo -e "${GREEN}${BOLD} Mosaic framework installed.${RESET}"
|
|
echo ""
|
|
|
|
if [[ ! -f "$TARGET_DIR/SOUL.md" ]]; then
|
|
echo -e " Run ${CYAN}mosaic init${RESET} to set up your agent identity."
|
|
echo ""
|
|
fi
|