Files
punktfunk-plugin-rom-manager/README.md
T
enricobuehler 33e91e4d00
CI / publish (push) Has been cancelled
CI / build (push) Has been cancelled
feat: scope + un-bundle the plugin; @unom/ui SPA; custom-emulator editor
Reliability + correctness pass on the packaging and UI.

Packaging — drop the self-contained bundle:
- Rename to `@punktfunk/plugin-rom-manager` (scoped). An unscoped package can't
  be split across registries, which is the only reason bundling effect + the SDK
  into every plugin seemed necessary. Scoped names resolve from one scope-map, so
  the plugin depends on `@punktfunk/host` + `effect` as SHARED (hoisted) deps —
  no duplication, no ~1 MB effect copy per plugin. Build is back to plain `tsc`.
  (Runner-side discovery of `@punktfunk/plugin-*` shipped in @punktfunk/host 0.1.1.)

UI — use the console's design system so it reads as family:
- SPA rebuilt on @unom/style tokens + Tailwind v4 + the console's exact theme
  (violet chrome, Geist, card/button vocabulary). Build-time only → static assets
  in dist/ui, no @unom runtime dep. Skips @unom/ui's AnimatedButton/Card, which
  statically import ~7 MB of UI sound assets — same look, 416 KB dist/ui.
- Add a custom-emulator editor (Emulators page) writing config.emulators — you can
  now add a launcher Punktfunk doesn't ship a definition for.
- Fix brand capitalization to "Punktfunk" in all UI copy + docs.

Install docs + CI updated for the scoped `bun add @punktfunk/plugin-rom-manager`
flow. 48 engine tests green; backend + UI typecheck + biome clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 12:16:04 +02:00

150 lines
6.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# @punktfunk/plugin-rom-manager
A [Punktfunk](https://git.unom.io/unom/punktfunk) plugin that scans your ROM folders, maps them to
emulators, fetches box art, and reconciles everything into the host game library — so your retro games
appear automatically in the Punktfunk web console, native clients, and Moonlight `/applist`, each
launchable and covered in art. It ships a web UI that lives **inside the Punktfunk console** (no second
password, no second port).
Think of it as [Steam ROM Manager](https://github.com/SteamGridDB/steam-rom-manager) for Punktfunk: it
even uses the same art source (SteamGridDB) when you give it an API key.
---
## Requirements
- A Punktfunk host (Linux or Windows) with the **scripting runner** (`punktfunk-scripting`) enabled.
- [Bun](https://bun.sh) (the runner is Bun).
- `@punktfunk/host` **≥ 0.1.1** — it provides the plugin-UI surface (`servePluginUi`) and discovers
scoped `@punktfunk/plugin-*` packages.
## Install
The plugin is scoped under `@punktfunk`, so one bunfig line points that scope at the Punktfunk registry
while everything else (like `effect`) resolves from npm — no bundling, and `@punktfunk/host` + `effect`
are shared across all your plugins:
```sh
mkdir -p "$(punktfunk config-dir)/plugins" && cd "$(punktfunk config-dir)/plugins"
cat > bunfig.toml <<'EOF'
[install.scopes]
"@punktfunk" = "https://git.unom.io/api/packages/unom/npm/"
EOF
bun add @punktfunk/plugin-rom-manager
# then enable the runner (opt-in):
systemctl --user enable --now punktfunk-scripting # Linux
Enable-ScheduledTask PunktfunkScripting # Windows
```
Open the Punktfunk console — a **ROM Manager** entry appears in the nav. That's it.
> **Headless / host-only boxes** without the console can use the [standalone UI](#standalone-ui) or
> just drop a `config.json` (below) — the sync engine is fully functional from the file alone.
## Quick start (config file)
The plugin owns `<config_dir>/rom-manager/config.json`. A minimal example:
```jsonc
{
"roots": [
{ "dir": "/mnt/roms/snes", "platform": "snes" },
{ "dir": "/mnt/roms/ps1", "platform": "ps1", "excludes": ["*.sav", "bios/**"] }
],
"art": { "provider": "auto", "steamGridDbKey": "" }
}
```
See [`config.example.json`](./config.example.json) for every option. Then, from the plugin dir:
```sh
bunx punktfunk-plugin-rom-manager scan # what the scanner finds
bunx punktfunk-plugin-rom-manager detect # which emulators are installed
bunx punktfunk-plugin-rom-manager preview # the desired library (no host write)
bunx punktfunk-plugin-rom-manager sync # reconcile into the live library
```
## Box art
Two sources, selected by `art.provider`:
| Provider | Key? | Art types | Notes |
|---------------|-------|------------------------------------|-------|
| `steamgriddb` | yes | portrait + hero + logo + header | What Steam ROM Manager uses; best coverage + fuzzy title match. Free key from [steamgriddb.com profile preferences](https://www.steamgriddb.com/profile/preferences). |
| `libretro` | no | box-art portrait only | Keyless [libretro-thumbnails](https://thumbnails.libretro.com); works out of the box. |
| `auto` (default) | — | SteamGridDB if a key is set, else libretro | |
Art URLs are handed to the host as-is; native clients/console fetch them directly (so **clients need
internet for covers**), and Moonlight covers are proxied host-side. Verdicts are cached in
`cache.json` and never re-probed unless you change provider/key.
## Platforms & emulators
~25 built-in platforms (NES, SNES, N64, GameCube, Wii, Switch, GB/GBC/GBA, NDS, 3DS, PS1/2/PSP,
Genesis, Master System, Game Gear, Saturn, Dreamcast, PC Engine, Neo Geo Pocket, WonderSwan, Lynx,
Atari 2600/7800, Xbox). Arcade (MAME/FBNeo romset versioning) is intentionally out of scope.
Emulators are detected best-effort (PATH → Flatpak → known install paths): RetroArch (+ cores),
Dolphin, PCSX2, DuckStation, PPSSPP, mGBA, melonDS, Flycast, xemu, plus Azahar/Ryujinx. Any platform's
emulator, core, and args are overridable per-platform and per-game (config or UI). Missing an
emulator? Add a `custom-*` template.
Disc games are folded so a set is **one entry**: `.m3u` playlists absorb their discs, `.cue`/`.gdi`
sheets hide their tracks, `.chd` stands alone.
## The UI
### Console-hosted (default)
`servePluginUi` serves the SPA on a loopback ephemeral port behind a per-boot secret and registers it
with the host; the console reverse-proxies it and gates it with your existing console sign-in. Four
pages: **Setup** (ROM roots), **Emulators** (detected + per-platform overrides), **Games** (scan
preview with covers + per-title include toggles), **Sync** (status, art settings, sync options).
### Standalone UI
For host-only installs, set `ui.standalone: true` (config). It serves the same SPA on a fixed port:
```jsonc
"ui": { "standalone": true, "port": 47993, "bind": "127.0.0.1" }
```
A **non-loopback bind requires a password** (fail-closed — the UI can edit launch commands, i.e.
host-user code):
```sh
bunx punktfunk-plugin-rom-manager set-password 'your-password'
```
## Security
- **ROM filenames are untrusted input** that ends up in a `sh -c` / `cmd.exe /c` string run as the host
user. Every path goes through strict per-OS quoting (`src/quote.ts`); on Windows, names containing
`cmd.exe` metacharacters (`" % ! ^ & | < >`) are refused rather than risk injection.
- `config.json` lives in the hardened `<config_dir>`; the plugin refuses a group/world-writable one.
- Outbound network in v1: SteamGridDB (if keyed) and libretro-thumbnails only. No telemetry.
## Development
```sh
bun install # backend deps (@punktfunk/host + effect) from the Gitea registry
bun test # engine unit tests (pure core: quoting, scanner, reconcile, art)
bun run typecheck
bun run build:all # backend → dist/ (tsc), SPA → dist/ui/ (Vite)
cd ui && bun run dev # SPA dev server (point it at a running standalone instance)
```
- The backend is plain `tsc` output — it depends on `@punktfunk/host` + `effect` at runtime (shared
with the runner and other plugins, not bundled).
- The **SPA** (`ui/`) uses the console's design system (`@unom/ui` + `@unom/style` + Tailwind v4) so it
reads as family. Those are **build-time only** — the SPA compiles to static assets in `dist/ui`, so
the published plugin carries no `@unom` runtime dependency. Installing them needs the `@unom` scope in
your `~/.npmrc` (with the Gitea token); CI needs the same token as a secret.
Requires `@punktfunk/host` ≥ 0.1.1.
## License
MIT OR Apache-2.0.