Vedi e chiudi i server localhost (vite, next, django, uvicorn...) lasciati accesi: /localhost apre il pannello.

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 113 lines1import { atom, read, update } from 'claude-code'
2import type { EngineInterface, Register } from 'claude-code'
3
4import type { LocalhostServer } from '../types'
5import { parseCwds, parseListeners, shortPath, toServers } from './servers'
6
7const PANE = 'localhost'
8const servers = atom({ plugin: 'localhost', key: 'servers' } as const, [])
9
10async function scan($: EngineInterface): Promise<LocalhostServer[]> {
11 const listening = await $.process.run(['lsof', '-nP', '-iTCP', '-sTCP:LISTEN', '-F', 'pcn'])
12 const listeners = parseListeners(listening.stdout)
13 if (listeners.length === 0) {
14 return []
15 }
16
17 const pids = [...new Set(listeners.map(listener => listener.pid))].join(',')
18 const where = await $.process.run(['lsof', '-a', '-d', 'cwd', '-F', 'pn', '-p', pids])
19
20 return toServers(listeners, parseCwds(where.stdout), await $.session.cwd())
21}
22
23async function refresh($: EngineInterface): Promise<LocalhostServer[]> {
24 const found = await scan($).catch(() => [] as LocalhostServer[])
25 await update($, servers, () => found)
26
27 return found
28}
29
30async function stop($: EngineInterface, pids: number[]): Promise<void> {
31 if (pids.length > 0) {
32 await $.process.run(['kill', ...pids.map(String)]).catch(() => undefined)
33 await $.clock.sleep(600)
34 }
35 await refresh($)
36}
37
38export const register: Register = on => {
39 on('session.start', async ($, e, next) => {
40 await $.command.register({
41 name: 'localhost',
42 description: 'Mostra i server localhost accesi, con i pulsanti per aprirli o fermarli',
43 })
44
45 return next(e)
46 })
47
48 on('command.run', { command: 'localhost' }, async $ => {
49 const found = await refresh($)
50 await $.ui.open({ id: PANE, title: 'localhost', focus: true, closeOnEscape: true })
51
52 return { text: `${found.length} server localhost in ascolto.` }
53 })
54
55 // A dev server is usually started or stopped by a Bash call, so look again right after one.
56 on('tool.call', { tool: 'Bash' }, async ($, e, next) => {
57 const ran = await next(e)
58 void refresh($)
59
60 return ran
61 })
62
63 on('ui.render', { component: 'Pane', requestId: PANE }, async ($, e) => {
64 const { Box, Button, Link, Text } = $.ui.resolve(e)
65 const list = await read($, servers)
66 const home = await $.env.get('HOME')
67 const here = list.filter(server => server.isHere)
68
69 return (
70 <Box flexDirection="column">
71 {list.length === 0 && <Text dimColor>Nessun server localhost acceso.</Text>}
72 {list.map(server => (
73 <Box key={`row-${server.pid}-${server.port}`} flexDirection="row" columnGap={1}>
74 <Text bold color={server.isHere ? 'success' : undefined}>
75 :{server.port}
76 </Text>
77 <Text dimColor>{server.command}</Text>
78 <Box flexGrow={1}>
79 <Text wrap="truncate-start" dimColor={!server.isHere}>
80 {shortPath(server.cwd, home)}
81 </Text>
82 </Box>
83 <Link href={`http://localhost:${server.port}`} label="apri" />
84 <Button
85 key={`stop-${server.pid}-${server.port}`}
86 label="ferma"
87 onPress={() => stop($, [server.pid])}
88 />
89 </Box>
90 ))}
91 <Box flexDirection="row" columnGap={1} marginTop={1}>
92 {here.length > 0 && (
93 <Button
94 key="stop-here"
95 label={`Ferma quelli di questo progetto (${here.length})`}
96 variant="primary"
97 onPress={() => stop($, here.map(server => server.pid))}
98 />
99 )}
100 {list.length > 0 && (
101 <Button
102 key="stop-all"
103 label="Ferma tutti"
104 onPress={() => stop($, list.map(server => server.pid))}
105 />
106 )}
107 <Button key="refresh" label="Aggiorna" onPress={() => void refresh($)} />
108 </Box>
109 </Box>
110 )
111 })
112}
113hooks/servers.ts 65 lines1import type { LocalhostServer } from '../types'
2
3export type Listener = { pid: number; command: string; port: number }
4
5// Reads `lsof -F pcn` output: a `p<pid>` line opens a process, `c<cmd>` names it,
6// each `n<addr>:<port>` is one listening socket of it.
7export function parseListeners(stdout: string): Listener[] {
8 const found = new Map<string, Listener>()
9 let pid = 0
10 let command = ''
11
12 for (const line of stdout.split('\n')) {
13 const field = line[0]
14 const value = line.slice(1)
15
16 if (field === 'p') {
17 pid = Number(value)
18 } else if (field === 'c') {
19 command = value
20 } else if (field === 'n') {
21 const port = Number(value.slice(value.lastIndexOf(':') + 1))
22 if (pid > 0 && Number.isInteger(port) && port > 0) {
23 found.set(`${pid}:${port}`, { pid, command, port })
24 }
25 }
26 }
27
28 return [...found.values()].sort((a, b) => a.port - b.port)
29}
30
31// Reads `lsof -a -d cwd -F pn` output into pid -> working directory.
32export function parseCwds(stdout: string): Map<number, string> {
33 const cwds = new Map<number, string>()
34 let pid = 0
35
36 for (const line of stdout.split('\n')) {
37 if (line[0] === 'p') {
38 pid = Number(line.slice(1))
39 } else if (line[0] === 'n' && pid > 0) {
40 cwds.set(pid, line.slice(1))
41 }
42 }
43
44 return cwds
45}
46
47// Desktop apps and system daemons run from `/`; a dev server runs from a project folder.
48export function toServers(
49 listeners: Listener[],
50 cwds: Map<number, string>,
51 projectDir: string,
52): LocalhostServer[] {
53 return listeners
54 .map(listener => ({ ...listener, cwd: cwds.get(listener.pid) ?? '/' }))
55 .filter(server => server.cwd !== '/')
56 .map(server => ({
57 ...server,
58 isHere: server.cwd === projectDir || server.cwd.startsWith(`${projectDir}/`),
59 }))
60}
61
62export function shortPath(path: string, home: string | undefined): string {
63 return home && path.startsWith(home) ? `~${path.slice(home.length)}` : path
64}
65types/index.d.ts 14 lines1export type LocalhostServer = {
2 pid: number
3 command: string
4 port: number
5 cwd: string
6 isHere: boolean
7}
8
9declare module 'claude-code' {
10 interface PluginState {
11 localhost: { servers: LocalhostServer[] }
12 }
13}
14