Bozze da Markdown
Titoli, grassetto, corsivo, liste annidate, citazioni, codice e link diventano il corpo di una bozza online. Quello che non supporta (HTML grezzo, tabelle) lo rifiuta con un errore, senza perdere testo in silenzio.
Riga di comando · beta · non ufficiale
Crea bozze da Markdown, metti in coda le note e programmale, e se vuoi fatti scrivere una prima stesura da un LLM, anche locale. Pubblicare è sempre un tuo sì esplicito.
$ substack article draft post.md
Bozza creata: id 123456789
$ substack article publish 123456789
Errore: Pubblicazione non interattiva: aggiungi
--yes per confermare esplicitamente
$ substack note add "Una nota breve"
$ substack note schedule a1b2c3d4e5f6 \
--at 2026-10-09T09:00:00+02:00
$ substack notes run-due # cron / k3s
Senza --yes il comando si ferma prima di toccare la rete.
Substack CLI è un programma da riga di comando che porta la scrittura di una newsletter fuori dal browser: scrivi in Markdown con il tuo editor, il CLI crea la bozza online, tu la rivedi nell'editor di Substack e la pubblichi quando decidi. Le note brevi vivono in una coda locale e partono a mano o a orario.
Titoli, grassetto, corsivo, liste annidate, citazioni, codice e link diventano il corpo di una bozza online. Quello che non supporta (HTML grezzo, tabelle) lo rifiuta con un errore, senza perdere testo in silenzio.
Le note vivono in locale come bozze. Le pubblichi a mano o le programmi con un orario: notes run-due pensa al resto. Una nota dall'esito incerto non viene mai ripubblicata.
Anthropic oppure qualunque server compatibile OpenAI (llama.cpp, Ollama, vLLM, LM Studio). Il testo generato diventa solo una bozza o una nota in coda: non pubblica mai nulla.
Un'immagine Docker, i manifest per k3s (CronJob, PVC, Secret) e un timer systemd. Tre sole dipendenze di runtime: marked, yaml, zod.
git clone https://github.com/fullo/substack-cli.git
cd substack-cli
npm run setup
Serve Node 22.18 o successivo. npm run setup installa le dipendenze, compila e lancia npm link, che registra il comando substack come collegamento simbolico al repository: ogni npm run build successivo è subito attivo. Se la cartella globale di npm non è scrivibile dal tuo utente, usa un gestore di versioni come nvm oppure un prefisso nella tua home, non sudo. Senza link il comando è node dist/cli/main.js. Per toglierlo: npm unlink -g substack-cli.
substack config init --publication la-tua-newsletter --lang it
È la parte prima di .substack.com. Con substack config show vedi la configurazione effettiva. I messaggi sono in inglese di default: --lang it li porta in italiano, comprese le parole di conferma. In alternativa c'è la variabile SUBSTACK_LANG=it, che ha la precedenza sul file di configurazione.
substack auth guide # istruzioni passo passo
substack auth set # il valore non viene mostrato
substack auth check # verifica che la sessione sia valida
Substack non ha API key: il CLI usa il cookie substack.sid del tuo account. Trattalo come una password e non incollarlo in chat o in file versionati. In alternativa puoi passarlo con la variabile SUBSTACK_SID.
---
title: Il titolo dell'articolo
subtitle: Un sottotitolo, se ti serve
---
## Un titolo di sezione
Testo con **grassetto**, *corsivo* e un [link](https://example.com).
- una lista
- con due voci
substack article draft post.md --dry-run # prova, senza inviare nulla
substack article draft post.md # crea la bozza online
substack article list # elenca le bozze
Il file inizia con un front-matter: title è obbligatorio, subtitle facoltativo. Puoi anche passare il testo da un altro programma con cat post.md | substack article draft -.
substack article list --status published # id, titolo, link
substack article list --status published --limit 50 --page 2 # pagine da 10, 25, 50 o 100
substack article list --search "green coding" # cerca tra gli articoli pubblicati
substack article export 123456789 --out post.md # un articolo come Markdown
Ogni comando fa una sola richiesta e non scorre le pagine da solo. export scrive Markdown con front-matter; embed e widget senza forma Markdown diventano un commento e un avviso. Un «scarica tutto» non c'è, di proposito..
substack article update 123456789 --title "Un titolo migliore" --dry-run
substack article update 123456789 --title "Un titolo migliore"
# Scrivi "aggiorna" per confermare:
Vengono inviati solo i campi che cambiano, e gli articoli già pubblicati restano intatti. Con --file il file Markdown sostituisce corpo, titolo e sottotitolo: provalo prima su una bozza di prova, perché sovrascrive le modifiche fatte nell'editor web.
substack image upload copertina.png # carica e stampa l'URL
substack article draft post.md --cover copertina.png # carica, crea la bozza, imposta la copertina
substack article update 123456789 --cover copertina.png --yes # imposta la copertina su una bozza esistente
substack article update 123456789 --cover https://substack-post-media.s3.amazonaws.com/... --yes # oppure un URL già caricato
Formati accettati: PNG, JPEG, GIF e WebP, fino a 10 MB. Con article draft --cover l'immagine si carica prima di creare la bozza; se poi l'impostazione della copertina fallisce, il messaggio riporta l'id della bozza e il comando per ripetere solo la copertina. Gli endpoint di caricamento e copertina non sono ancora confermati: dopo l'aggiornamento il CLI rilegge la bozza e, se la copertina non risulta impostata, esce con codice 3.
substack article publish 123456789
# Scrivi "pubblica" per confermare:
substack article publish 123456789 --yes # da script
substack article publish 123456789 --yes --send-email # anche l'email agli iscritti
Da terminale ti viene chiesta una conferma digitata; da uno script serve --yes, altrimenti il comando si ferma subito. L'email agli iscritti parte solo con --send-email.
substack article schedule 123456789 --at 2026-10-09T09:00:00+02:00
substack article schedule 123456789 --at 2026-10-09T09:00:00+02:00 --email only_free
substack article schedule 123456789 --cancel
Con la programmazione è diverso: Substack non ha l'opzione «nessuna email», quindi con article schedule l'email parte sempre. Scegli i destinatari con --email (everyone di default, only_free, only_paid o founding); compaiono nel riepilogo prima della conferma.
substack note add "Una nota breve con un [link](https://example.com)"
substack note list --status draft
substack note schedule a1b2c3d4e5f6 --at 2026-10-09T09:00:00+02:00
substack note publish a1b2c3d4e5f6 # subito, a mano
substack note unschedule a1b2c3d4e5f6 # torna in bozza
Le date vogliono sempre il fuso: +02:00 oppure Z per UTC. A orario le note vengono pubblicate da substack notes run-due, pensato per girare ogni minuto (vedi «Su una macchina sempre accesa»).
# con un server locale, per esempio llama.cpp
llama-server -m modello.gguf --port 8080
substack config init --publication la-tua-newsletter --force \
--provider openai-compat --llm-base-url http://localhost:8080 --model locale
substack generate article --topic "Cos'è il green coding" --draft
substack generate note --topic "Un consiglio sul green coding"
Con --provider anthropic si usa invece l'API di Anthropic con la variabile ANTHROPIC_API_KEY. Il testo viene generato nella lingua del CLI, salvo --lang. L'articolo generato viene salvato in locale e, con --draft, diventa una bozza online; la nota va nella coda. Non viene pubblicato niente.
docker build -f deploy/Dockerfile -t substack-cli:0.1.0 .
docker save substack-cli:0.1.0 | sudo k3s ctr images import -
kubectl apply -f deploy/k3s/namespace.yaml -f deploy/k3s/pvc.yaml
read -rs SID && kubectl -n substack create secret generic substack-secrets \
--from-literal=SUBSTACK_SID="$SID"; unset SID
# in deploy/k3s/configmap.yaml sostituisci SOSTITUISCI-SUBDOMAIN con il tuo
kubectl apply -f deploy/k3s/configmap.yaml -f deploy/k3s/cronjob.yaml
Il CronJob esegue notes run-due ogni minuto. In alternativa, su una VM Linux, usa il timer systemd in deploy/systemd. Quando il cookie scade il comando esce con codice 2 e il Job risulta fallito, così te ne accorgi.
auth guide · set · check · loginOttieni, salva e verifica il cookie (login apre un browser, con Playwright installato a parte).config init · showCrea e mostra la configurazione (--lang en|it).article draft <file|->Crea una bozza da Markdown. --cover, --dry-run.article listElenca le bozze, o gli articoli pubblicati con --status published (id, titolo, link). --limit 10|25|50|100, --page, --search.article export <id>Scarica un articolo come Markdown con front-matter. --out, --force.article update <id>Modifica una bozza non pubblicata. --title, --subtitle, --file, --cover, --yes, --dry-run.article publish <id>Pubblica una bozza. --yes, --send-email, --dry-run.article schedule <id>Programma con lo scheduler di Substack (--at) o annulla (--cancel). --email sceglie i destinatari, --yes, --dry-run.image upload <file>Carica un'immagine (PNG, JPEG, GIF, WebP, max 10 MB) e stampa l'URL.note add <testo|->Aggiunge una nota alla coda locale.note list · schedule · unscheduleElenca (--status), programma (--at) o rimette in bozza.note publish · resolvePubblica subito; resolve --published|--retry chiude una nota rimasta in dubbio.notes run-duePubblica le note programmate scadute (cron, systemd, k3s).generate article · noteGenera il testo con un LLM (--topic, --lang, --provider, --draft).Quasi tutti i comandi accettano --json per un output leggibile da altri programmi.
Quattro modi di usarlo, dal più semplice al più automatico.
Scrivi l'articolo come file Markdown, magari in un repository Git insieme agli altri. Quando è pronto lo mandi a Substack e lo rivedi nel loro editor.
substack article draft post.md --dry-run
substack article draft post.md
# apri il link, rivedi, poi:
substack article publish 123456789
Il testo lo prepara un assistente (per esempio un assistente di codice nel terminale) e il CLI lo trasforma in bozza passandolo da stdin. Tu decidi se e quando pubblicare.
cat bozza-dell-assistente.md | substack article draft -
Prepari in una volta le note e fissi gli orari. Un timer sulla tua macchina le pubblica al momento giusto; se qualcosa va storto (cookie scaduto, Substack che non risponde) lo vedi come errore, e una nota dubbia non viene mai ripubblicata.
substack note add "Lunedì: nuova puntata online"
substack note schedule a1b2c3d4e5f6 --at 2026-10-12T09:00:00+02:00
substack notes run-due # ogni minuto, da cron
Un modello che gira sul tuo computer (llama.cpp, Ollama…) scrive una prima stesura sul tema che indichi. Il risultato diventa una bozza online da rifinire: niente esce senza la tua revisione.
substack generate article \
--topic "Cos'è il green coding" --draft
Il progetto è nato con una domanda: come impedire che uno script pubblichi, o mandi un'email a tutti gli iscritti, quando non doveva? Le risposte:
--yes quando non c'è un terminale, oppure una parola digitata a mano. Senza terminale il controllo avviene prima di qualsiasi richiesta di rete.--send-email. Di default non si invia nulla.substack.com. La chiave Anthropic va solo ad api.anthropic.com.http, https, mailto); niente HTML grezzo; caratteri invisibili e di controllo rifiutati.marked, yaml, zod.npm audit).