Anexa a skill do seu design system ao primeiro pedido de peça visual da sessão. Configure o nome da skill para ligar.

Cinco mods para o Claude Code, escritos como plugins de function hooks. Desenham barras e faixas acima do prompt, avisam sobre cache e uso do plano, sobem um servidor de prévia para HTML e lembram o modelo de usar o seu design system.
Funcionam no terminal e na aba Code do app desktop. Textos e comandos estão em português.

CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1 no bloco env do ~/.claude/settings.json. Sem ela nenhum dos mods carrega.{
"env": {
"CLAUDE_CODE_ENABLE_FUNCTION_HOOKS": "1"
}
}
Se o arquivo já tem um bloco env, acrescente só a linha da variável.
Num terminal com claude aberto, instale cada mod que quiser:
/plugin install progresso --marketplace Kelvi-Maycon/claude-code-mods
/plugin install barra-uso --marketplace Kelvi-Maycon/claude-code-mods
/plugin install cache-frio --marketplace Kelvi-Maycon/claude-code-mods
/plugin install previa --marketplace Kelvi-Maycon/claude-code-mods
/plugin install ds-primeiro --marketplace Kelvi-Maycon/claude-code-mods
Na primeira vez o Claude Code pergunta se quer adicionar o marketplace: responda y. Depois escolha o escopo do usuário (o primeiro da lista). O mod já roda na sessão atual e nas próximas.
Instalado no escopo do usuário pelo terminal, o mod carrega também na aba Code do app desktop. O comando /plugin install só existe no terminal.

Barras de progresso acima do prompt. O modelo recebe uma ferramenta (progresso) e uma regra curta no system prompt: em tarefa com vários passos, cria uma barra com o plano e a atualiza conforme avança. Cada barra mostra o passo ativo, a etapa, o percentual e o estado (andamento, esperando você, falhou, concluída).
Comandos:
/progresso: mostra ou esconde as barras. Aceita on, off ou painel (abre o painel lateral)./progresso-demo: demonstração de 30 segundos com todos os estados, faixas, painel e sons./progresso-limpar: remove todas as barras e faixas.Ícones por tipo de agente: executor-leve, executor, executor-pesado, executor-design, investigador e leitor têm robôs próprios. Explore usa a lupa do investigador. general-purpose, Plan, claude, fork e qualquer outro nome usam o robô padrão, com o nome do tipo em cinza. Modelo e esforço vêm do frontmatter (name, model, effort) dos arquivos em ~/.claude/agents/.
Um passo cujo título termina com o tipo do agente entre parênteses, como Revisar tudo (executor-pesado), aparece no painel como agente planejado.
![]()
Uma linha de pílulas acima do campo:
5H e 7D: uso do plano nas janelas de 5 horas e 7 dias, com o tempo até renovar.CTX: contexto usado até a compactação automática, pelo limite que o Claude Code informa (275k quando ele não informa nenhum).CACHE: tempo restante do cache de prompt (TTL de 60 minutos, fixo) e taxa de acerto.DS on/off: só aparece com o ds-primeiro configurado. Clicar alterna o DS nesta sessão.Comando: /uso, com on ou off.
Quando a sessão ficou parada além do TTL do cache e o contexto passa de 100k tokens, o próximo envio regrava o cache inteiro e custa mais. O mod segura esse envio uma vez, devolve o texto ao campo e avisa. Enviar de novo dentro de 3 minutos passa direto. Comandos de barra (/compact, /clear) nunca são segurados.
Comando: /cache-frio mostra contexto e tempo de cache. /cache-frio ttl <minutos> ajusta o TTL (padrão 60), /cache-frio off e /cache-frio on desligam e ligam o aviso.
Quando o modelo grava ou edita um .html, o mod sobe um python3 -m http.server na pasta do arquivo, a partir da porta 8765, e mostra a URL acima do prompt com botões para abrir, copiar e parar. O modelo recebe uma linha dizendo que o servidor já está no ar, para não abrir outro. Comandos Bash que matariam o servidor (kill, pkill, killall no processo dele) são negados.
Arquivos em /tmp e /var/folders não viram prévia.
Comando: /previa <caminho> serve uma pasta, /previa status lista os servidores, /previa parar encerra.
Requisitos: python3 e lsof no PATH. O botão Abrir usa o comando open do macOS; em Linux ele não faz nada e o botão de copiar continua funcionando.
No primeiro pedido de peça visual da sessão (página, landing, dashboard, slide, tela, componente...), anexa ao contexto uma linha pedindo que o modelo carregue a skill do seu design system antes de criar. Antes do envio aparece uma faixa avisando, com o botão "Tirar deste envio". A detecção é por verbos e alvos em português.
Não anexa quando o pedido já cita um design system, uma identidade visual, a sua skill, o rótulo ou um dos termos extras. Depois de compactar ou de /clear, volta a anexar no próximo pedido visual.
Sem configuração o mod não faz nada: não anexa, não mostra faixa e não registra comando. Para ligar, configure as opções do plugin. A tela de opções aparece na instalação, e depois fica em /config:
| Opção | O que é | Exemplo |
|---|---|---|
skill | Nome da skill do design system. Vazio desliga o mod. | meu-design-system |
rotulo | Nome exibido na faixa, no toast e na linha anexada. Vazio usa o nome da skill. | Minha Marca |
ignorar | Termos extras, separados por vírgula, que dispensam a linha quando citados. | marca do cliente, sem ds |
As opções ficam salvas no ~/.claude/settings.json, em pluginConfigs.
Comando: /ds mostra o estado. /ds on e /ds off valem para a sessão; /ds sempre on e /ds sempre off mudam o padrão.
claude plugin update <mod>@claude-code-mods
Depois, numa sessão aberta, rode /reload-plugins.
claude plugin uninstall <mod>@claude-code-mods
Cada plugin fica em plugins/<nome>/, com os hooks em hooks/ e os testes em tests/.
claude plugin validate plugins/progresso
claude plugin test plugins/progresso
O tsconfig.json de cada plugin estende .claude-plugin/types/tsconfig.json, que o Claude Code gera no primeiro carregamento do plugin. Num clone novo o editor e o tsc acusam tipos ausentes até esse primeiro carregamento (ou um claude plugin validate). A pasta gerada fica fora do git.
Os sons do progresso são gerados por plugins/progresso/sounds/gerar.py.
A estrutura das barras do progresso foi portada do plan-progress (zycck/claude-mods), de Kirill Serditov, sob licença MIT. O texto da licença está em plugins/progresso/licenses/plan-progress-LICENSE.
MIT. Veja LICENSE.
hooks/register.tsx 180 lines1import { atom, read, update } from 'claude-code'
2import type { EngineInterface, Register } from 'claude-code'
3
4import { pedeDs } from './detecta'
5
6const anexado = atom({ plugin: 'ds-primeiro', key: 'anexado' } as const, false)
7const ligado = atom({ plugin: 'ds-primeiro', key: 'ligado' } as const, null)
8const pular = atom({ plugin: 'ds-primeiro', key: 'pular' } as const, false)
9const rascunho = atom({ plugin: 'ds-primeiro', key: 'rascunho' } as const, false)
10// O estado efetivo (`ligado ?? padrao`), publicado para outros mods lerem: `padrao` vive só neste módulo.
11const publicado = atom({ plugin: 'ds-primeiro', key: 'ativo' } as const, true)
12
13const USO = 'Use /ds, /ds on, /ds off, /ds sempre on ou /ds sempre off.'
14
15// Padrão guardado no store (`/ds sempre`) e o último resultado da detecção no
16// rascunho, para só gravar estado quando ele muda.
17let padrao = true
18let casa = false
19
20async function ativo($: EngineInterface) {
21 return (await read($, ligado)) ?? padrao
22}
23
24async function publica($: EngineInterface) {
25 const agora = await ativo($)
26 await update($, publicado, () => agora)
27}
28
29const texto = (valor: unknown) => (typeof valor === 'string' ? valor.trim() : '')
30
31export const register: Register = (on, options) => {
32 // Sem a skill do design system configurada o mod não faz nada: não anexa,
33 // não registra /ds, não desenha faixa e não publica `ativo` (a barra-uso
34 // esconde a pílula DS).
35 const skill = texto(options.skill)
36
37 if (skill === '') {
38 return
39 }
40
41 const rotulo = texto(options.rotulo) || skill
42 const termos = [skill, rotulo, ...texto(options.ignorar).split(',')]
43 const linha = `ds-primeiro: em peça visual o usuário usa por padrão o design system ${rotulo}. Carregue a skill ${skill} antes de criar, a menos que o pedido defina outra identidade. Se o pedido não for visual, ignore esta linha.`
44
45 on('session.start', async ($, e, next) => {
46 padrao = (await $.store.get('sempre')) !== false
47 await $.command.register({
48 name: 'ds',
49 description: `${rotulo} anexado ao primeiro pedido visual da sessão`,
50 argumentHint: '[on | off | sempre on | sempre off]',
51 })
52 await publica($)
53
54 return next(e)
55 })
56
57 on('command.run', { command: 'ds' }, async ($, e) => {
58 const args = e.args.trim().toLowerCase().split(/\s+/).join(' ')
59
60 if (args === 'on' || args === 'off' || args === 'sempre on' || args === 'sempre off') {
61 const liga = args.endsWith('on')
62
63 if (args.startsWith('sempre')) {
64 padrao = liga
65 await $.store.set('sempre', liga)
66 }
67
68 await update($, ligado, () => liga)
69 await publica($)
70
71 if (liga) {
72 await update($, anexado, () => false)
73 }
74 } else if (args !== '') {
75 return { text: USO }
76 }
77
78 const sessao = (await ativo($)) ? 'ligado nesta sessão' : 'desligado nesta sessão'
79 const linha = (await read($, anexado)) ? 'DS já anexado (/ds on anexa de novo)' : 'DS ainda não anexado'
80
81 return { text: `${sessao} · ${linha} · padrão ${padrao ? 'ligado' : 'desligado'}` }
82 })
83
84 on('prompt.edit', async ($, e, next) => {
85 const campo = await next(e)
86 const casaAgora = pedeDs(campo.text, termos)
87
88 if (casaAgora !== casa) {
89 casa = casaAgora
90 await update($, rascunho, () => casaAgora)
91 }
92
93 return campo
94 })
95
96 // O Enter do usuário: `composer` no terminal, `sdk` no app desktop.
97 on('prompt.submit', async ($, e, next) => {
98 if (e.origin.kind !== 'composer' && e.origin.kind !== 'sdk') {
99 return next(e)
100 }
101
102 const pulou = await read($, pular)
103 const anexa = !pulou && pedeDs(e.text, termos) && (await ativo($)) && !(await read($, anexado))
104 const entrou = await next(anexa ? { ...e, context: [...(e.context ?? []), linha] } : e)
105
106 // Um envio segurado por outro hook volta ao campo: nada muda até ele entrar.
107 if (entrou.drop !== undefined) {
108 return entrou
109 }
110
111 if (anexa) {
112 await update($, anexado, () => true)
113 $.ui.toast(`DS ${rotulo} anexado a este pedido · /ds off desliga`)
114 }
115
116 if (pulou) {
117 await update($, pular, () => false)
118 }
119
120 if (casa) {
121 casa = false
122 await update($, rascunho, () => false)
123 }
124
125 return entrou
126 })
127
128 // O DS carregado sai do contexto na compactação: rearma.
129 on('session.compact', async ($, e, next) => {
130 const feito = await next(e)
131
132 if (e.agentId === undefined && e.trigger !== 'precompute' && feito.skip === undefined) {
133 await update($, anexado, () => false)
134 }
135
136 return feito
137 })
138
139 on('session.end', async ($, e, next) => {
140 if (e.reason === 'clear' || e.reason === 'resume') {
141 casa = false
142 await update($, anexado, () => false)
143 await update($, ligado, () => null)
144 await update($, pular, () => false)
145 await update($, rascunho, () => false)
146 await publica($)
147 }
148
149 return next(e)
150 })
151
152 on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
153 const mostra =
154 !e.props.hasSurvey &&
155 (await read($, rascunho)) &&
156 !(await read($, anexado)) &&
157 !(await read($, pular)) &&
158 (await ativo($))
159
160 const abaixo = await next(e)
161
162 if (!mostra) {
163 return abaixo
164 }
165
166 const { Box, Button, Text } = $.ui.resolve(e)
167
168 // Empilha sobre as faixas dos outros mods; a deste fica junto do campo.
169 return (
170 <Box flexDirection="column">
171 {abaixo}
172 <Box>
173 <Text dimColor>{`DS ${rotulo} será anexado a este pedido `}</Text>
174 <Button key="tirar" label="Tirar deste envio" onPress={() => update($, pular, () => true)} />
175 </Box>
176 </Box>
177 )
178 })
179}
180hooks/detecta.ts 36 lines1const VERBO =
2 /\b(?:cria|crie|criar|faz|faca|fazer|refaz|refaca|recria|monta|monte|constroi|construa|desenha|redesenha|melhora|melhore)\b/
3
4const ALVO =
5 /\b(?:relatorio visual|(?:pagina|landing|lp|site|html|dashboard|painel|visualizacao|slide|deck|layout|tela|hero|componente|blog|video|motion|ui|interface)s?|paineis|visualizacoes)\b/
6
7// O pedido já cita um design system, dispensa skill ou nomeia a identidade de outra marca.
8const FORA =
9 /design system|identidade visual|\bsem ds\b|nao use skill|\b(?:design|identidade|marca) d[ao] /
10
11// Caminho de arquivo com duas partes ou mais, entre aspas ou solto: uma pasta
12// com o nome do DS no meio de um caminho não é o usuário citando o DS. A skill
13// citada como /nome-da-skill tem uma parte só e fica.
14const CAMINHO = /(['"])~?\/[^'"]*\/[^'"]*\1|(?:^|\s)~?\/[^\s/]+\/\S*/g
15
16/** Minúsculas, sem acento: a forma em que o texto e os termos são comparados. */
17export function normaliza(texto: string): string {
18 return texto.toLowerCase().normalize('NFD').replace(/[̀-ͯ]/g, '')
19}
20
21/**
22 * Diz se o texto pede uma peça visual sem definir a identidade. `termos` são
23 * os nomes que, citados no pedido, dispensam a linha: a skill, o rótulo e os
24 * termos extras da configuração.
25 */
26export function pedeDs(texto: string, termos: readonly string[] = []): boolean {
27 const t = normaliza(texto).replace(CAMINHO, ' ')
28 const citaTermo = termos.some(termo => {
29 const n = normaliza(termo).trim()
30
31 return n !== '' && t.includes(n)
32 })
33
34 return VERBO.test(t) && ALVO.test(t) && !FORA.test(t) && !citaTermo
35}
36types/index.d.ts 17 lines1declare module 'claude-code' {
2 interface PluginState {
3 'ds-primeiro': {
4 /** A linha do DS já entrou nesta sessão. */
5 anexado: boolean
6 /** `/ds on` ou `/ds off` nesta sessão; null segue o padrão guardado. */
7 ligado: boolean | null
8 /** "Tirar deste envio" foi pressionado para o rascunho atual. */
9 pular: boolean
10 /** O rascunho no campo casa a detecção. */
11 rascunho: boolean
12 /** Ligado nesta sessão (`/ds on|off`, ou o padrão de `/ds sempre`); a `barra-uso` lê. */
13 ativo: boolean
14 }
15 }
16}
17