/touched opens a pane listing every file Claude read or edited this session, with counts

These are the mods from the video "Claude Code Mods: 3 Official Mods + 3 I Built Without Writing Code". Claude Code wrote each one from a single sentence. They were tested in Claude Code 2.1.288.
| Mod | What it does | Prompt used |
|---|---|---|
secret-guard | Blocks reads of .env files and printing API keys, and shows a red warning above the prompt | Make me a mod called secret-guard. If Claude tries to read a .env file or print an API key, block it and show a red warning above the prompt saying what it blocked. |
checkpoint | Commits the files Claude edited after every turn, and adds /rollback to undo the last checkpoint | Make me a mod called checkpoint. After every turn where Claude edited files, commit the changes to git as a checkpoint and show a toast. Add a /rollback command that undoes the last checkpoint. |
touched | /touched opens a pane listing every file Claude read or edited this session | Make me a mod with a /touched command. It opens a pane that lists every file Claude has read or edited in this session, with a count for each. (then: show paths relative to the project folder and put edited files in orange) |
A mod runs with your permissions and isn't sandboxed. Read what each one does first:
claude plugin validate ./secret-guard
The calls: line lists everything the mod can do. None of these three make network requests or read environment variables.
Requires Claude Code 2.1.287 or later (claude update). Inside a Claude Code session:
/plugin install secret-guard --marketplace sverma24/claude-code-mods
Or from your shell:
claude plugin marketplace add sverma24/claude-code-mods
claude plugin install secret-guard@sauravgenai
Swap secret-guard for checkpoint or touched. Run /reload-plugins in an open session, or start a new one.
Clone this repo, then:
claude --plugin-dir ./secret-guard
That loads it for one session only. Run /plugin and you'll see 1 mod active.
secret-guard matches patterns in the tool call. A command that builds the path or variable name indirectly can get past it. Treat it as a seatbelt, not a vault.checkpoint only tracks files changed with the Edit and Write tools, not files changed through shell commands. /rollback refuses if you have uncommitted changes.hooks/register.tsx 76 lines1import { atom, read, update } from 'claude-code'
2import type { Register } from 'claude-code'
3
4import type { Touch } from '../types'
5
6const PANE = 'touched'
7const EDITED = '#d97757'
8const files = atom({ plugin: 'touched', key: 'files' } as const, [])
9
10const bump = (list: Touch[], path: string, kind: 'reads' | 'edits'): Touch[] => {
11 const found = list.some(one => one.path === path)
12 const base = found ? list : [...list, { path, reads: 0, edits: 0 }]
13
14 return base.map(one => (one.path === path ? { ...one, [kind]: one[kind] + 1 } : one))
15}
16
17export const register: Register = on => {
18 on('session.start', async ($, e, next) => {
19 await $.command.register({
20 name: 'touched',
21 description: 'Show files Claude has read or edited this session',
22 })
23
24 return next(e)
25 })
26
27 on('command.run', { command: 'touched' }, async $ => {
28 await $.ui.open({ id: PANE, title: 'Touched files' })
29
30 return { text: 'Touched files pane opened.' }
31 })
32
33 on('tool.call', { tool: 'Read' }, async ($, e, next) => {
34 await update($, files, list => bump(list, e.file_path, 'reads'))
35
36 return next(e)
37 })
38
39 on('tool.call', { tool: 'Edit' }, async ($, e, next) => {
40 await update($, files, list => bump(list, e.file_path, 'edits'))
41
42 return next(e)
43 })
44
45 on('tool.call', { tool: 'Write' }, async ($, e, next) => {
46 await update($, files, list => bump(list, e.file_path, 'edits'))
47
48 return next(e)
49 })
50
51 on('ui.render', { component: 'Pane', requestId: PANE }, async ($, e) => {
52 const { Box, Text } = $.ui.resolve(e)
53 const list = await read($, files)
54 const root = (await $.session.cwd()).replace(/\/$/, '') + '/'
55 const shown = (path: string) => (path.startsWith(root) ? path.slice(root.length) : path)
56 const room = Math.max(1, (e.viewport?.rows ?? 24) - 5)
57 const sorted = [...list].sort((a, b) => b.reads + b.edits - (a.reads + a.edits))
58
59 return (
60 <Box flexDirection="column">
61 {sorted.length === 0 && <Text dimColor>No files touched yet.</Text>}
62 {sorted.slice(0, room).map(one => (
63 <Text color={one.edits > 0 ? EDITED : undefined}>
64 {String(one.reads + one.edits).padStart(3)}x {shown(one.path)}
65 <Text dimColor>
66 {' '}
67 ({one.reads} read, {one.edits} edit)
68 </Text>
69 </Text>
70 ))}
71 {sorted.length > room && <Text dimColor>+{sorted.length - room} more</Text>}
72 </Box>
73 )
74 })
75}
76types/index.d.ts 8 lines1export type Touch = { path: string; reads: number; edits: number }
2
3declare module 'claude-code' {
4 interface PluginState {
5 touched: { files: Touch[] }
6 }
7}
8