SLOPSHOPPER

prompt-upgrade

/boost asks a few quick questions, then rewrites a short prompt into a detailed one you can run, edit or copy

panecommandtoastmodel
v1.0.0no licenseupdated 2026-10-24hussam8988/claude-mods/plugins/prompt-upgrade
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · prompt-upgrade
│ ┃ Prompt Upgrade ✕ › fix the failing auth test and add an audit log call │ ┃ Prompt Upgrade │ ┃ ⏺ Read(src/auth.ts) │ ┃ Type a short prompt and press Enter. I'll as ⎿ Read 6 lines │ ┃ quick questions, then write a detailed versi ⏺ Update(src/auth.ts) │ ┃ Prompt: : e.g. make a landing page for my ho ⎿ 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 │ │ › /boost │ │ ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts

Draws

Pane · Prompt Upgrade
Prompt Upgrade Type a short prompt and press Enter. I'll ask a few quick questions, then write a detailed version. Prompt: : e.g. make a landing page for my honey brand ⏎ upgr
README

Hussam's Claude Code mods

Three mods for the Code tab of the Claude desktop app (and terminal sessions):

ModWhat it doesHow to use
auto-modelReads your first prompt of a session and picks Haiku (quick questions), Sonnet (normal work) or Opus (big builds) for the whole session. It never switches mid-session, so the prompt cache stays intact and you don't pay to re-read the conversation. If you chose a model yourself with /model, it leaves it alone.Automatic. /auto-model shows the pick; /auto-model off / on; /auto-model opus before your first prompt forces one.
prompt-upgradeAsks you 3 quick questions, then rewrites a one-line prompt into a detailed one you can edit, run, put in the prompt box, or copy./boost make a landing page for my honey brand
todays-reportCounts the tasks Claude finished, estimates hours saved and what that time is worth — today, this week, month, year./report · /report rate 45 to set your hourly value (default $40)

Model use: auto-model makes one tiny Haiku call per new session; prompt-upgrade makes one Haiku call (questions) and one Sonnet call (the rewrite) each time you run it; todays-report makes none.

The "hours saved" figure is an estimate: 3 min for a question answered, otherwise 5 min + 2 min per tool action + 6 min per file edit, capped at 3 h per task.

Source 2 files
hooks/register.tsx 310 lines
1import { atom, read, update } from 'claude-code'
2import type { Register } from 'claude-code'
3
4import type { UpgradeView } from '../types'
5
6// Prompt Upgrade — /boost <short prompt>
7// Asks a few quick questions, then rewrites the short prompt into a detailed
8// one you can edit, copy, put in the prompt box, or run with one click.
9
10const PANE = 'prompt-upgrade'
11const EMPTY: UpgradeView = { phase: 'start', original: '', questions: [], result: '', error: '', round: 0 }
12
13const view = atom({ plugin: 'prompt-upgrade', key: 'view' } as const, EMPTY)
14const answers = atom({ plugin: 'prompt-upgrade', key: 'answers' } as const, [] as string[])
15const draft = atom({ plugin: 'prompt-upgrade', key: 'draft' } as const, '')
16
17const ASK_SYSTEM = `You help someone turn a short request to an AI assistant into a detailed, high-quality prompt.
18Before rewriting, you ask the few questions whose answers would change the result the most:
19the goal or audience, the scope, the format or deliverable, constraints (tools, length, style, deadline), and what "done" looks like.
20Ask 3 questions (never more than 4). Each is one short, plain sentence a busy person can answer in a few words.
21Skip anything the request already answers. Reply with ONLY a JSON array of strings, nothing else.`
22
23const WRITE_SYSTEM = `You rewrite a short request into a detailed prompt for an AI coding/work assistant (Claude Code).
24Write it in the person's own voice ("I want...", "Build...").
25Include, as fits: the goal and why, context, specific requirements, the deliverable and its format, constraints, quality bar, and how to check it is done.
26Use the person's answers; where they skipped a question, choose a sensible default and say so briefly in the prompt.
27Keep it tight: short sections or bullets, no filler, no preamble. Reply with ONLY the finished prompt.`
28
29function parseQuestions(text: string): string[] {
30  const start = text.indexOf('[')
31  const end = text.lastIndexOf(']')
32  if (start >= 0 && end > start) {
33    try {
34      const list = JSON.parse(text.slice(start, end + 1))
35      if (Array.isArray(list)) {
36        const qs = list.map(q => String(q).trim()).filter(q => q.length > 0)
37        if (qs.length > 0) return qs.slice(0, 4)
38      }
39    } catch {
40      // fall through to line parsing
41    }
42  }
43  return text
44    .split('\n')
45    .map(line => line.replace(/^[\s\-*\d.)]+/, '').trim())
46    .filter(line => line.endsWith('?'))
47    .slice(0, 4)
48}
49
50async function begin($: any, original: string) {
51  const text = original.trim()
52  if (text === '') return
53  const before = await read($, view)
54  const round = before.round + 1
55  await update($, answers, () => [])
56  await update($, draft, () => '')
57  await update($, view, () => ({ ...EMPTY, phase: 'asking' as const, original: text, round }))
58
59  const r = await $.model.complete({
60    model: 'haiku',
61    system: ASK_SYSTEM,
62    prompt: 'Request:\n"""\n' + text + '\n"""',
63    maxTokens: 400,
64    effort: 'low',
65    timeoutMs: 30000,
66  })
67  if (!r.isAnswered) {
68    await update($, view, v => ({ ...v, phase: 'error' as const, error: 'Could not get questions (' + r.reason + ').' }))
69    return
70  }
71  const questions = parseQuestions(r.text)
72  if (questions.length === 0) {
73    await write($)
74    return
75  }
76  await update($, view, v => ({ ...v, phase: 'questions' as const, questions }))
77}
78
79async function write($: any) {
80  const v = await read($, view)
81  const given = await read($, answers)
82  await update($, view, cur => ({ ...cur, phase: 'writing' as const }))
83
84  const qa = v.questions
85    .map((q, i) => 'Q: ' + q + '\nA: ' + ((given[i] || '').trim() || '(skipped)'))
86    .join('\n\n')
87  const r = await $.model.complete({
88    model: 'sonnet',
89    system: WRITE_SYSTEM,
90    prompt: 'Short request:\n"""\n' + v.original + '\n"""\n\nQuestions and answers:\n' + (qa || '(none)') + '\n\nThe detailed prompt:',
91    maxTokens: 2000,
92    timeoutMs: 90000,
93  })
94  if (!r.isAnswered) {
95    await update($, view, cur => ({ ...cur, phase: 'error' as const, error: 'Could not write the prompt (' + r.reason + ').' }))
96    return
97  }
98  const result = r.text.trim()
99  await update($, draft, () => result)
100  await update($, view, cur => ({ ...cur, phase: 'done' as const, result }))
101}
102
103async function finalText($: any): Promise<string> {
104  const edited = await read($, draft)
105  const v = await read($, view)
106  return (edited || v.result).trim()
107}
108
109export const register: Register = on => {
110  on('session.start', async ($, e, next) => {
111    const out = await next(e)
112    try {
113      await $.command.register({
114        name: 'boost',
115        description: 'Prompt Upgrade: answer a few questions, get a detailed prompt',
116        argumentHint: '<your short prompt>',
117      })
118    } catch (err) {
119      $.ui.log('prompt-upgrade: could not add /boost (' + String(err) + ')')
120    }
121    return out
122  })
123
124  on('command.run', { command: 'boost' }, async ($, e) => {
125    const opened = await $.ui.open({ id: PANE, title: 'Prompt Upgrade', focus: true })
126    const text = (e.args || '').trim()
127    if (text === '') {
128      const v = await read($, view)
129      await update($, view, () => ({ ...EMPTY, round: v.round + 1 }))
130    } else {
131      void begin($, text)
132    }
133    if (!opened.isPlaced) {
134      return { text: 'Prompt Upgrade could not open its panel here (' + String((opened as any).reason || 'no panel') + '). It works in the Code tab of the desktop app and in a terminal session.' }
135    }
136    return {}
137  })
138
139  on('ui.render', { component: 'Pane', requestId: PANE }, async ($, e) => {
140    const { Box, Text, Button, Input } = $.ui.resolve(e) as any
141    const v = await read($, view)
142    const width = Math.max(30, (e.props as any).bodyColumns ?? 60)
143
144    const header = (
145      <Box flexDirection="column" marginBottom={1}>
146        <Text bold>Prompt Upgrade</Text>
147        {v.original !== '' && <Text dimColor wrap="wrap">Your prompt: {v.original}</Text>}
148      </Box>
149    )
150
151    if (v.phase === 'start') {
152      return (
153        <Box flexDirection="column" width={width}>
154          {header}
155          <Text>Type a short prompt and press Enter. I'll ask a few quick questions, then write a detailed version.</Text>
156          {Input && (
157            <Input
158              key={'start-' + v.round}
159              label="Prompt: "
160              placeholder="e.g. make a landing page for my honey brand"
161              submitLabel="upgrade"
162              autoFocus
163              onSubmit={(text: string) => {
164                void begin($, text)
165              }}
166            />
167          )}
168        </Box>
169      )
170    }
171
172    if (v.phase === 'asking' || v.phase === 'writing') {
173      return (
174        <Box flexDirection="column" width={width}>
175          {header}
176          <Text dimColor>{v.phase === 'asking' ? 'Thinking of a few questions…' : 'Writing your detailed prompt…'}</Text>
177        </Box>
178      )
179    }
180
181    if (v.phase === 'error') {
182      return (
183        <Box flexDirection="column" width={width}>
184          {header}
185          <Text color="red">{v.error}</Text>
186          <Box marginTop={1}>
187            <Button key="retry" label="Try again" variant="primary" onPress={() => { void begin($, v.original) }} />
188            <Text> </Text>
189            <Button key="close" label="Close" role="dismiss" onPress={() => $.ui.close({ id: PANE })} />
190          </Box>
191        </Box>
192      )
193    }
194
195    if (v.phase === 'questions') {
196      return (
197        <Box flexDirection="column" width={width}>
198          {header}
199          <Text dimColor>Answer what you can — skip any. Then press Write my prompt.</Text>
200          {v.questions.map((q, i) => (
201            <Box flexDirection="column" marginTop={1}>
202              <Text wrap="wrap">{i + 1}. {q}</Text>
203              {Input && (
204                <Input
205                  key={'a-' + v.round + '-' + i}
206                  placeholder="your answer"
207                  submitLabel={i === v.questions.length - 1 ? 'write' : 'next'}
208                  autoFocus={i === 0 ? true : undefined}
209                  onInput={(text: string) => {
210                    void update($, answers, list => {
211                      const copy = list.slice()
212                      copy[i] = text
213                      return copy
214                    })
215                  }}
216                  onSubmit={(text: string) => {
217                    void (async () => {
218                      await update($, answers, list => {
219                        const copy = list.slice()
220                        copy[i] = text
221                        return copy
222                      })
223                      if (i === v.questions.length - 1) await write($)
224                    })()
225                  }}
226                />
227              )}
228            </Box>
229          ))}
230          <Box marginTop={1}>
231            <Button key="write" label="Write my prompt" variant="primary" onPress={() => { void write($) }} />
232            <Text> </Text>
233            <Button key="skip" label="Skip questions" onPress={() => { void write($) }} />
234          </Box>
235        </Box>
236      )
237    }
238
239    // done
240    const lines = Math.min(18, Math.max(6, v.result.split('\n').length + 1))
241    return (
242      <Box flexDirection="column" width={width}>
243        {header}
244        <Text dimColor>Your upgraded prompt — edit it right here, then pick what to do with it.</Text>
245        {Input ? (
246          <Input
247            key={'result-' + v.round}
248            value={v.result}
249            rows={lines}
250            submitLabel="run"
251            onInput={(text: string) => { void update($, draft, () => text) }}
252            onSubmit={(text: string) => {
253              void (async () => {
254                await update($, draft, () => text)
255                const t = await finalText($)
256                await $.ui.close({ id: PANE })
257                await $.prompt.submit({ text: t, asUser: true })
258              })()
259            }}
260          />
261        ) : (
262          <Text wrap="wrap">{v.result}</Text>
263        )}
264        <Box marginTop={1} flexWrap="wrap">
265          <Button
266            key="run"
267            label="Run it"
268            variant="primary"
269            onPress={() => {
270              void (async () => {
271                const t = await finalText($)
272                await $.ui.close({ id: PANE })
273                await $.prompt.submit({ text: t, asUser: true })
274              })()
275            }}
276          />
277          <Text> </Text>
278          <Button
279            key="fill"
280            label="Put in prompt box"
281            onPress={() => {
282              void (async () => {
283                const t = await finalText($)
284                await $.prompt.fill({ text: t })
285                await $.ui.close({ id: PANE })
286              })()
287            }}
288          />
289          <Text> </Text>
290          <Button
291            key="copy"
292            label="Copy"
293            onPress={() => {
294              void (async () => {
295                const t = await finalText($)
296                const done = await $.ui.copy({ text: t, surface: e.surface })
297                $.ui.toast(done.isCopied ? 'Prompt copied' : 'Could not copy here — use Put in prompt box')
298              })()
299            }}
300          />
301          <Text> </Text>
302          <Button key="again" label="Start over" onPress={() => { void update($, view, cur => ({ ...EMPTY, round: cur.round + 1 })) }} />
303          <Text> </Text>
304          <Button key="close" label="Close" role="dismiss" onPress={() => $.ui.close({ id: PANE })} />
305        </Box>
306      </Box>
307    )
308  })
309}
310
types/index.d.ts 22 lines
1export type UpgradePhase = 'start' | 'asking' | 'questions' | 'writing' | 'done' | 'error'
2
3export type UpgradeView = {
4  phase: UpgradePhase
5  /** The short prompt the person started from */
6  original: string
7  /** The quick questions the model asked */
8  questions: string[]
9  /** The detailed prompt, once written */
10  result: string
11  /** What went wrong, in the 'error' phase */
12  error: string
13  /** Bumped on every new run so the fields redraw empty */
14  round: number
15}
16
17declare module 'claude-code' {
18  interface PluginState {
19    'prompt-upgrade': { view: UpgradeView; answers: string[]; draft: string }
20  }
21}
22