La plupart des outils de développement commencent de la même façon. Vous devez répéter une action, alors vous écrivez un script. Le script gagne des options. Les options gagnent des sous-commandes. Très vite, vous avez une CLI.
Nous avons vécu exactement cela en construisant un outil de plateforme interne pour aider notre équipe solutions à livrer plus vite. Il crée des Projets, lance des environnements de dev, génère des configurations et déploie en staging.
À mesure que notre CLI gagnait en périmètre, le nombre de nos utilisateurs internes augmentait aussi. Puis quelque chose d’intéressant s’est produit : soudain, nous ne construisions plus seulement pour des développeurs humains, mais aussi pour des agents de code.
À quoi ressemble une bonne DX
Avant de parler des agents, parlons de ce qui rend une CLI vraiment agréable pour les développeurs humains.
Spaces assume des choix sur les détails peu intéressants. Il choisit des structures de répertoires raisonnables pour que vous n’ayez pas à le faire. Il génère les fichiers de configuration que vous copieriez sinon depuis le dernier projet. Il connecte les services entre eux pour que votre API et votre frontend communiquent dès le premier jour, pas après une heure à modifier du YAML ou à faire du « vibing ».
Une session typique ressemble à ceci :
$ spaces init my-project$ cd my-project$ spaces devTrois commandes. Vous passez de rien à un projet multiservice en cours d’exécution, avec hot reload, base de données et Dockerfiles générés. C’est le niveau attendu.
Les commandes importantes se répartissent généralement en trois catégories :
Génération de structure -- Elles créent la structure, posent des questions, affichent des options et permettent d’explorer.
Développement -- C’est votre boucle interne. Elles s’exécutent, tout simplement.
Opérations -- Elles touchent à la production. Prudence, vérifiez deux fois avant d’agir
Tout cela est le minimum attendu. Puis notre deuxième utilisateur est arrivé, et nous avons découvert qu’il nous fallait quelque chose d’un peu différent, et de beaucoup plus intéressant.
Le deuxième utilisateur : l’agent
Nous avons construit un sélecteur de modules en TUI pour init. Des changements soignés, des commandes, un résultat très propre.
Puis un agent a essayé de l’utiliser.
L’agent a vu des codes d’échappement ANSI bruts. \\\\x1b[36m?\\\\x1b[0m Select components. Il ne pouvait pas envoyer de touches fléchées ni cocher ou décocher des sélections. La commande lui était entièrement inaccessible.
Avec le recul, la correction paraît évidente : ajouter une option --components. Mais l’enseignement réel dépassait largement cette option.
Chaque prompt est une option déguisée
Chaque fois que votre CLI pose une question interactive, il existe un contrat implicite : « J’ai besoin de cette information pour continuer. » Pour remplir ce contrat, vous pouvez utiliser un prompt interactif. Ou vous pouvez utiliser une option ou un fichier de configuration.
L’astuce consiste à penser d’abord à l’information, puis à la méthode de saisie.
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)Trois chemins d’entrée, un seul chemin d’exécution. La logique métier ne sait pas comment les entrées sont arrivées. Cela signifie que vous la testez une fois, pas trois.
L’option -y mérite une attention particulière. Elle ne signifie pas seulement « ignorer les confirmations ». C’est un contrat qui dit : je fournis tout ce dont vous avez besoin par programme, ne bloquez pas sur stdin. Lorsque vous exécutez avec -y, chaque prompt se résout soit en valeur d’option, soit en valeur par défaut intelligente. Si ce n’est pas possible, la commande échoue clairement au lieu de rester bloquée.
Exemple en pratique : les éléments interactifs de cet article.
Les éléments ont été construits dans un nouveau dépôt. Il a ensuite été demandé à un agent de connecter le projet à [Spaces CLI] pour le déploiement. Il a commencé par exécuter --help sur les commandes pertinentes pour comprendre l’interface. À partir de là, il a identifié les fichiers de configuration nécessaires, généré un config.yaml, puis connecté le Dockerfile et les paramètres de registry, sans accompagnement étape par étape.
Dans le même passage, il a configuré le pipeline CI GitHub Actions. Entre un seul prompt et un déploiement en ligne, il s’est écoulé moins de 10 minutes, un temps de cycle que nous travaillons activement à réduire. Ensuite, le dépôt était entièrement configuré et les embeds étaient déployés avec Koyeb — en tant que Space, généré et déployé via Spaces lui-même. Oui, les démos interactives intégrées dans cet article sur Spaces s’exécutent comme un Space. Spaception.
Comme chaque entrée interactive avait un équivalent sous forme d’option, l’agent pouvait opérer de bout en bout en autonomie.
Les données structurées comme interface
La CLI que notre équipe construisait aidait nos ingénieurs en IA appliquée à livrer des apps. Mais toutes les apps ne se ressemblent pas. Certaines ont besoin d’un backend et d’une base de données vectorielle. D’autres ont seulement besoin d’une base de données relationnelle, d’un frontend et de quelques API. Quelques-unes sont des services worker-only, sans UI du tout.
Nous n’allions pas coder en dur les types de modules. Nous avons donc construit un système de plugins où chaque composant est un plugin qui déclare ses propres propriétés :
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: ...Les plugins sont introspectables. Vous pouvez les lister, les sérialiser, les comparer. Un humain parcourt un sélecteur TUI. Un agent interroge le registre et reçoit du JSON. Mêmes données, rendu différent.
Cela a résolu par accident un problème dont nous n’avions pas conscience. Avant, ajouter un nouveau type de module signifiait mettre à jour le sélecteur, le générateur de Dockerfile, l’écriture du fichier env et le template compose. Maintenant, cela signifie écrire une seule classe de plugin. Le registre est la source de vérité unique, et tout le reste y lit ses données.
Apprendre aux agents à comprendre votre Projet
Un vieux dicton affirme que le contenu est roi. Avec les agents, c’est le contexte.
Ce qui a le plus amélioré l’utilisabilité pour les agents a été de générer deux fichiers à chaque init :
context.json -- un instantané structuré du projet : les modules présents, les ports utilisés, les commandes à exécuter et les variables d’environnement nécessaires.
AGENTS.md -- un ensemble de règles écrites pour les LLMs, plus impératives que votre documentation habituelle. Pas « ce projet utilise PostgreSQL », mais « exécutez mycli dev --migrate avant de tester des modifications de base de données ».
Un agent qui lit ces fichiers avant d’agir fait nettement moins d’erreurs. En pratique, il ne devine pas les numéros de port, n’exécute pas la mauvaise commande de test et n’essaie pas d’installer des dépendances déjà gérées par la toolchain.
Le fichier de contexte évite aussi les hypothèses périmées côté agent. Lorsque vous ajoutez un module ou modifiez une cible de déploiement, le fichier de contexte se met à jour automatiquement au prochain dev ou init. L’agent lit un état à jour à chaque fois.
L’état implicite est l’ennemi
Le problème le plus subtil que nous avons rencontré était l’état implicite. Notre commande add lisait config.yaml depuis le répertoire de travail courant. Un humain fait un cd vers le bon dossier sans y penser. Un agent qui exécute des commandes depuis la racine d’un espace de travail n’a aucun moyen de savoir qu’il doit se trouver dans un sous-répertoire.
La correction :
# Before: CWD dependencyconfig = load_config(Path.cwd() / "config.yaml")
# After: explicit with fallbackconfig = load_config( path or find_config_in_parents(Path.cwd()))Chaque hypothèse cachée -- CWD, variables d’environnement, dotfiles dans $HOME -- est un endroit où un agent peut trébucher. Des paramètres explicites avec des fallbacks raisonnables résolvent le problème pour les agents et simplifient aussi le scripting pour les humains.
La checklist
Avec le recul, chaque changement était petit pris isolément. Il s’agissait simplement d’un ensemble de principes appliqués de manière cohérente :
Chaque entrée interactive a un équivalent sous forme d’option
Chaque option a une valeur par défaut intelligente pour le mode headless
L’état est explicite. CWD, variables d’environnement et chemins de configuration sont des entrées, pas des hypothèses
Les plugins sont des modèles de données, pas seulement du code. Introspectables par défaut
Les fichiers de contexte donnent aux agents, à la CI et aux scripts une description structurée du projet
Construire un meilleur outil pour tous
Ce qui est intéressant, c’est que rien de tout cela n’a rendu la CLI moins bonne pour les humains. Le sélecteur TUI fonctionne toujours et reste soigné, les indicateurs de progression tournent toujours, les dialogues de confirmation remplissent toujours leur rôle. Nous avons simplement ajouté une deuxième porte.
Et cette deuxième porte s’est révélée être la plus importante. Non pas parce que les agents comptent plus que les humains, mais parce que les contraintes qu’ils imposent sont les mêmes que celles qui rendent une CLI composable, scriptable et testable. Concevoir pour les agents nous a forcés à construire un meilleur outil pour tout le monde.
Si vous construisez des outils de développement aujourd’hui, vous n’avez pas besoin d’une API agent séparée. Vous devez examiner chaque appel input(), chaque hypothèse sur CWD, chaque sortie uniquement pretty-printed, et vous demander : que se passe-t-il si l’utilisateur de l’autre côté est un processus, pas une personne ?
La réponse à cette question améliorera votre outil dans tous les cas.
Spaces CLI est construit par Lorenzo Signoretti, Riwa Hoteit et Sam Fenwick chez Mistral AI. Merci en particulier à notre équipe Applied AI, les premiers utilisateurs de la CLI et les plus exigeants, dont l’usage réel a façonné chaque modèle décrit ici. Nous avons hâte de voir les applications qu’elle les aidera à construire avec nos clients pour répondre à des cas d’usage difficiles.
La couche d’outillage entre humains et agents est encore en cours de définition. Si construire des outils de développement à l’intersection de l’IA et de l’infrastructure vous parle, nous recrutons.




