SLOPSHOPPER

effort-optimizer

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

newbandspinnercommandstatusprompt
v0.1.0no licenseupdated 2026-10-23jz-wilson/claude-mods/effort-optimizer
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · effort-optimizer
› fix the failing auth test and add an audit log call ⏺ Read(src/auth.ts) ⎿ Read 6 lines ⏺ Update(src/auth.ts) ⎿ Added 2 lines, removed 1 line ⏺ Bash(bun test) ⎿ 3 pass, 1 fail ● Done. refresh now rejects expired claims and logs an audit event. ✻ Worked for 42s · done 4:20 PM › /effort-optimizer ⎿ effort-optimizer: Effort Optimizer is on, mode: suggest. ⎿ effort-optimizer: This chat runs at: unknown yet. ⎿ effort-optimizer: Effort setting: not found, the mod sets the level on each request. ⎿ effort-optimizer: Type /effort-optimizer auto, suggest, off or on. ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts
README

claude-mods

Mods for Claude Code.

ModWhat it does
cache-barStatus-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.
filetreeFork 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-deskTask list above the prompt that follows Claude's to-do list, plus a side pane with one live card per subagent.
helpin-perPane listing open Helpin PER-* tasks; pick one to have Claude start working on it.
cache-clockCountdown above the prompt until the chat's cache goes cold, with a Keep warm button.
handoffWhen 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).
reflectWhen you correct Claude, offers to save the fix as a lasting rule in CLAUDE.md or a rules folder.
chat-inventorySide 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-optimizerBefore 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-refinerA 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 setting

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.

Install

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 and claude-hud

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:

  • backs up settings.json to settings.json.bak-cache-bar;
  • moves the current statusLine.command (claude-hud's, for example) into inner in ~/.claude/plugins/cache-bar/config.json;
  • points 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.

Development

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-bar
Source 2 files
hooks/register.tsx 538 lines
1import 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}
538
types/index.d.ts 27 lines
1export 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