mirror of
https://github.com/JuliusBrussee/caveman.git
synced 2026-08-11 13:21:09 +02:00
Better install docs+scripts
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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".
|
||||
```
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user