Park side-ideas mid-task with /idea, without Claude seeing them; list them with /ideas and send one later as a prompt.

Four mods for Claude Code:
Type these at the prompt of a Claude Code session in a terminal:
/plugin install prompt-enhancer --marketplace aravindsriraj/claude-code-mods
/plugin install design-picker --marketplace aravindsriraj/claude-code-mods
/plugin install assumption-tracker --marketplace aravindsriraj/claude-code-mods
/plugin install idea-shelf --marketplace aravindsriraj/claude-code-mods
The first time, Claude Code asks whether to add the marketplace (github:aravindsriraj/claude-code-mods): answer y. Then choose a scope; the user scope is listed first, and Enter picks it. The mod starts working in that session right away, and with the user scope in every session after.
Mods built on Claude Code's hooks API need a recent Claude Code; the API is in early access and may change between releases.
A one-line bar above the prompt box with an ✨ Enhance button.
fix the export bug.ctrl+x tab, then e. The bar shows "Enhancing…", then your draft is replaced with a rewritten prompt.s) or just Enter. Undo (u) puts your original draft back.The rewrite sees what the main model sees: your CLAUDE.md and memory files, the installed skills, and the conversation so far, so "the bug" can become a real file and function. On the first prompt of a new session, before there is a conversation, it reads your instruction files and skill names directly.
Good to know:
@file mentions or pasted images, and the transcript shows the message as sent by the mod. For those prompts, press Enter instead.When Claude asks you to choose a design direction with its AskUserQuestion tool (often while using the frontend-design skill), each option is drawn as a mockup above the usual list:
preview is an SVG it is shown as is; otherwise the card shows the label, description, color swatches for any hex colors mentioned, and the preview text.Press Pick on a mockup, or answer in the list below it as usual; whichever you do first is the answer Claude gets.
Which questions it takes: single-pick questions whose header or text mentions design, direction, aesthetic, visual, theme, palette, colour, typography, layout, mockup, UI or UX, when every question in the call is one. Everything else, including a call that mixes design and other questions, is drawn exactly as before.
Good to know:
While Claude works, it records each assumption it acts on: a requirement nobody stated, a project convention, a fact about the code it hasn't checked. It does this by calling the mod's record_assumption tool, which the mod lists in Claude's tools and tells it about in the system prompt, then keeps working without stopping to ask.
ctrl+x tab, then a. That opens a side pane listing every assumption, with Claude's reason when it gave one.Good to know:
/clear starts the list over.Park a side-idea without derailing what Claude is doing, and send it later.
| Command | What it does |
|---|---|
/idea <text> | Puts the idea on the shelf with a timestamp. Runs at once, even while Claude is working, and shows a 💡 toast. |
/ideas | Opens the shelf in a side pane, numbered, with a Send button per idea. Press its number (or click Send) to run it. Esc closes. |
/idea-send <n> | Sends idea n as a prompt and takes it off the shelf. |
A sent idea is submitted as your own prompt (the transcript labels it "Prompt from the idea-shelf plugin"). If Claude is busy it waits its turn.
Claude never sees a shelved idea. The mod replaces the text of your /idea command with [shelved for later; hidden from Claude] before it reaches the conversation, so Claude only learns that something was shelved. /ideas draws the list in a pane, outside the conversation. Your typed text does stay in your local prompt history (up-arrow) and in the session file's record of the command, neither of which Claude reads.
The shelf lives in ~/.claude/idea-shelf.md, one idea per line (- [2026-10-09 14:32] your idea), shared by every project and session. You can edit it by hand.
Each mod is a folder with .claude-plugin/plugin.json, hooks/hooks.json and the hooks module hooks/register.tsx; .claude-plugin/marketplace.json at the root lists them all.
claude plugin validate .
claude plugin validate ./prompt-enhancer
claude plugin test ./prompt-enhancer
claude plugin validate ./design-picker
claude plugin test ./design-picker
claude plugin validate ./assumption-tracker
claude plugin test ./assumption-tracker
claude plugin validate ./idea-shelf
claude plugin test ./idea-shelf
To try a change, start a session with the mod loaded from your working copy: claude --plugin-dir ./prompt-enhancer. Claude Code then writes the API's type declarations into .claude-plugin/types/ and a tsconfig.json beside them, so tsc -p ./prompt-enhancer type-checks it; both are gitignored.
hooks/register.tsx 150 lines1import { atom, read, update } from 'claude-code'
2import type { EngineInterface, Register } from 'claude-code'
3
4import type { Idea } from '../types'
5
6const PANE = 'idea-shelf'
7const HIDDEN = '[shelved for later; hidden from Claude]'
8
9const ideas = atom({ plugin: 'idea-shelf', key: 'ideas' } as const, [] as Idea[])
10
11/** The shelf file: plain markdown in ~/.claude, so it outlives sessions and can be edited by hand. */
12const shelfPath = async ($: EngineInterface) => `${(await $.env.get('HOME')) ?? '.'}/.claude/idea-shelf.md`
13
14const LINE = /^- \[([^\]]+)\] (.*)$/
15
16const parse = (file: string): Idea[] =>
17 file.split('\n').flatMap(line => {
18 const m = LINE.exec(line)
19 return m?.[1] && m[2] ? [{ at: m[1], text: m[2] }] : []
20 })
21
22const format = (list: readonly Idea[]) => `# Idea shelf\n\n${list.map(i => `- [${i.at}] ${i.text}\n`).join('')}`
23
24const pad = (n: number) => String(n).padStart(2, '0')
25const stamp = (ms: number) => {
26 const d = new Date(ms)
27 return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}`
28}
29
30/** Reads the shelf file (empty when there is none yet) and mirrors it into state for the pane. */
31async function load($: EngineInterface): Promise<Idea[]> {
32 const path = await shelfPath($)
33 const list = (await $.fs.exists(path)) ? parse(await $.fs.read(path)) : []
34 await update($, ideas, () => list)
35 return list
36}
37
38// ponytail: read-modify-write, so two sessions shelving in the same instant can lose one; fine for one person.
39async function save($: EngineInterface, list: readonly Idea[]) {
40 await $.fs.write(await shelfPath($), format(list))
41 await update($, ideas, () => [...list])
42}
43
44/** Takes `idea` off the shelf; false when it is no longer there (sent already, or edited out of the file). */
45async function take($: EngineInterface, idea: Idea): Promise<boolean> {
46 const list = await load($)
47 const at = list.findIndex(i => i.at === idea.at && i.text === idea.text)
48 if (at === -1) return false
49 await save($, list.filter((_, i) => i !== at))
50 return true
51}
52
53/** The idea as the person's own prompt: a turn of its own once Claude is idle. */
54const submit = ($: EngineInterface, idea: Idea) => $.prompt.submit({ text: idea.text, asUser: true })
55
56export const register: Register = on => {
57 on('session.start', async ($, e, next) => {
58 await $.command.register({
59 name: 'idea',
60 description: 'Park a side-idea on the shelf for later; Claude never sees it',
61 argumentHint: '<text>',
62 immediate: true,
63 })
64 await $.command.register({ name: 'ideas', description: 'Show the idea shelf, with a Send button per idea', immediate: true })
65 await $.command.register({ name: 'idea-send', description: 'Send shelved idea n as a prompt and take it off the shelf', argumentHint: '<n>' })
66 await load($)
67 return next(e)
68 })
69
70 on('command.run', { command: 'idea' }, async ($, e) => {
71 const text = e.args.replace(/\s+/g, ' ').trim()
72 if (!text) return { text: 'Usage: /idea <text>' }
73
74 const list = [...(await load($)), { at: stamp(await $.clock.now()), text }]
75 await save($, list)
76 $.ui.toast(`💡 Shelved #${list.length}: ${text}`)
77 return { text: `Shelved as idea #${list.length}. Parked for later, not part of the current task.` }
78 })
79
80 on('command.run', { command: 'ideas' }, async $ => {
81 await load($)
82 await $.ui.open({ id: PANE, title: 'Idea shelf', focus: true, closeOnEscape: true })
83 return {}
84 })
85
86 on('command.run', { command: 'idea-send' }, async ($, e) => {
87 const n = Number(e.args.trim())
88 const list = await load($)
89 const idea = Number.isInteger(n) ? list[n - 1] : undefined
90 if (idea === undefined) {
91 return { text: list.length === 0 ? 'The idea shelf is empty.' : `Usage: /idea-send <n>, n from 1 to ${list.length}. /ideas lists them.` }
92 }
93 await take($, idea)
94 // A command's own hook may not submit (the prompt would wait on the turn it holds): submit just after.
95 $.clock.after(0, () => void submit($, idea))
96 return { text: `Sent idea #${n} as a prompt.` }
97 })
98
99 // The `/idea <text>` record would show Claude the idea: keep its text out of the conversation.
100 on('session.append', async ($, e, next) => {
101 const content = e.message.content
102 const isIdea = content.some(b => b.type === 'text' && typeof b.text === 'string' && b.text.includes('<command-name>/idea</command-name>'))
103 if (!isIdea) return next(e)
104
105 return next({
106 ...e,
107 message: {
108 ...e.message,
109 content: content.map(b =>
110 b.type === 'text' && typeof b.text === 'string'
111 ? { ...b, text: b.text.replace(/(<command-args>)[\s\S]*?(<\/command-args>)/, `$1${HIDDEN}$2`) }
112 : b,
113 ),
114 },
115 })
116 })
117
118 on('ui.render', { component: 'Pane', requestId: PANE }, async ($, e) => {
119 const { Box, Button, Text } = $.ui.resolve(e)
120 const list = await read($, ideas)
121
122 const sendOne = async (idea: Idea) => {
123 if (!(await take($, idea))) return $.ui.toast('That idea is no longer on the shelf.')
124 await $.ui.close({ id: PANE })
125 await submit($, idea)
126 }
127
128 return (
129 <Box flexDirection="column">
130 {list.length === 0 && <Text dimColor>{'The shelf is empty. Park an idea with /idea <text>.'}</Text>}
131 {list.map((idea, i) => (
132 <Box key={`idea:${i}`} flexDirection="row">
133 <Button
134 key={`send:${i}`}
135 label="Send"
136 {...(i < 9 ? { hotkey: String(i + 1) } : {})}
137 onPress={() => sendOne(idea)}
138 />
139 <Text>
140 {' '}
141 <Text bold>{i + 1}.</Text> {idea.text} <Text dimColor>({idea.at})</Text>
142 </Text>
143 </Box>
144 ))}
145 {list.length > 0 && <Text dimColor>Press a number or click Send to run that idea as a prompt. Esc closes.</Text>}
146 </Box>
147 )
148 })
149}
150types/index.d.ts 10 lines1/** One shelved idea: when it was parked (local time, `YYYY-MM-DD HH:MM`) and its text. */
2export type Idea = { at: string; text: string }
3
4declare module 'claude-code' {
5 interface PluginState {
6 /** The shelf as last read from its file, for the pane to draw. */
7 'idea-shelf': { ideas: Idea[] }
8 }
9}
10