Approve your coding agent's actions with a Guitar Hero controller.
Code like a guitar GOD. π€
Vibe coding is fast but hands-off; reviewing every step is safe but slow. Vibe Hero is the middle ground: Claude Code proposes each command or edit, and you approve or reject it with a guitar gesture β keeping a human glance in the loop at rhythm-game speed.
ββββββββββββββββ 1. wants to run a tool
β Claude Code ββββββββββββββββ
ββββββββββββββββ βΌ
β² PermissionRequest hook
β β
β β 2. POST /decision (blocks)
4. {"behavior":"allow"} βΌ
β βββββββββββββββββββββ
ββββββββββββββββ bridge :8787 β
β vibe_hero.server β
βββββββββββββββββββββ
β²
β 3. you strum
βββββββββββββββββββββ
β guitar (USB HID) β
βββββββββββββββββββββ
- Claude Code fires its
PermissionRequesthook whenever it wants to run a tool. - The hook POSTs the pending request to the local bridge, which prints what is being approved and blocks until you play something.
- Green fret + strum = allow. Red fret + strum = deny.
- The decision goes back to Claude Code as JSON on the hook's stdout.
Fail-safe: if the bridge isn't running, the hook exits silently with no output, and Claude Code falls back to its normal keyboard prompt. An unplugged guitar never blocks your session.
- Python 3.9+
- A Guitar Hero / Rock Band-style controller that enumerates as a gamepad (see Known controllers)
- Claude Code
- macOS: your terminal needs Input Monitoring and Accessibility permission (System Settings β Privacy & Security) to read the controller
git clone https://github.com/ProjectX-collab/Vibe-Hero.git
cd Vibe-Hero
python -m pip install -e .Clone controllers (DOYO, NBCP, β¦) use inconsistent button indices, so this step is not optional β the shipped defaults are wrong on most units.
python -m vibe_hero.diagnosePress each fret one at a time, then strum, then whammy, and note the indices it prints. Then create your mapping and edit it to match:
python -m vibe_hero.diagnose --templateThat copies mappings.example.json to
mappings.json β gitignored, since it's specific to your hardware. If your
unit is already in KNOWN_CONTROLLERS.md, copy that
block instead and skip the discovery.
Check your work before wiring anything up:
python -m vibe_hero.play --verifyIt names each fret in turn and tells you whether it arrived. If every fret and both strum directions check out, your mapping is right.
For raw event output instead, python -m vibe_hero.guitar prints one line
per strum. Holding frets alone prints nothing β by design, not a fault.
python -m vibe_hero.serverLeave it running in its own terminal. This is where you'll see what Claude Code is asking for.
Only one process can hold the controller. Quit
vibe_hero.guitarbefore startingvibe_hero.server.
Working inside this repo, the hook is already registered by the
checked-in .claude/settings.json β nothing to
configure. Start Claude Code and ask it to do something. When it wants to
run a command, the bridge terminal shows:
β³ APPROVAL WANTED β Bash: git status
green + strum = allow | red + strum = deny
Glance at it, then play your answer.
Merge the hooks key from
hooks/settings.example.json into your
~/.claude/settings.json and replace the placeholder with the absolute path
to your checkout. Restart Claude Code to load it.
The hook itself is pure standard library, so plain python3 runs it β it
does not need the environment where pygame is installed. That one is
only for the bridge.
| Gesture | Decision |
|---|---|
| π’ Green + strum | Allow |
| π΄ Red + strum | Deny |
| Anything else | Ignored β the request stays pending |
Three rules hold the design together:
- Only a strum decides. Frets change what a strum means; they never answer on their own, so resting your hand on the neck is safe.
- A bare strum means nothing. Strumming with no fret held is the easiest thing to do by accident, so it must not be what approves a command. Approving costs a deliberate press.
- Deny wins ties. Any chord containing red denies, even with green also held, so a fumbled fret can't turn a reject into an approve.
Everything else is deliberately unmapped. The gesture vocabulary is the fun part and it's being designed in the open β bring opinions.
There's a tiny Guitar Hero in the box:
python -m vibe_hero.play βͺ VIBE HERO score 1240 streak Γ2 (11) 47.3s
β β β β β
β β βββ β β
β β β β β
βββ β β β β
βββββββββββββββββββββββββββββββββββ
( β ) ( β ) [βββ] ( β ) ( β )
PERFECT +200
Notes fall down five lanes; hold the matching fret and strum as one crosses the line. Same rule as the bridge β only a strum commits.
The controller can only be held by one process, so you can't run the game next to a running bridge. Run it inside the bridge instead:
python -m vibe_hero.server --jamThe game plays while nothing is pending. The moment Claude Code asks for something, the neck clears and the approval prompt takes over β approvals always win, and a strum is never counted as both a note and an answer.
My guitar isn't detected. If diagnose reports Joysticks found: 0,
check the dongle and pairing first. Some clones enumerate as a keyboard
rather than a gamepad; python -m vibe_hero.diagnose --keyboard (needs
pip install -e ".[keyboard]") will show you which keystrokes the frets
emit. There's no keyboard input layer yet β it's on the roadmap, and a
report of your unit helps.
Nothing happens when I strum. Confirm the layers in order:
python -m vibe_hero.guitar prints events β curl http://127.0.0.1:8787/health returns vibe hero ok β the bridge prints
β³ APPROVAL WANTED when Claude Code asks. Whichever step is silent is the
broken one.
The bridge never prints anything. PermissionRequest hooks only fire
when Claude Code actually asks for permission. In a session whose
permission mode auto-approves, the hook never runs and the bridge stays
quiet. That's expected.
The strum registers but Claude Code ignores it. Deny and ask rules in
your Claude Code settings are evaluated independently of hooks, so an
allow can't override a matching deny rule. Check your permission
settings.
Port 8787 is taken. Set VIBE_HERO_PORT for the bridge and a matching
VIBE_HERO_URL for the hook.
Vibe Hero never widens what the agent can do. It only replaces how you answer permission prompts you'd otherwise answer by keyboard β deny and ask rules in your Claude Code settings still apply and are evaluated independently.
The bridge binds to 127.0.0.1 and has no authentication, which is
appropriate for a local dev tool but worth understanding: any process
running as you on the same machine can POST to it and consume a pending
approval slot. It can't approve anything on its own β only your strum
produces a decision, and the decision goes to the hook that asked β but a
hostile local process could make your strum answer its request instead of
Claude Code's. Don't expose port 8787 beyond loopback.
Because the bridge prints the tool and its arguments before blocking, you always see what you're approving. A strum should never be blind.
- Keyboard-mode input layer for clones that enumerate as keyboards
- Whammy bar to scroll pending diffs
- Chords for batch approvals
- Sound feedback
- Configurable gesture β decision mapping in
mappings.json - Star power = enter plan mode?
Please use private vulnerability reporting rather than a
public issue. SECURITY.md also explains which parts of the codebase are
security-relevant and why.
Mappings for untested controllers are the most valuable contribution β see CONTRIBUTING.md. Reports of guitars that didn't work are welcome too.
python -m pip install -e ".[dev]"
python -m pytest