SLOPSHOPPER

bang-prefill

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

newpromptprocess
★ 1v0.1.0MITupdated 2026-10-21MaximusPokeZ/bang-prefill
A shopper browsing a rack in a slop shop
README

bang-prefill

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">

Why

<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.

How it works

  1. Claude writes a line that is a whole ! <command>: as inline code or inside a code block.
  2. When the turn ends and your prompt box is empty, the mod fills it with that command. If there are several, the last one wins.
  3. You press Enter, or edit the command first.
  4. The mod runs it with 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.

Requirements

  • Claude Code 2.1.303 or later, in a terminal session. Tested on 2.1.303. bang-prefill is a hooks module ("mod"), an early-access plugin API that may change between releases.
  • macOS or Linux with /bin/zsh.

Install

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.

Limitations

  • Interactive commands do not work: the command gets no TTY.
  • A command is killed after 10 minutes.
  • It runs /bin/zsh -lc, so aliases and functions from ~/.zshrc are not loaded.
  • Any prompt you send that starts with ! (bang, space) is run as a shell command, whether or not the mod filled it in.

Development

claude plugin validate .
claude plugin test .
python3 -I scripts/gen-assets.py assets   # regenerate the README images

License

MIT

Source 1 files
hooks/register.ts 46 lines
1import 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