bc2f310e73
* 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>
87 lines
4.1 KiB
Markdown
87 lines
4.1 KiB
Markdown
# 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`](../.github/workflows/plugin-registry-index.yml)
|
|
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:
|
|
|
|
```yaml
|
|
- 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.
|