Fortschritt über dem Prompt: Zeit, Tool-Calls und jeder Helfer-Agent live

Ein Claude-Code-Mod, der über dem Prompt live zeigt, woran Claude gerade arbeitet: wie lange der Durchgang schon läuft, wie viele Tool-Calls es gab und, wenn Claude Helfer-Agenten startet, jeden Helfer mit Aufgabe, Modell und Status. Praktisch für lange Aufgaben, bei denen mehrere Subagents parallel arbeiten.
Im Terminal in Claude Code eintippen:
/plugin install fortschritt --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 fortschritt --marketplace boonpflug/claude-mods-deutsch
hooks/register.tsx 97 lines1import { atom, read, update } from 'claude-code'
2import type { EngineInterface, Register } from 'claude-code'
3
4import type { Run } from '../types'
5
6const run = atom({ plugin: 'fortschritt', key: 'run' } as const, null)
7
8const CRABS = ['🦀', '🦞', '🐙', '🦑', '🐡']
9
10function bar(done: number, total: number, width = 14) {
11 const filled = total === 0 ? 0 : Math.round((done / total) * width)
12 return '█'.repeat(filled) + '░'.repeat(width - filled)
13}
14
15function clock(ms: number) {
16 const s = Math.max(0, Math.floor(ms / 1000))
17 return `${Math.floor(s / 60)}:${String(s % 60).padStart(2, '0')}`
18}
19
20async function tick($: EngineInterface) {
21 while ((await read($, run))?.isRunning) {
22 const now = await $.clock.now()
23 await update($, run, r => (r ? { ...r, now } : r))
24 await $.clock.sleep(1000)
25 }
26}
27
28export const register: Register = on => {
29 on('turn.start', async ($, e, next) => {
30 if (e.agentId === undefined) {
31 const now = await $.clock.now()
32 const r = await read($, run)
33 // laufen noch Hintergrund-Helfer, gehört dieser Durchgang zum selben Lauf
34 const waiting = r?.helpers.some(one => !one.isDone)
35 await update($, run, prev =>
36 waiting && prev ? { ...prev, isRunning: true } : { startedAt: now, now, tools: 0, helpers: [], isRunning: true },
37 )
38 if (!r?.isRunning) void tick($)
39 }
40 return next(e)
41 })
42
43 on('turn.complete', async ($, e, next) => {
44 if (e.agentId !== undefined) {
45 await update($, run, r =>
46 r ? { ...r, helpers: r.helpers.map(one => (one.id === e.agentId ? { ...one, isDone: true } : one)) } : r,
47 )
48 } else {
49 await update($, run, r => (r ? { ...r, isRunning: r.helpers.some(one => !one.isDone) } : r))
50 }
51 return next(e)
52 })
53
54 on('tool.call', async ($, e, next) => {
55 await update($, run, r => (r ? { ...r, tools: r.tools + 1 } : r))
56 return next(e)
57 })
58
59 on('agent.spawn', async ($, e, next) => {
60 const started = await next(e)
61 if (started.deny === undefined && started.agentId !== undefined) {
62 const helper = { id: started.agentId, task: e.description, model: started.model.replace(/^claude-/, ''), isDone: false }
63 await update($, run, r => (r ? { ...r, helpers: [...r.helpers, helper] } : r))
64 }
65 return started
66 })
67
68 on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
69 const r: Run | null = await read($, run)
70 if (!r || e.props.hasSurvey) return next(e)
71
72 const { Box, Text } = $.ui.resolve(e)
73 const done = r.helpers.filter(one => one.isDone).length
74
75 return (
76 <Box flexDirection="column">
77 <Text>
78 <Text color={r.isRunning ? 'yellow' : 'green'} bold>
79 {r.isRunning ? (done < r.helpers.length && r.helpers.length > 0 ? '⏳ Helfer arbeiten' : '⏳ Claude arbeitet') : '✅ Fertig'}
80 </Text>
81 <Text dimColor> · {clock(r.now - r.startedAt)} · {r.tools} Tool-Calls</Text>
82 {r.helpers.length > 0 && (
83 <Text dimColor> · Helfer {done}/{r.helpers.length} </Text>
84 )}
85 {r.helpers.length > 0 && <Text color="cyan">{bar(done, r.helpers.length)}</Text>}
86 </Text>
87 {r.helpers.map((helper, i) => (
88 <Text dimColor={helper.isDone}>
89 {' '}{CRABS[i % CRABS.length]} {helper.task} <Text dimColor>({helper.model})</Text>{' '}
90 {helper.isDone ? <Text color="green">✓ fertig</Text> : <Text color="yellow">… arbeitet</Text>}
91 </Text>
92 ))}
93 </Box>
94 )
95 })
96}
97types/index.d.ts 9 lines1export type Helper = { id: string; task: string; model: string; isDone: boolean }
2export type Run = { startedAt: number; now: number; tools: number; helpers: Helper[]; isRunning: boolean }
3
4declare module 'claude-code' {
5 interface PluginState {
6 fortschritt: { run: Run | null }
7 }
8}
9