Files
plugin-helper/.agents/skills/beatsaber-plugin-manager/SKILL.md
T
2026-06-29 10:57:11 -07:00

269 lines
11 KiB
Markdown

---
name: beatsaber-plugin-manager
description: Install or update a Beat Saber plugin in the plugin-helper repo by using the local helper workflow. Use when the user asks to add, install, update, bump, lock, bootstrap BSIPA, or manage a Beat Saber plugin release for a BSManager instance. Prefer upstream GitHub release artifacts for normal plugins; use BeatMods primarily as compatibility/dependency metadata, with CDN artifacts only for inaccessible upstream assets, BeatMods-only packages, or framework/library dependencies.
---
# Install Beat Saber Plugin
Use the repository's own `plugin-helper` commands to manage plugins for BSManager instances whenever the helper supports the operation. Do not manually copy release files into the game instance except:
- to bootstrap BSIPA/core packages before the helper has a first-class bootstrap command
- to undo your own mistaken install before rerunning the helper
## Hard Guardrail
For ordinary GitHub-hosted plugins, require an explicit GitHub repository or
release URL from the user's prompt or from a user-provided local planning note
before selecting any release.
- If the user has provided a GitHub repository URL but not a release URL, use
that exact repository's release API and choose the most appropriate
non-draft, non-prerelease release/asset for the target Beat Saber instance.
- If the user has provided no GitHub repository or release URL for the plugin,
stop and ask the user for one.
- Do not search the web to discover a repository or "correct" project URL.
- Do not substitute a similar repo, fork, project, or package name.
- If the provided URL is a general releases page, use that repo's release API and choose the latest non-draft, non-prerelease release unless the user asks for a specific tag/version.
- If the provided URL is a tag URL, use that exact tag.
Exception: if the user explicitly asks to bootstrap a Beat Saber version or
install verified mods without providing GitHub URLs, use BeatMods metadata to
identify compatible versions and dependency closure. Still prefer upstream
GitHub release artifacts when BeatMods exposes a `gitUrl` and a matching
release/asset can be found. Use BeatMods CDN artifacts only when the upstream
artifact is inaccessible, no matching upstream release asset exists, the package
is effectively BeatMods-only, or the package is a framework/library dependency
such as .NET assemblies. Record the artifact source plus BeatMods `modVersion`,
version id, `zipHash`, dependencies, and supported game version in the repo
notes/lock data.
Accepted URL shapes include:
```text
https://github.com/<owner>/<repo>/releases
https://github.com/<owner>/<repo>/releases/tag/<tag>
https://github.com/<owner>/<repo>/releases/download/<tag>/<asset>
```
## Workflow
1. Confirm the workspace is the `plugin-helper` repo.
```bash
test -f pyproject.toml && test -d src/plugin_helper && test -d registry && test -d locks
```
2. Read the local helper behavior before changing files.
Inspect at least:
```bash
sed -n '1,220p' README.md
sed -n '1,260p' src/plugin_helper/cli.py
sed -n '1,260p' src/plugin_helper/planner.py
sed -n '1,220p' src/plugin_helper/models.py
sed -n '1,220p' registry/plugins.toml
sed -n '1,220p' locks/<instance>.lock.toml
sed -n '1,220p' docs/SMOKETEST.md
```
3. Determine the instance.
Prefer the instance the user names. If omitted and the working context clearly points at one lockfile, use that instance. Otherwise run:
```bash
PYTHONPATH=src python -m plugin_helper instances
```
4. Resolve the release source.
For BeatMods bootstrap or verified packages, query BeatMods with a browser-like user agent:
```bash
PYTHONPATH=src python - <<'PY'
import json, urllib.request
from plugin_helper.beatmods import by_version_id, normalize_mods
game_version = "<instance>"
url = f"https://beatmods.com/api/mods?status=verified&gameVersion={game_version}&gameName=BeatSaber&platform=steampc"
req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0 plugin-helper"})
with urllib.request.urlopen(req, timeout=20) as response:
data = json.load(response)
mods = normalize_mods(data)
mods_by_version_id = by_version_id(mods)
print(json.dumps(
[
{
"name": mod.name,
"modId": mod.mod_id,
"gitUrl": mod.git_url,
"category": mod.category,
"versionId": mod.version_id,
"modVersion": mod.mod_version,
"zipHash": mod.zip_hash,
"dependencies": mod.dependencies,
"dependencyNames": [
mods_by_version_id[dep].name
for dep in mod.dependencies
if dep in mods_by_version_id
],
}
for mod in mods
],
indent=2,
)[:20000])
PY
```
BeatMods dependency entries are mod-version ids. Resolve the selected mod's
dependency closure before downloading. For each resolved package, prefer its
upstream `gitUrl` release artifacts when a matching release asset exists.
Fall back to BeatMods CDN only for inaccessible/missing upstream assets,
BeatMods-only packages, or framework/library dependencies. CDN URLs are:
```text
https://beatmods.com/cdn/mod/<zipHash>.zip
```
For BSIPA bootstrap, expect the archive to contain root-relative `IPA/` and
`IPA.exe` files whether sourced from GitHub or BeatMods. Extract it into the
instance root and run `IPA.exe -n` under the same Proton environment used by
the smoketest. This creates/copies the root `winhttp.dll` and root `Libs/`
substrate that IPA needs.
For GitHub URLs, resolve the release from the user-provided repository or
release URL only.
Derive `<owner>/<repo>` and optional `<tag>` from the URL. Query the GitHub API directly for metadata:
```bash
curl -sS https://api.github.com/repos/<owner>/<repo>/releases
curl -sS https://api.github.com/repos/<owner>/<repo>/releases/tags/<tag>
```
Pick the asset that matches the Beat Saber instance/version. Prefer an exact versioned asset such as `1.40.8.zip` over broad or source archives. If multiple plausible assets remain, ask the user.
5. Inspect the asset before selecting an install strategy.
Download to the helper state directory:
```bash
mkdir -p .state/instances/<instance>/downloads/<plugin-id>
curl -L --fail -o .state/instances/<instance>/downloads/<plugin-id>/<asset-name> "<browser_download_url>"
sha256sum .state/instances/<instance>/downloads/<plugin-id>/<asset-name>
```
Match the checksum against GitHub's `digest` when available. Inspect zip contents:
```bash
unzip -l .state/instances/<instance>/downloads/<plugin-id>/<asset-name>
```
Strategy guide:
- `dll-to-plugins`: asset is a single `.dll` that belongs in `Plugins/`.
- `bsipa-zip`: zip top-level paths are only `IPA/`, `Libs/`, or `Plugins/`.
- `root-zip`: zip contains valid game-root paths outside the BSIPA top-level set. Use this for BSIPA/bootstrap archives because `IPA.exe`, `IPA.runtimeconfig*.json`, and root `winhttp.dll` are game-root files.
- `zip-to-pending`: only when the release is intended for `IPA/Pending/`.
- `manual`: do not use for installable releases.
6. Update the registry and lockfile.
Add or update exactly one `[[plugins]]` entry in `registry/plugins.toml` with:
```toml
[[plugins]]
id = "<stable-plugin-id>"
name = "<human name>"
repo = "<owner>/<repo>"
asset_patterns = ["<asset-name-or-pattern>"]
install_strategy = "<strategy>"
category = "<category-if-obvious>"
```
Add or update the matching `[[plugins]]` entry in `locks/<instance>.lock.toml` with:
```toml
[[plugins]]
id = "<stable-plugin-id>"
repo = "<owner>/<repo>"
tag = "<tag>"
asset = "<asset-name>"
sha256 = "<asset-sha256>"
```
Preserve unrelated registry and lockfile content. Do not invent dependency versions unless they are explicitly stated by the provided release notes or existing local metadata.
7. Use the helper to validate, plan, and apply.
Always pass `--state-dir .state` so the helper uses the repo-local downloaded asset:
```bash
PYTHONPATH=src python -m plugin_helper --state-dir .state check --instance <instance>
PYTHONPATH=src python -m plugin_helper --state-dir .state plan --instance <instance> --plugin <plugin-id>
PYTHONPATH=src python -m plugin_helper --state-dir .state apply <generated-plan-path>
```
Before applying, read or summarize the generated plan enough to confirm it changes only the intended plugin files.
8. Verify the result.
Confirm the installed file hashes match the plan or archive members:
```bash
PYTHONPATH=src python -m plugin_helper --state-dir .state state --instance <instance>
PYTHONPATH=src python -m plugin_helper --state-dir .state check --instance <instance>
PYTHONPATH=src python -m unittest discover -s tests
```
Use `PYTHONPATH=src`; plain `python -m unittest` may fail in this source-layout repo.
After any successful apply that changes a live BSManager instance, always
run the documented live smoketest before the final response unless the user
explicitly says not to. Do not stop at helper check, unit tests, compile
checks, or file-hash verification for live installs. For live Beat Saber
validation, follow `docs/SMOKETEST.md`. Before starting the launch, announce
in agent chat how long the smoketest window will run for, using the current
duration from `docs/SMOKETEST.md` unless the user requested a different
duration. Do not rely on `timeout` to kill the full game process tree.
Prefer the documented foreground Proton launch with a background watchdog
that sleeps for the smoke window, then terminates Beat Saber by process
name. Confirm `Logs/_latest.log` has the expected IPA/plugin lines and
enough menu/UI initialization evidence for the plugin under test. If the
game remains open after the watchdog cleanup, say so and ask the user to
close it manually rather than leaving the turn with Beat Saber running.
For BSIPA/SongCore bootstrap, expected successful log lines include:
```text
Game version <version>
Loading plugins from Plugins and found <n>
Beat Saber IPA (BSIPA): <version>
SongCore (SongCore): <version>
```
Warnings about older mod target game-version metadata can be acceptable when
BeatMods verified that exact package for the target Beat Saber version, but
record them in the tracker or roadmap. Also record when a BeatMods CDN
artifact was used so it can be migrated to upstream GitHub later if possible.
9. Final response.
Include:
- release URL/tag/asset used
- files changed in the repo and helper state
- live instance files changed
- backup path created by the helper
- validation commands and results
## Mistake Recovery
If you installed from the wrong release or repo during the current task:
1. Restore affected live files from the helper backup created by that mistaken apply.
2. Remove mistaken downloaded assets and mistaken plan files from `.state`.
3. Correct the registry and lockfile to the user-provided release URL.
4. Rerun `check`, `plan`, and `apply` with `--state-dir .state`.
5. Keep or report only the backup relevant to the final correct apply unless the user asks for full audit history.