Plugin SDK
Verified against v0.21.0 · sdk/plugin-sdk/src/host.js, sdk/plugin-sdk/src/cli.js, sdk/plugin-sdk/src/bundle.js, sdk/plugin-sdk/src/settings.js, sdk/plugin-ui/parley.css, internal/api/plugindev.go, internal/api/plugins.go, internal/plugin/describe.go
@parley/plugin-sdk is the guest library: capability-aware methods that fail
fast in the guest when a grant is missing, then still call the Extism bridge,
which remains the enforcing check. @parley/plugin-ui is optional CSS: add a
class (parley-btn, parley-panel, parley-input) and the rule uses the host
color tokens (--color-accent, --color-surface, --color-ink).
node sdk/plugin-sdk/src/cli.js scaffold ./my-pluginnode sdk/plugin-sdk/src/cli.js verify ./my-pluginnode sdk/plugin-sdk/src/cli.js keygen ./release # release.key (0600) and release.pubnode sdk/plugin-sdk/src/cli.js pack ./my-plugin --key ./release.keynode sdk/plugin-sdk/src/cli.js sign bundle.parley --key ./release.keynode sdk/plugin-sdk/src/cli.js types ./my-plugin # TypeScript for the settings schemanode sdk/plugin-sdk/src/cli.js dev ./my-pluginpack writes dist/<name>-<version>.parley from manifest.json,
plugin.wasm, and ui.js and slots.json when present, and prints its digest
and key id. sign re-signs an existing bundle in place. Both produce output
byte-identical to the server’s reference packer: a CI test reproduces the
golden vectors in sdk/abi/bundle-v1/ with the test key. build still copies
the UI into dist/<name>-<version>.ui.js for a PLUGIN_DIR install.
dev packs, signs with ~/.config/parley/dev.key (or --key; the key is
created on first use), uploads to POST /api/catalog/bundles with the
parley_session cookie from PARLEY_SESSION, and prints the install link for
PARLEY_ORG (default default) on BASE_URL. The instance must trust the dev
key through PLUGIN_TRUSTED_KEYS, or run locally with
PLUGIN_ALLOW_UNSIGNED=true and dev --unsigned. This replaces the
POST /api/orgs/{org}/admin/plugins/dev-register route compiled only with
-tags plugindev, which installs a bare package with no bundle and is kept
for the server’s own tests. Build your first plugin
walks through the whole loop.
The guest wrapper generateGuestHookTypes prints TypeScript signatures for
on_session_state, on_session_action, on_event and on_job from
sdk/abi/v1.json.
The wire protocol is frozen at version 1; see Plugin wire protocol.
The Plugin API reference documents every hook input,
host function request and response, and the window.parley UI bridge.
Settings
Section titled “Settings”createHost(grants, call, manifest) adds getSettings(), which calls
parley_settings_get and returns the non-secret values, and getSecret(name),
which reads a secret settings field through parley_secret_get. getSettings()
throws in the guest, without calling the host, if the manifest declares no
settings. A "format": "secret" field is an implicit secrets grant for its
own name, so getSecret allows it without a separate grant.
generateSettingsTypes(manifest) prints TypeScript: a Settings interface of
the non-secret fields (an enum becomes a union, a field absent from
required without a default is optional, since the host applies defaults) and a SecretName union of the secret ones.
Manifest
Section titled “Manifest”A plugin manifest (manifest.json; a legacy package.json is still read) is JSON with manifest: 1, kind: "plugin", name,
version (major.minor.patch), optional quotaBytes, capabilities (each
capability plus optional scope), kinds for ceremonies the plugin
provides, and optional slots for UI chrome: panel, room, toolbar,
nav, and export-menu, and an optional settings schema (see Settings above). An unknown slot is refused at preview and install.
A plugin that does not declare a slot appears nowhere in that chrome; a UI
bundle with no sidecar still occupies the nested panel so existing installs
keep working. parley-plugin build writes <name>-<version>.slots.json
beside the UI bundle. Installing through the operator API still requires grantsAccepted: true. Consent sentences are not written in the frontend; they come from
internal/plugin/describe.go, next to the guards that enforce them.
What each grant actually permits
Section titled “What each grant actually permits”Quoted from Describe in internal/plugin/describe.go:
- fetch (scope is an allowlist host): “Can send anything it holds — including session data it has read — to “ plus the expanded host summary.
- kv (unscoped): “Can store and read back data of its own on this server, under any key. It cannot see what another plugin stored.”
- kv (scoped): “Can store and read back data of its own on this server, under keys beginning <scope>. It cannot see what another plugin stored.”
- secrets (unscoped): “Can store and read back every secret on this instance — API tokens, passwords — encrypted on this server. Combined with any outbound access below, it can send them.”
- log: “Can write lines into this server’s log, where they sit alongside Parley’s own.”
- session:read (unscoped): “Can read the live state of every room running a ceremony this plugin itself provides, in any space in this org: who is seated, which of them is looking at its panel, and whatever that ceremony records. It cannot read a planning poker or standup room, or any other plugin’s rooms.”
- session:patch (unscoped): “Can change the live state of every room running a ceremony this plugin itself provides, in any space in this org — move that room’s phase, reveal or hide what it is showing everyone in it. It cannot touch a planning poker or standup room, or any other plugin’s rooms.”
- jobs: “Can schedule its own work to run later on this server, outside any request a person made.”
- emit: “Can publish events of its own, which other plugins receive as though Parley had raised them.”
- events (unscoped): “Is sent a copy of every event Parley raises, payload included.”
An unknown capability is described as: “Parley does not recognize the capability … and cannot say what it permits. Do not grant it.”