SLOPSHOPPER

cosa-hai-fatto

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

newbandguardmodel
A shopper browsing a rack in a slop shop
README

5 mod per Claude Code (per chi non è sviluppatore)

Cinque mod gratuite per chi usa Claude Code per costruire i propri progetti (un sito, un'app, un'automazione) anche senza saper programmare.

ModIn una riga
💾save-pointClaude ha rotto qualcosa? Torni a quando funzionava con un clic.
💡cosa-hai-fattoDopo ogni modifica ti spiega in parole semplici cosa ha fatto Claude e come provarlo.
⛽benzinaVedi quanto ti resta del piano Claude prima che si fermi, e quando si ricarica.
⚡quick-repliesPulsanti per le risposte che scrivi sempre: "Continua", "Procedi", "Non funziona, controlla tu".
🌐localhostVedi le app che hai acceso sul computer, aprile nel browser o spegnile.

Puoi installarne una sola, alcune o tutte e cinque: sono indipendenti.


Indice


Cos'è una mod

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:

  • sopra il prompt (la "banda"): benzina, cosa-hai-fatto e quick-replies scrivono lì;
  • in un pannello che si apre con un comando: /salvataggi (save-point) e /localhost.

Cosa ti serve

  1. Claude Code 2.1.287 o più recente. Per controllare, nel terminale: `` claude --version ` Se è più vecchio, aggiornalo con: ` claude update ``
  2. Il terminale. Le mod si installano dal terminale; una volta installate funzionano anche nella scheda Code dell'app desktop di Claude.
  3. Solo per save-point: git. Sul Mac di solito c'è già. Se manca, nel terminale: xcode-select --install. Su Linux: installa il pacchetto git.

Installazione passo passo

  1. Apri il terminale ed entra nella cartella di un tuo progetto, poi avvia Claude Code: `` cd percorso/del/tuo/progetto claude ``
  2. Nel punto dove scrivi a Claude, incolla una riga per ogni mod che vuoi e premi Invio: `` /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 ``
  3. La prima volta Claude Code chiede Add marketplace?: rispondi y.
  4. Poi ti chiede dove installarla: lascia la prima opzione (il tuo utente) e premi Invio. Così la mod funziona in tutti i tuoi progetti.
  5. Per quick-replies e cosa-hai-fatto compare anche una schermata con le impostazioni: puoi lasciare quelle predefinite e premere Invio.
  6. Vedrai 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

Le mod una per una

💾 save-point

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:

ComandoCosa succede
/salvataggiApre 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.
/salvaSalva adesso, a mano.
/salva funziona il loginSalva 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:

  • Puoi sempre annullare. Prima di tornare indietro, save-point salva com'era il progetto in quel momento ("Com'era prima di tornare a…"). Se cambi idea, torni a quel salvataggio.
  • Claude viene avvisato. Dopo un ritorno indietro, save-point dice a Claude che i file sono cambiati, così non lavora su una versione che non c'è più.
  • Non tocca il tuo git. Se usi già git o GitHub, i tuoi commit e branch restano come sono: i salvataggi stanno in una cartella separata, ~/.claude/save-points/.
  • Cosa non salva: cartelle pesanti che si rigenerano da sole (node_modules, .venv, venv, __pycache__, .next, dist, build, target, .cache), i file .log e i file che il tuo progetto ignora già con .gitignore.
  • Non salva mai la tua home intera né la radice del disco: se avvii Claude lì, save-point non fa niente e te lo dice. Avvia Claude dentro la cartella del progetto.
  • Spazio su disco: i salvataggi sono compressi e i file uguali non vengono duplicati. Se un giorno vuoi liberare spazio, puoi cancellare la cartella ~/.claude/save-points/.

💡 cosa-hai-fatto

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 ]
  • Spiegamelo meglio chiede a Claude di spiegarti con calma cosa ha cambiato, perché e come verificarlo.
  • Nascondi toglie il riassunto fino alla prossima modifica.
  • Se Claude ha solo risposto a una domanda senza toccare file, non compare niente.

Da sapere:

  • Il riassunto lo scrive Claude Haiku, il modello più piccolo e veloce: dopo ogni risposta che modifica file fa una chiamata breve. Consuma poco, ma conta sul tuo piano.
  • Lingua: italiano di default. Puoi cambiarla dalle impostazioni (es. english, español).

⛽ benzina

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%
  • 5 ore: quanto ti resta del limite che si ricarica ogni cinque ore, e a che ora si ricarica.
  • Settimana: quanto ti resta del limite settimanale.
  • Chat: quanto spazio resta nella conversazione attuale.

Quando serve ti dà un consiglio in chiaro:

  • "Quasi a secco: tra poco Claude si ferma fino alle 16:40."
  • "La chat è lunga e Claude inizia a dimenticare: se passi a un'altra cosa, scrivi /clear."

Ti avvisa anche con una notifica quando superi l'80% e il 95% di un limite.

ComandoCosa succede
/benzinaNasconde 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.


⚡ quick-replies

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

🌐 localhost

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 ]
  • apri apre l'app nel browser.
  • ferma la spegne.
  • In verde quelle del progetto in cui sei.
  • L'elenco si aggiorna da solo quando Claude avvia o ferma qualcosa.

Da sapere: funziona su Mac e Linux.


Impostazioni

Due mod hanno impostazioni:

ModImpostazioneDefault
quick-repliesRisposte rapide (separate da `\`)`Continua\Procedi\Ok a tutte le tue raccomandazioni\Non funziona, controlla tu e sistemalo`
cosa-hai-fattoLingua delle spiegazioniitaliano

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.


Aggiornare, disattivare, disinstallare

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.


Domande frequenti

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.


Problemi comuni

Cosa vediCosa fare
Marketplace file not foundControlla di aver scritto bene cavallinilorenzo/claude-code-mods.
Plugin "…" not found in marketplaceControlla 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 homeHai avviato Claude nella tua home. Chiudi, entra nella cartella del progetto (cd …) e riavvia claude.
save-point dice che serve gitMac: xcode-select --install. Linux: installa il pacchetto git.
benzina mostra solo la ChatUsi una chiave API invece di un abbonamento, oppure Claude non ha ancora risposto in questa sessione.
La banda sopra il prompt non si vedeSi può ridurre con [-] o ctrl+x ctrl+a: premi di nuovo per riaprirla.
Una mod non fa nienteAggiorna Claude Code (claude update) e controlla con claude plugin list che sia attiva.

Per chi vuole modificarle

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.


Licenza

MIT: puoi usarle, modificarle e ricondividerle, mantenendo la nota di copyright.

Fatto da Lorenzo Cavallini.

Source 3 files
hooks/register.tsx 149 lines
1import { 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}
149
hooks/recap.ts 52 lines
1import 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}
52
types/index.d.ts 18 lines
1export 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