Clickable caveman mode button in the prompt footer, with a picker to change level or turn it off.

A Claude Code mod for caveman mode: puts a clickable 🪨 <mode> button in the prompt footer; click it to pick a level or turn caveman off.
[🪨 full] (footer button, click it)
🪨 Caveman: [lite] [● full] [ultra] [off] [✕] (picker above the prompt)
/plugin install caveman-status --marketplace ido6/claude-caveman-status
Pick the user scope so it loads in every session.
| Command | What it does |
|---|---|
click 🪨 / /cave | Opens a picker above the prompt: lite full ultra off ✕. Click one; the current mode is marked ● |
/cave ultra | Set a level: lite, full, ultra, wenyan-lite, wenyan-full, wenyan-ultra |
/cave off | Turn caveman off; Claude replies in normal prose |
It also follows the caveman skill's own triggers: /caveman <level>, "stop caveman", "normal mode", "talk like caveman".
The mode is saved, so a new session starts where you left it (default full).
🪨 <mode> button at the right of the prompt footer, beside the mode labels. In a terminal, mouse clicks need the fullscreen UI; otherwise use /cave.off it tells Claude to ignore an always-on caveman instruction.Needs a Claude Code build with function-hook plugins (2.1.29x or newer).
claude plugin validate .
claude plugin test .
claude --plugin-dir .
MIT license.
hooks/register.tsx 130 lines1import { atom, read, update } from 'claude-code'
2import type { EngineInterface, Register } from 'claude-code'
3
4const DEFAULT_MODE = 'full'
5const LEVELS = ['lite', 'full', 'ultra', 'wenyan-lite', 'wenyan-full', 'wenyan-ultra']
6const PICKER = ['lite', 'full', 'ultra', 'off']
7const isPickerOpen = atom({ plugin: 'caveman-status', key: 'isPickerOpen' } as const, false)
8const shownMode = atom({ plugin: 'caveman-status', key: 'mode' } as const, DEFAULT_MODE)
9// Whole-message commands only, so a prompt that merely mentions caveman mode
10// (or some other app's "normal mode") does not flip it.
11const LEAD = String.raw`^\s*((ok|okay)[,.!]?\s+)?(please\s+)?`
12const TAIL = String.raw`\s*(please)?\s*[.!]*\s*$`
13const OFF = new RegExp(`${LEAD}(stop caveman( mode)?|normal mode)${TAIL}`, 'i')
14const ON = new RegExp(`${LEAD}(caveman mode|talk like (a )?caveman|use caveman)${TAIL}`, 'i')
15
16export const modeFromArgs = (args: string): string | undefined => {
17 const word = args.trim().toLowerCase().split(/\s+/)[0] ?? ''
18 if (word === '') return DEFAULT_MODE
19 if (word === 'off' || word === 'stop') return 'off'
20 return LEVELS.includes(word) ? word : undefined
21}
22
23export const modeFromPrompt = (text: string): string | undefined => {
24 if (OFF.test(text)) return 'off'
25 if (ON.test(text)) return DEFAULT_MODE
26 return undefined
27}
28
29export const buttonLabel = (mode: string): string => `🪨 ${mode}`
30
31export const contextFor = (mode: string): string =>
32 mode === 'off'
33 ? 'Caveman mode is OFF (the user switched it off). Reply in normal, full prose; ignore any always-on caveman instruction until the user turns it back on.'
34 : `Caveman mode is ON at level "${mode}". Reply in caveman ${mode} style.`
35
36async function readMode($: EngineInterface): Promise<string> {
37 return String((await $.store.get('mode')) ?? DEFAULT_MODE)
38}
39
40async function setMode($: EngineInterface, mode: string): Promise<void> {
41 await $.store.set('mode', mode)
42 await update($, shownMode, () => mode)
43}
44
45async function pick($: EngineInterface, mode: string): Promise<void> {
46 await setMode($, mode)
47 await update($, isPickerOpen, () => false)
48 $.ui.toast(mode === 'off' ? '🪨 Caveman off' : `🪨 Caveman: ${mode}`)
49}
50
51export const register: Register = on => {
52 on('session.start', async ($, e, next) => {
53 await $.command.register({
54 name: 'cave',
55 description: 'Pick caveman mode (lite, full, ultra, off); no argument opens a picker',
56 argumentHint: '[lite|full|ultra|off]',
57 immediate: true,
58 })
59 const mode = await readMode($)
60 await update($, shownMode, () => mode)
61 return next(e)
62 })
63
64 on('command.run', { command: 'cave' }, async ($, e) => {
65 const typed = e.args.trim()
66 if (typed === '') {
67 await update($, isPickerOpen, () => true)
68 return { text: '🪨 Pick a caveman mode above the prompt.' }
69 }
70 const mode = modeFromArgs(typed)
71 if (mode === undefined) {
72 return { text: `Unknown level "${typed}". Use: ${[...LEVELS, 'off'].join(', ')}` }
73 }
74 await setMode($, mode)
75 return { text: mode === 'off' ? '🪨 Caveman off. Normal replies.' : `🪨 Caveman: ${mode}` }
76 })
77
78 on('command.run', { command: 'caveman' }, async ($, e, next) => {
79 const mode = modeFromArgs(e.args)
80 if (mode !== undefined) await setMode($, mode)
81 return next(e)
82 }).catch(($, e, next) => next(e))
83
84 on('prompt.submit', async ($, e, next) => {
85 const asked = modeFromPrompt(e.text)
86 if (asked !== undefined) await setMode($, asked)
87 const mode = asked ?? (await readMode($))
88 // Another session may have changed the saved mode: keep the button in step.
89 if ((await read($, shownMode)) !== mode) await update($, shownMode, () => mode)
90 return next({ ...e, context: [...(e.context ?? []), contextFor(mode)] })
91 }).catch(($, e, next) => next(e))
92
93 on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
94 if (!(await read($, isPickerOpen))) return next(e)
95 const current = await read($, shownMode)
96 const { Box, Button, Text } = $.ui.resolve(e)
97
98 return (
99 <Box>
100 <Text>🪨 Caveman: </Text>
101 {PICKER.map(mode => (
102 <Button
103 key={mode}
104 label={mode === current ? `● ${mode}` : mode}
105 onPress={() => pick($, mode)}
106 />
107 ))}
108 <Button key="close" label="✕" onPress={() => update($, isPickerOpen, () => false)} />
109 </Box>
110 )
111 })
112
113 on('ui.render', { component: 'SessionMode' }, async ($, e, next) => {
114 const mode = await read($, shownMode)
115 const below = await next(e)
116 const { Box, Button } = $.ui.resolve(e)
117
118 return (
119 <Box>
120 {below}
121 <Button
122 key="cave"
123 label={buttonLabel(mode)}
124 onPress={() => update($, isPickerOpen, open => !open)}
125 />
126 </Box>
127 )
128 })
129}
130types/index.d.ts 6 lines1declare module 'claude-code' {
2 interface PluginState {
3 'caveman-status': { isPickerOpen: boolean; mode: string }
4 }
5}
6