Ctrl+P rewrites the draft in the prompt box before you send it

Claude Code mods I use daily.
rewrite-prompt: press Ctrl+P and the draft in the prompt box is rewritten in place by a model (Sonnet, on your plan). Nothing is sent until you press Enter, and Ctrl+_ undoes it.write-for-me: press Ctrl+N and the next prompt is written for you from the last 12 messages of the conversation. If the box has text, it is used as a hint ("tests too") and expanded. Nothing is sent, and Ctrl+_ undoes it.prompt-box: the prompts you type are drawn in the scrollback inside a thick cyan box instead of one > text line. Task notifications and messages from other sessions keep their normal rows. No key binding needed.tool-oneline: each tool call is drawn as one line (⏺ Bash(echo hello) (ctrl+o to expand)) with no result block under it. Set detail to runs for one line per run of calls (⏺ Ran 3 tools: Bash ×2, Read). Ctrl+O still shows the full rows. A failed call keeps its error text.claude plugin marketplace add ekinertac/claude-code-mods
claude plugin install rewrite-prompt@ekinertac-mods
claude plugin install write-for-me@ekinertac-mods
claude plugin install prompt-box@ekinertac-mods
claude plugin install tool-oneline@ekinertac-mods
Then bind the keys. Add these to the Global block of ~/.claude/keybindings.json and start a new session:
"ctrl+p": "app:toggleDiffNoiseFilter",
"ctrl+n": "app:toggleDiffPreSession"
To use the runs level, add this to ~/.claude/settings.json:
"pluginConfigs": { "tool-oneline": { "options": { "detail": "runs" } } }
No keybinding action runs a mod command. The mod draws a zero-size button above the prompt whose action is app:toggleDiffNoiseFilter (inert outside the diff panel; write-for-me uses app:toggleDiffPreSession the same way, so the two never share an action), and the chord you bind to that action presses it. Nothing shows on screen.
claude plugin test <mod> (stubs the model, no network).hooks/register.js 71 lines1// rewrite-prompt: Ctrl+P rewrites the draft in the prompt box, in place, without sending it.
2//
3// Why a Button: no keybinding action runs a mod command. A Button's `action` is pressed by
4// whatever chord the person bound to that engine action, so keybindings.json maps ctrl+p to
5// app:toggleDiffNoiseFilter (inert outside the diff panel) and this Button answers it.
6// Setup: keybindings.json (Global: "ctrl+p": "app:toggleDiffNoiseFilter") and
7// CLAUDE_CODE_PLUGIN_DIRS in settings.json pointing at this folder.
8// Depends on: $.prompt.read/fill, $.model.complete (uses the session's plan), $.ui.
9// Constraint: the model reply replaces the draft, so the original is lost; ctrl+_ (chat:undo) restores it.
10
11const SYSTEM =
12 "Rewrite the user's draft as a prompt for a coding agent. Keep the intent, the language and " +
13 'every concrete detail (paths, names, numbers, flags). Make it clearer and more specific and ' +
14 'cut filler. Do not add requirements the draft does not state. Reply with the rewritten prompt only.'
15
16// One rewrite at a time, so a second press can't overwrite the draft with a stale answer.
17// Declared at the top level because `claude plugin validate` only follows `$` into such functions.
18let busy = false
19
20async function rewrite($) {
21 if (busy) return
22 const { text } = await $.prompt.read()
23 if (!text.trim()) {
24 $.ui.toast('rewrite: the prompt is empty')
25 return
26 }
27 busy = true
28 $.ui.toast('rewriting…')
29 try {
30 const r = await $.model.complete({
31 model: 'sonnet',
32 system: SYSTEM,
33 prompt: text,
34 maxTokens: 2048,
35 timeoutMs: 60000,
36 })
37 if (!r.isAnswered) {
38 $.ui.toast('rewrite failed: ' + r.reason)
39 return
40 }
41 await $.prompt.fill({ text: r.text.trim(), mode: 'replace' })
42 } finally {
43 busy = false
44 }
45}
46
47export function register(on) {
48 on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
49 const { Box, Button } = $.ui.resolve(e)
50 // Keep what other mods draw in the band
51 const rest = await next(e)
52 // Zero-size and out of the layout: the Button must be mounted for its chord to fire, but it
53 // should not take a row or show a label
54 const mine = Box({
55 position: 'absolute',
56 width: 0,
57 height: 0,
58 children: [
59 Button({
60 key: 'rewrite',
61 label: ' ',
62 plain: true,
63 action: 'app:toggleDiffNoiseFilter',
64 onPress: () => rewrite($),
65 }),
66 ],
67 })
68 return Box({ flexDirection: 'column', children: rest ? [rest, mine] : [mine] })
69 })
70}
71