턴이 끝나면 다음 프롬프트 후보 3개를 프롬프트 위에 제안

턴이 끝나면 대화 흐름을 보고 다음에 보낼 만한 프롬프트 후보 3개를 입력창 위에 보여주는 mod입니다.
닫기로 숨깁니다./suggest: 지금 바로 후보 만들기/suggest on · /suggest off: 턴마다 자동 제안 켜기/끄기 (세션이 바뀌어도 유지, 기본은 켬)후보는 세션 자체 대화를 이어 묻는($.model.fork) 방식이라 프롬프트 캐시를 쓰지만, 턴마다 짧은 모델 호출이 한 번씩 더 생깁니다. 비용이 신경 쓰이면 /suggest off로 끄고 필요할 때만 /suggest를 쓰세요.
hooks/register.tsx 97 lines1import { atom, read, update } from 'claude-code'
2import type { EngineInterface, Register } from 'claude-code'
3
4import type { Suggestion } from '../types'
5import { ASK, parseSuggestions } from './parse'
6
7const suggestions = atom({ plugin: 'prompt-suggest', key: 'suggestions' } as const, [] as Suggestion[])
8const isEnabled = atom({ plugin: 'prompt-suggest', key: 'isEnabled' } as const, true)
9const isLoading = atom({ plugin: 'prompt-suggest', key: 'isLoading' } as const, false)
10
11const STORE_KEY = 'isEnabled'
12
13async function refresh($: EngineInterface) {
14 await update($, isLoading, () => true)
15 try {
16 const reply = await $.model.fork({ prompt: ASK })
17 const list = reply.isAnswered ? parseSuggestions(reply.text) : []
18 await update($, suggestions, () => list)
19 const first = list[0]
20 if (first !== undefined) await $.prompt.suggest({ text: first })
21 } finally {
22 await update($, isLoading, () => false)
23 }
24}
25
26async function runSuggest($: EngineInterface, args: string): Promise<string> {
27 const arg = args.trim().toLowerCase()
28 if (arg === 'on' || arg === 'off') {
29 const value = arg === 'on'
30 await $.store.set(STORE_KEY, value)
31 await update($, isEnabled, () => value)
32 if (!value) await update($, suggestions, () => [])
33
34 return `자동 프롬프트 제안을 ${value ? '켰' : '껐'}습니다.`
35 }
36 await refresh($)
37
38 return '다음 프롬프트 후보를 입력창 위에 표시했습니다.'
39}
40
41export const register: Register = on => {
42 on('session.start', async ($, e, next) => {
43 await $.command.register({
44 name: 'suggest',
45 description: '프롬프트 제안: 인자 없으면 지금 제안, on/off로 자동 제안 켜기/끄기',
46 })
47 const stored = await $.store.get(STORE_KEY)
48 await update($, isEnabled, () => stored !== false)
49
50 return next(e)
51 })
52
53 on('command.run', { command: 'suggest' }, async ($, e) => ({ text: await runSuggest($, e.args) }))
54
55 on('prompt.submit', async ($, e, next) => {
56 // 슬래시 명령이 command.run으로 오지 않고 프롬프트로 들어올 때의 대체 처리: "/suggest [on|off]"
57 const m = /^\s*\/suggest(?:\s+(on|off))?\s*$/i.exec(e.text)
58 if (m !== null) return { drop: await runSuggest($, m[1] ?? '') }
59 await update($, suggestions, () => [])
60
61 return next(e)
62 }).catch(($, e, next) => next(e))
63
64 on('turn.complete', async ($, e, next) => {
65 const result = await next(e)
66 if (e.reason === 'answer' && (await read($, isEnabled))) void refresh($)
67
68 return result
69 })
70
71 on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
72 const list = await read($, suggestions)
73 const loading = await read($, isLoading)
74 if (e.props.hasSurvey || (list.length === 0 && !loading)) return next(e)
75
76 const { Box, Button, Text } = $.ui.resolve(e)
77
78 if (list.length === 0) {
79 return (
80 <Box>
81 <Text dimColor>다음 프롬프트 후보를 만드는 중…</Text>
82 </Box>
83 )
84 }
85
86 return (
87 <Box flexDirection="column">
88 <Text dimColor>다음 프롬프트 제안 (누르면 입력창에 채움)</Text>
89 {list.map((text, i) => (
90 <Button key={`s${i}`} label={text} onPress={() => $.prompt.fill({ text })} />
91 ))}
92 <Button key="close" label="닫기" onPress={() => update($, suggestions, () => [])} />
93 </Box>
94 )
95 })
96}
97hooks/parse.ts 22 lines1const MAX = 3
2
3export const ASK = [
4 '지금까지의 대화를 보고, 사용자가 다음에 보낼 만한 프롬프트 후보를 3개 제안해.',
5 '- 사용자 입장에서 쓴 한국어 지시문, 각 40자 이내',
6 '- 서로 다른 방향(이어서 진행, 검증/테스트, 개선/정리 등)',
7 '- 한 줄에 하나씩, 번호·기호·따옴표·설명 없이 후보 3줄만 출력',
8].join('\n')
9
10export function parseSuggestions(text: string): string[] {
11 return text
12 .split('\n')
13 .map(line =>
14 line
15 .replace(/^\s*(?:[-*•]|\d+[.)])\s*/, '')
16 .replace(/^["'“]|["'”]$/g, '')
17 .trim(),
18 )
19 .filter(line => line.length > 0 && line.length <= 120)
20 .slice(0, MAX)
21}
22types/index.d.ts 8 lines1export type Suggestion = string
2
3declare module 'claude-code' {
4 interface PluginState {
5 'prompt-suggest': { suggestions: Suggestion[]; isEnabled: boolean; isLoading: boolean }
6 }
7}
8