Draws each successful tool call as one line (Edit file +12 −3); errors and running calls keep the engine's full block

Function-hook mods for Claude Code: shell safety, secret guarding, session telemetry, context panels, and workflow tools. Each mod is an isolated plugin with its own tests, and you enable only the ones you want.
| Mod | Description |
|---|---|
guarda | Intercepts risky Bash commands (rm -rf, git reset --hard, force push, new dependencies) and asks for confirmation explaining the impact. |
lixeira | Sends rm / Remove-Item targets to the Windows Recycle Bin instead of deleting them permanently. |
cofre | Keeps API keys out of the conversation: /chave stores them in .env, secret file reads display only variable names, outputs are masked, and pasted keys are quarantined. |
atomic-commit-guard | Blocks commits that mix top-level directories, violate Conventional Commits, or lack a Co-Authored-By trailer. |
universal-audit-log | A hook on * that logs every event (tool calls, prompts, turns, fs/http/process calls from other plugins) with secret redaction. |
| Mod | Description |
|---|---|
context-monitor | Context weather and token usage above the prompt, replay and activity panels, plain-language explanation before each edit, and ai-memory notes. |
cache-frio | Confirms before submitting a prompt to a large conversation whose prompt cache has gone cold. |
session-telemetry | Timestamp on each response, TTFT and tok/s per step, and visual notice whenever the responding model switches. |
session-time-machine | /timemachine: renders the session as a timeline of prompts, tool calls, and turn completions. |
turn-inspector | Side pane displaying the session turn-by-turn: every model request and what it carried. |
clean-view | Collapses each successful tool call into a single concise line (Edit file +12 −3); errors and in-flight calls retain the full block. |
webfetch-cache | Serves repeated WebFetch requests (identical URL and prompt) directly from the session cache. |
reflexo | Detects corrections and constraints in prompts, offering to save them as rules in CLAUDE.md. |
| Mod | Description |
|---|---|
agent-flow | Live diagram of agent flow: main loop context, subagent tree, and context passed/returned by each agent. |
agent-radar | One live line per running subagent above the prompt; /radar opens a pane with messages from each. |
delegation-dock | Status line indicator showing in-flight delegations (agy/codex/ollama) and alerting when one returns empty. |
phase-tracker | Tracks and displays the current development phase (Spec → Red Test → Surgical Code → Verification). |
next-prompts | Band with 3 next-step suggestions generated by a free Ollama cloud model; click one to draft. |
git-sidebar | Lazygit-style side pane: worktrees, local branches, branch switching, and worktree checkout. |
wait-quiz | Quizzes a flashcard in a band above the prompt while Claude is working. |
meus-mods | Central hub: quick-access bar above the prompt and /mods command displaying the status of each mod. |
Requires Claude Code with function-hook mods support.
git clone https://github.com/Covre81/claude-code-mods.git ~/.claude/mods
Enable the mods by listing their absolute paths in CLAUDE_CODE_PLUGIN_DIRS inside ~/.claude/settings.json. On Windows, paths are separated by ;:
{
"env": {
"CLAUDE_CODE_PLUGIN_DIRS": "C:/Users/me/.claude/mods/guarda;C:/Users/me/.claude/mods/cofre;C:/Users/me/.claude/mods/session-telemetry"
}
}
Order matters: the last plugin in the list sits closest to the engine. For instance, lixeira should come after guarda so guarda inspects the original rm -rf first.
Several mods include a command to toggle them on or off without restarting (/webfetch-cache on|off, /radar-toggle, /telemetria…); /mods displays the status of all mods.
Each mod follows the same structure:
<mod>/
.claude-plugin/plugin.json manifest
.claude-plugin/types/ engine types (git-ignored, see below)
hooks/hooks.json hooks registered by the mod
hooks/register.ts(x) code
hooks/*.test.ts(x) tests
Engine types are not committed to the repository. To typecheck and test a mod, copy .claude-plugin/types from an existing mod or generate them with your Claude Code instance.
Tests run directly within Claude Code, not via vitest/jest:
claude plugin test guarda # single mod
for d in */hooks/hooks.json; do claude plugin test "${d%%/*}"; done # all mods
Commits follow Conventional Commits, scoped to one mod per commit (enforced by atomic-commit-guard).
Six mods (agent-flow, git-sidebar, session-time-machine, turn-inspector, universal-audit-log, webfetch-cache) originate from davila7/claude-code-templates, and agent-radar originates from hamzafer/claude-code-mods, both under the MIT License and adapted here. Copyright notices are preserved in THIRD_PARTY_NOTICES.md.
hooks/register.tsx 100 lines1import { atom, read, update } from 'claude-code'
2import type { Register } from 'claude-code'
3
4const isEnabledRef = atom({ plugin: 'clean-view', key: 'isEnabled' } as const, false)
5
6export type Line = { glyph: string; tool: string; target: string; note?: string }
7
8const lines = (s: unknown) => (typeof s === 'string' && s.length ? s.split('\n').length : 0)
9const str = (v: unknown) => (typeof v === 'string' ? v : '')
10const num = (o: unknown, k: string) => {
11 const v = (o as Record<string, unknown> | null | undefined)?.[k]
12 return typeof v === 'number' ? v : undefined
13}
14const short = (s: string, n = 70) => (s.length > n ? `${s.slice(0, n - 1)}…` : s)
15
16// Pure: one tool call to the single line that stands for it. null = leave the engine's block.
17export function summarize(tool: string, input: unknown, output: unknown): Line | null {
18 const i = (input ?? {}) as Record<string, unknown>
19 switch (tool) {
20 case 'Edit': {
21 const add = lines(i.new_string)
22 const del = lines(i.old_string)
23 return { glyph: '●', tool, target: str(i.file_path), note: `+${add} −${del}` }
24 }
25 case 'Write':
26 return { glyph: '●', tool, target: str(i.file_path), note: `${lines(i.content)} linhas` }
27 case 'Read': {
28 const f = (output as { file?: unknown } | null | undefined)?.file
29 const n = num(f, 'numLines')
30 return { glyph: '●', tool, target: str(i.file_path), note: n !== undefined ? `${n} linhas` : undefined }
31 }
32 case 'Bash':
33 return { glyph: '●', tool, target: short(str(i.command).replace(/\s+/g, ' ')) }
34 case 'Grep':
35 case 'Glob': {
36 const n = num(output, 'numFiles')
37 return { glyph: '●', tool, target: short(str(i.pattern)), note: n !== undefined ? `${n} ${n === 1 ? 'arquivo' : 'arquivos'}` : undefined }
38 }
39 default:
40 return null
41 }
42}
43
44export const register: Register = on => {
45 on('session.start', async ($, e, next) => {
46 const saved = await $.store.get('isEnabled').catch(() => undefined)
47 // Published even when nothing was saved: other plugins (the meus-mods panel)
48 // read the state, never this atom's initial, so unset would read as unknown.
49 await update($, isEnabledRef, () => (typeof saved === 'boolean' ? saved : isEnabledRef.initial))
50 await $.command.register({
51 name: 'clean-view',
52 description: 'Liga/desliga o clean-view: resume cada chamada de ferramenta numa linha.',
53 argumentHint: 'on|off',
54 })
55 return next(e)
56 })
57
58 on('command.run', { command: 'clean-view' }, async ($, e) => {
59 const arg = e.args.trim().toLowerCase()
60 if (arg !== 'on' && arg !== 'off') {
61 const isEnabled = await read($, isEnabledRef)
62 return { text: `clean-view esta ${isEnabled ? 'ativado' : 'desativado'}. Uso: /clean-view on · /clean-view off` }
63 }
64 await update($, isEnabledRef, () => arg === 'on')
65 await $.store.set('isEnabled', arg === 'on') // $.state is per session; the store survives a restart
66 return { text: `clean-view ${arg === 'on' ? 'ativado' : 'desativado'}.` }
67 })
68
69 on('ui.render', { component: 'ToolUse' }, async ($, e, next) => {
70 if (e.props.isRunning || e.props.isErrored || e.props.isInterrupted) return next(e)
71 // read() inside ui.render subscribes the row: /clean-view on|off redraws it
72 if (!(await read($, isEnabledRef))) return next(e)
73
74 const line = summarize(e.props.tool, e.props.input, e.props.output)
75 if (!line) return next(e)
76
77 const { Box, Text } = $.ui.resolve(e)
78 return (
79 <Box gap={1}>
80 <Text color="green">{line.glyph}</Text>
81 <Text bold>{line.tool}</Text>
82 <Text wrap="truncate-middle">{line.target}</Text>
83 {line.note ? <Text dimColor>{line.note}</Text> : null}
84 </Box>
85 )
86 })
87
88 // Numa chamada isolada a saída (stdout do Bash, diff do Edit) é um ToolResult
89 // à parte, desenhado embaixo da linha: sem isto o resumo vinha com o bloco
90 // inteiro logo abaixo. Erro continua aparecendo inteiro.
91 on('ui.render', { component: 'ToolResult' }, async ($, e, next) => {
92 // ToolResult has no isInterrupted: an interrupted Bash says so in its output
93 const o = e.props.output
94 if (e.props.isErrored || (typeof o === 'object' && o !== null && 'interrupted' in o && o.interrupted === true)) return next(e)
95 if (!(await read($, isEnabledRef)) || !summarize(e.props.tool, {}, e.props.output)) return next(e)
96 const { Box } = $.ui.resolve(e)
97 return <Box />
98 })
99}
100types/index.d.ts 12 lines1export type CleanViewState = {
2 isEnabled: boolean
3}
4
5declare module 'claude-code' {
6 interface PluginState {
7 'clean-view': {
8 isEnabled: boolean
9 }
10 }
11}
12