Pick the session's model and effort from the mods pane.

Mods para o Claude Code: plugins de function hooks que mudam a interface e o comportamento do próprio Claude Code.
| Mod | O que faz |
|---|---|
mods-hub | Botão ◆ mods na linha do mode e janela lateral para ligar, desligar e configurar os mods. |
status-inline | Medidores compactos (contexto, rate limits, modelo, custo, tokens) na linha do mode. |
model-picker | Troca o modelo e o effort da sessão pela janela de mods. |
<img width="1084" height="101" alt="image" src="https://github.com/user-attachments/assets/ac343e6d-1300-4469-9443-7b241f6e7b14" /> <img width="216" height="427" alt="image" src="https://github.com/user-attachments/assets/b03d8f45-afad-45ec-b53d-415e3e89bba8" />
Em qualquer sessão do Claude Code:
/plugin marketplace add Sales16/claude-mods
/plugin install mods-hub@claude-mods
/plugin install status-inline@claude-mods
/plugin install model-picker@claude-mods
mods-hub junto: o status-inline depende dele.Para pegar uma versão nova do repositório:
/plugin marketplace update claude-mods
/reload-plugins
Para remover:
/plugin uninstall model-picker@claude-mods
/plugin uninstall status-inline@claude-mods
/plugin uninstall mods-hub@claude-mods
Útil enquanto você mexe nos mods: o terminal vigia as pastas e recarrega o mod sozinho quando um arquivo muda.
Clone o repositório primeiro. Os exemplos abaixo usam C:\MyProjects\claude-mods; troque pelo caminho do seu clone.
git clone https://github.com/Sales16/claude-mods.git C:\MyProjects\claude-mods
Para todos os terminais, em ~/.claude/settings.json:
{
"env": {
"CLAUDE_CODE_PLUGIN_DIRS": "C:\\MyProjects\\claude-mods\\plugins\\mods-hub;C:\\MyProjects\\claude-mods\\plugins\\status-inline;C:\\MyProjects\\claude-mods\\plugins\\model-picker"
}
}
Para um terminal só:
claude --plugin-dir C:\MyProjects\claude-mods\plugins\mods-hub --plugin-dir C:\MyProjects\claude-mods\plugins\status-inline --plugin-dir C:\MyProjects\claude-mods\plugins\model-picker
Use um jeito só. Instalado e carregado pela pasta ao mesmo tempo, o mod roda duas vezes (dois ◆ mods na linha).
Cada opção dos mods fica salva no settings.json do usuário, em pluginConfigs, e vale entre sessões. O nome da chave muda com o jeito de carregar (status-inline@claude-mods instalado, status-inline@inline pela pasta): ao trocar de jeito, refaça as escolhas pelo ◆ mods.
Adiciona ◆ mods no começo da linha do mode:
◆ mods ⏵⏵ auto mode on │ ctx ▰▰▰▰▰▱▱▱ 62% │ …
Clicar em ◆ mods (ou rodar /mods) abre uma janela lateral com cada mod que tem configurações:
● model-picker
■ Ativo
▸ Modelo: opus
▸ Effort: high
● status-inline
■ Ativo
▾ Barra: ▰▱
● ▰▱
○ █░
■ ctx %
Amarelo a partir de % − 70 +
●/○ liga ou desliga o mod inteiro; ■/□ liga ou desliga cada opção.▸) abre a lista de valores embaixo dela; escolher um aplica na hora.− e +, de 5 em 5.Opção própria: Cor do ◆ mods (verde-neon, ciano, magenta, laranja, azul, roxo, branco).
Clique e hover dependem de o terminal enviar o mouse para o Claude Code, o que acontece no modo tela cheia. Sem mouse, use /mods, Tab e Enter.
Desenha medidores depois da linha do mode, em verde neon:
⏵⏵ auto mode on │ ctx ▰▰▰▰▰▱▱▱ 62% │ 5h ▰▱▱▱▱▱▱▱ 18% ↻14:30 │ 7d ▰▰▰▱▱▱▱▱ 41% ↻sex │ opus5.5·xh │ $1.23 │ tok 1.2M │ cmp ≈18k │ 🔥 →36%
| Medidor | O que mostra |
|---|---|
ctx | Quanto da janela de contexto o último request usou. |
5h | Uso da janela de rate limit de 5 horas e quando ela reseta. |
7d | Uso da janela semanal e o dia em que ela reseta. |
opus5.5·xh | Modelo e effort (lo, md, hi, xh, max) do request atual. |
$1.23 | Custo da sessão, como o /cost soma. |
tok 1.2M | Tokens gastos na sessão, conversa principal e subagents juntos, desde que o mod carregou. |
cmp ≈18k | Tokens que faltam até o auto-compact. |
🔥 →36% | Uso que a janela de 5h terá no reset, se o ritmo atual continuar. |
Configurações:
| Opção | Valores |
|---|---|
| Ativo | liga ou desliga os medidores |
| Cor | verde-neon, ciano, magenta, laranja, azul, roxo, branco |
| Barra | ▰▱, █░, ■□, ●○, ⣿⣀, ━┄ |
| Reset | oculto (18%), valor (18% 9:00), ícone (18% ↻9:00), texto (18% reset às 9:00) |
| Tokens | total, sem-cache (sem o cache relido), in/out (↑1.2M ↓40k), oculto |
| ctx, 5h, 7d, Modelo, Effort, Custo, cmp, Projeção 5h, Barras | mostra ou esconde cada item |
| Amarelo a partir de % | padrão 70 |
| Vermelho a partir de % | padrão 90 |
7d, modelo/effort. ctx e 5h nunca somem.◆ mods aberta ao lado, o mod conta a largura dela, porque a linha do mode passa por baixo das duas.Troca o modelo e o effort da sessão pela janela do ◆ mods.
| Opção | Valores |
|---|---|
| Ativo | liga ou desliga as trocas e a sincronização |
| Modelo | default, opus, sonnet, haiku, fable |
| Effort | low, medium, high, xhigh, max |
/model. Escolher um effort roda /effort <nível>, que executa quando a sessão fica parada./model, /effort ou troca de modelo pelo /config feita à mão passa a aparecer na janela. Um modelo como claude-opus-5-5 ou opus[1m] aparece como opus.claude-mods/
.claude-plugin/marketplace.json lista os mods para o /plugin install
plugins/
mods-hub/ botão ◆ mods, janela lateral, /mods
status-inline/ medidores na linha do mode
model-picker/ modelo e effort pela janela de mods
Cada mod tem .claude-plugin/plugin.json (nome, opções em userConfig), hooks/hooks.json e o módulo em hooks/register.ts(x), com seus testes em hooks/*.test.ts(x).
Um mod aparece na janela quando o plugin.json declara campos em userConfig.
enabled vira o liga/desliga do mod; o mod deve checar options.enabled e ficar inerte quando for false.string com options vira uma lista de valores.description do campo:Explicação do campo, uma ideia por linha.
Ex.: {#39FF14}ctx ▰▰▰▰▰▱▱▱ 62%{/}
-- <opção>
Explicação daquela opção.
Ex.: {#00F0FF}ctx ▰▰▰▰▰▱▱▱ 62%{/}
O Claude Code remove as quebras de linha do description, então o hub reencontra as linhas: uma linha começa em -- <opção> (uma das opções do campo), em Ex.: e depois de ., ! ou ? quando a frase seguinte começa com letra maiúscula. Comece toda linha assim e termine cada frase com ponto. Uma linha Ex.: é desenhada como exemplo, e {#RRGGBB}texto{/} pinta aquele trecho na cor.
No /config do Claude Code a mesma descrição aparece como texto puro, com as marcações cruas.
claude plugin validate plugins/<mod>
claude plugin test plugins/<mod>hooks/register.ts 147 lines1import type { EngineInterface, PluginOptions, Register } from 'claude-code'
2
3import { EFFORTS, MODELS } from './choices'
4
5/** What this mod last set or adopted, so a reload applies only a pick that changed. */
6type Applied = { model?: string; effort?: string }
7
8type Field = 'model' | 'effort'
9
10/** True while this mod sets the model or effort itself, so its own change is not adopted back. */
11let isApplyingModel = false
12let isApplyingEffort = false
13
14function message(error: unknown): string {
15 return error instanceof Error ? error.message : String(error)
16}
17
18/** The pick a model or effort spelling stands for, or undefined when none does. */
19export function toPick(field: Field, raw: string): string | undefined {
20 const value = raw.trim().toLowerCase()
21 if (field === 'effort') return EFFORTS.includes(value) ? value : undefined
22 if (MODELS.includes(value)) return value
23
24 // A full id or a variant (`claude-opus-5-5`, `opus[1m]`) counts as its family.
25 return MODELS.find(model => model !== 'default' && value.includes(model))
26}
27
28async function readApplied($: EngineInterface): Promise<Applied | undefined> {
29 const value = await $.store.get('applied')
30
31 return value !== null && typeof value === 'object' ? (value as Applied) : undefined
32}
33
34async function setOwnRow($: EngineInterface, field: Field, value: string): Promise<void> {
35 await $.config.set({ key: field === 'model' ? 'model-picker.model' : 'model-picker.effort', value })
36}
37
38/**
39 * The first load takes the session's model and effort as the picks, so
40 * installing the mod changes nothing.
41 */
42async function adoptSession($: EngineInterface, options: PluginOptions): Promise<void> {
43 const [model, effort] = await Promise.all([$.session.model(), $.env.get('CLAUDE_EFFORT')])
44 const applied: Applied = {
45 model: toPick('model', model) ?? String(options['model']),
46 effort: (effort === undefined ? undefined : toPick('effort', effort)) ?? String(options['effort']),
47 }
48 // Stored before the rows change: each change reloads this mod.
49 await $.store.set('applied', applied)
50 if (applied.model !== options['model']) await setOwnRow($, 'model', applied.model ?? '')
51 if (applied.effort !== options['effort']) await setOwnRow($, 'effort', applied.effort ?? '')
52}
53
54/**
55 * Sets the session's model and effort to the picks in `options`, each only
56 * when it differs from what this mod last set: a config change reloads the
57 * mod, and every load runs this again.
58 */
59async function applyPicks($: EngineInterface, options: PluginOptions, applied: Applied): Promise<void> {
60 const model = String(options['model'])
61 const effort = String(options['effort'])
62 const next: Applied = { ...applied }
63
64 if (model !== applied.model) {
65 isApplyingModel = true
66 const result = await $.config.set({ key: 'model', value: model }).finally(() => {
67 isApplyingModel = false
68 })
69 if (result.deny === undefined) {
70 next.model = model
71 $.ui.toast(`modelo → ${model}`)
72 } else {
73 $.ui.toast(`modelo: ${result.deny}`)
74 }
75 }
76
77 if (effort !== applied.effort) {
78 next.effort = effort
79 $.ui.toast(`effort → ${effort}`)
80 isApplyingEffort = true
81 // Queued until the session is idle; nothing here waits on it.
82 void $.command
83 .run({ command: 'effort', args: effort })
84 .catch(error => $.ui.toast(`effort: ${message(error)}`))
85 .finally(() => {
86 isApplyingEffort = false
87 })
88 }
89
90 await $.store.set('applied', next)
91}
92
93/**
94 * Takes a model or effort the person set elsewhere (/model, /effort, /config)
95 * as this mod's pick, so the mods pane shows what the session runs.
96 */
97async function adopt($: EngineInterface, options: PluginOptions, field: Field, raw: string): Promise<void> {
98 const pick = toPick(field, raw)
99 if (pick === undefined) return
100
101 const applied = (await readApplied($)) ?? {}
102 await $.store.set('applied', { ...applied, [field]: pick })
103 if (options[field] !== pick) await setOwnRow($, field, pick)
104}
105
106export const register: Register = (on, options) => {
107 const isEnabled = options['enabled'] !== false
108
109 on('session.start', async ($, e, next) => {
110 const started = await next(e)
111 if (!isEnabled) return started
112
113 // A pick never set or adopted yet (a first load, or a store from before
114 // every field had a pick) takes the session's own instead of changing it.
115 const applied = await readApplied($)
116 if (applied?.model === undefined || applied.effort === undefined) {
117 await adoptSession($, options)
118 } else {
119 await applyPicks($, options, applied)
120 }
121
122 return started
123 })
124
125 on('command.run', { command: 'model' }, async ($, e, next) => {
126 const ran = await next(e)
127 if (isEnabled && !isApplyingModel && e.args.trim() !== '') await adopt($, options, 'model', e.args)
128
129 return ran
130 }).catch(($, e, next) => next(e))
131
132 on('command.run', { command: 'effort' }, async ($, e, next) => {
133 const ran = await next(e)
134 if (isEnabled && !isApplyingEffort && e.args.trim() !== '') await adopt($, options, 'effort', e.args)
135
136 return ran
137 }).catch(($, e, next) => next(e))
138
139 on('config.set', { key: 'model' }, async ($, e, next) => {
140 const set = await next(e)
141 const isOwn = isApplyingModel || (e.origin?.kind === 'plugin' && e.origin.name === 'model-picker')
142 if (isEnabled && !isOwn && set.deny === undefined) await adopt($, options, 'model', String(set.value))
143
144 return set
145 }).catch(($, e, next) => next(e))
146}
147hooks/choices.ts 6 lines1/** The `model` option's picks: the base models `/model` offers. */
2export const MODELS: readonly string[] = ['default', 'opus', 'sonnet', 'haiku', 'fable']
3
4/** The `effort` option's picks: the levels `/effort` takes. */
5export const EFFORTS: readonly string[] = ['low', 'medium', 'high', 'xhigh', 'max']
6