Commits files Claude edited as a git checkpoint after each turn, and adds /rollback to undo the last one.

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.ts 79 lines1import type { Register } from 'claude-code'
2
3const PREFIX = 'checkpoint:'
4
5export const register: Register = on => {
6 // Files edited during the current turn; the module's own variable, reset each turn.
7 let edited = new Set<string>()
8
9 on('session.start', async ($, e, next) => {
10 await $.command.register({
11 name: 'rollback',
12 description: 'Undo the last checkpoint commit',
13 })
14
15 return next(e)
16 })
17
18 for (const tool of ['Edit', 'Write'] as const) {
19 on('tool.call', { tool }, async ($, e, next) => {
20 const ran = await next(e)
21 if (ran.deny === undefined && ran.isError !== true) edited.add(e.file_path)
22
23 return ran
24 })
25 }
26
27 on('turn.complete', async ($, e, next) => {
28 const files = [...edited]
29 edited = new Set()
30 const done = await next(e)
31 if (files.length === 0) return done
32
33 const add = await $.process.run(['git', 'add', '--', ...files])
34 if (add.exitCode !== 0) {
35 $.ui.toast(`checkpoint failed: ${add.stderr.trim().slice(0, 80)}`)
36 return done
37 }
38
39 const names = files.map(f => f.split('/').pop()).join(', ')
40 const commit = await $.process.run([
41 'git', 'commit', '-m', `${PREFIX} ${names}`, '--only', '--', ...files,
42 ])
43 if (commit.exitCode !== 0) {
44 // Nothing to commit (edits left the files unchanged) is not worth a toast.
45 if (!/nothing (added )?to commit|no changes added/.test(commit.stdout + commit.stderr)) {
46 $.ui.toast(`checkpoint failed: ${commit.stderr.trim().slice(0, 80)}`)
47 }
48 return done
49 }
50
51 $.ui.toast(`Checkpoint saved: ${files.length} file${files.length === 1 ? '' : 's'}`)
52
53 return done
54 })
55
56 on('command.run', { command: 'rollback' }, async $ => {
57 const log = await $.process.run(['git', 'log', '-1', '--format=%h %s'])
58 if (log.exitCode !== 0) return { text: 'rollback: not a git repository with commits.' }
59
60 const [hash, ...rest] = log.stdout.trim().split(' ')
61 const subject = rest.join(' ')
62 if (!subject.startsWith(PREFIX)) {
63 return { text: `rollback: last commit (${hash}) is not a checkpoint; nothing undone.` }
64 }
65
66 const status = await $.process.run(['git', 'status', '--porcelain', '--untracked-files=no'])
67 if (status.stdout.trim() !== '') {
68 return { text: 'rollback: uncommitted changes would be lost; commit or stash them first.' }
69 }
70
71 const reset = await $.process.run(['git', 'reset', '--hard', 'HEAD~1'])
72 if (reset.exitCode !== 0) return { text: `rollback failed: ${reset.stderr.trim()}` }
73
74 $.ui.toast('Rolled back last checkpoint')
75
76 return { text: `Rolled back ${hash} (${subject}).` }
77 })
78}
79