Ctrl+N writes your next prompt from the conversation so far; a draft in the box is used as a hint

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 87 lines1// write-for-me: Ctrl+N writes the next prompt for you, from the conversation so far, into the prompt box.
2//
3// Empty box: the model proposes the most likely next message. Text in the box: it is treated as a
4// hint ("tests too", "do the second option") and expanded into a full prompt using the conversation.
5// Nothing is sent; the result lands in the box for editing, and ctrl+_ (chat:undo) restores the hint.
6//
7// Why a Button: no keybinding action runs a mod command. A Button's `action` is pressed by whatever
8// chord the person bound to that engine action, so keybindings.json maps ctrl+n to
9// app:toggleDiffPreSession (inert outside the diff panel) and this Button answers it. rewrite-prompt
10// uses app:toggleDiffNoiseFilter the same way; the two must not share an action.
11// Setup: keybindings.json (Global: "ctrl+n": "app:toggleDiffPreSession") and the plugin loaded.
12// Depends on: $.session.messages, $.prompt.read/fill, $.model.complete (the session's plan), $.ui.
13
14const SYSTEM =
15 'You write the next message a developer will send to their coding agent, in their voice, ' +
16 'from the conversation so far. Pick the single most useful next step the conversation points to ' +
17 '(a follow-up, a fix for what just failed, the next item of a plan). Be specific: reuse the ' +
18 'paths, names and numbers already in the conversation, in the language the developer writes in. ' +
19 'If a hint is given, it is what the developer wants; expand it into a full prompt and add nothing ' +
20 'it does not imply. Never invent requirements. Reply with the message text only, no quotes, no preamble.'
21
22const MESSAGES = 12 // recent turns are enough to know the next step; older ones only cost tokens
23const PER_MESSAGE = 1500 // chars; keeps one huge tool dump from crowding out the rest
24
25// One write at a time, so a second press can't fill the box with a stale answer.
26// Declared at the top level because `claude plugin validate` only follows `$` into such functions.
27let busy = false
28
29function transcript(messages) {
30 return messages
31 .slice(-MESSAGES)
32 .map(m => `${m.role === 'user' ? 'Developer' : 'Agent'}: ${(m.text || '').slice(0, PER_MESSAGE)}`)
33 .join('\n\n')
34}
35
36async function write($) {
37 if (busy) return
38 const [{ text: hint }, messages] = await Promise.all([$.prompt.read(), $.session.messages()])
39 if (!messages.length) {
40 $.ui.toast('write-for-me: nothing to go on yet, start the conversation first')
41 return
42 }
43 busy = true
44 $.ui.toast('writing…')
45 try {
46 const r = await $.model.complete({
47 model: 'sonnet',
48 system: SYSTEM,
49 prompt: transcript(messages) + (hint.trim() ? `\n\nHint from the developer: ${hint.trim()}` : ''),
50 maxTokens: 1024,
51 timeoutMs: 60000,
52 })
53 if (!r.isAnswered) {
54 $.ui.toast('write-for-me failed: ' + r.reason)
55 return
56 }
57 await $.prompt.fill({ text: r.text.trim(), mode: 'replace' })
58 } finally {
59 busy = false
60 }
61}
62
63export function register(on) {
64 on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
65 const { Box, Button } = $.ui.resolve(e)
66 // Keep what other mods draw in the band
67 const rest = await next(e)
68 // Zero-size and out of the layout: the Button must be mounted for its chord to fire, but it
69 // should not take a row or show a label
70 const mine = Box({
71 position: 'absolute',
72 width: 0,
73 height: 0,
74 children: [
75 Button({
76 key: 'write',
77 label: ' ',
78 plain: true,
79 action: 'app:toggleDiffPreSession',
80 onPress: () => write($),
81 }),
82 ],
83 })
84 return Box({ flexDirection: 'column', children: rest ? [rest, mine] : [mine] })
85 })
86}
87