Counts tool calls, shows a band above the prompt, and adds /tally plus a ping tool

Sample mod. Counts tool calls, draws a band above the composer, adds /tally, and registers a ping tool.
How mods load, consent, and the $ API: docs/agent/MODS.md (한국어).
first-mod/
├── .claude-plugin/plugin.json
└── hooks/
├── hooks.json
└── register.js
~/.pawn/mods/first-mod.plugin.json version.· tool calls: N…./tally. The model can call mcp__first-mod__ping (it returns pong). Reset and the note field use ui.press and ui.input.hooks/register.js 72 lines1// The count, shared by the hooks below
2let calls = 0
3let note = ''
4
5// Pawn / Claude Code calls this once when the mod loads
6export function register(on) {
7 // Runs when the session starts, before your first prompt
8 on('session.start', async ($, e, next) => {
9 await $.command.register({
10 name: 'tally',
11 description: 'Show how many tool calls the agent has made'
12 })
13 await $.tool.register({
14 name: 'ping',
15 description: 'Reply pong. Use when the user asks this mod to ping.',
16 inputSchema: { type: 'object', properties: {} },
17 handler: async () => 'pong'
18 })
19 $.ui.invalidate('ui.render')
20 return next(e)
21 })
22
23 // Runs each time the agent is about to use a tool
24 on('tool.call', async ($, e, next) => {
25 calls += 1
26 $.ui.invalidate('ui.render')
27 return next(e)
28 })
29
30 // Runs when you type /tally
31 on('command.run', { command: 'tally' }, async () => {
32 return { text: 'The agent has made ' + calls + ' tool calls since this mod loaded' + (note ? ' · note: ' + note : '') }
33 })
34
35 // Interactive band above the composer
36 on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
37 const el = $.ui.resolve(e)
38 return next({
39 ...e,
40 props: {
41 ...e.props,
42 plugin: $.plugin.name,
43 tree: el.Box({
44 children: [
45 el.Text({ text: 'first-mod · tool calls: ' + calls + (note ? ' · ' + note : '') }),
46 el.Button({ id: 'reset-tally', text: 'Reset count' }),
47 el.Input({ id: 'note', placeholder: 'Optional note…', value: note })
48 ]
49 })
50 }
51 })
52 })
53
54 on('ui.press', { id: 'reset-tally' }, async ($, e) => {
55 calls = 0
56 $.ui.toast('Tally reset')
57 $.ui.invalidate('ui.render')
58 return e
59 })
60
61 on('ui.input', { id: 'note' }, async ($, e) => {
62 note = String(e.value || '')
63 $.ui.invalidate('ui.render')
64 return e
65 })
66
67 // Runs each time the spinner is drawn
68 on('ui.render', { component: 'Spinner' }, async ($, e, next) => {
69 return next({ ...e, props: { ...e.props, suffix: ' · tool calls: ' + calls + '…' } })
70 })
71}
72