La maggior parte degli strumenti per sviluppatori nasce allo stesso modo. Si deve fare qualcosa ripetutamente, quindi si scrive uno script. Lo script acquisisce flag. I flag acquisiscono sottocomandi. Prima ancora di accorgersene, si ha una CLI.
Abbiamo vissuto esattamente questa esperienza creando uno strumento di piattaforma interno per aiutare il nostro team solutions a rilasciare più rapidamente. Esegue lo scaffolding dei progetti, avvia ambienti di sviluppo, genera configurazioni e distribuisce in staging.
Con l'ampliarsi dell'ambito della nostra CLI, è cresciuto anche il numero di utenti interni. Poi, però, è successo qualcosa di interessante: all'improvviso non stavamo più costruendo solo per sviluppatori umani, ma anche per coding agent.
Com'è una buona DX
Prima di parlare di agent, parliamo di ciò che rende una CLI davvero piacevole per gli sviluppatori umani.
Spaces prende decisioni nette sulle parti noiose. Sceglie strutture di directory sensate, così non deve farlo Lei. Genera i file di configurazione che altrimenti copierebbe dall'ultimo progetto. Collega i servizi tra loro affinché la Sua API e il Suo frontend possano comunicare fin dal primo giorno, non dopo un'ora di editing YAML o di “andare a intuito”.
Ecco com'è una sessione tipica:
$ spaces init my-project$ cd my-project$ spaces devTre comandi. Si passa dal nulla a un progetto multi-servizio in esecuzione con hot reload, un database e Dockerfile generati. Questo è lo standard.
I comandi che contano tendono a rientrare in tre categorie:
Scaffolding -- Creano struttura, pongono domande, mostrano opzioni, consentono di esplorare.
Sviluppo -- Sono il Suo inner loop. Semplicemente “girano”.
Operations -- Interagiscono con la produzione. Attenzione: controllare due volte prima di agire
Tutto questo è il minimo indispensabile. Poi però è arrivato il nostro secondo utente e abbiamo scoperto che avevamo bisogno di qualcosa di un po' diverso, e molto più interessante.
Il secondo utente: l'agent
Abbiamo creato un selettore di moduli TUI per init. Modifiche eleganti, comandi, un aspetto ottimo.
Poi un agent ha provato a usarlo.
L'agent vedeva codici di escape ANSI grezzi. \\\\x1b[36m?\\\\x1b[0m Seleziona componenti. Non riusciva a inviare tasti freccia né ad attivare o disattivare selezioni. Era completamente escluso dal comando.
A posteriori, la soluzione sembra ovvia: aggiungere un flag --components. Ma l'intuizione reale era più ampia di un singolo flag.
Ogni prompt è un flag mascherato
Ogni volta che la Sua CLI pone una domanda interattiva, esiste un contratto implicito: "Mi serve questa informazione per procedere." Per soddisfare quel contratto, può usare un prompt interattivo. Oppure può usare un flag o un file di configurazione.
Il trucco è pensare prima all'informazione e poi al metodo di input.
def init_command( components: str | None = Option(None), yes: bool = Option(False, "-y"),): if components: selected = components.split(",") elif yes: selected = get_defaults() else: selected = show_picker()
# Same logic from here create_project(selected)Tre percorsi di input, un unico percorso di esecuzione. La logica di business non sa come siano arrivati gli input. Questo significa che la si testa una volta, non tre.
Il flag -y merita un'attenzione particolare. Non significa solo "salta le conferme". È un contratto che dice: Sto fornendo programmaticamente tutto ciò di cui hai bisogno, non bloccare su stdin. Quando si esegue con -y, ogni prompt si risolve in un valore di flag o in un default intelligente. Se non può, fallisce in modo evidente invece di restare bloccato.
Esempio pratico: gli elementi interattivi in questo post.
Gli elementi sono stati creati in un repository nuovo. A un agent è stato poi chiesto retroattivamente di collegare il progetto a [spaces cli] per il deployment. Ha iniziato eseguendo --help sui comandi pertinenti per comprendere l'interfaccia. Da lì ha capito quali file di configurazione servivano, ha generato un config.yaml e ha collegato il Dockerfile e le impostazioni del registry, senza bisogno di assistenza.
Nello stesso passaggio, ha configurato la pipeline CI di GitHub Actions. Da un singolo prompt a un deployment live sono serviti meno di 10 minuti (un cycle time che stiamo lavorando attivamente per ridurre). Dopodiché, il repository era completamente configurato e gli embed erano deployed con Koyeb — come Space, scaffoldato e distribuito tramite Spaces stesso. Sì, le demo interattive incorporate in questo post su Spaces vengono eseguite come Space. Spaception.
Poiché ogni input interattivo aveva un equivalente tramite flag, l'agent poteva operare autonomamente end-to-end.
Dati strutturati come interfaccia
La CLI che il nostro team stava creando aiutava i nostri applied AI engineer a rilasciare app. Ma non tutte le app sono uguali. Alcune richiedono un backend e un database vettoriale. Altre hanno bisogno solo di un database relazionale, un frontend e alcune API. Alcune sono servizi solo worker, senza alcuna UI.
Non avevamo intenzione di hardcodare i tipi di modulo. Così abbiamo creato un sistema di plugin in cui ogni componente è un plugin che dichiara le proprie proprietà:
class ModulePlugin(BaseModel): type_id: str category: str default_port: int
def get_env_vars(self) -> list[EnvVarDef]: ... def get_dev_command(self, port: int) -> str: ...I plugin sono introspezionabili. È possibile elencarli, serializzarli, confrontarli. Un umano sfoglia un selettore TUI. Un agent interroga il registry e riceve JSON. Stessi dati, rendering diverso.
Questo ha risolto accidentalmente un problema che non sapevamo di avere. Prima, aggiungere un nuovo tipo di modulo significava aggiornare il selettore, il generatore di Dockerfile, lo writer dei file env e il template compose. Ora significa scrivere una singola classe plugin. Il registry è la single source of truth e tutto legge da lì.
Insegnare agli agent il Suo progetto
C'è un vecchio detto secondo cui il contenuto è re. Con gli agent, lo è il contesto.
La cosa con il maggiore impatto sull'usabilità per gli agent è stata generare due file a ogni init:
context.json -- uno snapshot strutturato del progetto: quali moduli esistono, quali porte usano, comandi da eseguire, env var necessarie.
AGENTS.md -- un insieme di regole scritte per gli LLM e più imperative rispetto al Suo file abituale. Non "questo progetto usa PostgreSQL", ma "esegui mycli dev --migrate prima di testare modifiche al database".
Un agent che legge questi file prima di agire commette drasticamente meno errori. In pratica, non tirerà a indovinare i numeri di porta, non eseguirà il comando di test sbagliato, non proverà a installare dipendenze già gestite dalla toolchain.
Il file di contesto agisce anche come cache-buster per le supposizioni obsolete dell'agent. Quando si aggiunge un modulo o si modifica un target di deploy, il file di contesto si aggiorna automaticamente al successivo dev o init. L'agent legge ogni volta uno stato aggiornato.
Lo stato implicito è il nemico
Il problema più sottile che abbiamo incontrato è stato lo stato implicito. Il nostro comando add leggeva config.yaml dalla directory di lavoro corrente. Un umano esegue cd nella cartella giusta senza pensarci. Un agent che esegue comandi dalla root di un workspace non ha idea di dover essere in una sottodirectory.
La soluzione:
# Before: CWD dependencyconfig = load_config(Path.cwd() / "config.yaml")
# After: explicit with fallbackconfig = load_config( path or find_config_in_parents(Path.cwd()))Ogni assunzione nascosta -- CWD, variabili d'ambiente, dotfile in $HOME -- è un punto in cui un agent inciamperà. Parametri espliciti con fallback sensati risolvono il problema per gli agent e rendono più facile lo scripting anche per gli umani.
La checklist
A posteriori, le modifiche erano piccole singolarmente. Solo un insieme di principi applicati con coerenza:
Ogni input interattivo ha un equivalente tramite flag
Ogni flag ha un default intelligente per la modalità headless
Lo stato è esplicito. CWD, env var e percorsi di configurazione sono input, non assunzioni
I plugin sono data model, non solo codice. Introspezionabili per default
I file di contesto forniscono agli agent (e alla CI, e agli script) una descrizione strutturata del progetto
Costruire uno strumento migliore per tutti
La parte curiosa è che nulla di tutto questo ha peggiorato la CLI per gli umani. Il selettore TUI funziona ancora e ha ancora un aspetto elegante, gli spinner di avanzamento continuano a girare, le finestre di conferma continuano a confermare. Abbiamo semplicemente aggiunto una seconda porta.
E quella seconda porta si è rivelata la più importante. Non perché gli agent contino più degli umani, ma perché i vincoli che impongono sono gli stessi che rendono una CLI componibile, scriptabile e testabile. Progettare per gli agent ci ha costretto a costruire uno strumento migliore per tutti.
Se sta creando strumenti per sviluppatori in questo momento, non Le serve un'API agent separata. Deve guardare ogni chiamata input(), ogni assunzione sul CWD, ogni output solo pretty-printed, e chiedersi: cosa succede se l'utente dall'altra parte è un processo, non una persona?
La risposta a questa domanda migliorerà comunque il Suo strumento.
Spaces CLI è stata creata da Lorenzo Signoretti, Riwa Hoteit e Sam Fenwick in Mistral AI. Un ringraziamento speciale al nostro team Applied AI, i primi e più esigenti utenti della CLI, il cui uso reale ha plasmato ogni pattern descritto qui. Siamo entusiasti di vedere le applicazioni che li aiuterà a creare con i nostri clienti per risolvere use case complessi.
Il livello di tooling tra umani e agent è ancora in fase di definizione. Se creare strumenti per sviluppatori all'intersezione tra AI e infrastruttura Le sembra ciò che fa per Lei, stiamo assumendo.




