diff --git a/.agents/skills/beatsaber-plugin-builder/SKILL.md b/.agents/skills/beatsaber-plugin-builder/SKILL.md new file mode 100644 index 0000000..633e39e --- /dev/null +++ b/.agents/skills/beatsaber-plugin-builder/SKILL.md @@ -0,0 +1,94 @@ +--- +name: beatsaber-plugin-builder +description: Build, test-compile, or package Beat Saber PC BSIPA plugin source on Linux from local checkouts, GitHub branches, or pull requests. Use when asked to build a Beat Saber plugin, compile a plugin PR, produce a DLL/zip artifact, configure BeatSaberDir/local refs for dotnet builds, or diagnose Linux build failures for net472 BSIPA projects. +--- + +# Build Beat Saber Plugin + +Use this skill to compile PC BSIPA plugin projects on this Linux host, especially from the `plugin-helper` repo. The workflow is adapted from the Setlist repo's Linux/Cursor build notes. + +For detailed Linux/BSMT behavior, read [linux-bsipa-build.md](references/linux-bsipa-build.md) when you need to configure a project, fix missing references, package artifacts, or explain a failure. + +## Core Workflow + +1. Confirm context. + + ```bash + pwd + git status --short + ``` + + In `plugin-helper`, run commands from repo root and keep temporary source checkouts under `.state/build/` unless the user asks for another location. Do not disturb unrelated dirty files. + +2. Resolve source. + + For a GitHub PR, clone or reuse a checkout under `.state/build`, add/fetch the upstream remote if needed, and check out the PR head: + + ```bash + git clone https://github.com//.git .state/build/ + git -C .state/build/ fetch origin pull//head:pr- + git -C .state/build/ checkout pr- + ``` + + If the PR is from a fork and the repo already has a fork remote, preserve it. Never overwrite local source changes without explicit approval. + +3. Inspect build shape. + + ```bash + find -maxdepth 3 -name '*.sln' -o -name '*.csproj' -o -name 'manifest.json' -o -name 'Directory.Build.props' + sed -n '1,240p' .csproj + ``` + + Note target framework, `BeatSaberDir`, `LocalRefsDir`, package references, and whether `DisableCopyToPlugins` is already set. + +4. Choose Beat Saber references. + + Prefer a BSManager instance matching the plugin or manifest `gameVersion`. Common local roots: + + ```text + /home/pleb/.local/share/BSManager/BSInstances/ + /home/pleb/Windows/Users/pleb/BSManager/BSInstances/ + ``` + + Use a local `.csproj.user` or MSBuild properties rather than committing machine paths. For test builds, pass `-p:DisableCopyToPlugins=True` and, for BSMT projects that expose it, `-p:DisableCopyToGame=True` so compilation does not mutate the game install. + +5. Restore and build. + + Prefer the solution when present; otherwise build the plugin `.csproj`: + + ```bash + dotnet restore + dotnet build -c Release -p:DisableCopyToPlugins=True -p:DisableCopyToGame=True + ``` + + If the project lacks .NET Framework reference assemblies on Linux, add or pass `Microsoft.NETFramework.ReferenceAssemblies.net472` as described in the reference file. + +6. Collect artifacts. + + Find produced DLLs and release zips: + + ```bash + find -path '*/bin/*' \( -name '*.dll' -o -name '*.zip' \) -print + ``` + + Verify the DLL name, version, and manifest. If the result is meant for `plugin-helper`, place a copy under `.state/instances//downloads//` and use the helper plan/apply workflow rather than hand-copying into a BSManager instance. + +7. Validate. + + For skill edits inside this repo, run: + + ```bash + python /home/pleb/.codex/skills/.system/skill-creator/scripts/quick_validate.py .agents/skills/beatsaber-plugin-builder + PYTHONPATH=src python -m compileall -q src tests + PYTHONPATH=src python -m unittest discover -s tests + ``` + + For a plugin build, at minimum report the exact `dotnet build` result and artifact paths. For live game validation, use `docs/SMOKETEST.md` and tear down Beat Saber processes afterward. + +## Failure Triage + +- Missing `Microsoft.NETFramework.ReferenceAssemblies`: add the net472 reference-assemblies package or pass an equivalent MSBuild/package restore fix. +- Missing `Main.dll`, `HMUI.dll`, `IPA.Loader.dll`, `BSML.dll`, `SongCore.dll`, or similar: `BeatSaberDir` points at the wrong/unmodded instance, or dependencies are absent from `Plugins/`/`Libs/`. +- BSMT copies to `IPA/Pending` or `Plugins` during build: rebuild with `-p:DisableCopyToPlugins=True -p:DisableCopyToGame=True` unless the user explicitly wants deployment. +- NuGet package restore fails because a source is missing: inspect `NuGet.config` and installed package sources; use repo-local configuration where possible. +- API compile errors from a PR: inspect the target game/dependency versions before changing code. Prefer one target version rather than compatibility branches unless the user asks for multi-version support. diff --git a/.agents/skills/beatsaber-plugin-builder/agents/openai.yaml b/.agents/skills/beatsaber-plugin-builder/agents/openai.yaml new file mode 100644 index 0000000..29f67c3 --- /dev/null +++ b/.agents/skills/beatsaber-plugin-builder/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Build Beat Saber Plugin" + short_description: "Build BSIPA plugins on Linux" + default_prompt: "Use $beatsaber-plugin-builder to build this Beat Saber plugin PR on Linux." diff --git a/.agents/skills/beatsaber-plugin-builder/references/linux-bsipa-build.md b/.agents/skills/beatsaber-plugin-builder/references/linux-bsipa-build.md new file mode 100644 index 0000000..58d377e --- /dev/null +++ b/.agents/skills/beatsaber-plugin-builder/references/linux-bsipa-build.md @@ -0,0 +1,131 @@ +# Linux BSIPA Build Reference + +## Source Notes + +This workflow comes from `/home/pleb/ops/beatsaber/setlist/docs/pc-modding.md` and `/home/pleb/ops/beatsaber/setlist/docs/bootstrap.md`. Prefer those local files and local clones under `~/src` over web copies when deeper reference material is needed. + +## Toolchain + +- PC BSIPA plugins are usually .NET Framework `net472` class libraries. +- Linux `dotnet` SDK 6+ can build them because output DLLs are platform-agnostic CIL loaded by Beat Saber under Proton. +- `BeatSaberModdingTools.Tasks` supplies the MSBuild targets normally driven by Visual Studio/Rider BSMT extensions. +- On this host, `dotnet --list-sdks` should show a usable SDK. NuGet is available for package inspection. + +## Game References + +Point `BeatSaberDir` at a modded BSManager instance containing: + +```text +Beat Saber.exe +Beat Saber_Data/Managed/ +IPA/ +Libs/ +Plugins/ +winhttp.dll +``` + +Preferred local managed instance root: + +```text +/home/pleb/.local/share/BSManager/BSInstances/ +``` + +Windows mirror root: + +```text +/home/pleb/Windows/Users/pleb/BSManager/BSInstances/ +``` + +Use `BeatSaberVersion.txt` for the exact game version. The manifest `gameVersion` normally uses the `major.minor.patch` prefix, not the build suffix. + +## Project Configuration + +Prefer machine-local configuration in `.csproj.user`: + +```xml + + + /home/pleb/.local/share/BSManager/BSInstances/1.40.8 + + +``` + +Some projects use `LocalRefsDir` and set: + +```xml +$(LocalRefsDir) +``` + +For those, pass `-p:LocalRefsDir=/path/to/instance` or add a local `.csproj.user` property if the project imports it. + +When changing a project file for Linux portability, prefer the smallest explicit fix: + +- Add `Microsoft.NETFramework.ReferenceAssemblies.net472` when MSBuild reports missing .NET Framework reference assemblies. +- Set or pass `DisableCopyToPlugins=True` for artifact-only builds. +- Keep hint paths rooted at `$(BeatSaberDir)` where possible. +- Do not add broad multi-version compatibility logic unless requested. + +## Build Commands + +From the plugin checkout: + +```bash +dotnet restore +dotnet build -c Release -p:DisableCopyToPlugins=True +``` + +For a Debug smoke build: + +```bash +dotnet build -c Debug -p:DisableCopyToPlugins=True +``` + +Expected outputs: + +```text +bin/Debug/.dll +bin/Release/.dll +bin/Release/zip/-.zip +bin//Artifact/Plugins/.dll +``` + +The exact layout depends on BSMT version and project customization. + +## BSMT Copy Behavior + +`BeatSaberModdingTools.Tasks` may copy built DLLs into the game directory. On Unix hosts, its process check can behave differently and some projects add a custom target to copy into `/Plugins/`. + +For builds that should not alter a live instance, pass both property names; different BSMT projects/targets surface different copy switches: + +```bash +-p:DisableCopyToPlugins=True -p:DisableCopyToGame=True +``` + +If the purpose is to install the built artifact, copy it into plugin-helper's state downloads and use `plugin-helper plan/apply`, or follow `docs/SMOKETEST.md` for intentional live validation. + +## Common Reference Failures + +- `MSB3644` or missing `.NETFramework,Version=v4.7.2` reference assemblies: add `Microsoft.NETFramework.ReferenceAssemblies.net472`. +- Missing game assemblies such as `Main.dll`, `HMUI.dll`, `UnityEngine.CoreModule.dll`: `BeatSaberDir` is wrong or incomplete. +- Missing mod dependencies such as `BSML.dll`, `SongCore.dll`, `SiraUtil.dll`, `BeatSaberPlaylistsLib.dll`: install or point at an instance containing those plugins, or fetch the dependency DLL from its verified release only when appropriate. +- `IPA.Loader.dll` missing: BSIPA is not bootstrapped in that instance. + +## Artifact Handoff To plugin-helper + +For a built plugin DLL intended for a managed instance: + +```bash +mkdir -p .state/instances//downloads/ +cp //bin/Release/.dll .state/instances//downloads//.dll +sha256sum .state/instances//downloads//.dll +``` + +Then update registry/lock data only if the user asked to manage/install the artifact, and use: + +```bash +PYTHONPATH=src python -m plugin_helper --state-dir .state check --instance +PYTHONPATH=src python -m plugin_helper --state-dir .state plan --instance --plugin +PYTHONPATH=src python -m plugin_helper --state-dir .state apply +``` + +Inspect the generated plan before applying. diff --git a/.agents/skills/install-beatsaber-plugin/SKILL.md b/.agents/skills/beatsaber-plugin-manager/SKILL.md similarity index 77% rename from .agents/skills/install-beatsaber-plugin/SKILL.md rename to .agents/skills/beatsaber-plugin-manager/SKILL.md index 39c7140..e0e9d77 100644 --- a/.agents/skills/install-beatsaber-plugin/SKILL.md +++ b/.agents/skills/beatsaber-plugin-manager/SKILL.md @@ -1,5 +1,5 @@ --- -name: install-beatsaber-plugin +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. --- @@ -12,11 +12,16 @@ Use the repository's own `plugin-helper` commands to manage plugins for BSManage ## Hard Guardrail -For ordinary GitHub-hosted plugins, require an explicit GitHub release URL from -the user's prompt before selecting any release or repository. +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 prompt does not contain a GitHub release URL, stop and ask the user for it. -- Do not search the web to discover a repository or "correct" release URL. +- 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. @@ -75,15 +80,38 @@ https://github.com///releases/download// For BeatMods bootstrap or verified packages, query BeatMods with a browser-like user agent: ```bash - python - <<'PY' + PYTHONPATH=src python - <<'PY' import json, urllib.request + from plugin_helper.beatmods import by_version_id, normalize_mods + game_version = "" 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 = data["mods"] if isinstance(data, dict) and "mods" in data else data - print(json.dumps(mods, indent=2)[:20000]) + 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 ``` @@ -103,7 +131,8 @@ https://github.com///releases/download// 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 URL only. +For GitHub URLs, resolve the release from the user-provided repository or +release URL only. Derive `/` and optional `` from the URL. Query the GitHub API directly for metadata: @@ -189,13 +218,20 @@ https://github.com///releases/download// Use `PYTHONPATH=src`; plain `python -m unittest` may fail in this source-layout repo. - For live Beat Saber validation, follow `docs/SMOKETEST.md`. 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. 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. + 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: diff --git a/.agents/skills/install-beatsaber-plugin/agents/openai.yaml b/.agents/skills/beatsaber-plugin-manager/agents/openai.yaml similarity index 72% rename from .agents/skills/install-beatsaber-plugin/agents/openai.yaml rename to .agents/skills/beatsaber-plugin-manager/agents/openai.yaml index fa32492..6c652df 100644 --- a/.agents/skills/install-beatsaber-plugin/agents/openai.yaml +++ b/.agents/skills/beatsaber-plugin-manager/agents/openai.yaml @@ -1,4 +1,4 @@ interface: display_name: "Install Beat Saber Plugin" short_description: "Update Beat Saber plugins via helper" - default_prompt: "Use $install-beatsaber-plugin to install or update a Beat Saber plugin from this release URL: ." + default_prompt: "Use $beatsaber-plugin-manager to install or update a Beat Saber plugin from this release URL: ."