Logo Teamwork

Construire une App Kiro Crew pour piloter son blog technique

Published on
Authors
Version audio
Chargement...
App Kiro Crew — Blog Admin Dashboard


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

Architecture de l'App Blog Admin

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'agents
  • skills : dossiers contenant un SKILL.md qui sera injecté comme contexte
  • backend : serveur Python démarré automatiquement par Crew, avec healthcheck
  • ui.entry : le module ESM buildé par Vite — Crew l'injecte dans son shell
  • ui.pages : routes et labels affichés dans la navigation du dashboard Crew
  • crons : jobs récurrents avec "every" en secondes, un "message" envoyé à l'agent, et "enabled": false pour les déclarer sans les activer immédiatement

Page Dashboard

Blog 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

Flux de publication sociale

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

Cycle de vie d'un cron job
Cron Dashboard

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.

JobFréquenceCrédits/run
Audit SEOLundi 8h3
Scan dépendancesWeekdays 9h0 (script)
Nettoyage GitMar/Jeu 10h1
Vérification redirectionsMercredi 10h0 (script)
Résumé fin de semaineVendredi 16h4
Génération posts sociauxÀ la publication5

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.jsonbackend.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 :

  1. Le chemin ui.entry — Le manifeste résout le chemin depuis le dossier ui/ de l'app, pas depuis sa racine. Mettre ui/dist/index.mjs donnait une URL doublée (/ui/ui/dist/...). La valeur correcte est dist/index.mjs.

  2. Les externals Vite — Externaliser lucide-react pour 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 erreurs does not provide an export named 'X'.

  3. 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 forcer colorScheme: 'light' et un fond explicite sur le conteneur racine pour que les couleurs ne soient pas écrasées par le thème parent.

  4. 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 que react-router-dom fonctionne.

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 à :

  1. Générer le squelette avec kirocrew app init mon-app --ui --backend --cron
  2. Définir les agents (JSON) et skills (Markdown)
  3. Coder l'UI React exportée en module ESM
  4. Installer avec kirocrew app install ./mon-app
  5. 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 :

Partager cet article