프롬프트의 작업 종류(키워드)에 따라 메인 세션 요청의 모델을 Haiku/Sonnet/Opus로 고릅니다. 기본 꺼짐, /auto-model on|off|status. /model로 직접 고르면 그 세션은 자동 선택을 멈춥니다.

Claude Code 화면에 기능을 더해 주는 mods(플러그인) 6개입니다. Claude Code에 아래 문장을 복사해서 붙여넣기만 하면 Claude가 설치를 진행합니다.
| 플러그인 | 무엇을 해 주나요 | 쓰는 법 |
|---|---|---|
| agent-board | 서브에이전트·스킬·MCP·백그라운드 작업 현황을 보여 주는 패널 (고양이 포함) | /agent-board 입력으로 열고 닫기 |
| auto-model | 작업 종류에 맞춰 모델(Haiku/Sonnet/Opus)을 자동으로 골라 줌 | 기본 꺼짐. /auto-model on으로 켜기 |
| session-collab | 같은 PC에서 열린 다른 Claude Code 창과 대화하며 협업 | /session-collab:collab <상대 이름> <맡길 일> |
| blast-radius | 파일을 지우는 위험한 명령(rm -rf, git reset --hard 등)을 실행 전에 멈추고 영향을 보여 줌 | 자동. 진행/취소 버튼이 뜸 |
| replay-theater | Claude가 방금 고친 파일 내용을 한 단계씩 다시 보여 줌 | 수정 후 입력창 위의 Replay 버튼 |
| token-weather | 대화 기억 공간(context) 사용량을 신호등 색으로 표시 | 자동. 입력창 바로 위 |
아래에서 내 컴퓨터의 운영체제 제목을 누르면 그 운영체제의 설치 안내만 펼쳐집니다.
필요한 것
Claude Code 여는 법
claude 를 입력하고 Enter를 누릅니다.꼭 알아둘 것
Yes를 고르세요. 설치 명령을 실행하려면 허락이 필요합니다./로 시작하는 줄은 슬래시 명령입니다. 입력창에 그대로 입력하면 바로 실행됩니다.claude-mods 플러그인을 설치하려고 해. 나는 Windows를 쓰는 초보자야. 아직 아무것도 설치하거나 바꾸지 말고 아래 항목만 확인해서 표로 알려줘.
1. `claude --version` 결과가 2.1.295 이상인지
2. Git for Windows의 bash.exe가 있는지와 그 전체 경로 (보통 C:\Program Files\Git\bin\bash.exe)
3. Mods 기능을 쓸 수 있는지: 새로 만든 빈 임시 폴더에서 `claude plugin test`를 실행해서
"no hooks module to load"가 나오면 사용 가능이야. 확인이 끝나면 그 빈 임시 폴더는 지워줘.
하나라도 안 되면, 내가 따라 할 수 있게 해결 방법을 쉬운 말로 한 단계씩 알려주고 멈춰줘.
성공하면: 세 항목이 모두 "OK"인 표가 나옵니다.
안 되면:
claude update를 실행한 뒤 1단계를 다시 하세요.winget install --id Git.Git -e를 실행하거나 https://git-scm.com/downloads/win 에서 설치하세요. 그다음 터미널을 닫았다가 다시 열고 1단계를 다시 하세요.claude-mods 플러그인 6개를 설치해줘. 명령을 하나씩 실행하고 각 결과를 확인해줘.
1. claude plugin install agent-board --marketplace akaz01/claude-mods --scope user
(이 명령이 claude-mods 마켓플레이스 등록과 첫 번째 설치를 함께 해)
2. claude plugin install auto-model@claude-mods --scope user
3. claude plugin install session-collab@claude-mods --scope user
4. claude plugin install replay-theater@claude-mods --scope user
5. claude plugin install token-weather@claude-mods --scope user
6. 1단계에서 찾은 bash.exe 전체 경로를 넣어서 blast-radius를 설치해줘.
claude plugin install blast-radius@claude-mods --scope user --config "bashExecutable=<bash.exe 전체 경로>"
"already installed"가 나오면 이미 설치된 것이니 다음으로 넘어가면 돼.
settings.json 같은 설정 파일을 직접 고치지는 마.
성공하면: 각 줄마다 ✔ Successfully installed plugin이나 already installed가 나옵니다.
`claude plugin list --json`으로 claude-mods 플러그인 6개가 모두 설치되어 있고 켜져(enabled) 있는지 표로 보여줘.
버전도 아래와 같은지 확인해줘.
agent-board 0.7.0, auto-model 0.1.1, session-collab 0.1.0, blast-radius 0.1.1, replay-theater 0.1.2, token-weather 0.2.1
꺼져 있는 것이 있으면 `claude plugin enable <이름>@claude-mods --scope user`로 켜줘.
그리고 `claude plugin configure blast-radius@claude-mods`를 실행해서 bashExecutable 옆에 "set"이라고 나오는지도 확인해줘. "not set"이면 Git Bash 경로가 빠진 거야.
성공하면: 6줄 모두 enabled이고 버전이 같은 표가 나옵니다.
설치한 기능은 새로 시작한 Claude Code에서 동작합니다. 입력창에 /exit를 입력해 종료한 다음 claude로 다시 시작하세요. 또는 지금 창에서 /reload-plugins를 입력해도 됩니다.
아래 공통: 설치 후 써 보기의 프롬프트를 붙여넣으세요.
claude를 실행하기 전의 PowerShell에 한 줄씩 입력합니다.
claude plugin install agent-board --marketplace akaz01/claude-mods --scope user
claude plugin install auto-model@claude-mods --scope user
claude plugin install session-collab@claude-mods --scope user
claude plugin install replay-theater@claude-mods --scope user
claude plugin install token-weather@claude-mods --scope user
claude plugin install blast-radius@claude-mods --scope user --config "bashExecutable=C:\Program Files\Git\bin\bash.exe"
마지막 줄의 경로는 Git을 기본 위치에 설치한 경우입니다.
| 화면에 나온 말 / 증상 | 원인 | 해결 |
|---|---|---|
claude 실행 시 "스크립트를 실행할 수 없으므로" | PowerShell 실행 정책이 막음 | Set-ExecutionPolicy -Scope CurrentUser RemoteSigned 실행 후 터미널을 다시 열기 |
1 userConfig option not yet set (blast-radius) | Git Bash 경로를 넣지 않고 설치함 | Claude Code 입력창에 /plugin configure blast-radius@claude-mods를 입력하고 bash.exe 전체 경로 넣기 |
winget을 찾을 수 없음 | 오래된 Windows | https://git-scm.com/downloads/win 에서 직접 내려받아 설치 |
그 밖의 문제는 아래 공통: 자주 생기는 문제를 보세요.
macOS에서는 두 가지 방법이 있습니다. 방법 1(마켓플레이스)을 권장합니다. 최신 버전 6개가 모두 설치되고 업데이트도 쉽습니다. 방법 2는 저장소에 함께 올려 둔 mac_os_mods.zip 패키지를 쓰는 방법입니다.
필요한 것
Claude Code 여는 법
claude 를 입력하고 Enter를 누릅니다.꼭 알아둘 것
Yes를 고르세요./로 시작하는 줄은 슬래시 명령입니다. 입력창에 그대로 입력하면 바로 실행됩니다.claude-mods 플러그인을 설치하려고 해. 나는 macOS를 쓰는 초보자야. 아직 아무것도 설치하거나 바꾸지 말고 아래 항목만 확인해서 표로 알려줘.
1. `claude --version` 결과가 2.1.295 이상인지
2. Mods 기능을 쓸 수 있는지: 새로 만든 빈 임시 폴더에서 `claude plugin test`를 실행해서
"no hooks module to load"가 나오면 사용 가능이야. 확인이 끝나면 그 빈 임시 폴더는 지워줘.
하나라도 안 되면, 내가 따라 할 수 있게 해결 방법을 쉬운 말로 한 단계씩 알려주고 멈춰줘.
성공하면: 두 항목이 모두 "OK"인 표가 나옵니다.
안 되면: 버전이 낮다고 나오면 터미널에서 claude update를 실행한 뒤 1단계를 다시 하세요. Mods를 쓸 수 없다고 나오면 지금 계정이나 버전에서는 이 플러그인들이 동작하지 않습니다.
claude-mods 플러그인 6개를 설치해줘. 명령을 하나씩 실행하고 각 결과를 확인해줘.
1. claude plugin install agent-board --marketplace akaz01/claude-mods --scope user
(이 명령이 claude-mods 마켓플레이스 등록과 첫 번째 설치를 함께 해)
2. claude plugin install auto-model@claude-mods --scope user
3. claude plugin install session-collab@claude-mods --scope user
4. claude plugin install replay-theater@claude-mods --scope user
5. claude plugin install token-weather@claude-mods --scope user
6. claude plugin install blast-radius@claude-mods --scope user
"already installed"가 나오면 이미 설치된 것이니 다음으로 넘어가면 돼.
settings.json 같은 설정 파일을 직접 고치지는 마.
성공하면: 각 줄마다 ✔ Successfully installed plugin이나 already installed가 나옵니다.
`claude plugin list --json`으로 claude-mods 플러그인 6개가 모두 설치되어 있고 켜져(enabled) 있는지 표로 보여줘.
버전도 아래와 같은지 확인해줘.
agent-board 0.7.0, auto-model 0.1.1, session-collab 0.1.0, blast-radius 0.1.1, replay-theater 0.1.2, token-weather 0.2.1
꺼져 있는 것이 있으면 `claude plugin enable <이름>@claude-mods --scope user`로 켜줘.
입력창에 /exit를 입력해 종료한 다음 claude로 다시 시작하세요. 또는 지금 창에서 /reload-plugins를 입력해도 됩니다.
아래 공통: 설치 후 써 보기의 프롬프트를 붙여넣으세요.
claude를 실행하기 전의 터미널에 한 줄씩 입력합니다.
claude plugin install agent-board --marketplace akaz01/claude-mods --scope user
claude plugin install auto-model@claude-mods --scope user
claude plugin install session-collab@claude-mods --scope user
claude plugin install replay-theater@claude-mods --scope user
claude plugin install token-weather@claude-mods --scope user
claude plugin install blast-radius@claude-mods --scope user
macOS에서 처음 만들고 검증한 원래 패키지입니다(Claude Code 2.1.296에서 검증). 방법 1과 비교하면 다음이 다릅니다.
| 방법 1 (마켓플레이스) | 방법 2 (ZIP) | |
|---|---|---|
| 플러그인 수 | 6개 | 5개 (session-collab 없음) |
| 버전 | 최신 | auto-model 0.1.0, replay-theater 0.1.0, token-weather 0.2.0 |
| 신호등과 Replay 안내 | 함께 표시 | 파일을 고친 뒤에는 Replay 안내가 신호등을 가림 |
| auto-model 모델 지정 | haiku/sonnet/opus 별칭 (어느 계정이든 동작) | 고정 모델 ID (계정에 따라 동작하지 않을 수 있음) |
| 업데이트 | 명령 한 번 | ZIP을 다시 받아 설치 |
주의: 방법 1과 방법 2를 함께 설치하지 마세요. 같은 기능이 두 번 설치되어 화면에 두 번 나옵니다. 바꾸려면 먼저 제거한 뒤 설치하세요.
claude-mods 저장소의 macOS용 패키지 mac_os_mods.zip을 설치하려고 해. 나는 macOS를 쓰는 초보자야. 순서대로 진행하고 각 단계 결과를 알려줘.
1. `claude --version`이 2.1.295 이상인지 확인해줘.
2. https://github.com/akaz01/claude-mods/raw/main/mac_os_mods.zip 를 curl -L 로 ~/Downloads/mac_os_mods.zip 에 내려받아줘.
3. `shasum -a 256 ~/Downloads/mac_os_mods.zip` 결과가 아래 값과 같은지 확인해줘. 다르면 바로 멈춰줘.
70ccb51757215083e20da7ba53d065362ad3cd946a241e5a0bda33a68417e77c
4. 같으면 ~/Tools 폴더에 압축을 풀어줘. 결과가 ~/Tools/Mods 가 되어야 해. 이미 ~/Tools/Mods 가 있으면 덮어쓰지 말고 나한테 물어봐줘.
5. ~/Tools/Mods 폴더에서 `shasum -a 256 -c SHA256SUMS`를 실행해서 모든 파일이 OK인지 확인해줘.
~/Tools/Mods 폴더는 설치 후에도 지우거나 옮기면 안 된다는 점도 알려줘.
~/Tools/Mods 폴더에서 먼저 `zsh install.command --print-only`로 실행될 명령을 보여주고,
문제가 없으면 `zsh install.command`를 실행해서 설치해줘. 끝나면 `claude plugin list`로 아래 5개가 설치되어 있는지 표로 보여줘.
agent-board@my-mods, auto-model@my-mods, blast-radius@claude-code-playground-mods, replay-theater@claude-code-playground-mods, token-weather@claude-code-playground-mods
/exit로 종료한 다음 claude로 다시 시작하세요. 그다음 공통: 설치 후 써 보기의 프롬프트를 붙여넣으세요. session-collab은 이 패키지에 없으니 그 항목은 건너뜁니다.
| 화면에 나온 말 / 증상 | 원인 | 해결 |
|---|---|---|
command not found: claude | Claude Code가 설치되지 않았거나 경로(PATH)에 없음 | Claude Code를 설치한 뒤 터미널을 다시 열기 |
| Finder에서 install.command를 열면 "확인되지 않은 개발자" 경고 | macOS 보안 기능(Gatekeeper) | Finder 대신 터미널에서 zsh install.command로 실행 |
| 방법 2 설치 중 마켓플레이스(my-mods 등)가 이미 있다는 오류 | 예전에 다른 위치의 Mods 폴더로 설치한 적이 있음 | Claude에게 claude plugin marketplace list 결과를 보여 달라고 한 뒤 예전 등록을 지울지 결정 |
그 밖의 문제는 아래 공통: 자주 생기는 문제를 보세요.
새로 시작한 Claude Code에 붙여넣으세요.
방금 설치한 mods가 잘 동작하는지 하나씩 같이 확인하고 싶어. 나는 초보자니까 한 번에 하나씩 안내해줘.
1. agent-board: 내가 /agent-board 를 입력하면 패널이 열리고, 한 번 더 입력하면 닫히는지
2. auto-model: 내가 /auto-model status 를 입력하면 "꺼짐" 상태가 나오는지
3. token-weather: 네가 짧게 한 번 대답한 뒤 입력창 바로 위에 ● 신호등 줄이 보이는지
4. replay-theater: 시스템 임시 폴더에 테스트용 파일을 하나 만들고 한 줄 고쳐줘. 그 뒤 입력창 위에 Replay 버튼이 보이는지
5. blast-radius: 시스템 임시 폴더 안에 테스트 폴더를 새로 만들고 그 폴더만 rm -rf 로 지우는 명령을 실행해줘.
진행/취소 버튼이 나오면 내가 취소를 누를 거야. 그 뒤 테스트 폴더가 그대로 남아 있는지 확인해줘.
각 항목마다 내가 무엇을 입력하고 화면 어디를 보면 되는지 알려줘. 내 프로젝트 파일은 절대 건드리지 마.
session-collab은 Claude Code 창이 두 개 필요합니다. 터미널을 하나 더 열어 claude를 실행한 뒤, 한쪽 창에서 /session-collab:collab <상대 창 이름> <맡길 일>을 입력하세요. 상대 창 이름은 Claude에게 "다른 세션 목록 보여줘"라고 하면 알 수 있습니다.
| 화면에 나온 말 / 증상 | 원인 | 해결 |
|---|---|---|
Plugin "..." not found in marketplace "claude-mods" | 플러그인 이름 오타 | 위 표의 이름을 그대로 복사해서 다시 입력 |
Repository not found | 저장소 이름 오타 | akaz01/claude-mods인지 확인 (끝에 s가 있음) |
/agent-board를 입력해도 반응이 없음 | 설치 후 새로 시작하지 않음 | /reload-plugins 입력 또는 Claude Code 다시 시작 |
| 신호등이 안 보임 | 새 대화에서 아직 응답이 한 번도 없음 (첫 응답 뒤에 나타남) | Claude에게 아무 말이나 한 번 보내기 |
| 위 방법으로도 안 됨 | — | 아래 프롬프트로 Claude에게 진단 맡기기 |
claude-mods 설치나 실행 중에 문제가 생겼어. 나는 초보자야. 아래 오류를 분석해서 쉬운 말로 원인과 해결 방법을 알려줘.
[여기에 운영체제와 오류 메시지나 증상을 붙여넣기]
설정 파일을 고쳐야 한다면 무엇을 왜 바꾸는지 먼저 보여주고 내 허락을 받아줘. managed settings는 건드리지 마.
마켓플레이스로 설치한 경우(Windows, macOS 방법 1) 붙여넣으세요.
claude-mods를 최신 버전으로 업데이트해줘.
먼저 `claude plugin marketplace update claude-mods`를 실행하고, 설치된 claude-mods 플러그인마다 `claude plugin update <이름>@claude-mods --scope user`를 실행해줘.
끝나면 버전 표를 보여줘.
업데이트한 뒤에는 /reload-plugins를 입력하거나 Claude Code를 다시 시작하세요. macOS 방법 2(ZIP)로 설치했다면 방법 1로 바꾸는 것을 권장합니다.
claude-mods로 설치한 플러그인을 지우고 싶어. 실행할 명령 목록을 먼저 보여주고 내가 허락하면 실행해줘.
- 마켓플레이스로 설치한 경우: `claude plugin uninstall <이름>@claude-mods --scope user`로 6개를 지우고 `claude plugin marketplace remove claude-mods`
- macOS ZIP으로 설치한 경우: @my-mods 와 @claude-code-playground-mods 플러그인 5개를 uninstall 하고,
`claude plugin marketplace remove my-mods`, `claude plugin marketplace remove claude-code-playground-mods`
`claude plugin list`로 어떤 방식으로 설치되어 있는지 먼저 확인해줘.
claude를 입력하면 시작됩니다.claude-mods라는 이름의 마켓플레이스입니다./agent-board처럼 /로 시작하는 명령입니다. 입력창에 입력하면 바로 실행됩니다.hooks/register.ts 120 lines1import { atom, read, update } from 'claude-code'
2import type { EngineInterface, Register } from 'claude-code'
3
4import { classify, isAnsweredBy, MAX_LOG, MODEL_BY_TIER, preview, statusLine, statusReport } from './rules'
5import type { Decision } from '../types'
6
7const COMMAND = 'auto-model'
8const MANUAL_SOURCES = new Set(['command', 'picker', 'sdk'])
9const USAGE = '사용법: /auto-model on | off | status'
10
11// Session-only: every session starts off and only `/auto-model on` turns it on.
12const isEnabled = atom({ plugin: 'auto-model', key: 'isEnabled' } as const, false)
13const pinnedModel = atom({ plugin: 'auto-model', key: 'pinnedModel' } as const, null as string | null)
14const log = atom({ plugin: 'auto-model', key: 'log' } as const, [] as Decision[])
15
16const decisionFor = async ($: EngineInterface, turnId: string): Promise<Decision | undefined> =>
17 (await read($, log)).find(d => d.turnId === turnId)
18
19const patch = async ($: EngineInterface, turnId: string, change: Partial<Decision>): Promise<Decision | undefined> => {
20 await update($, log, list => list.map(d => (d.turnId === turnId ? { ...d, ...change } : d)))
21 const next = await decisionFor($, turnId)
22 if (next) $.ui.status(statusLine(next))
23 return next
24}
25
26const errorText = (error: unknown): string => (error instanceof Error ? error.message : String(error))
27
28const runCommand = async ($: EngineInterface, args: string): Promise<string> => {
29 const word = args.trim().toLowerCase()
30 if (word === 'on') {
31 await update($, isEnabled, () => true)
32 const pinned = await read($, pinnedModel)
33 $.ui.status(pinned ? `자동 모델 · 켜짐 · 수동 선택(${pinned}) 우선이라 대기` : '자동 모델 · 켜짐')
34 return pinned
35 ? `자동 선택을 켰습니다. 다만 이 세션은 /model로 ${pinned}를 골라서 수동 선택이 우선합니다.`
36 : '자동 선택을 켰습니다. 이 세션에서만 적용되고, 다음 세션은 꺼진 상태로 시작합니다.'
37 }
38 if (word === 'off') {
39 await update($, isEnabled, () => false)
40 $.ui.status(undefined)
41 return '자동 선택을 껐습니다. 이제 세션 모델(/model)만 씁니다.'
42 }
43 if (word === '' || word === 'status') {
44 return statusReport(await read($, isEnabled), await read($, pinnedModel), await read($, log))
45 }
46 return `알 수 없는 인자: "${args.trim()}"\n${USAGE}`
47}
48
49export const register: Register = on => {
50 on('session.start', async ($, e, next) => {
51 await $.command.register({ name: COMMAND, description: '작업 종류별 모델 자동 선택 (on | off | status)' })
52 return next(e)
53 })
54
55 on('command.run', { command: COMMAND }, async ($, e) => ({ text: await runCommand($, e.args) }))
56
57 // A model the person picks themselves wins for the rest of the session.
58 on('classic.PostModelSwitch', async ($, e, next) => {
59 if (MANUAL_SOURCES.has(e.source)) {
60 await update($, pinnedModel, () => e.to_model)
61 if (await read($, isEnabled)) $.ui.toast(`자동 모델: /model로 ${e.to_model}를 골라 이 세션은 자동 선택을 멈춥니다.`)
62 }
63 return next(e)
64 })
65
66 on('turn.start', async ($, e, next) => {
67 const isOn = (await read($, isEnabled)) && (await read($, pinnedModel)) === null
68 if (isOn && e.text.trim() !== '') {
69 const choice = classify(e.text)
70 const decision: Decision = {
71 turnId: e.turnId,
72 at: Date.now(),
73 prompt: preview(e.text),
74 tier: choice?.tier,
75 model: choice ? MODEL_BY_TIER[choice.tier] : undefined,
76 reason: choice?.reason ?? '맞는 규칙 없음 → 세션 모델 그대로',
77 }
78 await update($, log, list => [...list, decision].slice(-MAX_LOG))
79 $.ui.status(statusLine(decision))
80 }
81 return next(e)
82 })
83
84 on('turn.step', async function* ($, e, next) {
85 const decision = e.agentId ? undefined : await decisionFor($, e.turnId)
86 if (!decision?.model || !decision.tier || decision.error || decision.model === e.model) {
87 return yield* next(e)
88 }
89
90 // While nothing has reached the screen, a failed step can go out again on the session's model.
91 let sent = 0
92 try {
93 const stream = next({ ...e, model: decision.model })
94 for await (const chunk of stream) {
95 sent += 1
96 yield chunk
97 }
98 const result = await stream.result
99 const answered = result.usage?.model
100 if (!answered) {
101 const why = `응답 없음 (stopReason: ${result.stopReason ?? 'null'})`
102 await patch($, e.turnId, { error: why })
103 $.ui.toast(`자동 모델 오류: ${decision.model} 요청에 ${why}. 세션 모델 ${e.model}로 진행합니다.`)
104 return sent === 0 ? yield* next(e) : result
105 }
106 await patch($, e.turnId, { answeredBy: answered })
107 if (!isAnsweredBy(decision.tier, answered)) {
108 $.ui.toast(`자동 모델 경고: ${decision.tier}를 요청했지만 ${answered}가 답했습니다.`)
109 }
110 return result
111 } catch (error: unknown) {
112 const why = errorText(error)
113 await patch($, e.turnId, { error: why })
114 $.ui.toast(`자동 모델 오류: ${decision.model} 요청 실패 (${why}). 세션 모델 ${e.model}로 진행합니다.`)
115 if (sent === 0) return yield* next(e)
116 throw error
117 }
118 })
119}
120hooks/rules.ts 72 lines1import type { Choice, Decision, Rule, Tier } from '../types'
2
3// Portable Claude aliases; the installed CLI resolves the models available to each account.
4export const MODEL_BY_TIER: Record<Tier, string> = {
5 haiku: 'haiku',
6 sonnet: 'sonnet',
7 opus: 'opus',
8}
9
10// Checked top to bottom: when a prompt matches several, the larger model wins.
11export const RULES: readonly Rule[] = [
12 { tier: 'opus', label: '복잡한 설계', pattern: /설계|아키텍처|architecture|system design/i },
13 { tier: 'opus', label: '여러 파일 연결 분석', pattern: /여러 파일|파일들? (간|사이)|연결된|의존성 분석|코드베이스 (전체|분석)|전체 구조/i },
14 { tier: 'sonnet', label: '코드 수정', pattern: /수정|고쳐|버그|구현|추가해|리팩터|\b(fix|bug|implement|refactor)\b/i },
15 { tier: 'sonnet', label: '문서 작성', pattern: /문서 ?(작성|화)|README|가이드 (작성|써)|\bdocs?\b/i },
16 { tier: 'haiku', label: '파일명 정리', pattern: /파일 ?명|파일 이름|이름 (정리|바꿔)|\brename\b/i },
17 { tier: 'haiku', label: '단순 분류', pattern: /분류|\bclassify\b|\bcategori[sz]e\b/i },
18 { tier: 'haiku', label: '짧은 요약', pattern: /요약|\bsummar(y|ize|ise)\b|\btl;?dr\b/i },
19]
20
21export const MAX_LOG = 20
22const PREVIEW = 24
23
24export const classify = (text: string): Choice | undefined => {
25 const rule = RULES.find(one => one.pattern.test(text))
26 if (!rule) return undefined
27 const word = text.match(rule.pattern)?.[0] ?? ''
28 return { tier: rule.tier, reason: `"${word}" → ${rule.label}` }
29}
30
31export const preview = (text: string): string => {
32 const flat = text.replace(/\s+/g, ' ').trim()
33 return flat.length > PREVIEW ? `${flat.slice(0, PREVIEW)}…` : flat
34}
35
36export const isAnsweredBy = (tier: Tier, answered: string): boolean => answered.includes(tier)
37
38export const statusLine = (d: Decision): string => {
39 if (!d.tier) return `자동 모델 · 세션 모델 유지 · ${d.reason}`
40 const check = d.error
41 ? `✗ ${d.error}`
42 : d.answeredBy
43 ? `${isAnsweredBy(d.tier, d.answeredBy) ? '✓' : '⚠ 다른 모델'} ${d.answeredBy}`
44 : '응답 대기'
45 return `자동 모델 · ${d.tier} · 이유: ${d.reason} · 검증 ${check}`
46}
47
48const time = (at: number): string => {
49 const d = new Date(at)
50 const pad = (n: number) => String(n).padStart(2, '0')
51 return `${pad(d.getHours())}:${pad(d.getMinutes())}`
52}
53
54export const logLine = (d: Decision): string => {
55 const tier = (d.tier ?? '(유지)').padEnd(7)
56 const check = d.error
57 ? `✗ 오류: ${d.error}`
58 : d.answeredBy
59 ? `답한 모델 ${d.tier && !isAnsweredBy(d.tier, d.answeredBy) ? '⚠' : '✓'} ${d.answeredBy}`
60 : ''
61 return ` ${time(d.at)} ${tier} "${d.prompt}" ${d.reason} ${check}`.trimEnd()
62}
63
64export const statusReport = (isEnabled: boolean, pinned: string | null, log: readonly Decision[]): string => {
65 const head = [
66 `자동 선택: ${isEnabled ? '켜짐' : '꺼짐'}`,
67 pinned ? `수동 우선: /model로 ${pinned} 선택됨 → 이 세션은 자동 선택 중지` : '수동 우선: 없음 (/model 쓰면 이 세션은 자동 중지)',
68 ]
69 const body = log.length === 0 ? [' 아직 판단 기록 없음'] : log.map(logLine)
70 return [...head, '최근 판단', ...body].join('\n')
71}
72types/index.d.ts 27 lines1export type Tier = 'haiku' | 'sonnet' | 'opus'
2
3export type Rule = { tier: Tier; label: string; pattern: RegExp }
4
5export type Choice = { tier: Tier; reason: string }
6
7export type Decision = {
8 turnId: string
9 at: number
10 prompt: string
11 tier?: Tier
12 model?: string
13 reason: string
14 answeredBy?: string
15 error?: string
16}
17
18declare module 'claude-code' {
19 interface PluginState {
20 'auto-model': {
21 isEnabled: boolean
22 pinnedModel: string | null
23 log: Decision[]
24 }
25 }
26}
27