A minimal source for the HUD: one slot, a counter. Copy it and change what it sends.

The smallest working source for the HUD: it declares one slot, Ticks, and sends it a count every 5 seconds. The guide is SOURCES.md; this is the code beside it.
.claude-plugin/plugin.json lists hud under dependencies. Without the HUD this plugin doesn't load.hooks/register.ts is the whole source: a session.start hook that declares, then arms a $.clock.every that pushes. Every $.hud call is caught, and a refused declaration leaves it quiet.hooks/source.test.ts loads that register as an inline plugin beside the HUD and checks that the slot arrives under the sender's name, that a refused push doesn't stop it, and that a refused declaration leaves it quiet.plugin.json. The name is how the person sees and mutes you, and with a slot's id it is what Claude sees of you (source-template/ticks, in the note beside the person's messages), so keep both plain and short.register.ts, change the declaration: your name, an access line that says what your plugin reads, and your slots (an id, a pattern and a title each).push to send what it learned. When it has nothing to say, $.hud.clear({ id }).register. The test loads it inline, which keeps only that function's text.claude plugin validate examples/source-template from the repository root: the manifest, the hooks and the calls.claude plugin test . from the repository root runs the test with the rest of the suite. It needs the repository root because the HUD is the plugin under test: run from this folder alone, claude plugin test loads no HUD, and there is no $.hud. To test your own source the same way, put its folder under examples/ in a checkout of this repository, with a test like this one, and run it there.Don't install this plugin for its own sake: it puts a counter in your HUD.
hooks/register.ts 31 lines1import type { Register } from 'claude-code'
2
3// A source for the HUD (SOURCES.md): it says once what it is, then sends one slot every 5 seconds. Change the
4// declaration, the slot and what arms the clock to send what your plugin knows.
5export const register: Register = on => {
6 on('session.start', async ($, e, next) => {
7 // Every $.hud call is caught. A refusal is an answer ({ ok: false, why }), and a fault must not reach this hook.
8 let declared = false
9 try {
10 declared = (await $.hud.declare({
11 v: 1,
12 name: 'Source template',
13 access: 'Counts in its own memory. Reads nothing.', // shown to the person as written: say what your plugin reads
14 slots: [{ id: 'ticks', pattern: 'stat', title: 'Ticks' }],
15 })).ok
16 } catch { /* the HUD isn't answering: send nothing */ }
17
18 // Only a declared source can push, so a refused declaration leaves nothing to send.
19 if (declared) {
20 let n = 0 // a reload starts the count again; what must outlive one belongs in $.state
21 $.clock.every(5000, async () => {
22 n += 1
23 try {
24 await $.hud.push({ v: 1, id: 'ticks', pattern: 'stat', title: 'Ticks', value: n, note: 'one every 5 seconds' })
25 } catch { /* the next tick sends the count again */ }
26 })
27 }
28 return next(e)
29 })
30}
31