SLOPSHOPPER

task-progress

Barra de progresso das tarefas (passo atual, 3/7, cor por avanço) acima do prompt

newbandguard
v0.1.0no licenseupdated 2026-10-13DiogoHSM/claude-mods/task-progress
A shopper browsing a rack in a slop shop
README

claude-mods

Mods pessoais para Claude Code (terminal e aba Code do app desktop).

Mods

ModO que faz
task-progressBarra de progresso acima do prompt com passo atual, contador (3/7) e cor que vai do vermelho ao verde.

Instalação

claude plugin marketplace add DiogoHSM/claude-mods
claude plugin install task-progress@diogo-mods --scope user

Ou, no prompt do Claude Code:

/plugin install task-progress --marketplace DiogoHSM/claude-mods

Desenvolvimento local

claude plugin marketplace add ~/Developer/Github/claude-mods
claude plugin install task-progress@diogo-mods --scope user

Depois de editar um mod, rode /reload-plugins.

Validação

claude plugin validate task-progress
claude plugin test task-progress
Source 2 files
hooks/register.tsx 108 lines
1import { atom, read, update } from 'claude-code'
2import type { Register } from 'claude-code'
3
4import type { Step } from '../types'
5
6const steps = atom({ plugin: 'task-progress', key: 'steps' } as const, [] as Step[])
7
8export function colorFor(ratio: number): string {
9  if (ratio >= 1) return '#22c55e'
10  if (ratio >= 0.75) return '#84cc16'
11  if (ratio >= 0.5) return '#eab308'
12  if (ratio >= 0.25) return '#f97316'
13  return '#ef4444'
14}
15
16
17export const register: Register = on => {
18  on('tool.call', { tool: 'TodoWrite' }, async ($, e, next) => {
19    const ran = await next(e)
20    if (ran.deny === undefined && !ran.isError) {
21      await update($, steps, () =>
22        e.todos.map((t, i) => ({ id: String(i), subject: t.content, active: t.activeForm, status: t.status })),
23      )
24    }
25    return ran
26  })
27
28  on('tool.call', { tool: 'TaskCreate' }, async ($, e, next) => {
29    const ran = await next(e)
30    if (ran.deny === undefined && !ran.isError) {
31      const id = ran.result.task.id
32      await update($, steps, s => [
33        ...(s.every(x => x.status === 'completed') ? [] : s.filter(x => x.id !== id)),
34        { id, subject: e.subject, active: e.activeForm ?? e.subject, status: 'pending' },
35      ])
36    }
37    return ran
38  })
39
40  on('tool.call', { tool: 'TaskUpdate' }, async ($, e, next) => {
41    const ran = await next(e)
42    if (ran.deny === undefined && !ran.isError) {
43      await update($, steps, s =>
44        e.status === 'deleted'
45          ? s.filter(x => x.id !== e.taskId)
46          : s.map(x =>
47              x.id === e.taskId
48                ? {
49                    ...x,
50                    subject: e.subject ?? x.subject,
51                    active: e.activeForm ?? x.active,
52                    status: e.status ?? x.status,
53                  }
54                : x,
55            ),
56      )
57    }
58    return ran
59  })
60
61  on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
62    const list = await read($, steps)
63    if (e.props.hasSurvey || list.length === 0) return next(e)
64
65    const total = list.length
66    const done = list.filter(s => s.status === 'completed').length
67
68    const current = list.find(s => s.status === 'in_progress')
69    const position = current ? list.indexOf(current) + 1 : Math.min(done + 1, total)
70    const ratio = done / total
71    const color = colorFor(ratio)
72
73    const label = done === total ? 'Concluído' : current ? current.active : (list[done]?.subject ?? '')
74    const counter = ` ${position}/${total}`
75    const { Box, Text } = $.ui.resolve(e)
76
77    if (e.surface === 'terminal') {
78      const width = Math.max(10, e.props.bodyColumns - counter.length - 1)
79      const filled = Math.round(ratio * width)
80      return (
81        <Box flexDirection="column">
82          <Box>
83            <Text color={color}>{'█'.repeat(filled)}</Text>
84            <Text dimColor>{'░'.repeat(width - filled)}</Text>
85            <Text color={color} bold>{counter}</Text>
86          </Box>
87          <Text dimColor wrap="truncate-end">{label}</Text>
88        </Box>
89      )
90    }
91
92    const pct = `${Math.round(ratio * 100)}%`
93    return (
94      <Box flexDirection="column">
95        <Box flexDirection="row" alignItems="center" gap={1}>
96          <Box flexGrow={1} flexDirection="row" backgroundColor="subtle" overflow="hidden">
97            <Box width={pct} backgroundColor={color}>
98              <Text> </Text>
99            </Box>
100          </Box>
101          <Text color={color} bold>{counter.trim()}</Text>
102        </Box>
103        <Text dimColor wrap="truncate-end">{label}</Text>
104      </Box>
105    )
106  })
107}
108
types/index.d.ts 8 lines
1export type Step = { id: string; subject: string; active: string; status: 'pending' | 'in_progress' | 'completed' }
2
3declare module 'claude-code' {
4  interface PluginState {
5    'task-progress': { steps: Step[] }
6  }
7}
8