SLOPSHOPPER

next-steps

Кнопки со следующими шагами над полем ввода после каждого ответа

newbandcommandpromptmodeltimer
v0.1.0no licenseupdated 2026-10-07pewdredd/claude-code-mods/next-steps
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · next-steps
› 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 › /next-steps ⎿ next-steps: Next Steps выключен. ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts
README

Моды для Claude Code

Пять модов - маленьких плагинов, которые меняют интерфейс Claude Code: показывают кэш и лимиты, прячут личное на записи экрана, рисуют прогресс цели и не дают двум чатам править один файл вслепую.

Из ролика на канале @amir_aktay.

Установка

В Claude Code:

/plugin marketplace add pewdredd/claude-code-mods
/plugin install cache-keeper@aktay-mods

Остальные ставятся так же, меняется только имя: collision-guard, goal-meter, next-steps, recording-mode. Можно поставить любые по отдельности.

Что делает каждый мод

МодЧто делаетКак пользоваться
cache-keeperНад полем ввода: сколько осталось жить кэшу промпта (60 минут), заполнение контекста, лимиты 5 часов и недели, цена сессии. За 5 минут до остывания кэша - уведомление. Кнопка «перенести сессию»: Claude пишет сводку сессии, потом кнопка «Очистить и продолжить» сама делает /clear и отправляет сводку в чистый чат. Пока включён режим записи, здесь же горит красный «● REC»работает сам
next-stepsПосле ответа - три кнопки «что дальше», одним нажатием отправляют следующий запросработает сам, /next-steps - вкл/выкл
recording-modeПока пишешь экран, прячет в транскрипте почты, ключи, телефоны и суммы. Claude при этом видит всё как есть. Метку «● REC» рисует cache-keeper - ставь их вместе/record on, /record off
goal-meterРазбивает цель на шаги, отдаёт агенту и показывает полоску прогресса. После каждого ответа отмечает готовые шаги/goal-meter <цель>, /goal-meter off
collision-guardЕсли файл в последние 30 минут правил другой чат Claude Code - останавливает правку и просит агента спросить тебя. Повторная правка проходитработает сам

Что важно знать

  • Next Steps и Goal Meter после каждого ответа делают один короткий запрос к модели. Он идёт через кэш, но лимиты всё равно немного расходует.
  • Recording Mode прячет только то, что нарисовано на экране. В сам чат и в файлы идут настоящие данные.
  • Таймер Cache Keeper рассчитан на подписку, где кэш живёт час. На API по умолчанию кэш живёт 5 минут - поменяй CACHE_TTL_MS в cache-keeper/hooks/register.tsx.

Свой мод

Опиши словами, что мод должен показывать и когда, и попроси Claude Code его собрать. Любой мод отсюда - хороший образец: три файла в папке плагина и один register.tsx.

Source 2 files
hooks/register.tsx 100 lines
1import { atom, read, update } from 'claude-code'
2import type { Register } from 'claude-code'
3
4// Next Steps: после каждого ответа спрашиваем модель (через fork, из кэша)
5// о трёх следующих шагах и рисуем их кнопками над полем ввода.
6const steps = atom({ plugin: 'next-steps', key: 'steps' } as const, [])
7const isHidden = atom({ plugin: 'next-steps', key: 'isHidden' } as const, false)
8
9const ASK =
10  'Based on this conversation, suggest exactly 3 short next steps the user would most likely ask you to do next. ' +
11  'Answer in the language of the conversation. Each step is an imperative phrase under 60 characters. ' +
12  'Reply with a JSON array of 3 strings and nothing else.'
13
14function parse(text: string): string[] {
15  const match = text.match(/\[[\s\S]*\]/)
16  if (!match) return []
17  try {
18    const list = JSON.parse(match[0])
19    return Array.isArray(list) ? list.filter(s => typeof s === 'string').slice(0, 3) : []
20  } catch {
21    return []
22  }
23}
24
25export const register: Register = on => {
26  on('session.start', async ($, e, next) => {
27    await $.command.register({ name: 'next-steps', description: 'Показать или спрятать кнопки следующих шагов' })
28    return next(e)
29  })
30
31  on('command.run', { command: 'next-steps' }, async $ => {
32    const hidden = await update($, isHidden, v => !v)
33    return { text: hidden ? 'Next Steps выключен.' : 'Next Steps включён.' }
34  })
35
36  on('prompt.submit', async ($, e, next) => {
37    await update($, steps, () => [])
38    return next(e)
39  })
40
41  on('turn.complete', async ($, e, next) => {
42    const result = await next(e)
43    if (e.agentId || e.reason !== 'answer' || (await read($, isHidden))) return result
44
45    // fork живёт дольше хука, поэтому запускаем его таймером
46    $.clock.after(0, async () => {
47      try {
48        const answer = await $.model.fork({ prompt: ASK })
49        if (!answer.isAnswered) {
50          $.ui.log(`next-steps: модель не ответила (${answer.reason})`)
51          return
52        }
53        const list = parse(answer.text)
54        if (list.length === 0) $.ui.log(`next-steps: не разобрал ответ: ${answer.text.slice(0, 120)}`)
55        await update($, steps, () => list)
56      } catch (err) {
57        $.ui.log(`next-steps: ошибка ${String(err).slice(0, 160)}`)
58      }
59    })
60    return result
61  })
62
63  on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
64    const list = await read($, steps)
65    if (e.props.hasSurvey || e.props.isWorking || list.length === 0 || (await read($, isHidden))) {
66      return next(e)
67    }
68
69    const { Box, Button, Text } = $.ui.resolve(e)
70    const mine = (
71      <Box flexDirection="column" marginTop={1}>
72        <Text dimColor>дальше:</Text>
73        <Box flexDirection="column" paddingLeft={2}>
74          {list.map((step, i) => (
75            <Button
76              key={`step-${i}`}
77              plain
78              hotkey={String(i + 1)}
79              label={step}
80              onPress={async () => {
81                await update($, steps, () => [])
82                await $.prompt.submit({ text: step })
83              }}
84            />
85          ))}
86          <Button key="dismiss" plain dimColor hotkey="0" label="скрыть" onPress={() => update($, steps, () => [])} />
87        </Box>
88      </Box>
89    )
90
91    // место над полем ввода общее: дорисовываем под собой строки других модов
92    return (
93      <Box flexDirection="column">
94        {mine}
95        {await next(e)}
96      </Box>
97    )
98  })
99}
100
types/index.d.ts 8 lines
1export type NextStep = string
2
3declare module 'claude-code' {
4  interface PluginState {
5    'next-steps': { steps: NextStep[]; isHidden: boolean }
6  }
7}
8