Codex skill for development added. Tested by using it to set up Play Palace.

This commit is contained in:
Storm Dragon
2026-02-21 19:19:54 -05:00
parent efc5f20b5b
commit 1f7858e1fa
9 changed files with 675 additions and 0 deletions

View File

@@ -0,0 +1,81 @@
---
name: linux-game-manager-dev
description: Develop and maintain Linux Game Manager in this repository. Use when adding or removing games, editing `.install`/`.launch`/`.update` scripts, changing `linux-game-manager.sh` installer/launcher/removal/update/CLI behavior, validating accessibility-sensitive flows, or refreshing project-local skill docs after functionality changes.
---
# Linux Game Manager Development
Use this skill to make safe, consistent changes to Linux Game Manager and keep the skill accurate as the codebase evolves.
## Fast Routing
1. If the task adds/removes/changes a game, read `references/game-extension.md`.
2. If the task changes orchestration/CLI/config behavior, read `references/core-map.md`.
3. If the task changes behavior and you are updating this skill, read `references/skill-maintenance.md`.
4. Run `python3 .codex/skills/linux-game-manager-dev/scripts/audit_game_catalog.py` before and after major game-catalog changes.
## Core Workflow
1. Inspect relevant behavior and identify affected surfaces (`.install`, `.launch`, `.update`, `linux-game-manager.sh`, `speech`, docs).
2. Implement with existing patterns and helper functions instead of inventing new flows.
3. Validate runtime behavior with CLI/manual checks:
- `./linux-game-manager.sh -i`
- `./linux-game-manager.sh -r`
- `./linux-game-manager.sh -u`
- `./linux-game-manager.sh -t`
4. Validate catalog consistency:
- `python3 .codex/skills/linux-game-manager-dev/scripts/audit_game_catalog.py`
5. If bash files were edited:
- Verify `shellcheck` is installed.
- If missing, stop and prompt the user to install it (see `references/tooling-prereqs.md`).
- Run shellcheck on edited files and fix all reported errors.
6. If functionality changed, refresh this skill using `references/skill-maintenance.md`.
## Non-Negotiable Rules
1. Keep installer and launcher base filenames identical:
- `.install/<Game Name>.sh`
- `.launch/<Game Name>.game`
2. Ensure launcher scripts include a concrete `installPath`-based path so removal can locate game data.
3. Define `run_update()` in any `.update/<Game>.sh` script.
4. Preserve accessibility-first interaction:
- Keep dialog/yad wrapper usage (`ui_*` helpers) consistent.
- Do not introduce keyboard traps.
- For GUI applications, do not add new `speech-dispatcher`/`spd-say` dependencies.
5. Keep new script variables camelCase, function names snake_case, class names PascalCase.
6. For ambiguous "disable game" requests, default to disabling installer visibility only and preserve launcher playability unless the user explicitly requests complete disable.
## Required Validation
1. Run catalog audit:
- `python3 .codex/skills/linux-game-manager-dev/scripts/audit_game_catalog.py`
2. Run manual command checks relevant to change scope:
- install flow: `./linux-game-manager.sh -i`
- launcher flow: `./linux-game-manager.sh`
- removal flow: `./linux-game-manager.sh -r`
- update flow: `./linux-game-manager.sh -u`
3. Run shellcheck on every edited bash script and fix all issues.
- If shellcheck is unavailable, prompt the user to install it before continuing.
4. Validate this skill structure after edits:
- `python3 /home/storm/.codex/skills/.system/skill-creator/scripts/quick_validate.py .codex/skills/linux-game-manager-dev`
## Mandatory Skill Maintenance
Treat this skill as stale immediately after any change to:
- `linux-game-manager.sh`
- Any file in `.install/`, `.launch/`, `.update/`, or `speech/`
- `README.md`
- Any change to game lifecycle expectations (install path rules, update hooks, CLI options, helper function contracts)
When stale, perform the full refresh workflow in `references/skill-maintenance.md` before completing the task.
## References
- `references/core-map.md`: Architecture and extension map.
- `references/game-extension.md`: Exact workflow for adding/removing/extending games.
- `references/skill-maintenance.md`: Critical instructions to keep this skill accurate after code changes.
- `references/tooling-prereqs.md`: Required tools and shellcheck installation prompts by distro.
## Scripts
- `scripts/audit_game_catalog.py`: Reports installer/launcher/update consistency and disabled entries.

View File

@@ -0,0 +1,4 @@
interface:
display_name: "Linux Game Manager Dev"
short_description: "Add games and extend linux-game-manager safely"
default_prompt: "Use the project workflows to add games, modify manager behavior, validate accessibility-friendly flows, and keep this skill synchronized with code changes."

View File

@@ -0,0 +1,76 @@
# Linux Game Manager Core Map
Last refreshed: 2026-02-21
## Top-Level Structure
- `linux-game-manager.sh`: Main orchestrator, UI wrappers, CLI option handling, install/remove/update flows, cache and settings behavior.
- `.install/`: Per-game installer scripts sourced by `game_installer`.
- `.launch/`: Per-game launch scripts (`.game`) and runnable entries (`.sh`, usually symlinks).
- `.update/`: Optional per-game update scripts expected to define `run_update()`.
- `speech/speak_window_title.sh`: Accessibility helper for announcing focused window titles.
- `README.md`: Project summary and high-level behavior notes.
- `.files/`: Game-specific auxiliary assets/scripts used by some installers or launchers.
## Catalog Snapshot
- Installers: 38 (`.install/*.sh`)
- Launcher definitions: 38 (`.launch/*.game`)
- Launcher runnable entries: 26 (`.launch/*.sh`, both symlinks and files)
- Update scripts: 5 (`.update/*.sh`)
Regenerate this snapshot with:
```bash
python3 .codex/skills/linux-game-manager-dev/scripts/audit_game_catalog.py
```
## Main Runtime Flows
1. **Install flow**
- `game_installer` builds menu from `.install/*.sh`.
- Selected installer is sourced in the current shell.
- If `.launch/<game>.game` exists and `.launch/<game>.sh` does not, the manager creates a symlink.
2. **Launch flow**
- `game_launcher` enumerates `.launch/*.sh` entries.
- Entries with first line `#//` are skipped.
- Selected launcher is sourced/executed.
3. **Removal flow**
- `game_removal` resolves launcher entry to real script.
- It extracts the first `installPath`-containing line from launcher script to infer directory to remove.
- If no `installPath` reference is found, only launcher entry is removed.
4. **Update flow**
- `game_update` lists `.update/*.sh`.
- Selected updater is sourced.
- `run_update()` is called and must exist.
## CLI Surface
- `-i`: install game
- `-r`: remove game
- `-u`: update game
- `-t`: show number of available games
- `-C`: clear cache
- `-D`: create desktop launcher
- `-L`: show license
- `-h`: show help
- `-N`: enable no-cache mode
- `-R`: force redownload mode
## Config and State
- Cache: `${XDG_CACHE_HOME:-$HOME/.cache}/linux-game-manager`
- Config base: `${XDG_CONFIG_HOME:-$HOME/.config}/storm-games/linux-game-manager`
- Settings overrides: `settings.conf` in config base
- Default install root: `${HOME}/.local/games`
## Contributor Pitfalls
- Mismatched `.install/<Game>.sh` vs `.launch/<Game>.game` names create orphaned or invisible entries.
- Missing `installPath` reference in launcher prevents full uninstall.
- Missing `run_update()` in `.update` scripts breaks update flow.
- Bypassing shared download helpers risks cache and validation regressions.
- For `uv`-based Python games that use `accessible-output2`, include a host copy of `libspeechd.so.2` and export `LD_LIBRARY_PATH` from the launcher to avoid distro packaging differences.

View File

@@ -0,0 +1,134 @@
# Game Extension Workflow
Use this workflow whenever adding, renaming, removing, or substantially changing a game.
## Add a New Game
1. Create installer script:
- Path: `.install/<Game Name>.sh`
- Keep first line active (do not start with `#//`).
- Reuse manager helpers (`check_architecture`, `check_dependencies`, `download`, `download_named`, `get_installer`, `ui_*`).
- Install into a deterministic path under `${installPath}`.
2. Create launcher definition:
- Path: `.launch/<Game Name>.game`
- Keep base filename identical to installer (`<Game Name>`).
- Include an explicit install-path variable based on `installPath`, for example:
```bash
gamePath="${installPath}/MyGame"
```
- Run the game from that path.
3. Optional update script:
- Path: `.update/<Game Name>.sh`
- Implement `run_update()` (required by update flow).
4. Let installer create runnable launcher entry:
- The manager auto-creates `.launch/<Game Name>.sh` symlink when installer completes and `.game` exists.
## Rename a Game Safely
1. Rename `.install/<Old>.sh` to `.install/<New>.sh`.
2. Rename `.launch/<Old>.game` to `.launch/<New>.game`.
3. Rename `.update/<Old>.sh` if present.
4. Remove stale `.launch/<Old>.sh` if still present.
5. Re-run catalog audit and manual checks.
## Remove a Game From Repo
1. Delete `.install/<Game>.sh`.
2. Delete `.launch/<Game>.game`.
3. Delete `.launch/<Game>.sh` if tracked.
4. Delete `.update/<Game>.sh` if present.
5. Run catalog audit to confirm no orphan entries remain.
## Disable Policy (Important)
When a request says "disable <game>" without more detail:
1. Default behavior: disable installs only.
- Add `#//` to first line of `.install/<Game Name>.sh`.
- Do not disable `.launch/<Game Name>.game` by default.
- This preserves playability for users who already have the game installed.
2. If user explicitly requests complete disable:
- Add `#//` to first line of both:
- `.install/<Game Name>.sh`
- `.launch/<Game Name>.game`
3. If wording is ambiguous but could imply full disable/removal:
- Ask a short clarification question before changing launcher/removal behavior.
## Manual Validation Checklist
1. Run installer flow:
```bash
./linux-game-manager.sh -i
```
2. Confirm launcher files exist:
```bash
ls ".launch/<Game Name>.game" ".launch/<Game Name>.sh"
```
3. Run launcher flow:
```bash
./linux-game-manager.sh
```
4. Run removal flow:
```bash
./linux-game-manager.sh -r
```
5. If updater exists, run update flow:
```bash
./linux-game-manager.sh -u
```
6. Run consistency audit:
```bash
python3 .codex/skills/linux-game-manager-dev/scripts/audit_game_catalog.py
```
## Script Authoring Rules
- Use camelCase variable names.
- Use snake_case for function names.
- Keep scripts robust when sourced in the manager shell.
- Avoid introducing unnecessary colorized output.
- For edited bash scripts, run shellcheck and fix all errors.
- If `shellcheck` is not installed, prompt the user to install it first (see `references/tooling-prereqs.md`).
## Pattern: uv + Speech Dispatcher Host Libraries
Use this pattern for Python games installed with `uv` when runtime speech support depends on system `libspeechd`.
1. In installer script:
- Check dependencies: `git` and `uv`.
- Clone game repository into `${installPath}/<GameRepoDir>`.
- Run `uv sync` in the project directory that contains `pyproject.toml`.
- Locate `libspeechd.so.2` from host system using:
- `ldconfig -p` when available.
- Fallback paths for common distros:
- `/usr/lib/libspeechd.so.2` (Arch-style)
- `/usr/lib/x86_64-linux-gnu/libspeechd.so.2` (Debian/Ubuntu-style)
- `/usr/lib64/libspeechd.so.2` and related `/lib*` fallbacks
- Copy resolved library into a game-local directory such as:
- `${installPath}/<GameRepoDir>/<RuntimePath>/.host-libs/libspeechd.so.2`
- Fail with a clear message if library is not found.
2. In launcher script:
- Export `LD_LIBRARY_PATH` with the game-local `.host-libs` directory prepended.
- Launch via `uv run ...` from the same directory used for `uv sync`.
3. Removal safety:
- Ensure first `installPath` line in launcher points to a path that lets `game_removal` infer the game root correctly.

View File

@@ -0,0 +1,53 @@
# Skill Maintenance (Critical)
This skill is only trustworthy if it is refreshed immediately after behavior changes.
## Mandatory Refresh Triggers
Run this maintenance workflow whenever any of these change:
- `linux-game-manager.sh`
- Anything in `.install/`
- Anything in `.launch/`
- Anything in `.update/`
- Anything in `speech/`
- `README.md`
- Any game lifecycle contract (install path usage, symlink behavior, update function contracts, CLI flags)
## Refresh Workflow
1. Run catalog audit and capture output:
```bash
python3 .codex/skills/linux-game-manager-dev/scripts/audit_game_catalog.py
```
2. Re-check core flow definitions in `linux-game-manager.sh`:
```bash
rg -n "game_installer|game_launcher|game_removal|game_update|getopts|help\\(" linux-game-manager.sh
```
3. Update skill references:
- Update `references/core-map.md` catalog snapshot if counts changed.
- Update `references/core-map.md` flow descriptions if behavior changed.
- Update `references/game-extension.md` if onboarding or naming rules changed.
- Update this file if refresh triggers or process changed.
4. Validate the skill structure:
```bash
python3 /home/storm/.codex/skills/.system/skill-creator/scripts/quick_validate.py .codex/skills/linux-game-manager-dev
```
5. If any bash scripts changed, run shellcheck on edited files and fix all errors.
- If `shellcheck` is missing, pause and prompt the user to install it using `references/tooling-prereqs.md`.
## Completion Criteria
Do not consider maintenance complete until all are true:
1. Catalog audit shows no critical mismatches, and any warnings are reviewed.
2. All changed behavior is reflected in skill reference files.
3. Skill passes `quick_validate.py`.
4. Any edited bash scripts are shellcheck-clean.

View File

@@ -0,0 +1,38 @@
# Tooling Prerequisites
## Required Tool for Bash Changes
- `shellcheck` is mandatory whenever editing any bash/sh file in this repository.
- If `shellcheck` is not available, prompt the user to install it before continuing validation.
Check availability:
```bash
command -v shellcheck
```
## Prompt Template
Use this exact style when missing:
`shellcheck is required for bash/sh edits in linux-game-manager. Please install it, then I will continue validation.`
## Install Commands by Common Distro Family
- Arch/Manjaro:
- `sudo pacman -S shellcheck`
- Debian/Ubuntu/Linux Mint/Pop!_OS:
- `sudo apt update && sudo apt install -y shellcheck`
- Fedora/RHEL/CentOS Stream:
- `sudo dnf install -y ShellCheck`
- openSUSE:
- `sudo zypper install -y ShellCheck`
- Alpine:
- `sudo apk add shellcheck`
If distro is unknown, ask the user what distribution they are on and provide the matching package command.
## Notes for LGM Collaboration
- Do not skip shellcheck for “small” bash changes.
- Fix all shellcheck errors; warnings may be suppressed only when required by sourced-global patterns and with a short comment.

View File

@@ -0,0 +1,202 @@
#!/usr/bin/env python3
"""Audit Linux Game Manager catalog consistency."""
from __future__ import annotations
import argparse
import json
import sys
from pathlib import Path
def read_first_line(file_path: Path) -> str:
try:
with file_path.open("r", encoding="utf-8") as handle:
return handle.readline().strip()
except OSError:
return ""
def has_run_update(file_path: Path) -> bool:
try:
content = file_path.read_text(encoding="utf-8")
except OSError:
return False
return "run_update()" in content or "run_update ()" in content
def collect_names(file_paths: list[Path], suffix: str) -> list[str]:
return sorted(file_path.name[: -len(suffix)] for file_path in file_paths)
def count_disabled(file_paths: list[Path]) -> list[str]:
disabledNames = []
for file_path in file_paths:
if read_first_line(file_path).startswith("#//"):
disabledNames.append(file_path.name)
return sorted(disabledNames)
def build_report(repo_root: Path) -> dict:
installDir = repo_root / ".install"
launchDir = repo_root / ".launch"
updateDir = repo_root / ".update"
missingDirs = [
str(path.relative_to(repo_root))
for path in [installDir, launchDir, updateDir]
if not path.exists()
]
if missingDirs:
raise FileNotFoundError(
f"Expected directories not found: {', '.join(missingDirs)}"
)
installPaths = sorted(installDir.glob("*.sh"))
launchGamePaths = sorted(launchDir.glob("*.game"))
launchShPaths = sorted(launchDir.glob("*.sh"))
updatePaths = sorted(updateDir.glob("*.sh"))
installerNames = collect_names(installPaths, ".sh")
launcherGameNames = collect_names(launchGamePaths, ".game")
launcherShNames = collect_names(launchShPaths, ".sh")
launcherSymlinkCount = sum(1 for path in launchShPaths if path.is_symlink())
launcherFileCount = len(launchShPaths) - launcherSymlinkCount
missingLauncherGameForInstaller = sorted(
set(installerNames) - set(launcherGameNames)
)
missingInstallerForLauncherGame = sorted(
set(launcherGameNames) - set(installerNames)
)
missingLauncherShForLauncherGame = sorted(
set(launcherGameNames) - set(launcherShNames)
)
orphanLauncherSh = sorted(set(launcherShNames) - set(launcherGameNames))
updateWithoutRunFunction = sorted(
path.name for path in updatePaths if not has_run_update(path)
)
report = {
"repoRoot": str(repo_root),
"criticalMismatches": [
"missingLauncherGameForInstaller",
"updateScriptsMissingRunUpdate",
],
"warningMismatches": [
"missingInstallerForLauncherGame",
"missingLauncherShForLauncherGame",
"orphanLauncherSh",
],
"counts": {
"installers": len(installPaths),
"launcherGameDefinitions": len(launchGamePaths),
"launcherRunnableEntries": len(launchShPaths),
"launcherRunnableSymlinks": launcherSymlinkCount,
"launcherRunnableRegularFiles": launcherFileCount,
"updateScripts": len(updatePaths),
},
"disabledEntries": {
"installers": count_disabled(installPaths),
"launchers": count_disabled(launchShPaths),
},
"mismatches": {
"missingLauncherGameForInstaller": missingLauncherGameForInstaller,
"missingInstallerForLauncherGame": missingInstallerForLauncherGame,
"missingLauncherShForLauncherGame": missingLauncherShForLauncherGame,
"orphanLauncherSh": orphanLauncherSh,
"updateScriptsMissingRunUpdate": updateWithoutRunFunction,
},
}
return report
def print_human_report(report: dict) -> None:
counts = report["counts"]
mismatches = report["mismatches"]
criticalMismatches = report["criticalMismatches"]
warningMismatches = report["warningMismatches"]
disabledEntries = report["disabledEntries"]
print(f"Repository: {report['repoRoot']}")
print("Counts:")
print(f" Installers: {counts['installers']}")
print(f" Launcher .game files: {counts['launcherGameDefinitions']}")
print(f" Launcher .sh entries: {counts['launcherRunnableEntries']}")
print(f" Symlinks: {counts['launcherRunnableSymlinks']}")
print(f" Regular files: {counts['launcherRunnableRegularFiles']}")
print(f" Update scripts: {counts['updateScripts']}")
print("Disabled entries:")
print(f" Installers: {len(disabledEntries['installers'])}")
if disabledEntries["installers"]:
print(" " + ", ".join(disabledEntries["installers"]))
print(f" Launchers: {len(disabledEntries['launchers'])}")
if disabledEntries["launchers"]:
print(" " + ", ".join(disabledEntries["launchers"]))
print("Critical checks:")
for key in criticalMismatches:
values = mismatches[key]
print(f" {key}: {len(values)}")
if values:
print(" " + ", ".join(values))
print("Warnings:")
for key in warningMismatches:
values = mismatches[key]
print(f" {key}: {len(values)}")
if values:
print(" " + ", ".join(values))
criticalTotal = sum(len(mismatches[key]) for key in criticalMismatches)
if criticalTotal == 0:
print("Result: OK (no critical mismatches)")
else:
print("Result: ATTENTION REQUIRED (critical mismatches detected)")
def parse_args() -> argparse.Namespace:
defaultRepoRoot = Path(__file__).resolve().parents[4]
parser = argparse.ArgumentParser(
description="Audit installer/launcher/update consistency for linux-game-manager."
)
parser.add_argument(
"--repo-root",
default=str(defaultRepoRoot),
help=f"Repository root path (default: {defaultRepoRoot})",
)
parser.add_argument(
"--json",
action="store_true",
help="Emit JSON instead of human-readable text",
)
return parser.parse_args()
def main() -> int:
args = parse_args()
repoRoot = Path(args.repo_root).resolve()
try:
report = build_report(repoRoot)
except FileNotFoundError as error:
print(str(error), file=sys.stderr)
return 2
if args.json:
print(json.dumps(report, indent=2, sort_keys=True))
else:
print_human_report(report)
criticalTotal = sum(
len(report["mismatches"][bucket]) for bucket in report["criticalMismatches"]
)
return 1 if criticalTotal else 0
if __name__ == "__main__":
raise SystemExit(main())

69
.install/Play Palace.sh Normal file
View File

@@ -0,0 +1,69 @@
check_dependencies git uv
# shellcheck disable=SC2154 # installPath is exported by linux-game-manager.sh
gameRoot="${installPath}/PlayPalace11"
desktopPath="${gameRoot}/clients/desktop"
hostLibDir="${desktopPath}/.host-libs"
speechdLibPath=""
libCandidates=(
"/usr/lib/libspeechd.so.2"
"/usr/lib64/libspeechd.so.2"
"/usr/lib/x86_64-linux-gnu/libspeechd.so.2"
"/lib/x86_64-linux-gnu/libspeechd.so.2"
"/lib64/libspeechd.so.2"
)
if [[ -e "${gameRoot}" ]]; then
ui_msgbox "Game Installer" "Game Installer" "\"${gameRoot}\" already exists. Remove it first or choose a different game."
exit 1
fi
mkdir -p "${installPath}" || {
ui_msgbox "Game Installer" "Game Installer" "Could not create ${installPath}."
exit 1
}
if ! git -C "${installPath}" clone "https://github.com/XGDevGroup/PlayPalace11.git"; then
ui_msgbox "Game Installer" "Game Installer" "Could not clone Play Palace from GitHub."
exit 1
fi
if ! pushd "${desktopPath}" > /dev/null; then
ui_msgbox "Game Installer" "Game Installer" "Could not find desktop client path at ${desktopPath}."
exit 1
fi
if ! uv sync; then
popd > /dev/null || exit 1
ui_msgbox "Game Installer" "Game Installer" "uv sync failed for Play Palace."
exit 1
fi
popd > /dev/null || exit 1
if command -v ldconfig > /dev/null 2>&1; then
speechdLibPath="$(ldconfig -p 2> /dev/null | awk '/libspeechd\.so\.2/{print $NF; exit}')"
fi
if [[ -z "${speechdLibPath}" ]]; then
for libPath in "${libCandidates[@]}"; do
if [[ -r "${libPath}" ]]; then
speechdLibPath="${libPath}"
break
fi
done
fi
if [[ -z "${speechdLibPath}" ]]; then
ui_msgbox "Game Installer" "Game Installer" "Could not find libspeechd.so.2.\nInstall speech-dispatcher (Arch) or a libspeechd package (Debian/Ubuntu) and try again."
exit 1
fi
mkdir -p "${hostLibDir}" || {
ui_msgbox "Game Installer" "Game Installer" "Could not create ${hostLibDir}."
exit 1
}
if ! cp -Lf "${speechdLibPath}" "${hostLibDir}/libspeechd.so.2"; then
ui_msgbox "Game Installer" "Game Installer" "Could not copy ${speechdLibPath} into ${hostLibDir}."
exit 1
fi

18
.launch/Play Palace.game Normal file
View File

@@ -0,0 +1,18 @@
check_dependencies uv
# shellcheck disable=SC2154 # installPath is exported by linux-game-manager.sh
gamePath="${installPath}/PlayPalace11/clients"
desktopPath="${gamePath}/desktop"
hostLibDir="${desktopPath}/.host-libs"
if ! [[ -d "${desktopPath}" ]]; then
ui_msgbox "Linux Game Manager" "Linux Game Manager" "Play Palace is not installed at ${desktopPath}."
exit 1
fi
if [[ -d "${hostLibDir}" ]]; then
export LD_LIBRARY_PATH="${hostLibDir}${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}"
fi
pushd "${desktopPath}" || exit 1
uv run python client.py