#!/usr/bin/env bash set -euo pipefail # mosaic — Unified agent launcher and management CLI # # 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 # mosaic seq [subcommand] sequential-thinking MCP management (check/fix/start) # mosaic bootstrap 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}" VERSION="0.1.0" usage() { cat < [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 seq [subcommand] sequential-thinking MCP management: check [--runtime ] [--strict] fix [--runtime ] start 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 All arguments after the command are forwarded to the target CLI. USAGE } # Pre-flight checks check_mosaic_home() { if [[ ! -d "$MOSAIC_HOME" ]]; then echo "[mosaic] ERROR: ~/.config/mosaic not found." >&2 echo "[mosaic] Install with: curl -sL https://git.mosaicstack.dev/mosaic/bootstrap/raw/branch/main/remote-install.sh | sh" >&2 exit 1 fi } check_agents_md() { if [[ ! -f "$MOSAIC_HOME/AGENTS.md" ]]; then echo "[mosaic] ERROR: ~/.config/mosaic/AGENTS.md not found." >&2 echo "[mosaic] Re-run the installer: cd ~/src/mosaic-bootstrap && bash install.sh" >&2 exit 1 fi } check_soul() { if [[ ! -f "$MOSAIC_HOME/SOUL.md" ]]; then echo "[mosaic] SOUL.md not found. Running mosaic init..." "$MOSAIC_HOME/bin/mosaic-init" fi } check_runtime() { local cmd="$1" if ! command -v "$cmd" >/dev/null 2>&1; then echo "[mosaic] ERROR: '$cmd' not found in PATH." >&2 echo "[mosaic] Install $cmd before launching." >&2 exit 1 fi } check_sequential_thinking() { local runtime="${1:-all}" local checker="$MOSAIC_HOME/bin/mosaic-ensure-sequential-thinking" if [[ ! -x "$checker" ]]; then echo "[mosaic] ERROR: sequential-thinking checker missing: $checker" >&2 exit 1 fi if ! "$checker" --check --runtime "$runtime" >/dev/null 2>&1; then echo "[mosaic] ERROR: sequential-thinking MCP is required but not configured." >&2 echo "[mosaic] Fix config: $checker --runtime $runtime" >&2 echo "[mosaic] Or run: mosaic seq fix --runtime $runtime" >&2 echo "[mosaic] Manual server start: mosaic seq start" >&2 exit 1 fi } runtime_contract_path() { local runtime="$1" case "$runtime" in claude) echo "$MOSAIC_HOME/runtime/claude/RUNTIME.md" ;; codex) echo "$MOSAIC_HOME/runtime/codex/RUNTIME.md" ;; opencode) echo "$MOSAIC_HOME/runtime/opencode/RUNTIME.md" ;; *) echo "[mosaic] ERROR: unsupported runtime '$runtime' for runtime contract." >&2 exit 1 ;; esac } build_runtime_prompt() { local runtime="$1" local runtime_file runtime_file="$(runtime_contract_path "$runtime")" if [[ ! -f "$runtime_file" ]]; then echo "[mosaic] ERROR: runtime contract not found: $runtime_file" >&2 exit 1 fi cat <<'EOF' # 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. EOF cat "$MOSAIC_HOME/AGENTS.md" if [[ -f "$MOSAIC_HOME/USER.md" ]]; then printf '\n\n# User Profile\n\n' cat "$MOSAIC_HOME/USER.md" fi if [[ -f "$MOSAIC_HOME/TOOLS.md" ]]; then printf '\n\n# Machine Tools\n\n' cat "$MOSAIC_HOME/TOOLS.md" fi printf '\n\n# Runtime-Specific Contract\n\n' cat "$runtime_file" } # Ensure runtime contract is present at the runtime's native config path. # Used for runtimes that do not support CLI prompt injection. ensure_runtime_config() { local runtime="$1" local dst="$2" local tmp tmp="$(mktemp)" mkdir -p "$(dirname "$dst")" build_runtime_prompt "$runtime" > "$tmp" if ! cmp -s "$tmp" "$dst" 2>/dev/null; then mv "$tmp" "$dst" else rm -f "$tmp" fi } # Launcher functions launch_claude() { check_mosaic_home check_agents_md check_soul check_runtime "claude" check_sequential_thinking "claude" # Claude supports --append-system-prompt for direct injection local runtime_prompt runtime_prompt="$(build_runtime_prompt "claude")" echo "[mosaic] Launching Claude Code..." exec claude --append-system-prompt "$runtime_prompt" "$@" } launch_opencode() { check_mosaic_home check_agents_md check_soul check_runtime "opencode" check_sequential_thinking "opencode" # OpenCode reads from ~/.config/opencode/AGENTS.md ensure_runtime_config "opencode" "$HOME/.config/opencode/AGENTS.md" echo "[mosaic] Launching OpenCode..." exec opencode "$@" } launch_codex() { check_mosaic_home check_agents_md check_soul check_runtime "codex" check_sequential_thinking "codex" # Codex reads from ~/.codex/instructions.md ensure_runtime_config "codex" "$HOME/.codex/instructions.md" echo "[mosaic] Launching Codex..." exec codex "$@" } launch_yolo() { if [[ $# -eq 0 ]]; then echo "[mosaic] ERROR: yolo requires a runtime (claude|codex|opencode)." >&2 echo "[mosaic] Example: mosaic yolo claude" >&2 exit 1 fi local runtime="$1" shift case "$runtime" in claude) check_mosaic_home check_agents_md check_soul check_runtime "claude" check_sequential_thinking "claude" # Claude uses an explicit dangerous permissions flag. local runtime_prompt runtime_prompt="$(build_runtime_prompt "claude")" echo "[mosaic] Launching Claude Code in YOLO mode (dangerous permissions enabled)..." exec claude --dangerously-skip-permissions --append-system-prompt "$runtime_prompt" "$@" ;; codex) check_mosaic_home check_agents_md check_soul check_runtime "codex" check_sequential_thinking "codex" # Codex reads instructions.md from ~/.codex and supports a direct dangerous flag. ensure_runtime_config "codex" "$HOME/.codex/instructions.md" echo "[mosaic] Launching Codex in YOLO mode (dangerous permissions enabled)..." exec codex --dangerously-bypass-approvals-and-sandbox "$@" ;; opencode) check_mosaic_home check_agents_md check_soul check_runtime "opencode" check_sequential_thinking "opencode" # OpenCode defaults to allow-all permissions unless user config restricts them. ensure_runtime_config "opencode" "$HOME/.config/opencode/AGENTS.md" echo "[mosaic] Launching OpenCode in YOLO mode..." exec opencode "$@" ;; *) echo "[mosaic] ERROR: Unsupported yolo runtime '$runtime'. Use claude|codex|opencode." >&2 exit 1 ;; esac } # Delegate to existing scripts run_init() { check_mosaic_home exec "$MOSAIC_HOME/bin/mosaic-init" "$@" } run_doctor() { check_mosaic_home exec "$MOSAIC_HOME/bin/mosaic-doctor" "$@" } run_sync() { check_mosaic_home exec "$MOSAIC_HOME/bin/mosaic-sync-skills" "$@" } run_seq() { check_mosaic_home local checker="$MOSAIC_HOME/bin/mosaic-ensure-sequential-thinking" local action="${1:-check}" case "$action" in check) shift || true exec "$checker" --check "$@" ;; fix|apply) shift || true exec "$checker" "$@" ;; start) shift || true check_runtime "npx" echo "[mosaic] Starting sequential-thinking MCP server..." exec npx -y @modelcontextprotocol/server-sequential-thinking "$@" ;; *) echo "[mosaic] ERROR: Unknown seq subcommand '$action'." >&2 echo "[mosaic] Use: mosaic seq check|fix|start" >&2 exit 1 ;; esac } run_bootstrap() { check_mosaic_home exec "$MOSAIC_HOME/bin/mosaic-bootstrap-repo" "$@" } run_release_upgrade() { check_mosaic_home exec "$MOSAIC_HOME/bin/mosaic-release-upgrade" "$@" } run_project_upgrade() { check_mosaic_home 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 if [[ $# -eq 0 ]]; then usage exit 0 fi command="$1" shift case "$command" in claude) launch_claude "$@" ;; opencode) launch_opencode "$@" ;; codex) launch_codex "$@" ;; yolo|--yolo) launch_yolo "$@" ;; init) run_init "$@" ;; doctor) run_doctor "$@" ;; sync) run_sync "$@" ;; seq) run_seq "$@" ;; bootstrap) run_bootstrap "$@" ;; upgrade) run_upgrade "$@" ;; release-upgrade) run_release_upgrade "$@" ;; project-upgrade) run_project_upgrade "$@" ;; help|-h|--help) usage ;; version|-v|--version) echo "mosaic $VERSION" ;; *) echo "[mosaic] Unknown command: $command" >&2 echo "[mosaic] Run 'mosaic --help' for usage." >&2 exit 1 ;; esac