spacehub

surshar / claude-code

Claude Code is an agentic coding tool that lives in your terminal, understands your codebase, and helps you code faster by executing routine tasks, explaining complex code, and handling git workflows - all through natural language commands.

Built by Anthropic · github.com/anthropics

claude-code / mods / README.md
116 lines · 5.7 KBPreviewCodeRaw
1# Mods
2
3A mod is a Claude Code plugin whose behaviour lives in a hooks module: one
4`register(on, options)` entry that hooks the engine's events as functions
5`($, e, next)`. These three ship inside Claude Code; this folder is their
6source, published as it is built into the binary.
7
8| Mod | What it does | Seated |
9| --- | --- | --- |
10| [`sec-default`](sec-default) | Keeps an organization's classic hooks, prompt content, managed settings and tool policy out of reach of the plugins a person installs; adds no policy of its own. | Outermost, on a machine with managed settings or for a Team or Enterprise organization, unless managed `prependPlugins` says otherwise |
11| [`diff`](diff) | `/diff`: the session's uncommitted changes in a pane beside the transcript, file by file with their hunks, refreshed as Claude edits files and runs commands. | Built in |
12| [`telemetry`](telemetry) | Adds `$.telemetry` (`log`, `mark`) in the `engine.create` fold so a plugin can record an event as a first-party analytics row; sends nothing wherever Claude Code's analytics are off. | Built in |
13
14Each folder is a complete plugin: `.claude-plugin/plugin.json`, a
15`hooks/hooks.json` naming the module, and TypeScript under `hooks/` typed
16against the declarations `/plugin-types` writes (`import type … from
17'claude-code'`), kept here in `types/`. To read one running from source:
18
19 claude --plugin-dir mods/diff
20
21## Testing
22
23A mod's tests are in its `tests/` folder, and run with
24
25 claude plugin test mods/diff
26
27A test gets the engine's own `$` and a plugin's `on`. Each call on `$` is one
28the engine makes, through every hook of the mod loaded as it ships. The hooks
29the test registers with `on` sit beneath the mod, where the rest of the world
30would be, and nothing is beneath them: a call they leave unanswered throws,
31naming its event.
32
33A test file is named for what it covers under `hooks/` (`register.test.ts`
34beside `hooks/register.ts`, `git.test.ts` beside `hooks/git/`), and holds its
35imports, the tier the mod loads in, and one `describe` titled with that name;
36what several tests share sits under `tests/fixtures/`, one export a file.
37
38```ts
39import { describe, expect, mock, test, tier } from 'claude-code/testing'
40
41tier('builtin')
42
43describe('register', () => {
44 test('outside a git repository /diff says so, opens nothing', async ($, on) => {
45 const opened: string[] = []
46 mock.clock(on)
47 on('session.start', ($, e) => ({ cwd: e.cwd }))
48 on('command.register', ($, e) => ({ value: { command: e.name } }))
49 on('process.run', () => ({
50 value: { exitCode: 128, stdout: '', stderr: 'fatal: not a git repository' },
51 }))
52 on('ui.open', ($, e, next) => {
53 opened.push(e.id)
54 return next(e)
55 })
56
57 await $.session.start({ surface: 'terminal', isInteractive: true, cwd: '/work' })
58 const { text } = await $.command.run({
59 command: 'diff',
60 args: '',
61 origin: { kind: 'composer' },
62 })
63
64 expect(text).toContain("isn't in a git repository")
65 expect(opened).toEqual([])
66 })
67})
68```
69
70The kit's `mock` answers the world beneath the mod from memory, noun by noun,
71each member a plain function over `on` registering hooks where the test calls
72it: `mock.env(on, variables)` for `$.env`, `mock.store(on, entries)` for
73`$.store`, and `mock.clock(on)` for `$.clock`, whose `advance(ms)` resolves
74every wait the mod asked for (`$.clock.sleep`, `after`, `every`) as the clock
75crosses it. To see what a dispatch does before it answers, start it
76unawaited, `await clock.settle()`, then look: the clock stays where it was.
77`$.ui.press({ plugin, key })` presses a `Button` the test rendered, as a
78click in the terminal does.
79
80`tsc -p mods/tsconfig.json` typechecks every mod's hooks and tests against
81`types/` and each mod's own `types/` contract.
82
83## Composing mods: noun contracts
84
85A mod that adds a noun to `$` in the `engine.create` fold owns that noun's
86types, and keeps them in one place: its `types/index.d.ts`, a declaration
87file with no imports that exports the types the noun is made of, each named
88for the noun, and declares the noun on `EngineInterface` in `claude-code`
89(`telemetry/types/index.d.ts` exports `Telemetry`, `TelemetryLogEntry`,
90`TelemetryMarkEntry` and the rest, and declares `$.telemetry`).
91
92- The contract is the only declaration of the noun. The mod's own hooks
93 import its types from the folder (`import type { Telemetry } from
94 '../types'`), and the value its `engine.create` hook returns is checked
95 against `EngineInterface['telemetry']`, so the implementation cannot drift
96 from what callers read.
97- A mod that calls another's noun reads the same file and never copies it:
98 `mods/tsconfig.json` includes `*/types/**/*.d.ts`, so `$.telemetry.log(…)`
99 in `diff` types against `telemetry`'s contract as it stands, and a helper
100 that must name one of its types imports it from that folder by path.
101- A test of a mod that calls another's noun seats a provider for it, an inline
102 plugin whose `engine.create` hook adds the noun, and answers the calls the
103 way it answers the engine's: `on('telemetry.log', ($, e) => ({ value:
104 undefined }))` runs above the provider's own method, its `e` typed by the
105 contract. With no provider loaded the `$` build refuses the hook, naming the
106 noun nobody provides.
107
108A plugin outside this repository that depends on a mod's noun points its
109tsconfig `include` at that mod's `types/` folder for now; once the engine
110writes the contracts of the plugins a session has installed, `/plugin-types`
111will put them beside `claude-code.d.ts` and the include goes away.
112
113Early access: hooks modules load only where function hooks are enabled, and
114the API these mods are written against may change between releases without
115notice. They are not listed in this repository's marketplace; the copies that
116matter are the ones already in your Claude Code.