Submitting and reviewing plugins (open ecosystem)

中文:08-plugin-submission.md

Minibar plugins are npm packages: the host downloads them from npm or a nearby mirror and checks each one against the name + version + sha256 pinned in the signed index (10-backend.md §2b). The index (registry.json) is hosted on Vercel (https://minibar.084817.xyz; the server lives in server/). Bundled plugins come from Sources/BundledMiniApps/Catalog/ and are published by CI as @minibar-app/*; community plugins are published to npm by their authors, with a source copy under registry/plugins/<plugin id>/ in this repository for review and index builds. The app's Market shows a "Community" badge on the latter and npm download counts for every plugin.

1. How to submit

  1. Scaffold with minibar-cli init or copy one of docs/examples/ (including a package.json: version equal to the manifest's, keywords with minibar-plugin, no scripts), then make it pass locally:
    node tools/jsharness/run.js <plugin dir>            # render under the mock host
    node tools/jsharness/scan.js --strict <plugin dir>  # static scan with the registry rules, package.json included

    Try it on a Mac through Settings › Plugins › Install from Folder… or developer mode.

  2. Publish to npm: npm publish --access public (any name works; minibar-plugin-<name> is the convention). The index pins the exact version and the whole tarball's hash, and npm never lets a published version change, so updates are new versions.
  3. Put the plugin source under registry/plugins/<plugin id>/ (the directory name must equal the manifest id) and open a pull request (while the repository is private, send it through the channel named on the site's support page). The description must answer:
    • what the plugin does and where its data comes from;
    • what each permission is for (especially system, apps, files, clipboard);
    • what each entry in allowedHosts is for; "*" needs its own justification;
    • whether third-party code (libraries, build output) is included, with source and license.
  4. CI (.github/workflows/plugin-submission.yml) runs the static scan (--strict), a render under the mock host and a dry-run index build. Green is the precondition for review.
  5. A maintainer reviews against the checklist in §3 and confirms that the tarball published on npm matches the source in the PR (scripts/build_registry.py --resolve-from https://registry.npmjs.org downloads the published package, checks its manifest and computes the hash). Approval means merge; the Registry workflow then deploys the index, and apps see the plugin on their next refresh (cached 6 hours, immediately when the Market window opens).
  6. Updates: publish the new version to npm, bump version (semver) and open another PR; the app offers the update, and users can turn on automatic updates.

2. What the static scanner checks (tools/jsharness/scan.js)

AreaRuleResult
manifestreverse-DNS id, directory = id, semver version, name ≤ 32, description ≤ 200, unknown permissions, network without allowedHosts, forbidden allowedSchemeserror
manifestmissing author / homepage (--strict), allowedHosts: ["*"], asking for system / apps / files / clipboarderror / warning (review focus)
filesextension allowlist (same as PathSafety.allowedExtensions), symlinks, > 8 directory levels, a file > 5 MB, > 20 MB in total, > 200 fileserror
scriptstouching __host / __runtime / __meta, eval, Function constructor, require / import(), XMLHttpRequest / WebSocket, Node process, browser storageerror (no legitimate use)
scriptsentry without defineMiniApp, script > 1 MBerror
scriptsvery long lines (minified / obfuscated), long String.fromCharCode chains, runs of \x escapes, fetch / atob / setTimeout (absent in the runtime), clipboard reads, http URLs or hosts outside allowedHosts in the sourcewarning
web cards<script src="http…">error; other network references are warnings

The scanner is the first gate: it keeps out what can never be legitimate and marks what a human must look at.

3. Reviewer checklist

Tick every item before merging:

Common reasons for rejection: allowedHosts: ["*"] without justification, personal data sent to third parties, obfuscated code, a duplicate of a bundled plugin without improvements.

4. Revoking a plugin, and the drill

registry/revoked.json:

{ "revoked": [ { "id": "com.example.bad", "reason": "Sends clipboard contents to a third party" } ] }

After the merge the Registry workflow republishes registry.json. Every Mac with the plugin installed, on its next registry refresh (≤ 24 h, immediately when the Market opens):

  1. marks it "Removed from the market: <reason>" and refuses installs;
  2. auto-disables the installed copy (the card shows the reason; the user can view the source, uninstall, or knowingly re-enable);
  3. writes a log line.

Once the entry is removed from revoked.json, the next registry refresh resumes the paused copy by itself (only pauses caused by the revocation; pauses for crashes or timeouts stay, and the user's own enabled/disabled choice is kept).

Drill (once before each release, to prove the chain works):

  1. On a branch, add app.minibar.community.worldclock to revoked.json; run node server/dev.mjs --build (builds and serves the registry on this Mac; curl http://127.0.0.1:8000/registry.json shows it under revoked; plain http is accepted only from loopback addresses, and a local registry is unsigned, so the Market says "Unverified source");
  2. Set Settings › General › registry URL to http://127.0.0.1:8000/registry.json and open the Market: World Clock shows as removed and the installed copy is paused;
  3. Point the URL back at the real registry and confirm it recovers;
  4. Do not merge the drill branch.

4a. Not aiming for the official source? Run an external one

The official source only lists reviewed plugins. Team, private or experimental plugins can live in a source of your own: build and host a registry.json as described in plugin-guide.en.md §11, and users add its address under Settings › Market › External sources. Plugins from external sources wear a source tag and show as unsigned, but installs are verified, sandboxed and permission-gated exactly like official ones.

5. Registry settings on the maintainer side