@clicbase/mcp 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +66 -4
- package/clicbase-db-mcp.mjs +17 -1
- package/clicbase-mcp.mjs +17 -1
- package/package.json +3 -1
- package/portee.mjs +5 -0
- package/savoir.mjs +120 -0
package/README.md
CHANGED
|
@@ -47,6 +47,66 @@ une **écriture** : il disparaît du mode restreint au lieu d'y être admis. Un
|
|
|
47
47
|
oubli coûte alors une fonction manquante, que l'on remarque ; l'inverse aurait
|
|
48
48
|
coûté une écriture ouverte dans un mode qui promet de ne pas écrire.
|
|
49
49
|
|
|
50
|
+
## ⚠️ La forme de la commande npx
|
|
51
|
+
|
|
52
|
+
`npx -y @clicbase/mcp` **echouait** en `could not determine executable to run`
|
|
53
|
+
jusqu'a la version 0.1.1 : le paquet porte plusieurs commandes et aucune ne
|
|
54
|
+
s'appelait `mcp`, donc npx ne pouvait pas choisir. Deux formes sont justes :
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
npx -y -p @clicbase/mcp clicbase-mcp # tout-en-un, marche des 0.1.0
|
|
58
|
+
npx -y -p @clicbase/mcp clicbase-db-mcp # un seul projet
|
|
59
|
+
npx -y @clicbase/mcp # depuis 0.1.1 seulement
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`-p` designe le PAQUET a installer, l'argument suivant la COMMANDE a lancer.
|
|
63
|
+
Sans lui, npx cherche une commande portant le dernier segment du nom du paquet.
|
|
64
|
+
|
|
65
|
+
## Un seul projet : `clicbase-db-mcp`
|
|
66
|
+
|
|
67
|
+
C'est la variante qu'utilise un client sur sa propre base. Elle ne cree pas de
|
|
68
|
+
projet et ne voit que celui qu'on lui donne :
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
claude mcp add ma-base \
|
|
72
|
+
-e CLICBASE_DB_URL=https://clicbase.com/db/<slug> \
|
|
73
|
+
-e CLICBASE_SERVICE_KEY=<cle service> \
|
|
74
|
+
-e CLICBASE_READ_ONLY=1 \
|
|
75
|
+
-- npx -y -p @clicbase/mcp clicbase-db-mcp
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Les deux valeurs se trouvent dans le tableau de bord Clicbase, **section API du
|
|
79
|
+
projet** (`/dashboard?studio=<id>§ion=api`) : « API REST » donne l'URL,
|
|
80
|
+
« Cle service » la cle. Ce n'est PAS une cle `cbk_` : celles-la servent au
|
|
81
|
+
serveur tout-en-un et n'apparaissent que pour qui possede un VPS ou un Docker.
|
|
82
|
+
|
|
83
|
+
## Ce que l'assistant sait avant son premier appel
|
|
84
|
+
|
|
85
|
+
Un serveur MCP qui n'expose que des outils laisse le modele deviner les
|
|
86
|
+
conventions de la plateforme. Depuis la 0.1.2, celui-ci remplit le champ
|
|
87
|
+
`instructions` du protocole : le client le pose dans le contexte du modele A LA
|
|
88
|
+
CONNEXION, avant tout appel. On ne peut donc pas oublier de le lire.
|
|
89
|
+
|
|
90
|
+
Six pieges y sont enumeres, et ils ont en commun de repondre **200 OK** :
|
|
91
|
+
|
|
92
|
+
1. `with check` n'est pas `using`. Sur INSERT et UPDATE, PostgreSQL n'evalue
|
|
93
|
+
QUE `with check`.
|
|
94
|
+
2. Chaque nouvelle table accorde le CRUD a `authenticated` : un `grant select`
|
|
95
|
+
ne restreint rien, il faut `revoke`.
|
|
96
|
+
3. Toute DDL se termine par `notify pgrst, 'reload schema';`.
|
|
97
|
+
4. `service_role` a besoin de `BYPASSRLS`, sinon 200 OK et un tableau vide.
|
|
98
|
+
5. Une fonction Edge rend `{ status, body }`, jamais `new Response(...)`.
|
|
99
|
+
6. L'auth du PROJET (schema `auth` de la base, `auth.uid()`) ne se confond pas
|
|
100
|
+
avec celle de la PLATEFORME.
|
|
101
|
+
|
|
102
|
+
L'outil `clicbase_conventions` en rend le detail a la demande : formes exactes
|
|
103
|
+
des policies, ordre `revoke` puis `grant`, operateurs PostgREST. Il est classe
|
|
104
|
+
en LECTURE, donc il survit au mode restreint · c'est ce mode qui en a le plus
|
|
105
|
+
besoin, puisqu'il ne laisse que trois autres outils.
|
|
106
|
+
|
|
107
|
+
Le texte vit dans `savoir.mjs`, sans aucun import, pour rester chargeable par
|
|
108
|
+
les tests du depot.
|
|
109
|
+
|
|
50
110
|
## Quand l'utiliser
|
|
51
111
|
|
|
52
112
|
`run_sql` exécute du SQL arbitraire avec les droits du propriétaire de la base,
|
|
@@ -69,7 +129,7 @@ En clair :
|
|
|
69
129
|
claude mcp add clicbase \
|
|
70
130
|
-e CLICBASE_API_KEY=cbk_... \
|
|
71
131
|
-e CLICBASE_READ_ONLY=1 \
|
|
72
|
-
-- npx -y @clicbase/mcp
|
|
132
|
+
-- npx -y -p @clicbase/mcp clicbase-mcp
|
|
73
133
|
```
|
|
74
134
|
|
|
75
135
|
> ⚠️ `CLICBASE_READ_ONLY=1` est volontairement dans la commande d'installation.
|
|
@@ -77,12 +137,14 @@ claude mcp add clicbase \
|
|
|
77
137
|
> projet et la clé `service`. Retire cette ligne seulement quand tu sais
|
|
78
138
|
> pourquoi, et lis la section « Quand l'utiliser » avant.
|
|
79
139
|
|
|
80
|
-
### Depuis
|
|
140
|
+
### Depuis les sources
|
|
81
141
|
|
|
142
|
+
⚠️ `cd mcp` ne vaut que dans le monorepo Clicbase. Depuis le depot public, la
|
|
143
|
+
racine EST le paquet : ce README est publie aux deux endroits, et une ligne
|
|
144
|
+
juste d'un cote se trompe de l'autre.
|
|
82
145
|
|
|
83
146
|
```bash
|
|
84
|
-
cd mcp
|
|
85
|
-
npm install
|
|
147
|
+
git clone https://github.com/clicbase/mcp && cd mcp && npm install
|
|
86
148
|
```
|
|
87
149
|
|
|
88
150
|
## Configuration dans Claude Code
|
package/clicbase-db-mcp.mjs
CHANGED
|
@@ -12,6 +12,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
12
12
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
13
13
|
import { z } from "zod";
|
|
14
14
|
import { filtre, lectureSeule } from "./portee.mjs";
|
|
15
|
+
import { CONVENTIONS, INSTRUCTIONS } from "./savoir.mjs";
|
|
15
16
|
|
|
16
17
|
const BASE = process.env.CLICBASE_DB_URL ?? process.env.ROROUIRA_DB_URL;
|
|
17
18
|
const KEY = process.env.CLICBASE_SERVICE_KEY ?? process.env.ROROUIRA_SERVICE_KEY;
|
|
@@ -51,7 +52,15 @@ async function api(path, method = "GET", body) {
|
|
|
51
52
|
const out = (d) => ({ content: [{ type: "text", text: JSON.stringify(d, null, 2) }] });
|
|
52
53
|
const fail = (e) => ({ isError: true, content: [{ type: "text", text: String(e?.message ?? e) }] });
|
|
53
54
|
|
|
54
|
-
|
|
55
|
+
// ⚠️ `instructions` EST LU A LA CONNEXION, AVANT LE PREMIER APPEL. C'est le
|
|
56
|
+
// seul endroit qu'un assistant ne peut pas oublier de consulter : le client
|
|
57
|
+
// MCP le pose dans le contexte du modele. Sans lui, un assistant recevait
|
|
58
|
+
// TROIS outils et trente mots de description, puis devinait les conventions
|
|
59
|
+
// de la plateforme. Voir savoir.mjs.
|
|
60
|
+
const server = new McpServer(
|
|
61
|
+
{ name: "clicbase-db", version: "1.0.0" },
|
|
62
|
+
{ instructions: INSTRUCTIONS },
|
|
63
|
+
);
|
|
55
64
|
|
|
56
65
|
// ⚠️ EN LECTURE SEULE, LES OUTILS QUI ECRIVENT NE SONT PAS ENREGISTRES. Voir
|
|
57
66
|
// portee.mjs : un outil absent n'est jamais demande, un outil present qui
|
|
@@ -59,6 +68,13 @@ const server = new McpServer({ name: "clicbase-db", version: "1.0.0" });
|
|
|
59
68
|
const SEULEMENT_LECTURE = lectureSeule();
|
|
60
69
|
const outils = filtre(server, SEULEMENT_LECTURE);
|
|
61
70
|
|
|
71
|
+
outils.tool(
|
|
72
|
+
"clicbase_conventions",
|
|
73
|
+
"Les conventions Clicbase : formes exactes des policies RLS, droits par defaut, rechargement du schema, appels REST, fonctions Edge. A lire AVANT d'ecrire du SQL ou une policy.",
|
|
74
|
+
{},
|
|
75
|
+
async () => ({ content: [{ type: "text", text: CONVENTIONS }] }),
|
|
76
|
+
);
|
|
77
|
+
|
|
62
78
|
outils.tool(
|
|
63
79
|
"run_sql",
|
|
64
80
|
"Exécute du SQL (DDL/DML/RLS) sur la base. Après un changement de schéma, termine par: NOTIFY pgrst, 'reload schema';",
|
package/clicbase-mcp.mjs
CHANGED
|
@@ -21,6 +21,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
21
21
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
22
22
|
import { z } from "zod";
|
|
23
23
|
import { filtre, lectureSeule } from "./portee.mjs";
|
|
24
|
+
import { CONVENTIONS, INSTRUCTIONS } from "./savoir.mjs";
|
|
24
25
|
|
|
25
26
|
const API = process.env.CLICBASE_API ?? process.env.ROROUIRA_API ?? "https://clicbase.com/api/v1/databases";
|
|
26
27
|
|
|
@@ -77,7 +78,15 @@ async function call(name, path, method = "POST", body) {
|
|
|
77
78
|
return data;
|
|
78
79
|
}
|
|
79
80
|
|
|
80
|
-
|
|
81
|
+
// ⚠️ `instructions` EST LU A LA CONNEXION, AVANT LE PREMIER APPEL. C'est le
|
|
82
|
+
// seul endroit qu'un assistant ne peut pas oublier de consulter : le client
|
|
83
|
+
// MCP le pose dans le contexte du modele. Sans lui, un assistant recevait
|
|
84
|
+
// TROIS outils et trente mots de description, puis devinait les conventions
|
|
85
|
+
// de la plateforme. Voir savoir.mjs.
|
|
86
|
+
const server = new McpServer(
|
|
87
|
+
{ name: "clicbase", version: "2.0.0" },
|
|
88
|
+
{ instructions: INSTRUCTIONS },
|
|
89
|
+
);
|
|
81
90
|
|
|
82
91
|
// ⚠️ EN LECTURE SEULE, LES OUTILS QUI ECRIVENT NE SONT PAS ENREGISTRES. Voir
|
|
83
92
|
// portee.mjs : un outil absent n'est jamais demande, un outil present qui
|
|
@@ -85,6 +94,13 @@ const server = new McpServer({ name: "clicbase", version: "2.0.0" });
|
|
|
85
94
|
const SEULEMENT_LECTURE = lectureSeule();
|
|
86
95
|
const outils = filtre(server, SEULEMENT_LECTURE);
|
|
87
96
|
|
|
97
|
+
outils.tool(
|
|
98
|
+
"clicbase_conventions",
|
|
99
|
+
"Les conventions Clicbase : formes exactes des policies RLS, droits par defaut, rechargement du schema, appels REST, fonctions Edge. A lire AVANT d'ecrire du SQL ou une policy.",
|
|
100
|
+
{},
|
|
101
|
+
async () => ({ content: [{ type: "text", text: CONVENTIONS }] }),
|
|
102
|
+
);
|
|
103
|
+
|
|
88
104
|
outils.tool(
|
|
89
105
|
"create_database",
|
|
90
106
|
"Crée (ou récupère, idempotent) une base Clicbase pour un site. Renvoie la chaîne de connexion PostgreSQL, l'URL de l'API REST et les clés anon/service.",
|
package/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clicbase/mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
|
+
"mcp": "clicbase-mcp.mjs",
|
|
6
7
|
"clicbase-mcp": "clicbase-mcp.mjs",
|
|
7
8
|
"clicbase-db-mcp": "clicbase-db-mcp.mjs"
|
|
8
9
|
},
|
|
@@ -34,6 +35,7 @@
|
|
|
34
35
|
"clicbase-mcp.mjs",
|
|
35
36
|
"clicbase-db-mcp.mjs",
|
|
36
37
|
"portee.mjs",
|
|
38
|
+
"savoir.mjs",
|
|
37
39
|
"README.md",
|
|
38
40
|
"LICENSE"
|
|
39
41
|
],
|
package/portee.mjs
CHANGED
|
@@ -43,6 +43,11 @@ export const OUTILS = {
|
|
|
43
43
|
enable_rls: "ecriture",
|
|
44
44
|
create_policy: "ecriture",
|
|
45
45
|
get_oauth_provider: "lecture",
|
|
46
|
+
// ⚠️ RENDU EN LECTURE SEULE, ET C'EST TOUT L'INTERET. Cet outil ne rend que
|
|
47
|
+
// du texte : les conventions de la plateforme. C'est precisement le mode
|
|
48
|
+
// restreint qui en a le plus besoin, puisqu'il ne reste alors que trois
|
|
49
|
+
// outils et qu'un assistant doit deviner le reste.
|
|
50
|
+
clicbase_conventions: "lecture",
|
|
46
51
|
};
|
|
47
52
|
|
|
48
53
|
/** Ce qu'on garde en lecture seule. */
|
package/savoir.mjs
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
// CE QUE L'ASSISTANT DOIT SAVOIR, ET QU'AUCUN OUTIL NE LUI APPREND.
|
|
2
|
+
//
|
|
3
|
+
// Question de Teiki le 2026-09-17 : « quand un client ajoute la commande MCP à
|
|
4
|
+
// son IA, elle va tout comprendre ? » Mesuré dans le paquet publié, la réponse
|
|
5
|
+
// était non. En lecture seule, un assistant reçoit TROIS outils et une
|
|
6
|
+
// trentaine de mots de description. Il sait explorer un schéma. Il ignore tout
|
|
7
|
+
// ce qui fait perdre des journées ici.
|
|
8
|
+
//
|
|
9
|
+
// ⚠️ LES SIX PIÈGES CI-DESSOUS SONT RÉELS, PAS THÉORIQUES. Ils viennent de la
|
|
10
|
+
// section « Pièges qui ont déjà coûté cher » du dépôt, écrite après coup à
|
|
11
|
+
// chaque fois. Ils ont en commun de ne produire NI exception NI ligne de
|
|
12
|
+
// journal : une policy qui ne protège rien répond 200, un grant qui ne
|
|
13
|
+
// restreint pas répond 200, une clé service sans BYPASSRLS répond 200 avec un
|
|
14
|
+
// tableau vide. Un assistant qui les ignore croit avoir réussi.
|
|
15
|
+
//
|
|
16
|
+
// ⚠️ DEUX CANAUX, ET CE N'EST PAS UNE REDONDANCE. `INSTRUCTIONS` part dans le
|
|
17
|
+
// champ prévu par le protocole : le client MCP le pose dans le contexte du
|
|
18
|
+
// modèle À LA CONNEXION, avant le premier appel, et on ne peut donc pas
|
|
19
|
+
// oublier de le lire. `CONVENTIONS` est le détail, rendu par un outil quand
|
|
20
|
+
// l'assistant en a besoin. Tout mettre dans le premier gonflerait chaque
|
|
21
|
+
// conversation ; tout mettre dans le second laisserait l'assistant écrire une
|
|
22
|
+
// policy fausse sans avoir jamais su qu'il fallait demander.
|
|
23
|
+
//
|
|
24
|
+
// ⚠️ CE FICHIER N'IMPORTE RIEN, exprès : le lanceur de tests du dépôt charge
|
|
25
|
+
// les modules voisins avec leur extension, et un import casserait sa lecture.
|
|
26
|
+
|
|
27
|
+
/** Posé dans le contexte du modèle dès la connexion. Court par obligation. */
|
|
28
|
+
export const INSTRUCTIONS = `Tu es connecté à un projet Clicbase : une base PostgreSQL managée, exposée en API REST par PostgREST.
|
|
29
|
+
|
|
30
|
+
SIX PIÈGES QUI NE LÈVENT AUCUNE ERREUR. Ils répondent tous « 200 OK ».
|
|
31
|
+
1. \`with check\` n'est PAS \`using\`. Sur INSERT et UPDATE, PostgreSQL n'évalue QUE \`with check\`. Une policy qui n'a qu'un \`using\` ne protège pas l'écriture.
|
|
32
|
+
2. Chaque nouvelle table accorde le CRUD à \`authenticated\` par défaut. Un \`grant select\` ne restreint RIEN : il faut \`revoke\`.
|
|
33
|
+
3. Après tout changement de schéma, termine par \`notify pgrst, 'reload schema';\` sinon l'API continue de servir l'ancien schéma.
|
|
34
|
+
4. Le rôle \`service_role\` a besoin de \`BYPASSRLS\`. Sans lui : 200 OK et un tableau vide, sans explication.
|
|
35
|
+
5. Une fonction Edge renvoie \`{ status, body }\`, jamais \`new Response(...)\`.
|
|
36
|
+
6. Deux authentifications sans rapport coexistent. Celle du PROJET (tes utilisateurs finaux) vit dans le schéma \`auth\` de cette base et alimente \`auth.uid()\` dans les policies. Celle de la PLATEFORME (le compte Clicbase) ne s'écrit jamais dans une policy.
|
|
37
|
+
|
|
38
|
+
APPELS REST : en-têtes \`apikey\` ET \`Authorization: Bearer\`, la même clé dans les deux. Filtres dans l'URL, façon PostgREST : \`?select=id,title&status=eq.published&order=created_at.desc\`.
|
|
39
|
+
|
|
40
|
+
DEUX CLÉS À NE PAS CONFONDRE. \`anon\` est publique et soumise à la RLS, elle va dans le navigateur. \`service\` CONTOURNE la RLS et voit tout : serveur uniquement, jamais dans un front, jamais dans un dépôt.
|
|
41
|
+
|
|
42
|
+
Appelle \`clicbase_conventions\` avant d'écrire du SQL ou une policy : tu y trouveras les formes exactes.`;
|
|
43
|
+
|
|
44
|
+
/** Le détail, rendu à la demande par un outil de lecture. */
|
|
45
|
+
export const CONVENTIONS = `# Conventions Clicbase
|
|
46
|
+
|
|
47
|
+
## Row Level Security
|
|
48
|
+
|
|
49
|
+
\`\`\`sql
|
|
50
|
+
alter table articles enable row level security;
|
|
51
|
+
|
|
52
|
+
-- LECTURE : using suffit.
|
|
53
|
+
create policy "lecture_publique" on articles
|
|
54
|
+
for select to anon, authenticated using (status = 'published');
|
|
55
|
+
|
|
56
|
+
-- ÉCRITURE : with check est OBLIGATOIRE, using ne sert pas.
|
|
57
|
+
create policy "chacun_ses_lignes" on articles
|
|
58
|
+
for insert to authenticated with check (user_id = auth.uid());
|
|
59
|
+
|
|
60
|
+
create policy "modifier_les_siennes" on articles
|
|
61
|
+
for update to authenticated
|
|
62
|
+
using (user_id = auth.uid()) -- quelles lignes il peut viser
|
|
63
|
+
with check (user_id = auth.uid()); -- ce qu'il peut y écrire
|
|
64
|
+
|
|
65
|
+
notify pgrst, 'reload schema';
|
|
66
|
+
\`\`\`
|
|
67
|
+
|
|
68
|
+
⚠️ Sur UPDATE, les deux clauses ont des rôles différents : \`using\` choisit les
|
|
69
|
+
lignes visées, \`with check\` valide le résultat. Omettre la seconde laisse
|
|
70
|
+
réécrire \`user_id\` vers quelqu'un d'autre.
|
|
71
|
+
|
|
72
|
+
## Les droits, avant la RLS
|
|
73
|
+
|
|
74
|
+
La RLS filtre les lignes ; les GRANT décident si la table est atteignable. Une
|
|
75
|
+
nouvelle table est ouverte en CRUD à \`authenticated\` par défaut, donc :
|
|
76
|
+
|
|
77
|
+
\`\`\`sql
|
|
78
|
+
revoke all on articles from anon, authenticated;
|
|
79
|
+
grant select on articles to anon, authenticated;
|
|
80
|
+
grant insert, update, delete on articles to authenticated;
|
|
81
|
+
\`\`\`
|
|
82
|
+
|
|
83
|
+
Un \`grant select\` seul n'enlève rien : il ajoute. C'est \`revoke\` qui ferme.
|
|
84
|
+
|
|
85
|
+
## auth.uid()
|
|
86
|
+
|
|
87
|
+
Il rend le \`sub\` du JWT du projet, donc l'identifiant de l'utilisateur final
|
|
88
|
+
connecté. Il vaut NULL avec la clé \`anon\`. Une policy qui doit exiger un compte
|
|
89
|
+
écrit \`auth.uid() is not null\`, pas \`true\`.
|
|
90
|
+
|
|
91
|
+
## API REST
|
|
92
|
+
|
|
93
|
+
\`\`\`
|
|
94
|
+
GET /db/<slug>/articles?select=id,title&status=eq.published&order=created_at.desc
|
|
95
|
+
en-têtes : apikey: <clé> et Authorization: Bearer <clé>
|
|
96
|
+
\`\`\`
|
|
97
|
+
|
|
98
|
+
Opérateurs : \`eq.\`, \`neq.\`, \`gt.\`, \`gte.\`, \`lt.\`, \`lte.\`, \`like.\`, \`ilike.\`,
|
|
99
|
+
\`in.(a,b)\`, \`is.null\`. Les relations se tirent par \`select=*,auteur(*)\`.
|
|
100
|
+
|
|
101
|
+
## Après toute DDL
|
|
102
|
+
|
|
103
|
+
\`notify pgrst, 'reload schema';\` dans la même exécution. Sans cela, la table
|
|
104
|
+
existe en base et l'API répond 404 : le défaut ressemble à une erreur de nom.
|
|
105
|
+
|
|
106
|
+
## Fonctions Edge
|
|
107
|
+
|
|
108
|
+
\`\`\`js
|
|
109
|
+
export default async function (req) {
|
|
110
|
+
return { status: 200, body: { ok: true } };
|
|
111
|
+
}
|
|
112
|
+
\`\`\`
|
|
113
|
+
|
|
114
|
+
Jamais \`new Response(...)\` : l'hôte attend un objet, pas une réponse HTTP.
|
|
115
|
+
|
|
116
|
+
## Ce qu'aucun outil ne fera pour toi
|
|
117
|
+
|
|
118
|
+
Distinguer un \`drop table\` prévu d'un \`drop table\` accidentel. Le DDL est la
|
|
119
|
+
raison d'être de \`run_sql\`, et aucune liste de mots interdits ne sépare les
|
|
120
|
+
deux. Avant un ordre destructeur, dis ce que tu vas faire et demande.`;
|