Files
kandev/plugin-registry/README.md
T
João Salavisa bc2f310e73 feat(plugins): add plugin marketplace with curated registry and updates (#1798)
* feat(plugins): add plugin marketplace with curated registry and updates

Discover and install plugins from inside kandev instead of only by URL/upload.

- Backend marketplace subpackage: SQLite-backed sources, catalog fetch/merge
  (dedup, install_state, degraded-source handling), and the /api/plugins/
  marketplace[/sources] API. Install reuses the existing install-by-URL pipeline.
- Frontend: Settings > Plugins gains a Browse tab (search/category/star sort),
  source management, one-click install, and an Update button on the Installed
  tab when the catalog has a newer version. Plugins may ship an icon (manifest
  `icon` -> catalog icon_url) with a letter-tile fallback.
- Registry infra: plugin-registry/ (curated plugins.yaml + schema.json) and two
  GitHub Actions (zero-dependency Node build-index.mjs -> index.json on Pages,
  daily star refresh). Ships empty at launch; the empty catalog is valid.
- Spec: docs/specs/plugins/marketplace.md. No download telemetry; stars only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(plugins): address marketplace PR review feedback

- CodeQL: stub fetch routers keyed off a path segment, not a hostname substring.
- Nullable stars end to end: a failed star lookup stays unknown (not 0) and
  sorts last, instead of decoding null -> 0 and corrupting ranking.
- Fetch marketplace sources concurrently and dedupe identical in-flight
  downloads (singleflight), so a slow source can't serialize the Browse tab.
- Register /marketplace routes before the /:id wildcard (gin tree ordering).
- Duplicate source URL returns 409 ErrDuplicateSource, not a raw SQL error.
- Browse: filter category client-side so the category dropdown never collapses.
- marketplaceInstall swallows after toasting (no unhandled promise rejection).
- Source badge keys off source_id, not a name substring.
- build-index: read block-sequence manifest categories, not just inline.
- Registry index workflow validates on pull_request; Pages deploy gated to
  push/schedule/dispatch. Spec: align package_sha256 (advisory in v1) + sort enum.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(plugins): guard catalog repo_url against javascript: href injection

repo_url comes from an untrusted index.json, so a malicious/compromised source
could set a javascript: URL that runs in the operator's session when the Repo
link is clicked (rel/target don't block it). Only render the anchor for http(s)
schemes. Adds regression tests for the scheme guard and null-star rendering.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(plugins): require https for remote sources; test oversized download

- Reject remote plain-http marketplace sources (MITM catalog-poisoning risk);
  loopback http stays allowed for local dev/e2e fixtures.
- Add the missing oversized-response assertion to TestDownloadRejectsNon200AndOversized
  so the maxIndexBytes memory bound has coverage.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(plugins): detach source download from singleflight leader's context

Two concurrent Browse opens after a cache Refresh share one singleflight
download; if the leader's request context is canceled (it navigates away), the
waiters must not inherit that cancellation. Use context.WithoutCancel so values
are preserved but the leader can't cancel everyone; the client's 20s timeout
still bounds the download.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Kandev Agent <agent@kandev.dev>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-19 17:10:53 +01:00

4.1 KiB

Kandev plugin registry

This directory is the official Kandev marketplace source — the curated, PR-edited list of plugins that show up when a user opens Settings > Plugins > Browse inside Kandev.

It is deliberately minimal. It records which repositories are in the official catalog; it does not store plugin metadata (display name, description, author, version, tarball URL, checksum, stars). All of that is read from each plugin's own GitHub release when the catalog index is built, so the listing can never drift from what actually ships.

Files

File Purpose
plugins.yaml The curated pointer list — one entry per plugin repo. Human-edited via PR.
schema.json JSON Schema (draft 2020-12) that plugins.yaml MUST validate against. Enforced in CI.
build-index.mjs Build script (zero-dependency Node) that resolves each entry to a full catalog record and emits index.json.
build-index.test.mjs node --test coverage for the build script's parser and enrichment.

The generated index.json is published to GitHub Pages by the plugin-registry-index workflow and served as the fetch contract Kandev consumes.

One repository per plugin

Each plugin lives in its own public GitHub repository, named kdlbs/kandev-plugin-<name> for first-party plugins (third parties may use any owner/name). Every plugin repo publishes its package as a GitHub Release asset in the standard Kandev package format:

  • <id>-<version>.tar.gz — the plugin package, and
  • checksums.txt — the integrity manifest the install pipeline verifies.

The kdlbs/kandev-plugin-template starter repo is the recommended way to bootstrap a new plugin with the right layout and a release workflow.

The catalog ranks plugins by GitHub stars (with a "recently updated" alternative from each repo's last release). Kandev collects no download or usage telemetry — there is no "most installed" metric, by design.

Submitting a plugin to the official catalog

  1. Publish your plugin. Push it to a public GitHub repository and cut a GitHub Release whose assets include <id>-<version>.tar.gz and checksums.txt. The release must pass the standard package integrity gate.

  2. Fork this repo (kdlbs/kandev).

  3. Add one entry to plugin-registry/plugins.yaml pointing at your public repo:

    - id: my-plugin            # MUST equal the `id` in your plugin manifest
      repo: your-org/your-plugin-repo
      categories: [productivity]
    

    The id MUST match the id in your plugin manifest and be unique across the file. repo is owner/name. featured is a maintainer-only pin and should be left out of submissions.

  4. Open a pull request. CI validates plugins.yaml against schema.json and checks that your id matches the target repo's latest-release manifest id.

  5. A maintainer reviews and merges. Once merged, the index-build workflow picks up your entry and your plugin appears in the in-app catalog on the next build.

Teams and corporates: host your own source instead

You do not have to PR into this registry to share plugins internally. Kandev supports additional marketplace sources: any URL that serves an index.json document of the same shape can be added under Settings > Plugins as an extra source, and its plugins are merged into the catalog alongside the official ones.

This is the recommended path for a team or corporate registry — host your own index.json (for example by copying this directory's plugins.yaml + build-index.mjs + workflow into your own repo and pointing Kandev at your Pages URL). Adding a source is an explicit act of trust in that source's maintainer, much like brew tap-ing a third-party tap. When the same id appears in more than one source, the official source wins and later duplicates are hidden.

Sideloading is always available

Appearing in this registry is only about discovery. Anyone can still install an unlisted plugin by URL or by uploading a .tar.gz directly — the marketplace never removes those escape hatches.