TL;DR On 2.1.287 or later, ask for
make a mod that shows the current git branch above the prompt, pick Enable for this session, and it loads when the turn ends. Before installing someone else's,claude plugin validatelists what it touches.
A mod is a Claude Code plugin written in JavaScript or TypeScript whose functions run inside Claude Code. It can draw a line above the prompt or a pane beside the transcript, add a command that answers on the spot without spending a Claude turn, and hold, rewrite or answer a tool call before it runs. It works in the terminal from 2.1.287 and in the Desktop Code tab from 2.1.286.
If you use Claude Code as your harness and like to tinker, this is the dream: with TypeScript you can add buttons, panes, views and animations, and catch commands and subagents as they happen. The docs are long and on the abstract side, and their examples are deliberately small. So I tried it on something real: a status bar for craft, my development plugin.
Where a mod fits
| What it is | Where it runs | |
|---|---|---|
| Settings hook | A shell command, HTTP request or prompt fired on an event | Outside Claude Code |
| Skill | A markdown file of instructions | In what Claude reads |
| MCP | A server that gives Claude tools | Outside, as a separate process or service |
| Mod | Functions Claude Code calls on each event | Inside Claude Code |
Because it lives inside, each of a mod's hooks decides what happens to its event: it observes and lets it through, rewrites it before it continues, or answers it so the usual behavior never runs. For the other mechanisms, the 6 extension points sorts them by the question each one answers.
Ask Claude for a mod
1. Check your version
claude --version
2. Describe it in one sentence
make a mod that shows the current git branch above the prompt
Claude works from the built-in plugin-authoring skill (you can load it yourself with /plugin-authoring) and writes the mod under ~/.claude/dev-mods/<session-id>/. In default and acceptEdits modes it asks you to approve each file, since ~/.claude is a protected path.
3. Turn on hot reloading
When Claude saves the first file, Claude Code asks whether to enable hot reloading for the session. Pick Enable for this session: the mod loads when the turn ends and reloads each time Claude changes it. You'll find it in /plugin, on the Installed tab.
4. Keep it
The session's folder gets cleaned up over time. Copy the mod out and load it wherever you want:
claude --plugin-dir ~/mods/git-branch
This flow is interactive and I didn't capture it here: the steps and names come from the official guide.
A real one: the craft bar
Craft runs subagents that build in phases, and I leave them working. I wanted to see at a glance whether anything was moving, without asking Claude and without spending a turn. The design is one line above the prompt with the work, its phase, a square per slice and the agent running right now:
craft retry · implement · ■■■▣▣□ 3/6 · ⠋ draw 2:14 +1
It's five hooks in a single file, hooks/register.ts, declared in hooks/hooks.json as { "modules": ["./register.ts"] }:
command.runon/^craft:/records the phase when I run/craft:implement, then lets the skill run as usual.agent.spawnon/^craft:/adds the agent to the line, andturn.completewith the sameagentIdremoves it and marks its slice as built.ui.renderonAbovePromptdraws the line withBoxandText, on top of whatever Claude Code already draws there./craft:bar offis answered by the mod itself, with no Claude turn, and the choice is kept in$.storefor every session.session.endclears the state when the session closes.
on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
const line = pieces(state, await $.clock.now())
if (e.props.hasSurvey || line.length === 0 || (await hidden($))) return next(e)
const { Box, Text } = $.ui.resolve(e)
const below = await next(e)
const texts = line.map(piece => Text({ ...STYLES[piece.tone], children: piece.text }))
return Box({ flexDirection: 'column', children: [Box({ key: BAR, children: Text({ children: texts }) }), below] })
})
The first live try, on 2.1.292, showed this while craft built four slices:
craft gamma · implement · □□□□ 0/4
The work, the phase, the four squares, /craft:bar off and on, and staying out of the way of Claude Code's survey all passed. Three things failed: every agent showed up as build, because the slice name sits further down its prompt; two agents spawned in the same tenth of a second overwrote each other, so the +1 never appeared; and the squares never turned green, because the first version matched them by their check command and craft rewrites those commands. The takeaway: events tell you for certain when a subagent starts and ends; what's inside its prompt, they don't. The bar still lives on a working branch, not in the published craft.
Before you install someone else's
A mod isn't sandboxed: it runs with your permissions, can read your secrets, sees every prompt and can approve tool calls for you. claude plugin validate lists the events it handles and the calls it makes, without running it. This is the real output for the craft bar:
❯ ./register.ts hooks: command.run{command=/"^craft:"/}, agent.spawn{subagentType=/"^craft:"/}, turn.complete, session.end, ui.render{component=AbovePrompt}
❯ ./register.ts calls: $.clock.every (via tick), $.clock.now, $.fs.list (via folders), $.fs.read (via readSlices), $.session.root (via folders, readSlices), $.store.get (via hidden), $.store.set (via answerBar), $.ui.invalidate, $.ui.resolve
✔ Validation passed
Two more things: in the VS Code extension's chat panel and in claude -p, hooks run but nothing gets drawn. And /diff, which you may already use, is a built-in mod: cc-plugin-diff.
Official docs: Mods overview