SLOPSHOPPER

prompt-suggest

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

newbandcommandpromptmodel
v0.1.0no licenseupdated 2026-10-13wonr-spec/claude-mods/plugins/prompt-suggest
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · prompt-suggest
› 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 › /suggest ⎿ prompt-suggest: 다음 프롬프트 후보를 입력창 위에 표시했습니다. 다음 프롬프트 제안 (누르면 입력창에 채움) [ OK ] [ 닫기 ] ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts

Draws

Band
다음 프롬프트 제안 (누르면 입력창에 채움) [ OK ] [ 닫기 ]
README

prompt-suggest

턴이 끝나면 대화 흐름을 보고 다음에 보낼 만한 프롬프트 후보 3개를 입력창 위에 보여주는 mod입니다.

  • 제안 띠: 후보를 누르면 입력창에 채워지고, 고친 뒤 보내면 됩니다. 닫기로 숨깁니다.
  • Tab 제안: 첫 번째 후보는 빈 입력창에 흐린 글씨로도 나타나 Tab으로 바로 받을 수 있습니다.
  • /suggest: 지금 바로 후보 만들기
  • /suggest on · /suggest off: 턴마다 자동 제안 켜기/끄기 (세션이 바뀌어도 유지, 기본은 켬)

후보는 세션 자체 대화를 이어 묻는($.model.fork) 방식이라 프롬프트 캐시를 쓰지만, 턴마다 짧은 모델 호출이 한 번씩 더 생깁니다. 비용이 신경 쓰이면 /suggest off로 끄고 필요할 때만 /suggest를 쓰세요.

Source 3 files
hooks/register.tsx 97 lines
1import { 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}
97
hooks/parse.ts 22 lines
1const 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}
22
types/index.d.ts 8 lines
1export type Suggestion = string
2
3declare module 'claude-code' {
4  interface PluginState {
5    'prompt-suggest': { suggestions: Suggestion[]; isEnabled: boolean; isLoading: boolean }
6  }
7}
8