Update scripts to keep SOUL.md idempotent. Add release upgrade framework.

This commit is contained in:
2026-02-20 07:36:54 -06:00
parent eac247c5cb
commit 1e4eefeca3
9 changed files with 551 additions and 17 deletions

View File

@@ -30,6 +30,7 @@ The installer will:
- Add `~/.config/mosaic/bin` to your PATH - Add `~/.config/mosaic/bin` to your PATH
- Sync runtime adapters and skills - Sync runtime adapters and skills
- Run a health audit - Run a health audit
- Detect existing installs and prompt to keep or overwrite local files
- Prompt you to run `mosaic init` to set up your agent identity - Prompt you to run `mosaic init` to set up your agent identity
## First Run ## First Run
@@ -96,24 +97,58 @@ mosaic init # Generate SOUL.md (agent identity)
mosaic doctor # Health audit — detect drift and missing files mosaic doctor # Health audit — detect drift and missing files
mosaic sync # Sync skills from canonical source mosaic sync # Sync skills from canonical source
mosaic bootstrap <path> # Bootstrap a repo with Mosaic standards mosaic bootstrap <path> # Bootstrap a repo with Mosaic standards
mosaic upgrade [path] # Clean up stale per-project files (see below) mosaic upgrade check # Check release upgrade status (no changes)
mosaic upgrade --all # Upgrade all projects in ~/src mosaic upgrade # Upgrade installed Mosaic release (keeps SOUL.md by default)
mosaic upgrade --dry-run # Preview without changes mosaic upgrade --dry-run # Preview release upgrade without changes
mosaic upgrade --ref main # Upgrade from a specific branch/tag/commit ref
mosaic upgrade --overwrite # Upgrade release and overwrite local files
mosaic upgrade project ... # Project file cleanup mode (see below)
``` ```
## Upgrading Mosaic Release
Upgrade the installed framework in place:
```bash
# Default (safe): keep local SOUL.md + memory
mosaic upgrade
# Check current/target release info without changing files
mosaic upgrade check
# Non-interactive
mosaic upgrade --yes
# Pull a specific ref
mosaic upgrade --ref main
# Force full overwrite (fresh install semantics)
mosaic upgrade --overwrite --yes
```
`mosaic upgrade` re-runs the remote installer and passes install mode controls (`keep`/`overwrite`).
This is the manual upgrade path today and is suitable for future app-driven update checks.
## Upgrading Projects ## Upgrading Projects
After centralizing AGENTS.md and SOUL.md, existing projects may have stale files: After centralizing AGENTS.md and SOUL.md, existing projects may have stale files:
```bash ```bash
# Preview what would change across all projects # Preview what would change across all projects
mosaic upgrade --all --dry-run mosaic upgrade project --all --dry-run
# Apply to all projects # Apply to all projects
mosaic upgrade --all mosaic upgrade project --all
# Apply to a specific project # Apply to a specific project
mosaic upgrade ~/src/my-project mosaic upgrade project ~/src/my-project
```
Backward compatibility is preserved for historical usage:
```bash
mosaic upgrade --all # still routes to project-upgrade
mosaic upgrade ~/src/my-repo # still routes to project-upgrade
``` ```
What it does per project: What it does per project:
@@ -186,6 +221,10 @@ Pull the latest and re-run the installer:
cd ~/src/mosaic-bootstrap && git pull && bash install.sh cd ~/src/mosaic-bootstrap && git pull && bash install.sh
``` ```
If an existing install is detected, the installer prompts for:
- `keep` (recommended): preserve local `SOUL.md` and `memory/`
- `overwrite`: replace everything in `~/.config/mosaic`
Or use the one-liner again — it always pulls the latest: Or use the one-liner again — it always pulls the latest:
```bash ```bash

View File

@@ -14,6 +14,9 @@ set -euo pipefail
# mosaic doctor [args...] Health audit # mosaic doctor [args...] Health audit
# mosaic sync [args...] Sync skills # mosaic sync [args...] Sync skills
# mosaic bootstrap <path> Bootstrap a repo # mosaic bootstrap <path> Bootstrap a repo
# mosaic upgrade release Upgrade installed Mosaic release
# mosaic upgrade check Check release upgrade status (no changes)
# mosaic upgrade project [args] Upgrade project-local stale files
MOSAIC_HOME="${MOSAIC_HOME:-$HOME/.config/mosaic}" MOSAIC_HOME="${MOSAIC_HOME:-$HOME/.config/mosaic}"
VERSION="0.1.0" VERSION="0.1.0"
@@ -34,7 +37,10 @@ Management:
doctor [args...] Audit runtime state and detect drift doctor [args...] Audit runtime state and detect drift
sync [args...] Sync skills from canonical source sync [args...] Sync skills from canonical source
bootstrap <path> Bootstrap a repo with Mosaic standards bootstrap <path> Bootstrap a repo with Mosaic standards
upgrade [path] Clean up stale SOUL.md/CLAUDE.md in a project upgrade [mode] [args] Upgrade release (default) or project files
upgrade check Check release upgrade status (no changes)
release-upgrade [...] Upgrade installed Mosaic release
project-upgrade [...] Clean up stale SOUL.md/CLAUDE.md in a project
Options: Options:
-h, --help Show this help -h, --help Show this help
@@ -147,11 +153,54 @@ run_bootstrap() {
exec "$MOSAIC_HOME/bin/mosaic-bootstrap-repo" "$@" exec "$MOSAIC_HOME/bin/mosaic-bootstrap-repo" "$@"
} }
run_upgrade() { run_release_upgrade() {
check_mosaic_home
exec "$MOSAIC_HOME/bin/mosaic-release-upgrade" "$@"
}
run_project_upgrade() {
check_mosaic_home check_mosaic_home
exec "$MOSAIC_HOME/bin/mosaic-upgrade" "$@" exec "$MOSAIC_HOME/bin/mosaic-upgrade" "$@"
} }
run_upgrade() {
check_mosaic_home
# Default: upgrade installed release
if [[ $# -eq 0 ]]; then
run_release_upgrade
fi
case "$1" in
release)
shift
run_release_upgrade "$@"
;;
check)
shift
run_release_upgrade --dry-run "$@"
;;
project)
shift
run_project_upgrade "$@"
;;
# Backward compatibility for historical project-upgrade usage.
--all|--root)
run_project_upgrade "$@"
;;
--dry-run|--ref|--keep|--overwrite|-y|--yes)
run_release_upgrade "$@"
;;
-*)
run_release_upgrade "$@"
;;
*)
run_project_upgrade "$@"
;;
esac
}
# Main router # Main router
if [[ $# -eq 0 ]]; then if [[ $# -eq 0 ]]; then
usage usage
@@ -170,6 +219,8 @@ case "$command" in
sync) run_sync "$@" ;; sync) run_sync "$@" ;;
bootstrap) run_bootstrap "$@" ;; bootstrap) run_bootstrap "$@" ;;
upgrade) run_upgrade "$@" ;; upgrade) run_upgrade "$@" ;;
release-upgrade) run_release_upgrade "$@" ;;
project-upgrade) run_project_upgrade "$@" ;;
help|-h|--help) usage ;; help|-h|--help) usage ;;
version|-v|--version) echo "mosaic $VERSION" ;; version|-v|--version) echo "mosaic $VERSION" ;;
*) *)

124
bin/mosaic-release-upgrade Executable file
View File

@@ -0,0 +1,124 @@
#!/usr/bin/env bash
set -euo pipefail
# mosaic-release-upgrade — Upgrade installed Mosaic framework release.
#
# This re-runs the remote installer with explicit install mode controls.
# Default behavior is safe/idempotent (keep SOUL.md + memory).
#
# Usage:
# mosaic-release-upgrade
# mosaic-release-upgrade --ref main --keep
# mosaic-release-upgrade --ref v0.2.0 --overwrite --yes
MOSAIC_HOME="${MOSAIC_HOME:-$HOME/.config/mosaic}"
REMOTE_SCRIPT_URL="${MOSAIC_REMOTE_INSTALL_URL:-https://git.mosaicstack.dev/mosaic/bootstrap/raw/branch/main/remote-install.sh}"
BOOTSTRAP_REF="${MOSAIC_BOOTSTRAP_REF:-main}"
INSTALL_MODE="${MOSAIC_INSTALL_MODE:-keep}" # keep|overwrite
YES=false
DRY_RUN=false
usage() {
cat <<USAGE
Usage: $(basename "$0") [options]
Upgrade the installed Mosaic framework release.
Options:
--ref <name> Bootstrap archive ref (branch/tag/commit). Default: main
--keep Keep local files (SOUL.md, memory/) during upgrade (default)
--overwrite Overwrite target install directory contents
-y, --yes Skip confirmation prompt
--dry-run Show actions without executing
-h, --help Show this help
USAGE
}
while [[ $# -gt 0 ]]; do
case "$1" in
--ref)
[[ $# -lt 2 ]] && { echo "Missing value for --ref" >&2; exit 1; }
BOOTSTRAP_REF="$2"
shift 2
;;
--keep)
INSTALL_MODE="keep"
shift
;;
--overwrite)
INSTALL_MODE="overwrite"
shift
;;
-y|--yes)
YES=true
shift
;;
--dry-run)
DRY_RUN=true
shift
;;
-h|--help)
usage
exit 0
;;
*)
echo "Unknown argument: $1" >&2
usage >&2
exit 1
;;
esac
done
case "$INSTALL_MODE" in
keep|overwrite) ;;
*)
echo "[mosaic-release-upgrade] Invalid install mode: $INSTALL_MODE" >&2
exit 1
;;
esac
current_version="unknown"
if [[ -x "$MOSAIC_HOME/bin/mosaic" ]]; then
current_version="$("$MOSAIC_HOME/bin/mosaic" --version 2>/dev/null | awk '{print $2}' || true)"
[[ -n "$current_version" ]] || current_version="unknown"
fi
echo "[mosaic-release-upgrade] Current version: $current_version"
echo "[mosaic-release-upgrade] Target ref: $BOOTSTRAP_REF"
echo "[mosaic-release-upgrade] Install mode: $INSTALL_MODE"
echo "[mosaic-release-upgrade] Installer URL: $REMOTE_SCRIPT_URL"
if [[ "$DRY_RUN" == "true" ]]; then
echo "[mosaic-release-upgrade] Dry run: no changes applied."
exit 0
fi
if [[ "$YES" != "true" && -t 0 ]]; then
printf "Proceed with Mosaic release upgrade? [y/N]: "
read -r confirm
case "${confirm:-n}" in
y|Y|yes|YES) ;;
*)
echo "[mosaic-release-upgrade] Aborted."
exit 1
;;
esac
fi
if command -v curl >/dev/null 2>&1; then
curl -sL "$REMOTE_SCRIPT_URL" | \
MOSAIC_BOOTSTRAP_REF="$BOOTSTRAP_REF" \
MOSAIC_INSTALL_MODE="$INSTALL_MODE" \
MOSAIC_HOME="$MOSAIC_HOME" \
sh
elif command -v wget >/dev/null 2>&1; then
wget -qO- "$REMOTE_SCRIPT_URL" | \
MOSAIC_BOOTSTRAP_REF="$BOOTSTRAP_REF" \
MOSAIC_INSTALL_MODE="$INSTALL_MODE" \
MOSAIC_HOME="$MOSAIC_HOME" \
sh
else
echo "[mosaic-release-upgrade] ERROR: curl or wget required." >&2
exit 1
fi

View File

@@ -0,0 +1,65 @@
# mosaic-release-upgrade.ps1 — Upgrade installed Mosaic framework release (Windows)
#
# Usage:
# mosaic-release-upgrade.ps1
# mosaic-release-upgrade.ps1 -Ref main -Keep
# mosaic-release-upgrade.ps1 -Ref v0.2.0 -Overwrite -Yes
#
param(
[string]$Ref = $(if ($env:MOSAIC_BOOTSTRAP_REF) { $env:MOSAIC_BOOTSTRAP_REF } else { "main" }),
[switch]$Keep,
[switch]$Overwrite,
[switch]$Yes,
[switch]$DryRun
)
$ErrorActionPreference = "Stop"
$MosaicHome = if ($env:MOSAIC_HOME) { $env:MOSAIC_HOME } else { Join-Path $env:USERPROFILE ".config\mosaic" }
$RemoteInstallerUrl = if ($env:MOSAIC_REMOTE_INSTALL_URL) {
$env:MOSAIC_REMOTE_INSTALL_URL
} else {
"https://git.mosaicstack.dev/mosaic/bootstrap/raw/branch/main/remote-install.ps1"
}
$installMode = if ($Overwrite) { "overwrite" } elseif ($Keep) { "keep" } elseif ($env:MOSAIC_INSTALL_MODE) { $env:MOSAIC_INSTALL_MODE } else { "keep" }
if ($installMode -notin @("keep", "overwrite")) {
Write-Host "[mosaic-release-upgrade] Invalid install mode: $installMode" -ForegroundColor Red
exit 1
}
$currentVersion = "unknown"
$mosaicCmd = Join-Path $MosaicHome "bin\mosaic.ps1"
if (Test-Path $mosaicCmd) {
try {
$currentVersion = (& $mosaicCmd --version) -replace '^mosaic\s+', ''
}
catch {
$currentVersion = "unknown"
}
}
Write-Host "[mosaic-release-upgrade] Current version: $currentVersion"
Write-Host "[mosaic-release-upgrade] Target ref: $Ref"
Write-Host "[mosaic-release-upgrade] Install mode: $installMode"
Write-Host "[mosaic-release-upgrade] Installer URL: $RemoteInstallerUrl"
if ($DryRun) {
Write-Host "[mosaic-release-upgrade] Dry run: no changes applied."
exit 0
}
if (-not $Yes) {
$confirmation = Read-Host "Proceed with Mosaic release upgrade? [y/N]"
if ($confirmation -notin @("y", "Y", "yes", "YES")) {
Write-Host "[mosaic-release-upgrade] Aborted."
exit 1
}
}
$env:MOSAIC_BOOTSTRAP_REF = $Ref
$env:MOSAIC_INSTALL_MODE = $installMode
$env:MOSAIC_HOME = $MosaicHome
Invoke-RestMethod -Uri $RemoteInstallerUrl | Invoke-Expression

View File

@@ -31,7 +31,10 @@ Management:
doctor [args...] Audit runtime state and detect drift doctor [args...] Audit runtime state and detect drift
sync [args...] Sync skills from canonical source sync [args...] Sync skills from canonical source
bootstrap <path> Bootstrap a repo with Mosaic standards bootstrap <path> Bootstrap a repo with Mosaic standards
upgrade [path] Clean up stale SOUL.md/CLAUDE.md in a project upgrade [mode] [args] Upgrade release (default) or project files
upgrade check Check release upgrade status (no changes)
release-upgrade [...] Upgrade installed Mosaic release
project-upgrade [...] Clean up stale SOUL.md/CLAUDE.md in a project
Options: Options:
-h, --help Show this help -h, --help Show this help
@@ -142,6 +145,48 @@ switch ($command) {
& (Join-Path $MosaicHome "bin\mosaic-bootstrap-repo") @remaining & (Join-Path $MosaicHome "bin\mosaic-bootstrap-repo") @remaining
} }
"upgrade" { "upgrade" {
Assert-MosaicHome
if ($remaining.Count -eq 0) {
& (Join-Path $MosaicHome "bin\mosaic-release-upgrade.ps1")
break
}
$mode = $remaining[0]
$tail = if ($remaining.Count -gt 1) { $remaining[1..($remaining.Count - 1)] } else { @() }
switch -Regex ($mode) {
"^release$" {
& (Join-Path $MosaicHome "bin\mosaic-release-upgrade.ps1") @tail
}
"^check$" {
& (Join-Path $MosaicHome "bin\mosaic-release-upgrade.ps1") -DryRun @tail
}
"^project$" {
Write-Host "[mosaic] NOTE: mosaic-upgrade requires bash. Use Git Bash or WSL." -ForegroundColor Yellow
& (Join-Path $MosaicHome "bin\mosaic-upgrade") @tail
}
"^(--all|--root)$" {
Write-Host "[mosaic] NOTE: mosaic-upgrade requires bash. Use Git Bash or WSL." -ForegroundColor Yellow
& (Join-Path $MosaicHome "bin\mosaic-upgrade") @remaining
}
"^(--dry-run|--ref|--keep|--overwrite|-y|--yes)$" {
& (Join-Path $MosaicHome "bin\mosaic-release-upgrade.ps1") @remaining
}
"^-.*" {
& (Join-Path $MosaicHome "bin\mosaic-release-upgrade.ps1") @remaining
}
default {
Write-Host "[mosaic] NOTE: treating positional argument as project path." -ForegroundColor Yellow
Write-Host "[mosaic] NOTE: mosaic-upgrade requires bash. Use Git Bash or WSL." -ForegroundColor Yellow
& (Join-Path $MosaicHome "bin\mosaic-upgrade") @remaining
}
}
}
"release-upgrade" {
Assert-MosaicHome
& (Join-Path $MosaicHome "bin\mosaic-release-upgrade.ps1") @remaining
}
"project-upgrade" {
Assert-MosaicHome Assert-MosaicHome
Write-Host "[mosaic] NOTE: mosaic-upgrade requires bash. Use Git Bash or WSL." -ForegroundColor Yellow Write-Host "[mosaic] NOTE: mosaic-upgrade requires bash. Use Git Bash or WSL." -ForegroundColor Yellow
& (Join-Path $MosaicHome "bin\mosaic-upgrade") @remaining & (Join-Path $MosaicHome "bin\mosaic-upgrade") @remaining

View File

@@ -8,16 +8,96 @@ $ErrorActionPreference = "Stop"
$SourceDir = $PSScriptRoot $SourceDir = $PSScriptRoot
$TargetDir = if ($env:MOSAIC_HOME) { $env:MOSAIC_HOME } else { Join-Path $env:USERPROFILE ".config\mosaic" } $TargetDir = if ($env:MOSAIC_HOME) { $env:MOSAIC_HOME } else { Join-Path $env:USERPROFILE ".config\mosaic" }
$InstallMode = if ($env:MOSAIC_INSTALL_MODE) { $env:MOSAIC_INSTALL_MODE.ToLowerInvariant() } else { "prompt" } # prompt|keep|overwrite
$PreservePaths = @("SOUL.md", "memory")
function Write-Ok { param([string]$Msg) Write-Host "" -ForegroundColor Green -NoNewline; Write-Host $Msg } function Write-Ok { param([string]$Msg) Write-Host "" -ForegroundColor Green -NoNewline; Write-Host $Msg }
function Write-Warn { param([string]$Msg) Write-Host "" -ForegroundColor Yellow -NoNewline; Write-Host $Msg } function Write-Warn { param([string]$Msg) Write-Host "" -ForegroundColor Yellow -NoNewline; Write-Host $Msg }
function Write-Fail { param([string]$Msg) Write-Host "" -ForegroundColor Red -NoNewline; Write-Host $Msg } function Write-Fail { param([string]$Msg) Write-Host "" -ForegroundColor Red -NoNewline; Write-Host $Msg }
function Write-Step { param([string]$Msg) Write-Host ""; Write-Host $Msg -ForegroundColor White -BackgroundColor DarkGray } function Write-Step { param([string]$Msg) Write-Host ""; Write-Host $Msg -ForegroundColor White -BackgroundColor DarkGray }
function Test-ExistingInstall {
if (-not (Test-Path $TargetDir)) { return $false }
return (Test-Path (Join-Path $TargetDir "bin\mosaic")) -or (Test-Path (Join-Path $TargetDir "AGENTS.md")) -or (Test-Path (Join-Path $TargetDir "SOUL.md"))
}
function Select-InstallMode {
switch ($InstallMode) {
"prompt" { }
"keep" { return }
"overwrite" { return }
default {
Write-Fail "Invalid MOSAIC_INSTALL_MODE '$InstallMode'. Use: prompt, keep, overwrite."
exit 1
}
}
if (-not (Test-ExistingInstall)) {
$script:InstallMode = "overwrite"
return
}
if (-not [Environment]::UserInteractive) {
Write-Warn "Existing install detected without interactive input; defaulting to keep local files."
$script:InstallMode = "keep"
return
}
Write-Host ""
Write-Host "Existing Mosaic install detected at: $TargetDir"
Write-Host "Choose reinstall mode:"
Write-Host " 1) keep Keep local files (SOUL.md, memory/) while updating framework"
Write-Host " 2) overwrite Replace everything in $TargetDir"
Write-Host " 3) cancel Abort install"
$selection = Read-Host "Selection [1/2/3] (default: 1)"
$normalizedSelection = if ($null -eq $selection) { "" } else { $selection.ToLowerInvariant() }
switch ($normalizedSelection) {
"" { $script:InstallMode = "keep" }
"1" { $script:InstallMode = "keep" }
"k" { $script:InstallMode = "keep" }
"keep" { $script:InstallMode = "keep" }
"2" { $script:InstallMode = "overwrite" }
"o" { $script:InstallMode = "overwrite" }
"overwrite" { $script:InstallMode = "overwrite" }
"3" { Write-Fail "Install cancelled."; exit 1 }
"c" { Write-Fail "Install cancelled."; exit 1 }
"cancel" { Write-Fail "Install cancelled."; exit 1 }
default {
Write-Warn "Unrecognized selection '$selection'; defaulting to keep."
$script:InstallMode = "keep"
}
}
}
# ── Install framework ──────────────────────────────────────── # ── Install framework ────────────────────────────────────────
Write-Step " Installing Mosaic framework " Write-Step " Installing Mosaic framework "
if (-not (Test-Path $TargetDir)) { New-Item -ItemType Directory -Path $TargetDir -Force | Out-Null } if (-not (Test-Path $TargetDir)) { New-Item -ItemType Directory -Path $TargetDir -Force | Out-Null }
Select-InstallMode
if ($InstallMode -eq "keep") {
Write-Ok "Install mode: keep local SOUL.md/memory while updating framework"
}
else {
Write-Ok "Install mode: overwrite existing files"
}
$preserveTmp = $null
if ($InstallMode -eq "keep") {
$preserveTmp = Join-Path ([System.IO.Path]::GetTempPath()) ("mosaic-preserve-" + [Guid]::NewGuid().ToString("N"))
New-Item -ItemType Directory -Path $preserveTmp -Force | Out-Null
foreach ($relPath in $PreservePaths) {
$src = Join-Path $TargetDir $relPath
if (Test-Path $src) {
$dstParent = Join-Path $preserveTmp (Split-Path $relPath -Parent)
if (-not [string]::IsNullOrEmpty($dstParent) -and -not (Test-Path $dstParent)) {
New-Item -ItemType Directory -Path $dstParent -Force | Out-Null
}
Copy-Item $src (Join-Path $preserveTmp $relPath) -Recurse -Force
}
}
}
Get-ChildItem $TargetDir -Exclude ".git" | Remove-Item -Recurse -Force Get-ChildItem $TargetDir -Exclude ".git" | Remove-Item -Recurse -Force
Get-ChildItem $SourceDir -Exclude ".git" | ForEach-Object { Get-ChildItem $SourceDir -Exclude ".git" | ForEach-Object {
@@ -30,6 +110,24 @@ Get-ChildItem $SourceDir -Exclude ".git" | ForEach-Object {
} }
} }
if ($InstallMode -eq "keep" -and $null -ne $preserveTmp) {
foreach ($relPath in $PreservePaths) {
$src = Join-Path $preserveTmp $relPath
if (Test-Path $src) {
$dst = Join-Path $TargetDir $relPath
if (Test-Path $dst) {
Remove-Item $dst -Recurse -Force
}
$dstParent = Split-Path $dst -Parent
if (-not (Test-Path $dstParent)) {
New-Item -ItemType Directory -Path $dstParent -Force | Out-Null
}
Copy-Item $src $dst -Recurse -Force
}
}
Remove-Item -Path $preserveTmp -Recurse -Force -ErrorAction SilentlyContinue
}
Write-Ok "Framework installed to $TargetDir" Write-Ok "Framework installed to $TargetDir"
# ── Post-install tasks ─────────────────────────────────────── # ── Post-install tasks ───────────────────────────────────────

View File

@@ -3,6 +3,8 @@ set -euo pipefail
SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SOURCE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
TARGET_DIR="${MOSAIC_HOME:-$HOME/.config/mosaic}" TARGET_DIR="${MOSAIC_HOME:-$HOME/.config/mosaic}"
INSTALL_MODE="${MOSAIC_INSTALL_MODE:-prompt}" # prompt|keep|overwrite
PRESERVE_PATHS=("SOUL.md" "memory")
# Colors (disabled if not a terminal) # Colors (disabled if not a terminal)
if [[ -t 1 ]]; then if [[ -t 1 ]]; then
@@ -17,17 +19,125 @@ warn() { echo -e " ${YELLOW}⚠${RESET} $1" >&2; }
fail() { echo -e " ${RED}${RESET} $1" >&2; } fail() { echo -e " ${RED}${RESET} $1" >&2; }
step() { echo -e "\n${BOLD}$1${RESET}"; } 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, 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" step "Installing Mosaic framework"
mkdir -p "$TARGET_DIR" mkdir -p "$TARGET_DIR"
select_install_mode
if command -v rsync >/dev/null 2>&1; then if [[ "$INSTALL_MODE" == "keep" ]]; then
rsync -a --delete "$SOURCE_DIR/" "$TARGET_DIR/" ok "Install mode: keep local SOUL.md/memory while updating framework"
else else
rm -rf "$TARGET_DIR"/* ok "Install mode: overwrite existing files"
cp -R "$SOURCE_DIR"/* "$TARGET_DIR"/
fi fi
sync_framework
chmod +x "$TARGET_DIR"/bin/* chmod +x "$TARGET_DIR"/bin/*
chmod +x "$TARGET_DIR"/install.sh chmod +x "$TARGET_DIR"/install.sh

View File

@@ -8,12 +8,13 @@
# #
$ErrorActionPreference = "Stop" $ErrorActionPreference = "Stop"
$ArchiveUrl = "https://git.mosaicstack.dev/mosaic/bootstrap/archive/main.zip" $BootstrapRef = if ($env:MOSAIC_BOOTSTRAP_REF) { $env:MOSAIC_BOOTSTRAP_REF } else { "main" }
$ArchiveUrl = "https://git.mosaicstack.dev/mosaic/bootstrap/archive/$BootstrapRef.zip"
$WorkDir = Join-Path $env:TEMP "mosaic-bootstrap-$PID" $WorkDir = Join-Path $env:TEMP "mosaic-bootstrap-$PID"
$ZipPath = "$WorkDir.zip" $ZipPath = "$WorkDir.zip"
try { try {
Write-Host "[mosaic] Downloading bootstrap archive..." Write-Host "[mosaic] Downloading bootstrap archive (ref: $BootstrapRef)..."
New-Item -ItemType Directory -Path $WorkDir -Force | Out-Null New-Item -ItemType Directory -Path $WorkDir -Force | Out-Null
Invoke-WebRequest -Uri $ArchiveUrl -OutFile $ZipPath -UseBasicParsing Invoke-WebRequest -Uri $ArchiveUrl -OutFile $ZipPath -UseBasicParsing

View File

@@ -9,7 +9,8 @@
# #
set -eu set -eu
ARCHIVE_URL="https://git.mosaicstack.dev/mosaic/bootstrap/archive/main.tar.gz" BOOTSTRAP_REF="${MOSAIC_BOOTSTRAP_REF:-main}"
ARCHIVE_URL="https://git.mosaicstack.dev/mosaic/bootstrap/archive/${BOOTSTRAP_REF}.tar.gz"
TMPDIR_BASE="${TMPDIR:-/tmp}" TMPDIR_BASE="${TMPDIR:-/tmp}"
WORK_DIR="$TMPDIR_BASE/mosaic-bootstrap-$$" WORK_DIR="$TMPDIR_BASE/mosaic-bootstrap-$$"
@@ -18,7 +19,7 @@ cleanup() {
} }
trap cleanup EXIT trap cleanup EXIT
echo "[mosaic] Downloading bootstrap archive..." echo "[mosaic] Downloading bootstrap archive (ref: $BOOTSTRAP_REF)..."
mkdir -p "$WORK_DIR" mkdir -p "$WORK_DIR"