SLOPSHOPPER

session-dashboard

Painel lateral com todas as sessões ativas: trabalhando, concluída ou esperando resposta. Abra com /sessoes.

newpaneguardcommandprompttimer
A shopper browsing a rack in a slop shop
Preview could not run: ReferenceError: process is not defined at anonymous (file:///inner.js:48:21) at run (inner.js:982:9) at /home/runner/work/slopshopper/slopshopper/scripts/harness/run-one.ts:116:36
README

claude-code-mods

Mods que eu mesmo criei para o Claude Code (interface e comportamento: painéis, comandos, hooks). Fonte completa de cada um.

ModVersãoO que fazPasta
assumption-session0.1.0Lista as suposições que o Claude faz enquanto constrói, num painel onde você aprova ou corrige. Abra com /suposicoes.mods/assumption-session
cofre-chaves0.1.0Guarda chaves no .env pelo /chave e impede que elas passem pelo chat, pelas ferramentas ou pela saída delasmods/cofre-chaves
comandos-mod0.1.0/comandosmod abre um painel lateral com todos os seus mods e o comando de cada um.mods/comandos-mod
plain-answers0.1.0Faz o Claude responder em linguagem simples, sem jargão. Liga/desliga com /simples.mods/plain-answers
prompt-enhance0.1.0Melhora prompts vagos usando as instruções do projeto e as skills, troca o texto na caixa e só envia quando você clicar em Enviar.mods/prompt-enhance
session-dashboard0.1.0Painel lateral com todas as sessões ativas: trabalhando, concluída ou esperando resposta. Abra com /sessoes.mods/session-dashboard

Como usar

Cada pasta é um plugin do Claude Code (.claude-plugin/plugin.json, hooks/). Para carregar um mod, aponte para a pasta dele pela variável CLAUDE_CODE_PLUGIN_DIRS no settings.json (bloco env), conforme a documentação do Claude Code. Os mods são em português.

Antes de usar, leia o código de cada um. Os mods rodam hooks com acesso aos arquivos do seu projeto.

Observações

  • Os arquivos de tipagem gerados (.claude-plugin/types) não foram incluídos. Para compilar, rode o tsc no mod (há um tsconfig.json em cada um).
  • cofre-chaves não guarda chaves neste repo: o código só contém os padrões usados para detectar chaves e testes com valores fictícios.

Mods de terceiros que também uso

Não copiei o código destes. Estão listados no repositório claude-code-plugins, com a fonte e o commit exato:

  • skins (hellosverre/claude-skins)
  • mods-manager (ptpmediabr/mods-manager)
  • usage-weather (ptpmediabr/usage-weather)
  • ideas-shelf (ptpmediabr/ideas-shelf)
  • side-chat (ptpmediabr/side-chat)
Source 1 files
hooks/register.tsx 95 lines
1import type { Register } from 'claude-code'
2
3const PANE = 'sessions'
4const DIR = `${process.env.HOME}/.claude/session-dashboard`
5
6type Row = { id: string; title: string; cwd: string; state: 'working' | 'waiting' | 'done' | 'ended'; at: number }
7
8const LABEL = { working: 'trabalhando', waiting: 'esperando resposta', done: 'concluída', ended: 'encerrada' } as const
9const COLOR = { working: 'yellow', waiting: 'red', done: 'green', ended: 'gray' } as const
10
11let me: Row | null = null
12
13async function save($: any, state: Row['state']) {
14  if (!me) return
15  me = { ...me, state, at: await $.clock.now() }
16  await $.fs.write(`${DIR}/${me.id}.json`, JSON.stringify(me))
17}
18
19export const register: Register = on => {
20  on('session.start', async ($, e, next) => {
21    me = { id: await $.session.id(), title: 'nova sessão', cwd: await $.session.cwd(), state: 'done', at: 0 }
22    await save($, 'done')
23    await $.command.register({ name: 'sessoes', description: 'Painel com as sessões ativas e o status de cada uma' })
24    $.clock.every(2000, () => $.ui.invalidate('ui.render'))
25
26    return next(e)
27  })
28
29  on('command.run', { command: 'sessoes' }, async $ => {
30    await $.ui.open({ id: PANE, title: 'Sessões' })
31
32    return { text: 'Painel de sessões aberto.' }
33  })
34
35  on('prompt.submit', async ($, e, next) => {
36    if (me) me = { ...me, title: e.text.replace(/\s+/g, ' ').slice(0, 60) }
37    await save($, 'working')
38
39    return next(e)
40  })
41
42  on('tool.call', { tool: 'AskUserQuestion' }, async ($, e, next) => {
43    await save($, 'waiting')
44    const r = await next(e)
45    await save($, 'working')
46
47    return r
48  })
49
50  on('turn.complete', async ($, e, next) => {
51    if (e.agentId === undefined) await save($, 'done')
52
53    return next(e)
54  })
55
56  on('session.end', async ($, e, next) => {
57    await save($, 'ended')
58
59    return next(e)
60  })
61
62  on('ui.render', { component: 'Pane', requestId: PANE }, async ($, e) => {
63    const { Box, Text } = $.ui.resolve(e)
64    const now = await $.clock.now()
65    const rows: Row[] = []
66    for (const f of await $.fs.list(DIR).catch(() => [])) {
67      if (!f.name.endsWith('.json')) continue
68      try {
69        rows.push(JSON.parse(await $.fs.read(`${DIR}/${f.name}`)))
70      } catch {}
71    }
72    const live = rows.filter(r => r.state !== 'ended' || now - r.at < 10 * 60_000).sort((a, b) => b.at - a.at)
73    const count = (s: Row['state']) => live.filter(r => r.state === s).length
74
75    return (
76      <Box flexDirection="column">
77        <Text bold>
78          {count('working')} trabalhando · {count('waiting')} esperando · {count('done')} concluídas
79        </Text>
80        {live.length === 0 && <Text dimColor>Nenhuma sessão ainda.</Text>}
81        {live.map(r => (
82          <Box key={r.id} flexDirection="column" marginTop={1}>
83            <Text color={COLOR[r.state]}>
84              ● {LABEL[r.state]}
85              {r.id === me?.id ? '  (esta)' : ''}
86            </Text>
87            <Text>{r.title}</Text>
88            <Text dimColor>{r.cwd.split('/').slice(-2).join('/')}</Text>
89          </Box>
90        ))}
91      </Box>
92    )
93  })
94}
95