jevhooks.git / plugin / README.md
1# Chapter 1: plugin, what Claude Code loads
2
3Claude Code does a lot on its own: it reads files, edits them, runs shell
4commands, and decides when it is done. At each of those moments it raises
5an **event** (`PreToolUse` just before a tool runs, `Stop` just before a
6turn ends, thirty-three kinds in all) and lets plugins have a say. A plugin
7is a folder Claude Code is pointed at. This is that folder: it is the whole
8of jevhooks as far as Claude Code can tell.
9
10It brings two things.
11
12- **A mod** (`hooks/`, chapter 3): TypeScript that Claude Code runs inside
13  its own process, with a function for each event it cares about. It is
14  where a command is held, judged, and let go or put to you.
15- **An MCP server** (`.mcp.json`): a program Claude Code starts once per
16  session. MCP, the Model Context Protocol, is how a program offers tools
17  to the assistant. This one offers two read-only ones (`status` and
18  `recent`), but its real job is to make sure the daemon is running before
19  the first command needs it (chapter 11).
20
21```mermaid
22flowchart LR
23  CC["Claude Code session"]
24  subgraph plugin["plugin/"]
25    manifest[".claude-plugin/plugin.json<br/>the name"]
26    hooks["hooks/register.tsx<br/>the mod"]
27    mcp[".mcp.json<br/>starts bin/jevhooks mcp"]
28  end
29  daemon["jevhooks serve<br/>one per machine"]
30  CC -- "reads" --> manifest
31  CC -- "runs in-process" --> hooks
32  CC -- "starts per session" --> mcp
33  mcp -- "makes sure it runs" --> daemon
34  hooks -- "HTTP on a Unix socket" --> daemon
35```
36
37Notice that the mod does not go through the MCP server. It talks to the
38daemon directly, over a Unix socket, in a few milliseconds. The MCP server
39is the doorman, not the hallway.
40
41> **Aside: why a mod and not a settings hook?** Claude Code can also run a
42> shell command for each event, configured in your settings. That starts a
43> new process every time, and every Bash command would pay for one. A mod
44> is already loaded: a plainly read-only command is waved through in well
45> under a millisecond, without leaving Claude Code at all. And a mod can
46> draw: the band above the prompt is one.
47
48> **Try it.** `claude plugin validate plugin` reads this folder the way
49> Claude Code does and tells you what it found: the hooks the mod
50> registers (`session.start`, `command.run{command=jev}`,
51> `classic.PreToolUse`, `classic.Stop`, `classic.*`,
52> `ui.render{component=AbovePrompt}`), every host call it makes, and the
53> one piece of state it keeps. It needs no Rust and no daemon.
54
55## For the people who maintain it
56
57To run a session with it: `claude --plugin-dir ./plugin` from the
58repository root. The MCP server is `bin/jevhooks`, which is not committed:
59`just daemon` builds it and copies it here (chapter 11). Without it the mod
60still loads, finds no daemon, and passes on everything.
61
62`tsconfig.json` extends `.claude-plugin/types/tsconfig.json`, which Claude
63Code lays down itself (it is gitignored, so it appears only once the engine
64has loaded the plugin), and adds `allowImportingTsExtensions`, because a
65mod's imports name the `.ts` file.
66
67### In this folder
68
69| Path | What |
70| --- | --- |
71| [.claude-plugin/](.claude-plugin/) | Chapter 2: the manifest. |
72| [hooks/](hooks/) | Chapter 3: the mod, its bridge to the Rust, and the generated JavaScript. |
73| [types/](types/) | Chapter 4: the plugin's state, typed. |
74| [tests/](tests/) | Chapter 5: the plugin's tests, against a stand-in daemon. |
75| [.mcp.json](.mcp.json) | Starts `${CLAUDE_PLUGIN_ROOT}/bin/jevhooks mcp` once per session. |
76| [tsconfig.json](tsconfig.json) | For `tsc -p plugin`: the engine's config plus `.ts` imports. |
77| `bin/jevhooks` | Built by `just daemon`; not committed. |
78
79← Previous: [Prologue](../) · Up: [jevhooks](../) · Next: [Chapter 2, .claude-plugin/](.claude-plugin/) →