SLOPSHOPPER

tool-skins

Reskins tool calls with per-tool icons and theme colors; seven themes, switch with /skin.

newspinnerrowscommand
v0.1.0no licenseupdated 2026-10-12Isaacg1000/isaac-mods/plugins/tool-skins
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · tool-skins
› fix the failing auth test and add an audit log call ⏺ Read(src/auth.ts) ⎿ Read 6 lines ✎ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ ⟨Claude Code's own drawing⟩ │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ⏺ Bash(bun test) ⎿ 3 pass, 1 fail ● Done. refresh now rejects expired claims and logs an audit event. ✻ Worked for 42s · done 4:20 PM › /skin ⎿ tool-skins: Themes: ⎿ tool-skins: ▸ tokyo-night (Tokyo Night) ⎿ tool-skins: nord (Nord) ⎿ tool-skins: dracula (Dracula) ⎿ tool-skins: gruvbox (Gruvbox) ⎿ tool-skins: solarized (Solarized) ✦ ✻ Thinking… ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts

Draws

Spinner
✦ ✻ Thinking…
Tool row
✎ ╭──────────────────────────────────────────────────────────────────────────────────────────────── │ ⟨Claude Code's own drawing⟩ ╰────────────────────────────────────────────────────────────────────────────────────────────────
README

isaac-mods

Eight mods for Claude Code (terminal and the desktop app's Code tab). Requires Claude Code 2.1.287 or newer.

ModWhat it doesCommands
savvy-progressProgress 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-skinsThemed icons and colors for tool calls (Tokyo Night, Nord, Dracula, Gruvbox, Solarized, Catppuccin, Rose Pine)/skin <theme>
you-should-knowAfter a long reply, a Haiku side agent flags costs, risks and actions you might miss/ysk on, /ysk off, /ysk focus <text>
file-treeA panel showing which files Claude is reading or editing; turns green once committed/files
cache-taxWarns before a prompt reloads a cold prompt cache, with a token and cost estimate/keepwarm, /cachetax ttl <minutes>
blast-radiusStops rm -r, Remove-Item -Recurse, git clean -f, git reset --hard and similar, lists the files at stake, and waits for Proceed or Cancelautomatic
reflectSpots corrections in your prompts and offers to save them as rules in CLAUDE.md/reflect <rule>
terminal-browserA 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.

Install

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

Developing

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.

Source 2 files
hooks/register.tsx 137 lines
1import { atom, read, update } from 'claude-code'
2import type { Register } from 'claude-code'
3
4type Kind = 'read' | 'edit' | 'write' | 'run' | 'search' | 'web' | 'agent' | 'task' | 'mcp' | 'other'
5type Theme = { label: string; colors: Record<Kind, string>; error: string; icons: Record<Kind, string>; editBorder: string }
6
7const GLYPHS: Record<Kind, string> = {
8  read: '◫', edit: '✎', write: '✚', run: '❯', search: '⌕', web: '◍', agent: '✦', task: '☰', mcp: '⇄', other: '•',
9}
10const EMOJI: Record<Kind, string> = {
11  read: '📖', edit: '✏️', write: '📝', run: '⚡', search: '🔍', web: '🌐', agent: '🤖', task: '📋', mcp: '🔌', other: '🔧',
12}
13
14const THEMES: Record<string, Theme> = {
15  'tokyo-night': {
16    label: 'Tokyo Night',
17    colors: { read: '#7aa2f7', edit: '#e0af68', write: '#9ece6a', run: '#bb9af7', search: '#7dcfff', web: '#2ac3de', agent: '#ff9e64', task: '#73daca', mcp: '#b4f9f8', other: '#a9b1d6' },
18    error: '#f7768e', icons: GLYPHS, editBorder: '#e0af68',
19  },
20  nord: {
21    label: 'Nord',
22    colors: { read: '#88c0d0', edit: '#ebcb8b', write: '#a3be8c', run: '#b48ead', search: '#8fbcbb', web: '#81a1c1', agent: '#d08770', task: '#5e81ac', mcp: '#8fbcbb', other: '#d8dee9' },
23    error: '#bf616a', icons: GLYPHS, editBorder: '#ebcb8b',
24  },
25  dracula: {
26    label: 'Dracula',
27    colors: { read: '#8be9fd', edit: '#f1fa8c', write: '#50fa7b', run: '#bd93f9', search: '#8be9fd', web: '#ff79c6', agent: '#ffb86c', task: '#50fa7b', mcp: '#ff79c6', other: '#f8f8f2' },
28    error: '#ff5555', icons: GLYPHS, editBorder: '#ff79c6',
29  },
30  gruvbox: {
31    label: 'Gruvbox',
32    colors: { read: '#83a598', edit: '#fabd2f', write: '#b8bb26', run: '#d3869b', search: '#8ec07c', web: '#83a598', agent: '#fe8019', task: '#8ec07c', mcp: '#d3869b', other: '#ebdbb2' },
33    error: '#fb4934', icons: GLYPHS, editBorder: '#fabd2f',
34  },
35  solarized: {
36    label: 'Solarized',
37    colors: { read: '#268bd2', edit: '#b58900', write: '#859900', run: '#6c71c4', search: '#2aa198', web: '#268bd2', agent: '#cb4b16', task: '#2aa198', mcp: '#d33682', other: '#93a1a1' },
38    error: '#dc322f', icons: GLYPHS, editBorder: '#b58900',
39  },
40  catppuccin: {
41    label: 'Catppuccin Mocha',
42    colors: { read: '#89b4fa', edit: '#f9e2af', write: '#a6e3a1', run: '#cba6f7', search: '#94e2d5', web: '#74c7ec', agent: '#fab387', task: '#94e2d5', mcp: '#f5c2e7', other: '#cdd6f4' },
43    error: '#f38ba8', icons: EMOJI, editBorder: '#f9e2af',
44  },
45  'rose-pine': {
46    label: 'Rose Pine',
47    colors: { read: '#9ccfd8', edit: '#f6c177', write: '#31748f', run: '#c4a7e7', search: '#9ccfd8', web: '#ebbcba', agent: '#eb6f92', task: '#31748f', mcp: '#ebbcba', other: '#e0def4' },
48    error: '#eb6f92', icons: EMOJI, editBorder: '#ebbcba',
49  },
50}
51const NAMES = Object.keys(THEMES)
52
53function kindOf(tool: string): Kind {
54  if (/^mcp__/.test(tool)) return 'mcp'
55  if (/^(Read|NotebookRead|ListMcpResourcesTool|ReadMcpResource)/.test(tool)) return 'read'
56  if (/^(Edit|MultiEdit|NotebookEdit)$/.test(tool)) return 'edit'
57  if (tool === 'Write') return 'write'
58  if (/^(Bash|PowerShell|Monitor)$/.test(tool)) return 'run'
59  if (/^(Glob|Grep|ToolSearch|LSP)$/.test(tool)) return 'search'
60  if (/^Web/.test(tool)) return 'web'
61  if (/^(Agent|Task|Workflow|SendMessage)$/.test(tool)) return 'agent'
62  if (/^(TodoWrite|Task[A-Z])/.test(tool)) return 'task'
63  return 'other'
64}
65
66const theme = atom({ plugin: 'tool-skins', key: 'theme' } as const, 'tokyo-night')
67
68export const register: Register = on => {
69  on('session.start', async ($, e, next) => {
70    await $.command.register({
71      name: 'skin',
72      description: 'Switch the Claude Skins theme (no argument lists them)',
73      argumentHint: `<${NAMES.join('|')}|off>`,
74    })
75    const saved = await $.store.get('theme')
76    if (typeof saved === 'string' && (saved in THEMES || saved === 'off')) await update($, theme, () => saved)
77    return next(e)
78  })
79
80  on('command.run', { command: 'skin' }, async ($, e) => {
81    const wanted = e.args.trim().toLowerCase()
82    const current = await read($, theme)
83    if (!wanted) {
84      const rows = NAMES.map(n => `${n === current ? '▸' : ' '} ${n} (${THEMES[n]!.label})`).join('\n')
85      return { text: `Themes:\n${rows}\n  off\nUse /skin <name>.` }
86    }
87    if (!(wanted in THEMES) && wanted !== 'off') return { text: `No theme "${wanted}". Try: ${NAMES.join(', ')}, off` }
88    await update($, theme, () => wanted)
89    await $.store.set('theme', wanted)
90    return { text: wanted === 'off' ? 'Skins off.' : `Skin set to ${THEMES[wanted]!.label}.` }
91  })
92
93  on('ui.render', { component: 'ToolUse' }, async ($, e, next) => {
94    const t = THEMES[await read($, theme)]
95    if (!t) return next(e)
96    const { Box, Text } = $.ui.resolve(e)
97    const kind = kindOf(e.props.tool)
98    const color = e.props.isErrored ? t.error : t.colors[kind]
99    const icon = e.props.isErrored ? '✗' : t.icons[kind]
100    const inner = await next(e)
101    const isEdit = kind === 'edit' || kind === 'write'
102    return (
103      <Box flexDirection="row">
104        <Box width={3} flexShrink={0}>
105          <Text color={color} bold={e.props.isRunning}>
106            {icon}
107          </Text>
108        </Box>
109        {isEdit ? (
110          <Box flexGrow={1} flexShrink={1} flexDirection="column" borderStyle="round" borderColor={t.editBorder} paddingX={1}>
111            {inner}
112          </Box>
113        ) : (
114          <Box flexGrow={1} flexShrink={1} flexDirection="column">
115            {inner}
116          </Box>
117        )}
118      </Box>
119    )
120  })
121
122  on('ui.render', { component: 'Spinner' }, async ($, e, next) => {
123    const t = THEMES[await read($, theme)]
124    if (!t) return next(e)
125    const { Box, Text } = $.ui.resolve(e)
126    const kind: Kind = e.props.mode === 'tool-use' ? 'run' : e.props.mode === 'thinking' ? 'agent' : 'read'
127    return (
128      <Box flexDirection="row">
129        <Box width={3} flexShrink={0}>
130          <Text color={t.colors[kind]}>{t.icons[kind]}</Text>
131        </Box>
132        {await next(e)}
133      </Box>
134    )
135  })
136}
137
types/index.d.ts 8 lines
1export type SkinName = string
2
3declare module 'claude-code' {
4  interface PluginState {
5    'tool-skins': { theme: SkinName }
6  }
7}
8