Focus mode: hides tool calls, intermediate text, your prompts and command output; shows only a subtask list with progress bars and the final answer. /fokusz…

Saját Claude Code mod-ok (function hook pluginek) egy pluginpiacon.
claude plugin marketplace add Szotasz/claude-mods
claude plugin install usage-band@claude-mods
claude plugin install usage-header@claude-mods
claude plugin install project-pane@claude-mods
claude plugin install focus-mode@claude-mods
claude plugin install tool-hub@claude-mods
claude plugin install snake-pane@claude-mods
claude plugin install supabase-guard@claude-mods
claude plugin install public-repo-guard@claude-mods
claude plugin install decision-popup@claude-mods
Frissítés: claude plugin update usage-band@claude-mods, majd /reload-plugins (vagy új session).
A státuszsorba (a prompt alá) írja:
🤖 Opus 5.5 │ Ctx ███░░░░░ 42% 420k/1M │ 5h ███░░░░░ 34% → 16:20 │ Hét ███████░ 93% ⚠ → Cs 10:00
/model váltáskor és turn végén frissül)Nyelv / Language: /config → usage-band.language, vagy a settings.json-ban:
"pluginConfigs": { "usage-band": { "language": "en" } }
auto (alapértelmezett): magyar, ha a rendszer nyelve (LANG) magyar, különben angolhu: magyar (Hét, Cs 10:00)en: English (Week, Thu 10:00)Követelmény: Claude Code 2.1.289 körüli verzió. A mod-API early access, kiadásról kiadásra változhat.
Sáv a prompt fölött: a kontextusablak kategóriánként színezve, alatta az 5 órás és a heti limit.
Kontextus ██▓▓▓▓▓▓████████████▒▒▒▒▒▒▒▒▒░░░░░░░░░░░░░░ 42% 84k/200k
■ Rendszerprompt 3k ■ Eszközök 12k ■ Memória 400 ■ Üzenetek 69k ▒ Tömörítési tartalék 33k ░ Szabad 83k
Limitek 5 óra ███████░░░░░░░░░░░░░ 34% → 16:20 Hét ███████████████████░ 93% ⚠ → Cs 10:00
/context saját színével látszik (rendszerprompt, eszközök, MCP, memóriafájlok, skillek, ágensek, üzenetek), utána a tömörítési tartalék és a szabad hely halványan; alatta jelmagyarázat tokenszámmal. Az igény szerint betöltött (halasztott) eszközsémák kimaradnak, mert nincsenek az ablakban.$.session.usage({ breakdown: 'summary' })), API-hívás nélkül; minden turn végén és limitmozduláskor frissül./hasznalat kapcsolja, /hasznalat be|ki beállítja; az állapotot sessionök között megőrzi. Az eszköztárban (/eszkozok) is kapcsolható.Beállítás (/config): usage-header.language (auto/hu/en).
Oldalpanel az aktuális git repóhoz. Magától megnyílik, ha a session git repóban indul és a terminál legalább 144 oszlop széles (miután egyszer /project-tel megnyitottad, 110 is elég); keskenyebb ablaknál egy toast szól, hogy vár. Bármikor: /project.
acme/shop ↻ most [ Frissítés ]
GIT
🌿 develop ↑0 ↓0 · 7 módosított fájl
utolsó commit: 57704fe · 7 hónapja — Fix checkout flow
⚠ a main 13 committal előrébb jár, te a develop branch-en vagy
Előbb commitold a módosításokat, vagy tedd félre őket:
[ Stash + szinkron ]
DEPLOY (Netlify)
✔ ready main 3 perce
PULL REQUESTEK
✔ #142 Stripe webhook retry · approved
CI (GitHub Actions)
✘ netlify-deploy-verify main · 5 napja [ Kérdezd Claude-ot ]
SUPABASE
abcdefghijklmnopqrst · Acme Shop · eu-west-3
18 migráció · utolsó: 018_add_orders_rls.sql
GitHub Netlify Supabase
s): csak fast-forward, soha nem merge-öl, rebase-el vagy töröl.git pull --ff-onlygit switch main + git merge --ff-only origin/maingit stash push -u, vissza: git stash popr): 60 mp-enként és minden turn végén; git fetch 5 percenként.a): a legutóbbi elbukott CI-futásról kérdést tesz a promptba.Adatforrások (a CLI-k saját bejelentkezésével, a plugin tokent nem olvas):
| Szekció | Forrás | Ha hiányzik |
|---|---|---|
| Git | git | — |
| PR, CI | gh (gh auth login) | tipp a panelen |
| Deploy | netlify CLI + .netlify/state.json | npm i -g netlify-cli && netlify login |
| Supabase | supabase/.temp/project-ref, supabase/migrations, supabase projects list | supabase login az állapothoz |
Beállítás (/config): project-pane.language (auto/hu/en), project-pane.autoOpen (alapból be).
Fókusz mód: a transcriptből eltűnnek az eszközhívások és eredményeik, a köztes szövegek, a saját promptjaid és a parancsok kimenete. Csak két dolog marad: a prompt fölötti sávban a részfeladatok listája állapotsávval és pipával, alatta a turn végső válasza.
✔ Meglévő mod-repo áttekintése
◐ Játék-mod megírása ██████░░░░ 60%
○ Felvétel az eszközök közé
/fokusz kapcsolja, /fokusz be / /fokusz ki beállítja; az állapotot sessionök között megőrzi.mcp__focus-mode__plan eszközzel; a rendszerprompt bekapcsolt állapotban erre utasítja (minden több lépéses kérésnél előbb a részfeladatok, aztán frissítés indításkor, haladáskor és befejezéskor).Beállítás (/config): focus-mode.language (auto/hu/en).
Eszköztár: egy helyről kapcsolhatók a fenti modok. A prompt alatti lábléc jobb oldalán lévő ⚙ Eszközök gomb (vagy /eszkozok) a prompt fölötti sávban nyitja meg:
⚙ Szabolcs eszközei kattintás, vagy ctrl+x tab után szám [ Bezár ]
1. Fókusz mód ● BE [ Kikapcsol ] · Csak a részfeladatok listája és a végső válasz látszik
2. Limitsáv ○ KI [ Bekapcsol ] · Alsó sáv: 5 órás és heti limit, kontextusablak
3. Használati sáv ● BE [ Kikapcsol ] · Prompt fölött: kontextus kategóriánként színezve, 5 órás és heti limit
4. Projektpanel ● BE [ Kikapcsol ] · GitHub, CI, Netlify és Supabase állapot oldalt
5. Kígyó játék ● BE [ Kikapcsol ] · Oldalt nyílik promptküldéskor, amíg Claude dolgozik
ctrl+x tab után az 1–5 számbillentyűvel is./fokusz, /limitsav, /hasznalat, /project, /jatek (mind be|ki argumentummal).Új mod felvétele az eszköztárba: a mod írja a saját isOn állapotát, adjon /<parancs> be|ki-t, és hookolja a state.set-et { plugin: 'tool-hub', key: 'request' }-re; a hubban egy sor a TOOLS listába, egy ág a stateOf-ba és a kulcs a types/index.d.ts-be.
Beállítás (/config): tool-hub.language (auto/hu/en).
Kígyó játék az oldalpanelben, amíg Claude dolgozik.
⏳ Claude dolgozik…
Pont: 4 Rekord: 17
╭──────────────────────────────────────╮
│ │
│ ████████ │
│ ██ ● │
╰──────────────────────────────────────╯
p: szünet · r: újra
w: ↑ a: ← s: ↓ d: → p: szünet r: új
/jatek mindig nyitja). /jatek ki kikapcsolja, az eszköztárban (/eszkozok) is kapcsolható.wasd / hjkl, szóköz vagy p szünet, r új játék. Vagy ctrl+x tab a panelre, és a w a s d p r gombok.Client surface modul), nem terheli a sessiont.Supabase-őr: megállítja a Supabase-hívást, ha nem az aktuális mappa projektjére menne, és egygombos felugróban rákérdez.
supabase/.temp/project-ref, a .env* fájlok https://<ref>.supabase.co címei és a CLAUDE.md (ha egyetlen projekt-ID-t említ) adják, plusz amit a felugróban „Ez a mappa projektje” gombbal megerősítettél.execute_sql, apply_migration, deploy_edge_function, branch-műveletek, illetve supabase db push --project-ref …): kérdez — Megtiltom / Engedélyezem / Ez a mappa projektje.DROP, TRUNCATE, DELETE FROM, WHERE nélküli UPDATE, REVOKE, RLS kikapcsolása, reset_branch, delete_branch, pause_project, supabase db reset --linked./supaor be|ki, vagy az eszköztárban.Push-őr: nyilvános GitHub-repóba push előtt (és gh repo edit --visibility public / gh repo create --public előtt) átnézi, mi menne ki.
git push origin forrás:cél alakot is.project_id, táblázatsor), JWT / Supabase-kulcs, Stripe/Anthropic/OpenAI/GitHub/AWS/Google/Slack/Resend/Telegram/ElevenLabs kulcsok, privát kulcs, e-mail-cím (a saját git e-mail és a noreply/példa címek kivételével), helyi /Users/<te>/ útvonal, privát repóid nevei (gh repo list --visibility private), .env fájlok, és amit /pushor figyel <szöveg>-gel felvettél.gh CLI (bejelentkezve). /pushor be|ki.Döntési felugró: a döntések egygombos kérdésként jönnek.
/dontes be|ki, vagy az eszköztárban.project-pane bővítése:
vercel ls)Státuszsor bővítése (usage-band):
/compact$.session.usage().cost)userConfig)Új mod-ok:
.env, kulcsfájlok, supabase/.temp szerkesztésének tiltása (tool.call → deny)turn.complete, $.audio.play)/quote — a kijelölt szöveget idézetként a promptba teszi ($.ui.selection)prompt.compose)Egy plugin mappája közvetlenül is betölthető:
claude --plugin-dir ./plugins/usage-band
Ellenőrzés:
claude plugin validate plugins/usage-band
claude plugin test plugins/usage-bandhooks/register.tsx 255 lines1import { atom, read, update } from 'claude-code'
2import type { EngineInterface, Register } from 'claude-code'
3
4import type { Task, TaskStatus } from '../types'
5
6const isOn = atom({ plugin: 'focus-mode', key: 'isOn' } as const, false)
7const tasks = atom({ plugin: 'focus-mode', key: 'tasks' } as const, [])
8// A turnök végső válaszai: csak ezek az asszisztens-sorok látszanak fókusz módban.
9const answers = atom({ plugin: 'focus-mode', key: 'answers' } as const, [])
10
11const PLAN_TOOL = 'mcp__focus-mode__plan'
12const KEEP_ANSWERS = 100
13const WIDTH = 12
14
15type Strings = {
16 title: string
17 done: string
18 waiting: string
19 idle: string
20 on: string
21 off: string
22 usage: string
23}
24
25const STRINGS: Record<'hu' | 'en', Strings> = {
26 hu: {
27 title: 'Fókusz mód',
28 done: 'kész',
29 waiting: 'a részfeladatok hamarosan megjelennek…',
30 idle: 'bekapcsolva · /fokusz ki a kikapcsoláshoz',
31 on: 'Fókusz mód bekapcsolva: csak a részfeladatok és a végső válasz látszik.',
32 off: 'Fókusz mód kikapcsolva: újra látszik minden lépés.',
33 usage: 'Használat: /fokusz [be|ki]',
34 },
35 en: {
36 title: 'Focus mode',
37 done: 'done',
38 waiting: 'subtasks will show up shortly…',
39 idle: 'on · /fokusz off to turn it off',
40 on: 'Focus mode on: only the subtasks and the final answer are shown.',
41 off: 'Focus mode off: every step is shown again.',
42 usage: 'Usage: /fokusz [on|off]',
43 },
44}
45
46let t: Strings = STRINGS.en
47
48const PROMPT = `Focus mode is on. The person is not watching the transcript: your tool calls, their results and any text you write before the end of the turn are hidden from them. They only see a subtask list drawn from the ${PLAN_TOOL} tool, and your final message of the turn.
49
50- For any request that takes more than one step, first call ${PLAN_TOOL} with the subtasks of the job (3-8 short items, written in the person's language), before any other tool.
51- Call it again whenever a subtask starts, makes notable progress (estimate \`progress\` 0-100) or finishes. Always send the whole list.
52- Before you end the turn, every finished subtask must be marked done.
53- Make the final message a short summary of what was done, plus anything the person must decide or do. Questions for the person go in that final message or through AskUserQuestion; nothing else you write mid-turn reaches them.`
54
55const PLAN_SPEC = {
56 name: 'plan',
57 description:
58 'Sets the subtask list the person sees in focus mode, replacing the previous one. Send every subtask each time, with its status and an estimated progress percentage.',
59 inputSchema: {
60 type: 'object',
61 properties: {
62 tasks: {
63 type: 'array',
64 items: {
65 type: 'object',
66 properties: {
67 title: { type: 'string', description: 'Short subtask name, in the person\'s language.' },
68 status: { type: 'string', enum: ['pending', 'in_progress', 'done'] },
69 progress: { type: 'number', minimum: 0, maximum: 100, description: 'Estimated percent done.' },
70 },
71 required: ['title', 'status'],
72 },
73 },
74 },
75 required: ['tasks'],
76 },
77}
78
79const STATUSES: readonly TaskStatus[] = ['pending', 'in_progress', 'done']
80
81// A modell bemenetét tűrően olvassuk: hiányzó progress → státuszból becsült érték.
82export function parseTasks(input: unknown): Task[] {
83 if (!Array.isArray(input)) return []
84 return input.flatMap((raw): Task[] => {
85 if (typeof raw !== 'object' || raw === null) return []
86 const { title, status, progress } = raw as Record<string, unknown>
87 if (typeof title !== 'string' || title.trim() === '') return []
88 const s: TaskStatus = STATUSES.includes(status as TaskStatus) ? (status as TaskStatus) : 'pending'
89 const fallback = s === 'done' ? 100 : 0
90 const p = typeof progress === 'number' && Number.isFinite(progress) ? progress : fallback
91 return [{ title: title.trim(), status: s, progress: s === 'done' ? 100 : Math.max(0, Math.min(99, Math.round(p))) }]
92 })
93}
94
95export function bar(percent: number, width = WIDTH): string {
96 const filled = Math.max(0, Math.min(width, Math.round((percent / 100) * width)))
97 return '█'.repeat(filled) + '░'.repeat(width - filled)
98}
99
100export function overall(list: readonly Task[]): number {
101 if (list.length === 0) return 0
102 return Math.round(list.reduce((sum, task) => sum + task.progress, 0) / list.length)
103}
104
105export function isAnswer(text: string, list: readonly string[]): boolean {
106 const shown = text.trim()
107 if (shown === '') return false
108 return list.some(a => a.trim() === shown || a.includes(shown))
109}
110
111async function pickLanguage($: EngineInterface, setting: unknown): Promise<Strings> {
112 if (setting === 'hu' || setting === 'en') return STRINGS[setting]
113 const locale = (await $.env.get('LC_ALL')) || (await $.env.get('LANG')) || ''
114 return locale.toLowerCase().startsWith('hu') ? STRINGS.hu : STRINGS.en
115}
116
117async function setOn($: EngineInterface, value: boolean): Promise<void> {
118 await update($, isOn, () => value)
119 await $.store.set('isOn', value)
120}
121
122export const register: Register = (on, options) => {
123 if (options.language === 'hu' || options.language === 'en') t = STRINGS[options.language]
124
125 on('session.start', async ($, e, next) => {
126 t = await pickLanguage($, options.language)
127 const saved = (await $.store.get('isOn')) === true
128 await update($, isOn, () => saved)
129 await $.tool.register(PLAN_SPEC)
130 await $.command.register({
131 name: 'fokusz',
132 description: t === STRINGS.hu ? 'Fókusz mód be/ki: csak részfeladatok és végső válasz' : 'Focus mode on/off: subtasks and final answer only',
133 argumentHint: t === STRINGS.hu ? '[be|ki]' : '[on|off]',
134 immediate: true,
135 })
136 return next(e)
137 })
138
139 on('command.run', { command: 'fokusz' }, async ($, e) => {
140 const arg = e.args.trim().toLowerCase()
141 const wanted = ['be', 'on'].includes(arg) ? true : ['ki', 'off'].includes(arg) ? false : arg === '' ? !(await read($, isOn)) : null
142 if (wanted === null) return { text: t.usage }
143 await setOn($, wanted)
144 return { text: wanted ? t.on : t.off }
145 })
146
147 // Az eszköztár gombja parancs nélkül kapcsol (így nem marad parancssor a transcriptben).
148 on('state.set', { plugin: 'tool-hub', key: 'request' }, async ($, e, next) => {
149 const done = await next(e)
150 if (e.value?.tool === 'focus') await setOn($, e.value.on)
151 return done
152 })
153
154 on('tool.call', { tool: PLAN_TOOL }, async ($, e) => {
155 const list = parseTasks(e.tasks)
156 await update($, tasks, () => list)
157 const done = list.filter(task => task.status === 'done').length
158 return { result: `Plan updated: ${done}/${list.length} done.` }
159 })
160
161 on('prompt.compose', async ($, e, next) => {
162 const composed = await next(e)
163 if (!(await read($, isOn))) return composed
164 return { sections: [...composed.sections, { id: 'focus-mode:instructions', text: PROMPT, scope: 'session' as const }] }
165 })
166
167 on('turn.complete', async ($, e, next) => {
168 if (e.agentId === undefined && e.answer.trim() !== '') {
169 const answer = e.answer
170 await update($, answers, list => [...list, answer].slice(-KEEP_ANSWERS))
171 }
172 return next(e)
173 })
174
175 // Az elrejtett sorok helyére egy üres Box kerül: eszközhívások, a felhasználó promptjai és a parancsok kimenete.
176 on('ui.render', { component: ['ToolUse', 'ToolResult', 'ToolGroup', 'UserMessage', 'CommandOutput'] }, async ($, e, next) => {
177 if (!(await read($, isOn))) return next(e)
178 const { Box } = $.ui.resolve(e)
179 return <Box />
180 })
181
182 on('ui.render', { component: 'ToolProgress' }, async ($, e, next) => {
183 if (!(await read($, isOn))) return next(e)
184 return next({ ...e, props: { ...e.props, hint: '' } })
185 })
186
187 on('ui.render', { component: 'AssistantMessage' }, async ($, e, next) => {
188 if (!(await read($, isOn))) return next(e)
189 if (e.props.isSummary !== true && isAnswer(e.props.text, await read($, answers))) return next(e)
190 const { Box } = $.ui.resolve(e)
191 return <Box />
192 })
193
194 on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
195 if (e.props.hasSurvey || !(await read($, isOn))) return next(e)
196 const list = await read($, tasks)
197 // Más mod (pl. az eszköztár) is rajzolhat a sávba: az övét a lista alá tesszük.
198 const below = await next(e)
199 const { Box, Text } = $.ui.resolve(e)
200 const columns = e.props.bodyColumns
201
202 if (list.length === 0) {
203 return (
204 <Box flexDirection="column">
205 <Box>
206 <Text color="cyan" bold>{`◆ ${t.title}`}</Text>
207 <Text dimColor>{` ${e.props.isWorking ? t.waiting : t.idle}`}</Text>
208 </Box>
209 {below}
210 </Box>
211 )
212 }
213
214 const done = list.filter(task => task.status === 'done').length
215 const total = overall(list)
216 const titleRoom = Math.max(10, columns - WIDTH - 12)
217
218 return (
219 <Box flexDirection="column">
220 <Box>
221 <Text color="cyan" bold>{`◆ ${t.title}`}</Text>
222 <Text dimColor>{` ${done}/${list.length} ${t.done} `}</Text>
223 <Text color={total === 100 ? 'green' : 'cyan'}>{`${bar(total)} ${total}%`}</Text>
224 </Box>
225 {list.map((task, i) => {
226 const name = task.title.length > titleRoom ? `${task.title.slice(0, titleRoom - 1)}…` : task.title
227 if (task.status === 'done') {
228 return (
229 <Box key={`t${i}`}>
230 <Text color="green"> ✓ </Text>
231 <Text dimColor strikethrough>{name}</Text>
232 </Box>
233 )
234 }
235 if (task.status === 'in_progress') {
236 return (
237 <Box key={`t${i}`}>
238 <Text color="yellow"> ▸ </Text>
239 <Text bold>{`${name} `}</Text>
240 <Text color="yellow">{`${bar(task.progress, 8)} ${task.progress}%`}</Text>
241 </Box>
242 )
243 }
244 return (
245 <Box key={`t${i}`}>
246 <Text dimColor>{` ○ ${name}`}</Text>
247 </Box>
248 )
249 })}
250 {below}
251 </Box>
252 )
253 })
254}
255types/index.d.ts 13 lines1export type TaskStatus = 'pending' | 'in_progress' | 'done'
2export type Task = { title: string; status: TaskStatus; progress: number }
3
4// Az eszköztár (tool-hub) kérése: a kapcsoló gomb ezt írja, a mod a state.set hookjában veszi át.
5export type HubRequest = { tool: string; on: boolean; n: number }
6
7declare module 'claude-code' {
8 interface PluginState {
9 'tool-hub': { request: HubRequest | null }
10 'focus-mode': { isOn: boolean; tasks: Task[]; answers: string[] }
11 }
12}
13