A side panel listing your questions; click one to jump to it in the chat

| Mod | What it does |
|---|---|
| token-buddy | A cute critter in a side panel showing how much context, 5-hour and weekly usage you have left. /tokens reopens it. |
| my-questions | A side panel listing every question you asked in the session; click one to jump to it in the chat. /questions reopens it. |
| maryville-theme | Maryville University maroon and red in the transcript, in the terminal and the desktop app: tool rows with icons and timings, edit diff cards, shell output cards, your own messages in a red frame, brand spinner words. /maryville (dark) or /maryville light also installs a matching terminal theme into ~/.claude/themes/; /maryville off restores Claude Code's own drawing, /maryville band toggles a usage band above the prompt, /maryville status reports what it is drawing. In the desktop app, text follows the app's own light or dark appearance. |
| clipboard | A Clipboard side panel for snippets you want to hold on to across sessions, one list per project folder. A red keep button under every reply files that reply; the panel also keeps Claude's last answer or a note you type, and each clip has copy and delete links. /keep <text> files any text, /keep answer the last reply, /keep export writes everything to clipboard.md in the project folder, /keep clear empties it; /clipboard reopens the panel. In the terminal, Keep selection files whatever you highlighted with the mouse. |
At the prompt of a Claude Code terminal session:
/plugin install token-buddy --marketplace Mengchutsai/mod
/plugin install my-questions --marketplace Mengchutsai/mod
/plugin install maryville-theme --marketplace Mengchutsai/mod
/plugin install clipboard --marketplace Mengchutsai/mod
Answer y to add the marketplace, then pick a scope.
From a shell:
claude plugin install token-buddy --marketplace Mengchutsai/mod
claude plugin install my-questions --marketplace Mengchutsai/mod
claude plugin install maryville-theme --marketplace Mengchutsai/mod
claude plugin install clipboard --marketplace Mengchutsai/mod
In the desktop app: click + next to the prompt box → Plugins → Add marketplace with Mengchutsai/mod → Add plugin.
hooks/register.tsx 104 lines1import { atom, read, update } from 'claude-code'
2import type { EngineInterface, Register } from 'claude-code'
3
4import type { Question } from '../types'
5
6const PANE = 'my-questions'
7const TITLE = 'My Questions'
8const PERSON = new Set(['composer', 'bridge', 'sdk'])
9const questions = atom({ plugin: 'my-questions', key: 'questions' } as const, [])
10
11export function preview(text: string, max = 60): string {
12 const flat = text.replace(/\s+/g, ' ').trim()
13 return flat.length > max ? `${flat.slice(0, max - 1)}…` : flat
14}
15
16function merge(saved: readonly Question[], fresh: readonly Question[]): Question[] {
17 const ids = new Set(saved.map(q => q.requestId))
18 return [...saved, ...fresh.filter(q => !ids.has(q.requestId))].slice(-500)
19}
20
21// Rows seen while drawing; a drawing may not write state, so later events save them.
22let seen: Question[] = []
23
24async function save($: EngineInterface) {
25 if (seen.length === 0) return
26 const fresh = seen
27 seen = []
28 await update($, questions, saved => merge(saved, fresh))
29}
30
31export const register: Register = on => {
32
33 on('prompt.submit', async ($, e, next) => {
34 await save($)
35 return next(e)
36 })
37
38 on('turn.complete', async ($, e, next) => {
39 await save($)
40 return next(e)
41 })
42
43 on('session.start', async ($, e, next) => {
44 await $.command.register({ name: 'questions', description: 'Open the My Questions panel' })
45 void $.ui.open({ id: PANE, title: TITLE })
46
47 return next(e)
48 })
49
50 on('session.attach', async ($, e, next) => {
51 void $.ui.open({ id: PANE, title: TITLE })
52
53 return next(e)
54 })
55
56 on('command.run', { command: 'questions' }, async $ => {
57 const opened = await $.ui.open({ id: PANE, title: TITLE })
58 if (opened.isPlaced) return { text: 'My Questions panel opened.' }
59
60 await save($)
61 const list = await read($, questions)
62 const lines = list.map((q, i) => `${i + 1}. ${preview(q.text, 80)}`).join('\n')
63 return { text: `${lines || 'No questions yet.'}\n(panel not shown: ${opened.reason})` }
64 })
65
66 // Each prompt row the transcript draws is remembered with its row id, so the panel can scroll back to it.
67 on('ui.render', { component: 'UserMessage' }, async ($, e, next) => {
68 const text = e.props.text.trim()
69 if (PERSON.has(e.props.origin.kind) && text && !text.startsWith('<')) {
70 if (!seen.some(q => q.requestId === e.requestId)) seen = [...seen, { requestId: e.requestId, text }]
71 }
72
73 return next(e)
74 })
75
76 on('ui.render', { component: 'Pane', requestId: PANE }, async ($, e) => {
77 const { Box, Text, Button } = $.ui.resolve(e)
78 const list = merge(await read($, questions), seen)
79
80 if (list.length === 0) {
81 return <Text dimColor>Your questions will show up here.</Text>
82 }
83
84 return (
85 <Box flexDirection="column">
86 <Text dimColor>{list.length} questions · click to jump</Text>
87 {list.map((q, i) => (
88 <Button
89 key={`q-${i + 1}`}
90 plain
91 label={`${i + 1}. ${preview(q.text)}`}
92 onPress={async () => {
93 // A transcript row scrolls only in answer to the person's own
94 // input, which a click is; where the surface refuses, say why.
95 const { deny } = await $.ui.scroll({ to: { requestId: q.requestId }, block: 'start' })
96 if (deny) $.ui.toast(`Could not jump to question ${i + 1}: ${deny}`)
97 }}
98 />
99 ))}
100 </Box>
101 )
102 })
103}
104types/index.d.ts 8 lines1export type Question = { requestId: string; text: string }
2
3declare module 'claude-code' {
4 interface PluginState {
5 'my-questions': { questions: Question[] }
6 }
7}
8