These are my dotfiles. There are many like them, but these ones are mine.

hooks/register.ts 26 lines1import type { Register } from 'claude-code'
2
3// Claude Code wraps a paste as `\n\n<pasted_content id="c919">\n...\n</pasted_content id="c919">\n`.
4const PASTE = /\n*<pasted_content id="(\w+)">\n([\s\S]*?)\n<\/pasted_content id="\1">\n*/g
5const COMMAND = /^\/([\w:-]+)(?:\s+([\s\S]*))?$/
6
7export const register: Register = on => {
8 on('prompt.submit', async ($, e, next) => {
9 if (e.command) return next(e)
10 const text = e.text.replace(PASTE, '\n$2\n').trim()
11 if (text === e.text.trim()) return next(e)
12
13 // The engine finds slash commands before this event, so a rewritten `/name args` is plain text.
14 const [, name, args = ''] = COMMAND.exec(text) ?? []
15 if (name && (await $.command.list()).some(c => c.name === name)) {
16 // The host refuses `$.command.run` inside this hook, and a dropped prompt starts no turn.
17 $.clock.after(0, async () => {
18 await $.prompt.fill({ text: '' })
19 await $.command.run({ command: name, args }).catch(err => $.ui.toast(`/${name} failed: ${err}`))
20 })
21 return { drop: `runs /${name}` }
22 }
23 return next({ ...e, text })
24 })
25}
26