Spots corrections in your prompts and offers to save them as permanent rules in CLAUDE.md.

Eight mods for Claude Code (terminal and the desktop app's Code tab). Requires Claude Code 2.1.287 or newer.
| Mod | What it does | Commands |
|---|---|---|
| savvy-progress | Progress bar above the prompt; an Agents side panel with a pixel crab per subagent showing model, effort, task progress, context, tokens and estimated cost | /savvy, /crab-style <keyword> <color> <hat> |
| tool-skins | Themed icons and colors for tool calls (Tokyo Night, Nord, Dracula, Gruvbox, Solarized, Catppuccin, Rose Pine) | /skin <theme> |
| you-should-know | After a long reply, a Haiku side agent flags costs, risks and actions you might miss | /ysk on, /ysk off, /ysk focus <text> |
| file-tree | A panel showing which files Claude is reading or editing; turns green once committed | /files |
| cache-tax | Warns before a prompt reloads a cold prompt cache, with a token and cost estimate | /keepwarm, /cachetax ttl <minutes> |
| blast-radius | Stops rm -r, Remove-Item -Recurse, git clean -f, git reset --hard and similar, lists the files at stake, and waits for Proceed or Cancel | automatic |
| reflect | Spots corrections in your prompts and offers to save them as rules in CLAUDE.md | /reflect <rule> |
| terminal-browser | A text-mode browser pane; Claude can open pages there too | /browse <url or path> |
Cost figures are estimates at API list prices. you-should-know and reflect make small Haiku calls, which count toward your usage.
At a Claude Code prompt in a terminal:
/plugin install savvy-progress --marketplace Isaacg1000/isaac-mods
Answer y to add the marketplace, then pick the user scope. Repeat with each mod name you want.
Or from a shell, all at once:
claude plugin marketplace add Isaacg1000/isaac-mods
claude plugin install savvy-progress@isaac-mods --scope user
claude plugin install tool-skins@isaac-mods --scope user
claude plugin install you-should-know@isaac-mods --scope user
claude plugin install file-tree@isaac-mods --scope user
claude plugin install cache-tax@isaac-mods --scope user
claude plugin install blast-radius@isaac-mods --scope user
claude plugin install reflect@isaac-mods --scope user
claude plugin install terminal-browser@isaac-mods --scope user
Each mod lives in plugins/<name>/: hooks/register.tsx is the hooks module, types/index.d.ts its state contract. Check and test one with:
claude plugin validate plugins/<name>
claude plugin test plugins/<name>
When this folder is added as a local marketplace (claude plugin marketplace add <this folder>), edits are picked up with /reload-plugins.
hooks/register.tsx 151 lines1import { atom, read, update } from 'claude-code'
2import type { EngineInterface, Register } from 'claude-code'
3
4import type { Suggestion } from '../types'
5
6const suggestion = atom({ plugin: 'reflect', key: 'suggestion' } as const, null as Suggestion | null)
7
8const HEADER = '## Learned rules (Reflect)'
9
10// Phrases that usually mean the person is correcting Claude.
11const FEEDBACK = [
12 /^\s*(no|nope|nah|wrong|stop|don'?t|do not|never|always|ugh|actually)\b/i,
13 /\b(doesn'?t|does not|didn'?t) (look|work|feel) (good|right)\b/i,
14 /\bi (wish|would rather|'d rather|prefer|told you|said)\b/i,
15 /\b(instead of|rather than|not like that|that'?s not what|you should(n'?t)? have|please don'?t|stop (doing|using|adding))\b/i,
16 /\b(from now on|next time|in (the )?future|every time|going forward)\b/i,
17]
18
19const SYSTEM = `You turn a user's correction of an AI coding assistant into one durable rule for its instructions file.
20Write a single imperative sentence under 25 words, general enough to apply next time, with no preamble or quotes.
21If the message is a one-off instruction rather than a lasting preference, reply with exactly SKIP.`
22
23async function homeDir($: EngineInterface) {
24 return (await $.env.get('USERPROFILE')) ?? (await $.env.get('HOME')) ?? '~'
25}
26
27async function appendRule($: EngineInterface, path: string, rule: string) {
28 const exists = await $.fs.exists(path)
29 const text = exists ? ((await $.fs.read(path)) as string) : ''
30 const line = `- ${rule.replace(/^[-*]\s*/, '').trim()}`
31 if (text.includes(line)) return
32 const lines = text.split(/\r?\n/)
33 const at = lines.findIndex(l => l.trim() === HEADER)
34 if (at === -1) {
35 const sep = text.length === 0 ? '' : text.endsWith('\n') ? '\n' : '\n\n'
36 await $.fs.write(path, `${text}${sep}${HEADER}\n\n${line}\n`)
37 return
38 }
39 let end = at + 1
40 while (end < lines.length && (lines[end]!.trim() === '' || lines[end]!.trimStart().startsWith('- '))) end++
41 while (end > at + 1 && lines[end - 1]!.trim() === '') end--
42 lines.splice(end, 0, line)
43 await $.fs.write(path, lines.join('\n'))
44}
45
46// The person's edit of the suggested rule; kept out of state so typing doesn't redraw the field.
47let draft: string | undefined
48
49async function save($: EngineInterface, where: 'project' | 'user') {
50 const held = await read($, suggestion)
51 const s = held && draft?.trim() ? { ...held, rule: draft.trim() } : held
52 if (!s || !s.rule.trim()) return
53 const path =
54 where === 'project' ? `${(await $.session.cwd()).replace(/[\\/]$/, '')}/CLAUDE.md` : `${await homeDir($)}/.claude/CLAUDE.md`
55 try {
56 await appendRule($, path, s.rule)
57 draft = undefined
58 await update($, suggestion, v => (v ? { ...v, rule: s.rule, savedTo: path } : v))
59 $.ui.toast(`Rule saved to ${path}`)
60 $.clock.after(4000, () => void update($, suggestion, v => (v?.savedTo ? null : v)))
61 } catch (err) {
62 $.ui.toast(`Reflect could not write ${path}: ${String(err)}`)
63 }
64}
65
66export const register: Register = on => {
67 on('session.start', async ($, e, next) => {
68 await $.command.register({ name: 'reflect', description: 'Reflect: save a rule to CLAUDE.md: /reflect <rule>', argumentHint: '<rule>' })
69 return next(e)
70 })
71
72 on('command.run', { command: 'reflect' }, async ($, e) => {
73 const rule = e.args.trim()
74 if (!rule) return { text: 'Usage: /reflect <rule>. Reflect also offers this automatically when you correct Claude.' }
75 await update($, suggestion, () => ({ feedback: rule, rule, savedTo: null }))
76 return { text: 'Choose where to save it in the banner above the prompt.' }
77 })
78
79 on('prompt.submit', async ($, e, next) => {
80 const result = await next(e)
81 const kind = e.origin.kind
82 const text = e.text.trim()
83 if ((kind !== 'composer' && kind !== 'sdk' && kind !== 'bridge') || text.startsWith('/') || text.length > 1500) return result
84 if (!FEEDBACK.some(re => re.test(text))) return result
85
86 $.clock.after(0, () => {
87 void (async () => {
88 const messages = await $.session.messages().catch(() => [])
89 const lastReply = [...messages].reverse().find(m => m.role === 'assistant')?.text ?? ''
90 const reply = await $.model.complete({
91 model: 'haiku',
92 system: SYSTEM,
93 prompt: `Assistant's previous reply (for context):\n${lastReply.slice(-3000)}\n\nUser's message:\n${text}`,
94 maxTokens: 120,
95 timeoutMs: 30000,
96 })
97 if (!reply.isAnswered) return
98 const rule = reply.text.trim().split('\n')[0]!.replace(/^["'`-]\s*|["'`]$/g, '')
99 if (!rule || /^SKIP\b/i.test(rule)) return
100 draft = undefined
101 await update($, suggestion, () => ({ feedback: text, rule, savedTo: null }))
102 })()
103 })
104 return result
105 })
106
107 on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
108 const s = await read($, suggestion)
109 if (!s || e.props.hasSurvey) return next(e)
110 const { Box, Text, Button, Input } = $.ui.resolve(e)
111 if (s.savedTo) {
112 return (
113 <Box>
114 <Text color="success">🪞 Saved to {s.savedTo}</Text>
115 </Box>
116 )
117 }
118 return (
119 <Box flexDirection="column" borderStyle="round" borderColor="suggestion" paddingX={1}>
120 <Text bold color="suggestion">
121 🪞 Save this as a permanent rule?
122 </Text>
123 {Input ? (
124 <Input
125 key="rule"
126 value={s.rule}
127 submitLabel="Save to project"
128 onInput={value => {
129 draft = value
130 }}
131 onSubmit={value => {
132 draft = value
133 void save($, 'project')
134 }}
135 />
136 ) : (
137 <Text italic>{s.rule}</Text>
138 )}
139 <Box flexDirection="row" gap={2}>
140 <Button key="project" label="Project CLAUDE.md" variant="primary" hotkey="p" onPress={() => save($, 'project')} />
141 <Button key="user" label="User CLAUDE.md" hotkey="u" onPress={() => save($, 'user')} />
142 <Button key="dismiss" label="Dismiss" role="dismiss" hotkey="d" onPress={() => {
143 draft = undefined
144 void update($, suggestion, () => null)
145 }} />
146 </Box>
147 </Box>
148 )
149 })
150}
151types/index.d.ts 8 lines1export type Suggestion = { feedback: string; rule: string; savedTo: string | null }
2
3declare module 'claude-code' {
4 interface PluginState {
5 reflect: { suggestion: Suggestion | null }
6 }
7}
8