From 17bd736e5932b2d8da744e22fcc30958bf8e74d3 Mon Sep 17 00:00:00 2001 From: pleb Date: Sun, 28 Jun 2026 14:35:21 -0700 Subject: [PATCH] Document plugin helper agent workflow --- .../skills/install-beatsaber-plugin/SKILL.md | 83 +++++++++- AGENTS.md | 49 ++++++ docs/SMOKETEST.md | 144 ++++++++++++++++++ 3 files changed, 270 insertions(+), 6 deletions(-) create mode 100644 AGENTS.md create mode 100644 docs/SMOKETEST.md diff --git a/.agents/skills/install-beatsaber-plugin/SKILL.md b/.agents/skills/install-beatsaber-plugin/SKILL.md index f9c3472..39c7140 100644 --- a/.agents/skills/install-beatsaber-plugin/SKILL.md +++ b/.agents/skills/install-beatsaber-plugin/SKILL.md @@ -1,15 +1,19 @@ --- name: install-beatsaber-plugin -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, or manage a Beat Saber plugin release for a BSManager instance. The user must provide an explicit GitHub release URL in the prompt; if no release URL is present, ask for one and do not infer, search for, or substitute a different repository. +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. Do not manually copy release files into the game instance except to undo your own mistaken install before rerunning the helper. +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 -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 release URL from +the user's prompt before selecting any release or repository. - 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. @@ -17,6 +21,17 @@ Require an explicit GitHub release URL from the user's prompt before selecting a - 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 @@ -44,6 +59,7 @@ https://github.com///releases/download// sed -n '1,220p' src/plugin_helper/models.py sed -n '1,220p' registry/plugins.toml sed -n '1,220p' locks/.lock.toml + sed -n '1,220p' docs/SMOKETEST.md ``` 3. Determine the instance. @@ -54,9 +70,42 @@ https://github.com///releases/download// PYTHONPATH=src python -m plugin_helper instances ``` -4. Resolve the release from the user-provided URL only. +4. Resolve the release source. - For GitHub URLs, derive `/` and optional `` from the URL. Query the GitHub API directly for metadata: + For BeatMods bootstrap or verified packages, query BeatMods with a browser-like user agent: + + ```bash + python - <<'PY' + import json, urllib.request + 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]) + 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/.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 URL only. + + Derive `/` and optional `` from the URL. Query the GitHub API directly for metadata: ```bash curl -sS https://api.github.com/repos///releases @@ -85,7 +134,7 @@ https://github.com///releases/download// - `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. + - `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. @@ -140,6 +189,28 @@ 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. + + For BSIPA/SongCore bootstrap, expected successful log lines include: + + ```text + Game version + Loading plugins from Plugins and found + Beat Saber IPA (BSIPA): + SongCore (SongCore): + ``` + + 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: diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..78e3e54 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,49 @@ +# AGENTS.md + +Guidance for coding agents working in this repo. + +## Project Shape + +- This repo manages Beat Saber plugins for BSManager instances. +- Default instance roots are: + - `/home/pleb/Windows/Users/pleb/BSManager/BSInstances` + - `/home/pleb/.local/share/BSManager/BSInstances` +- A local BSManager source checkout may be available at + `/home/pleb/src/Zagrios/bs-manager`. Use it as a read-only reference when + investigating launch behavior, inherited Steam arguments, instance layout, or + Proton environment details unless the user explicitly asks for BSManager code + changes. +- Prefer repo-local state with `--state-dir .state` for planned installs unless + the task explicitly targets the user's live default state. + +## Workflow Rules + +- Run commands from the repo root with `PYTHONPATH=src`. +- For human-style inspection, prefer the menu with repo-local state: + `PYTHONPATH=src python -m plugin_helper --state-dir .state menu`. +- When targeting the local Linux BSManager install rather than the Windows + mirror, pass `--instances-root /home/pleb/.local/share/BSManager/BSInstances` + or choose that path explicitly in the menu. +- Use the helper commands instead of manually copying plugin files into an + instance. +- Treat BSIPA as a bootstrap phase: + - `bootstrap` installs the locked BSIPA archive and records generated files. + - ordinary plugin plans should depend on healthy bootstrap state. +- Be careful with duplicate instance names across Windows and local roots. Use + the menu or pass `--instances-root` explicitly when targeting one install. + +## Validation + +- Run `PYTHONPATH=src python -m unittest discover -s tests` after code changes. +- Run `PYTHONPATH=src python -m compileall -q src tests` for syntax/import + checks. +- For live game validation, follow `docs/SMOKETEST.md` and tear down Beat Saber + processes afterward. + +## Launch Notes + +- BSManager may inherit Beat Saber launch arguments configured in Steam. +- Do not assume a black screen is a plugin failure until checking + `Logs/_latest.log`, Unity `Player.log`, and the live process command line. +- Duplicate launch args such as `--no-yeet fpfc --no-yeet fpfc` can trigger a + fatal command-line parse error after BSIPA/plugin loading succeeds. diff --git a/docs/SMOKETEST.md b/docs/SMOKETEST.md new file mode 100644 index 0000000..2d3d930 --- /dev/null +++ b/docs/SMOKETEST.md @@ -0,0 +1,144 @@ +# Beat Saber Smoketest Workflow + +Use this workflow after installing or removing a plugin batch. It is adapted +from the Setlist repo's working Proton/BSManager smoketest notes. + +The routine smoketest should be short: about 10 seconds wall time for launch and +log check, followed by immediate teardown. If expected plugin log lines do not +appear inside that window, treat that as a failure to investigate instead of +stretching the timeout. + +## Preconditions + +- Run from the target BSManager instance directory, for example: + +```sh +cd "$HOME/.local/share/BSManager/BSInstances/1.44.1" +``` + +- The instance should contain BSIPA (`IPA/`, `IPA.exe`, `winhttp.dll`, and + `Plugins/`). +- If those files are absent, the run can still produce Unity `Player.log` + output, but it will not produce `Logs/_latest.log` or any IPA plugin-loading + evidence. Run `plugin-helper bootstrap --instance ` before using + this workflow to validate plugins. +- On Plasma + Wayland, the shell needs working desktop session variables: + `DISPLAY`, `WAYLAND_DISPLAY`, and `XDG_RUNTIME_DIR`. If the IDE terminal is + missing them, copy values from the logged-in desktop session. + +## Launch + +Run the Proton launch in the foreground with a short watchdog. Backgrounding +the Proton launch itself from an IDE terminal has been observed to exit early +before Beat Saber writes useful `_latest.log` lines. + +Important: `timeout` may stop the launcher wrapper without killing the full +Beat Saber/Proton process tree. Prefer a watchdog that sleeps for the smoke +window and then kills the game process by name. + +```sh +export SteamAppId=620980 SteamOverlayGameId=620980 SteamGameId=620980 +export WINEDLLOVERRIDES='winhttp=n,b' +export STEAM_COMPAT_DATA_PATH="$HOME/.local/share/BSManager/SharedContent/compatdata" +export STEAM_COMPAT_INSTALL_PATH="$PWD" +export STEAM_COMPAT_CLIENT_INSTALL_PATH="$HOME/.local/share/Steam" +export STEAM_COMPAT_APP_ID=620980 +export SteamEnv=1 +export OXR_PARALLEL_VIEWS=1 + +( + sleep 10 + pkill -TERM -f "[B]eat Saber.exe" || pkill -TERM -f "[B]eat Saber" || true + sleep 2 + pkill -KILL -f "[B]eat Saber.exe" || pkill -KILL -f "[B]eat Saber" || true +) & +smoke_watchdog_pid=$! +trap 'kill "$smoke_watchdog_pid" 2>/dev/null || true' EXIT + +steam-run "$HOME/.local/share/Steam/steamapps/common/Proton - Experimental/proton" \ + run "$PWD/Beat Saber.exe" --no-yeet fpfc 2>&1 | tee /tmp/bs-smoke.log + +wait "$smoke_watchdog_pid" 2>/dev/null || true +trap - EXIT +``` + +Useful launch arguments: + +| Argument | Use | +| --- | --- | +| `--verbose` | Opens the BSIPA console window. | +| `--debug` | Promotes debug logs to console output. | +| `--trace` | Enables very noisy BSIPA/internal traces. | +| `fpfc` | First-Person Flying Controller, useful for non-VR menu testing. | +| `--auto_play` | Built-in autoplayer for gameplay checks. | + +## Log Checks + +In another terminal, or immediately after the launch returns: + +```sh +tail -F Logs/_latest.log +``` + +For a batch install, check for: + +- BSIPA startup reaches plugin loading. +- the expected plugin names and versions appear in `Logs/_latest.log`. +- there are no missing assembly or dependency-resolution failures. +- there are no repeated unhandled exceptions from newly installed plugins. +- the game reaches the main menu. + +After the first successful launch, `plugin-helper bootstrap-check --instance +` should pass. Ordinary plugin plans for lockfiles that include BSIPA +depend on that recorded bootstrap state and the latest IPA log. + +For Setlist specifically: + +```sh +grep Setlist Logs/_latest.log +``` + +Expected Setlist signals include `platformUserId=...`, playlist lines with +`hasSyncUrl=...`, and `beatLeaderOwnerConfirmed=...`. When testing real playlist +sync, adding a map to an owned BeatLeader-synced playlist should log an HTTP +success line. + +## Teardown + +When the expected log lines are present before the watchdog fires, stop Beat +Saber immediately from a second terminal instead of waiting for the smoke window +to expire. + +```sh +pkill -TERM -f "[B]eat Saber.exe" || pkill -TERM -f "[B]eat Saber" || true +sleep 2 +pkill -KILL -f "[B]eat Saber.exe" || pkill -KILL -f "[B]eat Saber" || true +``` + +The bracketed pattern avoids matching the shell command that is running the +cleanup. If Beat Saber still remains open, close the game window manually and +then check for leftovers: + +```sh +ps -eo pid,ppid,stat,comm,args | rg -i '[B]eat Saber|[P]roton.*Beat Saber|[w]ineserver|[s]team-run' +``` + +If the smoketest fails, kill leftover Beat Saber, Wine, or Proton processes +before retrying, then inspect `/tmp/bs-smoke.log`, Unity `Player.log` in the +Proton compatdata, and the timestamped files under `Logs/`. + +## Plugin Development Notes From Setlist + +These are relevant when `plugin-helper` installs locally built personal plugins. + +- PC BSIPA plugins are .NET Framework class libraries loaded by BSIPA. Linux + `dotnet build` output is CIL and can be loaded by the Proton game instance. +- BSMT-style builds may copy the DLL directly into `/Plugins/`; + use `-p:DisableCopyToPlugins=True` when a build should not mutate the game + tree. +- `BeatSaberDir` in project user files or hint paths should point at the exact + BSManager instance being targeted. +- If a plugin build produces a shipped artifact, bump the plugin version before + the successful build so the IPA log line identifies the artifact under test. +- Setlist depends on BeatLeader being installed and signed in, PlaylistManager, + and BeatSaberPlaylistsLib. Its normal install target is `Plugins/Setlist.dll`.