padmon #
padmon shows the state of your Claude Code sessions on a pad controller: the JamJum JP-Mini or the Arturia MiniLab mkII. You can add other controllers with a profile (see controllers/README.md). Each pad shows one session. When you push a pad, iTerm2 or Claude Desktop comes to the front and shows that session.
What padmon does #
padmon gives one pad to each Claude Code session that operates in an iTerm2 pane or in Claude Desktop. The color of the pad shows the state of the session. For the colors and the pad order of the MiniLab, see Use the Arturia MiniLab mkII.
On the JP-Mini, the pads show these colors:
| Pad color | Session state | Meaning |
|---|---|---|
| Purple | working |
Claude does work. |
| Orange | waiting |
Claude waits for you. For example, Claude asks for a permission or asks a question. The pad pulses between dim and full brightness. One pulse takes 4 seconds. All orange pads pulse at the same time. The pad does not go off. |
| Blue | background |
Claude finished its turn, but background tasks still run. For example, a subagent or a background command runs. You can talk to Claude. |
| Green | idle |
Claude finished its turn. No background tasks run. |
| Off | — | No session uses this pad. |
On the JP-Mini, the saturation of the color shows how much of the context window the session uses:
- A new session has a fully saturated color.
- When the context window fills, the color becomes paler.
- At a full context window, the color stays pale but you can still see the hue.
When you push the pad of an iTerm2 session, iTerm2 comes to the front. iTerm2 shows the window, the tab and the pane of that session.
When you push the pad of a Claude Desktop session, Claude Desktop comes to the front and opens that session. If Claude Desktop stopped the session, it starts the session again.
When you push a pad that is off, iTerm2 opens a new window with a shell. padmon keeps the pushed pad for this window for 60 seconds. Until the session starts, the pad pulses in dim white on the JP-Mini. One pulse takes 2 seconds. If you start Claude Code in the window during this time, the session gets the pushed pad. If you push the same pad again during this time, iTerm2 shows the new window again. After 60 seconds, the pad is free again. A session that starts later in the window gets the lowest free pad. If padmon cannot find the session of the new window, it does not keep the pad.
A session keeps its pad until it ends. The session also keeps its pad after /clear, /compact and resume. A new session gets the lowest free pad that padmon does not keep for a new window. On the JP-Mini, the bottom-left pad is pad 1. The top-right pad is pad 16.
Requirements #
-
A Mac with macOS.
-
iTerm2, with the Python API on.
-
Claude Desktop, if you use Claude Code in Claude Desktop.
-
A supported controller:
- JamJum JP-Mini, connected with a USB cable or over Bluetooth.
- Arturia MiniLab mkII, connected with a USB cable, with its factory preset.
When more than one is connected, padmon uses the JP-Mini.
-
Claude Code.
-
devenv, to build padmon.
Install padmon #
-
Connect the controller to the Mac.
-
In iTerm2, open Settings > General > Magic.
-
Select Enable Python API.
-
In the repository folder, enter this command:
scripts/install-macos.shThe script builds padmon. It creates the app
~/Applications/padmon.app. It also installs a launchd agent with the namedev.padmon.daemon. The agent starts the padmon daemon from~/Applications/padmon.appwhen you log in. The agent starts the daemon again if the daemon stops. -
Make sure that the daemon operates. Enter this command:
tail ~/Library/Logs/padmon.logThe log must show
listening on …andcontroller connected. -
Push a lit pad. macOS asks if padmon can control iTerm2. Click OK. padmon uses this permission to show panes and to find closed panes. macOS asks again after each update of padmon.
Connect the Claude Code hooks #
Claude Code sends each session event to padmon through a hook.
CAUTION: ~/.claude/settings.json contains your other hooks and settings. Do not replace the file. Add the padmon hook to each event list.
-
Open
~/.claude/settings.json. -
Add this hook to each of these events:
SessionStart,SessionEnd,UserPromptSubmit,PreToolUse,PostToolUse,PostToolUseFailure,PermissionRequest,Notification,Stop,StopFailureandSubagentStop.{ "hooks": [ { "type": "command", "command": "[ -x /path/to/midi-agent-control/target/release/padmon ] && /path/to/midi-agent-control/target/release/padmon hook || true", "timeout": 5 } ] } -
Replace
/path/to/midi-agent-controlwith the path of your repository folder. -
Start a new Claude Code session. Sessions that started before this change do not send events.
Claude Desktop uses the same hooks from ~/.claude/settings.json. You do not need to do more for Claude Desktop sessions.
Connect the Antigravity (agy) hooks #
Antigravity CLI (agy) sends session events to padmon through ~/.gemini/config/hooks.json.
CAUTION: If ~/.gemini/config/hooks.json already exists, add the padmon hook to each event list instead of replacing the file.
-
Open
~/.gemini/config/hooks.json. -
Add the padmon hook to
SessionStart,PreInvocation,PostInvocation,PostToolUseandStop:{ "SessionStart": [ { "type": "command", "command": "[ -x /path/to/midi-agent-control/target/release/padmon ] && /path/to/midi-agent-control/target/release/padmon hook SessionStart || echo '{}'", "timeout": 5 } ], "PreInvocation": [ { "type": "command", "command": "[ -x /path/to/midi-agent-control/target/release/padmon ] && /path/to/midi-agent-control/target/release/padmon hook PreInvocation || echo '{}'", "timeout": 5 } ], "PostInvocation": [ { "type": "command", "command": "[ -x /path/to/midi-agent-control/target/release/padmon ] && /path/to/midi-agent-control/target/release/padmon hook PostInvocation || echo '{}'", "timeout": 5 } ], "PreToolUse": [ { "matcher": "ask_question", "hooks": [ { "type": "command", "command": "[ -x /path/to/midi-agent-control/target/release/padmon ] && /path/to/midi-agent-control/target/release/padmon hook PreToolUse || echo '{\"decision\":\"allow\"}'", "timeout": 5 } ] } ], "PostToolUse": [ { "matcher": ".*", "hooks": [ { "type": "command", "command": "[ -x /path/to/midi-agent-control/target/release/padmon ] && /path/to/midi-agent-control/target/release/padmon hook PostToolUse || echo '{}'", "timeout": 5 } ] } ], "Stop": [ { "type": "command", "command": "[ -x /path/to/midi-agent-control/target/release/padmon ] && /path/to/midi-agent-control/target/release/padmon hook Stop || echo '{}'", "timeout": 5 } ] } -
Replace
/path/to/midi-agent-controlwith the path of your repository folder.
Connect the statusline #
padmon gets the context window data (and Antigravity UI waiting state) from the statusline.
For Claude Code:
-
Open your statusline command. For example, open
~/.claude/statusline-command.sh. -
Add these lines after the line that reads the input (
input=$(cat)):padmon=/path/to/midi-agent-control/target/release/padmon [ -x "$padmon" ] && printf '%s' "$input" | "$padmon" statusline 2>/dev/null -
Replace
/path/to/midi-agent-controlwith the path of your repository folder.
For Antigravity (agy):
-
Open
~/.gemini/antigravity-cli/settings.json. -
Add
statusLinewith"stack_with_default": truesoagykeeps its built-in statusline while sending updates topadmon:{ "statusLine": { "type": "command", "command": "[ -x /path/to/midi-agent-control/target/release/padmon ] && /path/to/midi-agent-control/target/release/padmon statusline 2>/dev/null || true", "stack_with_default": true } }
padmon statusline writes no output. Your statusline does not change.
Connect the controller over Bluetooth #
- Turn on the JP-Mini.
- On the Mac, open Audio MIDI Setup.
- Select Window > Show MIDI Studio.
- Click Bluetooth in the toolbar (Configure Bluetooth).
- Click Connect next to JP-Mini.
When padmon first uses the controller over Bluetooth, macOS asks if padmon can use Bluetooth. Click Allow. macOS asks again after each update of padmon. When the USB cable and Bluetooth are both connected, padmon uses USB.
Use the Arturia MiniLab mkII #
- Connect the MiniLab to the Mac with a USB data cable.
- Make sure that the MiniLab uses its factory preset.
Pads 1 to 8 are on bank 1. Pads 9 to 16 are on bank 2. A session on pad 9 to 16 shows only when bank 2 is on.
| Pad color | Session state |
|---|---|
| Magenta | working |
| Yellow, blinking | waiting. The pad is on for 1.5 seconds and off for 0.5 seconds. |
| Blue | background |
| Green | idle |
| White, blinking | A new window. The pad is on for 1 second and off for 1 second. |
| Off | No session uses this pad. |
The MiniLab does not show the context fill. A pad lights red while you push it. When you release the pad, it shows its color again.
Update padmon #
-
Get the new source code.
-
Enter this command:
scripts/install-macos.sh
The script builds padmon again and starts the new daemon.
Remove padmon #
-
Enter this command:
scripts/uninstall-macos.shThe script stops the daemon. It removes the launchd agent and
~/Applications/padmon.app. -
Remove the padmon hooks from
~/.claude/settings.json. -
Remove the padmon lines from your statusline command.
Known limits #
- Interrupts: When you push Esc, Claude Code sends no event. The pad stays purple until the next event of that session. When you refuse a permission, the pad stays orange until your next prompt.
- Banks (JP-Mini): The JP-Mini has three banks. Each bank keeps its own pad colors. padmon writes the colors to the bank that the controller shows. When you change the bank, the pads can show old colors. Push a pad to show the current colors.
- Number of sessions: padmon shows a maximum of 16 sessions. A 17th session gets a pad at its first event after a pad becomes free.
- Terminals: padmon operates only with iTerm2 and Claude Desktop on macOS.
- Claude Desktop sessions: Claude Desktop stops the Claude Code process of a session that you do not use. padmon then removes the pad within 5 minutes. The session gets a pad again at its next event.
CLAUDE_CONFIG_DIR: padmon finds running Claude Desktop sessions in~/.claude/sessions. launchd starts the daemon without your shell settings. If you setCLAUDE_CONFIG_DIRin your shell, set it also in the launchd agent, or padmon can remove the pads of running Claude Desktop sessions.- Session types: padmon shows only interactive sessions. It ignores
claude -pand SDK sessions. - Orange pulse over Bluetooth (JP-Mini): Over Bluetooth, an orange pad on the JP-Mini pulses for approximately 5 minutes. The 5 minutes start when the session starts to wait. The last pulse ends at full brightness, and then the pad stays at full brightness. This decreases the use of the battery of the controller. Over USB, the pad pulses until Claude continues.
- Background tasks: The pad stays blue while a background task runs. A long background command, for example a development server, keeps the pad blue. When a background command stops, the pad can stay blue until the next event of that session. Usually this event is the next turn of Claude.
- Permissions after an update: After each update, macOS asks again if padmon can use Bluetooth and if padmon can control iTerm2. Click Allow and OK.
- Permission changes: macOS applies a change to the Bluetooth permission only when padmon starts. After you change the permission in System Settings, restart padmon (see Problems and solutions).
- Controller name: padmon finds the controller by its name. Do not rename the controller in macOS.
Problems and solutions #
| Problem | Possible cause | Solution |
|---|---|---|
| All pads stay off. | The daemon does not operate. | Enter launchctl print gui/$(id -u)/dev.padmon.daemon. Make sure that the state is running. Examine ~/Library/Logs/padmon.log. |
| All pads stay off. | The session started before you connected the hooks. | Start a new Claude Code session. |
| All pads stay off. | The hook and the daemon use different sockets. | The log shows the socket of the daemon in the listening on … line. The default socket is ~/.padmon/padmon.sock. If you set PADMON_SOCKET, set it for the hook and for the daemon. |
The log shows controller connect failed. |
The controller is not connected. | Connect the controller. The daemon finds it in 2 seconds or less. To see the ports, enter target/release/padmon midi-monitor. |
| Pushes on the controller do nothing, but the colors are correct. | The controller does not use its factory preset. | Reset the controller to its factory preset. Use padmon midi-monitor to see the messages of the pads. |
| A pushed pad does not show the pane. | The iTerm2 Python API is off. | Do the steps in Install padmon again. |
| A pushed pad of a Claude Desktop session does nothing. | Claude Desktop does not accept claude:// links. An administrator can turn them off with the policy "Disable claude:// deep-link handling". |
Examine ~/Library/Logs/padmon.log for opening Desktop session … failed. Ask your administrator to allow claude:// links. |
The log shows another padmon daemon is running. |
A second daemon started. | Stop the second daemon. The launchd agent must be the only daemon. |
| After a Nix garbage collection, the pads do not change when a session changes. | The hooks use target/release/padmon, and this binary uses a library from /nix/store that Nix removed. The daemon is not affected: the install script changes its copy to use the macOS library. |
Enter scripts/install-macos.sh again. |
| The pads show old colors. | You changed the bank. | Push a pad. |
| On the MiniLab, some pads show no color or a wrong color. | The controller lost its colors. For example, this occurs after a short disconnection, or after the Arturia MIDI Control Center changed the colors. | Push a pad. padmon then sends all colors again. |
The log shows pruning skipped: it2 … timed out. A pushed pad shows the pane only after 5 seconds. |
padmon has no permission to control iTerm2, or the permission question is open. | Answer the open question from macOS. Or open System Settings > Privacy & Security > Automation. Below padmon.app, turn on iTerm.app. |
The log shows bluetooth output unavailable: permission denied. |
padmon has no Bluetooth permission. | Open System Settings > Privacy & Security > Bluetooth. Turn on padmon. Then restart padmon. |
The USB cable is connected, but the log does not show controller connected (jamjum-jp-mini, usb). The controller stays on Bluetooth. |
Lockdown Mode is on. macOS does not connect a new USB accessory until you allow it. Or the cable only supplies power. | Turn off the JP-Mini, connect the USB cable, then turn on the JP-Mini. When macOS asks, allow the accessory to connect. If macOS does not ask, use a USB data cable. |
| Over Bluetooth, pushed pads work but the pads show no colors. | The Bluetooth output is not ready. | Examine the log for bluetooth output unavailable. Disconnect and connect the controller in Audio MIDI Setup. |
To restart the daemon, enter this command:
launchctl kickstart -k gui/$(id -u)/dev.padmon.daemon
How padmon works #
padmon is one program with four commands:
| Command | Started by | Function |
|---|---|---|
padmon daemon |
launchd | Keeps the state of each session. Sets the pad colors. Shows the session when you push a pad. Opens a new iTerm2 window when you push a pad that is off. |
padmon hook |
Claude Code, for each hook event | Sends the event to the daemon. |
padmon statusline |
Your statusline command | Sends the context window fill to the daemon. |
padmon midi-monitor [port prefix] |
You | Lists the MIDI ports and shows the messages of the controller. |
The hook and the statusline command send one line of JSON to the daemon through a Unix socket. The default socket is ~/.padmon/padmon.sock. Only your user can use the socket. The hook and the statusline command always exit with status 0. They write nothing to stdout. They stop after 1 second if the daemon does not accept the data. Because of this, padmon cannot stop Claude Code or make it slow.
The daemon uses the iTerm2 pane ID (ITERM_SESSION_ID) to identify an iTerm2 session. The daemon uses the it2 tool of iTerm2 to select a pane. Then it uses open to bring iTerm2 to the front, because macOS does not let a background program activate iTerm2 through it2. Every 5 minutes, the daemon removes the pads of panes that iTerm2 no longer has.
For a Claude Desktop session, the daemon uses the session ID that Claude Desktop sets (CLAUDE_CODE_HOST_SESSION_ID). It opens the session with open 'claude://code/continue?session=<ID>'. Every 5 minutes, the daemon also removes the pads of Claude Desktop sessions that have no Claude Code process. It finds these processes in ~/.claude/sessions.
The JP-Mini protocol #
The JP-Mini does not change its pad colors when it gets MIDI notes. padmon uses a SysEx message that we found in the KuSuite 5.3 editor app:
- The packet is
78 59, the command51, the payload length in 3 bytes (little-endian), the payload and a checksum. The checksum is the inverted sum of the payload bytes. - The payload is
FFand 18 color triplets. Each triplet has the order green, red, blue. padmon uses triplets 1 to 16. - The SysEx message is
F0, the packet as a 7-bit bitstream (lowest bit first) andF7.
Over Bluetooth, macOS connects the controller as a MIDI device. padmon reads the pushed pads from this MIDI port. padmon sends the pad colors through a separate Bluetooth service of the controller (AE40, characteristic AE41), as the same SysEx message in parts of 20 bytes.
The pads send note_on on MIDI channel 10. The notes are 36 to 51 or 52 to 67, as set by the bank. Pad 1 sends note 36 or note 52. jpmini.py has a Python version of the protocol.
Development #
-
Install devenv.
-
To do the tests, enter this command:
devenv shell -- cargo test -
To examine the code, enter this command:
devenv shell -- cargo clippy --all-targets -- -D warnings
The environment also supports a Linux build. On Linux, padmon uses ALSA for MIDI. The iTerm2 part operates only on macOS.
These documents give more data:
- The specification: docs/specs/archive/2026-09-23-pad-session-monitor.md
- The implementation plan: docs/plans/archive/2026-09-23-pad-session-monitor.md
- The Bluetooth specification: docs/specs/archive/2026-09-24-bluetooth-controller.md
- The Bluetooth implementation plan: docs/plans/archive/2026-09-25-bluetooth-controller.md
- The background state specification: docs/specs/archive/2026-09-25-background-state.md
- The new window specification: docs/specs/archive/2026-09-25-new-window.md
- The controller profiles specification: docs/specs/archive/2026-09-26-controller-profiles.md
- The controller profiles implementation plans: plan A and plan B
- The change log: CHANGELOG.md
License #
padmon is available under the MIT License. See LICENSE.
- Rust 96%
- Shell 2%
- Python 2%
- Nix <1%