SLOPSHOPPER

collapse-answers

Adds a button under each answer that collapses it, leaving only your question.

newpanebandspinnerrows
A shopper browsing a rack in a slop shop
README

claude-code-mods

Three small tweaks that make long Claude Code sessions easier to live with.

After a few hours in Claude Code, the same small annoyances kept getting in my way: I'd lose track of what I'd asked, scroll past screens of output to find my last question, and hit my usage limit without seeing it coming. Each mod here fixes one of those. They're tiny, they don't change how Claude works, and you can turn any of them off by removing one line from your settings.

1. prompt-highlight: see your own questions at a glance

In a long conversation, your messages and Claude's answers are the same plain text, so scrolling back to find "what did I ask again?" means reading everything. This mod puts your messages on a colored band, so you can find them while you scroll.

Your message on a blue band, Claude's answer below it in plain text

2. usage-status: know where you stand before you hit the limit

Claude subscriptions have two usage limits, one per 5 hours and one per week, and Claude Code doesn't show you either until you're close. This mod keeps both in the status line, with when each one resets, so you can decide whether to start a big task now or save it for later.

The status line showing 5h 7% and week 8% with their reset times

3. collapse-answers: fold away what you've already read

Some answers run for screens: tool calls, file listings, long explanations. Once you've read one, it's just in the way. This mod adds a ▾ Collapse button at the end of each answer. Press it and the whole answer folds down to one line under your question, which also makes the conversation easy to skim later. Press ▸ Show answer to bring it back.

Collapsing an answer down to the question and expanding it again

Install

  1. Clone the repo into ~/.claude/mods:
   git clone https://github.com/adriancoman/claude-code-mods.git ~/.claude/mods

If you already have a ~/.claude/mods folder, clone it somewhere else and use that path below.

  1. Tell Claude Code where the mods are. Open ~/.claude/settings.json and add the ones you want to the env block, separated by : (; on Windows):
   {
     "env": {
       "CLAUDE_CODE_PLUGIN_DIRS": "~/.claude/mods/prompt-highlight:~/.claude/mods/usage-status:~/.claude/mods/collapse-answers"
     }
   }
  1. Start a new Claude Code session. That's it, there's nothing to build or install.

Want to try one first? This loads it for a single session only:

claude --plugin-dir ~/.claude/mods/collapse-answers

To update later, run git pull in ~/.claude/mods.

Good to know

  • usage-status only works with a Claude subscription (Pro, Max, Team). On an API key there are no limits to show. The numbers update after each answer, so they'll appear once Claude has replied at least once.
  • collapse-answers only adds the button to answers given after the mod loaded, not to older ones.
  • Don't like the blue? Change BACKGROUND at the top of prompt-highlight/hooks/index.tsx.
  • These are built on Claude Code's mod API, which is still early access (tested on Claude Code 2.1.287). A future update could break them. If that happens, please open an issue.

Making changes

Each mod is a folder with a short TypeScript file in hooks/. To check a mod after editing it, run claude plugin validate <mod folder>. Once Claude Code has loaded a mod, it adds type definitions to the folder, and tsc -p <mod folder> will type-check your changes.

License

MIT

Source 2 files
hooks/index.tsx 89 lines
1import { atom, read, update } from 'claude-code'
2import type { EngineInterface, Register } from 'claude-code'
3
4import type { CollapseAnswersTurns } from '../types'
5
6const EMPTY: CollapseAnswersTurns = { rowTurn: {}, first: {}, last: {} }
7
8const turns = atom({ plugin: 'collapse-answers', key: 'turns' } as const, EMPTY)
9const collapsed = atom({ plugin: 'collapse-answers', key: 'collapsed' } as const, [])
10
11function toggle($: EngineInterface, turn: string) {
12  return update($, collapsed, list => (list.includes(turn) ? list.filter(t => t !== turn) : [...list, turn]))
13}
14
15export const register: Register = on => {
16  // The prompt row that opened the turn the next answer rows belong to.
17  let current: string | null = null
18
19  on('session.append', async ($, e, next) => {
20    const stored = await next(e)
21
22    if (e.agentId !== undefined || e.message.isMeta) {
23      return stored
24    }
25
26    if (e.message.type === 'user' && e.origin.kind === 'composer') {
27      current = e.uuid
28    } else if (e.message.type === 'assistant' && current !== null) {
29      const turn = current
30      // A text block draws under the row's id, a tool call under its tool_use_id.
31      const drawn = e.message.content.flatMap<string>(block =>
32        block.type === 'text' ? [e.uuid] : block.type === 'tool_use' && typeof block.id === 'string' ? [block.id] : [],
33      )
34      const hasText = e.message.content.some(block => block.type === 'text')
35
36      const head = drawn[0]
37
38      if (head !== undefined) {
39        await update($, turns, t => ({
40          rowTurn: { ...t.rowTurn, ...Object.fromEntries(drawn.map(id => [id, turn])) },
41          first: t.first[turn] ? t.first : { ...t.first, [turn]: head },
42          last: hasText ? { ...t.last, [turn]: e.uuid } : t.last,
43        }))
44      }
45    }
46
47    return stored
48  })
49
50  on('ui.render', async ($, e, next) => {
51    if (!['AssistantMessage', 'ToolUse', 'ToolResult', 'ToolGroup'].includes(e.component)) {
52      return next(e)
53    }
54
55    const key = e.component === 'ToolGroup' ? (e.props.calls[0]?.tool_use_id ?? e.requestId) : e.requestId
56    const t = await read($, turns)
57    const turn = t.rowTurn[key]
58
59    if (turn === undefined) {
60      return next(e)
61    }
62
63    const { Box, Button } = $.ui.resolve(e)
64
65    if ((await read($, collapsed)).includes(turn)) {
66      return key === t.first[turn] && e.component !== 'ToolResult' ? (
67        <Box>
68          <Button key={`expand-${turn}`} label="▸ Show answer" onPress={() => toggle($, turn)} />
69        </Box>
70      ) : (
71        <Box />
72      )
73    }
74
75    if (e.component === 'AssistantMessage' && key === t.last[turn]) {
76      return (
77        <Box flexDirection="column">
78          {await next(e)}
79          <Box marginTop={1}>
80            <Button key={`collapse-${turn}`} label="▾ Collapse" onPress={() => toggle($, turn)} />
81          </Box>
82        </Box>
83      )
84    }
85
86    return next(e)
87  })
88}
89
types/index.d.ts 12 lines
1export type CollapseAnswersTurns = {
2  rowTurn: Record<string, string>
3  first: Record<string, string>
4  last: Record<string, string>
5}
6
7declare module 'claude-code' {
8  interface PluginState {
9    'collapse-answers': { turns: CollapseAnswersTurns; collapsed: string[] }
10  }
11}
12