Types
Le SDK donne un type à chaque objet qu'un plugin écrit ou reçoit. L'éditeur montre ces types pendant l'écriture, avec leurs commentaires. Cette page les rassemble tous, rangés par partie du plugin, et la sidebar mène droit à chacun.
Où les trouver
Les types viennent de trois chemins, un par partie du plugin :
@hodi-app/plugin-sdk/server: les types de la partie serveur et du manifeste.hodi:sdk: les types de la partie web.@hodi-app/plugin-sdk/testing: les types du kit de test.
Le chemin hodi:sdk n'est pas un paquet npm. Le fichier tsconfig.json du plugin fait pointer hodi:sdk vers @hodi-app/plugin-sdk/hodi-sdk.
import type { PluginContext, PluginRequest, ServerPlugin } from '@hodi-app/plugin-sdk/server'Partie serveur
ServerPlugin
L'objet que server/index.ts exporte par défaut. Tous ses champs sont facultatifs : un plugin n'écrit que ceux dont il a besoin. Le contexte serveur explique chacun avec un exemple.
migrations?readonly string[]Les instructions SQL qui construisent la base du plugin, dans l'ordre. Une migration publiée ne change plus jamais.
routes?PluginRoutesLes routes que la partie web appelle pour les profils du foyer. Hodi les sert sous /api/plugins/<id>/, aux seuls profils prévus par audience.
serverRoutes?PluginRoutesLes routes de l'espace Serveur. Hodi les sert sous /api/server/plugins/<id>/, à l'admin du serveur seulement.
publicRoutes?PublicRoutesLes routes ouvertes à tout le monde, même sans être connecté. Hodi les sert sous /api/public/plugins/<id>/, avec la permission publicRoutes, à 30 requêtes par minute et par adresse.
tasks?Record<string, PluginTask>Les travaux que Hodi lance tout seul, à intervalle régulier, comme effacer chaque nuit ce qui a expiré.
feeds?Record<string, (ctx) => Promise<PluginFeedArticle[]>>Pour chaque flux déclaré dans plugin.json, la fonction qui donne ses articles. La clé est celle du flux.
aiActions?Record<string, PluginAiAction>Ce que fait chaque AiButton du plugin, rangé par le nom de son action. C'est le seul endroit où ctx.ai répond.
onArticle?(article: PluginArticle, ctx) => Promise<void>Hodi l'appelle pour chaque article qu'il lit pour la première fois.
checkSignIn?(who, ctx) => Promise<string | null>Hodi l'appelle à chaque connexion, avec la permission signIn. null laisse entrer la personne ; une clé des locales du plugin refuse la connexion avec ce message.
onProfileDeleted?(deleted, ctx) => Promise<void>Hodi l'appelle quand un profil est supprimé. Le plugin y efface ce qu'il gardait de ce profil.
onAccountDeleted?(deleted, ctx) => Promise<void>Hodi l'appelle quand un compte est supprimé. Le plugin y efface ce qu'il gardait de ce compte.
onHouseholdRemoved?(removed, ctx) => Promise<void>Hodi l'appelle quand un foyer est retiré du serveur. Le plugin y efface ce qu'il gardait de ce foyer.
exportData?(who, ctx) => Promise<unknown>Ce que le plugin garde d'une personne, en JSON. Hodi l'ajoute à la copie que cette personne télécharge dans Réglages › Compte et données.
PluginContext
Le ctx que Hodi donne à chaque route, tâche et hook. C'est par lui seul que le plugin lit sa base, ses réglages ou un site. Un champ lié à une permission ne marche que si le manifeste demande cette permission.
idstringL'id du plugin, tel que plugin.json le donne.
dbPluginDatabaseLa base SQLite du plugin, que lui seul voit.
settings() => SettingValuesLes réglages du plugin, avec la valeur que l'admin du serveur a choisie à cet instant.
log(message: string) => voidÉcrit une ligne dans le journal de Hodi. hodi-plugin dev affiche aussi ces lignes.
fetchText(url, options?) => Promise<{ url; text }>Lit une page d'un site listé dans permissions.network, et renvoie son adresse finale et son texte.
http(url, options?) => Promise<{ url; status; contentType; text }>Envoie n'importe quelle requête à un site listé dans permissions.network, comme un échange OAuth. Elle envoie 64 Ko et reçoit 512 Ko au plus.
articles{ get; page }Avec la permission articles : retrouve un article que Hodi a lu, et la page de cet article.
serverSettings() => Promise<ServerSettings>Les durées choisies dans Serveur › Contenu et Sauvegardes : combien de jours Hodi garde un article, une vidéo et une copie de sauvegarde.
mail{ available; appUrl; send }Avec la permission mail : envoie un e-mail par le serveur réglé dans Serveur › E-mail, 50 par heure au plus. available dit si ce serveur est réglé.
households{ list; get; of; create; suspend; resume; remove }Avec la permission households : liste, crée, suspend et retire les foyers du serveur.
accounts{ get; findByEmail; create; checkPassword; findByLogin; addLogin }Avec la permission accounts : retrouve, crée et vérifie des comptes. Le plugin n'obtient jamais de quoi se connecter à un compte.
notify?(notice) => Promise<void>Avec la permission notify : envoie une notification à un profil, dix par jour et par profil au plus.
ai?{ ask }Avec la permission ai, pour un plugin signé : pose une question à l'IA du foyer, une seule fois par clic sur un AiButton.
PluginDatabase
Le type de ctx.db, la base SQLite du plugin. Chaque fonction prend une requête SQL et ses paramètres, écrits ? dans la requête. Une colonne BLOB se lit et s'écrit en Uint8Array.
query(sql, params?) => Record<string, SqlValue>[]Pour un SELECT qui renvoie plusieurs lignes. Elle renvoie toutes les lignes, un objet par ligne.
get(sql, params?) => Record<string, SqlValue> | nullPour un SELECT qui renvoie une seule ligne. Elle renvoie la première ligne, ou null s'il n'y en a pas.
run(sql, params?) => { changes; lastInsertRowid }Pour un INSERT, un UPDATE ou un DELETE. Elle renvoie le nombre de lignes changées et l'id de la dernière ligne ajoutée.
PluginRequest
Ce qu'une route reçoit en premier argument : la requête de la partie web, déjà lue et vérifiée par Hodi.
method'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'La méthode HTTP de la requête.
pathstringLe chemin sous celui du plugin, comme /notes/12.
paramsRecord<string, string>Les parties variables du chemin. Pour la route /notes/:id appelée sur /notes/12, params.id vaut '12'.
queryRecord<string, string>Les paramètres écrits après le ? de l'adresse.
bodyunknownLe JSON envoyé par la partie web, ou null. Pour une route raw, les octets reçus.
whoPluginCallerLa personne qui appelle la route, telle que Hodi l'a vérifiée.
PluginCaller
La personne qui appelle une route, telle que Hodi l'a vérifiée. La route peut s'y fier sans rien contrôler elle-même. Aucun cookie, aucun en-tête et aucune session n'arrive jusqu'au plugin : il ne peut pas réutiliser la session de quelqu'un.
profileIdnumber | nullLe profil ouvert sur l'appareil. Il vaut null dans l'espace Serveur, quand l'admin du serveur n'a pas de profil dans le foyer.
householdIdnumber | nullLe foyer pour lequel la personne demande. Chaque requête qui lit les données d'un foyer filtre sur cet id.
kind'adult' | 'teen' | 'child'Le profil ouvert est celui d'un adulte, d'un ado ou d'un enfant.
householdAdminbooleantrue quand la personne est l'admin de son foyer.
serverAdminbooleantrue quand la personne est l'admin du serveur.
accountIdnumber | nullLe compte connecté sur l'appareil. Il peut être différent du compte du profil ouvert, par exemple quand un parent ouvre le profil de son enfant.
PluginResponse
Ce qu'une route renvoie : du JSON, ou un zip que Hodi construit et envoie en téléchargement. Hodi ne laisse passer que les codes 2xx et 4xx : si la route plante ou répond un autre code, la partie web reçoit 500.
status?numberLe code HTTP de la réponse. Il vaut 200 par défaut.
body?unknownN'importe quelle valeur JSON, que la partie web lit avec answer.json().
headers?{ 'Cache-Control'?: string }Le seul en-tête qu'une route peut choisir : combien de temps le navigateur garde la réponse.
moveDevice?numberAvec la permission households : l'id d'un foyer. À partir de cette réponse, l'appareil lit dans ce foyer.
zip?{ filename; entries: PluginZipEntry[] }Un zip à télécharger, à la place de body. Chaque entrée est un texte, ou la première colonne d'une ligne de la base ({ path, blob: { sql, params } }).
PluginRoutes
Un objet de routes. Chaque clé a la forme 'MÉTHODE /chemin', et une partie du chemin qui commence par : est variable, comme :id. La valeur est soit la fonction de la route, soit un objet avec ces options :
handler(request, ctx) => Promise<PluginResponse>La fonction de la route.
proof?booleanMarque une route sensible. Hodi ne la lance qu'après que la personne a retapé son mot de passe dans askToConfirm(). Sans ce mot de passe, la route répond 403 sans avoir tourné.
raw?trueLa route reçoit des octets au lieu du JSON, comme une image ou un fichier.
maxBody?numberLa taille maximale de ces octets, en octets, jusqu'à 10 Mo.
PublicRoutes
Les routes ouvertes à tout le monde, avant toute connexion. Elles ont la même forme que PluginRoutes, avec trois différences : elles ne reçoivent que du JSON, de 64 Ko au plus ; request.who y vaut null ; et leur réponse peut connecter l'appareil avec ce champ en plus :
signIn?numberAvec la permission signIn : l'id d'un compte. Hodi connecte l'appareil à ce compte, à condition que cet appel vienne de créer le compte ou de vérifier son mot de passe.
PluginTask
Un travail de tasks, que Hodi lance tout seul à intervalle régulier. Une tâche ne tourne pour aucun foyer ni aucune personne : ctx y refuse donc ce qui en demande un, comme l'IA ou une notification.
everynumberLe nombre de minutes entre deux lancements, de 15 à une semaine (10 080).
run(ctx) => Promise<void>Le travail lui-même.
PluginFeedArticle
Un article qu'un flux de feeds renvoie. Dans Hodi, cet article s'ouvre avec son résumé et un lien vers son site, jamais dans le lecteur.
titlestringLe titre de l'article.
linkstringL'adresse de l'article, en https, sur le site du flux.
summary?stringLe résumé de l'article, en texte simple.
publishedAt?string | numberLa date de publication : une date ISO, ou un nombre de millisecondes depuis 1970.
image?stringL'adresse d'une image, en https.
PluginAiAction
Une action de aiActions. Hodi la lance quand quelqu'un clique sur l'AiButton qui porte son nom, et renvoie sa réponse à la fonction onAnswer de ce bouton. L'action reçoit ces champs :
request.inputunknownLa valeur que le bouton a envoyée avec le clic, 16 Ko de JSON au plus.
request.who{ householdId; profileId }L'adulte qui a cliqué, et son foyer.
PluginArticle
Un article lu par Hodi, tel que ctx.articles.get() le renvoie ou que onArticle le reçoit.
idstringL'id de l'article dans Hodi.
titlestringLe titre de l'article.
urlstringL'adresse de l'article sur son site.
sourceIdstringL'id de la source qui a publié l'article.
sourceNamestringLe nom de cette source, comme RTL info.
publishedAtstring | nullLa date de publication, ou null quand le site ne la donne pas.
status'ok' | 'unreadable' | 'failed'ok : le lecteur a le texte de l'article. unreadable : la page n'a pas de texte à lire. failed : le site n'a pas répondu.
text{ title; byline; contentHtml; textLength } | nullLe texte que le lecteur montre, quand status vaut ok. Sinon, null.
PluginHousehold
Ce qu'un plugin peut savoir d'un foyer, avec la permission households. Il ne voit jamais ce que le foyer contient : ni ses profils, ni ses sources, ni ses lectures.
idnumberL'id du foyer.
namestringLe nom du foyer.
language'fr' | 'en'La langue du foyer.
createdAtnumberLa date de création du foyer, en millisecondes depuis 1970.
suspendedbooleantrue quand le plugin qui a créé le foyer l'a suspendu.
minebooleantrue quand ce plugin a créé le foyer. Un plugin ne peut suspendre ou retirer que ses propres foyers.
photostring | nullL'adresse de la photo du foyer, à passer à HouseholdAvatar.
PluginAccount
Ce qu'un plugin peut savoir d'un compte, avec la permission accounts.
idnumberL'id du compte.
emailstringL'adresse e-mail du compte.
loginOnlybooleantrue quand le compte n'a pas de mot de passe : il a été créé avec un login du plugin, comme google:<id>, et la personne entre par le plugin.
serverAdminbooleantrue quand ce compte est celui de l'admin du serveur.
Le manifeste
Manifest
plugin.json, tel que Hodi le lit. Le manifeste explique chaque champ avec un exemple.
idstringL'id du plugin : des lettres minuscules, des chiffres et des tirets. Il ne change plus une fois le plugin publié.
namestringLe nom du plugin, 60 caractères au plus.
versionstringLa version du plugin, comme 1.2.0.
descriptionstringUne phrase qui dit ce que fait le plugin, 280 caractères au plus.
apinumberLa version de l'API des plugins pour laquelle le plugin est écrit.
audience'admin' | 'all'Qui voit le plugin dans un foyer : l'admin du foyer seulement, ou tous les profils.
providesstring[]Ce que le plugin apporte à Hodi : un genre de profil (profile-kind:child), une interface (shell:child) ou la voix (speech).
menu{ group; after?; icon? } | nullOù se place l'entrée du plugin dans Réglages : son groupe, l'entrée après laquelle elle vient, et son icône.
settingsSettingSpec[]Les réglages du plugin, que l'admin du serveur change dans Serveur › Plugins.
permissionsPermissionsCe que le plugin demande à pouvoir faire en plus de l'essentiel.
feedsPluginFeed[]Les flux d'articles que le plugin ajoute à Hodi.
memoryMbnumber | nullLa mémoire de la partie serveur, en Mo : 16 par défaut, 32 au plus.
Permissions
Ce qu'un plugin peut demander en plus de l'essentiel : sans permission, il ne peut que garder ses données dans sa base, lire ses réglages et écrire dans le journal. Hodi vérifie chaque appel. Les six dernières permissions ne sont accordées qu'à un plugin signé.
networkstring[]Les sites que le plugin peut joindre avec ctx.fetchText() et ctx.http(), écrits par leur nom d'hôte exact.
articlesbooleanOuvre ctx.articles : retrouver un article lu par Hodi.
publicRoutesbooleanOuvre les routes publiques et les écrans d'avant la connexion.
notifybooleanOuvre ctx.notify : envoyer une notification.
mailbooleanOuvre ctx.mail : envoyer un e-mail.
accountsbooleanOuvre ctx.accounts : retrouver, créer et vérifier des comptes.
householdsbooleanOuvre ctx.households : gérer les foyers du serveur.
signInbooleanOuvre la réponse signIn d'une route publique et le hook checkSignIn.
aibooleanOuvre ctx.ai : poser une question à l'IA du foyer, sur un clic.
SettingSpec
Un réglage déclaré dans settings. Hodi dessine sa ligne dans Serveur › Plugins, et le plugin lit sa valeur avec ctx.settings().
keystringLa clé du réglage. Elle sert aussi de clé à son libellé dans les locales du plugin.
type'boolean' | 'number' | 'text' | 'choice'Le genre de valeur : un interrupteur, un nombre, un texte ou un choix dans une liste.
defaultboolean | number | stringLa valeur du réglage tant que l'admin du serveur ne l'a pas changée.
min?, max?numberPour un number : la plus petite et la plus grande valeur acceptées.
maxLength?numberPour un text : le nombre de caractères au plus.
options?string[]Pour un choice : les valeurs possibles.
PluginFeed
Un flux déclaré dans le champ feeds de plugin.json. feeds explique comment le remplir.
keystringLa clé du flux. La partie serveur donne ses articles sous la même clé, dans feeds.
namestringLe nom du flux, que Hodi montre comme nom de source.
sitestringL'adresse du site dont viennent les articles.
lang'fr' | 'en'La langue des articles.
topicstringLe thème de l'actu où les articles s'affichent, comme tech.
Partie web
WebPlugin
L'objet que definePlugin() reçoit : un champ par emplacement, tous facultatifs. Les emplacements expliquent chacun avec un exemple.
settingsPage?ComponentTypeLes options du plugin, dans Serveur › Plugins, sous les réglages que Hodi dessine.
page?ComponentType<{ layout }>menuIcon?ComponentType<{ size? }>L'icône de l'entrée du plugin dans Réglages, à 18 px.
menuHint?ComponentTypeLa ligne grise sous le nom de cette entrée.
serverSections?ServerSection[]overviewCards?OverviewCardSlot[]readerAction?ComponentType<{ article: ReaderArticle }>Un composant au bout de chaque article, dans le lecteur.
signInExtras?ComponentType<{ layout; signedIn }>Un composant sous le formulaire de Connexion.
afterSignIn?ComponentType<AfterSignInProps>Une étape juste après la connexion, avant d'entrer dans l'app.
whoIsReadingFooter?ComponentType<{ layout }>Un composant sous Qui lit ?, à côté de Gérer les profils.
ageConsent?AgeConsentsetupSteps?SetupStep[]Les étapes du plugin dans l'installation de Hodi.
accountRows?ComponentType<{ layout }>avatarMenu?AvatarMenuItem[]ServerSection
Une section de serverSections : une entrée dans la colonne de l'espace Serveur, et la page qu'elle ouvre.
idstringL'id de la section. Son adresse est #/server/plugin/<id du plugin>/<id>.
labelstringLe nom de la section : une clé des locales du plugin.
pageComponentType<{ layout }>La page de la section, construite avec SectionPage.
icon?ComponentType<{ size? }>L'icône de l'entrée, une icône Lucide.
hint?ComponentTypeLa ligne grise sous le nom de l'entrée, sur un téléphone.
OverviewCardSlot
Une carte de overviewCards, dans Serveur › Vue d'ensemble.
idstringL'id de la carte : des lettres minuscules, des chiffres et des tirets.
size'narrow' | 'wide' | 'full'La largeur de la carte sur un ordinateur : un tiers, deux tiers ou toute la largeur.
componentComponentType<{ from; to }>Le contenu de la carte, construit avec OverviewCard. Il reçoit le début et la fin de la période choisie.
SetupStep
Une étape de setupSteps. Pour placer une étape de Hodi parmi celles du plugin, une entrée peut aussi être { id, core: 'mail' }.
idstringL'id de l'étape. Après un rechargement, l'installation reprend à l'étape qui porte cet id.
titlestringLe titre de l'étape : une clé des locales du plugin.
introstringLa phrase sous le titre : une clé des locales du plugin.
pageComponentType<SetupStepProps>Le contenu de l'étape.
AfterSignInProps
Les props que reçoit le composant de afterSignIn.
layout'desktop' | 'phone'La taille de l'écran.
done() => voidTermine l'étape et fait entrer la personne dans l'app.
AvatarMenuItem
Une entrée de avatarMenu, dans le menu qui s'ouvre sous l'avatar.
idstringL'id de l'entrée.
labelstringLe texte de l'entrée : une clé des locales du plugin.
onClick() => voidCe que fait un clic sur l'entrée.
icon?ComponentType<{ size? }>L'icône de l'entrée, à 16 px.
ReaderArticle
L'article ouvert dans le lecteur, que reçoit le composant de readerAction. Pour en savoir plus sur cet article, la partie serveur le retrouve par son id avec ctx.articles.get().
idstringL'id de l'article dans Hodi.
titlestringLe titre de l'article.
urlstringL'adresse de l'article sur son site.
sourceIdstringL'id de la source qui a publié l'article.
sourceNamestringLe nom de cette source, comme RTL info.
AgeConsent
Le texte de ageConsent. C'est soit une clé des locales du plugin, soit un objet { text, links } : dans le texte, chaque balise <nom> devient un lien vers links[nom], ouvert dans un nouvel onglet.
textstringLe texte : une clé des locales du plugin.
linksRecord<string, string>L'adresse de chaque lien, par le nom de sa balise. Une adresse est sur ce serveur, ou en https.
Confirmation
Ce que reçoit askToConfirm(). Avec proof, la fenêtre demande aussi le mot de passe, et onConfirm reçoit une Proof qui contient password et code.
titlestringLa question posée, comme Effacer l'historique ?.
messageReactNodeCe qui se passera après le clic, en une phrase.
labelstringLe texte du bouton qui confirme.
onConfirm(proof: Proof) => void | Promise<void>Ce que fait le clic sur ce bouton. Si la promesse échoue, la fenêtre reste ouverte.
look?'danger' | 'primary'La couleur du bouton : rouge par défaut, pour une chose qui efface.
proof?{}Demande le mot de passe de la personne avant de confirmer, pour une route avec proof: true.
onCancel?() => voidCe qui se passe quand la fenêtre se ferme sans le bouton.
PluginLinks
Ce que renvoie usePluginLinks() : les adresses des écrans de Hodi et du plugin, à mettre dans un lien.
frontPagestringL'adresse de la une.
article(id) => stringL'adresse d'un article dans le lecteur, par son id.
settingsstringL'adresse de Réglages.
pluginsstringL'adresse de la page du plugin dans Serveur › Plugins.
page(path?) => stringL'adresse de la page du plugin dans Réglages, ou d'un de ses écrans avec path.
serverPage(section, path?) => stringL'adresse d'une section du plugin dans l'espace Serveur, ou d'un de ses écrans avec path.