mirror of
https://github.com/JuliusBrussee/caveman.git
synced 2026-08-11 13:21:09 +02:00
caveman-shrink@0.1.0 is now live on npm (pre-1.0). Restore the original default-on behavior for --with-mcp-shrink / -WithMcpShrink. Keep the `npm view caveman-shrink` probe — a transient registry outage now degrades to a clean manual-config skip instead of registering a `npx -y caveman-shrink` entry that would have spawned-failed. Also: sync `model: haiku` onto cavecrew investigator/reviewer canonicals so the top-level agents/ matches the synced plugins/caveman/agents/. README + CLAUDE.md flipped back to "On by default." Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
635 lines
26 KiB
PowerShell
635 lines
26 KiB
PowerShell
# caveman — smart multi-agent installer (Windows / PowerShell).
|
|
#
|
|
# One line:
|
|
# irm https://raw.githubusercontent.com/JuliusBrussee/caveman/main/install.ps1 | iex
|
|
#
|
|
# Detects which AI coding agents are on your machine and installs caveman for
|
|
# each one using its native distribution (plugin / extension / skill / rule
|
|
# file). Skips agents that aren't installed. Safe to re-run — each underlying
|
|
# install command is idempotent.
|
|
#
|
|
# Run `install.ps1 -Help` for the full reference (flags + agent matrix).
|
|
#
|
|
# Defaults: -WithHooks ON, -WithMcpShrink ON (when Claude Code is detected),
|
|
# -WithInit OFF. Use -Minimal to skip everything except the plugin/extension
|
|
# install. Use -All to also drop per-repo rule files into $PWD.
|
|
|
|
[CmdletBinding()]
|
|
param(
|
|
[switch]$DryRun,
|
|
[switch]$Force,
|
|
[switch]$SkipSkills,
|
|
[switch]$WithHooks,
|
|
[switch]$NoHooks,
|
|
[switch]$WithInit,
|
|
[switch]$WithMcpShrink,
|
|
[switch]$NoMcpShrink,
|
|
[switch]$All,
|
|
[switch]$Minimal,
|
|
[switch]$List,
|
|
[switch]$NoColor,
|
|
[switch]$Help,
|
|
[string[]]$Only = @()
|
|
)
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
$Repo = "JuliusBrussee/caveman"
|
|
$RawBase = "https://raw.githubusercontent.com/$Repo/main"
|
|
$HooksInstallUrl = "$RawBase/hooks/install.ps1"
|
|
$InitScriptUrl = "$RawBase/tools/caveman-init.js"
|
|
$McpShrinkPkg = "caveman-shrink"
|
|
|
|
# ── Help ────────────────────────────────────────────────────────────────────
|
|
if ($Help) {
|
|
@"
|
|
caveman installer (Windows) — detects your agents and installs caveman for each.
|
|
|
|
USAGE
|
|
install.ps1 [-DryRun] [-Force] [-Only <agent>[,<agent>]] [-All] [-Minimal]
|
|
[-WithHooks] [-NoHooks] [-WithInit] [-WithMcpShrink] [-NoMcpShrink]
|
|
[-SkipSkills] [-List] [-NoColor]
|
|
|
|
irm $RawBase/install.ps1 | iex
|
|
|
|
FLAGS
|
|
-DryRun Print what would run, do nothing.
|
|
-Force Re-run even if a target reports "already installed".
|
|
-Only <list> Comma-separated agent ids. Repeatable / array.
|
|
-All Turn on -WithHooks, -WithInit, -WithMcpShrink together.
|
|
-Minimal Skip hooks, MCP shrink, per-repo init. Plugin/extension only.
|
|
-WithHooks Claude Code: also wire SessionStart/UserPromptSubmit hooks
|
|
+ statusline + stats badge. ON by default.
|
|
-NoHooks Opt out of the default-on hooks install.
|
|
-WithMcpShrink Claude Code: register caveman-shrink MCP proxy. ON by default.
|
|
-NoMcpShrink Opt out of the default-on MCP shrink registration.
|
|
-WithInit Drop per-repo rule files into `$PWD for Cursor / Windsurf /
|
|
Cline / Copilot / AGENTS.md. OFF by default.
|
|
-SkipSkills Don't run the npx-skills auto-detect fallback.
|
|
-List Print the full provider matrix and exit.
|
|
-NoColor Disable ANSI color codes.
|
|
|
|
EXAMPLES
|
|
install.ps1 # default: plugin + hooks + MCP shrink
|
|
install.ps1 -All # also drop per-repo rule files
|
|
install.ps1 -Minimal # plugin/extension only
|
|
install.ps1 -DryRun -All
|
|
install.ps1 -Only claude -WithMcpShrink
|
|
install.ps1 -Only cursor,windsurf -WithInit
|
|
install.ps1 -List
|
|
|
|
URLS THE INSTALLER MAY FETCH FROM
|
|
$RawBase/install.ps1
|
|
$RawBase/hooks/install.ps1
|
|
$RawBase/tools/caveman-init.js
|
|
"@ | Write-Host
|
|
exit 0
|
|
}
|
|
|
|
# ── Resolve -All / -Minimal / default-auto switches ────────────────────────
|
|
if ($All -and $Minimal) {
|
|
Write-Error "-All and -Minimal are mutually exclusive."
|
|
exit 2
|
|
}
|
|
if ($All) {
|
|
$WithHooks = $true
|
|
$WithInit = $true
|
|
$WithMcpShrink = $true
|
|
}
|
|
# Default-auto: turn ON unless caller passed -Minimal or the explicit -No*
|
|
# opt-out switch.
|
|
if (-not $WithHooks -and -not $NoHooks -and -not $Minimal) {
|
|
$WithHooks = $true
|
|
}
|
|
if (-not $WithMcpShrink -and -not $NoMcpShrink -and -not $Minimal) {
|
|
$WithMcpShrink = $true
|
|
}
|
|
if ($Minimal) {
|
|
$WithHooks = $false
|
|
$WithMcpShrink = $false
|
|
$WithInit = $false
|
|
}
|
|
|
|
# ── Color helpers ──────────────────────────────────────────────────────────
|
|
$Esc = [char]27
|
|
function Say($msg) {
|
|
if ($NoColor) { Write-Host $msg }
|
|
else { Write-Host "$Esc[38;5;172m$msg$Esc[0m" }
|
|
}
|
|
function Note($msg) {
|
|
if ($NoColor) { Write-Host $msg }
|
|
else { Write-Host "$Esc[2m$msg$Esc[0m" }
|
|
}
|
|
function Warn($msg) {
|
|
if ($NoColor) { Write-Host $msg }
|
|
else { Write-Host "$Esc[31m$msg$Esc[0m" }
|
|
}
|
|
function Ok($msg) {
|
|
if ($NoColor) { Write-Host $msg }
|
|
else { Write-Host "$Esc[32m$msg$Esc[0m" }
|
|
}
|
|
|
|
# ── State ───────────────────────────────────────────────────────────────────
|
|
$OnlyList = @()
|
|
foreach ($o in $Only) {
|
|
foreach ($x in ($o -split ',')) {
|
|
$t = $x.Trim()
|
|
if ($t) {
|
|
# Backward-compat alias (matches install.sh).
|
|
if ($t -eq "aider") { $t = "aider-desk" }
|
|
$OnlyList += $t
|
|
}
|
|
}
|
|
}
|
|
|
|
$InstalledIds = @()
|
|
$SkippedIds = @()
|
|
$SkippedWhy = @()
|
|
$FailedIds = @()
|
|
$FailedWhy = @()
|
|
$DetectedCount = 0
|
|
|
|
function Want([string]$id) {
|
|
if ($OnlyList.Count -eq 0) { return $true }
|
|
return $OnlyList -contains $id
|
|
}
|
|
|
|
function Has-Cmd([string]$c) {
|
|
return [bool](Get-Command $c -ErrorAction SilentlyContinue)
|
|
}
|
|
|
|
# Detect repo root if running from a clone (vs irm | iex from raw.github).
|
|
function Get-RepoRoot {
|
|
$src = $PSCommandPath
|
|
if ($src -and (Test-Path $src)) {
|
|
$d = Split-Path -Parent $src
|
|
if ((Test-Path (Join-Path $d "install.ps1")) -and
|
|
(Test-Path (Join-Path $d "hooks")) -and
|
|
(Test-Path (Join-Path $d "tools"))) {
|
|
return $d
|
|
}
|
|
}
|
|
return $null
|
|
}
|
|
$RepoRoot = Get-RepoRoot
|
|
|
|
# ── Run helpers ─────────────────────────────────────────────────────────────
|
|
# Run a process, return $true if exit 0. Honors -DryRun. Errors do not throw.
|
|
# `$Args` is an automatic in PowerShell — name the param `$Argv` to avoid the
|
|
# implicit-collision warning under strict analysis.
|
|
function Try-Run {
|
|
param([string]$Exe, [string[]]$Argv)
|
|
if ($DryRun) {
|
|
Note " would run: $Exe $($Argv -join ' ')"
|
|
return $true
|
|
}
|
|
Write-Host " $ $Exe $($Argv -join ' ')"
|
|
try {
|
|
& $Exe @Argv
|
|
return ($LASTEXITCODE -eq 0)
|
|
} catch {
|
|
Write-Host " $($_.Exception.Message)" -ForegroundColor Red
|
|
return $false
|
|
}
|
|
}
|
|
|
|
function Record-Installed([string]$id) { $script:InstalledIds += $id }
|
|
function Record-Skipped([string]$id, [string]$why) {
|
|
$script:SkippedIds += $id
|
|
$script:SkippedWhy += $why
|
|
}
|
|
function Record-Failed([string]$id, [string]$why) {
|
|
$script:FailedIds += $id
|
|
$script:FailedWhy += $why
|
|
}
|
|
|
|
function Ensure-Node {
|
|
if ((Has-Cmd "node") -and (Has-Cmd "npx")) { return $true }
|
|
Warn " node + npx required for this target — install Node.js (https://nodejs.org) and re-run."
|
|
return $false
|
|
}
|
|
|
|
# ── Detection helpers ───────────────────────────────────────────────────────
|
|
$VsCodeExtRoots = @(
|
|
(Join-Path $HOME ".vscode\extensions"),
|
|
(Join-Path $HOME ".vscode-server\extensions"),
|
|
(Join-Path $HOME ".cursor\extensions"),
|
|
(Join-Path $HOME ".windsurf\extensions")
|
|
)
|
|
|
|
function Test-VscodeExt([string]$needle) {
|
|
foreach ($r in $VsCodeExtRoots) {
|
|
if (Test-Path $r) {
|
|
$found = Get-ChildItem -Path $r -ErrorAction SilentlyContinue |
|
|
Where-Object { $_.Name -match [regex]::Escape($needle) }
|
|
if ($found) { return $true }
|
|
}
|
|
}
|
|
return $false
|
|
}
|
|
|
|
function Test-CursorExt([string]$needle) {
|
|
$r = Join-Path $HOME ".cursor\extensions"
|
|
if (Test-Path $r) {
|
|
$found = Get-ChildItem -Path $r -ErrorAction SilentlyContinue |
|
|
Where-Object { $_.Name -match [regex]::Escape($needle) }
|
|
if ($found) { return $true }
|
|
}
|
|
return $false
|
|
}
|
|
|
|
# JetBrains config roots: Windows uses %APPDATA%\JetBrains, but cover the WSL
|
|
# bridge (~/.config/JetBrains) and macOS-on-PowerShell-Core path too so users
|
|
# running pwsh on different OSes get the same matrix.
|
|
$JetbrainsRoots = @(
|
|
(Join-Path $env:APPDATA "JetBrains"),
|
|
(Join-Path $HOME ".config\JetBrains"),
|
|
(Join-Path $HOME "Library/Application Support/JetBrains")
|
|
)
|
|
|
|
function Test-JetbrainsConfig {
|
|
foreach ($r in $JetbrainsRoots) {
|
|
if ($r -and (Test-Path $r)) { return $true }
|
|
}
|
|
return $false
|
|
}
|
|
|
|
function Test-JetbrainsPlugin([string]$needle) {
|
|
foreach ($r in $JetbrainsRoots) {
|
|
if ($r -and (Test-Path $r)) {
|
|
$found = Get-ChildItem -Path $r -Recurse -Directory -Depth 4 -ErrorAction SilentlyContinue |
|
|
Where-Object { $_.Name -match [regex]::Escape($needle) }
|
|
if ($found) { return $true }
|
|
}
|
|
}
|
|
return $false
|
|
}
|
|
|
|
# Resolve a detect spec like "command:foo||dir:~/.bar||vscode-ext:baz".
|
|
# Spec strings use $HOME / $env:HOME tokens that we expanded at build time —
|
|
# they're already absolute by the time they reach this function.
|
|
function Resolve-DetectSpec([string]$spec) {
|
|
if ([string]::IsNullOrWhiteSpace($spec)) { return $false }
|
|
foreach ($clause in ($spec -split '\|\|')) {
|
|
$c = $clause.Trim()
|
|
if (-not $c) { continue }
|
|
if ($c -match '^command:(.+)$') { if (Has-Cmd $matches[1]) { return $true } }
|
|
elseif ($c -match '^dir:(.+)$') { if (Test-Path $matches[1] -PathType Container) { return $true } }
|
|
elseif ($c -match '^file:(.+)$') { if (Test-Path $matches[1] -PathType Leaf) { return $true } }
|
|
elseif ($c -match '^vscode-ext:(.+)$') { if (Test-VscodeExt $matches[1]) { return $true } }
|
|
elseif ($c -match '^cursor-ext:(.+)$') { if (Test-CursorExt $matches[1]) { return $true } }
|
|
elseif ($c -eq 'jetbrains-config') { if (Test-JetbrainsConfig) { return $true } }
|
|
elseif ($c -match '^jetbrains-plugin:(.+)$') { if (Test-JetbrainsPlugin $matches[1]) { return $true } }
|
|
}
|
|
return $false
|
|
}
|
|
|
|
# ── Provider matrix (mirror of install.sh PROVIDER_*) ──────────────────────
|
|
# Keep this aligned with install.sh row-for-row. Columns:
|
|
# id, label, profile (npx-skills slug or empty for non-skills), detect,
|
|
# soft (1 = config-dir-only probe, no CLI on PATH).
|
|
$Providers = @(
|
|
@{ id='claude'; label='Claude Code'; profile=''; detect='command:claude'; soft=0 },
|
|
@{ id='gemini'; label='Gemini CLI'; profile=''; detect='command:gemini'; soft=0 },
|
|
@{ id='codex'; label='Codex CLI'; profile='codex'; detect='command:codex'; soft=0 },
|
|
@{ id='cursor'; label='Cursor'; profile='cursor'; detect="command:cursor||dir:$HOME\.cursor"; soft=0 },
|
|
@{ id='windsurf'; label='Windsurf'; profile='windsurf'; detect="command:windsurf||dir:$HOME\.codeium\windsurf||dir:$HOME\.windsurf"; soft=0 },
|
|
@{ id='cline'; label='Cline'; profile='cline'; detect='vscode-ext:cline'; soft=0 },
|
|
@{ id='copilot'; label='GitHub Copilot'; profile='github-copilot'; detect='command:gh'; soft=0 },
|
|
@{ id='continue'; label='Continue'; profile='continue'; detect='vscode-ext:continue.continue||vscode-ext:continue'; soft=0 },
|
|
@{ id='kilo'; label='Kilo Code'; profile='kilo'; detect="vscode-ext:kilocode||dir:$HOME\.kilocode"; soft=0 },
|
|
@{ id='roo'; label='Roo Code'; profile='roo'; detect='vscode-ext:roo||vscode-ext:rooveterinaryinc.roo-cline||cursor-ext:roo'; soft=0 },
|
|
@{ id='augment'; label='Augment Code'; profile='augment'; detect='vscode-ext:augment||jetbrains-plugin:augment'; soft=0 },
|
|
@{ id='aider-desk'; label='Aider Desk'; profile='aider-desk'; detect="command:aider||dir:$HOME\.aider-desk"; soft=0 },
|
|
@{ id='amp'; label='Sourcegraph Amp'; profile='amp'; detect='command:amp'; soft=0 },
|
|
@{ id='bob'; label='IBM Bob'; profile='bob'; detect="command:bob||dir:$HOME\.bob"; soft=0 },
|
|
@{ id='crush'; label='Crush'; profile='crush'; detect="command:crush||dir:$HOME\.config\crush"; soft=0 },
|
|
@{ id='devin'; label='Devin (terminal)'; profile='devin'; detect="command:devin||dir:$HOME\.config\devin"; soft=0 },
|
|
@{ id='droid'; label='Droid (Factory)'; profile='droid'; detect="command:droid||dir:$HOME\.factory"; soft=0 },
|
|
@{ id='forgecode'; label='ForgeCode'; profile='forgecode'; detect="command:forge||dir:$HOME\.forge"; soft=0 },
|
|
@{ id='goose'; label='Block Goose'; profile='goose'; detect="command:goose||dir:$HOME\.config\goose"; soft=0 },
|
|
@{ id='iflow'; label='iFlow CLI'; profile='iflow-cli'; detect="command:iflow||dir:$HOME\.iflow"; soft=0 },
|
|
@{ id='junie'; label='JetBrains Junie'; profile='junie'; detect="dir:$HOME\.junie||jetbrains-plugin:junie"; soft=1 },
|
|
@{ id='kiro'; label='Kiro CLI'; profile='kiro-cli'; detect="command:kiro||dir:$HOME\.kiro"; soft=0 },
|
|
@{ id='mistral'; label='Mistral Vibe'; profile='mistral-vibe'; detect="command:mistral||dir:$HOME\.vibe"; soft=0 },
|
|
@{ id='openhands'; label='OpenHands'; profile='openhands'; detect="command:openhands||dir:$HOME\.openhands"; soft=0 },
|
|
@{ id='opencode'; label='opencode'; profile='opencode'; detect="command:opencode||file:$HOME\.config\opencode\AGENTS.md"; soft=0 },
|
|
@{ id='qwen'; label='Qwen Code'; profile='qwen-code'; detect="command:qwen||dir:$HOME\.qwen"; soft=0 },
|
|
@{ id='qoder'; label='Qoder'; profile='qoder'; detect="dir:$HOME\.qoder"; soft=1 },
|
|
@{ id='rovodev'; label='Atlassian Rovo Dev'; profile='rovodev'; detect="command:rovodev||dir:$HOME\.rovodev"; soft=0 },
|
|
@{ id='tabnine'; label='Tabnine CLI'; profile='tabnine-cli'; detect="command:tabnine||dir:$HOME\.tabnine"; soft=0 },
|
|
@{ id='trae'; label='Trae'; profile='trae'; detect="command:trae||dir:$HOME\.trae"; soft=0 },
|
|
@{ id='warp'; label='Warp'; profile='warp'; detect="command:warp||dir:$HOME\.warp"; soft=0 },
|
|
@{ id='replit'; label='Replit Agent'; profile='replit'; detect="command:replit||dir:$HOME\.replit"; soft=0 },
|
|
@{ id='antigravity'; label='Google Antigravity'; profile='antigravity'; detect="dir:$HOME\.gemini\antigravity"; soft=1 }
|
|
)
|
|
|
|
# ── -List output ────────────────────────────────────────────────────────────
|
|
if ($List) {
|
|
Say "🪨 caveman provider matrix"
|
|
Write-Host ""
|
|
Write-Host (" {0,-13} {1,-22} {2}" -f "ID", "AGENT", "INSTALL MECHANISM")
|
|
Write-Host (" {0,-13} {1,-22} {2}" -f "----", "-----", "-----------------")
|
|
foreach ($p in $Providers) {
|
|
if ([string]::IsNullOrEmpty($p.profile)) {
|
|
$mech = if ($p.id -eq 'claude') { 'claude plugin install' }
|
|
elseif ($p.id -eq 'gemini') { 'gemini extensions install' }
|
|
else { '' }
|
|
} else {
|
|
$mech = "npx skills add ($($p.profile))"
|
|
}
|
|
if ($p.soft -eq 1) { $mech += ' (soft)' }
|
|
Write-Host (" {0,-13} {1,-22} {2}" -f $p.id, $p.label, $mech)
|
|
}
|
|
Write-Host ""
|
|
Note " Detection probes per agent live in install.ps1 \$Providers."
|
|
Note " Soft entries detect via config-dir presence only (no CLI on PATH)."
|
|
Write-Host ""
|
|
Note " Defaults: -WithHooks ON, -WithMcpShrink ON, -WithInit OFF."
|
|
Note " -All turns all three on, -Minimal turns all three off."
|
|
exit 0
|
|
}
|
|
|
|
# ── Banner ──────────────────────────────────────────────────────────────────
|
|
Say "🪨 caveman installer"
|
|
Note " $Repo"
|
|
if ($DryRun) { Note " (dry run — nothing will be written)" }
|
|
Write-Host ""
|
|
|
|
# ── Per-agent install functions ─────────────────────────────────────────────
|
|
function Install-Claude {
|
|
$script:DetectedCount++
|
|
Say "→ Claude Code detected"
|
|
$pluginDone = $false
|
|
|
|
$alreadyInstalled = $false
|
|
if (-not $Force) {
|
|
try {
|
|
$list = & claude plugin list 2>$null
|
|
if ($list -match "(?i)caveman") { $alreadyInstalled = $true }
|
|
} catch {}
|
|
}
|
|
if ($alreadyInstalled) {
|
|
Note " caveman plugin already installed (use -Force to reinstall)"
|
|
Record-Skipped "claude" "plugin already installed"
|
|
$pluginDone = $true
|
|
} else {
|
|
if ((Try-Run "claude" @("plugin", "marketplace", "add", $Repo)) -and
|
|
(Try-Run "claude" @("plugin", "install", "caveman@caveman"))) {
|
|
Record-Installed "claude"
|
|
$pluginDone = $true
|
|
} else {
|
|
Record-Failed "claude" "claude plugin install failed"
|
|
}
|
|
}
|
|
|
|
# -WithHooks: also run the standalone hooks installer (PowerShell variant).
|
|
if ($WithHooks) {
|
|
Say " → installing standalone hooks (-WithHooks)"
|
|
$hooksArgs = @()
|
|
if ($Force) { $hooksArgs += "-Force" }
|
|
|
|
$localPs1 = $null
|
|
if ($RepoRoot) {
|
|
$candidate = Join-Path $RepoRoot "hooks\install.ps1"
|
|
if (Test-Path $candidate) { $localPs1 = $candidate }
|
|
}
|
|
|
|
if ($DryRun) {
|
|
if ($localPs1) {
|
|
Note " would run: powershell -ExecutionPolicy Bypass -File $localPs1 $($hooksArgs -join ' ')"
|
|
} else {
|
|
Note " would run: irm $HooksInstallUrl | iex (with -Force=$Force)"
|
|
}
|
|
} else {
|
|
try {
|
|
if ($localPs1) {
|
|
& powershell -ExecutionPolicy Bypass -File $localPs1 @hooksArgs
|
|
if ($LASTEXITCODE -eq 0) { Record-Installed "claude-hooks" }
|
|
else { Record-Failed "claude-hooks" "hooks/install.ps1 exit $LASTEXITCODE" }
|
|
} else {
|
|
# Save to temp + run with -File so -Force works (irm | iex can't pass args).
|
|
$tmp = Join-Path $env:TEMP "caveman-hooks-install-$([Guid]::NewGuid()).ps1"
|
|
Invoke-WebRequest -Uri $HooksInstallUrl -OutFile $tmp -UseBasicParsing
|
|
try {
|
|
& powershell -ExecutionPolicy Bypass -File $tmp @hooksArgs
|
|
if ($LASTEXITCODE -eq 0) { Record-Installed "claude-hooks" }
|
|
else { Record-Failed "claude-hooks" "remote hooks installer exit $LASTEXITCODE" }
|
|
} finally {
|
|
Remove-Item $tmp -Force -ErrorAction SilentlyContinue
|
|
}
|
|
}
|
|
} catch {
|
|
Record-Failed "claude-hooks" $_.Exception.Message
|
|
}
|
|
}
|
|
}
|
|
|
|
# -WithMcpShrink: register the proxy. Probe npm first so a transient
|
|
# registry outage downgrades to a clean manual-config skip instead of
|
|
# registering an `npx -y caveman-shrink` entry that 404s on every spawn.
|
|
if ($WithMcpShrink) {
|
|
Say " → wiring caveman-shrink MCP proxy (-WithMcpShrink)"
|
|
if (Has-Cmd "npm") {
|
|
$packageOnNpm = $false
|
|
try { $null = & npm view $McpShrinkPkg 2>$null; $packageOnNpm = ($LASTEXITCODE -eq 0) } catch {}
|
|
if (-not $packageOnNpm) {
|
|
Warn " 'npm view $McpShrinkPkg' returned no metadata — registry unreachable or package missing."
|
|
Note " Skipping registration. Re-run -WithMcpShrink when the registry is reachable,"
|
|
Note " or copy the snippet below into your MCP config and point it at a local clone."
|
|
Record-Skipped "caveman-shrink" "npm registry probe failed"
|
|
Write-Host ""
|
|
return
|
|
}
|
|
}
|
|
$hasMcpAdd = $false
|
|
if (Has-Cmd "claude") {
|
|
try { $null = & claude mcp --help 2>$null; $hasMcpAdd = ($LASTEXITCODE -eq 0) } catch {}
|
|
}
|
|
if ($hasMcpAdd) {
|
|
if ($DryRun) {
|
|
Note " would run: claude mcp add caveman-shrink -- npx -y $McpShrinkPkg"
|
|
} else {
|
|
if (Try-Run "claude" @("mcp", "add", "caveman-shrink", "--", "npx", "-y", $McpShrinkPkg)) {
|
|
Record-Installed "caveman-shrink"
|
|
Note " registered. wrap an upstream by editing the mcpServers entry — see:"
|
|
Note " https://github.com/$Repo/tree/main/mcp-servers/caveman-shrink"
|
|
} else {
|
|
Record-Failed "caveman-shrink" "claude mcp add failed"
|
|
}
|
|
}
|
|
} else {
|
|
Note " 'claude mcp add' not available on this CLI. Add this snippet to your"
|
|
Note " Claude Code MCP config (settings.json or .mcp.json) manually:"
|
|
Write-Host ""
|
|
Write-Host ' {'
|
|
Write-Host ' "mcpServers": {'
|
|
Write-Host ' "fs-shrunk": {'
|
|
Write-Host ' "command": "npx",'
|
|
Write-Host ' "args": ['
|
|
Write-Host ' "caveman-shrink",'
|
|
Write-Host ' "npx", "@modelcontextprotocol/server-filesystem", "C:\\path\\to\\dir"'
|
|
Write-Host ' ]'
|
|
Write-Host ' }'
|
|
Write-Host ' }'
|
|
Write-Host ' }'
|
|
Write-Host ""
|
|
Record-Skipped "caveman-shrink" "manual config required (snippet printed)"
|
|
}
|
|
}
|
|
Write-Host ""
|
|
}
|
|
|
|
function Install-Gemini {
|
|
$script:DetectedCount++
|
|
Say "→ Gemini CLI detected"
|
|
$alreadyInstalled = $false
|
|
if (-not $Force) {
|
|
try {
|
|
$list = & gemini extensions list 2>$null
|
|
if ($list -match "(?i)caveman") { $alreadyInstalled = $true }
|
|
} catch {}
|
|
}
|
|
if ($alreadyInstalled) {
|
|
Note " caveman extension already installed (use -Force to reinstall)"
|
|
Record-Skipped "gemini" "extension already installed"
|
|
} else {
|
|
if (Try-Run "gemini" @("extensions", "install", "https://github.com/$Repo")) {
|
|
Record-Installed "gemini"
|
|
} else {
|
|
Record-Failed "gemini" "gemini extensions install failed"
|
|
}
|
|
}
|
|
Write-Host ""
|
|
}
|
|
|
|
function Install-ViaSkills {
|
|
param([string]$id, [string]$label, [string]$profile)
|
|
$script:DetectedCount++
|
|
Say "→ $label detected"
|
|
if (-not (Ensure-Node)) {
|
|
Record-Failed $id "node/npx missing"
|
|
Write-Host ""
|
|
return
|
|
}
|
|
$skillsArgs = @("-y", "skills", "add", $Repo)
|
|
if ($profile) { $skillsArgs += @("-a", $profile) }
|
|
if (Try-Run "npx" $skillsArgs) {
|
|
Record-Installed $id
|
|
} else {
|
|
Record-Failed $id "npx skills add failed (profile: $(if ($profile) { $profile } else { 'auto' }))"
|
|
}
|
|
Write-Host ""
|
|
}
|
|
|
|
# ── Run the install loop ────────────────────────────────────────────────────
|
|
foreach ($p in $Providers) {
|
|
if (-not (Want $p.id)) { continue }
|
|
if (-not (Resolve-DetectSpec $p.detect)) { continue }
|
|
switch ($p.id) {
|
|
'claude' { Install-Claude }
|
|
'gemini' { Install-Gemini }
|
|
default { Install-ViaSkills $p.id $p.label $p.profile }
|
|
}
|
|
}
|
|
|
|
# ── Generic fallback: npx skills add (auto-detect) ─────────────────────────
|
|
if (-not $SkipSkills -and $OnlyList.Count -eq 0 -and $DetectedCount -eq 0) {
|
|
Say "→ no known agents detected — running npx-skills auto-detect fallback"
|
|
if (Ensure-Node) {
|
|
if (Try-Run "npx" @("-y", "skills", "add", $Repo)) {
|
|
Record-Installed "skills-auto"
|
|
} else {
|
|
Record-Failed "skills-auto" "npx skills add (auto) failed"
|
|
}
|
|
}
|
|
Write-Host ""
|
|
}
|
|
|
|
# ── -WithInit: drop per-repo rule files into $PWD ──────────────────────────
|
|
# Avoid the variable name `$args` here — it shadows PowerShell's automatic
|
|
# unbound-args array. Use `$initArgs` instead.
|
|
function Run-Init {
|
|
$initArgs = @($PWD.Path)
|
|
if ($DryRun) { $initArgs += "--dry-run" }
|
|
if ($Force) { $initArgs += "--force" }
|
|
|
|
if ($RepoRoot -and (Test-Path (Join-Path $RepoRoot "tools\caveman-init.js")) -and (Has-Cmd "node")) {
|
|
if (Try-Run "node" (@((Join-Path $RepoRoot "tools\caveman-init.js")) + $initArgs)) { return $true }
|
|
return $false
|
|
}
|
|
|
|
if (-not (Has-Cmd "node")) {
|
|
Warn " node required to run caveman-init (install Node.js: https://nodejs.org)"
|
|
return $false
|
|
}
|
|
|
|
if ($DryRun) {
|
|
Note " would run: irm $InitScriptUrl | node - $($initArgs -join ' ')"
|
|
return $true
|
|
}
|
|
|
|
$tmp = Join-Path $env:TEMP "caveman-init-$([Guid]::NewGuid()).js"
|
|
try {
|
|
Invoke-WebRequest -Uri $InitScriptUrl -OutFile $tmp -UseBasicParsing
|
|
& node $tmp @initArgs
|
|
return ($LASTEXITCODE -eq 0)
|
|
} catch {
|
|
Warn " $($_.Exception.Message)"
|
|
return $false
|
|
} finally {
|
|
Remove-Item $tmp -Force -ErrorAction SilentlyContinue
|
|
}
|
|
}
|
|
|
|
if ($WithInit) {
|
|
Say "→ writing per-repo IDE rule files into $PWD (-WithInit)"
|
|
if (Run-Init) {
|
|
Record-Installed "caveman-init ($PWD)"
|
|
} else {
|
|
Record-Failed "caveman-init" "tools/caveman-init.js failed"
|
|
}
|
|
Write-Host ""
|
|
} elseif ($InstalledIds.Count -gt 0 -or $SkippedIds.Count -gt 0) {
|
|
Note " tip: re-run inside a repo with -All (or -WithInit) to also write per-repo"
|
|
Note " Cursor/Windsurf/Cline/Copilot/AGENTS.md rule files."
|
|
}
|
|
|
|
# ── Summary ────────────────────────────────────────────────────────────────
|
|
Write-Host ""
|
|
Say "🪨 done"
|
|
|
|
if ($InstalledIds.Count -gt 0) {
|
|
Ok " installed:"
|
|
foreach ($a in $InstalledIds) { Write-Host " - $a" }
|
|
}
|
|
|
|
if ($SkippedIds.Count -gt 0) {
|
|
Write-Host " skipped:"
|
|
for ($i = 0; $i -lt $SkippedIds.Count; $i++) {
|
|
Write-Host (" - {0} - {1}" -f $SkippedIds[$i], $SkippedWhy[$i])
|
|
}
|
|
}
|
|
|
|
if ($FailedIds.Count -gt 0) {
|
|
Warn " failed:"
|
|
for ($i = 0; $i -lt $FailedIds.Count; $i++) {
|
|
Warn (" - {0} - {1}" -f $FailedIds[$i], $FailedWhy[$i])
|
|
}
|
|
}
|
|
|
|
if ($InstalledIds.Count -eq 0 -and $SkippedIds.Count -eq 0 -and $FailedIds.Count -eq 0) {
|
|
Write-Host " nothing detected. install one of: claude, gemini, codex, cursor, windsurf, cline, copilot, opencode, roo, amp, goose, kiro, augment, aider-desk, continue, junie, trae, warp, ..."
|
|
Write-Host " or pass -Only <agent> to force a specific target (see -List for the full matrix)"
|
|
}
|
|
|
|
Write-Host ""
|
|
Note " start any session and say 'caveman mode', or run /caveman in Claude Code"
|
|
Note " uninstall: see https://github.com/$Repo#install"
|
|
|
|
# Exit non-zero only when EVERY detected agent failed (and at least one was
|
|
# detected). Skips don't count as failure.
|
|
if ($DetectedCount -gt 0 -and $InstalledIds.Count -eq 0 -and $SkippedIds.Count -eq 0) {
|
|
exit 1
|
|
}
|
|
exit 0
|