1212 ** fork → add a plugin → open a PR** to get it listed.
1313- Every merged plugin is ** validated, zipped, hashed, and signed** as a
1414 tamper-evident artifact.
15- - The repo publishes a ** catalog ** ( ` index.json ` + per- plugin metadata + signed
16- zips) via ** GitHub Releases ** . There is no separate backend server — * GitHub is
17- the marketplace API * .
15+ - On merge, CI ** uploads ** each changed plugin (signed zip + signed metadata +
16+ listing assets) to the ** VitoDeploy marketplace API ** at vitodeploy.com. The
17+ backend stores the artifacts and serves the marketplace listing .
1818- The Vito app shows a ** catalog** of plugins (name, description, icon,
1919 categories) with ** a link to each plugin's home page** — a discovery surface,
2020 exactly like muxy's marketplace listing.
@@ -37,16 +37,18 @@ Vito **already** has a plugin system and a rudimentary "marketplace":
3737
3838This design ** adds the registry/marketplace layer** on top: a single curated
3939monorepo with composer.json manifests, deterministic signed artifacts, and a
40- published catalog. The 3 official plugins move * into* this repo.
40+ backend that ingests them and serves the marketplace. The 3 official plugins
41+ move * into* this repo.
4142
4243### What is in v1 vs deferred
4344
4445- ** v1 (this work):** the monorepo + manifest schema + validate/pack/sign/publish
45- scripts + CI + the 3 migrated plugins + a published catalog, ** and** wiring
46- Vito's marketplace UI to read that catalog (display + homepage link).
46+ scripts + CI + the 3 migrated plugins + uploading signed artifacts to the
47+ vitodeploy.com marketplace API, ** and** wiring Vito's marketplace UI to read
48+ that listing (display + homepage link).
4749- ** Deferred (not v1):** rewiring Vito's * installer* to consume signed monorepo
4850 artifacts and derive the namespace from ` composer.json ` . Vito's existing
49- GitHub-URL/release install flow stays as-is. The catalog is discovery only
51+ GitHub-URL/release install flow stays as-is. The marketplace is discovery only
5052 for now; "Install" continues to use the existing path (or links out).
5153
5254## 3. End-to-end flow
@@ -75,20 +77,19 @@ Author forks repo
7577 └─ for EACH changed plugin, independently:
7678 ├─ pack deterministically (fixed order, epoch mtimes → stable sha256)
7779 ├─ sha256(zip)
78- ├─ sign the zip AND a metadata doc (name,version,sha256,perms, asset hashes)
80+ ├─ sign the zip AND a metadata doc (name,version,sha256,asset hashes)
7981 │ with the Vito release key (minisign -W) → two .minisig
80- ├─ upload zip + sigs + assets as a GitHub Release asset set
81- │ (tag: <name>-v<version>)
82- └─ regenerate and commit/publish catalog/index.json
82+ └─ POST zip + sigs + metadata + assets to the marketplace API
83+ (vitodeploy.com/api/plugins/upload), one multipart request
8384 │
8485 ▼
85- GitHub (Releases + raw index.json ) ← the marketplace " API"
86- ├─ serves catalog/index.json ( the list) + per-plugin metadata
87- └─ serves the signed zip + signatures as Release assets (CDN-backed)
86+ VitoDeploy backend (vitodeploy.com ) ← the marketplace API
87+ ├─ verifies the request against the metadata + headers, stores the artifact
88+ └─ serves the marketplace listing + signed zip/ signatures to the app
8889 │
8990 ▼
9091 Vito App
91- ├─ Marketplace UI fetches catalog/index.json → browse/search
92+ ├─ Marketplace UI fetches the listing → browse/search
9293 ├─ shows name, description, icon, categories, "Home page" link
9394 └─ (deferred) install: download signed zip → verify minisign → extract
9495```
@@ -127,16 +128,12 @@ vito-plugins/ # github.com/vitodeploy/plugins
127128│ │ └── images.mjs # icon/screenshot dimension + size checks (no deps)
128129│ ├── validate.mjs # validate one/all plugins (CI + local)
129130│ ├── pack.mjs # deterministic zip + sha256 for one plugin
130- │ ├── publish.mjs # pack+sign+upload changed plugins, build index.json
131- │ └── catalog.mjs # (re)generate catalog/index.json from plugins/
132- │
133- ├── catalog/
134- │ └── index.json # published catalog the app reads (generated)
131+ │ └── publish.mjs # pack + sign + upload changed plugins to the API
135132│
136133├── .github/
137134│ ├── workflows/
138135│ │ ├── validate.yml # on PR: validate + dry-run pack + meta gate
139- │ │ └── publish.yml # on push to main: pack + sign + release + index
136+ │ │ └── publish.yml # on push to main: pack + sign + upload
140137│ ├── ISSUE_TEMPLATE/
141138│ │ ├── 1-new-plugin.yml
142139│ │ ├── 2-report-plugin.yml # security/abuse report
@@ -197,60 +194,53 @@ muxy's "package.json + `muxy` key" pattern.
197194 the plugin root, so the PSR-4 prefix maps to ` "" ` (the plugin dir).
198195- ** ` extra.vito ` ** carries everything the marketplace listing needs; Vito's
199196 plugin * loader* ignores it (it only cares about ` Plugin.php ` ).
200- - ** ` min_vito_version ` ** lets the catalog /app hide plugins incompatible with the
201- running Vito version (advisory in v1).
197+ - ** ` min_vito_version ` ** lets the marketplace /app hide plugins incompatible with
198+ the running Vito version (advisory in v1).
202199
203200The published schema (` schema/manifest.schema.json ` ) is the single source for CI
204201and editor autocomplete.
205202
206- ## 6. The catalog ( ` catalog/index.json ` ) — the marketplace " API"
203+ ## 6. The upload contract — the marketplace API
207204
208- There is ** no backend** . The repo publishes a catalog that the app fetches over
209- HTTPS from GitHub (raw file on ` main ` , and/or a ` catalog ` GitHub Release for a
210- stable URL). Shape:
205+ There is ** no committed catalog** . CI is the single trusted publisher: on every
206+ push to ` main ` , ` scripts/publish.mjs ` packs each changed plugin and POSTs it to
207+ the marketplace API at ` vitodeploy.com/api/plugins/upload ` as one
208+ ` multipart/form-data ` request. The backend stores the artifact and owns the
209+ marketplace listing the app renders.
210+
211+ The wire format mirrors what the API's ` UploadPluginRequest ` validates:
211212
212- ``` jsonc
213- {
214- " generated_at" : " 2026-06-18T00:00:00Z" ,
215- " schema_version" : 1 ,
216- " plugins" : [
217- {
218- " name" : " vitodeploy/laravel-reverb" ,
219- " slug" : " laravel-reverb" ,
220- " display_name" : " Laravel Reverb" ,
221- " description" : " Laravel Reverb plugin for VitoDeploy" ,
222- " version" : " 2.0.0" ,
223- " official" : true ,
224- " categories" : [" laravel" , " websockets" ],
225- " homepage" : " https://vitodeploy.com/docs/plugins/laravel-reverb" ,
226- " repository" : " https://github.com/vitodeploy/plugins" ,
227- " author" : { " name" : " VitoDeploy" , " github" : " vitodeploy" },
228- " min_vito_version" : " 3.0.0" ,
229- " icon_url" : " https://github.com/vitodeploy/plugins/releases/download/laravel-reverb-v2.0.0/icon.svg" ,
230- " screenshots" : [" https://.../screenshot-1.png" ],
231- " namespace" : " App\\ Vito\\ Plugins\\ Vitodeploy\\ LaravelReverb\\ Plugin" ,
232- " artifact" : {
233- " url" : " https://github.com/vitodeploy/plugins/releases/download/laravel-reverb-v2.0.0/laravel-reverb-2.0.0.zip" ,
234- " sha256" : " …" ,
235- " size" : 12345 ,
236- " signature_url" : " https://.../laravel-reverb-2.0.0.zip.minisig" ,
237- " metadata_url" : " https://.../metadata.json" ,
238- " metadata_signature_url" : " https://.../metadata.json.minisig"
239- }
240- }
241- ]
242- }
243213```
214+ POST https://vitodeploy.com/api/plugins/upload
215+ Authorization: Bearer <VITO_UPLOAD_TOKEN>
216+ X-Plugin-Name / X-Plugin-Version / X-Plugin-Sha256 ← cross-checked against metadata + bytes
217+
218+ multipart/form-data:
219+ artifact the packed zip (file)
220+ signature minisign signature over the zip (string)
221+ metadata the signed metadata document (file, application/json)
222+ metadataSignature minisign signature over metadata.json (string)
223+ icon listing icon (file, hash declared in metadata)
224+ screenshot-N listing screenshots (files, hashes declared in metadata)
225+ ```
226+
227+ The signed ` metadata.json ` is the authoritative facts the API consents to —
228+ ` name ` , ` slug ` , ` version ` , zip ` sha256 ` /` size ` , ` description ` , ` namespace ` ,
229+ ` categories ` , ` min_vito_version ` , and a ` field ` +` filename ` +` sha256 ` entry for
230+ the icon and each screenshot. The API cross-checks the ` X-Plugin-* ` headers and
231+ the received bytes against this signed document, so every trusted fact is
232+ signature-covered (see §7).
244233
245- The app's marketplace UI reads ` plugins[] ` to render the catalog and the home
246- page link. The ` artifact ` block is what a future signed-install path consumes.
234+ Auth is a static bearer token (the single trusted publisher): the API compares
235+ ` Authorization: Bearer <token> ` against its ` PLUGINS_UPLOAD_TOKEN ` . The token is
236+ a GitHub Actions secret (` VITO_UPLOAD_TOKEN ` ) scoped to the publish workflow.
247237
248238## 7. Integrity model (signed metadata + signed zip)
249239
250240Identical to muxy: two minisign (Ed25519) signatures per publish — one over the
251- zip, one over a metadata document binding ` name ` , ` version ` , zip sha256,
252- declared permissions/capabilities, and each asset's sha256. The matching public
253- key is committed as ` minisign.pub ` and ** pinned in the Vito app** .
241+ zip, one over a metadata document binding ` name ` , ` version ` , zip sha256, and
242+ each asset's sha256. The matching public key is committed as ` minisign.pub ` and
243+ ** pinned in the Vito app** .
254244
255245When the signed-install path lands (deferred), Vito enforces, in order:
256246pinned key → verify signed metadata → verify zip sig + sha256 matches metadata →
@@ -275,12 +265,11 @@ no fork access. See SECURITY.md.
2752651 . Skip unless ` minisign.pub ` is real and ` MINISIGN_SECRET_KEY ` is set.
2762662 . Diff the merge → changed plugin dirs (or ` workflow_dispatch ` explicit list).
2772673 . For each: validate → ` scripts/pack.mjs ` → ` minisign -S -W ` (zip + metadata) →
278- create/update a GitHub Release ` <name>-v<version> ` with zip + sigs + assets →
279- ` scripts/catalog.mjs ` regenerates ` catalog/index.json ` → commit it back to
280- ` main ` (and/or attach to a ` catalog ` release for a stable URL).
268+ ` scripts/publish.mjs ` POSTs the signed zip + signatures + metadata + assets to
269+ ` vitodeploy.com/api/plugins/upload ` .
281270
282271Determinism: re-running publish on an unchanged plugin yields the identical zip
283- and hash, so redundant publishes dedupe by ` name@version + sha256 ` .
272+ and hash, so redundant uploads dedupe by ` name@version + sha256 ` on the backend .
284273
285274## 9. Packaging rules (PHP-specific divergence from muxy)
286275
@@ -307,10 +296,10 @@ path escapes, invalid `composer.json`.
307296## 11. Vito app-side changes (v1)
308297
309298In ` ~/Projects/vito ` :
310- - A ** catalog client ** that fetches ` vitodeploy/ plugins` ` catalog/index.json `
299+ - A ** marketplace listing ** rendered from the plugins uploaded to the backend
311300 (replacing or augmenting the GitHub-search queries in ` official.tsx ` /
312301 ` community.tsx ` ).
313- - Render the catalog : name, description, icon, categories, a ** "Home page"**
302+ - Render the listing : name, description, icon, categories, a ** "Home page"**
314303 link (` extra.vito.homepage ` ), and a star/repository link.
315304- Keep the existing GitHub-URL install dialog working unchanged.
316305- Commit the pinned ` minisign.pub ` into the app for the future verify path
@@ -323,20 +312,18 @@ Resolved:
323312 root ` plugins/ ` . ✓
324313- ** Manifest = ` composer.json ` with ` extra.vito ` ** , PSR-4 namespace
325314 authoritative. ✓
326- - ** Hosting = GitHub ** (Releases for signed zips/assets, ` catalog/index.json `
327- for the listing). No separate vitodeploy.com backend . ✓
315+ - ** Publishing = upload to the vitodeploy.com marketplace API ** (signed zip +
316+ signed metadata + assets); the backend owns the listing . ✓
328317- ** Signing = minisign/Ed25519** , pinned ` minisign.pub ` , two sigs (zip +
329318 metadata), key only in CI. ✓
330319- ** Publish granularity = incremental** (only changed plugins). ✓
331- - ** v1 app-side = catalog display + homepage link** ; installer rewiring
320+ - ** v1 app-side = marketplace display + homepage link** ; installer rewiring
332321 deferred. ✓
333322
334323Open (non-blocking; sensible defaults applied):
335- 1 . ** Stable catalog URL** — raw ` main ` file vs a dedicated ` catalog ` Release
336- asset. Default: publish both; app prefers the Release asset, falls back to
337- raw. (Release asset survives history rewrites and is CDN-backed.)
338- 2 . ** Per-plugin ` min_vito_version ` enforcement** — advisory in v1 (catalog
324+ 1 . ** Per-plugin ` min_vito_version ` enforcement** — advisory in v1 (metadata
339325 carries it; app may grey-out incompatible plugins later).
340- 3 . ** Community tier** — keep GitHub-topic search for non-monorepo community
341- plugins alongside the curated catalog, or require all via PR. Default: keep
342- topic-search community tab for now; official tab reads the curated catalog.
326+ 2 . ** Community tier** — keep GitHub-topic search for non-monorepo community
327+ plugins alongside the curated marketplace, or require all via PR. Default:
328+ keep topic-search community tab for now; official tab reads the marketplace.
329+ ```
0 commit comments