The /plugins route showed only what was already on disk, so a plugin nobody had
copied in by hand did not exist. It now shows a third list — what a remote
catalogue offers — and installing one fetches its source.
- plugins/marketplace.ts — a stand-in for the remote store, a JSON array in
process. One entry: music, pointing at gitea.officer.dev/plugins/music. It is
deliberately a module and not a fetch, so the shape of a catalogue entry gets
settled before anything depends on a server answering.
- plugins/fetch.ts — git clone --depth 1 into plugins/<appName>/, INSIDE the
platform repo because bun resolves the workspace links by walking up and a
plugin cloned beside it cannot import anything the platform provides
(measured, not assumed). https only, no credentials, GIT_TERMINAL_PROMPT=0 so
a repo that turned private fails instead of hanging. A failed clone removes
the directory: a half-written one reads as a broken plugin rather than an
absent one, which is the worse of the two.
- install.ts fetches first when the directory is missing, then RE-DISCOVERS
rather than assuming what it cloned. Everything after is the existing path,
including the frontend rebuild — so a cloned plugin's panels reach the SPA
without a restart.
- GET /api/plugins returns `available`, catalogue minus what is on disk. Each
entry gets the same live PATH probe a local plugin gets, so music's ffmpeg and
ffprobe are answered for this machine BEFORE the clone rather than after.
- The detail panel is a separate component for a catalogue entry, not a mode of
the installed one: there is no sidecar status, no enabled state and no route
to open, and what there IS — the source URL — the installed panel has no
reason to show. The streaming log is shared, because the marketplace install
is the case that needs it most: it clones before it does anything, and a slow
clone behind a blank panel looks like a hang.
The catalogue's icon is an absolute URL to the store's copy, which is the
[open] question the previous commit left: assets only publish at install, so a
plugin you have never installed had no artwork to draw.
Not done: no update verb, no signature, no private repos, and removePluginSource
has no caller — deleting source drops tables on the next push, and that wants a
verb that says so.
tsgo clean. 767 pass / 6 fail, all 6 identical on HEAD without this change.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>