From dfb4fc4ffda88f147d032ca6b824b55633a3f889 Mon Sep 17 00:00:00 2001 From: spoopyghosty0 <336754034+spoopyghosty0@users.noreply.github.com> Date: Wed, 30 Sep 2026 14:14:00 -0400 Subject: [PATCH] Share working configs and diagnostics bundles via GitHub issues MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - "Share working config…" (game menu, `frameport share-recipe`): saves the recipe as known-good and opens a prefilled working-config issue form; labelling it `catalog-accepted` runs catalog-from-issue.yml, which validates the YAML (scripts/catalog_from_issue.py) and opens a PR adding catalog/games/.yaml - "Collect logs" / "Report a problem…" (game menu, Settings, failure pop-up, `frameport diag collect|report`): redacted diagnostics zip (app + job logs, library entry, recipe, launch logs + re-triage, APK/ELF/PE listings instead of game files, Frame launcher/settings/Lepton/logcat/XRService logs via agent v21 `collect_diag`) and a prefilled bug-report issue to attach it to; `frameport diag inspect ` re-triages a bundle offline - diag/redact.py scrubs IPs, MACs, host/user names, home dirs, Steam ids, data and dump folders - App log (/logs/app.log) and saved GUI job logs (/logs/jobs/) - catalog.entry_from_library shared by "Save as known-good" and sharing; docs/DIAGNOSTICS.md --- .github/ISSUE_TEMPLATE/bug-report.yml | 44 +++ .github/ISSUE_TEMPLATE/config.yml | 1 + .github/ISSUE_TEMPLATE/working-config.yml | 49 +++ .github/workflows/catalog-from-issue.yml | 52 +++ CLAUDE.md | 6 + agent/frameport_agent.py | 86 ++++- docs/DIAGNOSTICS.md | 63 ++++ docs/INSTALL.md | 10 + docs/PLAYBOOK.md | 2 + scripts/catalog_from_issue.py | 101 ++++++ scripts/ui_smoke.py | 5 +- src/frameport/cli.py | 111 ++++++ src/frameport/core/applog.py | 75 ++++ src/frameport/core/winhost.py | 33 ++ src/frameport/diag/__init__.py | 1 + src/frameport/diag/bundle.py | 404 ++++++++++++++++++++++ src/frameport/diag/issue.py | 110 ++++++ src/frameport/diag/redact.py | 171 +++++++++ src/frameport/pipeline.py | 45 +++ src/frameport/recommend/catalog.py | 49 ++- src/frameport/targets/base.py | 4 + src/frameport/targets/frame_lepton.py | 3 + src/frameport/targets/pc_revive.py | 14 + src/frameport/ui/app.py | 132 +++++-- src/frameport/ui/components.py | 1 - src/frameport/ui/help.py | 7 + src/frameport/ui/jobs.py | 17 +- src/frameport/ui/views/activity.py | 5 +- src/frameport/ui/views/settings.py | 10 +- tests/test_art.py | 2 - tests/test_diag.py | 269 ++++++++++++++ tests/test_pc_target.py | 1 - tests/test_ui_help.py | 8 + uv.lock | 2 +- 34 files changed, 1849 insertions(+), 44 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/bug-report.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/working-config.yml create mode 100644 .github/workflows/catalog-from-issue.yml create mode 100644 docs/DIAGNOSTICS.md create mode 100644 scripts/catalog_from_issue.py create mode 100644 src/frameport/core/applog.py create mode 100644 src/frameport/diag/__init__.py create mode 100644 src/frameport/diag/bundle.py create mode 100644 src/frameport/diag/issue.py create mode 100644 src/frameport/diag/redact.py create mode 100644 tests/test_diag.py diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml new file mode 100644 index 0000000..9240d78 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -0,0 +1,44 @@ +name: Problem report +description: A game or FramePort doesn't work (FramePort → "Report a problem…" fills this in and saves a diagnostics zip). +title: "[Problem] " +labels: ["bug"] +body: + - type: markdown + attributes: + value: | + Please attach the **diagnostics zip** FramePort saved (drag it into the "Diagnostics" box below). It contains + logs, the recipe and device details with personal data removed — no game files. Without the app, it can be + read with `frameport diag inspect `. + - type: input + id: game + attributes: + label: Game + description: Title — package id (version, engine / XR API); empty for app problems + - type: textarea + id: description + attributes: + label: What happens? + description: What you did, what you expected, what you saw (in the headset, if it started) + validations: + required: true + - type: textarea + id: findings + attributes: + label: Launch test / triage + description: Filled in by FramePort from the last launch test + - type: textarea + id: recipe + attributes: + label: Recipe + render: yaml + - type: textarea + id: environment + attributes: + label: Environment + - type: textarea + id: logs + attributes: + label: Diagnostics + description: Drag the FramePort-diag-*.zip here (max 25 MB) + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..0086358 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1 @@ +blank_issues_enabled: true diff --git a/.github/ISSUE_TEMPLATE/working-config.yml b/.github/ISSUE_TEMPLATE/working-config.yml new file mode 100644 index 0000000..78e7219 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/working-config.yml @@ -0,0 +1,49 @@ +name: Working configuration +description: Submit a recipe that works on the Steam Frame (FramePort → game menu → "Share working config…" fills this in). +title: "[Working config] " +labels: ["working-config"] +body: + - type: markdown + attributes: + value: | + Thanks! A maintainer reviews the recipe; once it's labelled `catalog-accepted`, a pull request adding it to + `catalog/games/` is opened automatically. Please don't paste game files or links to them. + - type: input + id: game + attributes: + label: Game + description: Title — package id (platform, version, engine / XR API) + validations: + required: true + - type: input + id: result + attributes: + label: Result + description: works or issues (+ the last headless launch test) + validations: + required: true + - type: textarea + id: recipe + attributes: + label: Recipe + description: The catalog entry (catalog/games/.yaml). Don't edit the package line. + render: yaml + validations: + required: true + - type: textarea + id: environment + attributes: + label: Environment + description: FramePort, tool and SteamOS versions + - type: textarea + id: notes + attributes: + label: Notes + description: What you checked in the headset, known issues, anything unusual + - type: checkboxes + id: confirm + attributes: + label: Confirmation + options: + - label: I played the game in the headset with this recipe (headless launch tests can't check the picture). + required: true diff --git a/.github/workflows/catalog-from-issue.yml b/.github/workflows/catalog-from-issue.yml new file mode 100644 index 0000000..38227a5 --- /dev/null +++ b/.github/workflows/catalog-from-issue.yml @@ -0,0 +1,52 @@ +name: catalog-from-issue +# A maintainer adds the label `catalog-accepted` to a "Working configuration" issue → this opens a PR adding the +# recipe to catalog/games/. Never runs for unlabelled issues; the issue text is only read by the validating script +# (never interpolated into shell commands). Needs Settings → Actions → "Allow GitHub Actions to create pull requests". +on: + issues: + types: [labeled] + +permissions: + contents: write + pull-requests: write + issues: write + +jobs: + recipe-pr: + if: github.event.label.name == 'catalog-accepted' && contains(github.event.issue.labels.*.name, 'working-config') + runs-on: ubuntu-latest + env: + GH_TOKEN: ${{ github.token }} + ISSUE: ${{ github.event.issue.number }} + steps: + - uses: actions/checkout@v7 + - uses: astral-sh/setup-uv@v10.2.0 + - run: uv sync + - name: Write the recipe + id: recipe + run: | + gh issue view "$ISSUE" --json body -q .body > "$RUNNER_TEMP/body.md" + uv run python scripts/catalog_from_issue.py "$RUNNER_TEMP/body.md" --issue "$ISSUE" | tee "$RUNNER_TEMP/out.txt" + grep '^package=' "$RUNNER_TEMP/out.txt" >> "$GITHUB_OUTPUT" + - name: Check it loads + env: + PKG: ${{ steps.recipe.outputs.package }} + run: uv run python -c "import os, sys; from frameport.recommend import catalog; sys.exit(os.environ['PKG'] not in catalog.load(refresh=True))" + - name: Open the pull request + env: + PKG: ${{ steps.recipe.outputs.package }} + run: | + branch="catalog/issue-$ISSUE" + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git checkout -b "$branch" + git add "catalog/games/$PKG.yaml" + git commit -m "catalog: $PKG (from #$ISSUE)" + git push --force origin "$branch" + if ! gh pr view "$branch" >/dev/null 2>&1; then + gh pr create --head "$branch" --title "catalog: $PKG" \ + --body "Recipe submitted in #$ISSUE (working configuration). Closes #$ISSUE." + fi + - name: Report a failure on the issue + if: failure() + run: gh issue comment "$ISSUE" --body "Couldn't turn this into a catalog recipe — see the workflow run ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}." diff --git a/CLAUDE.md b/CLAUDE.md index 559ff9f..6d12c1b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -105,6 +105,12 @@ Rick and Morty runs on the Frame via its catalog recipe (OpenVR, no Revive), release matching the DLL build date, since Revive's version resources are stale) > portable copy unpacked from ReviveInstaller.exe in pure Python; never replaces the user's install). Library ids `rift.`, entries have `kind: rift`. - `parity.py` — rebuild catalog games from dumps and classify every APK entry difference vs known-good builds. + - `diag/` — user feedback without tokens (docs/DIAGNOSTICS.md): `redact.py` (every file/issue text: IPs, hosts, + home dirs, Steam ids, dump folders → placeholders), `bundle.py` (redacted diagnostics zip; agent v21 + `collect_diag`; `frameport diag collect|inspect|report`), `issue.py` (prefilled GitHub issue-form links, ≤7.5k + chars). "Share working config" → `working-config.yml` issue → maintainer label `catalog-accepted` → + `catalog-from-issue.yml` workflow (`scripts/catalog_from_issue.py` validates) opens a catalog PR. App log: + `core/applog.py` (`/logs/app.log`, finished GUI jobs in `/logs/jobs/`). - `agent/frameport_agent.py` — runs **on the Frame** (python3 stdlib only), JSON over SSH. Owns the install layout, launch.sh template, Steam shortcuts (binary VDF), launch tests. Bump `AGENT_VERSION` when changing it. - `bootstrap/bootstrap.sh` — one-time Frame setup served by the pairing server (sshd, app key, avahi service, Lepton). diff --git a/agent/frameport_agent.py b/agent/frameport_agent.py index cfc3342..75ec6a4 100644 --- a/agent/frameport_agent.py +++ b/agent/frameport_agent.py @@ -33,7 +33,7 @@ import sys import time import zlib -AGENT_VERSION = 20 +AGENT_VERSION = 21 HOME = os.path.expanduser("~") STEAM = os.path.join(HOME, ".local/share/Steam") ANCHORS = os.path.join(HOME, "Applications/quest-frame") @@ -1849,6 +1849,90 @@ def launch_test_pcvr(dep, anchor, log, seconds): "kind": "pcvr", "game_process": bool(game_seen)} +def _tail(path, max_bytes): + try: + size = os.path.getsize(path) + with open(path, "rb") as f: + if size > max_bytes: + f.seek(size - max_bytes) + data = f.read() + text = data.decode("utf-8", "replace") + return (f"[... first {size - max_bytes} bytes cut ...]\n" if size > max_bytes else "") + text + except OSError: + return None + + +def cmd_collect_diag(args): + """Everything useful for debugging without the game or the PC app: host runtime facts and, with a package, the + game's launcher, settings, deployment, logs (launch, Lepton logcat, Proton/Revive/Unreal) and its file listing. + Returns {"host": {...}, "files": {name: text}, "listing": ...}; the PC redacts and zips it.""" + max_bytes = int(args.get("max_bytes", 2 << 20)) + files, host = {}, {} + host["openxr_runtime"] = openxr_runtime() + layers = os.path.join(HOME, ".local/share/openxr/1/api_layers/explicit.d") + host["openxr_layers"] = sorted(os.listdir(layers)) if os.path.isdir(layers) else [] + host["kernel_keys"] = key_usage() + host["containers_conf"] = _tail(CONTAINERS_CONF, 20000) + host["podman"] = run(["podman", "ps", "-a", "--filter", "name=lepton", "--format", + "{{.Names}} {{.Status}}"]).stdout[-20000:] + host["steam_running"] = run(["pgrep", "-x", "steam"]).returncode == 0 + host["uptime"] = _tail("/proc/uptime", 200) + host["installed"] = [{k: g.get(k) for k in ("package", "title", "kind", "appid", "version", "agent_version")} + for g in cmd_list_installed({})["games"]] + out = {"agent_version": AGENT_VERSION, "host": host, "files": files} + pkg = args.get("package") + if not pkg: + # the OpenXR runtime's own log (XRService-_