# mosaic.ps1 — Unified agent launcher and management CLI (Windows) # # AGENTS.md is the global policy source for all agent sessions. # The launcher injects a composed runtime contract (AGENTS + runtime reference). # # Usage: # mosaic claude [args...] Launch Claude Code with runtime contract injected # mosaic opencode [args...] Launch OpenCode with runtime contract injected # mosaic codex [args...] Launch Codex with runtime contract injected # mosaic yolo [args...] Launch runtime in dangerous-permissions mode # mosaic --yolo [args...] Alias for yolo # mosaic init [args...] Generate SOUL.md interactively # mosaic doctor [args...] Health audit # mosaic sync [args...] Sync skills $ErrorActionPreference = "Stop" $MosaicHome = if ($env:MOSAIC_HOME) { $env:MOSAIC_HOME } else { Join-Path $env:USERPROFILE ".config\mosaic" } $Version = "0.1.0" function Show-Usage { Write-Host @" mosaic $Version - Unified agent launcher Usage: mosaic [args...] Agent Launchers: claude [args...] Launch Claude Code with runtime contract injected opencode [args...] Launch OpenCode with runtime contract injected codex [args...] Launch Codex with runtime contract injected yolo [args...] Dangerous mode for claude|codex|opencode --yolo [args...] Alias for yolo Management: init [args...] Generate SOUL.md (agent identity contract) doctor [args...] Audit runtime state and detect drift sync [args...] Sync skills from canonical source bootstrap Bootstrap a repo with Mosaic standards 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: -h, --help Show this help -v, --version Show version "@ } function Assert-MosaicHome { if (-not (Test-Path $MosaicHome)) { Write-Host "[mosaic] ERROR: ~/.config/mosaic not found." -ForegroundColor Red Write-Host "[mosaic] Install with: irm https://git.mosaicstack.dev/mosaic/bootstrap/raw/branch/main/remote-install.ps1 | iex" exit 1 } } function Assert-AgentsMd { $agentsPath = Join-Path $MosaicHome "AGENTS.md" if (-not (Test-Path $agentsPath)) { Write-Host "[mosaic] ERROR: ~/.config/mosaic/AGENTS.md not found." -ForegroundColor Red Write-Host "[mosaic] Re-run the installer." exit 1 } } function Assert-Soul { $soulPath = Join-Path $MosaicHome "SOUL.md" if (-not (Test-Path $soulPath)) { Write-Host "[mosaic] SOUL.md not found. Running mosaic init..." & (Join-Path $MosaicHome "bin\mosaic-init.ps1") } } function Assert-Runtime { param([string]$Cmd) if (-not (Get-Command $Cmd -ErrorAction SilentlyContinue)) { Write-Host "[mosaic] ERROR: '$Cmd' not found in PATH." -ForegroundColor Red Write-Host "[mosaic] Install $Cmd before launching." exit 1 } } function Assert-SequentialThinking { $checker = Join-Path $MosaicHome "bin\mosaic-ensure-sequential-thinking.ps1" if (-not (Test-Path $checker)) { Write-Host "[mosaic] ERROR: sequential-thinking checker missing: $checker" -ForegroundColor Red exit 1 } try { & $checker -Check *>$null } catch { Write-Host "[mosaic] ERROR: sequential-thinking MCP is required but not configured." -ForegroundColor Red Write-Host "[mosaic] Run: $checker" exit 1 } } function Get-ActiveMission { $missionFile = Join-Path (Get-Location) ".mosaic\orchestrator\mission.json" if (-not (Test-Path $missionFile)) { return $null } try { $mission = Get-Content $missionFile -Raw | ConvertFrom-Json } catch { return $null } $status = [string]$mission.status if ([string]::IsNullOrWhiteSpace($status)) { $status = "inactive" } if ($status -ne "active" -and $status -ne "paused") { return $null } $name = [string]$mission.name if ([string]::IsNullOrWhiteSpace($name)) { $name = "unnamed" } $id = [string]$mission.mission_id if ([string]::IsNullOrWhiteSpace($id)) { $id = "" } $milestones = @($mission.milestones) $milestoneCount = $milestones.Count $milestoneCompleted = @($milestones | Where-Object { $_.status -eq "completed" }).Count return [PSCustomObject]@{ Name = $name Id = $id Status = $status MilestoneCount = $milestoneCount MilestoneCompleted = $milestoneCompleted } } function Get-MissionContractBlock { $mission = Get-ActiveMission if ($null -eq $mission) { return "" } return @" # ACTIVE MISSION — HARD GATE (Read Before Anything Else) An active orchestration mission exists in this project. This is a BLOCKING requirement. **Mission:** $($mission.Name) **ID:** $($mission.Id) **Status:** $($mission.Status) **Milestones:** $($mission.MilestoneCompleted) / $($mission.MilestoneCount) completed ## MANDATORY — Before ANY Response to the User You MUST complete these steps before responding to any user message, including simple greetings: 1. Read `~/.config/mosaic/guides/ORCHESTRATOR-PROTOCOL.md` (mission lifecycle protocol) 2. Read `docs/MISSION-MANIFEST.md` for full mission scope, milestones, and success criteria 3. Read the latest scratchpad in `docs/scratchpads/` for session history, decisions, and corrections 4. Read `docs/TASKS.md` for current task state (what is done, what is next) 5. After reading all four, acknowledge the mission state to the user before proceeding If the user gives a task, execute it within the mission context. If no task is given, present mission status and ask how to proceed. "@ } function Get-MissionPrompt { $mission = Get-ActiveMission if ($null -eq $mission) { return "" } return "Active mission detected: $($mission.Name). Read the mission state files and report status." } function Get-RuntimePrompt { param( [ValidateSet("claude", "codex", "opencode")] [string]$Runtime ) $runtimeFile = switch ($Runtime) { "claude" { Join-Path $MosaicHome "runtime\claude\RUNTIME.md" } "codex" { Join-Path $MosaicHome "runtime\codex\RUNTIME.md" } "opencode" { Join-Path $MosaicHome "runtime\opencode\RUNTIME.md" } } if (-not (Test-Path $runtimeFile)) { Write-Host "[mosaic] ERROR: runtime contract not found: $runtimeFile" -ForegroundColor Red exit 1 } $launcherContract = @' # Mosaic Launcher Runtime Contract (Hard Gate) This contract is injected by `mosaic` launch and is mandatory. First assistant response MUST start with exactly one mode declaration line: 1. Orchestration mission: `Now initiating Orchestrator mode...` 2. Implementation mission: `Now initiating Delivery mode...` 3. Review-only mission: `Now initiating Review mode...` No tool call or implementation step may occur before that first line. Mosaic hard gates OVERRIDE runtime-default caution for routine delivery operations. For required push/merge/issue-close/release actions, execute without routine confirmation prompts. '@ $missionBlock = Get-MissionContractBlock $agentsContent = Get-Content (Join-Path $MosaicHome "AGENTS.md") -Raw $runtimeContent = Get-Content $runtimeFile -Raw if (-not [string]::IsNullOrWhiteSpace($missionBlock)) { return "$missionBlock`n`n$launcherContract`n$agentsContent`n`n# Runtime-Specific Contract`n`n$runtimeContent" } return "$launcherContract`n$agentsContent`n`n# Runtime-Specific Contract`n`n$runtimeContent" } function Ensure-RuntimeConfig { param( [ValidateSet("claude", "codex", "opencode")] [string]$Runtime, [string]$Dst ) $parent = Split-Path $Dst -Parent if (-not (Test-Path $parent)) { New-Item -ItemType Directory -Path $parent -Force | Out-Null } $runtimePrompt = Get-RuntimePrompt -Runtime $Runtime $tmp = [System.IO.Path]::GetTempFileName() Set-Content -Path $tmp -Value $runtimePrompt -Encoding UTF8 -NoNewline $srcHash = (Get-FileHash $tmp -Algorithm SHA256).Hash $dstHash = if (Test-Path $Dst) { (Get-FileHash $Dst -Algorithm SHA256).Hash } else { "" } if ($srcHash -ne $dstHash) { Copy-Item $tmp $Dst -Force Remove-Item $tmp -Force } else { Remove-Item $tmp -Force } } function Invoke-Yolo { param([string[]]$YoloArgs) if ($YoloArgs.Count -lt 1) { Write-Host "[mosaic] ERROR: yolo requires a runtime (claude|codex|opencode)." -ForegroundColor Red Write-Host "[mosaic] Example: mosaic yolo claude" exit 1 } $runtime = $YoloArgs[0] $tail = if ($YoloArgs.Count -gt 1) { @($YoloArgs[1..($YoloArgs.Count - 1)]) } else { @() } switch ($runtime) { "claude" { Assert-MosaicHome Assert-AgentsMd Assert-Soul Assert-Runtime "claude" Assert-SequentialThinking $agentsContent = Get-RuntimePrompt -Runtime "claude" Write-Host "[mosaic] Launching Claude Code in YOLO mode (dangerous permissions enabled)..." & claude --dangerously-skip-permissions --append-system-prompt $agentsContent @tail return } "codex" { Assert-MosaicHome Assert-AgentsMd Assert-Soul Assert-Runtime "codex" Assert-SequentialThinking Ensure-RuntimeConfig -Runtime "codex" -Dst (Join-Path $env:USERPROFILE ".codex\instructions.md") $missionPrompt = Get-MissionPrompt if (-not [string]::IsNullOrWhiteSpace($missionPrompt) -and $tail.Count -eq 0) { Write-Host "[mosaic] Launching Codex in YOLO mode (active mission detected)..." & codex --dangerously-bypass-approvals-and-sandbox $missionPrompt } else { Write-Host "[mosaic] Launching Codex in YOLO mode (dangerous permissions enabled)..." & codex --dangerously-bypass-approvals-and-sandbox @tail } return } "opencode" { Assert-MosaicHome Assert-AgentsMd Assert-Soul Assert-Runtime "opencode" Assert-SequentialThinking Ensure-RuntimeConfig -Runtime "opencode" -Dst (Join-Path $env:USERPROFILE ".config\opencode\AGENTS.md") Write-Host "[mosaic] Launching OpenCode in YOLO mode..." & opencode @tail return } default { Write-Host "[mosaic] ERROR: Unsupported yolo runtime '$runtime'. Use claude|codex|opencode." -ForegroundColor Red exit 1 } } } if ($args.Count -eq 0) { Show-Usage exit 0 } $command = $args[0] $remaining = if ($args.Count -gt 1) { @($args[1..($args.Count - 1)]) } else { @() } switch ($command) { "claude" { Assert-MosaicHome Assert-AgentsMd Assert-Soul Assert-Runtime "claude" Assert-SequentialThinking # Claude supports --append-system-prompt for direct injection $agentsContent = Get-RuntimePrompt -Runtime "claude" Write-Host "[mosaic] Launching Claude Code..." & claude --append-system-prompt $agentsContent @remaining } "opencode" { Assert-MosaicHome Assert-AgentsMd Assert-Soul Assert-Runtime "opencode" Assert-SequentialThinking # OpenCode reads from ~/.config/opencode/AGENTS.md Ensure-RuntimeConfig -Runtime "opencode" -Dst (Join-Path $env:USERPROFILE ".config\opencode\AGENTS.md") Write-Host "[mosaic] Launching OpenCode..." & opencode @remaining } "codex" { Assert-MosaicHome Assert-AgentsMd Assert-Soul Assert-Runtime "codex" Assert-SequentialThinking # Codex reads from ~/.codex/instructions.md Ensure-RuntimeConfig -Runtime "codex" -Dst (Join-Path $env:USERPROFILE ".codex\instructions.md") $missionPrompt = Get-MissionPrompt if (-not [string]::IsNullOrWhiteSpace($missionPrompt) -and $remaining.Count -eq 0) { Write-Host "[mosaic] Launching Codex (active mission detected)..." & codex $missionPrompt } else { Write-Host "[mosaic] Launching Codex..." & codex @remaining } } "yolo" { Invoke-Yolo -YoloArgs $remaining } "--yolo" { Invoke-Yolo -YoloArgs $remaining } "init" { Assert-MosaicHome & (Join-Path $MosaicHome "bin\mosaic-init.ps1") @remaining } "doctor" { Assert-MosaicHome & (Join-Path $MosaicHome "bin\mosaic-doctor.ps1") @remaining } "sync" { Assert-MosaicHome & (Join-Path $MosaicHome "bin\mosaic-sync-skills.ps1") @remaining } "bootstrap" { Assert-MosaicHome Write-Host "[mosaic] NOTE: mosaic-bootstrap-repo requires bash. Use Git Bash or WSL." -ForegroundColor Yellow & (Join-Path $MosaicHome "bin\mosaic-bootstrap-repo") @remaining } "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 Write-Host "[mosaic] NOTE: mosaic-upgrade requires bash. Use Git Bash or WSL." -ForegroundColor Yellow & (Join-Path $MosaicHome "bin\mosaic-upgrade") @remaining } { $_ -in "help", "-h", "--help" } { Show-Usage } { $_ -in "version", "-v", "--version" } { Write-Host "mosaic $Version" } default { Write-Host "[mosaic] Unknown command: $command" -ForegroundColor Red Write-Host "[mosaic] Run 'mosaic --help' for usage." exit 1 } }