A pane listing the files Claude reads and edits as a tree: highlighted while touched, green once committed. No tokens used.

Nine mods for Claude Code (2.1.287 or newer), written from the walkthrough video. Each lives in mods/<name>.
Install one, or all, from a terminal session:
/plugin install <name> --marketplace KianaVega/KianaVega
| Mod | What it does | Extra model usage |
|---|---|---|
| savvy-progress | Progress bar above the prompt and a live list of subagents with model, tokens and rough cost | none |
| tool-skins | Icons and colors for tool calls in seven themes, switch with /skin <name> | none |
| you-should-know | A second, small model reads long answers and flags anything important you might miss | one small call per long answer |
| file-tree | Pane showing files Claude reads and edits, green once committed (/file-tree) | none |
| cache-tax | Holds a prompt that would reload a cold cache and shows an estimate. /keepwarm pings before the cache expires | none, except keep-warm pings |
| blast-radius | Stops recursive deletes, git clean, hard resets and shows the affected files before you press Proceed | none |
| reflect | Offers to save corrections as a rule in CLAUDE.md | one small call when you save |
| terminal-browser | /browse <url or file> opens a pane with a screenshot (kitty or Ghostty) and the page text | none |
| replay-theater | Replay button after a turn that steps through each edit as a diff (/replay) | none |
hooks/register.tsx. Edit and reload.hooks/register.tsx 142 lines1import { atom, read, update } from 'claude-code'
2import type { Register } from 'claude-code'
3
4import type { FileState } from '../types'
5
6const PANE = 'file-tree'
7const files = atom({ plugin: 'file-tree', key: 'files' } as const, {} as Record<string, FileState>)
8const root = atom({ plugin: 'file-tree', key: 'root' } as const, '')
9
10const EDIT_TOOLS = new Set(['Edit', 'MultiEdit', 'Write', 'NotebookEdit'])
11
12const COLORS: Record<FileState, string> = {
13 reading: 'suggestion',
14 read: 'inactive',
15 editing: 'warning',
16 changed: 'claude',
17 committed: 'success',
18}
19
20const pathOf = (e: unknown): string | undefined => {
21 const i = e as { file_path?: unknown; notebook_path?: unknown }
22 const p = i.file_path ?? i.notebook_path
23
24 return typeof p === 'string' ? p : undefined
25}
26
27const mark =
28 (path: string, state: FileState) =>
29 (list: Record<string, FileState>): Record<string, FileState> => ({ ...list, [path]: state })
30
31type Node = { children: Map<string, Node>; path?: string }
32
33const build = (paths: string[], base: string): Node => {
34 const top: Node = { children: new Map() }
35 for (const full of paths) {
36 const rel = base !== '' && full.startsWith(base + '/') ? full.slice(base.length + 1) : full
37 let node = top
38 for (const part of rel.split('/').filter(Boolean)) {
39 if (!node.children.has(part)) node.children.set(part, { children: new Map() })
40 node = node.children.get(part)!
41 }
42 node.path = full
43 }
44
45 return top
46}
47
48const flatten = (node: Node, depth: number, out: Array<{ name: string; depth: number; path?: string }>) => {
49 const names = [...node.children.keys()].sort()
50 for (const name of names) {
51 const child = node.children.get(name)!
52 out.push({ name, depth, path: child.path })
53 flatten(child, depth + 1, out)
54 }
55
56 return out
57}
58
59export const register: Register = on => {
60 on('session.start', async ($, e, next) => {
61 await update($, root, () => '')
62 const cwd = await $.session.cwd()
63 await update($, root, () => cwd)
64 await $.command.register({ name: 'file-tree', description: 'Open the file tree pane' })
65 void $.ui.open({ id: PANE, title: 'Files' })
66
67 return next(e)
68 })
69
70 on('command.run', { command: 'file-tree' }, async $ => {
71 await $.ui.open({ id: PANE, title: 'Files' })
72
73 return { text: 'File tree opened.' }
74 })
75
76 // Watch: shimmer while a file is being touched, then settle.
77 on('tool.call', async ($, e, next) => {
78 const tool = String(e.tool)
79 const path = pathOf(e)
80
81 if (path !== undefined && (tool === 'Read' || EDIT_TOOLS.has(tool))) {
82 const isEdit = EDIT_TOOLS.has(tool)
83 await update($, files, mark(path, isEdit ? 'editing' : 'reading'))
84 const ran = await next(e)
85 const hasFailed = ran.deny !== undefined || ran.isError === true
86 await update($, files, (list): Record<string, FileState> => {
87 const was = list[path]
88 const settled: FileState = hasFailed ? 'read' : isEdit || was === 'changed' ? 'changed' : 'read'
89
90 return hasFailed && was === undefined ? list : { ...list, [path]: settled }
91 })
92
93 return ran
94 }
95
96 if (tool === 'Bash' && /\bgit\s+commit\b/.test((e as { command?: string }).command ?? '')) {
97 const ran = await next(e)
98 if (ran.deny === undefined && ran.isError !== true) {
99 await update($, files, list =>
100 Object.fromEntries(
101 Object.entries(list).map(([p, s]) => [p, s === 'changed' ? 'committed' : s]),
102 ) as Record<string, FileState>,
103 )
104 }
105
106 return ran
107 }
108
109 return next(e)
110 })
111
112 on('ui.render', { component: 'Pane', requestId: PANE }, async ($, e) => {
113 const { Box, Text } = $.ui.resolve(e)
114 const list = await read($, files)
115 const base = await read($, root)
116 const rows = flatten(build(Object.keys(list), base), 0, [])
117 const room = Math.max(1, (e.viewport?.rows ?? 24) - 4)
118
119 return (
120 <Box flexDirection="column">
121 {rows.length === 0 && <Text dimColor>No files touched yet.</Text>}
122 {rows.slice(0, room).map(r => {
123 const state = r.path === undefined ? undefined : list[r.path]
124
125 return (
126 <Text
127 color={state === undefined ? undefined : COLORS[state]}
128 bold={state === 'editing' || state === 'reading'}
129 dimColor={state === undefined}
130 >
131 {' '.repeat(r.depth)}
132 {state === undefined ? r.name + '/' : r.name}
133 {state === 'committed' ? ' committed' : ''}
134 </Text>
135 )
136 })}
137 {rows.length > room && <Text dimColor>+{rows.length - room} more</Text>}
138 </Box>
139 )
140 })
141}
142types/index.d.ts 8 lines1export type FileState = 'reading' | 'read' | 'editing' | 'changed' | 'committed'
2
3declare module 'claude-code' {
4 interface PluginState {
5 'file-tree': { files: Record<string, FileState>; root: string }
6 }
7}
8