Aller au contenu
↑↓Naviguer ↵Ouvrir escFermer

Créer une extension

Utilisez @openchamber/sdk pour ajouter votre propre panneau à OpenChamber. Une extension est une petite page web qu’OpenChamber affiche dans la barre latérale droite. Elle communique avec l’application via connectHost() : elle peut lire le projet et la session en cours, afficher des toasts, insérer du texte dans la zone de chat, attacher une tâche à une session et, avec l’accord de l’utilisateur, démarrer des sessions et envoyer des prompts.

Les extensions fonctionnent dans OpenChamber web et desktop. VS Code et mobile ne les chargent pas encore.

Ce qu’est une extension

Un dossier avec trois fichiers :

  • package.json avec un bloc openchamber (le manifeste)
  • panel/index.html, la page qu’OpenChamber affiche
  • panel/main.js, votre script, compilé en un seul fichier classique (un IIFE, pas un module ES, car la page se charge dans une iframe sandboxée)

OpenChamber ne compile jamais votre code. Livrez le fichier .js déjà construit. Le SDK inclut une commande de bundling ; n’importe quel bundler qui produit un IIFE convient aussi.

Fenêtre de terminal
npm install @openchamber/sdk
bunx openchamber-guest-bundle panel/main.ts panel/main.js

La commande de bundling tourne sur Bun. Sans Bun, utilisez esbuild ou tout autre bundler avec --format=iife --platform=browser.

Une extension complète et fonctionnelle, avec ses trois fichiers, se trouve sur la page Exemple. Copiez-la et changez l’API et la liste.

Faites pointer index.html vers main.js avec un <script src="main.js"></script> classique.

L’installer pendant le développement

  1. Lancez OpenChamber sur web ou desktop.
  2. Ouvrez Paramètres → Extensions.
  3. Collez le chemin absolu de votre dossier et cliquez sur Ajouter.

OpenChamber lit le manifeste et affiche une boîte de dialogue qui liste ce que l’extension demande (voir Capacités). Approuvez, et votre icône apparaît dans la barre latérale. Cliquez dessus et votre page se charge.

Vous pouvez aussi ajouter un .zip local ou un lien https vers un dépôt git ou un fichier zip. Seule une installation git peut se mettre à jour : montez version dans package.json, poussez, et OpenChamber vérifie peu après son ouverture et toutes les quelques heures, prévient l’utilisateur une fois et propose la mise à jour dans Paramètres → Extensions. Un lien vers openchamber://extensions/install?url=<encoded repository URL> sur n’importe quelle page ouvre l’application de bureau avec ce dépôt dans la boîte de dialogue d’ajout, vous pouvez donc proposer un bouton d’installation. Un #tag ou #branch dans l’URL fixe ce qu’elle suit. Ceux-ci sont copiés dans le dossier de données d’OpenChamber et exécutés depuis cette copie, donc livrez les fichiers construits, pas node_modules ni les sources TypeScript. Supprimer l’extension supprime la copie. Une installation depuis un dossier s’exécute directement depuis votre dossier, vous pouvez donc modifier, reconstruire et recharger.

Manifeste

{
"name": "@acme/hello",
"version": "1.0.0",
"openchamber": {
"apiVersion": 1,
"engines": { "openchamber": ">=1.24.0" },
"id": "acme-hello",
"name": "Hello",
"icon": "window",
"contributes": {
"panel": { "entry": "panel/index.html" },
"attach": "dialog",
"page": true,
"capabilities": ["prompt", "sessions"],
"integration": {
"name": "Acme",
"description": "Tasks from Acme",
"token": {
"apiOrigin": "https://api.acme.example",
"account": { "path": "/me", "name": "login" },
"scheme": "bearer"
},
"settings": [{ "id": "list-id", "label": "List ID" }]
}
}
}
}

version est obligatoire et doit être un semver. Paramètres → Extensions l’affiche sur la carte.

apiVersion vaut 1. OpenChamber refuse toute autre valeur.

engines.openchamber est optionnel. Indiquez la version d’OpenChamber la plus ancienne avec laquelle votre extension fonctionne, sous la forme 1.24.0 ou >=1.24.0. Les versions plus anciennes refusent de l’installer au lieu d’échouer plus tard.

id, name et icon nomment l’extension. id est en kebab-case et doit être unique parmi les extensions installées. icon est un nom Remixicon (RiWindowLine devient window) ou un fichier SVG dans votre dossier, comme icon.svg. contributes.panel est le panneau de l’extension dans la barre latérale : son entry est le fichier HTML dans votre dossier. Une extension sans panneau dans la barre latérale omet panel. Les anciens paquets gardent id, name et icon dans panel, et cela continue de fonctionner.

contributes.attach est optionnel. Il ajoute votre extension au menu + à côté de la zone de chat, pour que l’utilisateur puisse choisir une tâche et l’attacher à une session.

  • "dialog" ouvre votre page dans une fenêtre
  • true ou "panel" ouvre plutôt le panneau de la barre latérale
  • { "mode": "dialog", "entry": "panel/attach.html" } ouvre dans la fenêtre une page séparée à vous, pour que le sélecteur n’ait pas à partager de code avec le panneau de la barre latérale
  • sans cette clé, l’extension n’apparaît pas dans ce menu

ctx.surface vaut panel, dialog, page, status ou block. Un clic sur un élément attaché place ses données dans ctx.item, sinon cette valeur est null.

contributes.page: true propose votre panneau en plein écran dans le menu Pages des extensions au-dessus des sessions. { "entry": "panel/page.html", "title": "Board" } utilise un HTML distinct et un titre facultatif. panel.entry reste obligatoire, avec le même bac à sable et les mêmes permissions. Seul l’utilisateur ouvre la page. Recharger, mettre en pause, supprimer ou changer de serveur la ferme.

Pour un tableau, utilisez host.storage et les listes de projets, worktrees et sessions avec leurs états en direct. startSession peut viser un autre projet sans fermer le tableau. Voir Host API.

contributes.integration est optionnel. Il ajoute une carte dans Paramètres → Intégrations où l’utilisateur connecte un compte. Voir Comptes et réseau.

contributes.service est optionnel. Il déclare un service local (un processus qu’OpenChamber démarre à côté de l’extension) pour ce qu’une page web ne peut pas atteindre, comme un socket Docker. Ce processus s’exécute avec l’accès complet de l’utilisateur et sans sandbox, c’est pourquoi la boîte de dialogue d’approbation en avertit ; déclarez-en un seulement quand la page ne peut pas faire le travail. Voir le fichier GUEST_SERVICES.md du package. Un service avec provides: ["browser"] peut aussi remplacer le navigateur de l’agent : il répond aux actions browser.* sur le serveur, les agents naviguent donc sans application de bureau ouverte, et l’utilisateur le choisit dans Réglages → Outils OpenChamber. Un tel service n’a pas besoin de panneau. Un service peut aussi être informé de ce qui se passe dans les sessions de l’utilisateur : déclarez contributes.sessionEvents à côté, et OpenChamber envoie au service chaque tour qui se termine ou échoue et chaque permission ou question que l’agent demande, depuis chaque projet, sans fenêtre ouverte. Voir Événements de session. Et un service avec provides: ["notifications"] transmet les notifications de l’utilisateur à côté du bureau et du téléphone, vers Slack, Matrix ou un serveur à vous ; l’utilisateur connecte le compte dans Réglages → Intégrations et le jeton reste dans OpenChamber. Voir Canaux de notification. Un service avec provides: ["tracker"] est un outil de suivi des tâches à côté de Linear, avec sa carte de compte sous Comptes d’extensions ; voir Outils de suivi. Et un service avec provides: ["quota"] rapporte la consommation à côté des fournisseurs intégrés, en lisant les identifiants qu’OpenCode conserve pour les fournisseurs qu’il liste dans le manifeste ; voir Fournisseurs de consommation.

Capacités

Dessiner un panneau et lire la session en cours ne demandent aucune permission. Tout ce qui agit au nom de l’utilisateur en demande une. Listez ces actions dans contributes.capabilities :

CapacitéCe qu’elle autorise
promptenvoyer des messages dans la session de l’utilisateur (prompt({ send: true }), startSession avec text)
sessionslister les projets, worktrees et états des sessions ; créer et ouvrir des sessions dans les projets enregistrés
fileslire et écrire des fichiers dans le projet ouvert (readFile, writeFile, listDir, stat avec un chemin relatif)
modelgénération de texte ponctuelle avec le Small Model de l’utilisateur (generate), en dehors de toute session

Onze autres sont ajoutées pour vous : network quand vous déclarez une integration, service quand vous déclarez un service, filesystem quand vous déclarez des motifs filesystem, origins quand vous déclarez origins, conversation quand une action de session demande les messages (voir Actions, commandes et le badge), transcript quand sessionEvents ou sessionControl demande content (voir Événements de session), sessionControl quand votre service agit sur des sessions (voir Appeler OpenChamber), sessionAnswers quand il répond à la place de l’utilisateur aux demandes d’autorisation et aux formulaires de l’agent (sessionControl: { answers: true }), instructions quand il complète ce que lisent les agents, et skills quand vous déclarez contributes.skills, un dossier du paquet contenant des skills qui atteignent chaque agent tant que l’extension est active, et opencodePlugin quand vous déclarez contributes.opencodePlugin (voir plus bas).

L’utilisateur voit la liste complète une seule fois, à l’installation de l’extension, et l’approuve ou supprime l’extension. Si une nouvelle version demande davantage, la boîte de dialogue réapparaît. Un appel qui a besoin d’une capacité non approuvée par l’utilisateur échoue avec NOT_GRANTED.

Un plugin OpenCode dans votre paquet

Une extension peut embarquer un plugin OpenCode, pour que les gens installent une seule chose au lieu de deux. Déclarez "opencodePlugin": "plugin", un dossier du paquet avec index.js ou server.js à sa racine. OpenCode ne lit pas main : pour une entrée ailleurs, indiquez name et exports dans le package.json du dossier. L’entrée exporte par défaut { id, setup }, la forme qu’OpenCode charge ; elle n’importe rien, car OpenCode la charge avec un simple import() depuis votre dossier, où rien n’est installé. L’input d’un outil est du JSON Schema brut, et son execute répond { content }.

{
"contributes": {
"panel": { "id": "teapot-tools", "name": "Teapot Tools", "icon": "cup" },
"opencodePlugin": "plugin"
}
}

Le dialogue d’approbation gagne une ligne, Ajouter un plugin à OpenCode, qui dit clairement que le code du plugin s’exécute dans OpenCode avec tous les accès de l’utilisateur. Une fois autorisé, OpenChamber liste votre dossier dans la configuration OpenCode qu’il gère, le même fichier que pour contributes.skills ; l’OpenCode qu’il fait tourner surveille ce fichier et charge le plugin en quelques secondes. Désactiver ou supprimer l’extension le retire de la même façon. Rien n’est écrit dans l’opencode.json de l’utilisateur : ce fichier est le sien, et un plugin qui s’y trouverait survivrait à votre extension et tournerait dans chaque OpenCode de la machine.

Là où ce fichier n’atteint pas OpenCode, l’écran de l’extension le dit et propose Ajouter à la configuration globale, qui écrit votre dossier dans la configuration OpenCode globale de l’utilisateur comme une entrée qui lui appartient désormais. C’est le cas quand OpenChamber n’a pas démarré l’OpenCode auquel il parle, ou quand l’environnement du serveur définit son propre OPENCODE_CONFIG (le plugin se charge alors au prochain démarrage d’OpenCode). Un OpenCode sur une autre machine ne peut pas charger un dossier de celle-ci ; l’écran le dit et ne propose rien. Un plugin qui ne se charge pas est signalé par OpenCode dans Réglages → Plugins quand il y a une entrée ; votre extension continue de fonctionner dans tous les cas. L’installation refuse un paquet dont le dossier du plugin manque ou dont le package.json ne nomme aucun fichier d’entrée.

VS Code et les applications mobiles ne chargent aucune extension, donc aucun plugin ne les atteint. Une session dans un espace isolé fait tourner son propre OpenCode, qui ne lit jamais la configuration de l’hôte. Le mode entreprise traite le plugin comme un service local : il ne s’installe que depuis un dépôt listé par l’administrateur. Le SDK a un exemple fonctionnel, examples/opencode-plugin.

Générer du texte

Avec la capacité model, host.generate demande au Small Model de l’utilisateur une réponse ponctuelle : un résumé, un titre, un brouillon. Rien n’entre dans une session, aucun historique n’est conservé, et l’extension ne choisit jamais de fournisseur. OpenChamber utilise le même modèle que pour son propre travail en arrière-plan, choisi dans Paramètres → Sessions → Small Model, ou le petit modèle du fournisseur avec lequel l’utilisateur travaille, ou le modèle par défaut de sa configuration OpenCode.

const { text } = await host.generate({
prompt: task.description,
system: "Write a one-line summary. Return only the summary.",
maxOutputTokens: 200,
});

Quand aucun modèle n’est disponible, l’appel échoue avec NO_MODEL ; un modèle qui a échoué donne MODEL_FAILED. Les prompts sont limités à 64 000 caractères, et l’appel attend jusqu’à 90 secondes. L’utilisateur voit cette permission sous le nom « Utiliser ton Small Model » et cela lui coûte des tokens, donc gardez les prompts courts et déclenchez l’appel sur un clic, pas à chaque frappe.

Comptes et réseau

Votre page tourne dans un sandbox et ne peut pas accéder au réseau d’elle-même : scripts, styles, images, polices et médias ne se chargent que depuis votre paquet, et fetch n’atteint que les fichiers de votre paquet. Livrez les polices et les images dans le paquet plutôt que de les charger depuis un CDN.

Pour parler à un service extérieur, déclarez une integration et OpenChamber fait les appels pour vous via host.request, avec le token de l’utilisateur attaché. Le token n’atteint jamais votre page.

Pour tout le reste dont votre page a besoin directement, comme des polices ou des images depuis un CDN ou une API publique, listez jusqu’à 8 origines https :

"origins": ["https://fonts.example.com"]

L’utilisateur les voit dans la boîte de dialogue d’approbation comme des adresses auxquelles votre extension peut envoyer des données et dont elle peut en recevoir, et les approuve à nouveau si une mise à jour en ajoute une. Les origines approuvées sont ouvertes à fetch, aux images, aux polices, aux styles et aux médias, mais jamais aux scripts : le code doit être livré dans votre paquet. L’origine de la page est null, donc pour lire une réponse fetch, le serveur doit autoriser CORS pour cette origine.

En mode entreprise, une extension avec origins, une integration ou un service ne s’installe que depuis un dépôt Git listé par l’administrateur. Si votre extension peut s’en passer, elle reste installable pour ces équipes. Pour en développer une sur une telle machine, demandez à l’administrateur allowLocalExtensions, qui vous permet de l’installer depuis son dossier.

  • token : l’utilisateur colle un token d’API sur la carte Paramètres → Intégrations. apiOrigin est la seule origine que request peut appeler. account est optionnel : un chemin GET et un nom de champ, pour que la carte affiche qui est connecté. scheme indique comment le token est envoyé ; voir le tableau ci-dessous. Vérifiez dans la documentation de l’API du fournisseur quel en-tête elle attend.
  • oauth : l’utilisateur colle un client id, et OpenChamber exécute le flux d’autorisation. Nécessite authorizeUrl, tokenUrl et apiOrigin.
  • host: { "provider": "linear" } : réutilise le compte Linear déjà connecté dans OpenChamber. Pas besoin de client id.

Ce que chaque scheme envoie :

schemeEn-tête envoyé par OpenChamberQuand l’utiliser
omisAuthorization: <token>l’API documente un token sans préfixe dans l’en-tête
"bearer"Authorization: Bearer <token>l’API documente un token bearer ou un personal access token
"basic"Authorization: Basic base64(username:token)l’API documente HTTP Basic auth avec un nom d’utilisateur (souvent un e-mail) et un token d’API ; la carte demande les deux, et usernameLabel nomme le premier champ

settings ajoute des champs texte simples à la carte. Leurs valeurs arrivent dans ctx.settings. Un champ avec "secret": true est masqué sur la carte et n’atteint jamais une page ; votre service le lit avec readSettings().

Fichiers

Votre page ne peut pas toucher le disque elle-même. OpenChamber lit et écrit pour elle, dans les limites approuvées par l’utilisateur.

  • Un chemin relatif (README.md, src/index.ts, .) désigne le projet ouvert. Nécessite la capacité files. Sans projet ouvert, c’est NO_DIRECTORY.
  • Un chemin qui commence par / ou ~/ désigne n’importe quel autre endroit. Il doit correspondre à l’un des motifs que vous déclarez dans contributes.filesystem, et l’utilisateur voit exactement ces motifs dans la boîte de dialogue d’approbation :
"filesystem": ["~/.config/opencode/opencode.json", "~/notes/**"]

Les motifs utilisent * pour un segment de chemin et ** pour n’importe quelle profondeur. Tout ce qui est en dehors donne BAD_PATH, même après approbation. .. n’est jamais autorisé.

const config = await host.readFile("~/.config/opencode/opencode.json");
await host.writeFile("~/.config/opencode/opencode.json", nextJson);
const { entries } = await host.listDir(".");

Les écritures sont atomiques : OpenChamber écrit un fichier temporaire puis le renomme, donc un lecteur ne voit jamais un fichier à moitié écrit. Les fichiers sont lus et écrits en texte UTF-8, jusqu’à 2 Mo.

Une section dans Work Status

contributes.statusSection ajoute votre propre section au panneau Work Status à côté du chat. Les utilisateurs l’activent, la désactivent et la déplacent dans la liste des sections de ce panneau, comme les sections intégrées.

"statusSection": { "entry": "status/index.html", "title": "Recent commits", "height": 160 }
  • true réutilise panel.entry.
  • entry est une page HTML séparée dans votre dossier. Avec elle, panel.entry n’est pas nécessaire, et l’extension peut n’être que cette section, sans icône dans la barre latérale.
  • title remplace le nom du panneau dans l’en-tête de la section, jusqu’à 60 caractères.
  • height est la hauteur de départ en pixels, de 24 à 320 (120 par défaut). Appelez host.setHeight() quand la taille de votre contenu change.

Votre page voit ctx.surface à status. OpenChamber ne la charge que tant que le panneau Work Status est visible et votre section dépliée, et la décharge quand l’utilisateur replie la section. N’y gardez donc rien que vous ne puissiez reconstruire. Une extension réduite à une section peut utiliser des permissions, un service, une intégration et des fichiers, mais pas page, attach, actions ni commands. Web et bureau uniquement. L’exemple git-graph-status est une section complète.

Un éditeur pour votre type de fichier

contributes.fileEditors ouvre les fichiers dans votre page au lieu de l’éditeur de texte. Quand l’utilisateur ouvre un fichier correspondant dans Files, OpenChamber y affiche votre éditeur, avec la même bascule vers la source, le même bouton plein écran et le même bouton d’enregistrement que les visionneuses intégrées.

"fileEditors": [
{ "id": "canvas", "title": "Excalidraw", "match": ["*.excalidraw", "*.excalidraw.md"], "entry": "editor/index.html" }
]
  • match ne compare que les noms de fichiers, sans tenir compte de la casse. * correspond à n’importe quelle suite de caractères et ? à un seul. Un motif ne peut pas contenir / ni se composer uniquement de jokers.
  • entry est une page HTML dans votre dossier. panel.entry n’est pas nécessaire, et l’extension peut n’être que l’éditeur, sans icône dans la barre latérale.
  • content vaut "text" (par défaut) ou "binary", voir plus bas.
  • Jusqu’à 8 éditeurs par extension, avec jusqu’à 16 motifs chacun.

OpenChamber garde la main sur le fichier. Votre page ne le lit ni ne l’écrit jamais et n’a besoin d’aucune permission pour cela. OpenChamber vous transmet le texte, vous demande le texte modifié au moment d’enregistrer, et gère Cmd/Ctrl+S, l’enregistrement automatique, l’avertissement de modifications non enregistrées, les fins de ligne et les modifications faites sur le disque :

import { connectHost, createFileSaveTracker } from "@openchamber/sdk";
const host = connectHost();
let tracker = createFileSaveTracker(null);
host.onFileOpen((file) => {
if (file.encoding !== "text") return; // un éditeur de texte reçoit toujours du texte
load(file.content); // et aussi file.path, file.name, file.readOnly
tracker = createFileSaveTracker(versionOf(currentText()));
});
// Appelez ceci après chaque modification. `edited` retient l'enregistrement automatique jusqu'à ce que l'utilisateur s'arrête.
const changed = () => host.reportFileChange(tracker.observe(versionOf(currentText())));
host.onFileSnapshot(() => ({ content: currentText(), version: versionOf(currentText()) }));
// Les modifications faites pendant qu'OpenChamber écrivait laissent le fichier non enregistré.
host.onFileSaved((version) => host.reportFileChange({ dirty: tracker.markSaved(version), edited: false }));

version est n’importe quelle courte chaîne qui change quand le document change, comme un hash. Si un fichier n’est pas quelque chose que votre éditeur sait ouvrir, appelez host.reportFileUnsupported() et OpenChamber affiche sa source avec un avertissement. Les fichiers de plus de 20 MB restent dans la vue source.

Fichiers binaires

Indiquez "content": "binary" pour ouvrir des fichiers qui ne sont pas du texte, comme des tableurs, des documents ou des images. Votre éditeur reçoit les octets du fichier et renvoie des octets ; tout le reste fonctionne de la même façon.

host.onFileOpen((file) => {
if (file.encoding !== "binary") return;
open(file.bytes); // un Uint8Array, exactement comme sur le disque
});
host.onFileSnapshot(() => ({ bytes: currentBytes(), version: versionOf(currentBytes()) }));

Un éditeur binaire peut prendre en charge n’importe quel fichier correspondant à ses motifs, y compris ceux dont OpenChamber affiche lui-même un aperçu, comme les images et les PDF. Il n’a pas de vue source, et quand l’utilisateur le passe en plein écran ou l’en sort avec des modifications non enregistrées, OpenChamber les enregistre d’abord. Lire et écrire le format revient à votre éditeur : OpenChamber réécrit exactement les octets que vous renvoyez.

Votre page voit ctx.surface à file. OpenChamber la recharge, en lui retransmettant le fichier, quand le fichier change sur le disque, que l’utilisateur annule ses modifications, ou que l’éditeur passe en plein écran ou en sort. La première extension activée dont un motif correspond l’emporte, avant les aperçus propres à OpenChamber. Une extension réduite à un éditeur peut utiliser des permissions, un service, une intégration et des fichiers, mais pas page, attach, actions ni commands. Web et bureau uniquement. L’exemple checklist-editor est un éditeur complet.

Actions, commandes et le badge

Au-delà de la barre latérale et du menu +, une extension peut apparaître à trois autres endroits. Tous transmettent à l’extension un item, de la même façon qu’un clic sur une puce.

Actions sur les messages et les sessions. Déclarez des entrées de menu et OpenChamber les affiche à côté des entrées intégrées :

"actions": [
{ "id": "create-task", "label": "Create task", "icon": "add-line", "where": "message", "roles": ["assistant"] },
{ "id": "summarize", "label": "Summarize session", "where": "session", "payload": ["messages"] }
]

where accepte aussi selection (texte sélectionné dans un message), file (une ligne dans Files) et diff (lignes sélectionnées d’un fichier ou d’un diff). Leurs éléments portent un kind du même nom : selection contient le texte, file le chemin, diff le chemin et la plage de lignes. Sur une sélection, display indique comment l’action s’affiche : label (la valeur par défaut), icon avec le libellé comme infobulle, ou both.

Une action de message ouvre votre extension avec un ctx.item de kind: "message" : l’id et le titre de la session, le dossier du projet, l’id du message, son rôle et son texte. Une action de session l’ouvre avec kind: "session" et l’id, le titre et le dossier de la session. Ajoutez "payload": ["messages"] et l’élément porte aussi toute la conversation, du plus ancien au plus récent, le même texte que produit l’export Markdown. Cela demande la capacité conversation, que l’utilisateur voit comme une ligne à part dans la boîte de dialogue d’approbation. item.action vous dit quelle entrée a été cliquée.

Commandes slash qui attachent. Déclarez une commande et traitez-la dans la page :

"commands": [{ "name": "task", "description": "Attach a task by id" }]
host.onResolve(async ({ command, args }) => {
const task = await findTask(args.trim());
return task ? { providerId: "acme-tasks", id: task.id, title: task.title, url: task.url } : null;
});

Quand l’utilisateur tape /task ABC-12 dans la zone de chat, OpenChamber demande à votre extension de le résoudre et attache ce que vous renvoyez sous forme de puce, sans rien ouvrir. Renvoyez null pour “rien trouvé”. Si votre panneau est fermé, OpenChamber le charge en arrière-plan pour cet appel. Un nom déjà utilisé par OpenChamber ou OpenCode est ignoré.

Badge sur l’icône de la barre latérale. host.setBadge(3) place un point sur votre icône et affiche le nombre dans son infobulle, par exemple les tâches ouvertes ; host.setBadge(null) l’efface. Ouvrir le panneau l’efface aussi.

Vos outils dans le chat

Quand votre plugin OpenCode ou serveur MCP ajoute un outil, le chat affiche ses appels avec une icône générique et une sortie brute. Déclarez plutôt à quoi ils doivent ressembler, sans code :

"tools": [
{
"match": "mcp.jira.*",
"name": "Jira",
"icon": "task-line",
"title": "{input.key}",
"subtitle": "{output.status}",
"output": "table",
"columns": ["key", "summary", "status"]
}
]

match est le nom de l’outil tel qu’OpenCode le rapporte ; un * final correspond à un préfixe. icon est un nom Remixicon ou un fichier .svg dans votre paquet, comme panel.icon. title et subtitle sont des gabarits sur input, output et metadata de l’appel. output choisit comment le corps est rendu : text, json (un arbre), markdown, code avec un language, table avec columns (les lignes viennent du tableau de sortie ou de output.items) ou auto pour le rendu par défaut. Un match exact l’emporte sur un joker, et en cas d’égalité la première extension installée gagne. Aucune permission n’est nécessaire : cela ne change que la façon dont des données déjà dans le chat sont dessinées.

Une extension qui ne fait que styliser des outils n’a besoin d’aucune page : omettez panel.entry et elle n’aura pas d’icône dans la barre latérale, juste une carte dans Paramètres → Extensions. Sans page, elle peut déclarer tools et rien d’autre. L’autre exception est une section Work Status ou un éditeur de fichiers avec son propre entry, voir plus haut.

Premières lignes dans le panneau

import { connectHost } from "@openchamber/sdk";
const host = connectHost();
host.onReady((ctx) => {
document.body.dataset.theme = ctx.theme.mode;
document.body.dataset.surface = ctx.surface;
});

connectHost ne fonctionne qu’à l’intérieur d’OpenChamber. Ouverte comme un simple fichier, chaque appel est rejeté avec HOST_UNAVAILABLE.

Construisez d’abord le panneau avec les composants de @openchamber/sdk/ui : boutons, champs, listes déroulantes, onglets, listes et plus, stylés avec les couleurs et polices de l’application, pour que le panneau se sente partie intégrante d’OpenChamber. N’écrivez votre propre HTML et CSS que pour ce que le kit ne couvre pas. Voir Kit UI.

Voir aussi

  • Extensions pour installer par SSH et choisir l’identité Git du serveur
  • API hôte pour chaque méthode de connectHost, ses limites et les codes d’erreur
  • Kit UI pour les composants
  • Exemple pour une extension complète en trois fichiers à copier