Spielt nach jedem Durchgang alle Datei-Änderungen Schritt für Schritt ab

Ein Claude-Code-Mod, der jede Datei-Änderung eines Durchgangs mitschreibt. Mit /replay öffnet sich ein Panel, in dem du Änderung für Änderung durchgehst: welche Datei, was entfernt und was hinzugefügt wurde. Funktioniert für Code genauso wie für Texte, etwa einen Artikel-Entwurf, bei dem du genau sehen willst, wo Claude etwas umgeschrieben hat.
Im Terminal in Claude Code eintippen:
/plugin install replay --marketplace boonpflug/claude-mods-deutsch
Danach y zum Hinzufügen des Marketplace und mit Enter den Bereich „user“ wählen. Der Mod läuft sofort.
Aus der Shell geht es auch: claude plugin install replay --marketplace boonpflug/claude-mods-deutsch
hooks/register.tsx 101 lines1import { atom, read, update } from 'claude-code'
2import type { EngineInterface, Register } from 'claude-code'
3
4import type { Step } from '../types'
5
6const PANE = 'replay'
7const steps = atom({ plugin: 'replay', key: 'steps' } as const, [])
8const current = atom({ plugin: 'replay', key: 'current' } as const, [])
9const at = atom({ plugin: 'replay', key: 'at' } as const, 0)
10
11const lines = (s: string) => (s === '' ? [] : s.split('\n'))
12
13async function relative($: EngineInterface, path: string) {
14 const cwd = await $.session.cwd()
15 return path.startsWith(cwd + '/') ? path.slice(cwd.length + 1) : path
16}
17
18async function record($: EngineInterface, step: Step) {
19 await update($, current, list => [...list, step])
20}
21
22// spielt die Änderungen automatisch ab, eine alle 2,5 Sekunden
23async function play($: EngineInterface) {
24 const total = (await read($, steps)).length
25 for (let i = 1; i < total; i++) {
26 await $.clock.sleep(2500)
27 await update($, at, () => i)
28 }
29}
30
31export const register: Register = on => {
32 on('session.start', async ($, e, next) => {
33 await $.command.register({ name: 'replay', description: 'Änderungen des letzten Durchgangs abspielen' })
34 return next(e)
35 })
36
37 on('turn.start', async ($, e, next) => {
38 if (e.agentId === undefined) await update($, current, () => [])
39 return next(e)
40 })
41
42 on('tool.call', { tool: 'Edit' }, async ($, e, next) => {
43 const ran = await next(e)
44 if (!ran.isError) {
45 await record($, { file: await relative($, e.file_path), removed: lines(e.old_string), added: lines(e.new_string) })
46 }
47 return ran
48 })
49
50 on('tool.call', { tool: 'Write' }, async ($, e, next) => {
51 const ran = await next(e)
52 if (!ran.isError) {
53 await record($, { file: await relative($, e.file_path), removed: [], added: lines(e.content).slice(0, 12) })
54 }
55 return ran
56 })
57
58 on('turn.complete', async ($, e, next) => {
59 const done = await next(e)
60 const list = await read($, current)
61 if (list.length > 0) {
62 await update($, steps, () => list)
63 await update($, at, () => 0)
64 $.ui.toast(`🎬 ${list.length} Änderungen · /replay zum Abspielen`, { timeoutMs: 6000 })
65 }
66 return done
67 })
68
69 on('command.run', { command: 'replay' }, async $ => {
70 await update($, at, () => 0)
71 await $.ui.open({ id: PANE, title: 'Replay' })
72 void play($)
73 return { text: 'Replay läuft.' }
74 })
75
76 on('ui.render', { component: 'Pane', requestId: PANE }, async ($, e) => {
77 const { Box, Button, Text } = $.ui.resolve(e)
78 const list = await read($, steps)
79 if (list.length === 0) return <Text dimColor>Noch keine Änderungen aufgezeichnet.</Text>
80
81 const i = Math.min(await read($, at), list.length - 1)
82 const step = list[i]!
83 const files = new Set(list.map(s => s.file)).size
84
85 return (
86 <Box flexDirection="column">
87 <Text bold>🎬 Änderung {i + 1} von {list.length} · {files} Dateien</Text>
88 <Text color="cyan">{step.file}</Text>
89 <Text> </Text>
90 {step.removed.slice(0, 10).map(l => <Text color="red">- {l}</Text>)}
91 {step.added.slice(0, 10).map(l => <Text color="green">+ {l}</Text>)}
92 <Text> </Text>
93 <Box flexDirection="row" gap={1}>
94 <Button key="prev" label="1 ◀ Zurück" hotkey="1" onPress={() => update($, at, n => Math.max(0, n - 1))} />
95 <Button key="next" label="2 Weiter ▶" hotkey="2" onPress={() => update($, at, n => Math.min(list.length - 1, n + 1))} />
96 </Box>
97 </Box>
98 )
99 })
100}
101types/index.d.ts 8 lines1export type Step = { file: string; removed: string[]; added: string[] }
2
3declare module 'claude-code' {
4 interface PluginState {
5 replay: { steps: Step[]; current: Step[]; at: number }
6 }
7}
8