diff --git a/packages/mosaic/framework/bin/mosaic b/packages/mosaic/framework/bin/mosaic index 2b9b352..5a686f9 100755 --- a/packages/mosaic/framework/bin/mosaic +++ b/packages/mosaic/framework/bin/mosaic @@ -80,7 +80,7 @@ USAGE 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 + echo "[mosaic] Install with: bash <(curl -fsSL https://git.mosaicstack.dev/mosaic/mosaic-stack/raw/branch/main/tools/install.sh)" >&2 exit 1 fi } @@ -88,7 +88,7 @@ check_mosaic_home() { 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: npm install -g @mosaic/mosaic" >&2 + echo "[mosaic] Re-run the installer: bash <(curl -fsSL https://git.mosaicstack.dev/mosaic/mosaic-stack/raw/branch/main/tools/install.sh)" >&2 exit 1 fi } diff --git a/packages/mosaic/framework/bin/mosaic-release-upgrade b/packages/mosaic/framework/bin/mosaic-release-upgrade index 282650b..2bbbd09 100755 --- a/packages/mosaic/framework/bin/mosaic-release-upgrade +++ b/packages/mosaic/framework/bin/mosaic-release-upgrade @@ -12,7 +12,7 @@ set -euo pipefail # 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}" +REMOTE_SCRIPT_URL="${MOSAIC_REMOTE_INSTALL_URL:-https://git.mosaicstack.dev/mosaic/mosaic-stack/raw/branch/main/tools/install.sh}" BOOTSTRAP_REF="${MOSAIC_BOOTSTRAP_REF:-main}" INSTALL_MODE="${MOSAIC_INSTALL_MODE:-keep}" # keep|overwrite YES=false diff --git a/packages/mosaic/framework/bin/mosaic-release-upgrade.ps1 b/packages/mosaic/framework/bin/mosaic-release-upgrade.ps1 index 016ed7a..dc76cb3 100644 --- a/packages/mosaic/framework/bin/mosaic-release-upgrade.ps1 +++ b/packages/mosaic/framework/bin/mosaic-release-upgrade.ps1 @@ -19,7 +19,7 @@ $MosaicHome = if ($env:MOSAIC_HOME) { $env:MOSAIC_HOME } else { Join-Path $env:U $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" + "https://git.mosaicstack.dev/mosaic/mosaic-stack/raw/branch/main/tools/install.sh" } $installMode = if ($Overwrite) { "overwrite" } elseif ($Keep) { "keep" } elseif ($env:MOSAIC_INSTALL_MODE) { $env:MOSAIC_INSTALL_MODE } else { "keep" } diff --git a/packages/mosaic/framework/bin/mosaic.ps1 b/packages/mosaic/framework/bin/mosaic.ps1 index fcc0bfb..81b7e34 100644 --- a/packages/mosaic/framework/bin/mosaic.ps1 +++ b/packages/mosaic/framework/bin/mosaic.ps1 @@ -49,7 +49,7 @@ Options: 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" + Write-Host "[mosaic] Install with: bash <(curl -fsSL https://git.mosaicstack.dev/mosaic/mosaic-stack/raw/branch/main/tools/install.sh)" exit 1 } } diff --git a/packages/mosaic/framework/defaults/README.md b/packages/mosaic/framework/defaults/README.md index 0dc41b9..3ffeaa3 100644 --- a/packages/mosaic/framework/defaults/README.md +++ b/packages/mosaic/framework/defaults/README.md @@ -1,43 +1,41 @@ # Mosaic Agent Framework -Universal agent standards layer for Claude Code, Codex, and OpenCode. +Universal agent standards layer for Claude Code, Codex, OpenCode, and Pi. One config, every runtime, same standards. -> **This repository is a generic framework baseline.** No personal data, credentials, user-specific preferences, or machine-specific paths should be committed. All personalization happens at install time via `mosaic init` or by editing files in `~/.config/mosaic/` after installation. +> **This is the framework component of [mosaic-stack](https://git.mosaicstack.dev/mosaic/mosaic-stack).** No personal data, credentials, user-specific preferences, or machine-specific paths should be committed. All personalization happens at install time via `mosaic init` or by editing files in `~/.config/mosaic/` after installation. ## Quick Install ### Mac / Linux ```bash -curl -sL https://git.mosaicstack.dev/mosaic/bootstrap/raw/branch/main/remote-install.sh | sh +bash <(curl -fsSL https://git.mosaicstack.dev/mosaic/mosaic-stack/raw/branch/main/tools/install.sh) ``` ### Windows (PowerShell) ```powershell -irm https://git.mosaicstack.dev/mosaic/bootstrap/raw/branch/main/remote-install.ps1 | iex +# PowerShell installer coming soon — use WSL + the bash installer above. ``` ### From Source (any platform) ```bash -git clone https://git.mosaicstack.dev/mosaic/bootstrap.git ~/src/mosaic-bootstrap -cd ~/src/mosaic-bootstrap && bash install.sh +git clone git@git.mosaicstack.dev:mosaic/mosaic-stack.git ~/src/mosaic-stack +cd ~/src/mosaic-stack && bash tools/install.sh ``` -If Node.js 18+ is available, the remote installer automatically uses the TypeScript wizard instead of the bash installer for a richer setup experience. +The installer: -The installer will: - -- Install the framework to `~/.config/mosaic/` -- Add `~/.config/mosaic/bin` to your PATH -- Sync runtime adapters and skills -- Install and configure sequential-thinking MCP (hard requirement) -- 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 +- Downloads the framework from the monorepo archive +- Installs it to `~/.config/mosaic/` +- Installs `@mosaic/cli` globally via npm (TUI, gateway client, wizard) +- Adds `~/.config/mosaic/bin` to your PATH +- Syncs runtime adapters and skills +- Runs a health audit +- Detects existing installs and preserves local files (SOUL.md, USER.md, etc.) ## First Run @@ -58,7 +56,7 @@ The wizard configures three files loaded into every agent session: - `USER.md` — your user profile (name, timezone, accessibility, preferences) - `TOOLS.md` — machine-level tool reference (git providers, credentials, CLI patterns) -It also detects installed runtimes (Claude, Codex, OpenCode), configures sequential-thinking MCP, and offers curated skill selection from 8 categories. +It also detects installed runtimes (Claude, Codex, OpenCode, Pi), configures sequential-thinking MCP, and offers curated skill selection from 8 categories. ### Non-Interactive Mode @@ -77,9 +75,12 @@ If Node.js is unavailable, `mosaic init` falls back to the bash-based `mosaic-in ## Launching Agent Sessions ```bash +mosaic pi # Launch Pi with full Mosaic injection (recommended) mosaic claude # Launch Claude Code with full Mosaic injection mosaic codex # Launch Codex with full Mosaic injection mosaic opencode # Launch OpenCode with full Mosaic injection +mosaic yolo claude # Launch Claude in dangerous-permissions mode +mosaic yolo pi # Launch Pi in yolo mode ``` The launcher: @@ -100,23 +101,18 @@ You can still launch runtimes directly (`claude`, `codex`, etc.) — thin runtim ├── USER.md ← User profile and accessibility (generated by mosaic init) ├── TOOLS.md ← Machine-level tool reference (generated by mosaic init) ├── STANDARDS.md ← Machine-wide standards -├── guides/E2E-DELIVERY.md ← Mandatory E2E software delivery procedure -├── guides/PRD.md ← Mandatory PRD requirements gate before coding -├── guides/DOCUMENTATION.md ← Mandatory documentation standard and gates -├── bin/ ← CLI tools (mosaic, mosaic-init, mosaic-doctor, etc.) -├── dist/ ← Bundled wizard (mosaic-wizard.mjs) -├── guides/ ← Operational guides -├── tools/ ← Tool suites: git, portainer, authentik, coolify, codex, etc. +├── guides/ ← Operational guides (E2E delivery, PRD, docs, etc.) +├── bin/ ← CLI tools (mosaic launcher, mosaic-init, mosaic-doctor, etc.) +├── tools/ ← Tool suites: git, orchestrator, prdy, quality, etc. ├── runtime/ ← Runtime adapters + runtime-specific references -│ ├── claude/CLAUDE.md -│ ├── claude/RUNTIME.md -│ ├── opencode/AGENTS.md -│ ├── opencode/RUNTIME.md -│ ├── codex/instructions.md -│ ├── codex/RUNTIME.md -│ └── mcp/SEQUENTIAL-THINKING.json +│ ├── claude/ ← CLAUDE.md, RUNTIME.md, settings.json, hooks +│ ├── codex/ ← instructions.md, RUNTIME.md +│ ├── opencode/ ← AGENTS.md, RUNTIME.md +│ ├── pi/ ← RUNTIME.md, mosaic-extension.ts +│ └── mcp/ ← MCP server configs ├── skills/ ← Universal skills (synced from mosaic/agent-skills) ├── skills-local/ ← Local cross-runtime skills +├── memory/ ← Persistent agent memory (preserved across upgrades) └── templates/ ← SOUL.md template, project templates ``` @@ -124,6 +120,7 @@ You can still launch runtimes directly (`claude`, `codex`, etc.) — thin runtim | Launch method | Injection mechanism | | ------------------- | ----------------------------------------------------------------------------------------- | +| `mosaic pi` | `--append-system-prompt` with composed runtime contract + skills + extension | | `mosaic claude` | `--append-system-prompt` with composed runtime contract (`AGENTS.md` + runtime reference) | | `mosaic codex` | Writes composed runtime contract to `~/.codex/instructions.md` before launch | | `mosaic opencode` | Writes composed runtime contract to `~/.config/opencode/AGENTS.md` before launch | @@ -131,9 +128,6 @@ You can still launch runtimes directly (`claude`, `codex`, etc.) — thin runtim | `codex` (direct) | `~/.codex/instructions.md` thin pointer → load AGENTS + runtime reference | | `opencode` (direct) | `~/.config/opencode/AGENTS.md` thin pointer → load AGENTS + runtime reference | -Mosaic `AGENTS.md` enforces loading `guides/E2E-DELIVERY.md` before execution and -requires `guides/PRD.md` before coding and `guides/DOCUMENTATION.md` for code/API/auth/infra documentation gates. - ## Management Commands ```bash @@ -142,126 +136,53 @@ mosaic init # Interactive wizard (or legacy init) mosaic doctor # Health audit — detect drift and missing files mosaic sync # Sync skills from canonical source mosaic bootstrap # Bootstrap a repo with Mosaic standards -mosaic upgrade check # Check release upgrade status (no changes) -mosaic upgrade # Upgrade installed Mosaic release (keeps SOUL.md by default) -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) +mosaic upgrade # Upgrade installed Mosaic release +mosaic upgrade check # Check upgrade status (no changes) ``` -## Upgrading Mosaic Release +## Upgrading -Upgrade the installed framework in place: +Run the installer again — it handles upgrades automatically: ```bash -# Default (safe): keep local SOUL.md, USER.md, TOOLS.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 +bash <(curl -fsSL https://git.mosaicstack.dev/mosaic/mosaic-stack/raw/branch/main/tools/install.sh) ``` -`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 - -After centralizing AGENTS.md and SOUL.md, existing projects may have stale files: +Or from a local checkout: ```bash -# Preview what would change across all projects -mosaic upgrade project --all --dry-run - -# Apply to all projects -mosaic upgrade project --all - -# Apply to a specific project -mosaic upgrade project ~/src/my-project +cd ~/src/mosaic-stack && git pull && bash tools/install.sh ``` -Backward compatibility is preserved for historical usage: +The installer preserves local `SOUL.md`, `USER.md`, `TOOLS.md`, and `memory/` by default. + +### Flags ```bash -mosaic upgrade --all # still routes to project-upgrade -mosaic upgrade ~/src/my-repo # still routes to project-upgrade +bash tools/install.sh --check # Version check only +bash tools/install.sh --framework # Framework only (skip npm CLI) +bash tools/install.sh --cli # npm CLI only (skip framework) +bash tools/install.sh --ref v1.0 # Install from a specific git ref ``` -What it does per project: - -| File | Action | -| ----------- | ------------------------------------------------------------- | -| `SOUL.md` | Removed — now global at `~/.config/mosaic/SOUL.md` | -| `CLAUDE.md` | Replaced with thin pointer to global AGENTS.md | -| `AGENTS.md` | Stale load-order sections stripped; project content preserved | - -Backups (`.mosaic-bak`) are created before any modification. - ## Universal Skills -The installer syncs skills from `mosaic/agent-skills` into `~/.config/mosaic/skills/`, then links each skill into runtime directories (`~/.claude/skills`, `~/.codex/skills`, `~/.config/opencode/skills`). +The installer syncs skills from `mosaic/agent-skills` into `~/.config/mosaic/skills/`, then links each skill into runtime directories. ```bash mosaic sync # Full sync (clone + link) ~/.config/mosaic/bin/mosaic-sync-skills --link-only # Re-link only ``` -## Runtime Compatibility - -The installer pushes thin runtime adapters as regular files (not symlinks): - -- `~/.claude/CLAUDE.md` — pointer to `~/.config/mosaic/AGENTS.md` -- `~/.claude/settings.json`, `hooks-config.json`, `context7-integration.md` -- `~/.config/opencode/AGENTS.md` — pointer to `~/.config/mosaic/AGENTS.md` -- `~/.codex/instructions.md` — pointer to `~/.config/mosaic/AGENTS.md` -- `~/.claude/settings.json`, `~/.codex/config.toml`, and `~/.config/opencode/config.json` include sequential-thinking MCP config - -Re-sync manually: +## Health Audit ```bash -~/.config/mosaic/bin/mosaic-link-runtime-assets +mosaic doctor # Standard audit +~/.config/mosaic/bin/mosaic-doctor --fail-on-warn # Strict mode ``` ## MCP Registration -### How MCPs Are Configured in Claude Code - -**MCPs must be registered via `claude mcp add` — not by hand-editing `~/.claude/settings.json`.** - -`settings.json` controls hooks, model, plugins, and allowed commands. The `mcpServers` key in -`settings.json` is silently ignored by Claude Code's MCP loader. The correct file is `~/.claude.json`, -which is managed by the `claude mcp` CLI. - -```bash -# Register a stdio MCP (user scope = all projects, persists across sessions) -claude mcp add --scope user -- npx -y - -# Register an HTTP MCP (e.g. OpenBrain) -claude mcp add --scope user --transport http \ - --header "Authorization: Bearer " - -# List registered MCPs -claude mcp list -``` - -**Scope options:** - -- `--scope user` — writes to `~/.claude.json`, available in all projects (recommended for shared tools) -- `--scope project` — writes to `.claude/settings.json` in the project root, committed to the repo -- `--scope local` — default, machine-local only, not committed - -**Transport for HTTP MCPs must be `http`** — not `sse`. `type: "sse"` is a deprecated protocol -that silently fails to connect against FastMCP streamable HTTP servers. - ### sequential-thinking MCP (Hard Requirement) sequential-thinking MCP is required for Mosaic Stack. The installer registers it automatically. @@ -272,74 +193,12 @@ To verify or re-register manually: ~/.config/mosaic/bin/mosaic-ensure-sequential-thinking --check ``` -### OpenBrain Semantic Memory (Recommended) +### Claude Code MCP Registration -OpenBrain is the shared cross-agent memory layer. Register once per machine: +**MCPs must be registered via `claude mcp add` — not by hand-editing `~/.claude/settings.json`.** ```bash -claude mcp add --scope user --transport http openbrain https://your-openbrain-host/mcp \ - --header "Authorization: Bearer YOUR_TOKEN" -``` - -See [mosaic/openbrain](https://git.mosaicstack.dev/mosaic/openbrain) for setup and API docs. - -## Bootstrap Any Repo - -Attach any repository to the Mosaic standards layer: - -```bash -mosaic bootstrap /path/to/repo -``` - -This creates `.mosaic/`, `scripts/agent/`, and an `AGENTS.md` if missing. - -## Quality Rails - -Apply and verify quality templates: - -```bash -~/.config/mosaic/bin/mosaic-quality-apply --template typescript-node --target /path/to/repo -~/.config/mosaic/bin/mosaic-quality-verify --target /path/to/repo -``` - -Templates: `typescript-node`, `typescript-nextjs`, `monorepo` - -## Health Audit - -```bash -mosaic doctor # Standard audit -~/.config/mosaic/bin/mosaic-doctor --fail-on-warn # Strict mode -``` - -## Wizard Development - -The installation wizard is a TypeScript project in the root of this repo. - -```bash -pnpm install # Install dependencies -pnpm dev # Run wizard from source (tsx) -pnpm build # Bundle to dist/mosaic-wizard.mjs -pnpm test # Run tests (30 tests, vitest) -pnpm typecheck # TypeScript type checking -``` - -The wizard uses `@clack/prompts` for the interactive TUI and supports `--non-interactive` mode via `HeadlessPrompter` for CI and scripted installs. The bundled output (`dist/mosaic-wizard.mjs`) is committed to the repo so installs work without `node_modules`. - -## Re-installing / Updating - -Pull the latest and re-run the installer: - -```bash -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`, `USER.md`, `TOOLS.md`, and `memory/` -- `overwrite`: replace everything in `~/.config/mosaic` - -Or use the one-liner again — it always pulls the latest: - -```bash -curl -sL https://git.mosaicstack.dev/mosaic/bootstrap/raw/branch/main/remote-install.sh | sh +claude mcp add --scope user -- npx -y +claude mcp add --scope user --transport http --header "Authorization: Bearer " +claude mcp list ``` diff --git a/packages/mosaic/framework/remote-install.ps1 b/packages/mosaic/framework/remote-install.ps1 deleted file mode 100644 index 635ab11..0000000 --- a/packages/mosaic/framework/remote-install.ps1 +++ /dev/null @@ -1,38 +0,0 @@ -# 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" - -$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" -$ZipPath = "$WorkDir.zip" - -try { - Write-Host "[mosaic] Downloading bootstrap archive (ref: $BootstrapRef)..." - 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/packages/mosaic/framework/remote-install.sh b/packages/mosaic/framework/remote-install.sh deleted file mode 100755 index 114be02..0000000 --- a/packages/mosaic/framework/remote-install.sh +++ /dev/null @@ -1,63 +0,0 @@ -#!/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 - -BOOTSTRAP_REF="${MOSAIC_BOOTSTRAP_REF:-main}" -ARCHIVE_URL="https://git.mosaicstack.dev/mosaic/bootstrap/archive/${BOOTSTRAP_REF}.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 (ref: $BOOTSTRAP_REF)..." - -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 - -cd "$WORK_DIR/bootstrap" - -# Prefer TypeScript wizard if Node.js 18+ and bundle are available -WIZARD_BIN="$WORK_DIR/bootstrap/dist/mosaic-wizard.mjs" -if command -v node >/dev/null 2>&1 && [ -f "$WIZARD_BIN" ]; then - NODE_MAJOR="$(node -e 'console.log(process.versions.node.split(".")[0])')" - if [ "$NODE_MAJOR" -ge 18 ] 2>/dev/null; then - if [ -e /dev/tty ]; then - echo "[mosaic] Running wizard installer (Node.js $NODE_MAJOR detected)..." - node "$WIZARD_BIN" --source-dir "$WORK_DIR/bootstrap" Git ref for framework archive (default: main) # # Environment: -# MOSAIC_REGISTRY — npm registry URL (default: Gitea instance) -# MOSAIC_SCOPE — npm scope (default: @mosaic) -# MOSAIC_PREFIX — npm global prefix (default: ~/.npm-global) -# MOSAIC_NO_COLOR — disable colour (set to 1) +# MOSAIC_HOME — framework install dir (default: ~/.config/mosaic) +# MOSAIC_REGISTRY — npm registry URL (default: Gitea instance) +# MOSAIC_SCOPE — npm scope (default: @mosaic) +# MOSAIC_PREFIX — npm global prefix (default: ~/.npm-global) +# MOSAIC_NO_COLOR — disable colour (set to 1) +# MOSAIC_REF — git ref for framework (default: main) # ────────────────────────────────────────────────────────────────────────────── # -# The entire script is wrapped in main() so that bash reads it fully into -# memory before execution — required for safe curl-pipe usage. +# Wrapped in main() for safe curl-pipe usage. set -euo pipefail main() { +# ─── parse flags ────────────────────────────────────────────────────────────── +FLAG_CHECK=false +FLAG_FRAMEWORK=true +FLAG_CLI=true +GIT_REF="${MOSAIC_REF:-main}" + +while [[ $# -gt 0 ]]; do + case "$1" in + --check) FLAG_CHECK=true; shift ;; + --framework) FLAG_CLI=false; shift ;; + --cli) FLAG_FRAMEWORK=false; shift ;; + --ref) GIT_REF="${2:-main}"; shift 2 ;; + *) shift ;; + esac +done + # ─── constants ──────────────────────────────────────────────────────────────── +MOSAIC_HOME="${MOSAIC_HOME:-$HOME/.config/mosaic}" REGISTRY="${MOSAIC_REGISTRY:-https://git.mosaicstack.dev/api/packages/mosaic/npm/}" SCOPE="${MOSAIC_SCOPE:-@mosaic}" PREFIX="${MOSAIC_PREFIX:-$HOME/.npm-global}" CLI_PKG="${SCOPE}/cli" +REPO_BASE="https://git.mosaicstack.dev/mosaic/mosaic-stack" +ARCHIVE_URL="${REPO_BASE}/archive/${GIT_REF}.tar.gz" # ─── colours ────────────────────────────────────────────────────────────────── -# Detect colour support: works in terminals and bash <(curl …), but correctly -# disables when piped through a non-tty (curl … | bash). if [[ "${MOSAIC_NO_COLOR:-0}" == "1" ]] || ! [[ -t 1 ]]; then - R="" G="" Y="" B="" C="" DIM="" RESET="" + R="" G="" Y="" B="" C="" DIM="" BOLD="" RESET="" else R=$'\033[0;31m' G=$'\033[0;32m' Y=$'\033[0;33m' - B=$'\033[0;34m' C=$'\033[0;36m' DIM=$'\033[2m' RESET=$'\033[0m' + B=$'\033[0;34m' C=$'\033[0;36m' DIM=$'\033[2m' + BOLD=$'\033[1m' RESET=$'\033[0m' fi info() { echo "${B}ℹ${RESET} $*"; } @@ -47,6 +71,7 @@ ok() { echo "${G}✔${RESET} $*"; } warn() { echo "${Y}⚠${RESET} $*"; } fail() { echo "${R}✖${RESET} $*" >&2; } dim() { echo "${DIM}$*${RESET}"; } +step() { echo ""; echo "${BOLD}$*${RESET}"; } # ─── helpers ────────────────────────────────────────────────────────────────── @@ -58,12 +83,10 @@ require_cmd() { fi } -# Get the installed version of @mosaic/cli (empty string if not installed) -installed_version() { +installed_cli_version() { local json json="$(npm ls -g --depth=0 --json --prefix="$PREFIX" 2>/dev/null)" || true if [[ -n "$json" ]]; then - # Feed json via heredoc, not stdin pipe — safe in curl-pipe context node -e " const d = JSON.parse(process.argv[1]); const v = d?.dependencies?.['${CLI_PKG}']?.version ?? ''; @@ -72,14 +95,11 @@ installed_version() { fi } -# Get the latest published version from the registry -latest_version() { +latest_cli_version() { npm view "${CLI_PKG}" version --registry="$REGISTRY" 2>/dev/null || true } -# Compare two semver strings: returns 0 (true) if $1 < $2 version_lt() { - # Semver-aware comparison via node (handles pre-release correctly) node -e " const a=process.argv[1], b=process.argv[2]; const sp = v => { const i=v.indexOf('-'); return i===-1 ? [v,null] : [v.slice(0,i),v.slice(i+1)]; }; @@ -93,6 +113,14 @@ version_lt() { " "$1" "$2" 2>/dev/null } +framework_version() { + # Read VERSION from the installed mosaic launcher + local mosaic_bin="$MOSAIC_HOME/bin/mosaic" + if [[ -f "$mosaic_bin" ]]; then + grep -m1 '^VERSION=' "$mosaic_bin" 2>/dev/null | cut -d'"' -f2 || true + fi +} + # ─── preflight ──────────────────────────────────────────────────────────────── require_cmd node @@ -104,116 +132,211 @@ if [[ "$NODE_MAJOR" -lt 20 ]]; then exit 1 fi -# ─── ensure prefix directory exists ────────────────────────────────────────── - -if [[ ! -d "$PREFIX" ]]; then - info "Creating global prefix directory: $PREFIX" - mkdir -p "$PREFIX"/{bin,lib} -fi - -# ─── ensure npmrc scope mapping ────────────────────────────────────────────── - -NPMRC="$HOME/.npmrc" -SCOPE_LINE="${SCOPE}:registry=${REGISTRY}" - -if ! grep -qF "$SCOPE_LINE" "$NPMRC" 2>/dev/null; then - info "Adding ${SCOPE} registry to $NPMRC" - echo "$SCOPE_LINE" >> "$NPMRC" - ok "Registry configured" -fi - -# Ensure prefix is set (only if no prefix= line exists yet) -if ! grep -qF "prefix=$PREFIX" "$NPMRC" 2>/dev/null; then - if ! grep -q '^prefix=' "$NPMRC" 2>/dev/null; then - echo "prefix=$PREFIX" >> "$NPMRC" - info "Set npm global prefix to $PREFIX" - fi -fi - -# Ensure PREFIX/bin is on PATH -if [[ ":$PATH:" != *":$PREFIX/bin:"* ]]; then - warn "$PREFIX/bin is not on your PATH" - dim " Add to your shell rc: export PATH=\"$PREFIX/bin:\$PATH\"" -fi - -# ─── version check ─────────────────────────────────────────────────────────── - -info "Checking versions…" - -CURRENT="$(installed_version)" -LATEST="$(latest_version)" - -if [[ -z "$LATEST" ]]; then - fail "Could not reach registry at $REGISTRY" - fail "Check network connectivity and registry URL." - exit 1 -fi - echo "" -if [[ -n "$CURRENT" ]]; then - dim " Installed: ${CLI_PKG}@${CURRENT}" -else - dim " Installed: (none)" -fi -dim " Latest: ${CLI_PKG}@${LATEST}" +echo "${BOLD}Mosaic Stack Installer${RESET}" echo "" -# --check flag: just report, don't install -if [[ "${1:-}" == "--check" ]]; then - if [[ -z "$CURRENT" ]]; then - warn "Not installed. Run without --check to install." - exit 1 - elif [[ "$CURRENT" == "$LATEST" ]]; then - ok "Up to date." - exit 0 - elif version_lt "$CURRENT" "$LATEST"; then - warn "Update available: $CURRENT → $LATEST" - exit 2 +# ═══════════════════════════════════════════════════════════════════════════════ +# PART 1: Framework (bash launcher + guides + runtime configs + tools) +# ═══════════════════════════════════════════════════════════════════════════════ + +if [[ "$FLAG_FRAMEWORK" == "true" ]]; then + step "Framework (~/.config/mosaic)" + + FRAMEWORK_CURRENT="$(framework_version)" + HAS_FRAMEWORK=false + [[ -d "$MOSAIC_HOME/bin" ]] && [[ -f "$MOSAIC_HOME/bin/mosaic" ]] && HAS_FRAMEWORK=true + + if [[ -n "$FRAMEWORK_CURRENT" ]]; then + dim " Installed: framework v${FRAMEWORK_CURRENT}" + elif [[ "$HAS_FRAMEWORK" == "true" ]]; then + dim " Installed: framework (version unknown)" else - ok "Up to date (or ahead of registry)." - exit 0 + dim " Installed: (none)" fi -fi - -# ─── install / upgrade ────────────────────────────────────────────────────── - -if [[ -z "$CURRENT" ]]; then - info "Installing ${CLI_PKG}@${LATEST}…" -elif [[ "$CURRENT" == "$LATEST" ]]; then - ok "Already at latest version ($LATEST). Nothing to do." - exit 0 -elif version_lt "$CURRENT" "$LATEST"; then - info "Upgrading ${CLI_PKG}: $CURRENT → $LATEST…" -else - ok "Installed version ($CURRENT) is at or ahead of registry ($LATEST)." - exit 0 -fi - -# NOTE: Do NOT pass --registry here. The @mosaic scope is already mapped -# in ~/.npmrc. Passing --registry globally would redirect ALL deps (including -# @clack/prompts, commander, etc.) to the Gitea registry, causing 404s. -npm install -g "${CLI_PKG}@${LATEST}" \ - --prefix="$PREFIX" \ - 2>&1 | sed 's/^/ /' - -echo "" -ok "Mosaic CLI installed: $(installed_version)" -dim " Binary: $PREFIX/bin/mosaic" - -# ─── post-install: run wizard if first install ─────────────────────────────── - -if [[ -z "$CURRENT" ]]; then + dim " Source: ${REPO_BASE} (ref: ${GIT_REF})" echo "" - info "First install detected." - if [[ -t 0 ]] && [[ -t 1 ]]; then - echo " Run ${C}mosaic wizard${RESET} to set up your configuration." + + if [[ "$FLAG_CHECK" == "true" ]]; then + if [[ "$HAS_FRAMEWORK" == "true" ]]; then + ok "Framework is installed." + else + warn "Framework not installed." + fi else - dim " Run 'mosaic wizard' to set up your configuration." + # Download repo archive and extract framework + require_cmd tar + + WORK_DIR="$(mktemp -d "${TMPDIR:-/tmp}/mosaic-install-XXXXXX")" + cleanup_work() { rm -rf "$WORK_DIR"; } + trap cleanup_work EXIT + + info "Downloading framework from ${GIT_REF}…" + if command -v curl &>/dev/null; then + curl -fsSL "$ARCHIVE_URL" | tar xz -C "$WORK_DIR" + elif command -v wget &>/dev/null; then + wget -qO- "$ARCHIVE_URL" | tar xz -C "$WORK_DIR" + else + fail "curl or wget required to download framework." + exit 1 + fi + + # Gitea archives extract to / inside the work dir + EXTRACTED_DIR="$(find "$WORK_DIR" -maxdepth 1 -mindepth 1 -type d | head -1)" + FRAMEWORK_SRC="$EXTRACTED_DIR/packages/mosaic/framework" + + if [[ ! -d "$FRAMEWORK_SRC" ]]; then + fail "Framework not found in archive at packages/mosaic/framework/" + fail "Archive contents:" + ls -la "$WORK_DIR" >&2 + exit 1 + fi + + # Run the framework's own install.sh (handles keep/overwrite for SOUL.md etc.) + info "Installing framework to ${MOSAIC_HOME}…" + MOSAIC_INSTALL_MODE="${MOSAIC_INSTALL_MODE:-keep}" \ + MOSAIC_ALLOW_MISSING_SEQUENTIAL_THINKING=1 \ + MOSAIC_SKIP_SKILLS_SYNC="${MOSAIC_SKIP_SKILLS_SYNC:-0}" \ + bash "$FRAMEWORK_SRC/install.sh" + + ok "Framework installed" + echo "" + + # Ensure framework bin is on PATH + FRAMEWORK_BIN="$MOSAIC_HOME/bin" + if [[ ":$PATH:" != *":$FRAMEWORK_BIN:"* ]]; then + warn "$FRAMEWORK_BIN is not on your PATH" + dim " The 'mosaic' launcher lives here. Add to your shell rc:" + dim " export PATH=\"$FRAMEWORK_BIN:\$PATH\"" + fi fi fi -echo "" -ok "Done." +# ═══════════════════════════════════════════════════════════════════════════════ +# PART 2: @mosaic/cli (npm — TUI, gateway client, wizard) +# ═══════════════════════════════════════════════════════════════════════════════ + +if [[ "$FLAG_CLI" == "true" ]]; then + step "@mosaic/cli (npm package)" + + # Ensure prefix dir + if [[ ! -d "$PREFIX" ]]; then + info "Creating global prefix directory: $PREFIX" + mkdir -p "$PREFIX"/{bin,lib} + fi + + # Ensure npmrc scope mapping + NPMRC="$HOME/.npmrc" + SCOPE_LINE="${SCOPE}:registry=${REGISTRY}" + + if ! grep -qF "$SCOPE_LINE" "$NPMRC" 2>/dev/null; then + info "Adding ${SCOPE} registry to $NPMRC" + echo "$SCOPE_LINE" >> "$NPMRC" + ok "Registry configured" + fi + + if ! grep -qF "prefix=$PREFIX" "$NPMRC" 2>/dev/null; then + if ! grep -q '^prefix=' "$NPMRC" 2>/dev/null; then + echo "prefix=$PREFIX" >> "$NPMRC" + info "Set npm global prefix to $PREFIX" + fi + fi + + CURRENT="$(installed_cli_version)" + LATEST="$(latest_cli_version)" + + if [[ -n "$CURRENT" ]]; then + dim " Installed: ${CLI_PKG}@${CURRENT}" + else + dim " Installed: (none)" + fi + + if [[ -n "$LATEST" ]]; then + dim " Latest: ${CLI_PKG}@${LATEST}" + else + dim " Latest: (registry unreachable)" + fi + echo "" + + if [[ "$FLAG_CHECK" == "true" ]]; then + if [[ -z "$LATEST" ]]; then + warn "Could not reach registry." + elif [[ -z "$CURRENT" ]]; then + warn "Not installed." + elif [[ "$CURRENT" == "$LATEST" ]]; then + ok "Up to date." + elif version_lt "$CURRENT" "$LATEST"; then + warn "Update available: $CURRENT → $LATEST" + else + ok "Up to date (or ahead of registry)." + fi + else + if [[ -z "$LATEST" ]]; then + warn "Could not reach registry at $REGISTRY — skipping npm CLI." + elif [[ -z "$CURRENT" ]]; then + info "Installing ${CLI_PKG}@${LATEST}…" + npm install -g "${CLI_PKG}@${LATEST}" --prefix="$PREFIX" 2>&1 | sed 's/^/ /' + ok "CLI installed: $(installed_cli_version)" + elif [[ "$CURRENT" == "$LATEST" ]]; then + ok "Already at latest version ($LATEST)." + elif version_lt "$CURRENT" "$LATEST"; then + info "Upgrading ${CLI_PKG}: $CURRENT → $LATEST…" + npm install -g "${CLI_PKG}@${LATEST}" --prefix="$PREFIX" 2>&1 | sed 's/^/ /' + ok "CLI upgraded: $(installed_cli_version)" + else + ok "CLI is at or ahead of registry ($CURRENT ≥ $LATEST)." + fi + + # PATH check for npm prefix + if [[ ":$PATH:" != *":$PREFIX/bin:"* ]]; then + warn "$PREFIX/bin is not on your PATH" + dim " The 'mosaic' TUI/gateway CLI lives here (separate from the launcher)." + dim " Add to your shell rc: export PATH=\"$PREFIX/bin:\$PATH\"" + fi + fi +fi + +# ═══════════════════════════════════════════════════════════════════════════════ +# Summary +# ═══════════════════════════════════════════════════════════════════════════════ + +if [[ "$FLAG_CHECK" == "false" ]]; then + step "Summary" + + echo " ${BOLD}Framework launcher:${RESET} $MOSAIC_HOME/bin/mosaic" + echo " ${DIM}mosaic claude, mosaic yolo claude, mosaic pi, mosaic doctor, …${RESET}" + echo "" + echo " ${BOLD}npm CLI (TUI):${RESET} $PREFIX/bin/mosaic" + echo " ${DIM}mosaic tui, mosaic login, mosaic wizard, mosaic update, …${RESET}" + echo "" + + # Warn if there's a naming collision (both on PATH) + FRAMEWORK_BIN="$MOSAIC_HOME/bin" + if [[ ":$PATH:" == *":$FRAMEWORK_BIN:"* ]] && [[ ":$PATH:" == *":$PREFIX/bin:"* ]]; then + # Check which one wins + WHICH_MOSAIC="$(command -v mosaic 2>/dev/null || true)" + if [[ -n "$WHICH_MOSAIC" ]]; then + dim " Active 'mosaic' binary: $WHICH_MOSAIC" + if [[ "$WHICH_MOSAIC" == "$FRAMEWORK_BIN/mosaic" ]]; then + dim " (Framework launcher takes priority — this is correct)" + else + warn "npm CLI shadows the framework launcher!" + dim " Ensure $FRAMEWORK_BIN appears BEFORE $PREFIX/bin in your PATH." + fi + fi + fi + + # First install guidance + if [[ ! -f "$MOSAIC_HOME/SOUL.md" ]]; then + echo "" + info "First install detected. Set up your agent identity:" + echo " ${C}mosaic init${RESET} (interactive SOUL.md / USER.md setup)" + echo " ${C}mosaic wizard${RESET} (full guided wizard via Node.js)" + fi + + echo "" + ok "Done." +fi } # end main