Tre bottoni sopra il prompt a fine turno: i due prossimi passi naturali della conversazione e Replay

claude-code-mods è un marketplace di mod per Claude Code che ti permette di aggiungere all'interfaccia una barra della prompt cache, i prossimi passi a fine turno, un pannello di bottoni rapidi e il replay delle modifiche. Ogni mod è un plugin indipendente, installabile singolarmente con un comando.
⏳ cache-meter — una riga sopra il prompt con una barra che si svuota nel tempo: indica quanto resta dell'ora di prompt cache dall'ultima richiesta della conversazione principale. Verde oltre i 20 minuti, gialla oltre i 5, rossa sotto; allo scadere segnala che la richiesta successiva riscriverà la cache. Le richieste dei subagent non rinnovano la barra, perché usano una cache separata.
➡️ next-steps — a fine turno propone sopra il prompt due bottoni con i prossimi passi più naturali della conversazione, più un terzo bottone Replay. Premendo un passo, il prompt corrispondente viene inviato come se lo avessi scritto tu. I passi sono generati con una richiesta al modello a ogni fine turno, che consuma token.
🎛️ quick-buttons — un pannello laterale con un bottone per ogni comando ricorrente. Alla prima sessione dopo l'installazione la mod ti chiede quali skill e comandi vuoi come bottoni, elencando quelli presenti nella tua sessione; la scelta resta salvata tra le sessioni e puoi modificarla in qualsiasi momento.
⏪ replay-theater — registra le modifiche ai file dell'ultimo turno e le mostra in un pannello, un diff alla volta. È una variante della mod di esempio pubblicata da Anthropic in claude-code-playground, distribuita con licenza Apache-2.0; le differenze sono descritte in replay-theater/README.md.
claude --version.Una mod è codice eseguito con i tuoi permessi: può leggere e scrivere file, avviare processi e vedere prompt e tool call della sessione. Il sorgente di ogni mod è in hooks/, poche decine di righe: leggilo prima dell'installazione.
claude plugin marketplace add DarioFontanel/claude-code-mods
claude plugin install cache-meter@dario-mods
claude plugin install next-steps@dario-mods
claude plugin install quick-buttons@dario-mods
claude plugin install replay-theater@dario-mods
next-steps dichiara replay-theater come dipendenza: installandola, replay-theater viene installata automaticamente.
claude.Verifica: apri /plugin — sotto i tab compare la riga con le mod attive, ad esempio 4 mods active.
Gli stessi comandi sono disponibili dall'interno di una sessione come /plugin marketplace add e /plugin install. Per provare una mod in una sola sessione senza installarla, clona la repo e avvia claude --plugin-dir ./claude-code-mods/cache-meter.
Per disattivare una mod, disabilitala o disinstallala dal tab Installed di /plugin.
/replay — apre il pannello di replay sull'ultimo turno che ha modificato file. Nel pannello: n passo successivo, p passo precedente, c o Esc chiusura./azioni — apre il pannello di quick-buttons. Su un terminale di almeno 144 colonne il pannello si apre da solo all'avvio della sessione. Ogni bottone ha come tasto rapido la prima lettera libera del nome del comando./azioni config — riapre la scelta dei bottoni. Nel pannello di scelta: scrivi nel campo Cerca per filtrare l'elenco, spostati con le frecce, Invio aggiunge o toglie un comando, s salva, n e p cambiano pagina. Puoi scegliere fino a 9 comandi.ctrl+x poi Tab — sposta il focus sulla banda sopra il prompt. Da lì 1 e 2 inviano i passi proposti da next-steps, 3 apre il replay.Bottoni di quick-buttons — si scelgono dal pannello, con /azioni config, senza modificare il codice. L'elenco propone skill, comandi personalizzati e comandi di altri plugin; i comandi integrati di Claude Code sono esclusi. Un comando scelto che una sessione non ha non viene mostrato in quella sessione.
Durata della cache in cache-meter — la costante TTL_MS in cache-meter/hooks/register.js vale un'ora, la durata della prompt cache negli abbonamenti Claude. Se la tua cache dura 5 minuti, imposta 5 * 60 * 1000.
Lingua di next-steps — la richiesta al modello è la costante ASK in next-steps/hooks/register.tsx. I prompt proposti seguono la lingua della conversazione.
Per modificare una mod, clona la repo e caricala con claude --plugin-dir: ogni salvataggio ricarica il modulo nella sessione aperta.
claude-code-mods/
├── .claude-plugin/marketplace.json # elenco delle mod installabili
├── cache-meter/
├── next-steps/
├── quick-buttons/
└── replay-theater/ # Apache-2.0, con LICENSE propria
Ogni cartella è un plugin completo:
.claude-plugin/plugin.json — nome, versione e descrizione.hooks/hooks.json — il modulo da caricare.hooks/register.* — gli hook della mod.tests/ — i test, eseguibili con claude plugin test ./<mod>.claude plugin validate ./<mod> elenca gli eventi intercettati e le chiamate al motore di ciascuna mod.
Designed by Dario Fontanel, PhD
Aiuto PMI italiane ad integrare l'intelligenza artificiale per automatizzare i lavori ripetitivi, abbattere i costi e guadagnare tempo per crescere.
Licenza: MIT — replay-theater è distribuita con licenza Apache-2.0.
hooks/register.tsx 117 lines1import { atom, read, update } from 'claude-code'
2import type { EngineInterface, Register } from 'claude-code'
3
4import type { Step } from '../types'
5
6// I due passi proposti a fine turno, vuoti finché non arrivano
7const steps = atom({ plugin: 'next-steps', key: 'steps' } as const, [])
8// Vero mentre il modello sta scegliendo i passi
9const isThinking = atom({ plugin: 'next-steps', key: 'isThinking' } as const, false)
10
11// La domanda fatta al modello sopra la conversazione così com'è
12const ASK = [
13 'Non continuare il lavoro e non usare tool.',
14 'Guarda la conversazione e il punto in cui siamo: proponi i due prossimi passi più naturali che potrei chiederti adesso.',
15 'Devono essere concreti, diversi fra loro e eseguibili da te. Non proporre commit, push o di rivedere le modifiche: per quelli esiste già un bottone.',
16 'Rispondi SOLO con un array JSON di due oggetti, senza altro testo:',
17 '[{"label": "massimo 4 parole", "prompt": "il messaggio che ti scriverei, in prima persona, nella lingua della conversazione"}, {"label": "...", "prompt": "..."}]',
18].join('\n')
19
20// Dal testo del modello ai passi: il primo array JSON trovato, al massimo due voci valide
21export function parseSteps(text: string): Step[] {
22 const found = text.match(/\[[\s\S]*\]/)
23 if (!found) return []
24 try {
25 const list: unknown = JSON.parse(found[0])
26 if (!Array.isArray(list)) return []
27 return list
28 .filter((s) => typeof s?.label === 'string' && typeof s?.prompt === 'string' && s.label.trim() && s.prompt.trim())
29 .slice(0, 2)
30 .map((s) => ({ label: s.label.trim().slice(0, 40), prompt: s.prompt.trim() }))
31 } catch {
32 return []
33 }
34}
35
36// Cresce a ogni prompt: una proposta partita prima del prompt è vecchia e si scarta
37let generation = 0
38
39async function suggest($: EngineInterface, mine: number) {
40 let found: Step[] = []
41 try {
42 const reply = await $.model.fork({ prompt: ASK })
43 if (reply.isAnswered) found = parseSteps(reply.text)
44 } finally {
45 if (mine === generation) {
46 await update($, steps, () => found)
47 await update($, isThinking, () => false)
48 }
49 }
50}
51
52// Un passo scelto: la riga si svuota e parte un turno con quel prompt, come se l'avessi scritto io.
53// Il prompt.submit di una mod non ripassa dal suo stesso hook, quindi si azzera qui
54async function pick($: EngineInterface, step: Step) {
55 generation += 1
56 await update($, steps, () => [])
57 await update($, isThinking, () => false)
58 await $.prompt.submit({ text: step.prompt, asUser: true })
59}
60
61export const register: Register = (on) => {
62 // Un nuovo prompt: i passi proposti non valgono più
63 on('prompt.submit', async ($, e, next) => {
64 generation += 1
65 await update($, steps, () => [])
66 await update($, isThinking, () => false)
67 return next(e)
68 })
69
70 on('turn.complete', async ($, e, next) => {
71 const result = await next(e)
72 // Solo la conversazione principale, e solo quando Claude ha davvero risposto
73 if (!e.agentId && e.reason === 'answer') {
74 const mine = generation
75 await update($, isThinking, () => true)
76 // Fuori dal turno: la fine del turno non aspetta la proposta
77 $.clock.after(0, () => void suggest($, mine))
78 }
79 return result
80 })
81
82 on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
83 // Quello che disegnano le altre mod nella banda resta sotto la nostra riga
84 const theirs = await next(e)
85 if (e.props.hasSurvey || e.props.isWorking) return theirs
86
87 const { Box, Button, Text } = $.ui.resolve(e)
88 const proposed = await read($, steps)
89 const thinking = await read($, isThinking)
90
91 const row = (
92 <Box flexDirection="row" columnGap={3}>
93 {proposed.map((step, i) => (
94 <Button
95 key={'step-' + (i + 1)}
96 label={step.label}
97 hotkey={String(i + 1)}
98 plain
99 onPress={() => void pick($, step)}
100 />
101 ))}
102 {thinking ? <Text dimColor>prossimi passi…</Text> : null}
103 <Button key="replay" label="Replay" hotkey="3" plain onPress={() => void $.command.run({ command: 'replay' })} />
104 </Box>
105 )
106
107 return theirs ? (
108 <Box flexDirection="column">
109 {row}
110 {theirs}
111 </Box>
112 ) : (
113 row
114 )
115 })
116}
117types/index.d.ts 9 lines1// Un prossimo passo: l'etichetta del bottone e il prompt che invia
2export type Step = { label: string; prompt: string }
3
4declare module 'claude-code' {
5 interface PluginState {
6 'next-steps': { steps: Step[]; isThinking: boolean }
7 }
8}
9