Puts the `! <command>` Claude suggests into your prompt box: press Enter to run it, and Claude sees the output and keeps going

Stop copy-pasting the commands Claude asks you to run.
When Claude Code suggests a ! <command>, this mod puts it straight into your prompt box. Press Enter: the command runs, Claude sees the output and keeps working.
<img src="assets/demo.svg" alt="Claude suggests a command, it appears in the prompt box, Enter runs it, Claude answers the output" width="820">
<img src="assets/why.svg" alt="Five manual steps without the mod, one Enter with it" width="820">
Claude often needs something only you can run: a login, a check on your machine, a command outside its sandbox. Normally you select it, copy it, paste it, strip the backticks, run it, and then tell Claude to look at the output. With bang-prefill it is one keystroke, and Claude picks up the result on its own.
! <command>: as inline code or inside a code block.zsh -lc and sends Claude the stdout, stderr and exit code in place of the ! <command> text.Mentions inside a sentence are ignored, and text you have already typed is never overwritten.
/bin/zsh.At the Claude Code prompt:
/plugin install bang-prefill --marketplace MaximusPokeZ/bang-prefill
Answer y to add the marketplace, then pick a scope.
From a shell:
claude plugin install bang-prefill --marketplace MaximusPokeZ/bang-prefill
In the desktop app: click + next to the prompt box, then Plugins, Add marketplace with MaximusPokeZ/bang-prefill, then Add plugin.
/bin/zsh -lc, so aliases and functions from ~/.zshrc are not loaded.! (bang, space) is run as a shell command, whether or not the mod filled it in.claude plugin validate .
claude plugin test .
python3 -I scripts/gen-assets.py assets # regenerate the README images
hooks/register.ts 46 lines1import type { Register } from 'claude-code'
2
3// A command is a whole line of the answer: `! cmd`, or `! cmd` inside a code block; the last one wins.
4// Mentions inside a sentence (diagrams, examples) do not count.
5const WHOLE_LINE = /^[ \t]*`?(! [^`\n]+?)`?[ \t]*$/
6
7export function lastBangCommand(answer: string): string | undefined {
8 let cmd: string | undefined
9 for (const line of answer.split('\n')) cmd = line.match(WHOLE_LINE)?.[1].trim() ?? cmd
10 return cmd
11}
12
13export const register: Register = on => {
14 on('turn.complete', async ($, e, next) => {
15 const result = await next(e)
16 if (e.agentId !== undefined || e.reason !== 'answer') return result
17
18 const cmd = lastBangCommand(e.answer)
19 if (!cmd) return result
20
21 // Never overwrite what the person has already started typing
22 const box = await $.prompt.read()
23 if (box.text.trim() === '') await $.prompt.fill({ text: cmd, mode: 'replace' })
24
25 return result
26 })
27
28 // prompt.fill does not switch the prompt box into bash mode: `! cmd` would go out as a plain prompt.
29 // Intercept such a prompt, run the command here and hand its output to the model.
30 on('prompt.submit', async ($, e, next) => {
31 if (e.origin.kind !== 'composer' || e.command || !e.text.startsWith('! ')) return next(e)
32
33 const cmd = e.text.slice(2).trim()
34 const ran = await $.process
35 .run(['/bin/zsh', '-lc', cmd], { timeoutMs: 600_000 })
36 .catch(err => ({ exitCode: 1, stdout: '', stderr: String(err) }))
37
38 // The model gets the output instead of `! cmd`, so it sees the result and keeps working
39 const exit = ran.exitCode ? `\n<exit-code>${ran.exitCode}</exit-code>` : ''
40 return next({
41 ...e,
42 text: `<bash-input>${cmd}</bash-input>\n<bash-stdout>${ran.stdout}</bash-stdout><bash-stderr>${ran.stderr}</bash-stderr>${exit}`,
43 })
44 }).catch(($, e, next) => (next.called ? next(e) : { drop: 'bang-prefill: could not run the command' }))
45}
46