diff --git a/bin/mosaic-doctor.ps1 b/bin/mosaic-doctor.ps1 new file mode 100644 index 0000000..844f96c --- /dev/null +++ b/bin/mosaic-doctor.ps1 @@ -0,0 +1,206 @@ +# mosaic-doctor.ps1 +# Audits Mosaic runtime state and detects drift across agent runtimes. +# PowerShell equivalent of mosaic-doctor (bash). +$ErrorActionPreference = "Stop" + +param( + [switch]$FailOnWarn, + [switch]$Verbose, + [switch]$Help +) + +$MosaicHome = if ($env:MOSAIC_HOME) { $env:MOSAIC_HOME } else { Join-Path $env:USERPROFILE ".config\mosaic" } + +if ($Help) { + Write-Host @" +Usage: mosaic-doctor.ps1 [-FailOnWarn] [-Verbose] [-Help] + +Audit Mosaic runtime state and detect drift across agent runtimes. +"@ + exit 0 +} + +$script:warnCount = 0 + +function Warn { + param([string]$Message) + $script:warnCount++ + Write-Host "[WARN] $Message" -ForegroundColor Yellow +} + +function Pass { + param([string]$Message) + if ($Verbose) { Write-Host "[OK] $Message" -ForegroundColor Green } +} + +function Expect-Dir { + param([string]$Path) + if (-not (Test-Path $Path -PathType Container)) { Warn "Missing directory: $Path" } + else { Pass "Directory present: $Path" } +} + +function Expect-File { + param([string]$Path) + if (-not (Test-Path $Path -PathType Leaf)) { Warn "Missing file: $Path" } + else { Pass "File present: $Path" } +} + +function Check-RuntimeFileCopy { + param([string]$Src, [string]$Dst) + + if (-not (Test-Path $Src)) { return } + + if (-not (Test-Path $Dst)) { + Warn "Missing runtime file: $Dst" + return + } + + $item = Get-Item $Dst -Force -ErrorAction SilentlyContinue + if ($item -and ($item.Attributes -band [System.IO.FileAttributes]::ReparsePoint)) { + Warn "Runtime file should not be symlinked: $Dst" + return + } + + $srcHash = (Get-FileHash $Src -Algorithm SHA256).Hash + $dstHash = (Get-FileHash $Dst -Algorithm SHA256).Hash + if ($srcHash -ne $dstHash) { + Warn "Runtime file drift: $Dst (does not match $Src)" + } + else { + Pass "Runtime file synced: $Dst" + } +} + +function Warn-IfReparsePresent { + param([string]$Path) + if (-not (Test-Path $Path)) { return } + + $item = Get-Item $Path -Force -ErrorAction SilentlyContinue + if ($item -and ($item.Attributes -band [System.IO.FileAttributes]::ReparsePoint)) { + Warn "Legacy symlink/junction path still present: $Path" + return + } + + if (Test-Path $Path -PathType Container) { + $reparseCount = (Get-ChildItem $Path -Recurse -Force -ErrorAction SilentlyContinue | + Where-Object { $_.Attributes -band [System.IO.FileAttributes]::ReparsePoint } | + Measure-Object).Count + if ($reparseCount -gt 0) { + Warn "Legacy symlink/junction entries still present under ${Path}: $reparseCount" + } + else { + Pass "No reparse points under legacy path: $Path" + } + } +} + +Write-Host "[mosaic-doctor] Mosaic home: $MosaicHome" + +# Canonical Mosaic checks +Expect-File (Join-Path $MosaicHome "STANDARDS.md") +Expect-Dir (Join-Path $MosaicHome "guides") +Expect-Dir (Join-Path $MosaicHome "rails") +Expect-Dir (Join-Path $MosaicHome "rails\quality") +Expect-Dir (Join-Path $MosaicHome "rails\orchestrator-matrix") +Expect-Dir (Join-Path $MosaicHome "profiles") +Expect-Dir (Join-Path $MosaicHome "templates\agent") +Expect-Dir (Join-Path $MosaicHome "skills") +Expect-Dir (Join-Path $MosaicHome "skills-local") +Expect-File (Join-Path $MosaicHome "bin\mosaic-link-runtime-assets") +Expect-File (Join-Path $MosaicHome "bin\mosaic-sync-skills") +Expect-File (Join-Path $MosaicHome "bin\mosaic-projects") +Expect-File (Join-Path $MosaicHome "bin\mosaic-quality-apply") +Expect-File (Join-Path $MosaicHome "bin\mosaic-quality-verify") +Expect-File (Join-Path $MosaicHome "bin\mosaic-orchestrator-run") +Expect-File (Join-Path $MosaicHome "bin\mosaic-orchestrator-sync-tasks") +Expect-File (Join-Path $MosaicHome "bin\mosaic-orchestrator-drain") +Expect-File (Join-Path $MosaicHome "bin\mosaic-orchestrator-matrix-publish") +Expect-File (Join-Path $MosaicHome "bin\mosaic-orchestrator-matrix-consume") +Expect-File (Join-Path $MosaicHome "bin\mosaic-orchestrator-matrix-cycle") +Expect-File (Join-Path $MosaicHome "rails\orchestrator-matrix\transport\matrix_transport.py") +Expect-File (Join-Path $MosaicHome "rails\orchestrator-matrix\controller\tasks_md_sync.py") + +# Claude runtime file checks +$runtimeFiles = @("CLAUDE.md", "settings.json", "hooks-config.json", "context7-integration.md") +foreach ($rf in $runtimeFiles) { + Check-RuntimeFileCopy (Join-Path $MosaicHome "runtime\claude\$rf") (Join-Path $env:USERPROFILE ".claude\$rf") +} + +# OpenCode runtime adapter +Check-RuntimeFileCopy (Join-Path $MosaicHome "runtime\opencode\AGENTS.md") (Join-Path $env:USERPROFILE ".config\opencode\AGENTS.md") + +# Legacy migration surfaces +$legacyPaths = @( + (Join-Path $env:USERPROFILE ".claude\agent-guides"), + (Join-Path $env:USERPROFILE ".claude\scripts\git"), + (Join-Path $env:USERPROFILE ".claude\scripts\codex"), + (Join-Path $env:USERPROFILE ".claude\scripts\bootstrap"), + (Join-Path $env:USERPROFILE ".claude\scripts\cicd"), + (Join-Path $env:USERPROFILE ".claude\scripts\portainer"), + (Join-Path $env:USERPROFILE ".claude\templates"), + (Join-Path $env:USERPROFILE ".claude\presets\domains"), + (Join-Path $env:USERPROFILE ".claude\presets\tech-stacks"), + (Join-Path $env:USERPROFILE ".claude\presets\workflows") +) +foreach ($p in $legacyPaths) { + Warn-IfReparsePresent $p +} + +# Skills runtime checks (junctions or symlinks into runtime-specific dirs) +$linkTargets = @( + (Join-Path $env:USERPROFILE ".claude\skills"), + (Join-Path $env:USERPROFILE ".codex\skills"), + (Join-Path $env:USERPROFILE ".config\opencode\skills") +) + +$skillSources = @($MosaicHome + "\skills", $MosaicHome + "\skills-local") + +foreach ($runtimeSkills in $linkTargets) { + if (-not (Test-Path $runtimeSkills)) { continue } + + foreach ($sourceDir in $skillSources) { + if (-not (Test-Path $sourceDir)) { continue } + + Get-ChildItem $sourceDir -Directory | Where-Object { -not $_.Name.StartsWith(".") } | ForEach-Object { + $name = $_.Name + $skillPath = $_.FullName + $target = Join-Path $runtimeSkills $name + + if (-not (Test-Path $target)) { + Warn "Missing skill link: $target" + return + } + + $item = Get-Item $target -Force -ErrorAction SilentlyContinue + if (-not ($item.Attributes -band [System.IO.FileAttributes]::ReparsePoint)) { + Warn "Non-junction skill entry: $target" + return + } + + $targetResolved = $item.Target + if (-not $targetResolved -or (Resolve-Path $targetResolved -ErrorAction SilentlyContinue).Path -ne (Resolve-Path $skillPath -ErrorAction SilentlyContinue).Path) { + Warn "Drifted skill link: $target (expected -> $skillPath)" + } + else { + Pass "Linked skill: $target" + } + } + } +} + +# Broken junctions/symlinks in managed runtime skill dirs +$brokenLinks = 0 +foreach ($d in $linkTargets) { + if (-not (Test-Path $d)) { continue } + Get-ChildItem $d -Force -ErrorAction SilentlyContinue | Where-Object { + ($_.Attributes -band [System.IO.FileAttributes]::ReparsePoint) -and -not (Test-Path $_.FullName) + } | ForEach-Object { $brokenLinks++ } +} +if ($brokenLinks -gt 0) { + Warn "Broken skill junctions/symlinks detected: $brokenLinks" +} + +Write-Host "[mosaic-doctor] warnings=$($script:warnCount)" +if ($FailOnWarn -and $script:warnCount -gt 0) { + exit 1 +} diff --git a/bin/mosaic-link-runtime-assets.ps1 b/bin/mosaic-link-runtime-assets.ps1 new file mode 100644 index 0000000..6dc5bcd --- /dev/null +++ b/bin/mosaic-link-runtime-assets.ps1 @@ -0,0 +1,97 @@ +# mosaic-link-runtime-assets.ps1 +# Syncs Mosaic runtime config files into agent runtime directories. +# PowerShell equivalent of mosaic-link-runtime-assets (bash). +$ErrorActionPreference = "Stop" + +$MosaicHome = if ($env:MOSAIC_HOME) { $env:MOSAIC_HOME } else { Join-Path $env:USERPROFILE ".config\mosaic" } +$BackupStamp = Get-Date -Format "yyyyMMddHHmmss" + +function Copy-FileManaged { + param([string]$Src, [string]$Dst) + + $parent = Split-Path $Dst -Parent + if (-not (Test-Path $parent)) { New-Item -ItemType Directory -Path $parent -Force | Out-Null } + + # Remove existing symlink/junction + $item = Get-Item $Dst -Force -ErrorAction SilentlyContinue + if ($item -and $item.Attributes -band [System.IO.FileAttributes]::ReparsePoint) { + Remove-Item $Dst -Force + } + + if (Test-Path $Dst) { + $srcHash = (Get-FileHash $Src -Algorithm SHA256).Hash + $dstHash = (Get-FileHash $Dst -Algorithm SHA256).Hash + if ($srcHash -eq $dstHash) { return } + Rename-Item $Dst "$Dst.mosaic-bak-$BackupStamp" + } + + Copy-Item $Src $Dst -Force +} + +function Remove-LegacyPath { + param([string]$Path) + + if (-not (Test-Path $Path)) { return } + + $item = Get-Item $Path -Force -ErrorAction SilentlyContinue + if ($item -and $item.Attributes -band [System.IO.FileAttributes]::ReparsePoint) { + Remove-Item $Path -Force + return + } + + if (Test-Path $Path -PathType Container) { + # Remove symlinks/junctions inside, then empty dirs + Get-ChildItem $Path -Recurse -Force | Where-Object { + $_.Attributes -band [System.IO.FileAttributes]::ReparsePoint + } | Remove-Item -Force + + Get-ChildItem $Path -Recurse -Directory -Force | + Sort-Object { $_.FullName.Length } -Descending | + Where-Object { (Get-ChildItem $_.FullName -Force | Measure-Object).Count -eq 0 } | + Remove-Item -Force + } +} + +# Remove legacy compatibility paths +$legacyPaths = @( + (Join-Path $env:USERPROFILE ".claude\agent-guides"), + (Join-Path $env:USERPROFILE ".claude\scripts\git"), + (Join-Path $env:USERPROFILE ".claude\scripts\codex"), + (Join-Path $env:USERPROFILE ".claude\scripts\bootstrap"), + (Join-Path $env:USERPROFILE ".claude\scripts\cicd"), + (Join-Path $env:USERPROFILE ".claude\scripts\portainer"), + (Join-Path $env:USERPROFILE ".claude\scripts\debug-hook.sh"), + (Join-Path $env:USERPROFILE ".claude\scripts\qa-hook-handler.sh"), + (Join-Path $env:USERPROFILE ".claude\scripts\qa-hook-stdin.sh"), + (Join-Path $env:USERPROFILE ".claude\scripts\qa-hook-wrapper.sh"), + (Join-Path $env:USERPROFILE ".claude\scripts\qa-queue-monitor.sh"), + (Join-Path $env:USERPROFILE ".claude\scripts\remediation-hook-handler.sh"), + (Join-Path $env:USERPROFILE ".claude\templates"), + (Join-Path $env:USERPROFILE ".claude\presets\domains"), + (Join-Path $env:USERPROFILE ".claude\presets\tech-stacks"), + (Join-Path $env:USERPROFILE ".claude\presets\workflows"), + (Join-Path $env:USERPROFILE ".claude\presets\jarvis-ralph.json") +) + +foreach ($p in $legacyPaths) { + Remove-LegacyPath $p +} + +# Sync runtime config files (copy, not link) +$runtimeFiles = @("CLAUDE.md", "settings.json", "hooks-config.json", "context7-integration.md") +foreach ($rf in $runtimeFiles) { + $src = Join-Path $MosaicHome "runtime\claude\$rf" + if (-not (Test-Path $src)) { continue } + $dst = Join-Path $env:USERPROFILE ".claude\$rf" + Copy-FileManaged $src $dst +} + +# OpenCode runtime adapter +$opencodeSrc = Join-Path $MosaicHome "runtime\opencode\AGENTS.md" +if (Test-Path $opencodeSrc) { + $opencodeDst = Join-Path $env:USERPROFILE ".config\opencode\AGENTS.md" + Copy-FileManaged $opencodeSrc $opencodeDst +} + +Write-Host "[mosaic-link] Runtime assets synced (non-symlink mode)" +Write-Host "[mosaic-link] Canonical source: $MosaicHome" diff --git a/bin/mosaic-migrate-local-skills.ps1 b/bin/mosaic-migrate-local-skills.ps1 new file mode 100644 index 0000000..9da85e9 --- /dev/null +++ b/bin/mosaic-migrate-local-skills.ps1 @@ -0,0 +1,90 @@ +# mosaic-migrate-local-skills.ps1 +# Migrates runtime-local skill directories to Mosaic-managed junctions. +# Uses directory junctions (no elevation required) with fallback to copies. +# PowerShell equivalent of mosaic-migrate-local-skills (bash). +$ErrorActionPreference = "Stop" + +param( + [switch]$Apply, + [switch]$Help +) + +$MosaicHome = if ($env:MOSAIC_HOME) { $env:MOSAIC_HOME } else { Join-Path $env:USERPROFILE ".config\mosaic" } +$LocalSkillsDir = Join-Path $MosaicHome "skills-local" + +if ($Help) { + Write-Host @" +Usage: mosaic-migrate-local-skills.ps1 [-Apply] [-Help] + +Migrate runtime-local skill directories (e.g. ~/.claude/skills/jarvis) to +Mosaic-managed skills by replacing local directories with junctions to +~/.config/mosaic/skills-local. + +Default mode is dry-run. +"@ + exit 0 +} + +if (-not (Test-Path $LocalSkillsDir)) { + Write-Host "[mosaic-local-skills] Missing local skills dir: $LocalSkillsDir" -ForegroundColor Red + exit 1 +} + +$skillRoots = @( + (Join-Path $env:USERPROFILE ".claude\skills"), + (Join-Path $env:USERPROFILE ".codex\skills"), + (Join-Path $env:USERPROFILE ".config\opencode\skills") +) + +$count = 0 + +Get-ChildItem $LocalSkillsDir -Directory | ForEach-Object { + $name = $_.Name + $src = $_.FullName + + foreach ($root in $skillRoots) { + if (-not (Test-Path $root)) { continue } + $target = Join-Path $root $name + + # Already a junction/symlink — check if it points to the right place + $existing = Get-Item $target -Force -ErrorAction SilentlyContinue + if ($existing -and ($existing.Attributes -band [System.IO.FileAttributes]::ReparsePoint)) { + $currentTarget = $existing.Target + if ($currentTarget -and ($currentTarget -eq $src -or (Resolve-Path $currentTarget -ErrorAction SilentlyContinue).Path -eq (Resolve-Path $src -ErrorAction SilentlyContinue).Path)) { + continue + } + } + + # Only migrate local directories containing SKILL.md + if ((Test-Path $target -PathType Container) -and + (Test-Path (Join-Path $target "SKILL.md")) -and + -not ($existing -and ($existing.Attributes -band [System.IO.FileAttributes]::ReparsePoint))) { + + $count++ + if ($Apply) { + $stamp = Get-Date -Format "yyyyMMddHHmmss" + Rename-Item $target "$target.mosaic-bak-$stamp" + try { + New-Item -ItemType Junction -Path $target -Target $src -ErrorAction Stop | Out-Null + Write-Host "[mosaic-local-skills] migrated: $target -> $src" + } + catch { + Write-Host "[mosaic-local-skills] Junction failed for $name, falling back to copy" + Copy-Item $src $target -Recurse -Force + Write-Host "[mosaic-local-skills] copied: $target <- $src" + } + } + else { + Write-Host "[mosaic-local-skills] would migrate: $target -> $src" + } + } + } +} + +if ($Apply) { + Write-Host "[mosaic-local-skills] complete: migrated=$count" +} +else { + Write-Host "[mosaic-local-skills] dry-run: migratable=$count" + Write-Host "[mosaic-local-skills] re-run with -Apply to migrate" +} diff --git a/bin/mosaic-sync-skills.ps1 b/bin/mosaic-sync-skills.ps1 new file mode 100644 index 0000000..c2a8fd5 --- /dev/null +++ b/bin/mosaic-sync-skills.ps1 @@ -0,0 +1,126 @@ +# mosaic-sync-skills.ps1 +# Syncs canonical skills and links them into agent runtime skill directories. +# Uses directory junctions (no elevation required) with fallback to copies. +# PowerShell equivalent of mosaic-sync-skills (bash). +$ErrorActionPreference = "Stop" + +param( + [switch]$LinkOnly, + [switch]$NoLink, + [switch]$Help +) + +$MosaicHome = if ($env:MOSAIC_HOME) { $env:MOSAIC_HOME } else { Join-Path $env:USERPROFILE ".config\mosaic" } +$SkillsRepoUrl = if ($env:MOSAIC_SKILLS_REPO_URL) { $env:MOSAIC_SKILLS_REPO_URL } else { "https://git.mosaicstack.dev/mosaic/agent-skills.git" } +$SkillsRepoDir = if ($env:MOSAIC_SKILLS_REPO_DIR) { $env:MOSAIC_SKILLS_REPO_DIR } else { Join-Path $MosaicHome "sources\agent-skills" } +$MosaicSkillsDir = Join-Path $MosaicHome "skills" +$MosaicLocalSkillsDir = Join-Path $MosaicHome "skills-local" + +if ($Help) { + Write-Host @" +Usage: mosaic-sync-skills.ps1 [-LinkOnly] [-NoLink] [-Help] + +Sync canonical skills into ~/.config/mosaic/skills and link all Mosaic skills +into runtime skill directories using directory junctions. + +Options: + -LinkOnly Skip git clone/pull and only relink + -NoLink Sync canonical skills but do not update runtime links + -Help Show help +"@ + exit 0 +} + +foreach ($d in @($MosaicHome, $MosaicSkillsDir, $MosaicLocalSkillsDir)) { + if (-not (Test-Path $d)) { New-Item -ItemType Directory -Path $d -Force | Out-Null } +} + +# Fetch skills from git +if (-not $LinkOnly) { + if (Test-Path (Join-Path $SkillsRepoDir ".git")) { + Write-Host "[mosaic-skills] Updating skills source: $SkillsRepoDir" + git -C $SkillsRepoDir pull --rebase + } + else { + Write-Host "[mosaic-skills] Cloning skills source to: $SkillsRepoDir" + $parentDir = Split-Path $SkillsRepoDir -Parent + if (-not (Test-Path $parentDir)) { New-Item -ItemType Directory -Path $parentDir -Force | Out-Null } + git clone $SkillsRepoUrl $SkillsRepoDir + } + + $sourceSkillsDir = Join-Path $SkillsRepoDir "skills" + if (-not (Test-Path $sourceSkillsDir)) { + Write-Host "[mosaic-skills] Missing source skills dir: $sourceSkillsDir" -ForegroundColor Red + exit 1 + } + + # Sync: remove old, copy new + if (Test-Path $MosaicSkillsDir) { + Get-ChildItem $MosaicSkillsDir | Remove-Item -Recurse -Force + } + Copy-Item "$sourceSkillsDir\*" $MosaicSkillsDir -Recurse -Force +} + +if (-not (Test-Path $MosaicSkillsDir)) { + Write-Host "[mosaic-skills] Canonical skills dir missing: $MosaicSkillsDir" -ForegroundColor Red + exit 1 +} + +if ($NoLink) { + Write-Host "[mosaic-skills] Canonical sync completed (link update skipped)" + exit 0 +} + +function Link-SkillIntoTarget { + param([string]$SkillPath, [string]$TargetDir) + + $name = Split-Path $SkillPath -Leaf + if ($name.StartsWith(".")) { return } + + $linkPath = Join-Path $TargetDir $name + + # Already a junction/symlink — recreate + $existing = Get-Item $linkPath -Force -ErrorAction SilentlyContinue + if ($existing -and ($existing.Attributes -band [System.IO.FileAttributes]::ReparsePoint)) { + Remove-Item $linkPath -Force + } + elseif ($existing) { + Write-Host "[mosaic-skills] Preserve existing runtime-specific entry: $linkPath" + return + } + + # Try junction first, fall back to copy + try { + New-Item -ItemType Junction -Path $linkPath -Target $SkillPath -ErrorAction Stop | Out-Null + } + catch { + Write-Host "[mosaic-skills] Junction failed for $name, falling back to copy" + Copy-Item $SkillPath $linkPath -Recurse -Force + } +} + +$linkTargets = @( + (Join-Path $env:USERPROFILE ".claude\skills"), + (Join-Path $env:USERPROFILE ".codex\skills"), + (Join-Path $env:USERPROFILE ".config\opencode\skills") +) + +foreach ($target in $linkTargets) { + if (-not (Test-Path $target)) { New-Item -ItemType Directory -Path $target -Force | Out-Null } + + # Link canonical skills + Get-ChildItem $MosaicSkillsDir -Directory | ForEach-Object { + Link-SkillIntoTarget $_.FullName $target + } + + # Link local skills + if (Test-Path $MosaicLocalSkillsDir) { + Get-ChildItem $MosaicLocalSkillsDir -Directory | ForEach-Object { + Link-SkillIntoTarget $_.FullName $target + } + } + + Write-Host "[mosaic-skills] Linked skills into: $target" +} + +Write-Host "[mosaic-skills] Complete" diff --git a/install.ps1 b/install.ps1 new file mode 100644 index 0000000..eeed628 --- /dev/null +++ b/install.ps1 @@ -0,0 +1,70 @@ +# Mosaic Bootstrap — Windows Installer +# PowerShell equivalent of install.sh +# +# Usage: +# powershell -ExecutionPolicy Bypass -File install.ps1 +# +$ErrorActionPreference = "Stop" + +$SourceDir = $PSScriptRoot +$TargetDir = if ($env:MOSAIC_HOME) { $env:MOSAIC_HOME } else { Join-Path $env:USERPROFILE ".config\mosaic" } + +if (-not (Test-Path $TargetDir)) { New-Item -ItemType Directory -Path $TargetDir -Force | Out-Null } + +# Sync files: remove old content, copy new +Write-Host "[mosaic-install] Copying framework to $TargetDir..." +Get-ChildItem $TargetDir -Exclude ".git" | Remove-Item -Recurse -Force +Get-ChildItem $SourceDir -Exclude ".git" | ForEach-Object { + $dest = Join-Path $TargetDir $_.Name + if ($_.PSIsContainer) { + Copy-Item $_.FullName $dest -Recurse -Force + } + else { + Copy-Item $_.FullName $dest -Force + } +} + +Write-Host "[mosaic-install] Installed framework to $TargetDir" + +# Run post-install scripts (PowerShell versions) +$binDir = Join-Path $TargetDir "bin" + +Write-Host "[mosaic-install] Linking runtime compatibility assets" +try { + & "$binDir\mosaic-link-runtime-assets.ps1" +} +catch { + Write-Host "[mosaic-install] WARNING: runtime asset linking failed (framework install still complete)" -ForegroundColor Yellow +} + +Write-Host "[mosaic-install] Syncing universal skills" +if ($env:MOSAIC_SKIP_SKILLS_SYNC -eq "1") { + Write-Host "[mosaic-install] Skipping skills sync (MOSAIC_SKIP_SKILLS_SYNC=1)" +} +else { + try { + & "$binDir\mosaic-sync-skills.ps1" + } + catch { + Write-Host "[mosaic-install] WARNING: skills sync failed (framework install still complete)" -ForegroundColor Yellow + } +} + +Write-Host "[mosaic-install] Migrating runtime-local skills to Mosaic junctions" +try { + & "$binDir\mosaic-migrate-local-skills.ps1" -Apply +} +catch { + Write-Host "[mosaic-install] WARNING: local skill migration failed (framework install still complete)" -ForegroundColor Yellow +} + +Write-Host "[mosaic-install] Running health audit" +try { + & "$binDir\mosaic-doctor.ps1" +} +catch { + Write-Host "[mosaic-install] WARNING: doctor reported issues (run mosaic-doctor.ps1 -FailOnWarn)" -ForegroundColor Yellow +} + +Write-Host "[mosaic-install] Done." +Write-Host "[mosaic-install] Add to PATH: `$env:USERPROFILE\.config\mosaic\bin" diff --git a/remote-install.ps1 b/remote-install.ps1 new file mode 100644 index 0000000..d94dcba --- /dev/null +++ b/remote-install.ps1 @@ -0,0 +1,37 @@ +# Mosaic Bootstrap — Remote Installer (Windows PowerShell) +# +# One-liner: +# irm https://git.mosaicstack.dev/mosaic/bootstrap/raw/branch/main/remote-install.ps1 | iex +# +# Or explicit: +# powershell -ExecutionPolicy Bypass -File remote-install.ps1 +# +$ErrorActionPreference = "Stop" + +$ArchiveUrl = "https://git.mosaicstack.dev/mosaic/bootstrap/archive/main.zip" +$WorkDir = Join-Path $env:TEMP "mosaic-bootstrap-$PID" +$ZipPath = "$WorkDir.zip" + +try { + Write-Host "[mosaic] Downloading bootstrap archive..." + New-Item -ItemType Directory -Path $WorkDir -Force | Out-Null + Invoke-WebRequest -Uri $ArchiveUrl -OutFile $ZipPath -UseBasicParsing + + Write-Host "[mosaic] Extracting..." + Expand-Archive -Path $ZipPath -DestinationPath $WorkDir -Force + + $InstallScript = Join-Path $WorkDir "bootstrap\install.ps1" + if (-not (Test-Path $InstallScript)) { + throw "install.ps1 not found in archive" + } + + Write-Host "[mosaic] Running install..." + & $InstallScript + + Write-Host "[mosaic] Done." +} +finally { + Write-Host "[mosaic] Cleaning up temporary files..." + Remove-Item -Path $ZipPath -Force -ErrorAction SilentlyContinue + Remove-Item -Path $WorkDir -Recurse -Force -ErrorAction SilentlyContinue +} diff --git a/remote-install.sh b/remote-install.sh new file mode 100755 index 0000000..e0ae3fa --- /dev/null +++ b/remote-install.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env sh +# Mosaic Bootstrap — Remote Installer (POSIX) +# +# One-liner: +# curl -sL https://git.mosaicstack.dev/mosaic/bootstrap/raw/branch/main/remote-install.sh | sh +# +# Or with wget: +# wget -qO- https://git.mosaicstack.dev/mosaic/bootstrap/raw/branch/main/remote-install.sh | sh +# +set -eu + +ARCHIVE_URL="https://git.mosaicstack.dev/mosaic/bootstrap/archive/main.tar.gz" +TMPDIR_BASE="${TMPDIR:-/tmp}" +WORK_DIR="$TMPDIR_BASE/mosaic-bootstrap-$$" + +cleanup() { + rm -rf "$WORK_DIR" +} +trap cleanup EXIT + +echo "[mosaic] Downloading bootstrap archive..." + +mkdir -p "$WORK_DIR" + +if command -v curl >/dev/null 2>&1; then + curl -sL "$ARCHIVE_URL" | tar xz -C "$WORK_DIR" +elif command -v wget >/dev/null 2>&1; then + wget -qO- "$ARCHIVE_URL" | tar xz -C "$WORK_DIR" +else + echo "[mosaic] ERROR: curl or wget required" >&2 + exit 1 +fi + +if [ ! -f "$WORK_DIR/bootstrap/install.sh" ]; then + echo "[mosaic] ERROR: install.sh not found in archive" >&2 + exit 1 +fi + +echo "[mosaic] Running install..." +cd "$WORK_DIR/bootstrap" +bash install.sh + +echo "[mosaic] Cleaning up temporary files..." +# cleanup runs via trap