Pane listing the CLAUDE.md, memory and other instruction files this session loaded, by tier, with size.

A band above the prompt that lists the CLAUDE.md, memory and other instruction files the session loaded, grouped by tier with an estimated token count, in collapsed or expanded form.
/plugin marketplace add namtrdev/claude-mods
/plugin install context-files@jackie-mods
Commands: /context-files.
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.
startExpanded, default true): Open the band with the full file list; off starts it collapsed to one line.MIT, Copyright (c) 2026 Jackie.
Icons: Lucide (ISC). See THIRD_PARTY_NOTICES.md.
hooks/register.tsx 162 lines1import { atom, read, update } from 'claude-code'
2import type { Register } from 'claude-code'
3
4import { icon } from './icons'
5
6import type { Loaded } from '../types'
7
8const files = atom({ plugin: 'context-files', key: 'files' } as const, [])
9const isExpanded = atom({ plugin: 'context-files', key: 'isExpanded' } as const, true)
10
11const ORDER = ['managed', 'user', 'project', 'local', 'memory']
12const COLOR: Record<string, string> = {
13 managed: '#ff7a7a',
14 user: '#5fe3ff',
15 project: '#7dff9a',
16 local: '#ffe066',
17 memory: '#ff8ae8',
18}
19
20const tokens = (chars: number) => {
21 const n = Math.round(chars / 4)
22 return n >= 1000 ? `${(n / 1000).toFixed(1)}k` : String(n)
23}
24
25export const register: Register = (on, options) => {
26 on('session.start', async ($, e, next) => {
27 await $.command.register({
28 name: 'context-files',
29 description: 'Expand or collapse the loaded-instruction-files band',
30 })
31 await update($, isExpanded, () => options.startExpanded !== false)
32 $.ui.invalidate('prompt.context')
33
34 return next(e)
35 })
36
37 on('command.run', { command: 'context-files' }, async $ => {
38 const was = await read($, isExpanded)
39 await update($, isExpanded, () => !was)
40
41 return { text: was ? 'Context files band collapsed.' : 'Context files band expanded.' }
42 })
43
44 on('prompt.context', async ($, e, next) => {
45 const out = await next(e)
46 const list = out.instructionFiles ?? e.instructionFiles ?? []
47 const loaded: Loaded[] = list.map(one => ({
48 path: one.path,
49 kind: one.kind,
50 chars: one.content.length,
51 parent: one.parent,
52 }))
53 await update($, files, () => loaded)
54
55 return out
56 })
57
58 on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
59 if (e.props.hasSurvey) {
60 return next(e)
61 }
62
63 const ui = $.ui.resolve(e)
64 const { Box, Button, Text } = ui
65 const hasSvg = ui.Svg !== undefined
66 const [list, open] = [await read($, files), await read($, isExpanded)]
67 const total = list.reduce((sum, one) => sum + one.chars, 0)
68 const cols = e.viewport?.columns ?? 80
69 const groups = ORDER.map(kind => {
70 const group = list.filter(one => one.kind === kind)
71 return { kind, count: group.length, chars: group.reduce((a, one) => a + one.chars, 0) }
72 }).filter(one => one.count > 0)
73 const pct = (part: number) => (total === 0 ? 0 : Math.round((part / total) * 100))
74 const cells = (part: number, width: number) =>
75 total === 0 ? 0 : Math.max(1, Math.round((part / total) * width))
76 const stacked = (width: number) =>
77 groups.map(one => (
78 <Text color={COLOR[one.kind]}>{'▓'.repeat(cells(one.chars, width))}</Text>
79 ))
80 const summary =
81 list.length === 0 ? 'no instruction files loaded yet' : `${list.length} files · ~${tokens(total)} tok`
82
83 const toggle = (
84 <Box gap={1}>
85 {hasSvg && icon(ui, open ? 'chevron-down' : 'chevron-right', '#9ca3af', '')}
86 <Button
87 key="toggle"
88 label={hasSvg ? (open ? 'Collapse' : 'Expand ') : open ? '▾ Collapse' : '▸ Expand '}
89 onPress={() => update($, isExpanded, () => !open)}
90 />
91 </Box>
92 )
93
94 if (!open) {
95 return (
96 <Box gap={2}>
97 {toggle}
98 <Text bold>CONTEXT</Text>
99 <Text dimColor>{summary}</Text>
100 <Box>{stacked(Math.max(8, Math.min(24, cols - 50)))}</Box>
101 </Box>
102 )
103 }
104
105 const bar = Math.max(10, Math.min(30, cols - 30))
106
107 return (
108 <Box flexDirection="column" gap={1}>
109 <Box gap={2}>
110 {toggle}
111 <Text bold>CONTEXT</Text>
112 <Text dimColor>{summary}</Text>
113 </Box>
114 {groups.length > 0 && (
115 <Box flexDirection="column" gap={1}>
116 <Box>{stacked(Math.max(10, Math.min(60, cols - 10)))}</Box>
117 <Box gap={2}>
118 {groups.map(one => (
119 <Text color={COLOR[one.kind]}>
120 {one.kind} {one.count} · {tokens(one.chars)}
121 </Text>
122 ))}
123 </Box>
124 </Box>
125 )}
126 {groups.map(group => (
127 <Box flexDirection="column" gap={1}>
128 <Text bold color={COLOR[group.kind]}>
129 {group.kind.toUpperCase()}
130 </Text>
131 {list
132 .filter(one => one.kind === group.kind)
133 .map(one => {
134 const name = one.path.split('/').pop() ?? one.path
135 const dir = one.path.slice(0, one.path.length - name.length).replace(/^\/Users\/[^/]+/, '~')
136 const n = cells(one.chars, bar)
137 return (
138 <Box flexDirection="column">
139 <Box gap={2}>
140 <Box gap={1}>
141 {one.parent !== undefined && icon(ui, 'corner-down-right', '#9ca3af', '↳')}
142 {hasSvg && icon(ui, 'file-text', COLOR[group.kind], '')}
143 <Text bold>{name}</Text>
144 </Box>
145 <Text dimColor>~{tokens(one.chars)} tok</Text>
146 </Box>
147 <Box gap={1}>
148 <Text color={COLOR[group.kind]}>{'▓'.repeat(n)}</Text>
149 <Text dimColor>{'░'.repeat(Math.max(0, bar - n))}</Text>
150 <Text dimColor>{String(pct(one.chars)).padStart(3)}%</Text>
151 </Box>
152 <Text dimColor>{dir}</Text>
153 </Box>
154 )
155 })}
156 </Box>
157 ))}
158 </Box>
159 )
160 })
161}
162hooks/icons.tsx 26 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 'chevron-right': '<path d="m9 18 6-6-6-6" />',
5 'chevron-down': '<path d="m6 9 6 6 6-6" />',
6 'file-text': '<path d="M15 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7Z" /> <path d="M14 2v4a2 2 0 0 0 2 2h4" /> <path d="M10 9H8" /> <path d="M16 13H8" /> <path d="M16 17H8" />',
7 'corner-down-right': '<polyline points="15 10 20 15 15 20" /> <path d="M4 4v7a4 4 0 0 0 4 4h12" />',
8}
9
10export type IconName = keyof typeof PATHS
11
12export const svg = (name: IconName, color: string, size: number) =>
13 `<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>`
14
15// eslint-disable-next-line @typescript-eslint/no-explicit-any
16export type Ui = { Svg?: any; Text: any }
17
18export const icon = (ui: Ui, name: IconName, color: string, glyph: string, size = 16) => {
19 const { Svg, Text } = ui
20 return Svg === undefined ? (
21 <Text color={color}>{glyph}</Text>
22 ) : (
23 <Svg source={svg(name, color, size)} alt={name} width={size} height={size} />
24 )
25}
26types/index.d.ts 8 lines1export type Loaded = { path: string; kind: string; chars: number; parent?: string }
2
3declare module 'claude-code' {
4 interface PluginState {
5 'context-files': { files: Loaded[]; isExpanded: boolean }
6 }
7}
8