diff --git a/README.md b/README.md index 37a7a21..4523026 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,21 @@ A social simulation scenario engine. Feed it documents describing any scenario, ### Setup +One-command bootstrap (installs uv if missing, creates `.env`, syncs +dependencies, checks your LLM provider CLI and runs `mirofish doctor`): + +```bash +# macOS / Linux +./scripts/setup.sh # or: --provider codex-cli, --yes + +# Windows (PowerShell) +.\scripts\setup.ps1 # or: -Provider codex-cli, -Yes +# If script execution is disabled (stock Windows default): +powershell -ExecutionPolicy Bypass -File scripts\setup.ps1 +``` + +Or manually: + ```bash cp .env.example .env # Default: claude-cli (uses your Claude Code subscription) diff --git a/scripts/setup.ps1 b/scripts/setup.ps1 new file mode 100644 index 0000000..962054c --- /dev/null +++ b/scripts/setup.ps1 @@ -0,0 +1,116 @@ +# One-command bootstrap for MiroFish on Windows (PowerShell 5.1+). +# +# Usage: +# .\scripts\setup.ps1 # sensible defaults +# .\scripts\setup.ps1 -Provider codex-cli # pick LLM provider (default: claude-cli) +# .\scripts\setup.ps1 -Yes # non-interactive (auto-install uv) +# +# If script execution is disabled on your machine (stock Windows defaults to +# an ExecutionPolicy of Restricted), run it as: +# powershell -ExecutionPolicy Bypass -File scripts\setup.ps1 +# +# What it does: +# 1. Verifies (or installs) uv +# 2. Creates .env from .env.example and sets LLM_PROVIDER +# 3. Installs dependencies with `uv sync` +# 4. Verifies the provider CLI is available and hints at login if needed +# 5. Runs `mirofish doctor` for a final health check +# +# Note: full Windows runtime support for the claude-cli provider also needs +# the fixes in PR #26 (POSIX cwd, argv length limit, cp1252 decoding). + +param( + [ValidateSet("claude-cli", "codex-cli")] + [string]$Provider = "claude-cli", + [switch]$Yes +) + +$ErrorActionPreference = "Stop" +$RepoDir = Split-Path -Parent $PSScriptRoot +Set-Location $RepoDir + +function Say($msg) { Write-Host "==> $msg" -ForegroundColor Cyan } +function Warn($msg) { Write-Host "WARN $msg" -ForegroundColor Yellow } + +# --- 1. uv ------------------------------------------------------------------ +if (Get-Command uv -ErrorAction SilentlyContinue) { + Say "uv found: $(uv --version)" +} else { + if (-not $Yes) { + if ([Console]::IsInputRedirected) { + Write-Host "uv is not installed and stdin is not interactive; re-run with -Yes to auto-install." + exit 1 + } + $ans = Read-Host "uv is not installed. Install it now from https://astral.sh/uv? [y/N]" + if ($ans -notmatch '^(y|yes)$') { Write-Host "Aborted: uv is required."; exit 1 } + } + Say "Installing uv..." + # Download to a file first: avoids executing a partial stream and iex + # scope pollution (vs irm | iex) + $uvInstaller = Join-Path $env:TEMP "uv-install-$PID.ps1" + Invoke-RestMethod https://astral.sh/uv/install.ps1 -OutFile $uvInstaller + & $uvInstaller + Remove-Item $uvInstaller -ErrorAction SilentlyContinue + $env:Path = "$env:USERPROFILE\.local\bin;$env:Path" + if (-not (Get-Command uv -ErrorAction SilentlyContinue)) { + Write-Host "uv installed but not on PATH; open a new terminal and re-run." + exit 1 + } +} + +# --- 2. .env ---------------------------------------------------------------- +if (-not (Test-Path ".env")) { + Copy-Item ".env.example" ".env" + Say "Created .env from .env.example" +} +# Symmetric UTF-8 read (Get-Content on PS 5.1 decodes BOM-less files as ANSI, +# which would corrupt non-ASCII values on the read-modify-write cycle) +$envText = [IO.File]::ReadAllText((Join-Path $RepoDir ".env"), (New-Object System.Text.UTF8Encoding($false))) +if ($envText -match "(?m)^LLM_PROVIDER=") { + $envText = $envText -replace "(?m)^LLM_PROVIDER=.*$", "LLM_PROVIDER=$Provider" +} else { + $envText = $envText.TrimEnd() + "`nLLM_PROVIDER=$Provider`n" +} +[IO.File]::WriteAllText((Join-Path $RepoDir ".env"), $envText, (New-Object System.Text.UTF8Encoding($false))) +Say "LLM_PROVIDER=$Provider" + +# --- 3. UTF-8 (Windows defaults to a legacy code page like cp1252) ---------- +$env:PYTHONUTF8 = "1" +$env:PYTHONIOENCODING = "utf-8" +Say "PYTHONUTF8=1 set for this session (add it to your user environment variables for permanence)" + +# --- 4. dependencies -------------------------------------------------------- +Say "Installing dependencies (uv sync)..." +uv sync +if ($LASTEXITCODE -ne 0) { + Write-Host "uv sync failed." + Warn "On Windows with Python 3.12 this is typically rapidfuzz >=3.14 shipping no cp312 wheels (fix proposed in PR #26)." + Warn "Workaround until that lands: 'uv python pin 3.11' and re-run this script." + exit 1 +} + +# --- 5. provider CLI -------------------------------------------------------- +if ($Provider -eq "claude-cli") { + if (Get-Command claude -ErrorAction SilentlyContinue) { + $claudeVer = "" + try { $claudeVer = (claude --version 2>$null | Select-Object -First 1) } catch {} + if ($claudeVer) { Say "claude CLI found: $claudeVer" } else { Say "claude CLI found." } + Warn "If you have never logged in, run 'claude' once and use /login (headless '-p' calls fail otherwise)." + } else { + Warn "claude CLI not found on PATH. Install Claude Code (https://claude.com/claude-code) and log in before running simulations." + } +} else { + if (Get-Command codex -ErrorAction SilentlyContinue) { + Say "codex CLI found." + } else { + Warn "codex CLI not found on PATH. Install and authenticate it before running simulations." + } +} + +# --- 6. health check -------------------------------------------------------- +Say "Running mirofish doctor..." +uv run mirofish doctor +if ($LASTEXITCODE -ne 0) { Warn "doctor reported issues (see above) - fix them before your first run." } + +Say "Done. Try:" +Write-Host ' uv run mirofish run --files --requirement "Predict public reaction over 30 days" --platform parallel --max-rounds 15' diff --git a/scripts/setup.sh b/scripts/setup.sh new file mode 100755 index 0000000..36a8f6f --- /dev/null +++ b/scripts/setup.sh @@ -0,0 +1,127 @@ +#!/usr/bin/env bash +# One-command bootstrap for MiroFish on macOS / Linux. +# +# Usage: +# ./scripts/setup.sh # interactive-ish, sensible defaults +# ./scripts/setup.sh --provider codex-cli # pick LLM provider (default: claude-cli) +# ./scripts/setup.sh --yes # non-interactive (auto-install uv) +# +# What it does: +# 1. Verifies (or installs) uv +# 2. Creates .env from .env.example and sets LLM_PROVIDER +# 3. Installs dependencies with `uv sync` +# 4. Verifies the provider CLI is available and hints at login if needed +# 5. Runs `mirofish doctor` for a final health check + +set -euo pipefail + +usage() { + cat <<'USAGE' +One-command bootstrap for MiroFish on macOS / Linux. + +Usage: + ./scripts/setup.sh # sensible defaults + ./scripts/setup.sh --provider codex-cli # pick LLM provider (default: claude-cli) + ./scripts/setup.sh --yes # non-interactive (auto-install uv) + +Steps: verify/install uv -> create .env -> uv sync -> check provider CLI + -> run `mirofish doctor`. +USAGE +} + +PROVIDER="claude-cli" +ASSUME_YES=0 + +while [ $# -gt 0 ]; do + case "$1" in + --provider) PROVIDER="${2:?--provider requires a value}"; shift 2 ;; + --yes|-y) ASSUME_YES=1; shift ;; + -h|--help) usage; exit 0 ;; + *) echo "Unknown option: $1 (see --help)"; exit 2 ;; + esac +done + +case "$PROVIDER" in + claude-cli|codex-cli) ;; + *) echo "Unsupported provider '$PROVIDER' (expected: claude-cli | codex-cli)"; exit 2 ;; +esac + +REPO_DIR="$(cd "$(dirname "$0")/.." && pwd)" +cd "$REPO_DIR" + +# Colors only on a real terminal, and never when NO_COLOR is set. +if [ -t 1 ] && [ -z "${NO_COLOR:-}" ]; then + C_SAY='\033[1;36m'; C_WARN='\033[1;33m'; C_OFF='\033[0m' +else + C_SAY=''; C_WARN=''; C_OFF='' +fi +say() { printf '%b==>%b %s\n' "$C_SAY" "$C_OFF" "$*"; } +warn() { printf '%bWARN%b %s\n' "$C_WARN" "$C_OFF" "$*"; } + +# --- 1. uv ------------------------------------------------------------------ +if command -v uv >/dev/null 2>&1; then + say "uv found: $(uv --version)" +else + if [ "$ASSUME_YES" -ne 1 ]; then + if [ ! -t 0 ]; then + echo "uv is not installed and stdin is not a terminal; re-run with --yes to auto-install." + exit 1 + fi + printf 'uv is not installed. Install it now from https://astral.sh/uv? [y/N] ' + read -r ans + case "$ans" in y|Y|yes|YES) ;; *) echo "Aborted: uv is required."; exit 1 ;; esac + fi + say "Installing uv..." + # Download to a file first: avoids executing a partially-received stream + # and leaves an inspectable artifact (vs curl | sh) + uv_installer="$(mktemp)" + trap 'rm -f "$uv_installer"' EXIT + curl -LsSf https://astral.sh/uv/install.sh -o "$uv_installer" + sh "$uv_installer" + # The installer drops uv into ~/.local/bin (or ~/.cargo/bin on older setups) + export PATH="$HOME/.local/bin:$HOME/.cargo/bin:$PATH" + command -v uv >/dev/null 2>&1 || { echo "uv installed but not on PATH; open a new shell and re-run."; exit 1; } +fi + +# --- 2. .env ---------------------------------------------------------------- +if [ ! -f .env ]; then + cp .env.example .env + say "Created .env from .env.example" +fi +if grep -q '^LLM_PROVIDER=' .env; then + # portable in-place edit (BSD sed on macOS needs the '' argument) + sed -i.bak "s/^LLM_PROVIDER=.*/LLM_PROVIDER=${PROVIDER}/" .env && rm -f .env.bak +else + printf '\nLLM_PROVIDER=%s\n' "$PROVIDER" >> .env +fi +say "LLM_PROVIDER=${PROVIDER}" + +# --- 3. dependencies -------------------------------------------------------- +say "Installing dependencies (uv sync)..." +uv sync + +# --- 4. provider CLI -------------------------------------------------------- +case "$PROVIDER" in + claude-cli) + if command -v claude >/dev/null 2>&1; then + say "claude CLI found: $(claude --version 2>/dev/null || echo 'version unknown')" + warn "If you have never logged in, run 'claude' once and use /login (headless '-p' calls fail otherwise)." + else + warn "claude CLI not found on PATH. Install Claude Code (https://claude.com/claude-code) and log in before running simulations." + fi + ;; + codex-cli) + if command -v codex >/dev/null 2>&1; then + say "codex CLI found." + else + warn "codex CLI not found on PATH. Install and authenticate it before running simulations." + fi + ;; +esac + +# --- 5. health check -------------------------------------------------------- +say "Running mirofish doctor..." +uv run mirofish doctor || warn "doctor reported issues (see above) — fix them before your first run." + +say "Done. Try:" +printf ' uv run mirofish run --files --requirement "Predict public reaction over 30 days" --platform parallel --max-rounds 15\n'