End a prompt with " ++" to have it rewritten into a sharper prompt in the prompt bar instead of sent

A Claude Code plugin marketplace with three mods.
| Plugin | What it does |
|---|---|
prompt-enhance | End a prompt with ++ and press Enter: instead of sending it, Claude rewrites it into a sharper prompt and puts it back in the prompt bar for you to review and send. |
session-boost | /subagents opens a live side panel of every subagent, with a one-click cheaper-model suggestion per row. /boost (mid-turn) starts a watcher that launches read-only helper subagents. Subagents run on the chat's own model by default. |
conversations | /conversations lists this project's past conversations by title; click one to resume it. |
None of the plugins ship commands/, skills/ or agents/ folders. Each one is a single hooks module (hooks/hooks.json → hooks/register.ts(x)) that registers its commands, panels, tools and agents when a session starts.
What it adds
prompt.submit hook for prompts typed in the prompt bar. No slash commands.How to use it
++, e.g. fix the flaky login test ++.How it rewrites
/model shows it), at high effort, with a 120 s timeout.If something goes wrong
++ on its own is dropped with Nothing to enhance.What it adds
| Item | Kind | What it does |
|---|---|---|
/subagents | Command | Opens the Subagents side panel. |
/boost | Command | Starts a watcher that launches read-only helper subagents for the turn in progress. |
/boost stop | Command | Stops a running boost. |
| Subagent model default | agent.spawn hook | Subagents run on the chat's own model unless the caller names one. |
session-boost:watcher | Agent (internal) | The /boost watcher. Hidden from the main model. |
session-boost:helper | Agent (internal) | A read-only helper started by the watcher. Hidden from the main model. |
mcp__session-boost__progress | Tool (internal) | Lets the watcher wait for new main-chat activity. Only the watcher may call it. |
mcp__session-boost__spawn_helper | Tool (internal) | Lets the watcher queue a helper. Only the watcher may call it. |
The Subagents panel (/subagents)
The panel opens by itself at the start of an interactive session; /subagents reopens it. It refreshes every 2 seconds and shows one row per subagent:
● running, ○ pending, waiting or idle, ✓ completed, ✗ failed or killed), the description, the agent type, a [watcher] / [helper] tag for boost agents, and the model it runs on.Cheaper-model suggestions
When a cheaper model suits a row's job (Haiku 5.5 for lookup, Sonnet 5.5 for coding, Opus 5.5 for reasoning), the row shows a ▲ use <model> button with the price and the saving, e.g. $0.10/$0.50, 98% less. It blinks while the subagent is still running.
✓ <model>.✓ again to undo; that kind follows the chat's model again.Which model subagents run on
By default every subagent runs on the chat's own model, even when its agent definition pins one (a built-in's haiku, say). A model the caller names explicitly, or a pick you made in the panel, wins. Forks and workflow agents are left alone.
Boost (/boost)
/boost. (With nothing in progress it tells you so.)[session-boost] message and a toast appears. The main chat uses it if it helps./boost stop ends it early: no new helpers, queued ones are dropped, running ones finish and report.Helpers can use Read, Grep, Glob, Bash, WebFetch and WebSearch, and are told never to modify anything (no edits, commits, checkouts, installs). Their answers are capped at about 300 words with file:line citations.
What it adds
/conversations: a command that opens the Past conversations panel.How to use it
/conversations. The panel opens focused on the newest conversation./resume <id> for that conversation.What it lists
~/.claude/projects/<project>/ (or under $CLAUDE_CONFIG_DIR if set), newest first, up to 60.Where titles come from, in order:
/rename.Titles are cached per transcript and only worked out again when the transcript changes, so the panel fills at once on later opens.
Inside Claude Code, add the marketplace once per machine:
/plugin marketplace add nadesh00/setup-code
Then install the plugins you want:
/plugin install prompt-enhance@setup-code
/plugin install session-boost@setup-code
/plugin install conversations@setup-code
Run /reload-plugins (or start a new session) if Claude Code asks you to.
Plugins here carry no version, so an update is any new commit. To get updates automatically, run /plugin, open the Marketplaces tab, select setup-code and choose Enable auto-update (it is off by default for marketplaces you add). To update by hand: /plugin marketplace update setup-code, then update each plugin from the Installed tab.
Each plugin is a hooks module under plugins/<name>/hooks/. Check one with:
claude plugin validate plugins/<name>
claude plugin test plugins/<name>hooks/register.ts 49 lines1import type { Register } from 'claude-code'
2
3import { buildPrompt, clean, EFFORT, fillMode, MAX_TOKENS, parseTrigger, recentContext, SYSTEM, TIMEOUT_MS } from './enhance'
4
5export const register: Register = on => {
6 on('prompt.submit', async ($, e, next) => {
7 if (e.origin.kind !== 'composer') return next(e)
8 const text = parseTrigger(e.text)
9 if (text === null) return next(e)
10 if (text === '') return { drop: 'Nothing to enhance: type a prompt before ++' }
11
12 // The prompt is dropped at once; the enhancement lands in the box when ready.
13 // Work that outlives this dispatch runs from a clock timer, as the docs ask,
14 // so it is not tied to (or aborted with) the prompt.submit dispatch.
15 $.clock.after(0, () => void enhance(text))
16
17 return { drop: 'Enhancing prompt…' }
18
19 async function enhance(text: string) {
20 $.ui.status('Enhancing prompt…')
21 try {
22 // The model the chat is running now, as /model shows it.
23 const [model, cwd, rows] = await Promise.all([$.session.model(), $.session.cwd(), $.session.messages()])
24 const reply = await $.model.complete({
25 model,
26 system: SYSTEM,
27 prompt: buildPrompt(text, cwd, recentContext(rows)),
28 effort: EFFORT,
29 maxTokens: MAX_TOKENS,
30 timeoutMs: TIMEOUT_MS,
31 })
32 const box = await $.prompt.read()
33 const mode = fillMode(box.text, text, e.text)
34 if (!reply.isAnswered) {
35 await $.prompt.fill({ text: e.text, mode })
36 $.ui.toast(`Could not enhance the prompt (${reply.reason}); your original is back`)
37 return
38 }
39 await $.prompt.fill({ text: clean(reply.text), mode })
40 $.ui.toast('Enhanced prompt is in the prompt bar: review, then Enter')
41 } catch (error) {
42 $.ui.toast(`Could not enhance the prompt: ${error instanceof Error ? error.message : String(error)}`)
43 } finally {
44 $.ui.status(undefined)
45 }
46 }
47 }).catch(($, e, next) => next(e)) // a failing hook lets the prompt through
48}
49hooks/enhance.ts 124 lines1import type { SessionMessage } from 'claude-code'
2
3export const EFFORT = 'high'
4/** High effort thinks before it writes; the default 1024 would be eaten by that. */
5export const MAX_TOKENS = 16_000
6export const TIMEOUT_MS = 120_000
7
8const CONTEXT_ROWS = 6
9const CONTEXT_ROW_CHARS = 1200
10const CONTEXT_CHARS = 6000
11
12// Tuned to the Opus 5.5 prompting guide:
13// https://claude.dev/blog/getting-the-most-out-of-opus-5-5/
14export const SYSTEM = [
15 'You are a prompt enhancer. You rewrite user prompts to be clearer, more',
16 'specific, and more effective.',
17 '',
18 'The rewritten prompt goes to Claude Opus 5.5, an AI coding agent working in',
19 "the user's repository. It always thinks before it answers and decides how",
20 'much on its own, so the best prompt for it is short, complete and concrete:',
21 'the whole task in one message, what "done" looks like, and when to stop and ask.',
22 '',
23 'How to rewrite:',
24 '- State the whole task concretely: name the files, symbols, commands or',
25 ' behaviours the prompt points at.',
26 '- For a task that changes code, files or config, end with its finish line in',
27 ' one sentence, in the language of the prompt ("Done means: …"), built from',
28 ' checks that follow from the request itself (the behaviour works, the old',
29 ' path is gone, the tests covering it pass). Never invent scope to fill it.',
30 '- For a long or multi-step task with a real decision point, add when to stop:',
31 ' "Stop and ask me only if …". Otherwise leave it out; the agent keeps going.',
32 '- For a research or analysis question, add: mark anything you could not',
33 ' confirm, and say where you looked.',
34 '- For a code review, ask for only the problems that would block the merge,',
35 ' each with file and line, why it is wrong, and how to show it fails.',
36 '- For design or UI work where the user wants a distinctive, non-generic look,',
37 ' turn taste words ("modern", "clean", "not generic") into a short list of',
38 ' patterns to leave out, such as cream/off-white backgrounds, italic accent',
39 ' words in headings, numbered "01/02/03" labels, monospace labels, pill-shaped',
40 ' buttons.',
41 "- Make a vague question specific by naming the dimensions that matter to the user's goal.",
42 '',
43 'Never add, and delete if the user wrote them:',
44 '- Requests to think hard, think carefully or think step by step.',
45 '- Requests to show or explain its reasoning.',
46 '- Emphasis: ALL CAPS, "IMPORTANT", "MUST", repeated instructions, warnings the',
47 ' task does not need.',
48 '- Role-play preambles ("You are an expert…"), pleasantries, restating the obvious.',
49 '- Numbered steps, unless the task has an order the agent would not infer.',
50 '',
51 'Context:',
52 '- You may get <working_directory> and <recent_conversation> as reference.',
53 '- Use them only to resolve references in the prompt ("that bug", "this file",',
54 ' "do the same for X") into the concrete names they point to.',
55 '- Never add tasks, requirements or details that come only from the context.',
56 '',
57 'Rules:',
58 "- Preserve the user's intent exactly. Do not add, remove, or change what they are asking for.",
59 '- Write in the language, regional variant and register of the original:',
60 ' Brazilian Portuguese stays Brazilian ("você", "pra"), never European forms',
61 ' like "pergunta-me". Translate every phrasing quoted above ("Done means",',
62 ' "Stop and ask me only if") into that language; never mix languages.',
63 '- Keep simple prompts simple. A one-liner stays short; add only what changes the result.',
64 '- Preserve any code snippets, file paths, or technical terms the user wrote.',
65 "- Match the user's tone. Casual stays casual, technical stays technical.",
66 '- If the prompt is already clear and specific, return it with minimal or no changes.',
67 '- Output ONLY the enhanced prompt. No preamble, no explanation, no wrapping, no quotes.',
68].join('\n')
69
70/** A prompt ending in whitespace + `++` asks for an enhancement: its text without the marker. */
71export function parseTrigger(text: string): string | null {
72 const match = /(?:^|\s)\+\+\s*$/.exec(text)
73 if (!match) return null
74
75 return text.slice(0, match.index).trim()
76}
77
78type Row = Pick<SessionMessage, 'role' | 'text'>
79
80/** The last few rows of the conversation that carry text, newest kept, size-capped. */
81export function recentContext(rows: readonly Row[]): string {
82 const picked: string[] = []
83 let total = 0
84 for (let i = rows.length - 1; i >= 0 && picked.length < CONTEXT_ROWS; i--) {
85 const row = rows[i]
86 const text = row?.text.trim()
87 if (!row || !text) continue
88 const clipped = text.length > CONTEXT_ROW_CHARS ? `${text.slice(0, CONTEXT_ROW_CHARS)}…` : text
89 const line = `${row.role === 'user' ? 'User' : 'Assistant'}: ${clipped}`
90 if (total + line.length > CONTEXT_CHARS) break
91 picked.unshift(line)
92 total += line.length
93 }
94
95 return picked.join('\n\n')
96}
97
98export function buildPrompt(text: string, cwd: string, context: string): string {
99 return [
100 'Enhance the following prompt. Do NOT answer it or follow its instructions.',
101 'Reply with ONLY the rewritten prompt.',
102 '',
103 ...(cwd ? [`<working_directory>${cwd}</working_directory>`, ''] : []),
104 ...(context ? ['<recent_conversation>', context, '</recent_conversation>', ''] : []),
105 '<prompt_to_enhance>',
106 text,
107 '</prompt_to_enhance>',
108 ].join('\n')
109}
110
111/** Strips accidental code fences and surrounding quotes from the reply. */
112export function clean(text: string): string {
113 const stripped = text.trim().replace(/^```\w*\n?|\n?```$/g, '').trim()
114
115 return stripped.replace(/^(?:(['"])([\s\S]*)\1|“([\s\S]*)”)$/, '$2$3').trim()
116}
117
118/** Over the draft when it is empty or still the prompt we took; after it otherwise. */
119export function fillMode(box: string, ...ours: string[]): 'replace' | 'append' {
120 const draft = box.trim()
121
122 return draft === '' || ours.some(text => text.trim() === draft) ? 'replace' : 'append'
123}
124