Quando Claude chiude con una proposta o una domanda di conferma, mostra il passaggio e i bottoni Sì, procedi e No, fermati. /conferma lo accende e lo spegne

Un marketplace di mod per Claude Code, pensato per capire e controllare una sessione: barra della cache e dei consumi, registro di ciò che viene scritto fuori dalla repo, conferma delle proposte, raccolta degli inciampi, modalità rec per le registrazioni e inbox con bozze, etichette e task. Ogni mod è un plugin indipendente, installabile singolarmente con un comando.
Ogni mod si accende e si spegne con un comando /..., che funziona in terminale, nell'app Desktop e da iPad. Lo stato si ricorda tra una sessione e l'altra.
⏳ barra-cache (/barra) — tre righe sopra il prompt. La prima è la memoria della chat: una barra con i token del contesto occupato e quanto è costata la sessione a listino (verde fino al 60%, gialla fino all'80%, rossa oltre, con il suggerimento Nuova chat). La seconda mostra l'*uso delle 5 ore* e l'*uso della settimana* dell'abbonamento, solo dove il motore riporta le cifre. La terza dice da quanto è aperta la sessione e quanto resta di cache calda: la prompt cache dura un'ora dall'ultima richiesta della conversazione principale (le richieste dei subagent non la rinnovano), poi la richiesta successiva riscrive tutto il contesto a prezzo pieno. Con la cache scaduta e almeno 20mila token di contesto compare un avviso rosso. Sotto, una riga con branch, file modificati e commit da pubblicare (letta da git, solo dove $.process esiste: nel cloud non compare) e i bottoni: Commit e push (tasto g), Push (tasto p, compare solo quando ci sono commit da pubblicare e nessuna modifica da committare, senza token) e Handoff (tasto n), che fa scrivere al modello un riassunto di ripartenza in ~/.claude/handoffs/, svuota la chat con /clear e riparte da quel file. Dove la banda non viene disegnata (ad esempio l'app su iPad) restano /cache, che mostra minuti rimasti, contesto, costo dell'ultimo turno e quota di cache letta, /push e /handoff (alias /nuova). I comandi sono anche file statici in barra-cache/commands/, perché l'elenco dello slash dell'app non mostra quelli registrati a runtime; l'hook risponde prima del modello, quindi non costano token.
📒 scritture-esterne (/scritture) — a fine turno elenca tutto ciò che il turno ha scritto fuori dalla repo: Notion, Postpickr, Spreaker, Gmail, Calendar, Drive, git push, gh, curl -X POST e gli script con --applica o --elimina. Ogni riga ha servizio, azione, bersaglio, un link apri se la risposta ne contiene uno, e una croce rossa se la chiamata è fallita o negata. Le letture non compaiono. Il registro sparisce all'inizio del turno successivo. Riconosce dal nome i connettori più comuni; per quelli con id opaco serve l'opzione servizi del plugin.
✅ conferma-proposte (/conferma) — quando l'ultima risposta di Claude chiude con una proposta o una domanda di conferma (una domanda, oppure formule come "procedo", "vuoi che", "confermi"), mostra sopra il prompt il passaggio che propone e due bottoni: Sì, procedi (tasto s) e No, fermati (tasto x). Il bottone invia la risposta come se l'avessi scritta tu. Non usa il modello, quindi non consuma token. Funziona anche nel cloud e da iPad.
🛠 correggi (/correggi) — durante la sessione annota gli inciampi: tool che falliscono, azioni negate dal sistema di permessi e le tue correzioni ("non vedo", "hai sbagliato", "riprova"). Con almeno due inciampi compare una riga con il conteggio, le skill usate e il bottone Proponi correzione (tasto l), che chiede a Claude di individuare la causa e proporre la modifica esatta alla skill o al file di istruzioni, senza applicarla. /correggi elenco mostra gli inciampi raccolti. Consuma token solo quando premi il bottone.
🔴 rec (/rec) — la modalità per registrare un video o lavorare in una sessione live con ospiti. Maschera a schermo chiavi e valori dei .env, email, nomi, telefoni, indirizzi, codice fiscale, partita IVA, IBAN, importi in euro e cifre vicino a parole come fatturato, margine, compenso, preventivo. I risultati di posta, chat, task, file, calendario, Notion e strumenti di pagamento si disegnano nascosti. Claude continua a lavorare sui dati reali: cambia solo ciò che si vede (se modifica un file da 29 a 39 euro, il file cambia davvero, lo schermo no). Tiene chiusi i file privati (.env, credenziali, fatture, contratti, preventivi, buste paga) e gli strumenti di pagamento, e ogni prompt porta una nota nascosta che chiede a Claude di usare segnaposto al posto di nomi e cifre. Un ● REC rosso sopra il prompt e nel piè di pagina ricorda che è acceso. /rec alterna, /rec rigoroso maschera anche ogni cifra grande, /rec off spegne, /rec config crea ~/.claude/mods-data/rec/config.json per il tuo nome (nomiVisibili), le persone da nascondere (nomiNascosti), le cartelle private (percorsiPrivati) e gli strumenti extra (strumentiAffari, strumentiChiusi). Limiti: cambia ciò che è disegnato, non ciò che è memorizzato; il riconoscimento per pattern non prende tutto quello che è scritto a parole, quindi riguarda il girato prima di pubblicarlo; i titoli delle chat nella barra laterale dell'app Desktop non si possono mascherare. Adattata da recording-mode di Nate Herk (MIT, vedi rec/NOTICE.md).
📬 posta (posta o /posta) — apre un pannello con la inbox Gmail: l'elenco delle ultime 12 mail per account (una finestra di 5 righe che scorre con j e k), i bottoni e, sotto, il testo intero della mail selezionata (tutti i messaggi del thread, dal più recente, con allegati), letto da Gmail senza modello. I bottoni sono Bozza (b), Label (l), Bozza+Label (m), Task (t), più Aggiorna (r) e Chiudi (x). Le quattro azioni girano dentro la mod, senza subagent: il modello riceve il thread senza alcun tool e sceglie solo i contenuti, mentre le scritture su Gmail e Notion le fa il codice dopo averle validate (la chat resta pulita: l'esito compare nel pannello e in un avviso). Bozza scrive con le regole di ~/.claude/mods-data/posta/sistematore.md e crea una bozza di risposta (la mail non viene mai inviata). Label fa scegliere al modello economico un'etichetta tra quelle esistenti e la applica solo se il nome coincide (non ne crea). Task fa proporre al modello economico titolo, scadenza, urgenza, importanza, impegno e contesto, li riporta ai valori ammessi (scadenza non valida o passata: domani alle 09:00) e crea la pagina nel database Tasks di Notion con il link alla mail. Solo Label e Bozza+Label tolgono la mail dall'inbox (finisce nella cartella dell'etichetta); Bozza e Task la lasciano in inbox. I lavori in corso mostrano uno spinner, quelli riusciti spariscono dopo 8 secondi e quelli falliti si rilanciano con Riprova (y). Nel dettaglio compare l'ultimo messaggio del thread; i precedenti sono righe ripiegate e si aprono con Mostra precedenti (p). Scrivendo posta senza barra il pannello si apre senza avviso dell'app e senza token; posta off lo chiude. Gli account sono in DEFAULT_ACCOUNTS e si sostituiscono con la chiave accounts dello store. In modalità Auto anche le chiamate della mod passano dal classificatore, che sulle scritture non dà verdetto: vanno autorizzate in permissions.allow le letture Gmail (search_threads, get_thread, list_labels) e le quattro scritture (create_draft, label_thread, unlabel_thread di Gmail, notion-create-pages di Notion), con il prefisso mcp__<id connettore>__. Un errore resta visibile solo sulla mail a cui appartiene e sparisce quando la stessa azione riesce.
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/: leggilo prima dell'installazione.
claude plugin marketplace add andreabrugnoli/mods
claude plugin install barra-cache@andrea-mods
claude plugin install scritture-esterne@andrea-mods
claude plugin install conferma-proposte@andrea-mods
claude plugin install correggi@andrea-mods
claude plugin install rec@andrea-mods
claude plugin install posta@andrea-mods
claude.Verifica: apri /plugin e controlla che le mod risultino attive. Per provare una mod in una sola sessione senza installarla, clona la repo e avvia claude --plugin-dir ./mods/barra-cache. Per disattivarla, usa il tab Installed di /plugin.
Le sessioni cloud (claude.ai/code, app per iPad) non leggono le tue impostazioni locali: partono da un contenitore pulito e caricano i plugin dichiarati nella repo su cui lavori. Per attivare una mod in una repo, aggiungi a .claude/settings.json di quella repo:
{
"extraKnownMarketplaces": {
"andrea-mods": { "source": { "source": "github", "repo": "andreabrugnoli/mods" } }
},
"enabledPlugins": { "barra-cache@andrea-mods": true, "rec@andrea-mods": true }
}
Lo fa per te scripts/abilita-cloud.sh <percorso-repo> [mod ...], che unisce la voce alle impostazioni esistenti. Se il tuo gitignore (anche globale) esclude .claude/settings.json, aggiungilo con git add -f .claude/settings.json, poi commit e push. La sessione cloud legge il file dal branch.
Su iPad la banda sopra il prompt può non essere disegnata: restano /cache, /push e /nuova, e tutti i comandi on/off (/barra, /scritture, /conferma, /correggi, /rec) funzionano uguale. Lo stato ($.store) e i riassunti in ~/.claude/handoffs/ vivono nel contenitore della sessione e si perdono alla sua chiusura.
Accendere e spegnere (senza argomento alternano; accettano anche on e off):
/barra: la barra sopra il prompt./scritture: il registro delle scritture esterne./conferma: i bottoni Sì e No sulle proposte./correggi: la raccolta degli inciampi (/correggi elenco li mostra)./rec: la modalità registrazione (/rec rigoroso, /rec off, /rec config).posta o /posta: la inbox con i bottoni Bozza, Label e Task (posta aggiorna, posta off).Altri comandi di barra-cache:
/cache: minuti di cache rimasti, contesto occupato, costo dell'ultimo turno e quota di cache letta./push: pubblica i commit del branch corrente./nuova: riassume la sessione in un file e riparte da una chat pulita.Durata della cache in barra-cache: parte da un'ora (TTL_MS) e si corregge da sola. Dopo una pausa di almeno 6 minuti guarda la quota di cache letta: se la cache è stata riscritta, passa a 5 minuti e lo ricorda tra le sessioni; se è stata letta, conferma l'ora.
Soglie dei colori in barra-cache: CONTEXT_WARN, CONTEXT_BAD, COST_WARN, CACHE_OK e CACHE_BAD, in cima allo stesso file.
Nomi dei connettori in scritture-esterne: opzione servizi del plugin.
Per modificare una mod, clona la repo e caricala con claude --plugin-dir: ogni salvataggio ricarica il modulo nella sessione aperta.
mods/
├── .claude-plugin/marketplace.json # elenco delle mod installabili
├── barra-cache/
├── scritture-esterne/
├── conferma-proposte/
├── correggi/
├── rec/
├── posta/
└── scripts/abilita-cloud.sh
Ogni cartella di mod è un plugin completo: .claude-plugin/plugin.json, hooks/hooks.json, hooks/register.js e tests/ (eseguibili con claude plugin test ./<mod>). claude plugin validate ./<mod> elenca gli eventi intercettati e le chiamate al motore.
Licenza: MIT.
hooks/register.js 100 lines1// Il testo che chiude una proposta: una domanda, o una formula tipica di richiesta di conferma
2const PROPOSAL = /(\?\s*[)"»]?\s*$)|(procedo|procediamo|vuoi che|ti va( bene)?|confermi|lo faccio|vado avanti|dimmi se)/i
3
4// La proposta in attesa di risposta: { text } finché non arriva un prompt
5let proposal = null
6
7// Accorcia un testo su una riga
8function clip(text, n) {
9 const one = String(text).replace(/\s+/g, ' ').trim()
10 return one.length > n ? one.slice(0, n - 1) + '…' : one
11}
12
13// Dall'ultima risposta di Claude al passaggio che propone, oppure null se non propone niente
14export function findProposal(answer) {
15 const paragraphs = String(answer ?? '').split(/\n\s*\n/).map((p) => p.trim()).filter(Boolean)
16 const last = paragraphs[paragraphs.length - 1]
17 return last && PROPOSAL.test(last) ? last : null
18}
19
20// Il nuovo stato dopo un comando: "on" accende, "off" spegne, senza argomento alterna
21export function parseToggle(args, current) {
22 const arg = String(args ?? '').trim().toLowerCase()
23 if (/^(on|acceso|attiva|si|sì)$/.test(arg)) return true
24 if (/^(off|spento|disattiva|no)$/.test(arg)) return false
25 return !current
26}
27
28// Acceso o spento: lo decide /conferma e resta nello store tra una sessione e l'altra
29let enabled = true
30
31async function runConferma($, e) {
32 enabled = parseToggle(e.args, enabled)
33 proposal = null
34 try {
35 await $.store.set('attiva', enabled)
36 } catch {}
37 $.ui.invalidate('ui.render')
38 return { text: 'conferma-proposte · ' + (enabled ? 'acceso' : 'spento') }
39}
40
41export function register(on) {
42 on('session.start', async ($, e, next) => {
43 try {
44 if ((await $.store.get('attiva')) === false) enabled = false
45 } catch {}
46 await $.command.register({ name: 'conferma', description: 'Accende o spegne i bottoni Sì e No sulle proposte', argumentHint: '[on|off]', immediate: true })
47 return next(e)
48 })
49
50 on('command.run', { command: 'conferma' }, runConferma)
51 // Il comando statico del plugin si chiama anche conferma-proposte:conferma
52 on('command.run', { command: 'conferma-proposte:conferma' }, runConferma)
53
54 // Un nuovo prompt, scritto da te o inviato da un bottone: la proposta non vale più
55 on('prompt.submit', ($, e, next) => {
56 if (proposal) {
57 proposal = null
58 $.ui.invalidate('ui.render')
59 }
60 return next(e)
61 })
62
63 // A fine turno, solo la conversazione principale e solo se Claude ha davvero risposto
64 on('turn.complete', ($, e, next) => {
65 if (enabled && !e.agentId && e.reason === 'answer') {
66 const found = findProposal(e.answer)
67 proposal = found ? { text: found } : null
68 $.ui.invalidate('ui.render')
69 }
70 return next(e)
71 })
72
73 on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
74 const theirs = await next(e)
75 if (!enabled || !proposal || e.props.isWorking) return theirs
76 const { Box, Text, Button } = $.ui.resolve(e)
77 // Il bottone toglie la proposta e invia la risposta come se l'avessi scritta tu
78 const reply = (text) => () => {
79 proposal = null
80 $.ui.invalidate('ui.render')
81 return $.prompt.submit({ text, asUser: true })
82 }
83 return Box({
84 flexDirection: 'column',
85 children: [
86 Text({ dimColor: true, children: ['Claude propone · ' + clip(proposal.text, 220)] }),
87 Box({
88 flexDirection: 'row',
89 children: [
90 Button({ key: 'si', label: 'Sì, procedi', hotkey: 's', variant: 'primary', onPress: reply('Sì, procedi.') }),
91 Text({ children: [' '] }),
92 Button({ key: 'no', label: 'No, fermati', hotkey: 'x', onPress: reply('No, fermati qui e aspetta le mie istruzioni.') }),
93 ],
94 }),
95 theirs,
96 ].filter(Boolean),
97 })
98 })
99}
100