SLOPSHOPPER

model-band

Band above the prompt: current model and reasoning effort.

newband
★ 1v0.1.4no licenseupdated 2026-10-03Lunik/gmz-claude-marketplace/model-band
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · model-band
› 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 Model Opus 5.5 ▾ ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts

Draws

Band
Model Opus 5.5 ▾
README

model-band

Lets you switch model and reasoning effort without typing a command.

Model Sonnet 5.5 ▾ ┃ Effort medium ▾

Behavior

  • Band above the prompt, not in the bottom-right grid.
  • Selecting a model runs /model <id>, selecting an effort runs /effort <level>.
  • Offered models: Fable 5.1, Opus 5.5, Sonnet 5.5, Haiku 4.5. The current model is always shown, even if it is not in the list.
  • Efforts: low, medium, high, xhigh, max.
  • Before any turn has run, the effort is read from settings.json (modelSettings.<model>.effortLevel, else effortLevel). After that it follows what the turn reports.

Customizing

The model and effort lists are the MODELS and EFFORTS constants in hooks/register.tsx. Bump version in plugin.json after editing.

Source 1 files
hooks/register.tsx 82 lines
1import type { EngineInterface, Register } from 'claude-code'
2
3// "claude-sonnet-5-5" -> "Sonnet 5.5"; drops "claude-" and a trailing -YYYYMMDD
4export const displayName = (model: string) => {
5  const [name, ...ver] = model.replace(/^claude-/, '').replace(/-\d{8}$/, '').split('-')
6  return [name[0].toUpperCase() + name.slice(1), ver.join('.')].filter(Boolean).join(' ')
7}
8
9// ponytail: fixed list; a model not in it still shows (as current) but is not offered
10const MODELS = ['claude-fable-5-1', 'claude-opus-5-5', 'claude-sonnet-5-5', 'claude-haiku-4-5-20251001']
11const EFFORTS = ['low', 'medium', 'high', 'xhigh', 'max']
12
13// effort before any turn: the per-model setting, else the global one
14const savedEffort = async ($: EngineInterface, m: string) => {
15  const s = (await $.settings.read().catch(() => ({}))) as { effortLevel?: string; modelSettings?: Record<string, { effortLevel?: string }> }
16  return s.modelSettings?.[m]?.effortLevel ?? s.effortLevel
17}
18
19export const register: Register = on => {
20  // ponytail: module vars reset on hot reload; model/effort show again after the next turn step
21  let model = ''
22  let effort: string | number | undefined
23
24  on('turn.step', async function* ($, e, next) {
25    if (!e.agentId && e.index === 0 && (e.effort !== effort || e.model !== model)) {
26      effort = e.effort
27      model = e.model
28      $.ui.invalidate('ui.render')
29    }
30    return yield* next(e)
31  })
32
33  on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
34    if (e.props.hasSurvey) return next(e)
35    const m = model || (await $.session.model())
36    if (!m) return next(e)
37    effort ??= await savedEffort($, m)
38
39    const { Box, Text, Select } = $.ui.resolve(e)
40    const modelIds = MODELS.includes(m) ? MODELS : [m, ...MODELS]
41    // stack under whatever another plugin drew here; the engine's own default is no tree
42    const other = await next(e)
43    const line = (
44      <Box>
45        <Text bold color="#7dcfff">Model </Text>
46        <Select
47          key="model"
48          value={m}
49          options={modelIds.map(id => ({ value: id, label: displayName(id) }))}
50          onSelect={id => {
51            model = id
52            void $.command.run({ command: 'model', args: id })
53            $.ui.invalidate('ui.render')
54          }}
55        />
56        {effort !== undefined && (
57          <Box>
58            <Text color="#565f89"> ┃ </Text>
59            <Text bold color="#7dcfff">Effort </Text>
60            <Select
61              key="effort"
62              value={String(effort)}
63              options={EFFORTS.map(v => ({ value: v }))}
64              onSelect={v => {
65                effort = v
66                void $.command.run({ command: 'effort', args: v })
67                $.ui.invalidate('ui.render')
68              }}
69            />
70          </Box>
71        )}
72      </Box>
73    )
74    return other.type === 'engine' ? line : (
75      <Box flexDirection="column">
76        {other}
77        {line}
78      </Box>
79    )
80  })
81}
82