The plugins Godwit's Extensions page offers as reviewed. Godwit reads the signed
registry.json from dl.godwit.email/plugins/. The design is
docs/superpowers/specs/2026-10-14-native-plugins-design.md in the Godwit repository.
registry.jsonlists plugins and their releases. Each release carries itsplugin.json(asmanifest) and, per file it ships, the URL, size and SHA-256. Godwit trusts those exact files, never what a plugin says about itself.- A merge to
mainrunsgodwit-registry check --fetch(the validator Godwit itself uses, plus a download of every file), signsregistry.jsonwith the repository's ed25519 key and uploads both files todl.godwit.email/plugins/. Godwit verifies the signature with the public key built into it, and refuses a registry older than the one it has. - Godwit installs a release by downloading its file and checking the size and SHA-256. A mismatch is discarded; nothing lets a user install it anyway.
- A native plugin (a shared library) runs with all of Godwit's rights. A listed native file is installed and enabled after one confirmation. A script plugin runs in Godwit's sandbox; being listed does not grant its permissions, the user still allows them.
In-repository script plugins live under packages/, including Zen Free.
One Plugins workflow discovers and checks all packages and publishes a selected plugin
on manual dispatch. Publish separately validates, signs, and uploads the registry.
Open a pull request that adds to plugins (or to an existing plugin's releases):
{
"name": "example",
"title": { "default": "Example", "zh-Hans": "示例" },
"summary": { "default": "Translates mail with an example service. Sends the text to example.com." },
"homepage": "https://github.com/someone/godwit-example",
"releases": [
{
"version": "0.1.0",
"minGodwit": "1.2.0",
"manifest": { "name": "example", "version": "0.1.0", "native": { "abi": 1 }, "capabilities": ["translate"], "config": [] },
"artifacts": {
"darwin-arm64": { "url": "https://github.com/someone/godwit-example/releases/download/v0.1.0/example-darwin-arm64.dylib", "sha256": "…", "size": 11355106 }
}
}
]
}- Native releases name each file by platform:
darwin-arm64,darwin-amd64,windows-amd64,windows-arm64,linux-amd64,linux-arm64. A missing platform is simply not offered there. The file is the bare library (no archive), at most 64 MiB, exporting the C ABI insdk/native/godwit_plugin.h. - A script release has one file,
any: the plugin bundled into the single.jsfile itsmanifest.mainnames, at most 5 MiB. - Bump the top-level
updated(RFC 3339) in every pull request.
Each packages/<name>/ contains plugin.json and package.json, whose scripts provide
typecheck, test, and build. The directory name must match the manifest name. Builds
write dist/plugin.json and a bundled dist/index.js. New packages join the shared CI
without adding a workflow.
- Release Godwit 1.7.0 or later with model plugin support; its release publishes the matching
@godwit/sdkversion. The published 1.6.0 SDK predates the model API. - Bump
packages/<name>/package.jsonandplugin.jsontogether and merge to main. - Create and push
<name>-vX.Y.Zfor that main commit. - Run Actions → Plugins → Run workflow on main, supplying the plugin name, tag, and actual
minimum released Godwit version. The workflow checks the tagged source, installs the
published SDK, and publishes
index.js,plugin.json,<name>.tar.gz,registry-plugin.json, andSHA256SUMSto GitHub Releases. - Add the released
registry-plugin.jsonentry toregistry.json, or merge its release into the existing entry while preserving older releases. Bumpupdatedand open a PR. Publish validates the real downloads, signs, and uploads the registry.
PR checks provide a <name>-dist artifact. Until the model API's SDK is published, they
pack it from the pinned Godwit commit used by the goja integration test. Release runs
install the published SDK from npm. Update the host pin when updating the SDK/API.
Release runs require contents: write. If a failed run leaves a draft release, inspect
and delete that unpublished draft before retrying. Never replace published files; bump
the version instead. The workflow does not modify the registry.
Before approving:
- Source. Read the code, or know who builds it and from what. Prefer releases built by the author's public CI from a tagged commit, so the file can be traced to its source.
- Files.
check --fetchpasses: every file is at its URL with the size and SHA-256 listed. URLs should be immutable (a release asset, not a branch). - Manifest. It is the plugin's real
plugin.json. For a native plugin, itsdescribeanswers the same name, version, ABI and capabilities, or Godwit will not start it. For a script plugin, the permissions are no wider than what it does. - Risk in plain words. If the plugin depends on an unofficial or rate-limited service, or
sends mail content somewhere,
summarysays so.
Remove the release (or the whole plugin) and merge. On its next refresh (on opening Extensions, and every 24 hours) Godwit stops every installed copy of a file the registry no longer lists and asks the user before it runs again.
go run github.com/godwit-email/godwit/cmd/godwit-registry@main keygenprints a key pair.- The private key goes to the repository secret
GODWIT_REGISTRY_KEY, and nowhere else. - The public key goes into
registryPublicKeyin Godwit'score/registry.go. Godwit's plugin directory is off while that constant is empty. - Rotating the key needs a Godwit release with the new public key; publish with the old key until that release is out.
Repository secrets: GODWIT_REGISTRY_KEY, and R2_ACCESS_KEY_ID and R2_SECRET_ACCESS_KEY
(the same R2 token Godwit's release workflow uses).