plugin.json and, usually, one TypeScript module. Cordial runs the module as a sandboxed Deno process and talks to it in newline-delimited JSON over stdin and stdout. A plugin cannot open a file, a socket or a subprocess, so asking Cordial to do things through capabilities is the only surface there is. Why: ADR-003, ADR-007.
A manifest with no entry is a plugin made of data: a texture pack, a set of flags, a preferences page. Cordial logs data only, nothing to start and carries on (ADR-021). The rest of this page is about the kind that runs.
The smallest plugin that works
1
Create the files
plugin.json:main.ts:2
Grant it log
Until something is granted the plugin is not refused, it is never started: Grants are per profile (ADR-013), so approving a plugin in a throwaway profile does not approve it in the one you play on.
plugin hello: no capabilities granted, not started. Flip the log switch on the plugin’s row in Settings, or write ~/.local/share/cordial/profiles/default/plugin-grants.json:3
Run it
Start Cordial and look for the plugin’s line in the client output:
Keep the directory name and the manifest
id identical. Cordial does not check. Grants, settings, events and flags.set key off the manifest id, but the flag layer read back at startup and the enablement check key off the directory name. A plugin in hello-dev/ calling itself hello writes flags into a hello/ directory that nothing installed.plugin.json
Only id is required.
Discovery is quiet in two cases:
- A directory with no
plugin.jsonis passed over without comment. One whoseplugin.jsonwill not parse is announced asplugin: /path/plugin.json is not loadable (…). - A directory whose name starts with
.is skipped, because the installer stages half-unpacked plugins there.
Capability names are not method names
flags.write is a capability: what a user grants, and the string a refusal names. flags.set is the method it gates. Calling flags.write gets unknown method "flags.write", an error rather than a permission error. settings.read is a capability and settings.get is the method.
The full list and the method each one gates is on Capabilities and grants.
Test without packaging
You do not need an archive, an index or an install step. Cordial discovers plugins from a directory and follows symlinks to directories. Give the run its own data root so you stay off the profile you play on:/tmp, which is tmpfs and comes out of RAM, and delete it afterwards. just dev and just client take the same variable.
Two development switches exist. Neither is supported configuration:
Seeing a plugin’s output
If a plugin subscribes to events and receives none, rule out that there are none to receive. A plugin event is written straight into your stdin with no queue, so nothing arrives if the publisher is not running or was never granted
events.publish. A core event is addressed by capability, so check lifecycle.read was granted in this profile, and which of the events you are waiting for are actually published (events).
Next
Protocol and sandbox
messages, pushes, what Deno permits, how a plugin starts.
Capabilities and grants
the list, default deny, refusals, per-profile grants.
Events
core events and plugin-declared events.
FastFlags
read the resolved set, contribute a layer.
The rest of the surface
notifications, presence, URLs, session state, settings, preferences, asset overlays.
What you cannot do
the walls.
plugins/README.md covers versions, dependencies and publishing. Decisions: ADR-008 (Deno), ADR-018 (sub-sandbox), ADR-020 (preferences).