# Démarrer : votre première app, de zéro à la publication

> Tutoriel complet : compte développeur, un widget météo sans une ligne de code (manifeste seul), l'installation de test, la publication sur le marketplace — puis la même app avec la CLI, puis avec du code hébergé.
> Catégorie : Démarrer · mis à jour le 2026-09-04 · plateforme v1

## Ce que vous allez construire

Une **app Capibara** ajoute des écrans, des widgets, des panneaux, des tâches
ou des blocs de site aux organisations qui l'installent. Elle se décrit dans
un fichier unique, `add-on.json` (le **manifeste**), et se distribue par le
marketplace. Vous n'avez jamais besoin du code source de Capibara.

Trois façons de faire tourner une app, combinables :

| Façon | Ce que vous écrivez | Où ça tourne |
|---|---|---|
| **Déclaratif** | le manifeste seul (un widget `template`) | Capibara collecte et rend tout |
| **Code hébergé** (`main`) | du TypeScript `defineApp` qui renvoie du [Kit](/dev/docs/kit) | dans le bac à sable Capibara, sans serveur à gérer |
| **Backend externe** (`backend.url`) | le même `defineApp`, servi par votre serveur | chez vous, requêtes signées |

Dans ce tutoriel vous construisez **« Ma météo »** : d'abord un widget météo
sur l'accueil des organisations, **sans une ligne de code** (15 minutes),
publié sur le marketplace ; puis vous refaites le même chemin avec la CLI ;
puis vous ajoutez une **page** avec du code hébergé. Le widget déclaratif est
exactement celui de l'app « Time » (Night Plugin's), déjà publiée sur le
marketplace.

---

## Avant de commencer

- Un compte Capibara, et **au moins une organisation dont vous êtes
  propriétaire** : c'est sur elle que vous installerez l'app en test.
- Un forfait **Pro ou Business** sur cette organisation : le portail
  développeur est réservé aux comptes Pro+ (un compte uniquement Gratuit voit
  un écran qui propose de passer à Pro).
- Pour les parties 2 et 3 : **Node.js 18 ou plus** sur votre machine.

Le portail est sur `developer.capibara.fr` (documentation publique, bouton
« Se connecter ») ; une fois connecté, vous travaillez sur
`capibara.fr/dev`.

---

## Partie 1 — Un widget météo sans code (depuis le portail)

### 1. Créer votre compte développeur

Sur `/dev`, un formulaire vous demande un **nom public** (2 à 80 caractères)
— celui qui apparaît sur vos apps et sur votre page du marketplace. Un
**slug** en est dérivé (minuscules, tirets) : vous le retrouverez dans le
manifeste (`author.devAccountSlug`). Un compte Capibara n'a qu'un compte
développeur ; recréer renvoie le vôtre.

### 2. Créer l'app

Depuis le tableau de bord, **« Nouvelle app »** ouvre un assistant en trois
écrans :

1. **Identité** — le nom (« Ma météo »), l'identifiant proposé
   automatiquement (`ma-meteo` : kebab-case, 3 à 50 caractères, **immuable**,
   unique sur toute la plateforme — `time` est déjà pris) et la catégorie
   (`productivity`).
2. **Point de départ** — choisissez **« Manifeste seul »** : aucun code
   n'est généré, vous allez écrire le manifeste vous-même.
3. **Récapitulatif** — créer.

Vous arrivez sur la page de l'app. Sa barre latérale regroupe ses sections :
**Construire** (Général, Manifeste, Code, Studio), **Tester & suivre**
(Tests, Journal, Données), **Publier** (Versions, Distribution, Support),
**Intégrations** (Backend & webhooks). La section Général tient une checklist
« Prochaines étapes » qui se coche au fil du tutoriel.

### 3. Écrire le manifeste

Section **Manifeste**, onglet **JSON** : collez ceci en remplaçant
`votre-slug` par votre slug de développeur (l'onglet Formulaire montre les
mêmes champs, les deux éditent le même objet).

```json
{
  "key": "ma-meteo",
  "name": "Ma météo",
  "version": "0.1.0",
  "author": { "name": "Votre nom", "devAccountSlug": "votre-slug" },
  "compatibility": { "colibri": ">=1.0" },
  "capabilities": [
    "ui.widget",
    "http.fetch:geocoding-api.open-meteo.com",
    "http.fetch:api.open-meteo.com"
  ],
  "contributes": {
    "widgets": [
      {
        "key": "meteo",
        "title": "Météo",
        "kind": "declarative",
        "template": "weather",
        "config": [
          { "key": "address", "label": "Votre ville ou adresse", "type": "text", "placeholder": "Paris", "required": true }
        ],
        "minHeight": 260
      }
    ]
  },
  "pricing": { "model": "free" },
  "i18n": {
    "fr": "La météo de votre ville sur l'accueil, propulsée par Open-Meteo.",
    "en": "Your city's weather on the dashboard, powered by Open-Meteo."
  }
}
```

Ce que chaque bloc dit à la plateforme :

- `key`, `name`, `version` — l'identité. La `key` doit être **celle du
  projet** ; la `version` est un semver, chaque changement publié en prend un
  nouveau.
- `author.devAccountSlug` — votre slug, exactement (le portail refuse un
  manifeste signé par un autre développeur).
- `compatibility.colibri` — la version de plateforme visée (`>=1.0`
  aujourd'hui). `modules` (optionnel) liste les modules Capibara requis.
- `capabilities` — ce que l'app a le droit de faire. `ui.widget` pour
  contribuer un widget d'accueil ; **`http.fetch:<hôte>`** pour chaque hôte
  appelé : le template `weather` appelle les deux API Open-Meteo, elles
  doivent être déclarées, sinon le validateur vous les liste comme manquantes.
- `contributes.widgets[]` — le widget : `kind: "declarative"` +
  `template: "weather"`, et un champ `config` que **l'utilisateur** remplit
  sur son accueil (sa ville). Capibara géocode l'adresse, interroge la météo
  et dessine le widget à sa charte. Vous ne collectez ni ne stockez rien.
- `pricing` — gratuite ici. Voir [Revenus](/dev/docs/revenus) pour une app
  payante.
- `i18n.fr` / `i18n.en` — l'accroche d'une phrase (les deux sont
  obligatoires ; le marketplace affiche le français).

Cliquez **« Enregistrer la version (brouillon) »**. Le manifeste est validé
en direct avec les mêmes règles que le serveur ; une erreur nomme le champ
fautif. La version `0.1.0` apparaît dans **Versions**, en brouillon.

> Un widget déclaratif n'a pas de code : la section Code reste vide, c'est
> normal. La référence complète des champs est dans [Le manifeste](/dev/docs/manifeste),
> et [Widgets déclaratifs](/dev/docs/widgets-declaratifs) décrit les templates
> `weather`, `kpi` et `list`.

### 4. Installer en test et voir le widget

Section **Tests** : choisissez une organisation dont vous êtes propriétaire,
puis **« Installer en test »**. L'app est posée sur cette organisation en
mode test (sa fiche n'est visible de personne d'autre, elle n'est pas comptée
dans les installations).

Dans l'organisation :

1. **Accueil** › **« Personnaliser »** › **« Ajouter un widget »** › rubrique
   **Apps** › « Météo · Ma météo ».
2. Le widget affiche son petit formulaire : saisissez une ville, **« Enregistrer »**.
3. La météo s'affiche (température, ressenti, vent, humidité, ciel, heure
   locale). Le bouton **« Modifier »** change la ville.

L'app apparaît aussi dans **Modules › Apps** de l'organisation, avec un
encart « installation de test ». Le **Journal** du portail reste vide pour un
widget déclaratif : aucun code ne s'exécute, la plateforme fait la collecte.

### 5. Publier sur le marketplace

Section **Versions** › sur la `0.1.0`, **« Soumettre à la review »**. Le
manifeste est revalidé, un build automatisé tourne (ici, sans code, il ne
fait que valider), puis la version passe en `REVIEW`. Une personne de
l'équipe Capibara la relit — les délais et les motifs de refus courants sont
dans [Publication et review](/dev/docs/publication). Approuvée, elle passe en
`APPROVED` : **« Publier »** la rend installable partout.

Section **Distribution** : la **fiche marketplace** a été créée à la première
publication (accroche reprise de `i18n.fr`, catégorie du projet). Complétez
la description et ajoutez des captures, puis **« Enregistrer la fiche »**.
Votre app est visible sur `/marketplace/ma-meteo` et dans le catalogue
**Modules › Apps** de chaque organisation. Une adresse de support
(`support.email` dans le manifeste) est fortement conseillée : une
organisation qui installe votre app doit pouvoir vous écrire.

Vous venez de publier une app Capibara sans écrire une ligne de code.

---

## Partie 2 — La même app avec la CLI

La CLI `capibara` est la voie des développeurs : un dossier local lié au
projet du portail par une **clé de projet**, des déploiements de test à
chaque sauvegarde, le journal en direct, la soumission en review — sans
tunnel, sans serveur. Référence complète : [La CLI capibara](/dev/docs/cli).

### 1. Installer la CLI et préparer le dossier

```bash
npm i -g @capibara-dev/cli        # Node.js 18+ ; ou npx @capibara-dev/cli …
mkdir ma-meteo && cd ma-meteo
```

Créez `add-on.json` avec le manifeste de la partie 1.

### 2. Lier le dossier au projet

Dans le portail, section **Code** › carte **« Clé de projet & CLI »** ›
**« Générer une clé »** (donnez-lui un libellé : « Portable de Léa »). La clé
`cpk_…` s'affiche **une seule fois** ; elle n'ouvre que ce projet, jamais
votre compte.

```bash
capibara login --key cpk_…
```

La CLI vérifie la clé, l'enregistre dans `.capibara/key` (mode 600, ajouté
au `.gitignore` — ne la committez jamais), écrit `capibara.json` (`app`,
`manifest`) et vous liste les organisations de test possibles. Si le
manifeste porte encore `"devAccountSlug": "votre-slug"`, elle le remplace
par votre slug.

### 3. Déployer en test

```bash
capibara dev --org <slug-de-votre-organisation> --once
```

`dev` envoie le manifeste, crée (ou met à jour) la version brouillon, pose
l'installation de test et affiche son adresse. Comme il n'y a pas de code, la
CLI note « aucun code hébergé » : c'est attendu. Sans `--once`, `dev`
reste ouvert et **redéploie à chaque sauvegarde** du dossier, en suivant le
journal.

```bash
capibara status                       # version, canal, installations de test
capibara install --org <slug>         # poser une installation de test sans redéployer
capibara install --remove --org <slug>   # la retirer (ses données sont purgées)
```

### 4. Publier

```bash
capibara deploy            # crée la version du manifeste et l'envoie en review
capibara deploy --publish  # une fois approuvée : publication
```

Le portail et la CLI passent par les **mêmes** fonctions : ce que vous
faites d'un côté se voit de l'autre (section Versions, Journal, Tests).

---

## Partie 3 — Ajouter une page avec du code hébergé

Un widget déclaratif s'arrête au template. Pour un écran sur mesure, vous
écrivez du **code hébergé** : un `defineApp` en TypeScript qui renvoie des
écrans en [Kit](/dev/docs/kit) (des composants décrits en JSON, dessinés par
Capibara), exécuté dans le bac à sable de la plateforme.

### 1. Partir du starter « Page simple »

Créez une **nouvelle app** dans le portail (par exemple `ma-meteo-page`) en
choisissant cette fois le starter **« Page simple »** : le projet reçoit
une version `0.1.0` avec le manifeste, une page, un widget, un test et
l'**Édition rapide** (section Code) déjà remplie. Vous pouvez tout faire
depuis le portail — mais faisons-le en local :

```bash
mkdir ma-meteo-page && cd ma-meteo-page
capibara init --template page --app ma-meteo-page   # même code que le starter du portail
npm install
capibara login --key cpk_…                          # la clé de CE projet
capibara dev --org <slug>
```

En quelques secondes, l'organisation de test a une entrée de menu
« Ma Meteo Page » (une page rendue par le Kit, un compteur dans le KV) et un
widget « Compteur ». Modifiez `src/index.ts`, enregistrez : la version est
rebundlée, l'isolate rechargé, le journal défile.

Le starter a écrit `add-on.json` avec `main: "src/index.ts"`, les
capabilities `ui.menu`, `ui.widget`, `storage.tenant.ro`,
`storage.tenant.rw`, une entrée de menu `view: "home"` et un widget
`kind: "kit"`.

### 2. Y mettre la météo

Ajoutez au manifeste les deux hôtes Open-Meteo dans `capabilities`, et un
réglage saisi par l'administrateur de l'organisation :

```json
"capabilities": ["ui.menu", "ui.widget", "http.fetch:geocoding-api.open-meteo.com", "http.fetch:api.open-meteo.com"],
"settings": {
  "fields": [{ "key": "city", "label": "Ville par défaut", "type": "text", "placeholder": "Paris" }]
}
```

Puis remplacez `src/index.ts` par :

```ts
import { defineApp, ui, type AppContext, type KitNode } from '@capibara-dev/sdk';

/** Le corps d'un fetch sortant arrive en base64 : on le décode en UTF-8. */
function b64ToUtf8(b64: string): string {
  const bin = atob(b64);
  const bytes = new Uint8Array(bin.length);
  for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
  return new TextDecoder().decode(bytes);
}

/** ctx.fetch = HTTP sortant BORNÉ : l'hôte doit être déclaré http.fetch:<hôte> au manifeste. */
async function fetchJson(ctx: AppContext, url: string): Promise<unknown> {
  const res = await ctx.fetch(url);
  if (res.status !== 200) throw new Error('réponse ' + res.status);
  return JSON.parse(b64ToUtf8(res.bodyBase64)) as unknown;
}

async function weather(ctx: AppContext, city: string): Promise<KitNode> {
  const geo = await fetchJson(ctx, 'https://geocoding-api.open-meteo.com/v1/search?name=' + encodeURIComponent(city) + '&count=1&language=fr&format=json') as {
    results?: Array<{ latitude: number; longitude: number; name?: string }>;
  };
  const hit = geo.results?.[0];
  if (!hit) return ui.note({ tone: 'warning', text: 'Ville « ' + city + ' » introuvable.' });
  const fore = await fetchJson(ctx, 'https://api.open-meteo.com/v1/forecast?latitude=' + hit.latitude + '&longitude=' + hit.longitude + '&current=temperature_2m,wind_speed_10m&timezone=auto') as {
    current?: { temperature_2m?: number; wind_speed_10m?: number };
  };
  const t = fore.current?.temperature_2m;
  if (typeof t !== 'number') return ui.note({ tone: 'warning', text: 'Météo indisponible pour le moment.' });
  return ui.kpi({ label: hit.name ?? city, value: Math.round(t) + '°C', hint: 'Vent ' + Math.round(fore.current?.wind_speed_10m ?? 0) + ' km/h', icon: 'globe' });
}

/** Le réglage « city » déclaré au manifeste arrive dans ctx.install.config (saisi par un admin de l'organisation). */
function cityOf(ctx: AppContext): string {
  const c = ctx.install.config.city;
  return typeof c === 'string' && c.trim() ? c.trim() : 'Paris';
}

export default defineApp({
  pages: {
    home: {
      async render({ ctx }) {
        return ui.doc(
          ui.stack({ gap: 'lg' }, [
            ui.pageHeader({ title: 'Météo', subtitle: 'Données Open-Meteo, ville réglée par votre organisation.' }),
            ui.card({ title: 'Conditions actuelles' }, [await weather(ctx, cityOf(ctx))]),
          ]),
          { title: 'Météo' },
        );
      },
    },
  },
  widgets: {
    compteur: async ({ ctx }) => weather(ctx, cityOf(ctx)),
  },
});
```

Enregistrez : `capibara dev` redéploie, la page « Météo » et le widget
montrent la météo de la ville réglée par l'organisation (Modules › Apps ›
votre app › Réglages). `capibara logs --follow` suit les exécutions ;
`npm test` lance le test du starter sur le harnais officiel
(`@capibara-dev/sdk/testing`), qui exécute votre `defineApp` en local avec
un faux `ctx`.

Pour aller plus loin, l'app **« Météo (exemple) »** (téléchargeable depuis
l'[index de la documentation](/dev/docs)) reprend tout cela avec des actions,
un formulaire de réglages, une collection de villes favorites, un bloc et une
page pour le site public — commentée ligne à ligne.

---

## Le cycle de vie d'une version

```
DRAFT ──submit──▶ REVIEW ──approuvé──▶ APPROVED ──publish──▶ PUBLISHED
  ▲                   │
  └────────────────REJECTED (avec notes) ◀──┘
```

- **DRAFT** — votre brouillon, enregistrable autant de fois que nécessaire.
- **REVIEW** — manifeste revalidé, build automatisé (manifeste, dépendances
  du dépôt lié, scan des sources ; le code envoyé par la CLI est déjà bundlé
  et scanné), puis relecture humaine.
- **APPROVED** — publiable quand vous le décidez. **PUBLISHED** —
  installable. **REJECTED** — refusée avec des notes ; corrigez et
  resoumettez.

Une version en `REVIEW`, `APPROVED` ou `PUBLISHED` ne change plus :
incrémentez `version` et enregistrez un nouveau brouillon — la section
Manifeste vous le propose pré-rempli depuis la dernière version.

---

## Checklist de première publication

- [ ] Le manifeste valide sans erreur.
- [ ] `permissions` et `capabilities` sont **minimales** — seulement ce que
      l'app utilise ([Scopes et consentement](/dev/docs/permissions)).
- [ ] `i18n.fr` et `i18n.en` décrivent l'app en une phrase claire.
- [ ] `support.email` est renseigné.
- [ ] Le tarif (`pricing`) correspond à ce que vous annoncez.
- [ ] Vous avez testé sur une organisation : installation, utilisation,
      désinstallation.
- [ ] Si l'app est payante, l'onboarding Stripe Connect est fait depuis
      **Revenus** ([Revenus et rev-share](/dev/docs/revenus)).

---

## Et ensuite ?

- [La CLI capibara](/dev/docs/cli) — toutes les commandes, les starters, les
  fichiers du projet.
- [Le manifeste](/dev/docs/manifeste) — chaque champ en détail.
- [Le Kit](/dev/docs/kit) et le Kit Studio — les composants de vos écrans.
- [Le runtime](/dev/docs/runtime) — `defineApp`, `ctx`, actions,
  événements, planifications.
- [Façades](/dev/docs/facades) et [Scopes](/dev/docs/permissions) — lire et
  écrire les données de l'organisation (CRM, facturation, boutique…).
- [Backend externe](/dev/docs/backend-externe) — héberger la logique chez
  vous.
- [Publication et review](/dev/docs/publication) · [Revenus](/dev/docs/revenus).
- [Utiliser cette doc avec votre IA](/dev/docs/llm) — si vous construisez
  avec un assistant.
