Reply modes (short, detail, ask-me, critique) as commands and a band above the prompt.

A row of buttons above the prompt that switches reply modes: short, detail, ask-me and critique. The active mode adds a one-line instruction to each message you send.
/plugin marketplace add namtrdev/claude-mods
/plugin install reply-modes@jackie-mods
Commands: /short, /detail, /ask-me, /critique, /mode-off.
Built for Claude Code mods (hook modules). Nothing here runs outside your machine: the mod reads session events and draws its own interface.
You set these when you install the mod, and can change them later in the Claude Code config menu.
shortNote, default Trả lời tối đa 3 câu.): The line added to your message in Short mode.detailNote, default Trả lời đầy đủ, có lập luận và ví dụ.): The line added to your message in Detail mode.askMeNote, default Dùng coaching: đặt câu hỏi giúp tôi tự làm rõ vấn đề, chưa đưa đáp án ngay.): The line added to your message in Ask me mode.critiqueNote, default Chỉ nêu rủi ro, điểm yếu và phương án thay thế.): The line added to your message in Critique mode.MIT, Copyright (c) 2026 Jackie.
Icons: Lucide (ISC). See THIRD_PARTY_NOTICES.md.
hooks/register.tsx 86 lines1import { atom, read, update } from 'claude-code'
2import type { Register } from 'claude-code'
3
4import { icon } from './icons'
5
6import type { ModeName } from '../types'
7
8const mode = atom({ plugin: 'reply-modes', key: 'mode' } as const, 'off')
9
10const BASE_MODES: { name: ModeName; label: string; note: string }[] = [
11 { name: 'short', label: 'Short', note: 'Trả lời tối đa 3 câu.' },
12 { name: 'detail', label: 'Detail', note: 'Trả lời đầy đủ, có lập luận và ví dụ.' },
13 { name: 'ask-me', label: 'Ask me', note: 'Dùng coaching: đặt câu hỏi giúp tôi tự làm rõ vấn đề, chưa đưa đáp án ngay.' },
14 { name: 'critique', label: 'Critique', note: 'Chỉ nêu rủi ro, điểm yếu và phương án thay thế.' },
15]
16
17const ICON = { short: 'zap', detail: 'list', 'ask-me': 'circle-help', critique: 'shield-alert', off: 'circle' } as const
18
19export const register: Register = (on, options) => {
20 const MODES = BASE_MODES.map(one => {
21 const key = { short: 'shortNote', detail: 'detailNote', 'ask-me': 'askMeNote', critique: 'critiqueNote', off: '' }[one.name]
22 const custom = key === '' ? undefined : options[key]
23
24 return typeof custom === 'string' && custom.trim() !== '' ? { ...one, note: custom } : one
25 })
26
27 on('session.start', async ($, e, next) => {
28 for (const one of MODES) {
29 await $.command.register({ name: one.name, description: `Reply mode: ${one.label}. ${one.note}` })
30 }
31 await $.command.register({ name: 'mode-off', description: 'Turn the reply mode off' })
32
33 return next(e)
34 })
35
36 for (const one of MODES) {
37 on('command.run', { command: one.name }, async $ => {
38 await update($, mode, () => one.name)
39
40 return { text: `Reply mode: ${one.label}.` }
41 })
42 }
43
44 on('command.run', { command: 'mode-off' }, async $ => {
45 await update($, mode, () => 'off')
46
47 return { text: 'Reply mode off.' }
48 })
49
50 on('prompt.submit', async ($, e, next) => {
51 const current = await read($, mode)
52 const found = MODES.find(one => one.name === current)
53 if (e.command !== undefined || found === undefined) {
54 return next(e)
55 }
56
57 return next({ ...e, text: `${e.text}\n\n(Chế độ ${found.label}: ${found.note})` })
58 })
59
60 on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
61 if (e.props.hasSurvey) {
62 return next(e)
63 }
64 const ui = $.ui.resolve(e)
65 const { Box, Button, Text } = ui
66 const hasSvg = ui.Svg !== undefined
67 const current = await read($, mode)
68
69 return (
70 <Box gap={2}>
71 <Text bold>MODE</Text>
72 {MODES.map(one => (
73 <Box gap={1}>
74 {hasSvg && icon(ui, ICON[one.name], current === one.name ? '#7dff9a' : '#9ca3af', '')}
75 <Button
76 key={one.name}
77 label={hasSvg ? one.label : current === one.name ? `● ${one.label}` : `○ ${one.label}`}
78 onPress={() => update($, mode, () => (current === one.name ? 'off' : one.name))}
79 />
80 </Box>
81 ))}
82 </Box>
83 )
84 })
85}
86hooks/icons.tsx 28 lines1// Icons from Lucide (https://lucide.dev, ISC license), lucide-static 0.469.0.
2// Svg draws on the desktop, editor and mobile surfaces; the terminal has none, so a glyph stands in.
3const PATHS = {
4 'circle': '<circle cx="12" cy="12" r="10" />',
5 'circle-dot': '<circle cx="12" cy="12" r="10" /> <circle cx="12" cy="12" r="1" />',
6 'zap': '<path d="M4 14a1 1 0 0 1-.78-1.63l9.9-10.2a.5.5 0 0 1 .86.46l-1.92 6.02A1 1 0 0 0 13 10h7a1 1 0 0 1 .78 1.63l-9.9 10.2a.5.5 0 0 1-.86-.46l1.92-6.02A1 1 0 0 0 11 14z" />',
7 'list': '<path d="M3 12h.01" /> <path d="M3 18h.01" /> <path d="M3 6h.01" /> <path d="M8 12h13" /> <path d="M8 18h13" /> <path d="M8 6h13" />',
8 'circle-help': '<circle cx="12" cy="12" r="10" /> <path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3" /> <path d="M12 17h.01" />',
9 'shield-alert': '<path d="M20 13c0 5-3.5 7.5-7.66 8.95a1 1 0 0 1-.67-.01C7.5 20.5 4 18 4 13V6a1 1 0 0 1 1-1c2 0 4.5-1.2 6.24-2.72a1.17 1.17 0 0 1 1.52 0C14.51 3.81 17 5 19 5a1 1 0 0 1 1 1z" /> <path d="M12 8v4" /> <path d="M12 16h.01" />',
10}
11
12export type IconName = keyof typeof PATHS
13
14export const svg = (name: IconName, color: string, size: number) =>
15 `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="${color}" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">${PATHS[name]}</svg>`
16
17// eslint-disable-next-line @typescript-eslint/no-explicit-any
18export type Ui = { Svg?: any; Text: any }
19
20export const icon = (ui: Ui, name: IconName, color: string, glyph: string, size = 16) => {
21 const { Svg, Text } = ui
22 return Svg === undefined ? (
23 <Text color={color}>{glyph}</Text>
24 ) : (
25 <Svg source={svg(name, color, size)} alt={name} width={size} height={size} />
26 )
27}
28types/index.d.ts 8 lines1export type ModeName = 'off' | 'short' | 'detail' | 'ask-me' | 'critique'
2
3declare module 'claude-code' {
4 interface PluginState {
5 'reply-modes': { mode: ModeName }
6 }
7}
8