feat: unify install.sh — single installer for framework + npm CLI

- 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 2f68237046
commit 8a83aed9b1
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: 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 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_HOME — framework install dir (default: ~/.config/mosaic)
# MOSAIC_REGISTRY — npm registry URL (default: Gitea instance) # MOSAIC_REGISTRY — npm registry URL (default: Gitea instance)
# MOSAIC_SCOPE — npm scope (default: @mosaic) # MOSAIC_SCOPE — npm scope (default: @mosaic)
# MOSAIC_PREFIX — npm global prefix (default: ~/.npm-global) # MOSAIC_PREFIX — npm global prefix (default: ~/.npm-global)
# MOSAIC_NO_COLOR — disable colour (set to 1) # 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,15 +132,100 @@ if [[ "$NODE_MAJOR" -lt 20 ]]; then
exit 1 exit 1
fi fi
# ─── ensure prefix directory exists ────────────────────────────────────────── echo ""
echo "${BOLD}Mosaic Stack Installer${RESET}"
echo ""
# ═══════════════════════════════════════════════════════════════════════════════
# 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
dim " Installed: (none)"
fi
dim " Source: ${REPO_BASE} (ref: ${GIT_REF})"
echo ""
if [[ "$FLAG_CHECK" == "true" ]]; then
if [[ "$HAS_FRAMEWORK" == "true" ]]; then
ok "Framework is installed."
else
warn "Framework not installed."
fi
else
# 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
# ═══════════════════════════════════════════════════════════════════════════════
# 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 if [[ ! -d "$PREFIX" ]]; then
info "Creating global prefix directory: $PREFIX" info "Creating global prefix directory: $PREFIX"
mkdir -p "$PREFIX"/{bin,lib} mkdir -p "$PREFIX"/{bin,lib}
fi fi
# ─── ensure npmrc scope mapping ────────────────────────────────────────────── # Ensure npmrc scope mapping
NPMRC="$HOME/.npmrc" NPMRC="$HOME/.npmrc"
SCOPE_LINE="${SCOPE}:registry=${REGISTRY}" SCOPE_LINE="${SCOPE}:registry=${REGISTRY}"
@@ -122,7 +235,6 @@ if ! grep -qF "$SCOPE_LINE" "$NPMRC" 2>/dev/null; then
ok "Registry configured" ok "Registry configured"
fi fi
# Ensure prefix is set (only if no prefix= line exists yet)
if ! grep -qF "prefix=$PREFIX" "$NPMRC" 2>/dev/null; then if ! grep -qF "prefix=$PREFIX" "$NPMRC" 2>/dev/null; then
if ! grep -q '^prefix=' "$NPMRC" 2>/dev/null; then if ! grep -q '^prefix=' "$NPMRC" 2>/dev/null; then
echo "prefix=$PREFIX" >> "$NPMRC" echo "prefix=$PREFIX" >> "$NPMRC"
@@ -130,90 +242,101 @@ if ! grep -qF "prefix=$PREFIX" "$NPMRC" 2>/dev/null; then
fi fi
fi fi
# Ensure PREFIX/bin is on PATH CURRENT="$(installed_cli_version)"
if [[ ":$PATH:" != *":$PREFIX/bin:"* ]]; then LATEST="$(latest_cli_version)"
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 if [[ -n "$CURRENT" ]]; then
dim " Installed: ${CLI_PKG}@${CURRENT}" dim " Installed: ${CLI_PKG}@${CURRENT}"
else else
dim " Installed: (none)" dim " Installed: (none)"
fi fi
if [[ -n "$LATEST" ]]; then
dim " Latest: ${CLI_PKG}@${LATEST}" dim " Latest: ${CLI_PKG}@${LATEST}"
else
dim " Latest: (registry unreachable)"
fi
echo "" echo ""
# --check flag: just report, don't install if [[ "$FLAG_CHECK" == "true" ]]; then
if [[ "${1:-}" == "--check" ]]; then if [[ -z "$LATEST" ]]; then
if [[ -z "$CURRENT" ]]; then warn "Could not reach registry."
warn "Not installed. Run without --check to install." elif [[ -z "$CURRENT" ]]; then
exit 1 warn "Not installed."
elif [[ "$CURRENT" == "$LATEST" ]]; then elif [[ "$CURRENT" == "$LATEST" ]]; then
ok "Up to date." ok "Up to date."
exit 0
elif version_lt "$CURRENT" "$LATEST"; then elif version_lt "$CURRENT" "$LATEST"; then
warn "Update available: $CURRENT$LATEST" warn "Update available: $CURRENT$LATEST"
exit 2
else else
ok "Up to date (or ahead of registry)." ok "Up to date (or ahead of registry)."
exit 0
fi fi
fi else
if [[ -z "$LATEST" ]]; then
# ─── install / upgrade ────────────────────────────────────────────────────── warn "Could not reach registry at $REGISTRY — skipping npm CLI."
elif [[ -z "$CURRENT" ]]; then
if [[ -z "$CURRENT" ]]; then
info "Installing ${CLI_PKG}@${LATEST}" 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 elif [[ "$CURRENT" == "$LATEST" ]]; then
ok "Already at latest version ($LATEST). Nothing to do." ok "Already at latest version ($LATEST)."
exit 0
elif version_lt "$CURRENT" "$LATEST"; then elif version_lt "$CURRENT" "$LATEST"; then
info "Upgrading ${CLI_PKG}: $CURRENT$LATEST" 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 else
ok "Installed version ($CURRENT) is at or ahead of registry ($LATEST)." ok "CLI is at or ahead of registry ($CURRENT$LATEST)."
exit 0
fi fi
# NOTE: Do NOT pass --registry here. The @mosaic scope is already mapped # PATH check for npm prefix
# in ~/.npmrc. Passing --registry globally would redirect ALL deps (including if [[ ":$PATH:" != *":$PREFIX/bin:"* ]]; then
# @clack/prompts, commander, etc.) to the Gitea registry, causing 404s. warn "$PREFIX/bin is not on your PATH"
npm install -g "${CLI_PKG}@${LATEST}" \ dim " The 'mosaic' TUI/gateway CLI lives here (separate from the launcher)."
--prefix="$PREFIX" \ dim " Add to your shell rc: export PATH=\"$PREFIX/bin:\$PATH\""
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 ""
info "First install detected."
if [[ -t 0 ]] && [[ -t 1 ]]; then
echo " Run ${C}mosaic wizard${RESET} to set up your configuration."
else
dim " Run 'mosaic wizard' to set up your configuration."
fi fi
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 "" echo ""
ok "Done." ok "Done."
fi
} # end main } # end main