manifest.json
{
"id": "com.yourname.myplugin",
"name": "My Plugin",
"version": "1.0.0",
"author": "Your Name",
"summary": "One line describing what this does.",
"entry": "index.html",
"mode": "session",
"permissions": ["voice", "eyes"],
"minAppBuild": 17,
"files": ["index.html", "plugin.js"]
}
| Field | Required | Notes |
|---|---|---|
id |
yes | Reverse-DNS, globally unique. This is your identity: installs, permissions and updates are keyed on it. Changing it makes a different plugin. |
name |
yes | Shown in the gallery and the plugin list. |
version |
yes | Semver. Bump it for every published change. |
author |
no | Shown to the user. |
summary |
no | One line, shown in the gallery. Write it for someone deciding whether to install. |
entry |
no | Defaults to index.html. |
mode |
no | session (default), ambient or face. See below. |
permissions |
no | Array of strings. See permissions.md. Empty means your plugin is pure web content. |
intents |
no | Spoken phrases your plugin claims, e.g. ["book a cab to the office"]. Tried after every built-in command. See commands.md. |
minAppBuild |
no | Refuse to install on older builds. Use it when you depend on a newer API. |
files |
no, but see below | Every file the plugin needs, as a path relative to the manifest. Omit it and ONLY your entry file is installed. |
files
There is no zip anywhere in the pipeline: not in the upload, not on the
device. The app fetches each path in files individually.
⚠️ files is optional in the code and effectively required in practice.
Leave it out and the installer falls back to [entry]: your entry file and
nothing else. Every stylesheet and script it references is silently absent, and
the plugin installs cleanly and renders blank. Our own hello example shipped
exactly that bug. If your plugin is one self-contained file you can omit it; the
moment there are two, list them.
Listing manifest.json itself is not needed. It is always fetched, and the
checker ignores it either way. Our examples list only the entry plus their
scripts.
So files is the definition of your plugin. A file you upload but don't list is
not installed, and a file you list but don't upload is a blocking error at
upload time. A stylesheet missing from the list is the most common way a plugin
works locally and ships blank.
mode
session: full screen, opened and closed by the user. Your page owns the
display; the robot's face is not visible while you run.
ambient: replaces the idle face on the dock. Good for clocks, ambient
visuals, a custom face.
One thing to know about ambient: it yields the moment the robot leaves idle. If the user speaks to it, your plugin gives up the screen until the conversation ends. Deep idle also always wins, for battery. Do not build an ambient plugin that depends on running continuously.
face: a character. Your page IS the robot's face, in every state: idle,
waking, listening, thinking, speaking, drowsy and asleep. Unlike ambient it
does not yield when the robot is busy, because drawing those moments is the
whole job. The app tells you what is happening through the state event; see
character.md.
Unknown fields and permissions
Unknown permission strings are dropped, not rejected: a plugin built
against a newer SDK still runs on an older app, with the subset that build
understands. Combine that with minAppBuild and feature detection to decide
what "still runs" should mean for you.