After each turn, a band above the prompt suggests a few things worth asking Claude next that you might not think of.

Claude Code mods by @sreshtalluri.

/plugin marketplace add sreshtalluri/claude-mods
/plugin install session-receipt@claude-mods
/plugin install daily-grind@claude-mods
/plugin install next-asks@claude-mods
Mods use Claude Code's function-hooks plugin API, which is early access, so you need a current Claude Code.
| Mod | What it does |
|---|---|
| session-receipt | /receipt prints a shareable receipt of your session. /receipt week totals your week. Copy image gives you a square PNG to post. |
| daily-grind | /grind opens a hub of daily puzzles to play while Claude works. Developer: Git Golf (reshape a commit graph under par), Heisenbug (find the buggy line), Install Order (dependency logic). Everyone: Ladder, Pixel Logic, Codebreaker. A Classics tab holds Lexer, Buckets and Daemons. Streaks, and /grind share for a shareable result. |
| next-asks | After each turn, a band above the prompt suggests up to three things worth asking next that you might not think of: a missed edge case, an unrun check, a related file. Press its number to put it in the prompt. One small Haiku call per turn. |

| Command | |
|---|---|
/receipt | This session: model, time, turns, tools, files, tokens, cache hit, cost, and how many times Claude said you were absolutely right. |
/receipt week | The last seven days: sessions, busiest day, top project, usual model, total. |
/receipt last | The previous session's receipt. |
~/.claude/receipts/ when it ends. Nothing leaves your machine.plugins/<name>/ (with .claude-plugin/plugin.json)..claude-plugin/marketplace.json.claude plugin validate plugins/<name>, claude plugin test plugins/<name> and claude plugin validate .The demo clip is a HyperFrames composition in media/receipt-demo: bun build.ts && npm run render.
MIT
hooks/register.tsx 95 lines1import { atom, read, update } from 'claude-code'
2import type { EngineInterface, Register } from 'claude-code'
3
4import { hangingWrap, HOTKEY_PREFIX, parseIdeas, SYSTEM, transcript } from './suggest'
5
6const ideas = atom({ plugin: 'next-asks', key: 'ideas' } as const, [] as string[])
7const isHidden = atom({ plugin: 'next-asks', key: 'isHidden' } as const, false)
8/** True while the draft opens a `/` command or `@` mention, whose picker draws above the band. */
9const isSearching = atom({ plugin: 'next-asks', key: 'isSearching' } as const, false)
10
11const setSearching = async ($: EngineInterface, value: boolean) => {
12 if ((await read($, isSearching)) !== value) await update($, isSearching, () => value)
13}
14
15/** Asks Haiku for follow-ups over the conversation's tail; a failed call just leaves the band empty. */
16const refresh = async ($: EngineInterface) => {
17 const tail = transcript(await $.session.messages())
18 if (tail === '') return
19 const r = await $.model.complete({
20 model: 'haiku',
21 system: SYSTEM,
22 prompt: tail,
23 maxTokens: 200,
24 effort: 'low',
25 timeoutMs: 20_000,
26 })
27 if (r.isAnswered) await update($, ideas, () => parseIdeas(r.text))
28}
29
30export const register: Register = on => {
31 // A new prompt makes the last turn's ideas stale.
32 on('prompt.submit', async ($, e, next) => {
33 await update($, ideas, () => [])
34 await setSearching($, false)
35 return next(e)
36 })
37
38 // Step aside while the person searches commands, skills or files, so the picker sits by the input.
39 on('prompt.edit', async ($, e, next) => {
40 const box = await next(e)
41 await setSearching($, /^\s*[/@]/.test(box.text))
42 return box
43 })
44
45 on('turn.complete', async ($, e, next) => {
46 const result = await next(e)
47 // Main loop only, and only turns that answered; a timer so the turn ends without waiting on Haiku.
48 if (e.agentId === undefined && e.reason === 'answer') {
49 $.clock.after(0, () => void refresh($).catch(() => {}))
50 }
51 return result
52 })
53
54 on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
55 const list = await read($, ideas)
56 const isQuiet = e.props.hasSurvey || e.props.isWorking || list.length === 0
57 if (isQuiet || (await read($, isHidden)) || (await read($, isSearching))) {
58 return next(e)
59 }
60
61 const { Box, Button, Text } = $.ui.resolve(e)
62 // The terminal wraps a label under its `1:`; wrap it ourselves with a hanging indent.
63 // Desktop draws a proportional font, so it wraps on its own.
64 const textWidth = e.props.bodyColumns - 4 - HOTKEY_PREFIX // border 2 + padding 2
65 const label = (idea: string) => (e.surface === 'terminal' ? hangingWrap(idea, textWidth) : idea)
66
67 // Theme keys, not hex, so the box follows the person's light or dark theme on every surface.
68 return (
69 <Box
70 flexDirection="column"
71 width={e.props.bodyColumns}
72 borderStyle="round"
73 borderColor="suggestion"
74 paddingX={1}
75 >
76 <Box justifyContent="space-between">
77 <Text color="claude" bold>
78 ✦ Worth asking next
79 </Text>
80 <Button key="hide" label="hide" plain dimColor onPress={() => update($, isHidden, () => true)} />
81 </Box>
82 {list.map((idea, i) => (
83 <Button
84 key={`idea-${i}`}
85 label={label(idea)}
86 hotkey={String(i + 1)}
87 plain
88 onPress={() => $.prompt.fill({ text: idea })}
89 />
90 ))}
91 </Box>
92 )
93 })
94}
95hooks/suggest.ts 56 lines1import type { SessionMessage } from 'claude-code'
2
3export const MAX_IDEAS = 3
4/** How much of the conversation's tail Haiku reads: enough for the current task, cheap per turn. */
5const TAIL_CHARS = 12_000
6
7export const SYSTEM = `You watch a developer's session with a coding agent. After the agent's latest turn, suggest up to ${MAX_IDEAS} short follow-up requests the developer could send next.
8
9Favor what they might not think to ask: a missed edge case, a test or check nobody ran, a related file or config that likely needs the same change, a risk, a cleanup, docs or deploy steps. Skip the obvious next step they are already heading to and anything the agent already did.
10
11Write each as the developer would type it to the agent, imperative, under 90 characters, one per line, no numbering or bullets. If nothing is worth suggesting, reply NONE.`
12
13/** The conversation's tail as plain lines, newest last, cut to `TAIL_CHARS`. */
14export const transcript = (messages: readonly SessionMessage[]): string => {
15 const lines: string[] = []
16 for (const m of messages) {
17 const tools = m.toolUses.map(t => t.tool).join(', ')
18 const text = m.text.trim()
19 if (text === '' && tools === '') continue
20 lines.push(`${m.role === 'user' ? 'DEVELOPER' : 'AGENT'}: ${text}${tools ? ` [tools: ${tools}]` : ''}`)
21 }
22 return lines.join('\n\n').slice(-TAIL_CHARS)
23}
24
25/** Width of the `1: ` a hotkeyed Button draws before its label. */
26export const HOTKEY_PREFIX = 3
27
28/**
29 * Word-wraps `text` to `width` cells with continuation lines indented by
30 * `HOTKEY_PREFIX`, so a wrapped label hangs under its first word, not its `1:`.
31 */
32export const hangingWrap = (text: string, width: number): string => {
33 const lines: string[] = []
34 let line = ''
35 for (const word of text.split(/\s+/).filter(Boolean)) {
36 if (line !== '' && line.length + 1 + word.length > width) {
37 lines.push(line)
38 line = word
39 } else {
40 line = line === '' ? word : `${line} ${word}`
41 }
42 }
43 if (line !== '') lines.push(line)
44 return lines.join(`\n${' '.repeat(HOTKEY_PREFIX)}`)
45}
46
47/** Haiku's reply as at most `MAX_IDEAS` clean one-line asks. */
48export const parseIdeas = (reply: string): string[] => {
49 if (/^\s*NONE\s*$/i.test(reply)) return []
50 return reply
51 .split('\n')
52 .map(l => l.replace(/^\s*(?:[-*•]|\d+[.)])\s*/, '').replace(/^["“]|["”]$/g, '').trim())
53 .filter(l => l !== '' && !/^NONE$/i.test(l))
54 .slice(0, MAX_IDEAS)
55}
56types/index.d.ts 8 lines1export type NextAsksIdeas = string[]
2
3declare module 'claude-code' {
4 interface PluginState {
5 'next-asks': { ideas: NextAsksIdeas; isHidden: boolean; isSearching: boolean }
6 }
7}
8