/cmdlog opens a pane with the shell commands, status and duration

An instrument panel for Claude Code. 309 mods for the terminal: 52 panels that stay in sight and switch on and off in /cockpit, plus guards, prompt automations, instant commands and focus helpers. Every mod is a small plugin you install with one line.
/plugin marketplace add MatheusChiodi/cockpit
/plugin install git-band@cockpit
/cockpit
Mods are TypeScript functions that run inside Claude Code and change how it behaves: they intercept tool calls, rewrite prompts, draw panes and bands, and register slash commands. They need Claude Code 2.1.287 or newer (built against 2.1.296).
From GitHub, inside Claude Code:
/plugin marketplace add MatheusChiodi/cockpit
/plugin install <mod>@cockpit
From a local folder:
git clone https://github.com/MatheusChiodi/cockpit
claude plugin marketplace add ./cockpit
claude plugin install git-guard@cockpit
Just to try one, without installing:
claude --plugin-dir ./cockpit/mods/turn-timer
Remove a mod with /plugin uninstall <mod>@cockpit. Turn every mod off at once with claude --safe-mode.
The 52 mods under Always visible are panels: bands above the prompt or status line entries that update on their own (git, context, cost, tests, CI, tasks, deadline, weather). None needs a command to show up.
The cockpit mod is their settings pane, and it comes along as a dependency of any panel.
| To | Do |
|---|---|
| Open the settings pane | /cockpit — Tab and arrows move, Enter or a click toggles, Esc closes |
| Switch a panel off or on | /cockpit off git-band · /cockpit on git-band |
| Switch all of them | /cockpit off all · /cockpit on all |
| Change the language | /cockpit lang en · /cockpit lang pt · /cockpit lang es |
Both choices are kept between sessions. A band disappears or returns at once; a status line entry disappears at once and returns on its next update. A panel switched off stays installed and simply does not draw.
Bands stack in the order the mods load, and the status line holds one entry per mod. With many panels on, the terminal gets crowded: start with four or five and adjust in /cockpit.
Every mod speaks English by default and can switch to Brazilian Portuguese or Spanish. The language is set once, in /cockpit, and every installed mod follows it: band labels, status line words, command output, guard messages, toasts and notifications. Without the cockpit mod installed, mods stay in English.
Slash command descriptions in the typeahead are read when a session starts, so they follow a language change on the next session. To make Claude itself answer in the chosen language, install reply-language.
| Always visible | 53 | cockpit git-band todo-band usage-band files-band error-band goal-band agents-band activity-status test-status ci-status stash-status uncommitted-band pr-status main-warning-band conflict-band project-status ports-band deps-status todo-count-status quality-status model-status cost-ticker idle-clock turn-progress last-prompt-band compact-nudge session-diff-band shield-band reset-countdown spend-today focus-band deadline-band pin-band clock-status day-timer commits-today dolar-status weather-status api-status terminal-pet runtime-band docker-band env-drift-band deploy-status todo-new-status release-status license-status install-status db-port-status migration-status env-ignore-status build-size-status |
| Security | 42 | rm-guard env-guard git-guard secret-scan prompt-secret-guard lockfile-guard path-jail main-branch-guard generated-guard sql-guard prod-guard install-ask pipe-shell-guard big-read-guard sudo-guard chmod-guard docker-guard kill-guard credential-file-guard upload-guard env-dump-guard registry-guard persistence-guard dotfile-guard ci-config-guard test-skip-guard suppress-guard migration-guard large-write-guard tunnel-guard hardcoded-secret-guard private-host-guard dynamic-exec-guard sql-concat-guard tls-off-guard cors-open-guard debug-flag-guard script-mode-guard dep-source-guard weak-crypto-guard gitignore-guard archive-add-guard |
| Interface | 34 | turn-timer tool-tally file-trail cmd-log git-status-line context-meter rate-limit-watch session-clock diff-pane turn-history prompt-history agent-pane token-chart cost-pane error-pane context-pane read-pane links-pane git-pane branch-pane stash-pane code-todo-pane deps-pane scripts-pane env-keys-pane test-pane slow-toast deny-toast big-output-toast timeline tool-clock result-fold group-peek prompt-stamp |
| Prompt and model | 30 | concise-mode persona prompt-shortcuts tldr git-context stack-context commit-style reply-language time-context os-context test-first explain-mode plan-first no-comments minimal-diff ask-first verify-claims paste-fence file-mention-hint trace-helper branch-naming pr-style a11y-rules security-rules react-rules tailwind-rules rn-rules java-rules oracle-rules answer-format |
| Commands | 42 | quick-notes pomodoro copy-last session-stats quote-selection git-peek git-branch git-stash git-undo run-script open-path ports kill-port tree find-file last-error env-check outdated json-tool uuid timestamp base64 hash calc color-tool regex-test http-probe snippets export-session standup loc-count big-files recent-files symbol-def file-author head-file file-diff path-size ignore-check file-compare churn hit-count |
| Focus and notifications | 42 | turn-notify break-reminder daily-streak error-notify needs-you-notify agent-done-notify long-cmd-notify tests-notify ci-notify pr-review-notify rate-limit-notify hydrate posture eye-rest stand-up alarms remind timebox session-cap late-night intent-check daily-target weekly-recap breathe budget-alert daily-budget-alert turn-toast milestone-toast push-reminder idle-commit-nudge topic-switch scatter-toast long-turn-toast step-back branch-drift edits-no-commit day-close quiet-window parking-lot file-churn resume-line wandering |
| Git and GitHub | 24 | commit-review wip-commit-guard marker-guard amend-guard stage-guard git-stage git-fixup git-sync behind-toast git-switch git-tag staged-status pr-open pr-checks pr-comments review-queue issue-context issue-link draft-nag pr-diff merge-ready release-notes changelog-entry merged-branches |
| Quality | 12 | untested-turn-toast red-turn-toast debugger-guard leftovers-toast any-guard long-file-toast dup-block-guard untested coverage-status empty-test-guard snapshot-update-guard catch-swallow-guard |
| Stacks | 12 | pm-guard script-guard missing-import-guard client-env-guard next-boundary-guard esm-guard alias-import-guard dev-server-guard ts-config-context env-undeclared-toast node-version-status pm-command |
| Context and cost | 18 | reread-guard grep-limit read-cap git-log-limit paste-size-toast cat-guard explore-nudge fill-toast cache-cold-toast flood-note heavy-calls tree-flood-guard agent-cap agent-straggler cost-per-hour cost-trend priciest-turn runout-projection |
Every panel is drawn the same way, so several of them read as one instrument:
━━━━───) instead of block bars.label state detail, as in tests ✓ 14:32 · 3.1s.kit/kit.tsx, copied into each mod at build time.| For | Mods |
|---|---|
| An everyday dashboard | git-band usage-band todo-band error-band test-status |
| Git without surprises | git-band conflict-band uncommitted-band main-warning-band pr-status ci-status |
| An eye on spend | usage-band cost-ticker spend-today reset-countdown compact-nudge |
| A seat belt | rm-guard git-guard env-guard secret-scan pipe-shell-guard prod-guard shield-band |
| Less typing | stack-context git-context commit-style prompt-shortcuts git-peek |
| Focus | focus-band deadline-band pin-band turn-notify break-reminder |
hooks/register.ts before installing it; every one here is short.prod-guard, install-ask and the others that say so force the confirmation prompt even when the command was already allowed.dolar-status (AwesomeAPI), weather-status (wttr.in, located by IP), api-status (Anthropic's status page), ports-band (localhost only).ci-status and pr-status need an authenticated gh.$.audio.play only plays on macOS, so notifications use the native channel.npm install
npm run build # manifests, the hub's panel list, previews and the three READMEs
npm run check # tsc + claude plugin validate on every mod + claude plugin test where tests exist
node scripts/mod.mjs <mod> # the same loop for one mod, without touching shared files
mods.json is the single source: name, category, descriptions in three languages and the preview lines of every mod.
mods/<name>/
hooks/register.ts(x) the mod
hooks/strings.ts every user-facing text, in en, pt and es
types/index.d.ts the $.state contract, for mods that draw
tests/ run by `claude plugin test`
plugin.json, hooks.json and each mod's copy of the kit are generated.
Adding mods with the agent. The repository ships a Claude Code agent, cockpit-mod-builder (.claude/agents/), that knows the conventions: the language pattern, the visual kit, the engine rules that trip people up, and the one-mod loop. Open Claude Code in the repository and ask for it by name, giving a category and the mods you want.
The type check uses the API declarations Claude Code itself writes. They are not in the repository: run a mod with claude --plugin-dir mods/<name> and copy mods/<name>/.claude-plugin/types/claude-code/index.d.ts plus claude-code-tools/index.d.ts into engine-types/.
Surveyed on 2026-10-13 through gh api and the official documentation. Star counts are from that day.
| Community collection | Stars | Known for |
|---|---|---|
| karanb192/awesome-claude-code-mods | 560 | A catalog of 3,836 mods across 1,987 repositories |
| YeonwooSung/my-claude-code-mods | 514 | agent-profiler, context-inspector, time-machine |
| hamzafer/claude-code-mods | 227 | context-bar, blast-radius, merge-gate |
| zenbu-labs/terminal-browser | 3,854 | A browser inside the terminal |
| jarrodwatts/claude-image-view | 222 | Images in the terminal |
Other ways people customize Claude Code, which are not mods: claude-hud and ccstatusline (status line scripts), ccusage (a CLI over the transcripts), tweakcc (patches the install), cc-safety-net (a PreToolUse hook).
MIT
hooks/register.tsx 91 lines1import { atom, read, update } from 'claude-code'
2import type { EngineInterface, Register } from 'claude-code'
3
4import { heading } from './kit'
5import { STRINGS } from './strings'
6
7import type { Run } from '../types'
8
9const PANE = 'cmd-log'
10const runs = atom({ plugin: 'cmd-log', key: 'runs' } as const, [])
11
12// cockpit: the language set in /cockpit (English when the hub is not installed)
13const LANG = { plugin: 'cockpit', key: 'lang' } as const
14
15const strings = async ($: EngineInterface) => STRINGS[(await $.state.get(LANG)).value ?? 'en']
16
17const begin = async ($: EngineInterface, id: string, command: string): Promise<number> => {
18 const startedAt = await $.clock.now()
19 const run: Run = { id, command: command.replace(/\s+/g, ' ').slice(0, 200), status: 'running', ms: 0 }
20 await update($, runs, list => [...list, run].slice(-200))
21
22 return startedAt
23}
24
25const finish = async ($: EngineInterface, id: string, startedAt: number, isOk: boolean) => {
26 const ms = (await $.clock.now()) - startedAt
27 await update($, runs, list =>
28 list.map(run => (run.id === id ? { ...run, status: isOk ? 'ok' : 'failed', ms } : run)),
29 )
30}
31
32const took = (ms: number): string => (ms >= 1000 ? `${(ms / 1000).toFixed(1)}s` : `${ms}ms`)
33
34export const register: Register = on => {
35 on('session.start', async ($, e, next) => {
36 const s = await strings($)
37 await $.command.register({ name: 'cmdlog', description: s.command })
38
39 return next(e)
40 })
41
42 on('command.run', { command: 'cmdlog' }, async $ => {
43 const s = await strings($)
44 await $.ui.open({ id: PANE, title: s.title })
45
46 return { text: s.opened }
47 })
48
49 on('tool.call', { tool: 'Bash' }, async ($, e, next) => {
50 const startedAt = await begin($, e.tool_use_id, e.command)
51 const ran = await next(e)
52 await finish($, e.tool_use_id, startedAt, ran.deny === undefined && ran.isError !== true)
53
54 return ran
55 })
56
57 on('tool.call', { tool: 'PowerShell' }, async ($, e, next) => {
58 const startedAt = await begin($, e.tool_use_id, e.command)
59 const ran = await next(e)
60 await finish($, e.tool_use_id, startedAt, ran.deny === undefined && ran.isError !== true)
61
62 return ran
63 })
64
65 on('ui.render', { component: 'Pane', requestId: PANE }, async ($, e) => {
66 const s = await strings($)
67 const ui = $.ui.resolve(e)
68 const { Box, Text } = ui
69 const list = await read($, runs)
70 const room = Math.max(1, (e.viewport?.rows ?? 24) - 6)
71 const width = Math.max(12, e.props.bodyColumns - 12)
72 const failed = list.filter(run => run.status === 'failed').length
73
74 return (
75 <Box flexDirection="column">
76 {heading(ui, 'session', s.label, s.summary(list.length, failed), e.props.bodyColumns)}
77 {list.length === 0 && <Text dimColor>{s.empty}</Text>}
78 {list.slice(-room).map(run => (
79 <Text wrap="truncate-end">
80 <Text color={run.status === 'failed' ? 'error' : run.status === 'ok' ? 'success' : 'warning'} bold>
81 {run.status === 'failed' ? '✗' : run.status === 'ok' ? '✓' : '…'}
82 </Text>{' '}
83 <Text dimColor>{(run.status === 'running' ? '' : took(run.ms)).padStart(6)}</Text>{' '}
84 <Text color={run.status === 'failed' ? 'error' : 'text'}>{run.command.slice(0, width)}</Text>
85 </Text>
86 ))}
87 </Box>
88 )
89 })
90}
91hooks/kit.tsx 75 lines1// Generated by the build from kit/kit.tsx. Do not edit by hand.
2import type { BoxProps, Color, ElementConstructor, RenderChildren, TextProps } from 'claude-code'
3
4// The collection's visual language: every band is an instrument row with a rail and a placard
5// in its subject's hue, then the values. Stacked, the bands line up as one panel.
6
7export type Ui = { Box: ElementConstructor<BoxProps>; Text: ElementConstructor<TextProps> }
8
9export type Group = 'git' | 'session' | 'project' | 'daily'
10
11export type Tone = 'success' | 'warning' | 'error'
12
13// Rail color per subject. Green, amber and red stay reserved for the state of a value.
14const HUE: Record<Group, Color> = { git: 'merged', session: 'claude', project: 'suggestion', daily: 'remember' }
15
16const LABEL_WIDTH = 10
17
18// Indent of continuation lines: lines up with the value column.
19export const INDENT = ' '.repeat(LABEL_WIDTH + 2)
20
21export const toneOf = (percent: number): Tone => (percent >= 85 ? 'error' : percent >= 60 ? 'warning' : 'success')
22
23// Slider gauge: a heavy stroke up to a knob, a light stroke after it. The knob is the same one
24// the settings pane uses for its switches, so every control in the collection reads alike.
25export const gauge = (percent: number, width: number): { fill: string; rest: string } => {
26 const at = Math.round((Math.min(100, Math.max(0, percent)) / 100) * (width - 1))
27
28 return { fill: `${'━'.repeat(at)}●`, rest: '─'.repeat(width - 1 - at) }
29}
30
31export const meter = (ui: Ui, percent: number, width: number, color: Color = toneOf(percent)) => {
32 const { Text } = ui
33 const { fill, rest } = gauge(percent, width)
34
35 return (
36 <Text>
37 <Text color={color}>{fill}</Text>
38 <Text dimColor>{rest}</Text>
39 </Text>
40 )
41}
42
43// One instrument row. `below` is what the other mods already drew in the band.
44export const row = (ui: Ui, group: Group, label: string, body: unknown, below?: unknown) => {
45 const { Box, Text } = ui
46
47 return (
48 <Box flexDirection="column">
49 {below as RenderChildren}
50 <Box>
51 <Text color={HUE[group]}>
52 ▍ {label.toUpperCase().slice(0, LABEL_WIDTH - 1).padEnd(LABEL_WIDTH)}
53 </Text>
54 {body as RenderChildren}
55 </Box>
56 </Box>
57 )
58}
59
60// Side pane header: a strong title, a summary and a rule.
61export const heading = (ui: Ui, group: Group, title: string, summary: string, columns: number) => {
62 const { Box, Text } = ui
63
64 return (
65 <Box flexDirection="column">
66 <Text>
67 <Text color={HUE[group]}>▍ </Text>
68 <Text bold>{title.toUpperCase()}</Text>
69 <Text dimColor> {summary}</Text>
70 </Text>
71 <Text dimColor>{'─'.repeat(Math.max(8, Math.min(columns, 64)))}</Text>
72 </Box>
73 )
74}
75hooks/strings.ts 31 lines1const en = {
2 command: 'Open the pane with the shell commands of the session, status and duration',
3 title: 'Commands',
4 opened: 'Commands pane opened.',
5 label: 'commands',
6 summary: (ran: number, failed: number) => `${ran} run · ${failed} failed`,
7 empty: 'No command run yet.',
8}
9
10type Strings = typeof en
11
12const pt: Strings = {
13 command: 'Abre o painel com os comandos de shell da sessão, status e duração',
14 title: 'Comandos',
15 opened: 'Painel de comandos aberto.',
16 label: 'comandos',
17 summary: (ran, failed) => `${ran} rodados · ${failed} com erro`,
18 empty: 'Nenhum comando executado ainda.',
19}
20
21const es: Strings = {
22 command: 'Abre el panel con los comandos de shell de la sesión, estado y duración',
23 title: 'Comandos',
24 opened: 'Panel de comandos abierto.',
25 label: 'comandos',
26 summary: (ran, failed) => `${ran} ejecutados · ${failed} con error`,
27 empty: 'Ningún comando ejecutado todavía.',
28}
29
30export const STRINGS = { en, pt, es }
31types/index.d.ts 13 lines1export type Run = {
2 id: string
3 command: string
4 status: 'running' | 'ok' | 'failed'
5 ms: number
6}
7
8declare module 'claude-code' {
9 interface PluginState {
10 'cmd-log': { runs: Run[] }
11 }
12}
13