SLOPSHOPPER

proximos-passos

Depois de cada resposta, o Haiku propõe três próximos pedidos em botões acima da caixa de mensagem (teclas 1-3)

newbandcommandpromptmodel
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · proximos-passos
› fix the failing auth test and add an audit log call ⏺ Read(src/auth.ts) ⎿ Read 6 lines ⏺ Update(src/auth.ts) ⎿ Added 2 lines, removed 1 line ⏺ Bash(bun test) ⎿ 3 pass, 1 fail ● Done. refresh now rejects expired claims and logs an audit event. ✻ Worked for 42s · done 4:20 PM › /custos ⎿ proximos-passos: Médias por chamada, em tokens, como a Anthropic informa: ⎿ proximos-passos: • Resposta normal do Claude (referência) (1): 91.000 lidos do cache · 2.100 novos · 4.300 gravados no cache · ⎿ proximos-passos: • Sugestões pelo Haiku (1): 0 lidos do cache · 12 novos · 0 gravados no cache · 1 de saída · 0.0 s ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts
README

claude-mods

Mods do Claude Code (function hooks) que uso todo dia. · Claude Code mods (function hooks) I use every day.

Português · English


Português

Seis mods, cada um resolvendo um problema que apareceu no uso real. A interface e as mensagens dos mods estão em português.

Instalar

Precisa do Claude Code 2.1.287 ou mais novo.

/plugin marketplace add diegocamara89/claude-mods
/plugin install sonnet-por-padrao@claude-mods-diego

Troque sonnet-por-padrao pelo nome de qualquer mod da tabela. Para testar sem instalar: claude --plugin-dir ./mods/<nome>.

Os mods

ModO que fazO que acessa
sonnet-por-padraoSubagente e agente de script (Workflow) criados sem modelo escrito rodam no Sonnet, em vez de herdar o Opus da conversa. Opus continua valendo quando escrito de propósito.Reescreve o modelo do subagente e o script do Workflow; grava um registro das decisões (com a descrição de cada subagente) em LOCALAPPDATA no Windows ou TMPDIR//tmp fora dele.
revisor-com-provaQuando volta o resultado de um revisor (Codex, codex exec, agy, council), anexa ao resultado um lembrete: achado só vale com prova, reproduza antes de corrigir, "aprovado sem achados" é válido.Só lê o comando; anexa texto ao resultado, que só o modelo vê. Os padrões agy.py e council.py são de skills do autor; sem elas, simplesmente não disparam.
proximos-passosDepois de cada resposta, o Haiku propõe três próximos pedidos em botões acima da caixa (teclas 1-3). /custos mostra os tokens medidos.Chama o Haiku (cerca de 1 mil tokens por sugestão, medido); desenha a faixa.
painel-vivoPainel ao vivo, para ler de relance: uma manchete com Pendências, Atividade e Progresso; fila do que pede você (permissão, pergunta, travado) e avisos; trabalhadores em árvore (subagentes, agentes de Workflow, comandos, agy e Codex aninhados em quem os chamou), cada um com tempo, modelo, tokens e o que faz agora; outras sessões em andamento numa linha; cotas de 5 h e 7 dias. "Explicar" traduz um trabalho para leigo e alimenta um glossário (/glossario).Envolve cada comando Bash e PowerShell para copiar a saída num arquivo local (tee, em LOCALAPPDATA ou TMPDIR) e marcar onde o agy grava os passos (AGY_EVENTS_DIR); apaga esses arquivos depois de 1 dia; se o rtk estiver instalado, passa o comando por ele antes; guarda um resumo de cada sessão no armazenamento do plugin para as outras sessões verem; lê o título da conversa no transcript com um script Python curto; chama o Haiku só no "Explicar".
lixeiraNega o apagar de vez e devolve ao Claude o comando que manda para a lixeira do sistema; barra comandos sem volta (git reset --hard, push --force, formatar, esvaziar lixeira, desligar ou reiniciar o computador, Format-Volume, Clear-Disk) até o usuário dizer sim (LIXEIRA_OK=1 no Bash, $env:LIXEIRA_OK=1; no PowerShell). Também pega o apagar pela esteira do PowerShell (`gci pasta \Remove-Item`). Temporários, caches e arquivos criados na conversa ficam livres.Lê o comando e nega quando precisa. No Windows usa o lixeira.ps1 que vem junto; no macOS e no Linux sugere trash ou gio trash.
varredura-pushAntes de git push e de publicação pelo gh (no Bash e no PowerShell), confere os commits que vão subir procurando chave, token e senha. Barra o envio se achar. Liberação pontual com VARREDURA_OK=1.Põe python varredura.py && antes do push. Usa o gitleaks se estiver no PATH; sem ele, regras de reserva.

Por que o revisor-com-prova

Revisor por LLM tende a achar defeito em trabalho correto quando o pedido manda "achar o que quebra": num estudo de 2026, o GPT-4o reprovou 73% de código correto com esse enquadramento, contra 26% com um pedido simples (arXiv 2603.00539). Do outro lado, quem recebe a crítica aceita sem conferir: diante de 48 relatórios falsos, o Claude Code rejeitou 1 (arXiv 2604.11950). Exigir prova reverteu isso para 85–96% de rejeição. O mod coloca essa regra exatamente onde ela faz falta: no resultado do revisor, e não em toda mensagem.

Requisitos e limites

  • painel-vivo: a saída ao vivo usa bash (no Windows, o Git Bash que o Claude Code já usa); o nome das outras sessões precisa de python no PATH; feito para o app desktop (no terminal, desenha uma versão só texto). O Codex não informa tokens: o painel mostra só o modelo, quando o comando o escolhe.
  • varredura-push: precisa de Python (python no Windows, python3 no macOS e no Linux).
  • sonnet-por-padrao: não vê script de Workflow rodado a partir de arquivo (scriptPath) nem Workflow salvo por nome.
  • revisor-com-prova: também dispara quando o agy é usado só para extrair dados; o lembrete começa com "se este resultado traz crítica".

Créditos

A leitura de comandos da lixeira (aspas, sudo/env, alvos seguros) veio de ideias do launch-codes, da OneWave AI (MIT).

Licença: MIT.


English

Six mods, each fixing a problem that came up in real use. The mods' UI and messages are in Portuguese.

Install

Requires Claude Code 2.1.287 or later.

/plugin marketplace add diegocamara89/claude-mods
/plugin install sonnet-por-padrao@claude-mods-diego

Replace sonnet-por-padrao with any mod name from the table. To try one without installing: claude --plugin-dir ./mods/<name>.

The mods

ModWhat it doesWhat it touches
sonnet-por-padrao ("Sonnet by default")Subagents and Workflow script agents created without an explicit model run on Sonnet instead of inheriting the session's Opus. An explicit opus is kept.Rewrites the subagent's model and the Workflow script; logs its decisions (with each subagent's description) to LOCALAPPDATA on Windows or TMPDIR//tmp elsewhere.
revisor-com-prova ("reviewer with evidence")When a reviewer returns (Codex, codex exec, agy, council), attaches a reminder to the result: a finding counts only with evidence, reproduce before fixing, "approved with no findings" is valid.Reads the command only; attaches model-only text to the result. The agy.py and council.py patterns come from the author's own skills; without them they simply never fire.
proximos-passos ("next steps")After each answer, Haiku proposes three next requests as buttons above the prompt (keys 1-3). /custos shows measured token use.Calls Haiku (about 1k tokens per suggestion, measured); draws the band.
painel-vivo ("live pane")Glanceable live pane: a headline with Pending, Activity and Progress; a queue of what needs you (permission, question, stuck worker) and warnings; workers as a tree (subagents, Workflow agents, commands, agy and Codex nested under whoever called them), each with elapsed time, model, tokens and what it is doing now; other busy sessions in one line; 5-hour and 7-day rate limits. "Explicar" explains a job in plain language and feeds a glossary (/glossario).Wraps each Bash and PowerShell command to copy its output to a local file (tee, in LOCALAPPDATA or TMPDIR) and to tell agy where to write its steps (AGY_EVENTS_DIR); deletes those files after 1 day; if rtk is installed, runs the command through it first; keeps a per-session summary in the plugin store so other sessions can see it; reads the conversation title from the transcript with a short Python script; calls Haiku only on "Explicar".
lixeira ("trash")Denies permanent deletes and hands Claude the command that moves the files to the system trash; blocks no-way-back commands (git reset --hard, push --force, format, emptying the trash, shutting down or restarting the computer, Format-Volume, Clear-Disk) until the user says yes (LIXEIRA_OK=1 in Bash, $env:LIXEIRA_OK=1; in PowerShell). It also catches deletes through a PowerShell pipeline (`gci folder \Remove-Item`). Temp folders, caches and files written in the session stay free.Reads the command and denies when needed. On Windows it uses the bundled lixeira.ps1; on macOS and Linux it suggests trash or gio trash.
varredura-push ("push scan")Before git push and gh publishing (in Bash and PowerShell), scans the commits about to leave for keys, tokens and passwords, and blocks the push on a hit. One-off override with VARREDURA_OK=1.Prepends python varredura.py && to the push. Uses gitleaks when on PATH; fallback rules otherwise.

Why revisor-com-prova

LLM reviewers find defects in correct work when told to "find what breaks": in a 2026 study GPT-4o rejected 73% of correct code under that framing, against 26% with a plain prompt (arXiv 2603.00539). On the receiving side, critique gets accepted unchecked: given 48 false reports, Claude Code rejected 1 (arXiv 2604.11950). Requiring evidence moved that to 85–96% rejection. The mod puts the rule exactly where it is needed: on the reviewer's result, not on every message.

Requirements and limits

  • painel-vivo: live output uses bash (on Windows, the Git Bash Claude Code already uses); other sessions' names need python on PATH; built for the desktop app (the terminal gets a text-only version). Codex does not report tokens: the pane shows only its model, when the command picks one.
  • varredura-push: needs Python (python on Windows, python3 on macOS and Linux).
  • sonnet-por-padrao: does not see Workflow scripts run from a file (scriptPath) or saved Workflows run by name.
  • revisor-com-prova: also fires when agy is used only for extraction; the reminder starts with "if this result carries critique".

Credits

The command parsing in lixeira (quotes, sudo/env, safe targets) builds on ideas from launch-codes by OneWave AI (MIT).

License: MIT.

Source 2 files
hooks/register.tsx 138 lines
1import { atom, read, update } from 'claude-code'
2import type { Register } from 'claude-code'
3
4import type { Opcao } from '../types'
5
6// Depois de cada resposta, o Haiku propõe 3 próximos pedidos (botões acima da caixa).
7// Medido em 02/10: o Haiku lê ~1 mil tokens; o modelo principal releria a conversa inteira (~850 mil).
8const opcoes = atom({ plugin: 'proximos-passos', key: 'opcoes' } as const, [])
9const gerando = atom({ plugin: 'proximos-passos', key: 'gerando' } as const, false)
10const nativa = atom({ plugin: 'proximos-passos', key: 'nativa' } as const, '')
11
12const SISTEMA = `Você sugere os próximos passos de uma conversa de trabalho entre um usuário e o assistente Claude.
13O usuário escreve curto e direto e decide o que fazer; o assistente executa. Escreva os pedidos no idioma em que o usuário escreve.
14Leia o último pedido do usuário e a resposta do assistente e proponha exatamente 3 próximos pedidos que o USUÁRIO mandaria agora.
15- Se a resposta terminou com uma pergunta ou com opções, as sugestões são as respostas plausíveis a ela (a recomendada primeiro).
16- Senão, os passos mais úteis a partir dali: continuar, conferir/testar, ou mudar de rumo.
17- Cada pedido escrito como o usuário escreveria, na primeira pessoa dele, imperativo curto ("sim, pode aplicar").
18- Nada genérico ("continue", "obrigado"). Nada que repita o que já foi feito.
19Responda APENAS um JSON válido, sem cercas de código: [{"rotulo": "<até 6 palavras>", "pedido": "<o texto que será enviado>"}, ...]`
20
21let ultimoPedido = ''
22
23// Tokens exatamente como a resposta da Anthropic informa (campo usage).
24async function medir($: any, modo: 'turno' | 'haiku', ms: number, u: any) {
25  const m = {
26    modo, ms,
27    novos: Number(u?.input_tokens ?? 0),
28    cache: Number(u?.cache_read_input_tokens ?? 0),
29    escrita: Number(u?.cache_creation_input_tokens ?? 0),
30    saida: Number(u?.output_tokens ?? 0),
31  }
32  const lista = ((await $.store.get('medicoes2')) as any[] | undefined) ?? []
33  await $.store.set('medicoes2', [...lista, m].slice(-600))
34}
35
36function ler(texto: string): Opcao[] {
37  const j = JSON.parse(String(texto).replace(/^[\s\S]*?(\[)/, '$1').replace(/\][^\]]*$/, ']'))
38  return Array.isArray(j)
39    ? j.filter((o: any) => o?.rotulo && o?.pedido).slice(0, 3)
40      .map((o: any) => ({ rotulo: String(o.rotulo).slice(0, 60), pedido: String(o.pedido) }))
41    : []
42}
43
44async function sugerir($: any, resposta: string) {
45  await update($, gerando, () => true)
46  let lista: Opcao[] = []
47  try {
48    const t0 = await $.clock.now()
49    const r: any = await $.model.complete({
50      model: 'haiku', system: SISTEMA, maxTokens: 300, timeoutMs: 15_000,
51      prompt: `Último pedido do usuário:\n${ultimoPedido.slice(0, 1000)}\n\nFim da resposta do assistente:\n${resposta.slice(-3000)}`,
52    })
53    await medir($, 'haiku', (await $.clock.now()) - t0, r.usage).catch(() => undefined)
54    if (r.isAnswered) lista = ler(r.text)
55  } catch {
56    /* erro, limite de tempo ou resposta fora do formato: sem sugestão desta vez */
57  } finally {
58    // Sempre desliga o "pensando", senão a faixa fica presa.
59    await update($, opcoes, () => lista)
60    await update($, gerando, () => false)
61  }
62}
63
64const fmt = (n: number) => Math.round(n).toLocaleString('pt-BR')
65
66export const register: Register = on => {
67  on('session.start', async ($, e, next) => {
68    await $.command.register({ name: 'custos', description: 'Tokens das sugestões comparados com uma resposta normal' })
69    // Um recarregamento no meio de uma sugestão deixaria o "pensando" ligado para sempre.
70    await update($, gerando, () => false)
71    return next(e)
72  })
73
74  on('command.run', { command: 'custos' }, async $ => {
75    const todas = (((await $.store.get('medicoes2')) as any[] | undefined) ?? []).filter(m => m.modo !== 'claude')
76    if (!todas.length) return { text: 'Ainda não há medições.' }
77    const nomes: Record<string, string> = { turno: 'Resposta normal do Claude (referência)', haiku: 'Sugestões pelo Haiku' }
78    const linhas: string[] = ['Médias por chamada, em tokens, como a Anthropic informa:']
79    for (const modo of ['turno', 'haiku']) {
80      const l = todas.filter(m => m.modo === modo)
81      if (!l.length) continue
82      const media = (k: string) => l.reduce((s, m) => s + Number(m[k] ?? 0), 0) / l.length
83      const ms = l.map(m => m.ms).filter(Boolean).sort((a, b) => a - b)
84      linhas.push(
85        `• ${nomes[modo]} (${l.length}): ${fmt(media('cache'))} lidos do cache · ${fmt(media('novos'))} novos · ${fmt(media('escrita'))} gravados no cache · ${fmt(media('saida'))} de saída` +
86        (ms.length ? ` · ${(ms[Math.floor(ms.length / 2)] / 1000).toFixed(1)} s` : ''),
87      )
88    }
89    return { text: linhas.join('\n') }
90  })
91
92  // Só para não repetir nos botões o que já está no Tab.
93  on('prompt.suggest', async ($, e: any, next) => {
94    if (e.origin?.kind === 'suggestion' && e.text) await update($, nativa, () => String(e.text))
95    return next(e)
96  })
97
98  on('prompt.submit', async ($, e, next) => {
99    ultimoPedido = e.text
100    await update($, opcoes, () => [])
101    await update($, nativa, () => '')
102    return next(e)
103  })
104
105  on('turn.complete', async ($, e, next) => {
106    const r = await next(e)
107    if (e.agentId || e.reason !== 'answer') return r
108    if (e.usage) await medir($, 'turno', e.durationMs, e.usage).catch(() => undefined)
109    // Mensagens automáticas de outros mods e respostas curtas não pedem sugestão.
110    // Mensagem automática de outro mod começa com "[nome-do-mod]".
111    if (/^\s*\[[\w-]+\]/.test(ultimoPedido) || e.answer.split(/\s+/).length < 15) return r
112    void sugerir($, e.answer)
113    return r
114  })
115
116  on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
117    const n = await read($, nativa)
118    const mesmo = (a: string, b: string) => a.trim().toLowerCase() === b.trim().toLowerCase()
119    const botoes = (await read($, opcoes)).filter(o => !n || !mesmo(o.pedido, n))
120    const ocupado = await read($, gerando)
121    if (e.props.hasSurvey || e.props.isWorking || (!botoes.length && !ocupado)) return next(e)
122    const { Box, Button, Text } = $.ui.resolve(e)
123    const enviar = (o: Opcao) => async () => {
124      await update($, opcoes, () => [])
125      await $.prompt.submit({ text: o.pedido, asUser: true })
126    }
127    return (
128      <Box flexDirection="row" flexWrap="wrap" columnGap={2}>
129        {ocupado && !botoes.length && <Text dimColor>pensando nos próximos passos…</Text>}
130        {botoes.map((o, i) => (
131          <Button key={`op${i}`} label={o.rotulo} hotkey={String(i + 1)} {...(i === 0 ? { variant: 'primary' as const } : {})} onPress={enviar(o)} />
132        ))}
133        {botoes.length > 0 && <Button key="fechar" plain label="✕" role="dismiss" onPress={() => update($, opcoes, () => [])} />}
134      </Box>
135    )
136  })
137}
138
types/index.d.ts 8 lines
1export type Opcao = { rotulo: string; pedido: string }
2
3declare module 'claude-code' {
4  interface PluginState {
5    'proximos-passos': { opcoes: Opcao[]; gerando: boolean; nativa: string }
6  }
7}
8