SLOPSHOPPER

localhost

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

newpaneguardcommandprocess
A shopper browsing a rack in a slop shop
Preview · a replayed session in a sandbox
claude · ~/work/app · localhost
│ ┃ localhost ✕ › fix the failing auth test and add an audit log call │ ┃ Nessun server localhost acceso. │ ┃ ⏺ Read(src/auth.ts) │ ┃ [ Aggiorna ] ⎿ 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 │ │ › /localhost │ ⎿ localhost: 0 server localhost in ascolto. │ │ ────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── › ? for shortcuts

Draws

Pane · localhost
Nessun server localhost acceso. [ Aggiorna ]
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 113 lines
1import { 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}
113
hooks/servers.ts 65 lines
1import 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}
65
types/index.d.ts 14 lines
1export 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