feat: unify install.sh — single installer for framework + npm CLI
Some checks failed
ci/woodpecker/push/ci Pipeline failed
ci/woodpecker/pr/ci Pipeline failed

- tools/install.sh now installs both components:
  1. Framework (bash launcher, guides, runtime configs) → ~/.config/mosaic/
  2. @mosaic/cli (TUI, gateway client, wizard) → ~/.npm-global/
- Downloads framework from monorepo archive (no bootstrap repo dependency)
- Supports --framework, --cli, --check, --ref flags
- Delete remote-install.sh and remote-install.ps1 (redundant redirectors)
- Update all stale mosaic/bootstrap references → mosaic/mosaic-stack
- Update README.md with monorepo install instructions

Deprecates: mosaic/bootstrap repo
This commit is contained in:
Jarvis
2026-04-01 21:32:19 -05:00
parent 1d731fd3f3
commit de12221d47
8 changed files with 300 additions and 419 deletions

View File

@@ -80,7 +80,7 @@ USAGE
check_mosaic_home() { check_mosaic_home() {
if [[ ! -d "$MOSAIC_HOME" ]]; then if [[ ! -d "$MOSAIC_HOME" ]]; then
echo "[mosaic] ERROR: ~/.config/mosaic not found." >&2 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 exit 1
fi fi
} }
@@ -88,7 +88,7 @@ check_mosaic_home() {
check_agents_md() { check_agents_md() {
if [[ ! -f "$MOSAIC_HOME/AGENTS.md" ]]; then if [[ ! -f "$MOSAIC_HOME/AGENTS.md" ]]; then
echo "[mosaic] ERROR: ~/.config/mosaic/AGENTS.md not found." >&2 echo "[mosaic] ERROR: ~/.config/mosaic/AGENTS.md not found." >&2
echo "[mosaic] Re-run the installer: cd ~/src/mosaic-bootstrap && bash install.sh" >&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 exit 1
fi fi
} }

View File

@@ -12,7 +12,7 @@ set -euo pipefail
# mosaic-release-upgrade --ref v0.2.0 --overwrite --yes # mosaic-release-upgrade --ref v0.2.0 --overwrite --yes
MOSAIC_HOME="${MOSAIC_HOME:-$HOME/.config/mosaic}" 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}" BOOTSTRAP_REF="${MOSAIC_BOOTSTRAP_REF:-main}"
INSTALL_MODE="${MOSAIC_INSTALL_MODE:-keep}" # keep|overwrite INSTALL_MODE="${MOSAIC_INSTALL_MODE:-keep}" # keep|overwrite
YES=false YES=false

View File

@@ -19,7 +19,7 @@ $MosaicHome = if ($env:MOSAIC_HOME) { $env:MOSAIC_HOME } else { Join-Path $env:U
$RemoteInstallerUrl = if ($env:MOSAIC_REMOTE_INSTALL_URL) { $RemoteInstallerUrl = if ($env:MOSAIC_REMOTE_INSTALL_URL) {
$env:MOSAIC_REMOTE_INSTALL_URL $env:MOSAIC_REMOTE_INSTALL_URL
} else { } 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" } $installMode = if ($Overwrite) { "overwrite" } elseif ($Keep) { "keep" } elseif ($env:MOSAIC_INSTALL_MODE) { $env:MOSAIC_INSTALL_MODE } else { "keep" }

View File

@@ -49,7 +49,7 @@ Options:
function Assert-MosaicHome { function Assert-MosaicHome {
if (-not (Test-Path $MosaicHome)) { if (-not (Test-Path $MosaicHome)) {
Write-Host "[mosaic] ERROR: ~/.config/mosaic not found." -ForegroundColor Red 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 exit 1
} }
} }

View File

@@ -1,43 +1,41 @@
# Mosaic Agent Framework # 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. 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 ## Quick Install
### Mac / Linux ### Mac / Linux
```bash ```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) ### Windows (PowerShell)
```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) ### From Source (any platform)
```bash ```bash
git clone https://git.mosaicstack.dev/mosaic/bootstrap.git ~/src/mosaic-bootstrap git clone git@git.mosaicstack.dev:mosaic/mosaic-stack.git ~/src/mosaic-stack
cd ~/src/mosaic-bootstrap && bash install.sh 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: - Downloads the framework from the monorepo archive
- Installs it to `~/.config/mosaic/`
- Install the framework to `~/.config/mosaic/` - Installs `@mosaic/cli` globally via npm (TUI, gateway client, wizard)
- Add `~/.config/mosaic/bin` to your PATH - Adds `~/.config/mosaic/bin` to your PATH
- Sync runtime adapters and skills - Syncs runtime adapters and skills
- Install and configure sequential-thinking MCP (hard requirement) - Runs a health audit
- Run a health audit - Detects existing installs and preserves local files (SOUL.md, USER.md, etc.)
- Detect existing installs and prompt to keep or overwrite local files
- Prompt you to run `mosaic init` to set up your agent identity
## First Run ## 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) - `USER.md` — your user profile (name, timezone, accessibility, preferences)
- `TOOLS.md` — machine-level tool reference (git providers, credentials, CLI patterns) - `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 ### 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 ## Launching Agent Sessions
```bash ```bash
mosaic pi # Launch Pi with full Mosaic injection (recommended)
mosaic claude # Launch Claude Code with full Mosaic injection mosaic claude # Launch Claude Code with full Mosaic injection
mosaic codex # Launch Codex with full Mosaic injection mosaic codex # Launch Codex with full Mosaic injection
mosaic opencode # Launch OpenCode 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: 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) ├── USER.md ← User profile and accessibility (generated by mosaic init)
├── TOOLS.md ← Machine-level tool reference (generated by mosaic init) ├── TOOLS.md ← Machine-level tool reference (generated by mosaic init)
├── STANDARDS.md ← Machine-wide standards ├── STANDARDS.md ← Machine-wide standards
├── guides/E2E-DELIVERY.md ← Mandatory E2E software delivery procedure ├── guides/ ← Operational guides (E2E delivery, PRD, docs, etc.)
├── guides/PRD.md ← Mandatory PRD requirements gate before coding ├── bin/ ← CLI tools (mosaic launcher, mosaic-init, mosaic-doctor, etc.)
├── guides/DOCUMENTATION.md ← Mandatory documentation standard and gates ├── tools/ ← Tool suites: git, orchestrator, prdy, quality, etc.
├── 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.
├── runtime/ ← Runtime adapters + runtime-specific references ├── runtime/ ← Runtime adapters + runtime-specific references
│ ├── claude/CLAUDE.md │ ├── claude/ ← CLAUDE.md, RUNTIME.md, settings.json, hooks
│ ├── claude/RUNTIME.md │ ├── codex/ ← instructions.md, RUNTIME.md
│ ├── opencode/AGENTS.md │ ├── opencode/ ← AGENTS.md, RUNTIME.md
│ ├── opencode/RUNTIME.md │ ├── pi/ ← RUNTIME.md, mosaic-extension.ts
── codex/instructions.md ── mcp/ ← MCP server configs
│ ├── codex/RUNTIME.md
│ └── mcp/SEQUENTIAL-THINKING.json
├── skills/ ← Universal skills (synced from mosaic/agent-skills) ├── skills/ ← Universal skills (synced from mosaic/agent-skills)
├── skills-local/ ← Local cross-runtime skills ├── skills-local/ ← Local cross-runtime skills
├── memory/ ← Persistent agent memory (preserved across upgrades)
└── templates/ ← SOUL.md template, project templates └── 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 | | 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 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 codex` | Writes composed runtime contract to `~/.codex/instructions.md` before launch |
| `mosaic opencode` | Writes composed runtime contract to `~/.config/opencode/AGENTS.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 | | `codex` (direct) | `~/.codex/instructions.md` thin pointer → load AGENTS + runtime reference |
| `opencode` (direct) | `~/.config/opencode/AGENTS.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 ## Management Commands
```bash ```bash
@@ -142,126 +136,53 @@ mosaic init # Interactive wizard (or legacy init)
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 check # Check release upgrade status (no changes) mosaic upgrade # Upgrade installed Mosaic release
mosaic upgrade # Upgrade installed Mosaic release (keeps SOUL.md by default) mosaic upgrade check # Check upgrade status (no 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 ## Upgrading
Upgrade the installed framework in place: Run the installer again — it handles upgrades automatically:
```bash ```bash
# Default (safe): keep local SOUL.md, USER.md, TOOLS.md + memory bash <(curl -fsSL https://git.mosaicstack.dev/mosaic/mosaic-stack/raw/branch/main/tools/install.sh)
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`). Or from a local checkout:
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:
```bash ```bash
# Preview what would change across all projects cd ~/src/mosaic-stack && git pull && bash tools/install.sh
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
``` ```
Backward compatibility is preserved for historical usage: The installer preserves local `SOUL.md`, `USER.md`, `TOOLS.md`, and `memory/` by default.
### Flags
```bash ```bash
mosaic upgrade --all # still routes to project-upgrade bash tools/install.sh --check # Version check only
mosaic upgrade ~/src/my-repo # still routes to project-upgrade 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 ## 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 ```bash
mosaic sync # Full sync (clone + link) mosaic sync # Full sync (clone + link)
~/.config/mosaic/bin/mosaic-sync-skills --link-only # Re-link only ~/.config/mosaic/bin/mosaic-sync-skills --link-only # Re-link only
``` ```
## Runtime Compatibility ## Health Audit
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:
```bash ```bash
~/.config/mosaic/bin/mosaic-link-runtime-assets mosaic doctor # Standard audit
~/.config/mosaic/bin/mosaic-doctor --fail-on-warn # Strict mode
``` ```
## MCP Registration ## 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 <name> -- npx -y <package>
# Register an HTTP MCP (e.g. OpenBrain)
claude mcp add --scope user --transport http <name> <url> \
--header "Authorization: Bearer <token>"
# 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 (Hard Requirement)
sequential-thinking MCP is required for Mosaic Stack. The installer registers it automatically. 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 ~/.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 ```bash
claude mcp add --scope user --transport http openbrain https://your-openbrain-host/mcp \ claude mcp add --scope user <name> -- npx -y <package>
--header "Authorization: Bearer YOUR_TOKEN" claude mcp add --scope user --transport http <name> <url> --header "Authorization: Bearer <token>"
``` claude mcp list
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
``` ```

View File

@@ -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
}

View File

@@ -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" </dev/tty
else
echo "[mosaic] Running wizard installer in non-interactive mode (no TTY)..."
node "$WIZARD_BIN" --source-dir "$WORK_DIR/bootstrap" --non-interactive
fi
echo "[mosaic] Cleaning up temporary files..."
exit 0
fi
fi
echo "[mosaic] Running legacy install..."
bash install.sh </dev/tty
echo "[mosaic] Cleaning up temporary files..."
# cleanup runs via trap

View File

@@ -1,45 +1,69 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# ─── Mosaic Stack Installer / Upgrader ──────────────────────────────────────── # ─── Mosaic Stack Installer / Upgrader ────────────────────────────────────────
# #
# Installs both components:
# 1. Mosaic framework → ~/.config/mosaic/ (bash launcher, guides, runtime configs, tools)
# 2. @mosaic/cli (npm) → ~/.npm-global/ (TUI, gateway client, wizard)
#
# Remote install (recommended): # Remote install (recommended):
# bash <(curl -fsSL https://git.mosaicstack.dev/mosaic/mosaic-stack/raw/branch/main/tools/install.sh) # bash <(curl -fsSL https://git.mosaicstack.dev/mosaic/mosaic-stack/raw/branch/main/tools/install.sh)
# #
# Remote install (alternative — use -s -- to pass flags): # Remote install (alternative — use -s -- to pass flags):
# curl -fsSL https://git.mosaicstack.dev/mosaic/mosaic-stack/raw/branch/main/tools/install.sh | bash -s -- # curl -fsSL https://git.mosaicstack.dev/mosaic/mosaic-stack/raw/branch/main/tools/install.sh | bash -s --
# curl -fsSL ... | bash -s -- --check
# #
# Local (from repo checkout): # Flags:
# bash tools/install.sh # --check Version check only, no install
# bash tools/install.sh --check # version check only # --framework Install/upgrade framework only (skip npm CLI)
# --cli Install/upgrade npm CLI only (skip framework)
# --ref <branch> Git ref for framework archive (default: main)
# #
# Environment: # Environment:
# MOSAIC_REGISTRY — npm registry URL (default: Gitea instance) # MOSAIC_HOME — framework install dir (default: ~/.config/mosaic)
# MOSAIC_SCOPE — npm scope (default: @mosaic) # MOSAIC_REGISTRY — npm registry URL (default: Gitea instance)
# MOSAIC_PREFIX — npm global prefix (default: ~/.npm-global) # MOSAIC_SCOPE — npm scope (default: @mosaic)
# MOSAIC_NO_COLOR — disable colour (set to 1) # 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 # Wrapped in main() for safe curl-pipe usage.
# memory before execution — required for safe curl-pipe usage.
set -euo pipefail set -euo pipefail
main() { 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 ──────────────────────────────────────────────────────────────── # ─── constants ────────────────────────────────────────────────────────────────
MOSAIC_HOME="${MOSAIC_HOME:-$HOME/.config/mosaic}"
REGISTRY="${MOSAIC_REGISTRY:-https://git.mosaicstack.dev/api/packages/mosaic/npm/}" REGISTRY="${MOSAIC_REGISTRY:-https://git.mosaicstack.dev/api/packages/mosaic/npm/}"
SCOPE="${MOSAIC_SCOPE:-@mosaic}" SCOPE="${MOSAIC_SCOPE:-@mosaic}"
PREFIX="${MOSAIC_PREFIX:-$HOME/.npm-global}" PREFIX="${MOSAIC_PREFIX:-$HOME/.npm-global}"
CLI_PKG="${SCOPE}/cli" CLI_PKG="${SCOPE}/cli"
REPO_BASE="https://git.mosaicstack.dev/mosaic/mosaic-stack"
ARCHIVE_URL="${REPO_BASE}/archive/${GIT_REF}.tar.gz"
# ─── colours ────────────────────────────────────────────────────────────────── # ─── 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 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 else
R=$'\033[0;31m' G=$'\033[0;32m' Y=$'\033[0;33m' 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 fi
info() { echo "${B}${RESET} $*"; } info() { echo "${B}${RESET} $*"; }
@@ -47,6 +71,7 @@ ok() { echo "${G}✔${RESET} $*"; }
warn() { echo "${Y}${RESET} $*"; } warn() { echo "${Y}${RESET} $*"; }
fail() { echo "${R}${RESET} $*" >&2; } fail() { echo "${R}${RESET} $*" >&2; }
dim() { echo "${DIM}$*${RESET}"; } dim() { echo "${DIM}$*${RESET}"; }
step() { echo ""; echo "${BOLD}$*${RESET}"; }
# ─── helpers ────────────────────────────────────────────────────────────────── # ─── helpers ──────────────────────────────────────────────────────────────────
@@ -58,12 +83,10 @@ require_cmd() {
fi fi
} }
# Get the installed version of @mosaic/cli (empty string if not installed) installed_cli_version() {
installed_version() {
local json local json
json="$(npm ls -g --depth=0 --json --prefix="$PREFIX" 2>/dev/null)" || true json="$(npm ls -g --depth=0 --json --prefix="$PREFIX" 2>/dev/null)" || true
if [[ -n "$json" ]]; then if [[ -n "$json" ]]; then
# Feed json via heredoc, not stdin pipe — safe in curl-pipe context
node -e " node -e "
const d = JSON.parse(process.argv[1]); const d = JSON.parse(process.argv[1]);
const v = d?.dependencies?.['${CLI_PKG}']?.version ?? ''; const v = d?.dependencies?.['${CLI_PKG}']?.version ?? '';
@@ -72,14 +95,11 @@ installed_version() {
fi fi
} }
# Get the latest published version from the registry latest_cli_version() {
latest_version() {
npm view "${CLI_PKG}" version --registry="$REGISTRY" 2>/dev/null || true npm view "${CLI_PKG}" version --registry="$REGISTRY" 2>/dev/null || true
} }
# Compare two semver strings: returns 0 (true) if $1 < $2
version_lt() { version_lt() {
# Semver-aware comparison via node (handles pre-release correctly)
node -e " node -e "
const a=process.argv[1], b=process.argv[2]; 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)]; }; 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 " "$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 ──────────────────────────────────────────────────────────────── # ─── preflight ────────────────────────────────────────────────────────────────
require_cmd node require_cmd node
@@ -104,116 +132,211 @@ if [[ "$NODE_MAJOR" -lt 20 ]]; then
exit 1 exit 1
fi 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 "" echo ""
if [[ -n "$CURRENT" ]]; then echo "${BOLD}Mosaic Stack Installer${RESET}"
dim " Installed: ${CLI_PKG}@${CURRENT}"
else
dim " Installed: (none)"
fi
dim " Latest: ${CLI_PKG}@${LATEST}"
echo "" echo ""
# --check flag: just report, don't install # ═══════════════════════════════════════════════════════════════════════════════
if [[ "${1:-}" == "--check" ]]; then # PART 1: Framework (bash launcher + guides + runtime configs + tools)
if [[ -z "$CURRENT" ]]; then # ═══════════════════════════════════════════════════════════════════════════════
warn "Not installed. Run without --check to install."
exit 1 if [[ "$FLAG_FRAMEWORK" == "true" ]]; then
elif [[ "$CURRENT" == "$LATEST" ]]; then step "Framework (~/.config/mosaic)"
ok "Up to date."
exit 0 FRAMEWORK_CURRENT="$(framework_version)"
elif version_lt "$CURRENT" "$LATEST"; then HAS_FRAMEWORK=false
warn "Update available: $CURRENT$LATEST" [[ -d "$MOSAIC_HOME/bin" ]] && [[ -f "$MOSAIC_HOME/bin/mosaic" ]] && HAS_FRAMEWORK=true
exit 2
if [[ -n "$FRAMEWORK_CURRENT" ]]; then
dim " Installed: framework v${FRAMEWORK_CURRENT}"
elif [[ "$HAS_FRAMEWORK" == "true" ]]; then
dim " Installed: framework (version unknown)"
else else
ok "Up to date (or ahead of registry)." dim " Installed: (none)"
exit 0
fi fi
fi dim " Source: ${REPO_BASE} (ref: ${GIT_REF})"
# ─── 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
echo "" echo ""
info "First install detected."
if [[ -t 0 ]] && [[ -t 1 ]]; then if [[ "$FLAG_CHECK" == "true" ]]; then
echo " Run ${C}mosaic wizard${RESET} to set up your configuration." if [[ "$HAS_FRAMEWORK" == "true" ]]; then
ok "Framework is installed."
else
warn "Framework not installed."
fi
else 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 <repo-name>/ 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
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 } # end main