SLOPSHOPPER

model-picker

Pick the model with one keypress when a session opens, no /model needed.

newbandcommandtoast
★ 2v0.2.0MITupdated 2026-10-09alfredtm/claude-model-picker
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · model-picker
› 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 › /pick-model ⎿ model-picker: Model picker shown above the prompt. Model: claude-opus-5-5. Press a digit to switch. 1: Fable 2: Opus 3: Sonnet 4: Haiku 0: Keep ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts

Draws

Band
Model: claude-opus-5-5. Press a digit to switch. 1: Fable 2: Opus 3: Sonnet 4: Haiku 0: Keep
README

model-picker

A Claude Code mod that lets you pick the model with one keypress the moment a session opens. No more typing /model.

Model: claude-fable-5-1. Press a digit to switch.
1: Fable  2: Opus  3: Sonnet  4: Haiku  0: Keep

Press a digit on the empty prompt and the mod runs /model <alias> for you. Press 0 to keep what you have. The band goes away either way.

Install

At the prompt of a terminal session:

/plugin install model-picker --marketplace alfredtm/claude-model-picker

Answer y to add the marketplace, then pick a scope (user is the usual one). The mod is active right away and in every session after.

Use

  • The band appears above the prompt on every interactive start.
  • 1 to 4 switch the model. 0 keeps the current one.
  • /pick-model brings the band back later in the session.

Headless runs (claude -p) never show it.

Default model

Set a model the mod switches to on every interactive start, before you press anything. Handy when something else (managed settings, say) resets the model between sessions.

Open /config, find Default model under model-picker and enter whatever /model accepts: opus, sonnet, a full id like claude-opus-5-5. Or put it in ~/.claude/settings.json by hand:

{
  "pluginConfigs": {
    "model-picker@alfredtm": {
      "options": { "defaultModel": "opus" }
    }
  }
}

(The key is the plugin's name as claude plugin list prints it; a --plugin-dir checkout is plain model-picker.)

The band still shows, with the default marked, so a digit overrides it for the session. Leave the option empty to keep whatever the session opened on.

Customise

The choices live in one list at the top of hooks/register.tsx:

const MODELS = [
  { hotkey: '1', label: 'Fable', value: 'fable' },
  { hotkey: '2', label: 'Opus', value: 'opus' },
  ...
]

value is whatever /model accepts: an alias like opus or a full model id.

Develop

Run it from a checkout without installing:

claude --plugin-dir /path/to/claude-model-picker

Edits to the hooks module hot-reload in that session. Check and test with:

claude plugin validate .
claude plugin test .

After the mod has loaded once, tsc -p . type-checks it against the declarations Claude Code lays down in .claude-plugin/types/.

License

MIT

Source 2 files
hooks/register.tsx 100 lines
1import { atom, read, update } from 'claude-code'
2import type { Register } from 'claude-code'
3
4// The choices, in the order drawn. The hotkey is the digit that picks it
5// from an empty prompt; the value is what `/model <value>` takes.
6const MODELS = [
7  { hotkey: '1', label: 'Fable', value: 'fable' },
8  { hotkey: '2', label: 'Opus', value: 'opus' },
9  { hotkey: '3', label: 'Sonnet', value: 'sonnet' },
10  { hotkey: '4', label: 'Haiku', value: 'haiku' },
11] as const
12
13const isShown = atom({ plugin: 'model-picker', key: 'isShown' } as const, false)
14const current = atom({ plugin: 'model-picker', key: 'current' } as const, '')
15
16export const register: Register = (on, options) => {
17  // The `defaultModel` option from /config (or settings.json `pluginConfigs`):
18  // what `/model` is run with on every interactive start. Empty means leave
19  // the session on whatever it opened with.
20  const defaultModel = typeof options.defaultModel === 'string' ? options.defaultModel.trim() : ''
21
22  on('session.start', async ($, e, next) => {
23    await $.command.register({
24      name: 'pick-model',
25      description: 'Show the model picker band above the prompt',
26    })
27
28    if (e.isInteractive) {
29      await update($, current, () => '')
30      await update($, isShown, () => true)
31
32      if (defaultModel) {
33        try {
34          await $.command.run({ command: 'model', args: defaultModel })
35        } catch (error) {
36          $.ui.toast(`model-picker: default model ${defaultModel}: ${error instanceof Error ? error.message : String(error)}`)
37        }
38      }
39
40      void $.session.model().then(model => update($, current, () => model))
41    }
42
43    return next(e)
44  })
45
46  on('command.run', { command: 'pick-model' }, async $ => {
47    const model = await $.session.model()
48    await update($, current, () => model)
49    await update($, isShown, () => true)
50
51    return { text: 'Model picker shown above the prompt.' }
52  })
53
54  on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
55    if (e.props.hasSurvey || !(await read($, isShown))) {
56      return next(e)
57    }
58
59    const { Box, Button, Text } = $.ui.resolve(e)
60    const model = await read($, current)
61
62    const pick = async (value: string) => {
63      await update($, isShown, () => false)
64      try {
65        await $.command.run({ command: 'model', args: value })
66      } catch (error) {
67        $.ui.toast(`model-picker: ${error instanceof Error ? error.message : String(error)}`)
68      }
69    }
70
71    return (
72      <Box flexDirection="column">
73        <Text dimColor>
74          Model{model ? `: ${model}` : ''}.
75          {defaultModel ? ` Default on start: ${defaultModel}.` : ''} Press a digit to switch.
76        </Text>
77        <Box gap={1}>
78          {MODELS.map(choice => (
79            <Button
80              key={choice.value}
81              hotkey={choice.hotkey}
82              label={choice.value === defaultModel ? `${choice.label} (default)` : choice.label}
83              plain
84              onPress={() => void pick(choice.value)}
85            />
86          ))}
87          <Button
88            key="keep"
89            hotkey="0"
90            label="Keep"
91            plain
92            dimColor
93            onPress={() => void update($, isShown, () => false)}
94          />
95        </Box>
96      </Box>
97    )
98  })
99}
100
types/index.d.ts 8 lines
1export type ModelPickerCurrent = string
2
3declare module 'claude-code' {
4  interface PluginState {
5    'model-picker': { isShown: boolean; current: ModelPickerCurrent }
6  }
7}
8