Construire une App Kiro Crew pour piloter son blog technique
- Published on
- Authors

- Name
- Sylvain BRUAS
- @sylvain_bruas
Dans les deux articles précédents, nous avons exploré l'architecture de Kiro Crew (Gateway, mémoire, sécurité) puis ses cas d'usage concrets (cron jobs, orchestration multi-agent, Apps existantes). Cette troisième partie est un retour d'expérience : j'ai construit une App Kiro Crew personnalisée pour piloter l'administration de ce blog.
L'objectif : regrouper dans une interface dédiée tout ce que je faisais manuellement — vérifier le statut des articles, valider les posts sociaux générés par l'agent, suivre les jobs planifiés, et monitorer les déploiements.
Pourquoi une App plutôt qu'un chat ?
Certains workflows ne rentrent pas dans une fenêtre de conversation. Quand je veux :
- Voir d'un coup d'œil les 3 posts sociaux en attente de validation
- Approuver ou rejeter un post LinkedIn d'un clic
- Vérifier que le dernier déploiement a passé l'audit SEO
- Toggler un cron job on/off sans écrire de commande
...une UI dédiée est plus efficace qu'un échange conversationnel.
C'est exactement ce que les Kiro Crew Apps permettent : combiner une interface React avec le runtime agent, l'event bus et les schedules dans un package cohérent.
Architecture de l'App
Une App Kiro Crew suit une structure imposée par le framework. On peut générer un squelette via kirocrew app init, mais voici la structure complète de l'app blog-admin :
kirocrew/
├── app.json # Manifeste KiroCrew (agents, crons, UI, backend)
├── agents/ # Définitions JSON des agents
│ ├── content-validator.json
│ ├── social-writer.json
│ └── deploy-monitor.json
├── skills/ # Skills Markdown (contexte injecté aux agents)
│ ├── blog-conventions/
│ │ └── SKILL.md
│ └── social-publishing/
│ └── SKILL.md
├── backend/ # Backend Python (API interne)
│ └── server.py
├── ui/ # Frontend React (pages du dashboard)
│ ├── src/
│ │ ├── App.tsx # Point d'entrée (exporté en ESM)
│ │ ├── components/
│ │ ├── pages/
│ │ ├── types/
│ │ └── data/
│ ├── package.json
│ └── vite.config.ts
└── README.md
Stack UI : React 19, TypeScript 5.9, Vite 6, Tailwind CSS 4, Lucide pour les icônes. L'UI est buildée comme un module ESM (pas un SPA classique) car Kiro Crew injecte le composant dans son propre shell. Le routing interne utilise React Router avec un MemoryRouter (Crew ne fournit pas de contexte Router parent).
Backend : Python minimal (stdlib http.server) qui sert une API d'articles en scannant le filesystem. Kiro Crew le démarre automatiquement.
Agents : 3 fichiers JSON déclarant chacun un agent spécialisé avec son prompt et ses outils autorisés.
Skills : fichiers Markdown injectés comme contexte aux agents, encodant les conventions du blog.
Le manifeste app.json
Le fichier app.json est le point d'entrée de l'App auprès du runtime Kiro Crew. Il déclare les agents, les crons, le backend et l'UI. On peut le créer manuellement ou générer un squelette avec kirocrew app init blog-admin --ui --backend --cron.
{
"name": "blog-admin",
"version": "1.0.0",
"displayName": "Blog Admin Dashboard",
"description": "Dashboard d'administration pour sylvain.bruas.fr",
"author": "sylvainbruas",
"agents": [
"agents/content-validator.json",
"agents/social-writer.json",
"agents/deploy-monitor.json"
],
"skills": [
"skills/blog-conventions",
"skills/social-publishing"
],
"tags": ["blog", "admin", "social", "seo", "deployment"],
"backend": {
"entryPoint": "backend/server.py",
"port": "auto",
"healthCheck": "/health"
},
"ui": {
"entry": "dist/index.mjs",
"pages": [
{ "route": "/apps/blog-admin", "label": "Blog Admin", "icon": "LayoutDashboard" },
{ "route": "/apps/blog-admin/articles", "label": "Articles", "icon": "FileText" },
{ "route": "/apps/blog-admin/social", "label": "Social", "icon": "Share2" },
{ "route": "/apps/blog-admin/jobs", "label": "Jobs", "icon": "Clock" },
{ "route": "/apps/blog-admin/deployments", "label": "Déploiements", "icon": "Rocket" }
]
},
"crons": [
{ "name": "seo-audit-weekly", "every": 604800, "message": "Audit SEO complet de tous les articles publiés", "enabled": false },
{ "name": "dep-scan-daily", "every": 86400, "message": "Scan de vulnérabilités des dépendances npm", "enabled": false },
{ "name": "git-hygiene", "every": 259200, "message": "Identifier les branches stale et commits WIP abandonnés", "enabled": false },
{ "name": "redirect-check", "every": 604800, "message": "Vérifier que toutes les redirections S3 fonctionnent", "enabled": false },
{ "name": "friday-summary", "every": 604800, "message": "Résumer les déploiements de la semaine", "enabled": false },
{ "name": "social-gen", "every": 86400, "message": "Générer des posts sociaux pour le dernier article publié", "enabled": false }
]
}
Points clés du manifeste :
agents: chemins relatifs vers les fichiers JSON de définition d'agentsskills: dossiers contenant unSKILL.mdqui sera injecté comme contextebackend: serveur Python démarré automatiquement par Crew, avec healthcheckui.entry: le module ESM buildé par Vite — Crew l'injecte dans son shellui.pages: routes et labels affichés dans la navigation du dashboard Crewcrons: jobs récurrents avec"every"en secondes, un"message"envoyé à l'agent, et"enabled": falsepour les déclarer sans les activer immédiatement
Page Dashboard
Le Dashboard donne une vue d'ensemble en un coup d'œil :
// Extrait simplifié du DashboardPage.tsx
const DashboardPage = () => {
const stats = mockDashboardStats
return (
<div className="space-y-8">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<StatCard
label="Articles publiés"
value={stats.publishedArticles}
icon={FileText}
color="success"
/>
<StatCard
label="Posts en attente"
value={stats.pendingSocialPosts}
icon={Share2}
color="warning"
/>
<StatCard
label="Déploiements (semaine)"
value={stats.deploymentsThisWeek}
icon={Rocket}
color="primary"
/>
<StatCard
label="Crédits utilisés"
value={stats.creditsUsedThisWeek}
icon={Zap}
color="primary"
/>
</div>
{/* + Dernier déploiement avec scores audit */}
{/* + Posts sociaux en attente */}
{/* + Dernières exécutions de jobs */}
</div>
)
}
4 métriques clés : articles publiés, posts sociaux en attente de validation, déploiements de la semaine, crédits Kiro Crew consommés.
En dessous : le dernier déploiement avec ses scores d'audit (SEO, Lighthouse), les posts en attente, et les dernières exécutions de jobs.
Page Validation des posts sociaux
Quand l'agent social-writer génère des posts à partir d'un article, ils arrivent ici en statut "En attente" :
- Chaque post est affiché avec sa plateforme (X, LinkedIn, Bluesky), son contenu complet, et la date de génération
- Deux boutons : ✓ Approuver / ✗ Rejeter
- Après approbation : un bouton "Publier" apparaît pour déclencher l'envoi via l'API de la plateforme
L'idée est de garder l'humain dans la boucle pour la publication sociale — l'agent rédige, vous validez. Le mode supervised dans le manifeste Crew garantit que rien ne part sans approbation explicite.
Page Jobs planifiés
Cette page expose les 6 jobs déclarés dans app.json avec une interface de gestion :
- Toggle on/off pour activer/désactiver un job sans toucher au manifeste
- Bouton "Run" pour déclencher une exécution manuelle
- Panel expandable montrant : durée de la dernière exécution, crédits consommés, output terminal, historique des runs
// Chaque job affiche son schedule et son dernier output
{job.lastRun?.output && (
<div className="bg-gray-900 rounded-lg p-4">
<pre className="text-sm text-green-400 whitespace-pre-line font-mono">
{job.lastRun.output}
</pre>
</div>
)}
Point économique : les jobs sont déclarés avec "enabled": false dans le manifeste. Cela permet de versionner la configuration sans déclencher d'exécutions dès l'installation. On active ensuite individuellement via le toggle dans l'interface, ou en CLI avec kirocrew cron enable blog-admin/seo-audit-weekly. Le scan de dépendances et la vérification des redirections sont des scripts purs (0 crédit). Seuls l'audit SEO, le résumé de fin de semaine et la génération de posts sociaux consomment des crédits d'inférence.
| Job | Fréquence | Crédits/run |
|---|---|---|
| Audit SEO | Lundi 8h | 3 |
| Scan dépendances | Weekdays 9h | 0 (script) |
| Nettoyage Git | Mar/Jeu 10h | 1 |
| Vérification redirections | Mercredi 10h | 0 (script) |
| Résumé fin de semaine | Vendredi 16h | 4 |
| Génération posts sociaux | À la publication | 5 |
Total estimé : ~18 crédits/semaine, soit environ $3/mois sur un plan Pro.
Déployer l'App sur Kiro Crew
Pour enregistrer et exécuter cette App dans votre instance Kiro Crew :
Prérequis
- Kiro Crew installé et le Gateway actif (
kirocrew gateway) - Un compte Kiro authentifié (plan Free minimum, Pro recommandé)
- Node.js 18+ pour le build de l'UI
- Python 3.10+ pour le backend
Étape 1 : Génération du squelette (optionnel)
Si vous partez de zéro, kirocrew app init génère le squelette :
kirocrew app init blog-admin --ui --backend --cron
Cela crée la structure de base avec app.json, un agent exemple, un skill, un backend Python et un frontend Vite.
Étape 2 : Build de l'UI
cd kirocrew/ui
pnpm install
pnpm build
# → Génère dist/index.mjs (module ESM)
Étape 3 : Installer l'App dans Kiro Crew
kirocrew app install /chemin/vers/kirocrew
Kiro Crew lit le app.json, déploie les agents, programme les crons, démarre le backend, et injecte l'UI dans le dashboard.
Étape 4 : Activer et vérifier
# Activer l'app
kirocrew app enable blog-admin
# Vérifier l'installation
kirocrew app info blog-admin
# Lister toutes les apps installées
kirocrew app list
Mode développement
Pour itérer rapidement sur l'UI sans rebuild à chaque modification :
# Active le live reload dans le dashboard Crew
kirocrew app dev blog-admin
# Ou lancer le dev server Vite en standalone
cd kirocrew/ui && pnpm dev
# → http://localhost:3100
kirocrew app dev active le mode no-store + live reload sur les changements de fichiers. Pratique pour ajuster le dashboard sans reconstruire à chaque fois.
Mode Docker (serveur always-on)
Pour une instance persistante qui tourne même quand votre Mac est éteint :
docker run -d --name kirocrew \
-p 127.0.0.1:5476:5476 \
-v kirocrew-home:/home/kirocrew \
ghcr.io/kirodotdev/kirocrew:stable
Une fois le container actif, installez l'app depuis le container ou montez le répertoire en volume.
Désinstallation
# Désactiver sans supprimer les données
kirocrew app disable blog-admin
# Supprimer complètement (préserve les données par défaut)
kirocrew app uninstall blog-admin
Connecter aux données réelles
Le backend Python
Le fichier backend/server.py scanne directement le filesystem pour lister les articles et calcule les statistiques :
def scan_articles():
"""Scan le répertoire blog pour extraire les frontmatter."""
articles = []
blog_path = Path(BLOG_DIR)
for mdx_file in blog_path.rglob("index.mdx"):
slug = str(mdx_file.parent.relative_to(blog_path))
content = mdx_file.read_text(encoding="utf-8")
frontmatter = parse_frontmatter(content)
status = determine_status(frontmatter, slug, devto_data)
articles.append({
"slug": slug,
"title": frontmatter.get("title", ""),
"date": str(frontmatter.get("date", ""))[:10],
"lang": frontmatter.get("lang", "fr"),
"status": status,
"tags": frontmatter.get("tags", []),
"summary": frontmatter.get("summary", ""),
"cover": frontmatter.get("cover", ""),
})
return articles
Kiro Crew démarre ce backend automatiquement (déclaré dans app.json → backend.entryPoint).
Le hook useApi
Côté UI, un hook React générique gère les appels au backend via le gateway Crew :
function useApi<T>(endpoint: string): FetchState<T> {
// Essaie les routes du gateway, puis fallback sur le port direct
const fetchData = useCallback(() => {
apiFetch(endpoint)
.then((res) => res.json())
.then((json) => setData(json))
.catch((err) => setError(err.message))
}, [endpoint])
useEffect(() => { fetchData() }, [fetchData])
return { data, loading, error, refetch: fetchData }
}
Chaque page consomme ce hook : useApi<ArticlesResponse>('/articles') pour la liste, useApi<DashboardStats>('/stats') pour les compteurs.
Ce que j'ai appris
Pièges d'intégration avec le shell Crew
En déployant l'app pour la première fois, j'ai rencontré trois problèmes classiques d'intégration :
-
Le chemin
ui.entry— Le manifeste résout le chemin depuis le dossierui/de l'app, pas depuis sa racine. Mettreui/dist/index.mjsdonnait une URL doublée (/ui/ui/dist/...). La valeur correcte estdist/index.mjs. -
Les externals Vite — Externaliser
lucide-reactpour réduire la taille du bundle semble logique, mais la version fournie par le runtime Crew ne contient pas forcément les mêmes exports que celle du projet. Mieux vaut bundler les icônes directement (~10 kB supplémentaires) pour éviter les erreursdoes not provide an export named 'X'. -
Le thème dark du shell — Le dashboard Crew est en thème sombre. Si l'app utilise des classes Tailwind en mode clair (
bg-white,text-gray-900), il faut forcercolorScheme: 'light'et un fond explicite sur le conteneur racine pour que les couleurs ne soient pas écrasées par le thème parent. -
Le Router — Crew injecte le composant App dans son propre shell sans fournir de contexte
<Router>. Il faut wraper l'app avec un<MemoryRouter>pour quereact-router-domfonctionne.
Le modèle mental "agent + UI" fonctionne
La séparation est nette : l'agent fait le travail lourd (rédiger, valider, scanner), l'UI donne le contrôle (approuver, rejeter, toggler, monitorer). Le mode supervised est essentiel pour les actions irréversibles comme publier sur les réseaux sociaux.
Les scripts zero-credit sont sous-estimés
Sur mes jobs planifiés, 2 ne consomment aucun crédit parce que ce sont des scripts bash simples. Le scan de dépendances (npm audit --json) et la vérification des redirections (scripts/test-redirects.sh) n'ont pas besoin de raisonnement IA. Kiro Crew les exécute directement sans appel modèle.
La mémoire persistante évite la dérive
Après quelques corrections sur les posts sociaux générés ("trop long pour X", "ajoute toujours le lien canonique", "pas d'émoji dans les posts LinkedIn pro"), l'agent apprend et les prochains drafts respectent les préférences. Sans mémoire persistante, il faudrait ré-expliquer ces conventions à chaque session.
Le rapport d'audit pré-deploy est un filet de sécurité
J'ai découvert un build cassé à cause d'une image PNG (au lieu de WebP) uniquement parce que l'audit l'a détecté. Sans ce check automatique, l'article serait parti en production avec un cover manquant.
Conclusion
Construire une App Kiro Crew se résume à :
- Générer le squelette avec
kirocrew app init mon-app --ui --backend --cron - Définir les agents (JSON) et skills (Markdown)
- Coder l'UI React exportée en module ESM
- Installer avec
kirocrew app install ./mon-app - Itérer avec
kirocrew app dev mon-app(live reload)
La courbe d'apprentissage est faible si vous connaissez déjà React et TypeScript. Le vrai travail est dans la définition des agents et des skills.
Sources :
- kiro.dev/crew — Page produit officielle
- github.com/kirodotdev/KiroCrew — Repository GitHub
- kiro.dev/blog/introducing-kiro-crew — Blog post de lancement
- dev.to/aws-builders — Getting Started with Kiro Crew — Guide de démarrage






