Reads each prompt, picks the effort level it needs with Ben's four-question framework, and offers Switch to that level, or sets it by itself in auto mode

Mods for Claude Code.
| Mod | What it does |
|---|---|
| cache-bar | Status-line bar under your existing status line showing how long the prompt cache stays warm, with a handoff countdown: cache ▰▰▰▱▱▱▱▱ 23m 182k · handoff in 18m. Turns yellow at a quarter of the TTL, red at the handoff threshold, and follows the drop to a 5-minute TTL. /cache-bar:setup installs it. |
| filetree | Fork of data-goblin/claude-code-filetree that shows only the files Claude touched this session; the header's ◆ button toggles to the whole tree. |
| helper-desk | Task list above the prompt that follows Claude's to-do list, plus a side pane with one live card per subagent. |
| helpin-per | Pane listing open Helpin PER-* tasks; pick one to have Claude start working on it. |
| cache-clock | Countdown above the prompt until the chat's cache goes cold, with a Keep warm button. |
| handoff | When a chat gets very long, writes a handoff prompt and starts a fresh chat with it (/refresh <focus>; the handoff tells the new chat to run /pickup). |
| reflect | When you correct Claude, offers to save the fix as a lasting rule in CLAUDE.md or a rules folder. |
| chat-inventory | Side pane, In this chat, listing the to-do list, connectors and calls per connector, files and skills this chat used. |
| screen-share | /screen-share hides names, emails, phone numbers and money amounts on screen while you record. |
| replay-theater | /replay steps through the file edits Claude made in the last turn, one diff at a time. |
| mods | /mods opens a pane to turn each mod on or off, including mods installed from other marketplaces, and to install mods a marketplace offers that are not installed yet. |
| effort-optimizer | Before Claude starts on a prompt, a quick Sonnet check picks the effort level it needs (Low to Max) and offers a one-click switch, or sets it by itself with /effort-optimizer auto. |
| prompt-refiner | A Refine button in the hint line under the prompt, any time in the chat. Press it and pick a rewrite style for the prompt in the box, like a phone keyboard's suggestions: Clearer, Shorter, Elaborate (fuller, same shape), Structured (the job, the why, the guardrails, done means), Ask me first (up to 3 questions on gaps), or My guidance. The rewrite shows above the prompt with what changed; Use it fills the box, Refine runs another pass. /refine <prompt> does the same. |
The eight mods other than replay-theater, from helper-desk to effort-optimizer, come from Ben AI's benai-mods (benai.co) and are redistributed with permission, with Windows fixes added here. replay-theater (one Ben AI change: /mods switch support) is Anthropic's sample mod from anthropics/claude-code-playground, under the Apache 2.0 license in its LICENSE.
Reflect saves a rule either to ~/.claude/CLAUDE.md (Save everywhere) or as its own note in a folder you choose (Save to vault). Set that folder as rules_folder when you install reflect, or later for reflect in /config. If it is empty, reflect asks for the folder the first time you press Save to vault.
At the prompt of a terminal session:
/plugin install helper-desk --marketplace jz-wilson/claude-mods
Answer y to add the marketplace, then pick a scope.
From a shell:
claude plugin install helper-desk --marketplace jz-wilson/claude-mods
claude plugin install cache-bar --marketplace jz-wilson/claude-mods
In the desktop app, where /plugin does not run: click + next to the prompt box, then Plugins, Add marketplace with jz-wilson/claude-mods, then Add plugin.
cache-bar is a status-line script, so it takes over statusLine and runs the previous status line inside it. After installing, run /cache-bar:setup. It:
settings.json to settings.json.bak-cache-bar;statusLine.command (claude-hud's, for example) into inner in ~/.claude/plugins/cache-bar/config.json;statusLine.command at the newest installed cache-bar version, and sets refreshInterval: 60 so the countdown repaints while you are idle.The previous status line prints first, unchanged, and the bar goes on its own line underneath. If the inner command fails, the bar still prints. Before the first request of a session there is no cache yet, so the bar is hidden; after expiry it shows a dim cold.
The same config file sets the handoff threshold and the context floor:
{ "inner": "…", "handoffMinutes": 5, "floorTokens": 80000 }
The threshold is capped at a fifth of the TTL, so it fits the 5-minute TTL too. Under floorTokens of context the bar says no handoff (<floor).
To undo, copy settings.json.bak-cache-bar back over settings.json, or set statusLine.command back to the inner value.
Each mod is a folder with .claude-plugin/plugin.json. CI runs claude plugin validate on every mod for each push and pull request, then claude plugin test for mods with a hooks module and bun test for the rest. A merge to main that bumps a mod's version publishes a GitHub release tagged <mod>-v<version>.
claude plugin validate agent-tree
claude plugin test agent-tree
bun test cache-barhooks/register.tsx 538 lines1import type { ConfigRow, EngineInterface, Register, SessionMessage } from 'claude-code'
2
3import type { Band, Level } from '../types'
4
5const BAND = { plugin: 'effort-optimizer', key: 'band' } as const
6const OVERRIDE = { plugin: 'effort-optimizer', key: 'override' } as const
7// Fits the footer slot (about 14 cells).
8const SHORT = { low: 'Low', medium: 'Medium', high: 'High', xhigh: 'XHigh', max: 'Max' } as const
9
10const LEVELS: readonly Level[] = ['low', 'medium', 'high', 'xhigh', 'max']
11const NAMES: Record<Level, string> = { low: 'Low', medium: 'Medium', high: 'High', xhigh: 'Extra high', max: 'Max' }
12
13const APPLIED_MS = 15000
14const ACCENT = '#d97757'
15const GOOD = '#16a34a'
16const BAD = '#dc2626'
17
18type Mode = 'suggest' | 'auto'
19type Pick = { level: Level; why: string }
20
21// Module variables reset on a reload; the band and the override live in $.state.
22let home = ''
23let enabled = true
24let mode: Mode = 'suggest'
25// Bumped on every prompt, so a slow pick for an older prompt is dropped.
26let gen = 0
27let nextId = 1
28// The level the main chat's last request ran at, before this mod touched it.
29let seen: Level | '' = ''
30// The level for the rest of the running turn, set by a Switch or by auto mode.
31let turnLevel: Level | null = null
32// The /config row that holds the effort setting, found at session start.
33let rowKey = ''
34// A /effort run waiting for the turn to end.
35// The level the mod puts on requests, and the picker level it saw when it took over.
36let hold: { level: Level; base: string } | null = null
37let lastEffort = ''
38// The prompt waiting for its pick at the turn's first request.
39let held: { text: string; mine: number } | null = null
40
41// The /mods switchboard turns this mod off in every chat with switches.json.
42const NAME = 'effort-optimizer'
43let off = false
44
45async function readSwitch($: EngineInterface) {
46 let now = false
47 try {
48 const path = `${$.plugin.root.replace(/\\/g, '/').split('/.claude/')[0] ?? ''}/.claude/mods/switches.json`
49 if (await $.fs.exists(path)) {
50 const json = JSON.parse(String(await $.fs.read(path))) as Record<string, unknown>
51 now = json[NAME] === false
52 }
53 } catch {
54 now = off
55 }
56 if (now === off) return
57 off = now
58 if (off) turnLevel = null
59 try {
60 $.ui.invalidate('ui.render')
61 } catch {
62 // The next state change redraws anyway.
63 }
64}
65
66async function log($: EngineInterface, what: string, err: unknown) {
67 try {
68 const path = `${home}/.claude/mods-logs/effort-optimizer.log`
69 const old = (await $.fs.exists(path)) ? String(await $.fs.read(path)) : ''
70 const msg = err instanceof Error ? err.message : String(err)
71 await $.fs.write(path, `${old.slice(-20_000)}${new Date(await $.clock.now()).toISOString()} ${what}: ${msg}\n`)
72 } catch {
73 // Nowhere left to report it.
74 }
75}
76
77async function getBand($: EngineInterface): Promise<Band | null> {
78 const { value } = await $.state.get(BAND)
79 return value ?? null
80}
81
82async function setBand($: EngineInterface, b: Band | null) {
83 await $.state.set(BAND, b)
84}
85
86// "Extra high", "x-high" and "xhigh" all read as xhigh.
87function norm(s: string) {
88 const t = s.toLowerCase().replace(/[^a-z]/g, '')
89 if (t === 'extrahigh' || t === 'xhigh') return 'xhigh'
90 return t
91}
92
93function asLevel(v: unknown): Level | '' {
94 if (typeof v !== 'string') return ''
95 const t = norm(v)
96 return (LEVELS as readonly string[]).includes(t) ? (t as Level) : ''
97}
98
99function isEffortRow(r: ConfigRow) {
100 if (!/effort/i.test(`${r.key} ${r.label}`)) return false
101 if (r.kind !== 'choice') return false
102 return (r.options ?? []).some(o => norm(o) === 'high')
103}
104
105async function findRow($: EngineInterface) {
106 const rows = await $.config.list()
107 return rows.find(isEffortRow) ?? null
108}
109
110// The level the chat runs at now: the last request's, else the setting's.
111async function currentLevel($: EngineInterface): Promise<Level | ''> {
112 if (seen) return seen
113 try {
114 const row = await findRow($)
115 return row ? asLevel(row.value) : ''
116 } catch {
117 return ''
118 }
119}
120
121// Sets the chat's effort setting, or, when no setting takes it, has this mod put the level on every request
122// until the person moves the picker. No /effort command is run.
123async function applyLevel($: EngineInterface, level: Level) {
124 turnLevel = level
125 let note = ''
126 try {
127 const row = await findRow($)
128 const option = row?.options?.find(o => norm(o) === level)
129 if (row && option && !row.isLocked) {
130 const r = await $.config.set({ key: row.key, value: option })
131 if (!r.deny) {
132 await $.state.set(OVERRIDE, null)
133 return ''
134 }
135 note = r.deny
136 } else {
137 note = row ? (row.isLocked ? 'the effort setting is locked' : 'the effort setting has no such level') : 'no effort setting found'
138 }
139 } catch (err) {
140 note = err instanceof Error ? err.message : String(err)
141 }
142 await log($, 'set effort', note)
143 hold = { level, base: lastEffort }
144 await $.state.set(OVERRIDE, level)
145 redraw($)
146 return ''
147}
148
149function redraw($: EngineInterface) {
150 try {
151 $.ui.invalidate('ui.render')
152 } catch {
153 // The next state change redraws anyway.
154 }
155}
156
157// Plain text: no em or en dashes, never the banned word, one short line.
158function cleanWhy(raw: string) {
159 const r = raw
160 .replace(/\s*[—–]\s*/g, ', ')
161 .replace(/\bship(s|ped|ping)?\b/gi, 'publish')
162 .replace(/\s+/g, ' ')
163 .trim()
164 .replace(/^["'`\s]+|["'`\s.]+$/g, '')
165 return r.slice(0, 90)
166}
167
168function lastAssistantText(messages: readonly SessionMessage[]) {
169 for (let i = messages.length - 1; i >= 0; i -= 1) {
170 const m = messages[i]!
171 if (m.role === 'assistant' && m.text.trim()) return m.text
172 }
173 return ''
174}
175
176// Ben's framework from the effort levels video: four questions, stop at the first yes.
177function pickPrompt(text: string, lastReply: string, now: Level | '') {
178 return `You pick the effort level an AI assistant (Claude) should use for one prompt. Effort sets how much time, reading and checking Claude spends, and how many decisions it makes alone. It is not how smart Claude is.
179
180Ask these four questions in this order and stop at the first yes:
181
1821. Will the person go back and forth with Claude on this, or is it a simple task? Then low.
183 Examples: brainstorming options, quick rewrites (friendlier, shorter), sorting or tagging items, short summaries, rough first drafts or outlines, small tweaks to something already made, simple questions.
1842. Would a hidden mistake cost money, a client, trust or a wrong decision? Then high.
185 Examples: checking numbers across sources, open-ended analysis with no set path (why did churn go up), reviewing a contract for risk, fact-checking, going through long documents, verifying and testing work made at a lower level, building or fixing a skill.
1863. Is the person handing off a long, hard and critical job and walking away, with no time to answer questions? All three must be true. Then xhigh.
187 Examples: a full research report from a year of calls and tickets, reviewing a 50 page agreement before signing, an investor pitch deck as the best one shot. Answer max only when the prompt asks for the deepest possible work and says it is critical; otherwise xhigh.
1884. Everything else is medium: everyday writing, repurposing content, meeting summaries with decisions and next steps, standard reports in a set format, quick research before a call. If you are not sure, medium.
189
190A task being long does not make it high. Rule-following work stays low or medium.
191If the prompt only approves, answers a question, or says to go on with earlier work (yes, go, ok, do it, option 2), answer keep.
192
193The chat runs at: ${now || 'unknown'}
194
195Claude's last reply (first 600 characters, for context):
196<reply>
197${lastReply || '(none, this is the first prompt)'}
198</reply>
199
200The new prompt:
201<prompt>
202${text}
203</prompt>
204
205Reply with JSON only: {"level": "low|medium|high|xhigh|max|keep", "why": "the reason in under 12 plain words"}`
206}
207
208function parsePick(text: string): Pick | null {
209 const m = /\{[\s\S]*\}/.exec(text)
210 if (!m) return null
211 try {
212 const j = JSON.parse(m[0]) as { level?: unknown; why?: unknown }
213 const level = asLevel(j.level)
214 if (!level) return null
215 return { level, why: cleanWhy(typeof j.why === 'string' ? j.why : '') }
216 } catch {
217 return null
218 }
219}
220
221async function pick($: EngineInterface, text: string, now: Level | ''): Promise<Pick | null> {
222 const messages = await $.session.messages()
223 const lastReply = lastAssistantText(messages).slice(0, 600)
224 const r = await $.model.complete({
225 model: 'claude-sonnet-5-5',
226 prompt: pickPrompt(text.slice(0, 4000), lastReply, now),
227 maxTokens: 120,
228 effort: 'low',
229 timeoutMs: 20_000,
230 })
231 if (!r.isAnswered) throw new Error(`pick failed: ${r.reason}`)
232 return parsePick(r.text)
233}
234
235async function hideLater($: EngineInterface, id: number) {
236 await $.clock.sleep(APPLIED_MS)
237 const b = await getBand($)
238 if (b && b.id === id && b.phase === 'applied') await setBand($, null)
239}
240
241async function showApplied($: EngineInterface, p: Pick, from: Level | '', note: string) {
242 const id = nextId++
243 await setBand($, { phase: 'applied', id, level: p.level, from, why: p.why, note })
244 void hideLater($, id).catch(err => log($, 'hide', err))
245}
246
247// Suggest mode: pick before the prompt goes in and, when the level differs, hold the prompt until the person answers.
248async function suggest($: EngineInterface, text: string, mine: number) {
249 const now = await currentLevel($)
250 try {
251 await $.ui.status('checking effort…')
252 } catch {
253 // The status line is a nicety.
254 }
255 let p: Pick | null = null
256 try {
257 p = await pick($, text, now)
258 } finally {
259 try {
260 await $.ui.status(undefined)
261 } catch {
262 // Nothing to clear.
263 }
264 }
265 if (!p || mine !== gen || p.level === now) return
266 const go = `Switch to ${NAMES[p.level]}`
267 const auto = 'Switch, and auto-adjust from now'
268 const keep = now ? `Keep ${NAMES[now]}` : 'Keep it as it is'
269 let answer = ''
270 try {
271 answer = await $.ui.ask(`This looks like a ${NAMES[p.level]} effort task${p.why ? `: ${p.why}` : ''}. Switch before Claude starts?`, {
272 options: [go, auto, keep],
273 header: 'Effort',
274 })
275 } catch {
276 // A dismissed dialog keeps the level and sends the prompt.
277 return
278 }
279 if (answer !== go && answer !== auto) return
280 if (answer === auto) {
281 mode = 'auto'
282 await $.store.set('mode', mode)
283 }
284 const note = await applyLevel($, p.level)
285 await showApplied($, p, now, note)
286}
287
288// Auto mode: pick before the prompt goes in, so the first request already runs at the right level.
289async function autoAdjust($: EngineInterface, text: string, mine: number) {
290 const now = await currentLevel($)
291 try {
292 await $.ui.status('picking effort…')
293 } catch {
294 // The status line is a nicety.
295 }
296 try {
297 const p = await pick($, text, now)
298 if (!p || mine !== gen) return
299 if (p.level === now) {
300 turnLevel = p.level
301 return
302 }
303 const note = await applyLevel($, p.level)
304 await showApplied($, p, now, note)
305 } finally {
306 try {
307 await $.ui.status(undefined)
308 } catch {
309 // Nothing to clear.
310 }
311 }
312}
313
314export const register: Register = on => {
315 on('session.start', async ($, e, next) => {
316 home = $.plugin.root.replace(/\\/g, '/').split('/.claude/')[0] ?? ''
317 await $.command.register({
318 name: 'effort-optimizer',
319 description: 'Effort Optimizer. auto: set effort by itself. suggest: offer a switch. off or on',
320 argumentHint: '[auto|suggest|off|on]',
321 })
322 enabled = (await $.store.get('enabled')) !== false
323 mode = (await $.store.get('mode')) === 'auto' ? 'auto' : 'suggest'
324 await readSwitch($).catch(() => undefined)
325 $.clock.every(3000, () => {
326 void readSwitch($).catch(() => undefined)
327 })
328 void (async () => {
329 const row = await findRow($)
330 rowKey = row?.key ?? ''
331 await log($, 'effort row', row ? `${row.key} = ${String(row.value)} [${(row.options ?? []).join(', ')}]${row.isLocked ? ' locked' : ''}` : 'none found')
332 })().catch(err => log($, 'find row', err))
333 return next(e)
334 })
335
336 on('prompt.submit', async ($, e, next) => {
337 if (off || !enabled) return next(e)
338 if (e.origin.kind !== 'composer' && e.origin.kind !== 'bridge') return next(e)
339 const text = e.text.trim()
340 if (!text || text.startsWith('/')) return next(e)
341 gen += 1
342 turnLevel = null
343 const mine = gen
344 // A new prompt makes an unanswered offer stale.
345 const b = await getBand($).catch(() => null)
346 if (b?.phase === 'offer') await setBand($, null).catch(() => undefined)
347 // The pick waits for the turn's first request, which carries the level the chat really runs at.
348 held = { text, mine }
349 return next(e)
350 }).catch(($, e, next) => next(e))
351
352 // Every main-chat request: note its level, and put the chosen one on it when the setting could not be changed.
353 on('turn.step', async function* ($, e, next) {
354 if (off || e.agentId !== undefined || typeof e.effort !== 'string') return yield* next(e)
355 lastEffort = e.effort
356 // The picker moved (by hand, or /effort landed): the mod lets go.
357 if (hold && e.effort !== hold.base) { hold = null; turnLevel = null }
358 let value: Level | null = enabled && hold ? hold.level : null
359 if (!hold && (await $.state.get(OVERRIDE)).value) {
360 await $.state.set(OVERRIDE, null)
361 redraw($)
362 }
363 // What the chat runs at now: the level the mod holds, else the picker's.
364 seen = value ?? (asLevel(e.effort) || seen)
365 if (held && e.index === 0 && enabled) {
366 const h = held
367 held = null
368 if (mode === 'auto') await autoAdjust($, h.text, h.mine).catch(err => log($, 'auto', err))
369 else await suggest($, h.text, h.mine).catch(err => log($, 'suggest', err))
370 value = enabled && hold ? hold.level : null
371 }
372 const want = turnLevel ?? (enabled ? (value ?? null) : null)
373 if (!want || want === e.effort) return yield* next(e)
374 void log($, 'request', `${e.effort} -> ${want}`)
375 return yield* next({ ...e, effort: want })
376 })
377
378 on('command.run', { command: 'effort-optimizer' }, async ($, e) => {
379 if (off) return { text: 'Effort Optimizer is off. Turn it on with /mods.' }
380 const arg = e.args.trim().toLowerCase()
381 if (arg === 'auto' || arg === 'suggest') {
382 mode = arg
383 enabled = true
384 await $.store.set('mode', mode)
385 await $.store.set('enabled', true)
386 return {
387 text:
388 mode === 'auto'
389 ? 'Effort Optimizer: auto. Every prompt now gets the effort level it needs, set by itself.'
390 : 'Effort Optimizer: suggest. When a prompt needs another level, a Switch button shows above the prompt.',
391 }
392 }
393 if (arg === 'off' || arg === 'on') {
394 enabled = arg === 'on'
395 await $.store.set('enabled', enabled)
396 if (!enabled) {
397 turnLevel = null
398 await $.state.set(OVERRIDE, null)
399 await setBand($, null)
400 }
401 return { text: enabled ? `Effort Optimizer is on (${mode}).` : 'Effort Optimizer is off. Type /effort-optimizer on to turn it back on.' }
402 }
403 const now = await currentLevel($)
404 const { value } = await $.state.get(OVERRIDE)
405 return {
406 text: [
407 `Effort Optimizer is ${enabled ? 'on' : 'off'}, mode: ${mode}.`,
408 `This chat runs at: ${now ? NAMES[now] : 'unknown yet'}${value ? ` (held by the mod: ${NAMES[value]})` : ''}.`,
409 `Effort setting: ${rowKey || 'not found, the mod sets the level on each request'}.`,
410 'Type /effort-optimizer auto, suggest, off or on.',
411 ].join('\n'),
412 }
413 })
414
415 on('ui.press', { plugin: 'effort-optimizer', element: 'switch' }, async ($, e) => {
416 const b = await getBand($)
417 if (!b || b.phase !== 'offer') return { element: e.element }
418 try {
419 const note = await applyLevel($, b.level)
420 await showApplied($, { level: b.level, why: b.why }, b.from, note)
421 } catch (err) {
422 await log($, 'switch', err)
423 await setBand($, { ...b, note: `Could not switch: ${(err instanceof Error ? err.message : String(err)).slice(0, 100)}` })
424 }
425 return { element: e.element }
426 })
427
428 on('ui.press', { plugin: 'effort-optimizer', element: 'auto' }, async ($, e) => {
429 mode = 'auto'
430 await $.store.set('mode', mode)
431 const b = await getBand($)
432 if (b?.phase === 'offer') {
433 const note = await applyLevel($, b.level).catch(err => String(err))
434 await showApplied($, { level: b.level, why: 'Auto-adjust is on for every prompt' }, b.from, note)
435 }
436 return { element: e.element }
437 })
438
439 on('ui.press', { plugin: 'effort-optimizer', element: 'info' }, async ($, e) => {
440 const b = await getBand($)
441 if (b) await setBand($, { ...b, phase: 'info' })
442 return { element: e.element }
443 })
444
445 on('ui.press', { plugin: 'effort-optimizer', element: 'info-close' }, async ($, e) => {
446 await setBand($, null)
447 return { element: e.element }
448 })
449
450 on('ui.press', { plugin: 'effort-optimizer', element: 'skip' }, async ($, e) => {
451 await setBand($, null)
452 return { element: e.element }
453 })
454
455 // The app's model menu belongs to the app: it refuses a chat that changes its own effort.
456 // So the footer slot beside it names the level the mod really sends.
457 on('ui.render', { component: 'SessionMode' }, async ($, e, next) => {
458 if (off || !enabled) return next(e)
459 const { value } = await $.state.get(OVERRIDE)
460 if (!value) return next(e)
461 const label = `Effort ${SHORT[value]}`
462 if (e.surface === 'desktop' || e.surface === 'vscode') {
463 const { Text } = $.ui.resolve(e)
464 return <Text color="#d97757">{label}</Text>
465 }
466 return next({ ...e, props: { ...e.props, modes: [...e.props.modes, label] } })
467 })
468
469 on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
470 if (off || !enabled || e.props.hasSurvey) return next(e)
471 const b = await getBand($)
472 if (!b) return next(e)
473 const rest = await next(e)
474 const { Box, Text, Button } = $.ui.resolve(e)
475 const noop = () => undefined
476
477 if (b.phase === 'applied') {
478 return (
479 <Box flexDirection="column">
480 {rest}
481 <Box flexDirection="row" gap={1}>
482 <Text wrap="truncate-end">
483 <Text color={GOOD}>✓</Text>
484 <Text> Effort set to </Text>
485 <Text bold>{NAMES[b.level]}</Text>
486 {b.from ? <Text dimColor> (was {NAMES[b.from]})</Text> : null}
487 {b.why ? <Text dimColor> · {b.why}</Text> : null}
488 </Text>
489 <Button key="info" label="ⓘ Learn more" variant="secondary" onPress={noop} />
490 </Box>
491 </Box>
492 )
493 }
494
495 if (b.phase === 'info') {
496 const was = b.from ? NAMES[b.from] : 'your old level'
497 return (
498 <Box flexDirection="column">
499 {rest}
500 <Box flexDirection="column">
501 <Text>
502 <Text color={ACCENT}>ⓘ</Text>
503 <Text bold> Claude now works at {NAMES[b.level]} effort</Text>
504 </Text>
505 <Text dimColor>The mod sets the level on each message before it leaves your computer.</Text>
506 <Text dimColor>The effort menu next to the model name still shows {was}. Only you can move that menu: the app does not let a chat change its own menu.</Text>
507 <Text dimColor>To check the real level, look for the orange "Effort {SHORT[b.level]}" label at the bottom, or type /effort-optimizer.</Text>
508 <Text dimColor>If you move the menu yourself, the mod follows the menu again.</Text>
509 <Box flexDirection="row" gap={1}>
510 <Button key="info-close" label="Got it" role="dismiss" onPress={noop} />
511 </Box>
512 </Box>
513 </Box>
514 )
515 }
516
517 return (
518 <Box flexDirection="column">
519 {rest}
520 <Box flexDirection="column">
521 <Text>
522 <Text color={ACCENT}>●</Text>
523 <Text bold> This looks like a {NAMES[b.level]} effort task</Text>
524 {b.from ? <Text dimColor> (you are on {NAMES[b.from]})</Text> : null}
525 </Text>
526 {b.why ? <Text dimColor>{b.why}</Text> : null}
527 <Box flexDirection="row" gap={1}>
528 <Button key="switch" label={`Switch to ${NAMES[b.level]}`} variant="primary" onPress={noop} />
529 <Button key="auto" label="Auto-adjust every prompt" variant="secondary" onPress={noop} />
530 <Button key="skip" label="Skip" role="dismiss" onPress={noop} />
531 </Box>
532 {b.note ? <Text color={BAD}>{b.note}</Text> : null}
533 </Box>
534 </Box>
535 )
536 })
537}
538types/index.d.ts 27 lines1export type Level = 'low' | 'medium' | 'high' | 'xhigh' | 'max'
2
3export type Band = {
4 /** offer: the suggestion and its buttons. applied: one line for a few seconds. info: the Learn more panel, open until closed. */
5 phase: 'offer' | 'applied' | 'info'
6 /** Changes with every new band, so an old timer never clears a newer one. */
7 id: number
8 /** The level the framework picked. */
9 level: Level
10 /** The level the chat ran at when the prompt went in. */
11 from: Level | ''
12 /** One short reason, from the framework. */
13 why: string
14 /** A line under the buttons: an error, or how the level was set. */
15 note: string
16}
17
18declare module 'claude-code' {
19 interface PluginState {
20 'effort-optimizer': {
21 band: Band | null
22 /** The level the mod puts on every main-chat request when the effort setting could not be changed. */
23 override: Level | null
24 }
25 }
26}
27