Better install docs+scripts

This commit is contained in:
Julius Brussee
2026-05-01 02:14:49 +02:00
parent 47cd8d72de
commit ec7ce3614b
8 changed files with 1368 additions and 489 deletions
+10 -2
View File
@@ -125,7 +125,9 @@ Reads flag file at `$CLAUDE_CONFIG_DIR/.caveman-active`. Outputs colored badge s
- `full` or empty → `[CAVEMAN]` (orange)
- anything else → `[CAVEMAN:<MODE_UPPERCASED>]` (orange)
Configured in `settings.json` under `statusLine.command`. PowerShell counterpart at `hooks/caveman-statusline.ps1` for Windows.
Then appends the lifetime-savings suffix (`⛏ 12.4k`) read from `$CLAUDE_CONFIG_DIR/.caveman-statusline-suffix` — written by `caveman-stats.js` on every `/caveman-stats` run. **Default on**; users opt out with `CAVEMAN_STATUSLINE_SAVINGS=0`. The suffix file is absent until `/caveman-stats` runs at least once, so fresh installs render no fake number.
Configured in `settings.json` under `statusLine.command`. PowerShell counterpart at `hooks/caveman-statusline.ps1` for Windows. Both scripts symlink-refuse and whitelist-validate the flag/suffix file contents — never echo arbitrary bytes.
### Hook installation
@@ -172,10 +174,16 @@ How caveman reaches each agent type:
| Windsurf | `.windsurf/rules/caveman.md` with `trigger: always_on` | Yes — always-on rule |
| Cline | `.clinerules/caveman.md` (auto-discovered) | Yes — Cline injects all .clinerules files |
| Copilot | `.github/copilot-instructions.md` + `AGENTS.md` | Yes — repo-wide instructions |
| Others | `npx skills add JuliusBrussee/caveman` | No — user must say `/caveman` each session |
| Others (Junie, Trae, Warp, Tabnine, Mistral, Qwen, Devin, Droid, ForgeCode, Bob, Crush, iFlow, OpenHands, Qoder, Rovo Dev, Replit, Antigravity, …) | `npx skills add JuliusBrussee/caveman -a <profile>` | No — user must say `/caveman` each session |
For agents without hook systems, minimal always-on snippet lives in README under "Want it always on?" — keep current with `rules/caveman-activate.md`.
**Adding a new agent.** When extending `install.sh` / `install.ps1`:
1. The profile slug must exist in upstream [vercel-labs/skills](https://github.com/vercel-labs/skills). Verify against the README before merging — wrong slugs cause `npx skills add` to fail at runtime, not at install-script load.
2. `install.ps1` is **not** auto-generated. It is a parallel source of truth, hand-kept in sync with `install.sh`. Any new agent row must land in both: `install.sh`'s `PROVIDER_*` arrays + `SKILLS_AGENTS` table, and `install.ps1`'s `$Providers` array. Run `bash install.sh --list` and `pwsh install.ps1 -List` and confirm the two outputs agree.
3. Soft probes (config-dir-only) are fine but tag them with `PROVIDER_SOFT=1` (sh) / `soft=1` (ps1). They render with `(soft)` in `--list` so users know detection is best-effort.
---
## Evals
+59 -297
View File
@@ -32,7 +32,7 @@
---
A [Claude Code](https://docs.anthropic.com/en/docs/claude-code) skill/plugin and Codex plugin that makes agent talk like caveman — cutting **~75% of output tokens** while keeping full technical accuracy. Now with [文言文 mode](#文言文-wenyan-mode), [terse commits](#caveman-commit), [one-line code reviews](#caveman-review), and a [compression tool](#caveman-compress) that cuts **~46% of input tokens** every session.
A [Claude Code](https://docs.anthropic.com/en/docs/claude-code) skill/plugin and Codex plugin that makes agent talk like caveman — cutting **~75% of output tokens** while keeping full technical accuracy. Now with [文言文 mode](#文言文-wenyan-mode), [terse commits / one-line reviews / lifetime stats](#caveman-skills), and a [compression tool](#caveman-compress-receipts) that cuts **~46% of input tokens** every session.
Based on the viral observation that caveman-speak dramatically reduces LLM token usage without losing technical substance. So we made it a one-line install.
@@ -122,14 +122,14 @@ Based on the viral observation that caveman-speak dramatically reduces LLM token
```
- **Faster response** — less token to generate = speed go brrr
- **Easier to read** — no wall of text, just the answer
- **Same accuracy** — all technical info kept, only fluff removed ([science say so](https://arxiv.org/abs/2604.00025))
- **Save money**~71% less output token = less cost
- **Easier to read** — no wall of text, just answer
- **Same accuracy** — all technical info kept, only fluff dropped ([science say so](https://arxiv.org/abs/2604.00025))
- **Save money**65% mean output reduction across [our benchmarks](#benchmarks) (range 22-87%)
- **Fun** — every code review become comedy
## Install
**One line, every agent on your machine:**
**One line. Detect every agent. Install for each.**
```bash
# macOS / Linux / WSL / Git Bash
@@ -139,208 +139,56 @@ curl -fsSL https://raw.githubusercontent.com/JuliusBrussee/caveman/main/install.
irm https://raw.githubusercontent.com/JuliusBrussee/caveman/main/install.ps1 | iex
```
Installer detect every agent on machine (Claude Code, Gemini CLI, Codex, Cursor, Windsurf, Cline, Copilot). Run native install for each. Skip what you not have. Safe to re-run. Pass `--only <agent>` for one target, `--dry-run` to preview.
Detects 30+ agents (Claude Code, Gemini CLI, Codex, Cursor, Windsurf, Cline, Copilot, Continue, Kilo, Roo, Augment, Aider Desk, Amp, Bob, Crush, Devin, Droid, ForgeCode, Goose, iFlow, JetBrains Junie, Kiro CLI, Mistral Vibe, OpenHands, opencode, Qwen Code, Qoder, Rovo Dev, Tabnine, Trae, Warp, Replit Agent, Antigravity, …). Runs each one's native install. Skips what you not have. Safe to re-run.
**Or pick your agent manually:**
By default the installer wires Claude Code's hooks + statusline + stats badge and registers the `caveman-shrink` MCP proxy on top of the plugin install. Pass `--minimal` to skip the extras and just install the plugin/extension. Pass `--all` to also drop per-repo rule files into the current directory.
| Agent | Install |
|-------|---------|
| Flag | What |
|---|---|
| `--all` | Plugin + hooks + statusline + MCP shrink + per-repo rule files in `$PWD`. The full ride. |
| `--minimal` | Plugin/extension only. No hooks, no MCP shrink, no per-repo rules. |
| `--dry-run` | Preview, write nothing |
| `--only <agent>` | One target only (repeatable) |
| `--with-hooks` | Claude Code: also wire standalone hooks + statusline + stats badge. **On by default.** |
| `--with-mcp-shrink` | Claude Code: register the [caveman-shrink](#caveman-shrink-mcp-middleware) MCP proxy. **On by default.** |
| `--with-init` | Drop always-on rule files into the current repo (Cursor / Windsurf / Cline / Copilot / AGENTS.md). Off by default; turned on by `--all`. |
| `--list` | Print full agent matrix and exit |
| `--force` | Re-run even if already installed |
`install.sh --help` for full reference.
**Manual install per agent:**
| Agent | Command |
|---|---|
| **Claude Code** | `claude plugin marketplace add JuliusBrussee/caveman && claude plugin install caveman@caveman` |
| **Codex** | Clone repo → Open Codex in repo → `/plugins` → Search "Caveman" → Install |
| **Gemini CLI** | `gemini extensions install https://github.com/JuliusBrussee/caveman` |
| **Cursor** | `npx skills add JuliusBrussee/caveman -a cursor` |
| **Windsurf** | `npx skills add JuliusBrussee/caveman -a windsurf` |
| **Copilot** | `npx skills add JuliusBrussee/caveman -a github-copilot` |
| **Cline** | `npx skills add JuliusBrussee/caveman -a cline` |
| **Any other** | `npx skills add JuliusBrussee/caveman` |
| **Cursor / Windsurf / Cline / Copilot** | `npx skills add JuliusBrussee/caveman -a <cursor\|windsurf\|cline\|github-copilot>` |
| **Codex / opencode / Roo / Amp / Goose / Kiro / Augment / Aider Desk / Continue / Kilo / Junie / Trae / Warp / Tabnine / Mistral / Qwen / Devin / Droid / ForgeCode / Bob / Crush / iFlow / OpenHands / Qoder / Rovo Dev / Replit / Antigravity** | `npx skills add JuliusBrussee/caveman -a <profile>` (see `install.sh --list` for the full slug list) |
| **Anything else (40+ agents)** | `npx skills add JuliusBrussee/caveman` (auto-detect) |
Install once. Use in every session for that install target after that. One rock. That it.
Standalone Claude Code hooks (without plugin): `bash <(curl -s https://raw.githubusercontent.com/JuliusBrussee/caveman/main/hooks/install.sh)`. Windows: `irm https://raw.githubusercontent.com/JuliusBrussee/caveman/main/hooks/install.ps1 | iex`. Manual fallback for stubborn Windows envs lives in [`docs/install-windows.md`](docs/install-windows.md).
Uninstall: disable the Claude plugin, `gemini extensions uninstall caveman`, or `npx skills remove caveman`.
### What You Get
Auto-activation is built in for Claude Code, Gemini CLI, and the repo-local Codex setup below. `npx skills add` installs the skill for other agents, but does **not** install repo rule/instruction files, so Caveman does not auto-start there unless you add the always-on snippet below.
| Feature | Claude Code | Codex | Gemini CLI | Cursor / Windsurf | Cline / Copilot | Others* |
|---|:-:|:-:|:-:|:-:|:-:|:-:|
| Caveman mode | Y | Y | Y | Y | Y | Y |
| Auto-activate every session | Y | Y¹ | Y | with `--with-init` | with `--with-init` | with `--with-init` |
| `/caveman` command | Y | Y¹ | Y | — | — | — |
| Mode switching (lite/full/ultra) | Y | Y¹ | Y | Y² | — | — |
| Statusline badge | Y | — | — | — | — | — |
| caveman-commit / caveman-review | Y | — | Y | Y | Y | Y |
| caveman-compress / caveman-help | Y | Y³ | Y | Y | Y | Y |
| caveman-stats | Y | — | — | — | — | — |
| cavecrew (subagents) | Y | — | — | — | — | — |
| Feature | Claude Code | Codex | Gemini CLI | Cursor | Windsurf | Cline | Copilot |
|---------|:-----------:|:-----:|:----------:|:------:|:--------:|:-----:|:-------:|
| Caveman mode | Y | Y | Y | Y | Y | Y | Y |
| Auto-activate every session | Y | Y¹ | Y | —² | —² | —² | —² |
| `/caveman` command | Y | Y¹ | Y | — | — | — | — |
| Mode switching (lite/full/ultra) | Y | Y¹ | Y | Y³ | Y³ | — | — |
| Statusline badge | Y⁴ | — | — | — | — | — | — |
| caveman-commit | Y | — | Y | Y | Y | Y | Y |
| caveman-review | Y | — | Y | Y | Y | Y | Y |
| caveman-compress | Y | Y | Y | Y | Y | Y | Y |
| caveman-help | Y | — | Y | Y | Y | Y | Y |
| caveman-stats | Y | — | — | — | — | — | — |
| cavecrew (subagents) | Y | — | — | — | — | — | — |
\* opencode, Roo, Amp, Goose, Kiro CLI, Augment, Aider Desk, Continue, Kilo, Junie (JetBrains), Trae, Warp, Tabnine, Mistral, Qwen, Devin, Droid, ForgeCode, Bob, Crush, iFlow, OpenHands, Qoder, Rovo Dev, Replit, Antigravity, and more via `npx skills`. AGENTS.md / IDE rule files reach Zed, generic agents, etc. via `--with-init`.
¹ Codex uses `$caveman` instead of `/caveman`. Auto-start ships when you run Codex inside this repo (via `.codex/hooks.json`); for other repos, copy the hook or use `$caveman` manually. ² Mode switching is on-demand via the skill, no slash command. ³ Compress only.
> [!NOTE]
> Auto-activation works differently per agent: Claude Code uses SessionStart hooks, this repo's Codex dogfood setup uses `.codex/hooks.json`, Gemini uses context files. Cursor/Windsurf/Cline/Copilot can be made always-on, but `npx skills add` installs only the skill, not the repo rule/instruction files.
>
> ¹ Codex uses `$caveman` syntax, not `/caveman`. This repo ships `.codex/hooks.json`, so caveman auto-starts when you run Codex inside this repo. The installed plugin itself gives you `$caveman`; copy the same hook into another repo if you want always-on behavior there too. caveman-commit and caveman-review are not in the Codex plugin bundle — use the SKILL.md files directly.
> ² Add the "Want it always on?" snippet below to those agents' system prompt or rule file if you want session-start activation.
> ³ Cursor and Windsurf receive the full SKILL.md with all intensity levels. Mode switching works on-demand via the skill; no slash command.
> ⁴ Available in Claude Code, but plugin install only nudges setup. Standalone `install.sh` / `install.ps1` configures it automatically when no custom `statusLine` exists.
<details>
<summary><strong>Claude Code — full details</strong></summary>
The plugin install gives you skills + auto-loading hooks. If no custom `statusLine` is configured, Caveman nudges Claude to offer badge setup on first session.
```bash
claude plugin marketplace add JuliusBrussee/caveman
claude plugin install caveman@caveman
```
**Standalone hooks (without plugin):** If you prefer not to use the plugin system:
```bash
# macOS / Linux / WSL
bash <(curl -s https://raw.githubusercontent.com/JuliusBrussee/caveman/main/hooks/install.sh)
# Windows (PowerShell)
irm https://raw.githubusercontent.com/JuliusBrussee/caveman/main/hooks/install.ps1 | iex
```
Or from a local clone: `bash hooks/install.sh` / `powershell -File hooks\install.ps1`
**Windows manual fallback** — if automated install fails (#249, #199, #72), set up plugin-skill activation by hand. Does **not** install standalone hooks/statusline.
```powershell
$ClaudeDir = if ($env:CLAUDE_CONFIG_DIR) { $env:CLAUDE_CONFIG_DIR } else { Join-Path $HOME ".claude" }
$PluginSkillDir = Join-Path $ClaudeDir ".agents\plugins\caveman\skills\caveman"
$MarketplaceDir = Join-Path $ClaudeDir ".agents\plugins"
$MarketplaceFile = Join-Path $MarketplaceDir "marketplace.json"
# Copy SKILL.md into plugin path
New-Item -ItemType Directory -Path $PluginSkillDir -Force | Out-Null
Copy-Item ".\skills\caveman\SKILL.md" "$PluginSkillDir\SKILL.md" -Force
# Create or update marketplace.json with caveman entry
New-Item -ItemType Directory -Path $MarketplaceDir -Force | Out-Null
if (Test-Path $MarketplaceFile) {
$marketplace = Get-Content $MarketplaceFile -Raw | ConvertFrom-Json
} else {
$marketplace = [pscustomobject]@{}
}
if (-not ($marketplace.PSObject.Properties.Name -contains "plugins")) {
$marketplace | Add-Member -NotePropertyName plugins -NotePropertyValue ([pscustomobject]@{})
}
$plugins = [ordered]@{}
foreach ($p in $marketplace.plugins.PSObject.Properties) { $plugins[$p.Name] = $p.Value }
$plugins["caveman"] = [ordered]@{ name = "caveman"; source = "JuliusBrussee/caveman"; version = "main" }
$marketplace.plugins = [pscustomobject]$plugins
$marketplace | ConvertTo-Json -Depth 10 | Set-Content -Path $MarketplaceFile -Encoding UTF8
```
Verify: `Test-Path "$PluginSkillDir\SKILL.md"` (should print `True`), then restart Claude Code and run `/caveman`.
Uninstall: `bash hooks/uninstall.sh` or `powershell -File hooks\uninstall.ps1`
**Statusline badge:** Shows `[CAVEMAN]`, `[CAVEMAN:ULTRA]`, etc. in your Claude Code status bar.
- **Plugin install:** If you do not already have a custom `statusLine`, Claude should offer to configure it on first session
- **Standalone install:** Configured automatically by `install.sh` / `install.ps1` unless you already have a custom statusline
- **Custom statusline:** Installer leaves your existing statusline alone. See [`hooks/README.md`](hooks/README.md) for the merge snippet
</details>
<details>
<summary><strong>Codex — full details</strong></summary>
**macOS / Linux:**
1. Clone repo → Open Codex in the repo directory → `/plugins` → Search "Caveman" → Install
2. Repo-local auto-start is already wired by `.codex/hooks.json` + `.codex/config.toml`
**Windows:**
1. Enable symlinks first: `git config --global core.symlinks true` (requires Developer Mode or admin)
2. Clone repo → Open VS Code → Codex Settings → Plugins → find "Caveman" under local marketplace → Install → Reload Window
3. Codex hooks are currently disabled on Windows, so use `$caveman` to start manually
This repo also ships `.codex/hooks.json` and enables hooks in `.codex/config.toml`, so caveman auto-activates while you run Codex inside this repo on macOS/Linux. The installed plugin gives you `$caveman`; if you want always-on behavior in other repos too, copy the same `SessionStart` hook there and enable:
```toml
[features]
codex_hooks = true
```
</details>
<details>
<summary><strong>Gemini CLI — full details</strong></summary>
```bash
gemini extensions install https://github.com/JuliusBrussee/caveman
```
Update: `gemini extensions update caveman` · Uninstall: `gemini extensions uninstall caveman`
Auto-activates via `GEMINI.md` context file. Also ships custom Gemini commands:
- `/caveman` — switch intensity level (lite/full/ultra/wenyan)
- `/caveman-commit` — generate terse commit message
- `/caveman-review` — one-line code review
- `/caveman-stats` — real token usage + estimated savings (reads session log)
</details>
<details>
<summary><strong>Cursor / Windsurf / Cline / Copilot — full details</strong></summary>
`npx skills add` installs the skill file only — it does **not** install the agent's rule/instruction file, so caveman does not auto-start. For always-on, add the "Want it always on?" snippet below to your agent's rules or system prompt.
| Agent | Command | Not installed | Mode switching | Always-on location |
|-------|---------|--------------|:--------------:|--------------------|
| Cursor | `npx skills add JuliusBrussee/caveman -a cursor` | `.cursor/rules/caveman.mdc` | Y | Cursor rules |
| Windsurf | `npx skills add JuliusBrussee/caveman -a windsurf` | `.windsurf/rules/caveman.md` | Y | Windsurf rules |
| Cline | `npx skills add JuliusBrussee/caveman -a cline` | `.clinerules/caveman.md` | — | Cline rules or system prompt |
| Copilot | `npx skills add JuliusBrussee/caveman -a github-copilot` | `.github/copilot-instructions.md` + `AGENTS.md` | — | Copilot custom instructions |
Uninstall: `npx skills remove caveman`
Copilot works with Chat, Edits, and Coding Agent.
</details>
<details>
<summary><strong>Any other agent (opencode, Roo, Amp, Goose, Kiro, and 40+ more)</strong></summary>
[npx skills](https://github.com/vercel-labs/skills) supports 40+ agents:
```bash
npx skills add JuliusBrussee/caveman # auto-detect agent
npx skills add JuliusBrussee/caveman -a amp
npx skills add JuliusBrussee/caveman -a augment
npx skills add JuliusBrussee/caveman -a goose
npx skills add JuliusBrussee/caveman -a kiro-cli
npx skills add JuliusBrussee/caveman -a roo
# ... and many more
```
Uninstall: `npx skills remove caveman`
> **Windows note:** `npx skills` uses symlinks by default. If symlinks fail, add `--copy`: `npx skills add JuliusBrussee/caveman --copy`
**Important:** These agents don't have a hook system, so caveman won't auto-start. Say `/caveman` or "talk like caveman" to activate each session.
**Want it always on?** Paste this into your agent's system prompt or rules file — caveman will be active from the first message, every session:
```
Terse like caveman. Technical substance exact. Only fluff die.
Drop: articles, filler (just/really/basically), pleasantries, hedging.
Fragments OK. Short synonyms. Code unchanged.
Pattern: [thing] [action] [reason]. [next step].
ACTIVE EVERY RESPONSE. No revert after many turns. No filler drift.
Code/commits/PRs: normal. Off: "stop caveman" / "normal mode".
```
Where to put it:
| Agent | File |
|-------|------|
| opencode | `.config/opencode/AGENTS.md` |
| Roo | `.roo/rules/caveman.md` |
| Amp | your workspace system prompt |
| Others | your agent's system prompt or rules file |
</details>
`--with-init` writes `.cursor/rules/caveman.mdc`, `.windsurf/rules/caveman.md`, `.clinerules/caveman.md`, `.github/copilot-instructions.md`, and `AGENTS.md` into the current repo so caveman auto-starts there.
## Usage
@@ -374,102 +222,21 @@ Level stick until you change it or session end.
## Caveman Skills
### caveman-commit
`/caveman-commit` — terse commit messages. Conventional Commits. ≤50 char subject. Why over what.
### caveman-review
`/caveman-review` — one-line PR comments: `L42: 🔴 bug: user null. Add guard.` No throat-clearing.
### caveman-help
`/caveman-help` — quick-reference card. All modes, skills, commands, one command away.
### caveman-stats
`/caveman-stats` — real token usage + estimated savings + dollar amount. Reads the Claude Code session JSONL directly so the numbers are not the model's guess. Pricing comes from the model id stamped on each turn; ratio comes from `benchmarks/` (only `full` has measured data so far).
```
Caveman Stats
──────────────────────────────────
Session: ...projects/my-app/abc123.jsonl
Turns: 47
──────────────────────────────────
Output tokens: 3,210
Cache-read tokens: 128,400
──────────────────────────────────
Est. without caveman: 9,171
Est. tokens saved: 5,961 (~65%)
Est. saved (USD): ~$0.089
──────────────────────────────────
Memory compressed: 2 files, ~1,920 tokens saved per session start (approx)
Savings est. from benchmarks/ (mean per-task). Pricing for claude-sonnet-4-7. Actual varies by task.
```
**Lifetime totals** — caveman-stats appends a snapshot to `~/.claude/.caveman-history.jsonl` on every run:
| Command | What |
| Skill | What |
|---|---|
| `/caveman-stats` | This session only |
| `/caveman-stats --all` | Everything ever recorded (one row per session, latest snapshot wins) |
| `/caveman-stats --since 7d` | Last 7 days (`Nh` or `Nd`) |
| `/caveman-stats --share` | One-line tweetable summary 🪨 |
| `/caveman-commit` | Terse commit messages. Conventional Commits, ≤50 char subject. Why over what. |
| `/caveman-review` | One-line PR comments: `L42: 🔴 bug: user null. Add guard.` No throat-clearing. |
| `/caveman-help` | Quick-reference card. All modes, skills, commands. |
| `/caveman-stats` | Real session token usage + estimated savings + USD. Lifetime aggregation via `--all`, time window via `--since 7d`, tweetable line via `--share`. Reads the Claude Code session JSONL directly, no model-side guessing. Claude Code only. |
| `/caveman:compress <file>` | Rewrites a memory file (e.g. `CLAUDE.md`) into caveman-speak. Saves backup as `<file>.original.md`. Cuts ~46% of *input* tokens every session start. Code/URLs/paths preserved byte-for-byte. |
| `cavecrew-investigator/builder/reviewer` | Caveman-style Claude Code subagents. Ultra intensity by default — agent-to-agent handoffs stay terse without manual reminders. |
**Statusline savings** — opt-in. Set `CAVEMAN_STATUSLINE_SAVINGS=1` in your shell environment, then your status bar shows `[CAVEMAN] ⛏ 12.4k` (lifetime tokens saved). Updates every time `/caveman-stats` runs.
**Statusline savings badge** — on by default. After your first `/caveman-stats` run the statusline appends `[CAVEMAN] ⛏ 12.4k` (lifetime tokens saved) and updates every time `/caveman-stats` runs. Don't want it? Set `CAVEMAN_STATUSLINE_SAVINGS=0` to silence.
Claude Code only — needs the hook system to read the session transcript.
### cavecrew
Caveman-flavored subagent presets for Claude Code. Three drop-ins:
- `@cavecrew-investigator` — read-only research. Returns `file:line` references in fragment form. No suggestions.
- `@cavecrew-builder` — small surgical edits in 1-2 files. Returns a caveman-style diff summary.
- `@cavecrew-reviewer` — PR review in `L<line>: <severity> <problem>. <fix>.` form. No praise, no scope creep.
When you delegate work to a subagent, agent-to-agent prose is exactly the spot where caveman's grammar earns the most. All three subagents inherit caveman rules at ultra intensity, so handoffs stay terse without you having to remind them every turn.
Claude Code only — subagents are a Claude Code primitive.
### caveman-init (cavepack)
Drop the always-on caveman rule into any repo, for every IDE agent at once. Idempotent.
```bash
# In your project root:
node tools/caveman-init.js # writes rule files for all targets
node tools/caveman-init.js --dry-run # preview what would change
node tools/caveman-init.js --only cline # one target only
```
Targets installed (skips any that already contain the caveman sentinel):
- `.cursor/rules/caveman.mdc` — Cursor frontmatter (`alwaysApply: true`)
- `.windsurf/rules/caveman.md` — Windsurf frontmatter (`trigger: always_on`)
- `.clinerules/caveman.md` — Cline (auto-discovered)
- `.github/copilot-instructions.md` — Copilot (appended below existing content)
- `AGENTS.md` — generic agent context (appended)
Existing rule files are left alone unless `--force` is passed; appendable targets (Copilot, AGENTS.md) get the caveman block appended below your existing content. To compress an existing `CLAUDE.md`, use `/caveman:compress` instead — that's a separate, higher-stakes operation.
### caveman-compress
`/caveman:compress <filepath>` — caveman make Claude *speak* with fewer tokens. **Compress** make Claude *read* fewer tokens.
Your `CLAUDE.md` loads on **every session start**. Caveman Compress rewrites memory files into caveman-speak so Claude reads less — without you losing the human-readable original.
```
/caveman:compress CLAUDE.md
```
```
CLAUDE.md ← compressed (Claude reads this every session — fewer tokens)
CLAUDE.original.md ← human-readable backup (you read and edit this)
```
### caveman-compress receipts
| File | Original | Compressed | Saved |
|------|----------:|----------:|------:|
|---|---:|---:|---:|
| `claude-md-preferences.md` | 706 | 285 | **59.6%** |
| `project-notes.md` | 1145 | 535 | **53.3%** |
| `claude-md-project.md` | 1122 | 636 | **43.3%** |
@@ -477,29 +244,24 @@ CLAUDE.original.md ← human-readable backup (you read and edit this)
| `mixed-with-code.md` | 888 | 560 | **36.9%** |
| **Average** | **898** | **481** | **46%** |
Code blocks, URLs, file paths, commands, headings, dates, version numbers — anything technical passes through untouched. Only prose gets compressed. See the full [caveman-compress README](caveman-compress/README.md) for details. [Security note](./caveman-compress/SECURITY.md): Snyk flags this as High Risk due to subprocess/file patterns — it's a false positive.
Full docs: [caveman-compress README](caveman-compress/README.md). [Snyk false-positive note](./caveman-compress/SECURITY.md).
## caveman-shrink (MCP middleware)
Wrap any MCP server. Cut the prose. Keep the substance.
Stdio proxy that wraps any MCP server, intercepts `tools/list` / `prompts/list` / `resources/list` responses, and compresses the `description` fields. Code, URLs, paths, identifiers stay byte-for-byte identical.
```jsonc
{
"mcpServers": {
"fs-shrunk": {
"command": "npx",
"args": [
"caveman-shrink",
"npx", "@modelcontextprotocol/server-filesystem", "/path/to/dir"
]
"args": ["caveman-shrink", "npx", "@modelcontextprotocol/server-filesystem", "/path/to/dir"]
}
}
}
```
`caveman-shrink` is a stdio proxy. It spawns the upstream MCP server, intercepts `tools/list` / `prompts/list` / `resources/list` responses, and runs caveman compression over the `description` fields (and anything else you list in `CAVEMAN_SHRINK_FIELDS`). Code, URLs, paths, and identifiers stay byte-for-byte identical — same boundaries as the parent skill.
What it does NOT touch in v1: tool-call response bodies, request bodies, or any non-prose data. See [`mcp-servers/caveman-shrink/`](mcp-servers/caveman-shrink) for full docs.
V1 does not touch tool-call response bodies or request payloads. Auto-register via `install.sh --with-mcp-shrink`. Full docs: [`mcp-servers/caveman-shrink/`](mcp-servers/caveman-shrink).
## Benchmarks
+59
View File
@@ -0,0 +1,59 @@
# Windows install fallback
If `irm https://raw.githubusercontent.com/JuliusBrussee/caveman/main/install.ps1 | iex` fails on Windows (issues #249, #199, #72), set up plugin-skill activation by hand. This does **not** install the standalone hooks or the statusline — for those, run `hooks/install.ps1` after this.
```powershell
$ClaudeDir = if ($env:CLAUDE_CONFIG_DIR) { $env:CLAUDE_CONFIG_DIR } else { Join-Path $HOME ".claude" }
$PluginSkillDir = Join-Path $ClaudeDir ".agents\plugins\caveman\skills\caveman"
$MarketplaceDir = Join-Path $ClaudeDir ".agents\plugins"
$MarketplaceFile = Join-Path $MarketplaceDir "marketplace.json"
# Copy SKILL.md into the plugin path
New-Item -ItemType Directory -Path $PluginSkillDir -Force | Out-Null
Copy-Item ".\skills\caveman\SKILL.md" "$PluginSkillDir\SKILL.md" -Force
# Create or update marketplace.json with the caveman entry
New-Item -ItemType Directory -Path $MarketplaceDir -Force | Out-Null
if (Test-Path $MarketplaceFile) {
$marketplace = Get-Content $MarketplaceFile -Raw | ConvertFrom-Json
} else {
$marketplace = [pscustomobject]@{}
}
if (-not ($marketplace.PSObject.Properties.Name -contains "plugins")) {
$marketplace | Add-Member -NotePropertyName plugins -NotePropertyValue ([pscustomobject]@{})
}
$plugins = [ordered]@{}
foreach ($p in $marketplace.plugins.PSObject.Properties) { $plugins[$p.Name] = $p.Value }
$plugins["caveman"] = [ordered]@{ name = "caveman"; source = "JuliusBrussee/caveman"; version = "main" }
$marketplace.plugins = [pscustomobject]$plugins
$marketplace | ConvertTo-Json -Depth 10 | Set-Content -Path $MarketplaceFile -Encoding UTF8
```
Verify: `Test-Path "$PluginSkillDir\SKILL.md"` should print `True`. Restart Claude Code, then run `/caveman` to confirm the skill loads.
## Codex on Windows
1. Enable symlinks first: `git config --global core.symlinks true` (requires Developer Mode or admin).
2. Clone repo → Open VS Code → Codex Settings → Plugins → find "Caveman" under the local marketplace → Install → Reload Window.
3. Codex hooks are currently disabled on Windows, so use `$caveman` to start the mode manually each session.
## `npx skills` symlink fallback
`npx skills` uses symlinks by default. If symlinks fail, add `--copy`:
```powershell
npx skills add JuliusBrussee/caveman --copy
```
## Want it always on (any agent)?
Paste this into the agent's system prompt or rules file:
```
Terse like caveman. Technical substance exact. Only fluff die.
Drop: articles, filler (just/really/basically), pleasantries, hedging.
Fragments OK. Short synonyms. Code unchanged.
Pattern: [thing] [action] [reason]. [next step].
ACTIVE EVERY RESPONSE. No revert after many turns. No filler drift.
Code/commits/PRs: normal. Off: "stop caveman" / "normal mode".
```
+6 -8
View File
@@ -10,7 +10,7 @@
const fs = require('fs');
const path = require('path');
const os = require('os');
const { readFlag, appendFlag, readHistory } = require('./caveman-config');
const { readFlag, appendFlag, readHistory, safeWriteFlag } = require('./caveman-config');
// Mean per-task savings from benchmarks/results/*.json (avg_savings: 65 across
// 10 tasks, sonnet-4-20250514). Only 'full' has measured data; lite / ultra /
@@ -321,13 +321,11 @@ function main() {
// Statusline suffix: tiny pre-rendered string the shell statusline can
// cat without parsing JSONL. Updated on every /caveman-stats run.
try {
const agg = aggregateHistory(historyPath, null);
const suffix = agg.estSavedTokens > 0 ? `${humanizeTokens(agg.estSavedTokens)}` : '';
fs.writeFileSync(path.join(claudeDir, '.caveman-statusline-suffix'), suffix, { mode: 0o600 });
} catch (e) {
// Best-effort; the badge still renders without it.
}
// Routed through safeWriteFlag — the suffix path is predictable and
// user-owned, same symlink-clobber surface as the .caveman-active flag.
const agg = aggregateHistory(historyPath, null);
const suffix = agg.estSavedTokens > 0 ? `${humanizeTokens(agg.estSavedTokens)}` : '';
safeWriteFlag(path.join(claudeDir, '.caveman-statusline-suffix'), suffix);
}
if (share) {
+5 -3
View File
@@ -38,10 +38,12 @@ if ([string]::IsNullOrEmpty($Mode) -or $Mode -eq "full") {
[Console]::Write("${Esc}[38;5;172m[CAVEMAN:$Suffix]${Esc}[0m")
}
# Optional savings suffix: opt-in via CAVEMAN_STATUSLINE_SAVINGS=1.
# Savings suffix: on by default. Opt out via CAVEMAN_STATUSLINE_SAVINGS=0.
# Reads a pre-rendered string written by caveman-stats.js. Refuses reparse
# points and strips control bytes (matches statusline.sh hardening).
if ($env:CAVEMAN_STATUSLINE_SAVINGS -eq "1") {
# points and strips control bytes (matches statusline.sh hardening). Until
# /caveman-stats has run at least once, the suffix file is absent and nothing
# is rendered — safe default for fresh installs.
if ($env:CAVEMAN_STATUSLINE_SAVINGS -ne "0") {
$SavingsFile = Join-Path $ClaudeDir ".caveman-statusline-suffix"
if (Test-Path $SavingsFile) {
try {
+5 -3
View File
@@ -34,12 +34,14 @@ else
printf '\033[38;5;172m[CAVEMAN:%s]\033[0m' "$SUFFIX"
fi
# Optional savings suffix: opt-in via CAVEMAN_STATUSLINE_SAVINGS=1.
# Savings suffix: on by default. Opt out via CAVEMAN_STATUSLINE_SAVINGS=0.
# Reads a pre-rendered string written by caveman-stats.js so we don't shell out
# to node on every keystroke. Refuses symlinks and strips control bytes —
# same hardening as the flag file (a local attacker could plant a file with
# ANSI escape codes otherwise).
if [ "${CAVEMAN_STATUSLINE_SAVINGS:-0}" = "1" ]; then
# ANSI escape codes otherwise). Until /caveman-stats has run at least once,
# the suffix file is absent and nothing is rendered — so the default is safe
# for fresh installs (no fake number, no crash).
if [ "${CAVEMAN_STATUSLINE_SAVINGS:-1}" != "0" ]; then
SAVINGS_FILE="${CLAUDE_CONFIG_DIR:-$HOME/.claude}/.caveman-statusline-suffix"
if [ -f "$SAVINGS_FILE" ] && [ ! -L "$SAVINGS_FILE" ]; then
SAVINGS=$(head -c 64 "$SAVINGS_FILE" 2>/dev/null | tr -d '\000-\037')
+532 -77
View File
@@ -4,66 +4,363 @@
# 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. Skips agents that aren't installed.
# Safe to re-run.
# 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.
#
# Flags:
# -DryRun List what would be installed and exit.
# -Only <list> Comma-separated agent list (claude,gemini,codex,cursor,
# windsurf,cline,copilot).
# -SkipSkills Don't run the npx-skills fallback.
# -Force Re-run even if a target reports "already installed".
# 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,
[string]$Only = ""
[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"
$OnlyList = if ($Only) { $Only.Split(',') | ForEach-Object { $_.Trim() } } else { @() }
$RawBase = "https://raw.githubusercontent.com/$Repo/main"
$HooksInstallUrl = "$RawBase/hooks/install.ps1"
$InitScriptUrl = "$RawBase/tools/caveman-init.js"
$McpShrinkPkg = "caveman-shrink"
function Say($msg) { Write-Host $msg -ForegroundColor DarkYellow }
function Note($msg) { Write-Host $msg -ForegroundColor DarkGray }
# ── Help ────────────────────────────────────────────────────────────────────
if ($Help) {
@"
caveman installer (Windows) detects your agents and installs caveman for each.
function Want($name) {
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 $name
return $OnlyList -contains $id
}
function Has($cmd) {
return [bool](Get-Command $cmd -ErrorAction SilentlyContinue)
function Has-Cmd([string]$c) {
return [bool](Get-Command $c -ErrorAction SilentlyContinue)
}
function Run {
param([string]$Cmd, [string[]]$Args)
# 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: $Cmd $($Args -join ' ')"
Note " would run: $Exe $($Argv -join ' ')"
return $true
}
Write-Host " $ $Cmd $($Args -join ' ')"
Write-Host " $ $Exe $($Argv -join ' ')"
try {
& $Cmd @Args
return $LASTEXITCODE -eq 0
& $Exe @Argv
return ($LASTEXITCODE -eq 0)
} catch {
Write-Host " $($_.Exception.Message)" -ForegroundColor Red
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 ""
$Installed = @()
$Skipped = @()
# ── Claude Code ────────────────────────────────────────────────────────────
if ((Want "claude") -and (Has "claude")) {
# ── Per-agent install functions ─────────────────────────────────────────────
function Install-Claude {
$script:DetectedCount++
Say "→ Claude Code detected"
$pluginDone = $false
$alreadyInstalled = $false
if (-not $Force) {
try {
@@ -73,17 +370,103 @@ if ((Want "claude") -and (Has "claude")) {
}
if ($alreadyInstalled) {
Note " caveman plugin already installed (use -Force to reinstall)"
$Skipped += "claude (already installed)"
Record-Skipped "claude" "plugin already installed"
$pluginDone = $true
} else {
Run "claude" @("plugin", "marketplace", "add", $Repo) | Out-Null
Run "claude" @("plugin", "install", "caveman@caveman") | Out-Null
$Installed += "claude"
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.
if ($WithMcpShrink) {
Say " → wiring caveman-shrink MCP proxy (-WithMcpShrink)"
$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 ""
}
# ── Gemini CLI ─────────────────────────────────────────────────────────────
if ((Want "gemini") -and (Has "gemini")) {
function Install-Gemini {
$script:DetectedCount++
Say "→ Gemini CLI detected"
$alreadyInstalled = $false
if (-not $Force) {
@@ -94,72 +477,144 @@ if ((Want "gemini") -and (Has "gemini")) {
}
if ($alreadyInstalled) {
Note " caveman extension already installed (use -Force to reinstall)"
$Skipped += "gemini (already installed)"
Record-Skipped "gemini" "extension already installed"
} else {
Run "gemini" @("extensions", "install", "https://github.com/$Repo") | Out-Null
$Installed += "gemini"
if (Try-Run "gemini" @("extensions", "install", "https://github.com/$Repo")) {
Record-Installed "gemini"
} else {
Record-Failed "gemini" "gemini extensions install failed"
}
}
Write-Host ""
}
# ── Codex ──────────────────────────────────────────────────────────────────
if ((Want "codex") -and (Has "codex")) {
Say "→ Codex CLI detected"
Run "npx" @("-y", "skills", "add", $Repo, "-a", "codex") | Out-Null
$Installed += "codex"
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 ""
}
# ── IDE rule-file targets via npx-skills ───────────────────────────────────
$IdeTargets = @()
if ((Want "cursor") -and ((Has "cursor") -or (Test-Path "$HOME\.cursor"))) {
$IdeTargets += "cursor"
}
if ((Want "windsurf") -and ((Has "windsurf") -or
(Test-Path "$HOME\.codeium\windsurf") -or
(Test-Path "$HOME\.windsurf"))) {
$IdeTargets += "windsurf"
}
if ((Want "cline") -and (Test-Path "$HOME\.vscode\extensions")) {
$clineExt = Get-ChildItem "$HOME\.vscode\extensions" -ErrorAction SilentlyContinue |
Where-Object { $_.Name -match "(?i)cline" }
if ($clineExt) { $IdeTargets += "cline" }
}
if ((Want "copilot") -and (Has "gh")) { $IdeTargets += "github-copilot" }
foreach ($tgt in $IdeTargets) {
Say "$tgt detected"
Run "npx" @("-y", "skills", "add", $Repo, "-a", $tgt) | Out-Null
$Installed += $tgt
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) ─────────────────────────
# Only fire if (a) no -Only filter, (b) skills not disabled, (c) we neither
# installed nor skipped anything. Otherwise skip — see install.sh comment.
if (-not $SkipSkills -and $OnlyList.Count -eq 0 -and
$Installed.Count -eq 0 -and $Skipped.Count -eq 0) {
if (-not $SkipSkills -and $OnlyList.Count -eq 0 -and $DetectedCount -eq 0) {
Say "→ no known agents detected — running npx-skills auto-detect fallback"
if (Run "npx" @("-y", "skills", "add", $Repo)) { $Installed += "skills-auto" }
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 ($Installed.Count -gt 0) {
Write-Host " installed for:"
foreach ($a in $Installed) { Write-Host "$a" }
if ($InstalledIds.Count -gt 0) {
Ok " installed:"
foreach ($a in $InstalledIds) { Write-Host " - $a" }
}
if ($Skipped.Count -gt 0) {
if ($SkippedIds.Count -gt 0) {
Write-Host " skipped:"
foreach ($a in $Skipped) { Write-Host "$a" }
for ($i = 0; $i -lt $SkippedIds.Count; $i++) {
Write-Host (" - {0} - {1}" -f $SkippedIds[$i], $SkippedWhy[$i])
}
}
if ($Installed.Count -eq 0 -and $Skipped.Count -eq 0) {
Write-Host " nothing detected. install one of: claude, gemini, cursor, windsurf, cline, codex"
Write-Host " or pass -Only <agent> to force a specific target"
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
+692 -99
View File
@@ -9,65 +9,209 @@
# file). Skips agents that aren't installed. Safe to re-run — each underlying
# install command is idempotent.
#
# Flags:
# --dry-run List what would be installed and exit.
# --only <agent> Install only for the named agent (claude|gemini|codex|
# cursor|windsurf|cline|copilot). Repeatable.
# --skip-skills Don't run the npx-skills fallback.
# --force Re-run even if a target reports "already installed".
# Run `install.sh --help` for the full reference (flags + agent matrix).
set -e
set -euo pipefail
# ── Constants ──────────────────────────────────────────────────────────────
REPO="JuliusBrussee/caveman"
RAW_BASE="https://raw.githubusercontent.com/$REPO/main"
BIN_NAME="caveman"
HOOKS_INSTALL_URL="$RAW_BASE/hooks/install.sh"
INIT_SCRIPT_URL="$RAW_BASE/tools/caveman-init.js"
MCP_SHRINK_PKG="caveman-shrink"
# ── Flags + state (no associative arrays — bash 3.2 safe) ──────────────────
# WITH_HOOKS / WITH_MCP_SHRINK default to "auto": flipped on by default after
# argument parsing unless --minimal is set or the user already passed an
# explicit override. WITH_INIT stays opt-in because it writes per-repo rule
# files into $PWD — too surprising to do for a bare curl|bash.
DRY=0
FORCE=0
SKIP_SKILLS=0
WITH_HOOKS=auto
WITH_INIT=0
WITH_MCP_SHRINK=auto
ALL=0
MINIMAL=0
LIST_ONLY=0
NO_COLOR=0
ONLY=()
# Result trackers — parallel indexed arrays of agent ids and reasons.
INSTALLED_IDS=()
SKIPPED_IDS=()
SKIPPED_WHY=()
FAILED_IDS=()
FAILED_WHY=()
DETECTED_COUNT=0
# ── Color setup (auto-disable on non-TTY) ──────────────────────────────────
if [ ! -t 1 ]; then NO_COLOR=1; fi
# ── Argument parsing ───────────────────────────────────────────────────────
print_help() {
cat <<'EOF'
caveman installer — detects your agents and installs caveman for each one.
USAGE
install.sh [flags]
curl -fsSL https://raw.githubusercontent.com/JuliusBrussee/caveman/main/install.sh | bash
curl -fsSL https://raw.githubusercontent.com/JuliusBrussee/caveman/main/install.sh | bash -s -- --with-hooks
FLAGS
--dry-run Print what would run, do nothing.
--force Re-run even if a target reports "already installed".
--only <agent> Install only for the named agent. Repeatable.
--skip-skills Don't run the npx-skills auto-detect fallback.
--all Turn on --with-hooks, --with-init, --with-mcp-shrink.
Recommended when running from inside a repo you want
always-on caveman in.
--minimal Just the plugin/extension install. Skips hooks,
statusline, MCP shrink, and per-repo rule files.
--with-hooks Claude Code: also run the standalone hooks installer
(SessionStart/UserPromptSubmit hooks + statusline +
stats badge). On by default — pass --minimal to skip.
--with-init Also run caveman-init against the current working
directory so per-repo IDE rule files are written for
Cursor/Windsurf/Cline/Copilot/AGENTS.md. Off by default.
--with-mcp-shrink Claude Code: register the caveman-shrink MCP middleware
proxy (or print the JSON snippet for manual setup).
On by default — pass --minimal to skip.
--list Print the full provider matrix and exit.
--no-color Disable ANSI color codes (auto-disabled on non-TTY).
-h, --help Show this help and exit.
AGENTS DETECTED
Run with --list for the full table including detection probes. Soft-detected
agents (config-dir-only probes) are tagged "(soft)" in --list output.
Native:
claude Claude Code plugin marketplace + plugin install
gemini Gemini CLI gemini extensions install
codex Codex CLI npx skills add (codex)
IDE / VS Code-family:
cursor Cursor IDE npx skills add (cursor)
windsurf Windsurf IDE npx skills add (windsurf)
cline Cline npx skills add (cline)
continue Continue (VS Code) npx skills add (continue)
kilo Kilo Code npx skills add (kilo)
roo Roo Code npx skills add (roo)
augment Augment Code npx skills add (augment)
CLI agents:
aider-desk Aider Desk npx skills add (aider-desk)
amp Sourcegraph Amp npx skills add (amp)
bob IBM Bob npx skills add (bob)
crush Crush npx skills add (crush)
devin Devin (terminal) npx skills add (devin)
droid Droid (Factory) npx skills add (droid)
forgecode ForgeCode npx skills add (forgecode)
goose Block Goose npx skills add (goose)
iflow iFlow CLI npx skills add (iflow-cli)
junie JetBrains Junie npx skills add (junie)
kiro Kiro CLI npx skills add (kiro-cli)
mistral Mistral Vibe npx skills add (mistral-vibe)
openhands OpenHands npx skills add (openhands)
opencode opencode npx skills add (opencode)
qwen Qwen Code npx skills add (qwen-code)
qoder Qoder npx skills add (qoder)
rovodev Atlassian Rovo Dev npx skills add (rovodev)
tabnine Tabnine CLI npx skills add (tabnine-cli)
trae Trae npx skills add (trae)
warp Warp npx skills add (warp)
replit Replit Agent npx skills add (replit)
antigravity Google Antigravity npx skills add (antigravity)
Per-repo rule files (via --with-init / --all):
copilot GitHub Copilot .github/copilot-instructions.md
agents AGENTS.md (Zed, etc.) AGENTS.md (universal)
URLS THE INSTALLER MAY FETCH FROM
https://raw.githubusercontent.com/JuliusBrussee/caveman/main/install.sh
https://raw.githubusercontent.com/JuliusBrussee/caveman/main/hooks/install.sh
https://raw.githubusercontent.com/JuliusBrussee/caveman/main/tools/caveman-init.js
https://github.com/JuliusBrussee/caveman (via gemini extensions install)
EXAMPLES
install.sh # default: plugin + hooks + MCP shrink
install.sh --all # also drop per-repo rule files
install.sh --minimal # plugin/extension only
install.sh --dry-run --all
install.sh --only claude --with-mcp-shrink
install.sh --only cursor --only windsurf --with-init
install.sh --list
EOF
}
while [ $# -gt 0 ]; do
case "$1" in
--dry-run) DRY=1 ;;
--force) FORCE=1 ;;
--skip-skills) SKIP_SKILLS=1 ;;
--only) shift; ONLY+=("$1") ;;
-h|--help)
cat <<EOF
caveman installer — detects your agents and installs caveman for each one.
Usage: install.sh [--dry-run] [--force] [--skip-skills] [--only <agent>]
Detected agents (anything in PATH or with a known config dir):
claude Claude Code → plugin marketplace + plugin install
gemini Gemini CLI → gemini extensions install
codex Codex CLI → npx skills add (codex profile)
cursor Cursor IDE → npx skills add (cursor profile)
windsurf Windsurf IDE → npx skills add (windsurf profile)
cline Cline (VS Code ext) → npx skills add (cline profile)
copilot GitHub Copilot → npx skills add (github-copilot profile)
* Anything else → npx skills add (auto-detect fallback)
EOF
exit 0 ;;
*) echo "unknown flag: $1" >&2; exit 2 ;;
--dry-run) DRY=1 ;;
--force) FORCE=1 ;;
--skip-skills) SKIP_SKILLS=1 ;;
--with-hooks) WITH_HOOKS=1 ;;
--with-init) WITH_INIT=1 ;;
--with-mcp-shrink) WITH_MCP_SHRINK=1 ;;
--all) ALL=1 ;;
--minimal) MINIMAL=1 ;;
--list) LIST_ONLY=1 ;;
--no-color) NO_COLOR=1 ;;
--only)
shift
if [ $# -eq 0 ]; then
echo "error: --only requires an argument" >&2
exit 2
fi
# Backward-compat alias: 'aider' was renamed to 'aider-desk' to match the
# upstream skills profile slug. Old install.sh --only aider keeps working.
_only="$1"
[ "$_only" = "aider" ] && _only="aider-desk"
ONLY+=("$_only") ;;
-h|--help) print_help; exit 0 ;;
*)
echo "error: unknown flag: $1" >&2
echo "run 'install.sh --help' for usage" >&2
exit 2 ;;
esac
shift
done
c_orange=$'\033[38;5;172m'
c_dim=$'\033[2m'
c_reset=$'\033[0m'
# Resolve --all / --minimal / "auto" defaults into concrete flag values.
if [ "$ALL" = 1 ] && [ "$MINIMAL" = 1 ]; then
echo "error: --all and --minimal are mutually exclusive" >&2
exit 2
fi
if [ "$ALL" = 1 ]; then
WITH_HOOKS=1
WITH_INIT=1
WITH_MCP_SHRINK=1
fi
if [ "$MINIMAL" = 1 ]; then
WITH_HOOKS=0
WITH_MCP_SHRINK=0
WITH_INIT=0
fi
[ "$WITH_HOOKS" = "auto" ] && WITH_HOOKS=1
[ "$WITH_MCP_SHRINK" = "auto" ] && WITH_MCP_SHRINK=1
# ── Color helpers ──────────────────────────────────────────────────────────
if [ "$NO_COLOR" = 1 ]; then
c_orange=""; c_dim=""; c_red=""; c_green=""; c_reset=""
else
c_orange=$'\033[38;5;172m'
c_dim=$'\033[2m'
c_red=$'\033[31m'
c_green=$'\033[32m'
c_reset=$'\033[0m'
fi
say() { printf '%s%s%s\n' "$c_orange" "$1" "$c_reset"; }
note() { printf '%s%s%s\n' "$c_dim" "$1" "$c_reset"; }
warn() { printf '%s%s%s\n' "$c_red" "$1" "$c_reset" >&2; }
ok() { printf '%s%s%s\n' "$c_green" "$1" "$c_reset"; }
# ──────────────────────────────────────────────────────────────────────────
say "🪨 caveman installer"
note " $REPO"
echo
# ── Helpers ────────────────────────────────────────────────────────────────
want() {
if [ ${#ONLY[@]} -eq 0 ]; then return 0; fi
local a
for a in "${ONLY[@]}"; do [ "$a" = "$1" ] && return 0; done
return 1
}
@@ -81,97 +225,546 @@ run() {
"$@"
}
INSTALLED=()
SKIPPED=()
# Run a command but never let its non-zero exit kill the script (set -e).
try() {
if [ "$DRY" = 1 ]; then
note " would run: $*"
return 0
fi
echo " $ $*"
"$@"
}
# ── Claude Code ────────────────────────────────────────────────────────────
if want claude && command -v claude >/dev/null 2>&1; then
has() { command -v "$1" >/dev/null 2>&1; }
ensure_node() {
if has node && has npx; then return 0; fi
warn " node + npx required for this target — install Node.js (https://nodejs.org) and re-run."
return 1
}
# Find the local repo root (the dir containing this script) if we are NOT
# running from a curl-pipe. BASH_SOURCE[0] is unreliable when piped to bash,
# so we double-check the file actually exists and has the expected siblings.
detect_repo_root() {
local src="${BASH_SOURCE[0]:-}"
if [ -n "$src" ] && [ -f "$src" ]; then
local d
d="$(cd "$(dirname "$src")" 2>/dev/null && pwd)"
if [ -n "$d" ] && [ -f "$d/install.sh" ] && [ -d "$d/hooks" ] && [ -d "$d/tools" ]; then
echo "$d"
return 0
fi
fi
return 1
}
REPO_ROOT="$(detect_repo_root || true)"
# Result recorders (idempotent against double-add).
record_installed() { INSTALLED_IDS+=("$1"); }
record_skipped() { SKIPPED_IDS+=("$1"); SKIPPED_WHY+=("$2"); }
record_failed() { FAILED_IDS+=("$1"); FAILED_WHY+=("$2"); }
# ── Provider matrix (parallel arrays — bash 3.2 safe) ──────────────────────
# id | label | install path/notes | detection probe(s) | soft-detection?
#
# When adding a new agent: the profile slug must exist in upstream
# vercel-labs/skills (see https://github.com/vercel-labs/skills). Detection
# probes can be `command:<bin>` (binary on PATH), `dir:<path>` (directory
# exists), `file:<path>` (file exists), `vscode-ext:<needle>`,
# `cursor-ext:<needle>`, `jetbrains-config`, or `jetbrains-plugin:<needle>`.
# Multiple clauses joined by `||` — any match counts. Soft entries (PROVIDER_SOFT=1)
# rely only on dir/file probes — kept in the matrix to maximize reach but
# tagged "(soft)" in --list output so users know detection is best-effort.
PROVIDER_IDS=(
"claude" "gemini" "codex"
"cursor" "windsurf" "cline" "copilot" "continue" "kilo" "roo" "augment"
"aider-desk" "amp" "bob" "crush" "devin" "droid" "forgecode" "goose"
"iflow" "junie" "kiro" "mistral" "openhands" "opencode" "qwen" "qoder"
"rovodev" "tabnine" "trae" "warp" "replit" "antigravity"
)
PROVIDER_LABELS=(
"Claude Code" "Gemini CLI" "Codex CLI"
"Cursor" "Windsurf" "Cline" "GitHub Copilot" "Continue" "Kilo Code" "Roo Code" "Augment Code"
"Aider Desk" "Sourcegraph Amp" "IBM Bob" "Crush" "Devin (terminal)" "Droid (Factory)" "ForgeCode" "Block Goose"
"iFlow CLI" "JetBrains Junie" "Kiro CLI" "Mistral Vibe" "OpenHands" "opencode" "Qwen Code" "Qoder"
"Atlassian Rovo Dev" "Tabnine CLI" "Trae" "Warp" "Replit Agent" "Google Antigravity"
)
PROVIDER_MECHS=(
"claude plugin install" "gemini extensions install" "npx skills add (codex)"
"npx skills add (cursor)" "npx skills add (windsurf)" "npx skills add (cline)"
"npx skills add (github-copilot)" "npx skills add (continue)" "npx skills add (kilo)"
"npx skills add (roo)" "npx skills add (augment)"
"npx skills add (aider-desk)" "npx skills add (amp)" "npx skills add (bob)"
"npx skills add (crush)" "npx skills add (devin)" "npx skills add (droid)"
"npx skills add (forgecode)" "npx skills add (goose)" "npx skills add (iflow-cli)"
"npx skills add (junie)" "npx skills add (kiro-cli)" "npx skills add (mistral-vibe)"
"npx skills add (openhands)" "npx skills add (opencode)" "npx skills add (qwen-code)"
"npx skills add (qoder)" "npx skills add (rovodev)" "npx skills add (tabnine-cli)"
"npx skills add (trae)" "npx skills add (warp)" "npx skills add (replit)"
"npx skills add (antigravity)"
)
PROVIDER_DETECT=(
"command:claude" "command:gemini" "command:codex"
"command:cursor||dir:$HOME/.cursor"
"command:windsurf||dir:$HOME/.codeium/windsurf||dir:$HOME/.windsurf"
"vscode-ext:cline"
"command:gh"
"vscode-ext:continue.continue||vscode-ext:continue"
"vscode-ext:kilocode||dir:$HOME/.kilocode"
"vscode-ext:roo||vscode-ext:rooveterinaryinc.roo-cline||cursor-ext:roo"
"vscode-ext:augment||jetbrains-plugin:augment"
"command:aider||dir:$HOME/.aider-desk"
"command:amp"
"command:bob||dir:$HOME/.bob"
"command:crush||dir:$HOME/.config/crush"
"command:devin||dir:$HOME/.config/devin"
"command:droid||dir:$HOME/.factory"
"command:forge||dir:$HOME/.forge"
"command:goose||dir:$HOME/.config/goose"
"command:iflow||dir:$HOME/.iflow"
"dir:$HOME/.junie||jetbrains-plugin:junie"
"command:kiro||dir:$HOME/.kiro"
"command:mistral||dir:$HOME/.vibe"
"command:openhands||dir:$HOME/.openhands"
"command:opencode||file:$HOME/.config/opencode/AGENTS.md"
"command:qwen||dir:$HOME/.qwen"
"dir:$HOME/.qoder"
"command:rovodev||dir:$HOME/.rovodev"
"command:tabnine||dir:$HOME/.tabnine"
"command:trae||dir:$HOME/.trae"
"command:warp||dir:$HOME/.warp"
"command:replit||dir:$HOME/.replit"
"dir:$HOME/.gemini/antigravity"
)
# Soft = no `command:` clause, only dir/file/jetbrains-plugin probes. These
# may false-positive on stale config dirs but greatly widen the reach.
PROVIDER_SOFT=(
0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 1 0 0 0 0 0 1
0 0 0 0 0 1
)
# ── --list output ──────────────────────────────────────────────────────────
if [ "$LIST_ONLY" = 1 ]; then
say "🪨 caveman provider matrix"
printf '\n %-13s %-22s %s\n' "ID" "AGENT" "INSTALL MECHANISM"
printf ' %-13s %-22s %s\n' "----" "-----" "-----------------"
i=0
total=${#PROVIDER_IDS[@]}
while [ $i -lt "$total" ]; do
soft=""
[ "${PROVIDER_SOFT[$i]:-0}" = "1" ] && soft=" (soft)"
printf ' %-13s %-22s %s%s\n' "${PROVIDER_IDS[$i]}" "${PROVIDER_LABELS[$i]}" "${PROVIDER_MECHS[$i]}" "$soft"
i=$((i + 1))
done
echo
note " Detection probes per agent live in install.sh PROVIDER_DETECT."
note " Soft entries detect via config-dir presence only (no CLI on PATH)."
echo
note " Defaults: --with-hooks ON, --with-mcp-shrink ON, --with-init OFF."
note " --all turns all three on, --minimal turns all three off."
exit 0
fi
# ── Detection helpers ──────────────────────────────────────────────────────
vscode_ext_present() {
# Looks for any extension dir matching the substring across common roots.
local needle="$1"
local roots=("$HOME/.vscode/extensions" "$HOME/.vscode-server/extensions" "$HOME/.cursor/extensions" "$HOME/.windsurf/extensions")
local r
for r in "${roots[@]}"; do
if [ -d "$r" ] && ls "$r" 2>/dev/null | grep -qi "$needle"; then
return 0
fi
done
return 1
}
cursor_ext_present() {
local needle="$1"
if [ -d "$HOME/.cursor/extensions" ] && ls "$HOME/.cursor/extensions" 2>/dev/null | grep -qi "$needle"; then
return 0
fi
return 1
}
jetbrains_present() {
# macOS path + Linux XDG path. Treat presence of a JetBrains config dir as
# "JetBrains is installed" — the AI Assistant ships in most products now.
if [ -d "$HOME/Library/Application Support/JetBrains" ]; then return 0; fi
if [ -d "$HOME/.config/JetBrains" ]; then return 0; fi
return 1
}
jetbrains_plugin_present() {
local needle="$1"
local roots=("$HOME/Library/Application Support/JetBrains" "$HOME/.config/JetBrains")
local r
for r in "${roots[@]}"; do
if [ -d "$r" ] && find "$r" -maxdepth 4 -type d -iname "*${needle}*" 2>/dev/null | grep -q .; then
return 0
fi
done
return 1
}
# Parse a PROVIDER_DETECT spec like "command:foo||dir:$HOME/x" and return 0
# if any clause matches.
detect_match() {
local spec="$1"
local IFS='|'
# Split on '||' by replacing the doubled pipe with a single delim safely.
local clauses
clauses=$(printf '%s' "$spec" | awk -v RS='||' '{print}' 2>/dev/null) || true
if [ -z "$clauses" ]; then clauses="$spec"; fi
local clause
while IFS= read -r clause; do
[ -z "$clause" ] && continue
case "$clause" in
command:*) has "${clause#command:}" && return 0 ;;
dir:*) [ -d "${clause#dir:}" ] && return 0 ;;
file:*) [ -f "${clause#file:}" ] && return 0 ;;
vscode-ext:*) vscode_ext_present "${clause#vscode-ext:}" && return 0 ;;
cursor-ext:*) cursor_ext_present "${clause#cursor-ext:}" && return 0 ;;
jetbrains-config) jetbrains_present && return 0 ;;
jetbrains-plugin:*) jetbrains_plugin_present "${clause#jetbrains-plugin:}" && return 0 ;;
esac
done <<EOF
$clauses
EOF
return 1
}
# ──────────────────────────────────────────────────────────────────────────
say "🪨 caveman installer"
note " $REPO"
if [ "$DRY" = 1 ]; then note " (dry run — nothing will be written)"; fi
echo
# ── Per-agent install functions (each returns 0/1) ─────────────────────────
install_claude() {
DETECTED_COUNT=$((DETECTED_COUNT + 1))
say "→ Claude Code detected"
if [ "$FORCE" = 1 ] || ! claude plugin list 2>/dev/null | grep -qi caveman; then
run claude plugin marketplace add "$REPO"
run claude plugin install "caveman@caveman"
INSTALLED+=("claude")
else
local plugin_done=0
if [ "$FORCE" = 0 ] && claude plugin list 2>/dev/null | grep -qi caveman; then
note " caveman plugin already installed (use --force to reinstall)"
SKIPPED+=("claude (already installed)")
fi
echo
fi
# ── Gemini CLI ─────────────────────────────────────────────────────────────
if want gemini && command -v gemini >/dev/null 2>&1; then
say "→ Gemini CLI detected"
if [ "$FORCE" = 1 ] || ! gemini extensions list 2>/dev/null | grep -qi caveman; then
run gemini extensions install "https://github.com/$REPO"
INSTALLED+=("gemini")
record_skipped "claude" "plugin already installed"
plugin_done=1
else
if try claude plugin marketplace add "$REPO" && \
try claude plugin install "caveman@caveman"; then
record_installed "claude"
plugin_done=1
else
record_failed "claude" "claude plugin install failed"
fi
fi
# --with-hooks: also run the standalone hooks installer.
if [ "$WITH_HOOKS" = 1 ]; then
say " → installing standalone hooks (--with-hooks)"
local hooks_args=""
[ "$FORCE" = 1 ] && hooks_args="--force"
if [ -n "$REPO_ROOT" ] && [ -f "$REPO_ROOT/hooks/install.sh" ]; then
if [ "$DRY" = 1 ]; then
note " would run: bash $REPO_ROOT/hooks/install.sh $hooks_args"
else
# shellcheck disable=SC2086
if bash "$REPO_ROOT/hooks/install.sh" $hooks_args; then
record_installed "claude-hooks"
else
record_failed "claude-hooks" "hooks/install.sh failed"
fi
fi
else
if ! has curl; then
warn " curl required to fetch hooks installer remotely"
record_failed "claude-hooks" "curl missing"
elif [ "$DRY" = 1 ]; then
note " would run: bash <(curl -fsSL $HOOKS_INSTALL_URL) $hooks_args"
else
# shellcheck disable=SC2086
if bash <(curl -fsSL "$HOOKS_INSTALL_URL") $hooks_args; then
record_installed "claude-hooks"
else
record_failed "claude-hooks" "remote hooks installer failed"
fi
fi
fi
fi
# --with-mcp-shrink: register the proxy (or print the snippet).
if [ "$WITH_MCP_SHRINK" = 1 ]; then
say " → wiring caveman-shrink MCP proxy (--with-mcp-shrink)"
if has claude && claude mcp --help >/dev/null 2>&1; then
# Newer Claude Code CLIs expose `claude mcp add`. Wrap stdio: proxy
# spawns the upstream as a child. Without an upstream the proxy is a
# no-op, so we register the proxy itself with a placeholder upstream
# and tell the user how to point it at a real server.
if [ "$DRY" = 1 ]; then
note " would run: claude mcp add caveman-shrink -- npx -y $MCP_SHRINK_PKG"
else
if try claude mcp add caveman-shrink -- npx -y "$MCP_SHRINK_PKG"; then
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"
fi
fi
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:"
cat <<'EOF'
{
"mcpServers": {
"fs-shrunk": {
"command": "npx",
"args": [
"caveman-shrink",
"npx", "@modelcontextprotocol/server-filesystem", "/path/to/dir"
]
}
}
}
EOF
record_skipped "caveman-shrink" "manual config required (snippet printed)"
fi
fi
echo
return 0
}
install_gemini() {
DETECTED_COUNT=$((DETECTED_COUNT + 1))
say "→ Gemini CLI detected"
if [ "$FORCE" = 0 ] && gemini extensions list 2>/dev/null | grep -qi caveman; then
note " caveman extension already installed (use --force to reinstall)"
SKIPPED+=("gemini (already installed)")
record_skipped "gemini" "extension already installed"
else
if try gemini extensions install "https://github.com/$REPO"; then
record_installed "gemini"
else
record_failed "gemini" "gemini extensions install failed"
fi
fi
echo
fi
}
# ── Codex ──────────────────────────────────────────────────────────────────
if want codex && command -v codex >/dev/null 2>&1; then
install_codex() {
DETECTED_COUNT=$((DETECTED_COUNT + 1))
say "→ Codex CLI detected"
run npx -y skills add "$REPO" -a codex
INSTALLED+=("codex")
if ! ensure_node; then
record_failed "codex" "node/npx missing"
echo
return 0
fi
if try npx -y skills add "$REPO" -a codex; then
record_installed "codex"
else
record_failed "codex" "npx skills add (codex) failed"
fi
echo
}
# Generic IDE/skills profile installer used by everything that goes through
# `npx skills add`. Pass an empty profile to use auto-detect.
install_via_skills() {
local id="$1"
local label="$2"
local profile="$3"
DETECTED_COUNT=$((DETECTED_COUNT + 1))
say "$label detected"
if ! ensure_node; then
record_failed "$id" "node/npx missing"
echo
return 0
fi
local cmd_ok=1
if [ -n "$profile" ]; then
if ! try npx -y skills add "$REPO" -a "$profile"; then cmd_ok=0; fi
else
if ! try npx -y skills add "$REPO"; then cmd_ok=0; fi
if [ "$cmd_ok" = 1 ]; then
note " used auto-detect — if your agent wasn't picked up, re-run with --only and a profile"
fi
fi
if [ "$cmd_ok" = 1 ]; then
record_installed "$id"
else
record_failed "$id" "npx skills add failed (profile: ${profile:-auto})"
fi
echo
}
# ── Run installs in declared order ─────────────────────────────────────────
# Claude: separate function (plugin + optional hooks + optional mcp-shrink).
if want claude && detect_match "command:claude"; then
install_claude
fi
# ── IDE rule-file targets via npx-skills ───────────────────────────────────
declare -a IDE_TARGETS
if want cursor && { command -v cursor >/dev/null 2>&1 || [ -d "$HOME/.cursor" ]; }; then
IDE_TARGETS+=("cursor")
fi
if want windsurf && { command -v windsurf >/dev/null 2>&1 || [ -d "$HOME/.codeium/windsurf" ] || [ -d "$HOME/.windsurf" ]; }; then
IDE_TARGETS+=("windsurf")
fi
if want cline && [ -d "$HOME/.vscode/extensions" ] && \
ls "$HOME/.vscode/extensions" 2>/dev/null | grep -qi cline; then
IDE_TARGETS+=("cline")
fi
if want copilot && command -v gh >/dev/null 2>&1; then
IDE_TARGETS+=("github-copilot")
# Gemini.
if want gemini && detect_match "command:gemini"; then
install_gemini
fi
for tgt in "${IDE_TARGETS[@]}"; do
say "$tgt detected"
run npx -y skills add "$REPO" -a "$tgt"
INSTALLED+=("$tgt")
echo
# Codex.
if want codex && detect_match "command:codex"; then
install_codex
fi
# IDE / agent skills targets — id, label, profile, detect spec. Profile slugs
# are validated against upstream vercel-labs/skills (see CLAUDE.md note). Add
# new rows here AND to the PROVIDER_* matrix above so --list stays accurate.
SKILLS_AGENTS=(
"cursor|Cursor|cursor|command:cursor||dir:$HOME/.cursor"
"windsurf|Windsurf|windsurf|command:windsurf||dir:$HOME/.codeium/windsurf||dir:$HOME/.windsurf"
"cline|Cline|cline|vscode-ext:cline"
"copilot|GitHub Copilot|github-copilot|command:gh"
"continue|Continue|continue|vscode-ext:continue.continue||vscode-ext:continue"
"kilo|Kilo Code|kilo|vscode-ext:kilocode||dir:$HOME/.kilocode"
"roo|Roo Code|roo|vscode-ext:roo||vscode-ext:rooveterinaryinc.roo-cline||cursor-ext:roo"
"augment|Augment Code|augment|vscode-ext:augment||jetbrains-plugin:augment"
"aider-desk|Aider Desk|aider-desk|command:aider||dir:$HOME/.aider-desk"
"amp|Sourcegraph Amp|amp|command:amp"
"bob|IBM Bob|bob|command:bob||dir:$HOME/.bob"
"crush|Crush|crush|command:crush||dir:$HOME/.config/crush"
"devin|Devin (terminal)|devin|command:devin||dir:$HOME/.config/devin"
"droid|Droid (Factory)|droid|command:droid||dir:$HOME/.factory"
"forgecode|ForgeCode|forgecode|command:forge||dir:$HOME/.forge"
"goose|Block Goose|goose|command:goose||dir:$HOME/.config/goose"
"iflow|iFlow CLI|iflow-cli|command:iflow||dir:$HOME/.iflow"
"junie|JetBrains Junie|junie|dir:$HOME/.junie||jetbrains-plugin:junie"
"kiro|Kiro CLI|kiro-cli|command:kiro||dir:$HOME/.kiro"
"mistral|Mistral Vibe|mistral-vibe|command:mistral||dir:$HOME/.vibe"
"openhands|OpenHands|openhands|command:openhands||dir:$HOME/.openhands"
"opencode|opencode|opencode|command:opencode||file:$HOME/.config/opencode/AGENTS.md"
"qwen|Qwen Code|qwen-code|command:qwen||dir:$HOME/.qwen"
"qoder|Qoder|qoder|dir:$HOME/.qoder"
"rovodev|Atlassian Rovo Dev|rovodev|command:rovodev||dir:$HOME/.rovodev"
"tabnine|Tabnine CLI|tabnine-cli|command:tabnine||dir:$HOME/.tabnine"
"trae|Trae|trae|command:trae||dir:$HOME/.trae"
"warp|Warp|warp|command:warp||dir:$HOME/.warp"
"replit|Replit Agent|replit|command:replit||dir:$HOME/.replit"
"antigravity|Google Antigravity|antigravity|dir:$HOME/.gemini/antigravity"
)
for spec in "${SKILLS_AGENTS[@]}"; do
IFS='|' read -r id label profile detect_spec <<EOF
$spec
EOF
if want "$id" && detect_match "$detect_spec"; then
install_via_skills "$id" "$label" "$profile"
fi
done
# ── Generic fallback: npx skills add (auto-detect) ─────────────────────────
# Only fire if (a) no --only filter was passed, (b) skills wasn't disabled,
# and (c) we neither installed nor skipped anything. Otherwise the user's
# already-installed setup or explicit --only target shouldn't be drowned in
# an unrelated fallback.
if [ "$SKIP_SKILLS" = 0 ] && [ ${#ONLY[@]} -eq 0 ] && \
[ ${#INSTALLED[@]} -eq 0 ] && [ ${#SKIPPED[@]} -eq 0 ]; then
# Only fire if (a) no --only filter, (b) skills not disabled, (c) we neither
# installed, skipped, nor failed anything detected.
if [ "$SKIP_SKILLS" = 0 ] && [ ${#ONLY[@]} -eq 0 ] && [ "$DETECTED_COUNT" -eq 0 ]; then
say "→ no known agents detected — running npx-skills auto-detect fallback"
if run npx -y skills add "$REPO"; then
INSTALLED+=("skills-auto")
if ensure_node; then
if try npx -y skills add "$REPO"; then
record_installed "skills-auto"
else
record_failed "skills-auto" "npx skills add (auto) failed"
fi
fi
echo
fi
# ── --with-init: drop per-repo rule files into $PWD ────────────────────────
run_init() {
local args=("$PWD")
[ "$DRY" = 1 ] && args+=("--dry-run")
[ "$FORCE" = 1 ] && args+=("--force")
if [ -n "$REPO_ROOT" ] && [ -f "$REPO_ROOT/tools/caveman-init.js" ]; then
if has node; then
try node "$REPO_ROOT/tools/caveman-init.js" "${args[@]}"
return $?
fi
fi
# Curl-pipe fallback: stream the init script into `node -`.
if ! has node; then
warn " node required to run caveman-init (install Node.js: https://nodejs.org)"
return 1
fi
if ! has curl; then
warn " curl required to fetch caveman-init remotely"
return 1
fi
if [ "$DRY" = 1 ]; then
note " would run: curl -fsSL $INIT_SCRIPT_URL | node - ${args[*]}"
return 0
fi
curl -fsSL "$INIT_SCRIPT_URL" | node - "${args[@]}"
}
if [ "$WITH_INIT" = 1 ]; then
say "→ writing per-repo IDE rule files into $PWD (--with-init)"
if run_init; then
record_installed "caveman-init ($PWD)"
else
record_failed "caveman-init" "tools/caveman-init.js failed"
fi
echo
elif [ ${#INSTALLED_IDS[@]} -gt 0 ] || [ ${#SKIPPED_IDS[@]} -gt 0 ]; then
# Friendly nudge for the per-repo flow (only when we actually did something).
note " tip: re-run inside a repo with --all (or --with-init) to also write per-repo"
note " Cursor/Windsurf/Cline/Copilot/AGENTS.md rule files."
fi
# ── Summary ────────────────────────────────────────────────────────────────
echo
say "🪨 done"
if [ ${#INSTALLED[@]} -gt 0 ]; then
echo " installed for:"
for a in "${INSTALLED[@]}"; do printf ' • %s\n' "$a"; done
if [ ${#INSTALLED_IDS[@]} -gt 0 ]; then
ok " installed:"
for a in "${INSTALLED_IDS[@]}"; do printf ' • %s\n' "$a"; done
fi
if [ ${#SKIPPED[@]} -gt 0 ]; then
if [ ${#SKIPPED_IDS[@]} -gt 0 ]; then
echo " skipped:"
for a in "${SKIPPED[@]}"; do printf ' • %s\n' "$a"; done
i=0
while [ $i -lt ${#SKIPPED_IDS[@]} ]; do
printf ' • %s — %s\n' "${SKIPPED_IDS[$i]}" "${SKIPPED_WHY[$i]}"
i=$((i + 1))
done
fi
if [ ${#INSTALLED[@]} -eq 0 ] && [ ${#SKIPPED[@]} -eq 0 ]; then
echo " nothing detected. install one of: claude, gemini, cursor, windsurf, cline, codex"
echo " or pass --only <agent> to force a specific target"
if [ ${#FAILED_IDS[@]} -gt 0 ]; then
warn " failed:"
i=0
while [ $i -lt ${#FAILED_IDS[@]} ]; do
printf ' • %s — %s\n' "${FAILED_IDS[$i]}" "${FAILED_WHY[$i]}" >&2
i=$((i + 1))
done
fi
if [ ${#INSTALLED_IDS[@]} -eq 0 ] && [ ${#SKIPPED_IDS[@]} -eq 0 ] && [ ${#FAILED_IDS[@]} -eq 0 ]; then
echo " nothing detected. run 'install.sh --list' to see all 30+ supported agents"
echo " or pass --only <agent> to force a specific target."
fi
echo
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 [ "$DETECTED_COUNT" -gt 0 ] && [ ${#INSTALLED_IDS[@]} -eq 0 ] && [ ${#SKIPPED_IDS[@]} -eq 0 ]; then
exit 1
fi
exit 0