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

11 KiB

name, description
name description
beatsaber-plugin-manager 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:

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.

    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:

    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:

    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:

    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:

    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:

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.

  1. Inspect the asset before selecting an install strategy.

    Download to the helper state directory:

    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:

    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.
  2. Update the registry and lockfile.

    Add or update exactly one [[plugins]] entry in registry/plugins.toml with:

    [[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:

    [[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.

  3. Use the helper to validate, plan, and apply.

    Always pass --state-dir .state so the helper uses the repo-local downloaded asset:

    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.

  4. Verify the result.

    Confirm the installed file hashes match the plan or archive members:

    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:

    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.

  5. 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.