Picks Sonnet 5.5 or Opus 5.5 per prompt: Opus for hard work, Sonnet for easy work

프롬프트를 보고 Sonnet 5.5와 Opus 5.5 중 맞는 모델로 자동 전환하는 Claude Code 모드. 어려운 작업(설계, 디버그, 보안, 성능 등)은 Opus, 쉬운 작업(오타, 이름 변경, 설명, 요약 등)은 Sonnet을 씁니다. 판단은 키워드와 길이로 하므로 토큰이 들지 않습니다.
curl -fsSL https://raw.githubusercontent.com/yeopjoonhyun/claude-auto-model/main/install.sh | bash
python3가 필요합니다.
PowerShell에서:
irm https://raw.githubusercontent.com/yeopjoonhyun/claude-auto-model/main/install.ps1 | iex
Windows PowerShell 5.1과 PowerShell 7 모두 됩니다. 기존 settings.json은 settings.json.bak으로 남깁니다.
~/.claude/mods/auto-model(Windows는 %USERPROFILE%\.claude\mods\auto-model)에 mod/ 폴더의 파일을 내려받고 ~/.claude/settings.json의 env.CLAUDE_CODE_PLUGIN_DIRS에 등록합니다. 새 세션부터 적용됩니다. AUTO_MODEL_REF 환경 변수로 다른 브랜치나 태그에서 설치할 수 있습니다.
/auto-model status: 현재 상태와 선택된 모델 보기/auto-model off: 끄기/auto-model on: 켜기모델을 바꾸면 프롬프트 캐시가 새로 만들어져 첫 응답이 비쌀 수 있습니다. 키워드는 mod/hooks/register.ts의 HARD, EASY 목록에서 고칠 수 있습니다.
hooks/register.ts 81 lines1import type { Register } from 'claude-code'
2
3const OPUS = 'claude-opus-5-5'
4const SONNET = 'claude-sonnet-5-5'
5
6// Words that suggest hard work: design, deep debugging, big changes.
7const HARD = [
8 '설계', '아키텍처', '구조', '리팩터', '리팩토링', '보안', '성능',
9 '원인', '왜 안', '왜안', '안 돼', '안돼', '버그', '디버그', '전체', '복잡', '최적화', '알고리즘',
10 'architecture', 'refactor', 'redesign', 'security', 'performance',
11 'root cause', 'debug', 'race condition', 'optimize', 'algorithm', 'design',
12]
13
14// Words that suggest easy work: small edits, questions, explanations.
15const EASY = [
16 '오타', '이름 바꿔', '이름바꿔', '주석', '설명해', '뭐야', '무슨 뜻', '간단', '한 줄', '포맷', '정렬',
17 '번역', '요약', '색', '문구', '텍스트', 'readme',
18 'typo', 'rename', 'comment', 'explain', 'what is', 'simple', 'one line', 'format', 'translate', 'summarize',
19]
20
21const count = (text: string, words: string[]) => words.filter(w => text.includes(w)).length
22
23// 'opus', 'sonnet', or undefined (not clear: keep whatever was chosen last).
24function pick(prompt: string): 'opus' | 'sonnet' | undefined {
25 const text = prompt.toLowerCase()
26 const hard = count(text, HARD) + (text.length > 600 ? 1 : 0)
27 const easy = count(text, EASY)
28
29 if (hard >= 1 && hard >= easy) return 'opus'
30 if (easy >= 1 && hard === 0 && text.length < 300) return 'sonnet'
31 return undefined
32}
33
34let isEnabled = true
35let chosen: string | undefined
36
37const label = (model: string) =>
38 model === OPUS ? 'Opus 5.5' : model === SONNET ? 'Sonnet 5.5' : model
39
40export const register: Register = (on) => {
41 on('session.start', async ($, e, next) => {
42 await $.command.register({
43 name: 'auto-model',
44 description: 'Auto model switching: /auto-model on | off | status',
45 })
46 return next(e)
47 })
48
49 on('command.run', { command: 'auto-model' }, async (_$, e) => {
50 const arg = e.args.trim()
51 if (arg === 'on') isEnabled = true
52 if (arg === 'off') {
53 isEnabled = false
54 chosen = undefined
55 }
56 const model = chosen === OPUS ? 'Opus 5.5' : chosen === SONNET ? 'Sonnet 5.5' : '세션 기본 모델 유지'
57 return { text: `자동 모델 전환: ${isEnabled ? '켜짐' : '꺼짐'} | 현재 선택: ${model}` }
58 })
59
60 // Decide once per prompt, from the person's own text only.
61 on('prompt.submit', async ($, e, next) => {
62 if (isEnabled && e.origin.kind === 'composer') {
63 const level = pick(e.text)
64 if (level !== undefined) chosen = level === 'opus' ? OPUS : SONNET
65 }
66 return next(e)
67 })
68
69 // Apply the choice to each request the main conversation makes.
70 // Show the model actually sent, once per answer (the first request of the turn).
71 on('turn.step', async function* ($, e, next) {
72 if (e.agentId !== undefined) return yield* next(e)
73 const model = isEnabled && chosen !== undefined ? chosen : e.model
74 if (e.index === 0) {
75 const note = model === e.model ? '' : ` (화면 표시: ${label(e.model)})`
76 $.ui.toast(`이번 답변 모델: ${label(model)}${note}`)
77 }
78 return yield* next(model === e.model ? e : { ...e, model })
79 })
80}
81