Dopo ogni risposta che cambia i file, sopra il prompt: cosa ha fatto Claude in parole semplici e come provarlo.

Cinque mod gratuite per chi usa Claude Code per costruire i propri progetti (un sito, un'app, un'automazione) anche senza saper programmare.
| Mod | In una riga | |
|---|---|---|
| 💾 | save-point | Claude ha rotto qualcosa? Torni a quando funzionava con un clic. |
| 💡 | cosa-hai-fatto | Dopo ogni modifica ti spiega in parole semplici cosa ha fatto Claude e come provarlo. |
| ⛽ | benzina | Vedi quanto ti resta del piano Claude prima che si fermi, e quando si ricarica. |
| ⚡ | quick-replies | Pulsanti per le risposte che scrivi sempre: "Continua", "Procedi", "Non funziona, controlla tu". |
| 🌐 | localhost | Vedi le app che hai acceso sul computer, aprile nel browser o spegnile. |
Puoi installarne una sola, alcune o tutte e cinque: sono indipendenti.
Una mod è un piccolo plugin che cambia come Claude Code si comporta e cosa ti mostra: una riga sopra il punto dove scrivi, un pannello laterale, un pulsante, un avviso. Non cambia il modello e non legge niente fuori dal tuo computer: gira dentro Claude Code, sul tuo Mac o PC.
Dove le vedi:
/salvataggi (save-point) e /localhost. claude --version ` Se è più vecchio, aggiornalo con: ` claude update ``xcode-select --install. Su Linux: installa il pacchetto git. cd percorso/del/tuo/progetto claude `` /plugin install save-point --marketplace cavallinilorenzo/claude-code-mods ` ` /plugin install cosa-hai-fatto --marketplace cavallinilorenzo/claude-code-mods ` ` /plugin install benzina --marketplace cavallinilorenzo/claude-code-mods ` ` /plugin install quick-replies --marketplace cavallinilorenzo/claude-code-mods ` ` /plugin install localhost --marketplace cavallinilorenzo/claude-code-mods ``Add marketplace?: rispondi y.Installed <nome>. Plugin is now active. La mod è già attiva, non serve riavviare.In alternativa, dal terminale normale (fuori da Claude Code):
claude plugin marketplace add cavallinilorenzo/claude-code-mods
claude plugin install save-point@lorenzo-mods
claude plugin install cosa-hai-fatto@lorenzo-mods
claude plugin install benzina@lorenzo-mods
claude plugin install quick-replies@lorenzo-mods
claude plugin install localhost@lorenzo-mods
Il problema: chiedi una modifica, Claude cambia dieci file e l'app non parte più. Non sai cosa ha toccato e non sai come tornare indietro.
Cosa fa: ogni volta che Claude sta per modificare i file, save-point salva com'era il progetto un attimo prima. Lo fa da solo, una volta per ogni tua richiesta, e il salvataggio prende il nome della richiesta.
Come si usa:
| Comando | Cosa succede |
|---|---|
/salvataggi | Apre l'elenco dei salvataggi. Accanto a ognuno c'è Torna qui: lo premi, confermi con Sì, torna qui e tutti i file tornano com'erano in quel momento. |
/salva | Salva adesso, a mano. |
/salva funziona il login | Salva adesso con un nome tuo: comparirà come ⭐ funziona il login. |
Cosa vedi in /salvataggi:
oggi 15:42 Prima di: «aggiungi il carrello» [ Torna qui ]
oggi 15:10 ⭐ funziona il login [ Torna qui ]
oggi 14:31 Prima di: «cambia i colori del sito» [ Torna qui ]
Da sapere:
~/.claude/save-points/.node_modules, .venv, venv, __pycache__, .next, dist, build, target, .cache), i file .log e i file che il tuo progetto ignora già con .gitignore.~/.claude/save-points/.Il problema: Claude risponde con nomi di file, funzioni e comandi. Tu vuoi solo sapere cosa è cambiato e se funziona.
Cosa fa: quando una risposta di Claude ha modificato dei file, sopra il prompt compare un riassunto in parole semplici, senza gergo tecnico, con un passo concreto per provarlo.
💡 In breve: Ora i clienti possono pagare con la carta e il carrello mostra le spese di spedizione.
👉 Prova: aggiungi un prodotto al carrello e premi «Paga».
[ Spiegamelo meglio ] [ Nascondi ]
Da sapere:
english, español).Il problema: stai lavorando e all'improvviso Claude si ferma perché hai finito il limite del tuo piano. Oppure la chat è così lunga che Claude inizia a dimenticare le cose.
Cosa fa: mostra sopra il prompt quanto ti resta, come l'indicatore della benzina:
⛽ 5 ore ▰▰▰▱▱▱▱▱ 38% · ricarica alle 16:40 Settimana ▰▰▰▰▰▰▱▱ 80% Chat ▰▰▰▰▰▱▱▱ 64%
Quando serve ti dà un consiglio in chiaro:
Ti avvisa anche con una notifica quando superi l'80% e il 95% di un limite.
| Comando | Cosa succede |
|---|---|
/benzina | Nasconde l'indicatore, o lo fa ricomparire. La scelta resta salvata. |
Da sapere: i limiti delle 5 ore e della settimana compaiono solo con un abbonamento Claude (Pro o Max) e dopo la prima risposta di Claude. Se usi una chiave API vedi solo la Chat.
Il problema: scrivi cento volte al giorno "continua", "procedi", "ok va bene".
Cosa fa: dopo ogni risposta di Claude, sopra il prompt compaiono dei pulsanti con le risposte che usi sempre. Un clic e la risposta parte.
[ Continua ] [ Procedi ] [ Ok a tutte le tue raccomandazioni ] [ Non funziona, controlla tu e sistemalo ]
Le risposte si cambiano dalle impostazioni: scrivile separate da |, fino a 9. Ad esempio:
Continua|Procedi|Spiegamelo in parole semplici|Avviamelo così lo provo
Il problema: Claude ti dice "l'app è su localhost:5173". Ma quale? Quante ne hai accese? Come le spegni?
Cosa fa: /localhost apre un pannello con tutte le app che hai acceso sul computer (vite, next, django, flask, uvicorn…), la porta, la cartella da cui sono partite e due pulsanti:
:5173 node ~/progetti/negozio apri [ ferma ]
:8000 Python ~/progetti/negozio/backend apri [ ferma ]
:3000 node ~/progetti/vecchio-test apri [ ferma ]
[ Ferma quelli di questo progetto (2) ] [ Ferma tutti ] [ Aggiorna ]
Da sapere: funziona su Mac e Linux.
Due mod hanno impostazioni:
| Mod | Impostazione | Default | ||||
|---|---|---|---|---|---|---|
| quick-replies | Risposte rapide (separate da `\ | `) | `Continua\ | Procedi\ | Ok a tutte le tue raccomandazioni\ | Non funziona, controlla tu e sistemalo` |
| cosa-hai-fatto | Lingua delle spiegazioni | italiano |
Le scegli la prima volta, nella schermata che compare durante l'installazione. Per cambiarle dopo, dal terminale:
echo '{"replies": "Continua|Procedi|Spiegamelo in parole semplici"}' | claude plugin configure quick-replies@lorenzo-mods --values-stdin
echo '{"language": "english"}' | claude plugin configure cosa-hai-fatto@lorenzo-mods --values-stdin
Per vedere i valori attuali: claude plugin configure quick-replies@lorenzo-mods.
Dal terminale:
claude plugin update save-point@lorenzo-mods # prende l'ultima versione
claude plugin disable benzina@lorenzo-mods # la spegne senza toglierla
claude plugin enable benzina@lorenzo-mods # la riaccende
claude plugin uninstall localhost@lorenzo-mods # la toglie
Dentro Claude Code, /plugin apre lo stesso menu con i pulsanti.
Devo saper programmare? No. Si installano incollando una riga e si usano con pulsanti e comandi in italiano.
Costano qualcosa? Le mod sono gratis. Solo cosa-hai-fatto fa una piccola chiamata a Claude Haiku dopo le risposte che modificano file, che conta sul tuo piano Claude come qualsiasi altro messaggio.
Mandano i miei dati da qualche parte? No. Lavorano sul tuo computer. save-point salva i file in ~/.claude/save-points/. cosa-hai-fatto manda a Claude (come fa già Claude Code) la tua richiesta, i nomi dei file modificati e la risposta di Claude, per scrivere il riassunto. Il codice è tutto in questa pagina: puoi leggerlo.
save-point sostituisce git o GitHub? No. È una rete di sicurezza per tornare indietro mentre lavori con Claude. Se vuoi pubblicare il progetto o lavorarci con altre persone, git e GitHub restano lo strumento giusto.
E il tasto Esc due volte di Claude Code ("rewind")? Il rewind di Claude Code annulla le modifiche fatte con i suoi strumenti di modifica dei file. save-point salva tutta la cartella, quindi copre anche quello che cambia per altre vie (comandi, installazioni, script), e i salvataggi non dipendono dalla conversazione: li ritrovi anche in una chat nuova.
Funzionano nell'app desktop? Sì, nella scheda Code. Si installano dal terminale una volta sola e poi le trovi anche lì.
Funzionano su Windows? benzina, cosa-hai-fatto e quick-replies sì. save-point e localhost sono pensate per Mac e Linux: su Windows non le ho provate.
| Cosa vedi | Cosa fare |
|---|---|
Marketplace file not found | Controlla di aver scritto bene cavallinilorenzo/claude-code-mods. |
Plugin "…" not found in marketplace | Controlla il nome della mod: save-point, cosa-hai-fatto, benzina, quick-replies, localhost. |
/plugin install non esiste nell'app desktop | È normale: installale dal terminale, poi le trovi anche nell'app. |
| save-point dice che non salva la home | Hai avviato Claude nella tua home. Chiudi, entra nella cartella del progetto (cd …) e riavvia claude. |
| save-point dice che serve git | Mac: xcode-select --install. Linux: installa il pacchetto git. |
| benzina mostra solo la Chat | Usi una chiave API invece di un abbonamento, oppure Claude non ha ancora risposto in questa sessione. |
| La banda sopra il prompt non si vede | Si può ridurre con [-] o ctrl+x ctrl+a: premi di nuovo per riaprirla. |
| Una mod non fa niente | Aggiorna Claude Code (claude update) e controlla con claude plugin list che sia attiva. |
Ogni mod è una cartella in mods/ con:
.claude-plugin/plugin.json, il nome, la descrizione e le impostazioni;hooks/register.tsx, il codice della mod;hooks/*.test.ts(x), i test;types/index.d.ts, i valori che la mod tiene in memoria.Per provarne una modificata senza installarla:
claude --plugin-dir ./mods/save-point
Per controllarla e lanciare i test:
claude plugin validate ./mods/save-point
claude plugin test ./mods/save-point
Idee e segnalazioni: apri una issue.
MIT: puoi usarle, modificarle e ricondividerle, mantenendo la nota di copyright.
Fatto da Lorenzo Cavallini.
hooks/register.tsx 149 lines1import { atom, read, update } from 'claude-code'
2import type { EngineInterface, Register } from 'claude-code'
3
4import { parseRecap, shortName, systemPrompt, userPrompt } from './recap'
5import type { TurnWork } from './recap'
6
7const recap = atom({ plugin: 'cosa-hai-fatto', key: 'recap' } as const, null)
8const isThinking = atom({ plugin: 'cosa-hai-fatto', key: 'isThinking' } as const, false)
9const isHidden = atom({ plugin: 'cosa-hai-fatto', key: 'isHidden' } as const, false)
10
11const FILE_TOOLS = new Set(['Edit', 'Write', 'NotebookEdit'])
12const EXPLAIN_MORE =
13 'Spiegami in parole semplici, come se non sapessi programmare, cosa hai cambiato nell\'ultima risposta, perché, e come posso verificare che funzioni.'
14
15function fileOf(input: unknown): string | undefined {
16 if (typeof input !== 'object' || input === null) {
17 return undefined
18 }
19 const record = input as Record<string, unknown>
20 const path = record.file_path ?? record.notebook_path
21 return typeof path === 'string' ? path : undefined
22}
23
24function describedCommand(input: unknown): string | undefined {
25 if (typeof input !== 'object' || input === null) {
26 return undefined
27 }
28 const record = input as Record<string, unknown>
29 const text = record.description ?? record.command
30 return typeof text === 'string' ? text.slice(0, 120) : undefined
31}
32
33async function explain($: EngineInterface, work: TurnWork, answer: string, language: string): Promise<void> {
34 await update($, isThinking, () => true)
35 try {
36 const reply = await $.model.complete({
37 model: 'haiku',
38 system: systemPrompt(language),
39 prompt: userPrompt(work, answer),
40 maxTokens: 300,
41 timeoutMs: 30_000,
42 })
43 const found = reply.isAnswered ? parseRecap(reply.text, work.files.length) : null
44 await update($, recap, () => found)
45 await update($, isHidden, () => false)
46 } finally {
47 await update($, isThinking, () => false)
48 }
49}
50
51export const register: Register = (on, options) => {
52 const language = String(options.language ?? 'italiano').trim() || 'italiano'
53 let work: TurnWork = { request: '', files: [], commands: [] }
54
55 on('turn.start', async ($, e, next) => {
56 work = { request: e.text, files: [], commands: [] }
57 await update($, recap, () => null)
58
59 return next(e)
60 })
61
62 on('tool.call', async ($, e, next) => {
63 const result = await next(e)
64 const cwd = await $.session.cwd()
65 const file = FILE_TOOLS.has(String(e.tool)) ? fileOf(e) : undefined
66 if (file !== undefined) {
67 const name = shortName(file, cwd)
68 if (!work.files.includes(name)) {
69 work = { ...work, files: [...work.files, name].slice(0, 30) }
70 }
71 }
72 if (String(e.tool) === 'Bash') {
73 const command = describedCommand(e)
74 if (command !== undefined) {
75 work = { ...work, commands: [...work.commands, command].slice(-8) }
76 }
77 }
78
79 return result
80 })
81
82 on('turn.complete', async ($, e, next) => {
83 const result = await next(e)
84 // Only the main conversation's turns, and only when files changed: a plain answer needs no recap.
85 if (e.agentId === undefined && e.reason === 'answer' && work.files.length > 0) {
86 // A recap that fails leaves the band empty; it never gets in the way of the session.
87 void explain($, work, e.answer, language).catch(() => undefined)
88 }
89
90 return result
91 })
92
93 on('session.end', async ($, e, next) => {
94 await update($, recap, () => null)
95
96 return next(e)
97 })
98
99 on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
100 const below = await next(e)
101 const current = await read($, recap)
102 const thinking = await read($, isThinking)
103 const isQuiet =
104 e.props.hasSurvey ||
105 e.props.isWorking ||
106 e.props.view.agentId !== undefined ||
107 (await read($, isHidden)) ||
108 (current === null && !thinking)
109
110 if (isQuiet) {
111 return below
112 }
113
114 const { Box, Button, Text } = $.ui.resolve(e)
115
116 return (
117 <Box flexDirection="column">
118 {current === null ? (
119 <Text key="thinking" dimColor>
120 💡 Preparo il riassunto in parole semplici…
121 </Text>
122 ) : (
123 <Box key="recap" flexDirection="column">
124 <Text>
125 <Text bold color="claude">💡 In breve: </Text>
126 {current.what}
127 </Text>
128 {current.tryIt !== null && (
129 <Text>
130 <Text bold>👉 Prova: </Text>
131 {current.tryIt}
132 </Text>
133 )}
134 <Box flexDirection="row" columnGap={1}>
135 <Button
136 key="more"
137 label="Spiegamelo meglio"
138 onPress={() => void $.prompt.submit({ text: EXPLAIN_MORE, asUser: true })}
139 />
140 <Button key="hide" label="Nascondi" onPress={() => update($, isHidden, () => true)} />
141 </Box>
142 </Box>
143 )}
144 {below}
145 </Box>
146 )
147 })
148}
149hooks/recap.ts 52 lines1import type { Recap } from '../types'
2
3export type TurnWork = {
4 request: string
5 files: string[]
6 commands: string[]
7}
8
9export function systemPrompt(language: string): string {
10 return [
11 `Spieghi a una persona che NON sa programmare cosa ha appena fatto un assistente di programmazione sul suo progetto. Rispondi in ${language}.`,
12 'Niente gergo tecnico: niente nomi di file, funzioni, librerie o comandi, a meno che la persona non li abbia usati lei stessa.',
13 'Parla di quello che la persona vedrà o potrà fare, non di come è fatto il codice.',
14 'Rispondi con esattamente due righe, senza altro testo:',
15 'COSA: una o due frasi brevi su cosa è cambiato (massimo 200 caratteri).',
16 'PROVA: un passo concreto per vedere la modifica (massimo 120 caratteri), oppure "-" se non c\'è niente da provare.',
17 ].join('\n')
18}
19
20export function userPrompt(work: TurnWork, answer: string): string {
21 const files = work.files.map(file => `- ${file}`).join('\n') || '- (nessuno)'
22 const commands = work.commands.map(command => `- ${command}`).join('\n') || '- (nessuno)'
23
24 return [
25 `La richiesta della persona:\n${clip(work.request, 1500)}`,
26 `I file modificati:\n${files}`,
27 `Le azioni eseguite:\n${commands}`,
28 `La risposta finale dell'assistente:\n${clip(answer, 4000)}`,
29 ].join('\n\n')
30}
31
32export function parseRecap(text: string, files: number): Recap | null {
33 const what = /^\s*COSA:\s*(.+)$/im.exec(text)?.[1]?.trim()
34 if (what === undefined || what.length === 0) {
35 return null
36 }
37 const tryIt = /^\s*PROVA:\s*(.+)$/im.exec(text)?.[1]?.trim()
38 const hasTry = tryIt !== undefined && tryIt !== '-' && tryIt.length > 1
39
40 return { what, tryIt: hasTry ? tryIt : null, files }
41}
42
43// The file name alone: the model needs a hint of what was touched, not the whole path.
44export function shortName(path: string, cwd: string): string {
45 const inside = path.startsWith(`${cwd}/`) ? path.slice(cwd.length + 1) : path
46 return inside.length > 80 ? `…${inside.slice(-79)}` : inside
47}
48
49function clip(text: string, max: number): string {
50 return text.length > max ? `${text.slice(0, max)}…` : text
51}
52types/index.d.ts 18 lines1export type Recap = {
2 /** What changed, in words for someone who does not code. */
3 what: string
4 /** How to see it working, if there is something to try. */
5 tryIt: string | null
6 files: number
7}
8
9declare module 'claude-code' {
10 interface PluginState {
11 'cosa-hai-fatto': {
12 recap: Recap | null
13 isThinking: boolean
14 isHidden: boolean
15 }
16 }
17}
18