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

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.
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.
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.
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.
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.
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/.
MIT
hooks/register.tsx 100 lines1import { 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}
100types/index.d.ts 8 lines1export type ModelPickerCurrent = string
2
3declare module 'claude-code' {
4 interface PluginState {
5 'model-picker': { isShown: boolean; current: ModelPickerCurrent }
6 }
7}
8