steam-idle-bot 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- steam_idle_bot-1.0.0/.coveragerc +10 -0
- steam_idle_bot-1.0.0/.env.example +66 -0
- steam_idle_bot-1.0.0/.githooks/pre-commit +20 -0
- steam_idle_bot-1.0.0/.github/FUNDING.yml +3 -0
- steam_idle_bot-1.0.0/.github/dependabot.yml +14 -0
- steam_idle_bot-1.0.0/.github/workflows/ci.yml +77 -0
- steam_idle_bot-1.0.0/.github/workflows/release.yml +206 -0
- steam_idle_bot-1.0.0/.github/workflows/static.yml +86 -0
- steam_idle_bot-1.0.0/.gitignore +29 -0
- steam_idle_bot-1.0.0/.python-version +1 -0
- steam_idle_bot-1.0.0/BACKLOG.md +159 -0
- steam_idle_bot-1.0.0/CHANGELOG.md +149 -0
- steam_idle_bot-1.0.0/CLAUDE.md +107 -0
- steam_idle_bot-1.0.0/COMMIT_NOTES.md +180 -0
- steam_idle_bot-1.0.0/LICENSE +21 -0
- steam_idle_bot-1.0.0/PKG-INFO +183 -0
- steam_idle_bot-1.0.0/README.md +153 -0
- steam_idle_bot-1.0.0/RELEASING.md +46 -0
- steam_idle_bot-1.0.0/SECURITY.md +41 -0
- steam_idle_bot-1.0.0/TODO.md +24 -0
- steam_idle_bot-1.0.0/docs/404.md +13 -0
- steam_idle_bot-1.0.0/docs/README.md +46 -0
- steam_idle_bot-1.0.0/docs/USAGE.md +66 -0
- steam_idle_bot-1.0.0/docs/_config.yml +28 -0
- steam_idle_bot-1.0.0/docs/_includes/footer.html +27 -0
- steam_idle_bot-1.0.0/docs/_includes/head.html +12 -0
- steam_idle_bot-1.0.0/docs/_includes/nav.html +14 -0
- steam_idle_bot-1.0.0/docs/_layouts/default.html +23 -0
- steam_idle_bot-1.0.0/docs/_layouts/home.html +111 -0
- steam_idle_bot-1.0.0/docs/assets/architecture.svg +105 -0
- steam_idle_bot-1.0.0/docs/assets/banner.svg +65 -0
- steam_idle_bot-1.0.0/docs/assets/css/style.css +196 -0
- steam_idle_bot-1.0.0/docs/assets/pipeline.svg +73 -0
- steam_idle_bot-1.0.0/docs/en/README.md +298 -0
- steam_idle_bot-1.0.0/docs/en/SECURITY.md +42 -0
- steam_idle_bot-1.0.0/docs/en/USAGE.md +96 -0
- steam_idle_bot-1.0.0/docs/idle-test-2026-06-23.md +97 -0
- steam_idle_bot-1.0.0/docs/index.md +5 -0
- steam_idle_bot-1.0.0/docs/pt-br/README.md +299 -0
- steam_idle_bot-1.0.0/docs/pt-br/SECURITY.md +42 -0
- steam_idle_bot-1.0.0/docs/pt-br/USAGE.md +96 -0
- steam_idle_bot-1.0.0/frontend/index.html +14 -0
- steam_idle_bot-1.0.0/frontend/package-lock.json +1402 -0
- steam_idle_bot-1.0.0/frontend/package.json +24 -0
- steam_idle_bot-1.0.0/frontend/src/App.tsx +107 -0
- steam_idle_bot-1.0.0/frontend/src/api.ts +120 -0
- steam_idle_bot-1.0.0/frontend/src/components/AuthDialog.tsx +52 -0
- steam_idle_bot-1.0.0/frontend/src/components/StatCard.tsx +31 -0
- steam_idle_bot-1.0.0/frontend/src/index.css +51 -0
- steam_idle_bot-1.0.0/frontend/src/main.tsx +10 -0
- steam_idle_bot-1.0.0/frontend/src/types.ts +33 -0
- steam_idle_bot-1.0.0/frontend/src/views/Dashboard.tsx +127 -0
- steam_idle_bot-1.0.0/frontend/src/views/LogsView.tsx +92 -0
- steam_idle_bot-1.0.0/frontend/src/views/ReportView.tsx +45 -0
- steam_idle_bot-1.0.0/frontend/src/views/SettingsView.tsx +249 -0
- steam_idle_bot-1.0.0/frontend/src/vite-env.d.ts +1 -0
- steam_idle_bot-1.0.0/frontend/tsconfig.json +20 -0
- steam_idle_bot-1.0.0/frontend/tsconfig.tsbuildinfo +1 -0
- steam_idle_bot-1.0.0/frontend/vite.config.ts +16 -0
- steam_idle_bot-1.0.0/pyproject.toml +79 -0
- steam_idle_bot-1.0.0/pytest.ini +8 -0
- steam_idle_bot-1.0.0/ruff.toml +6 -0
- steam_idle_bot-1.0.0/run-web.sh +34 -0
- steam_idle_bot-1.0.0/run.sh +155 -0
- steam_idle_bot-1.0.0/scripts/list_games.py +77 -0
- steam_idle_bot-1.0.0/src/steam_idle_bot/__init__.py +11 -0
- steam_idle_bot-1.0.0/src/steam_idle_bot/__main__.py +4 -0
- steam_idle_bot-1.0.0/src/steam_idle_bot/config/__init__.py +5 -0
- steam_idle_bot-1.0.0/src/steam_idle_bot/config/settings.py +482 -0
- steam_idle_bot-1.0.0/src/steam_idle_bot/main.py +958 -0
- steam_idle_bot-1.0.0/src/steam_idle_bot/steam/__init__.py +8 -0
- steam_idle_bot-1.0.0/src/steam_idle_bot/steam/badges.py +214 -0
- steam_idle_bot-1.0.0/src/steam_idle_bot/steam/browser_cookies.py +124 -0
- steam_idle_bot-1.0.0/src/steam_idle_bot/steam/card_drops.py +607 -0
- steam_idle_bot-1.0.0/src/steam_idle_bot/steam/client.py +480 -0
- steam_idle_bot-1.0.0/src/steam_idle_bot/steam/games.py +456 -0
- steam_idle_bot-1.0.0/src/steam_idle_bot/steam/inventory.py +157 -0
- steam_idle_bot-1.0.0/src/steam_idle_bot/steam/protocol.py +77 -0
- steam_idle_bot-1.0.0/src/steam_idle_bot/steam/steam_utility.py +430 -0
- steam_idle_bot-1.0.0/src/steam_idle_bot/steam/trading_cards.py +274 -0
- steam_idle_bot-1.0.0/src/steam_idle_bot/utils/__init__.py +28 -0
- steam_idle_bot-1.0.0/src/steam_idle_bot/utils/detailed_logger.py +154 -0
- steam_idle_bot-1.0.0/src/steam_idle_bot/utils/exceptions.py +77 -0
- steam_idle_bot-1.0.0/src/steam_idle_bot/utils/idle_tracker.py +368 -0
- steam_idle_bot-1.0.0/src/steam_idle_bot/utils/logger.py +86 -0
- steam_idle_bot-1.0.0/src/steam_idle_bot/utils/preflight.py +77 -0
- steam_idle_bot-1.0.0/src/steam_idle_bot/utils/redaction.py +15 -0
- steam_idle_bot-1.0.0/src/steam_idle_bot/webapi/__init__.py +6 -0
- steam_idle_bot-1.0.0/src/steam_idle_bot/webapi/controller.py +243 -0
- steam_idle_bot-1.0.0/src/steam_idle_bot/webapi/server.py +221 -0
- steam_idle_bot-1.0.0/tests/conftest.py +49 -0
- steam_idle_bot-1.0.0/tests/unit/test_badges.py +110 -0
- steam_idle_bot-1.0.0/tests/unit/test_badges_extra.py +145 -0
- steam_idle_bot-1.0.0/tests/unit/test_browser_cookies.py +74 -0
- steam_idle_bot-1.0.0/tests/unit/test_browser_cookies_extra.py +216 -0
- steam_idle_bot-1.0.0/tests/unit/test_card_drops.py +76 -0
- steam_idle_bot-1.0.0/tests/unit/test_card_drops_cache.py +206 -0
- steam_idle_bot-1.0.0/tests/unit/test_card_drops_extra.py +149 -0
- steam_idle_bot-1.0.0/tests/unit/test_client.py +484 -0
- steam_idle_bot-1.0.0/tests/unit/test_detailed_logger.py +113 -0
- steam_idle_bot-1.0.0/tests/unit/test_efficiency_caches.py +294 -0
- steam_idle_bot-1.0.0/tests/unit/test_games.py +301 -0
- steam_idle_bot-1.0.0/tests/unit/test_games_extra.py +284 -0
- steam_idle_bot-1.0.0/tests/unit/test_idle_tracker.py +194 -0
- steam_idle_bot-1.0.0/tests/unit/test_inventory.py +91 -0
- steam_idle_bot-1.0.0/tests/unit/test_main_cli.py +638 -0
- steam_idle_bot-1.0.0/tests/unit/test_main_extra.py +1028 -0
- steam_idle_bot-1.0.0/tests/unit/test_preflight.py +72 -0
- steam_idle_bot-1.0.0/tests/unit/test_redaction.py +29 -0
- steam_idle_bot-1.0.0/tests/unit/test_settings.py +237 -0
- steam_idle_bot-1.0.0/tests/unit/test_settings_extra.py +43 -0
- steam_idle_bot-1.0.0/tests/unit/test_signal_integration.py +81 -0
- steam_idle_bot-1.0.0/tests/unit/test_steam_idle_bot.py +252 -0
- steam_idle_bot-1.0.0/tests/unit/test_steam_utility.py +247 -0
- steam_idle_bot-1.0.0/tests/unit/test_steam_utility_extra.py +469 -0
- steam_idle_bot-1.0.0/tests/unit/test_trading_cards.py +166 -0
- steam_idle_bot-1.0.0/tests/unit/test_trading_cards_cache.py +41 -0
- steam_idle_bot-1.0.0/tests/unit/test_trading_cards_extra.py +135 -0
- steam_idle_bot-1.0.0/tests/unit/test_webapi.py +218 -0
- steam_idle_bot-1.0.0/uv.lock +1477 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Steam Idle Bot configuration via environment variables
|
|
2
|
+
# Copy to .env and fill in your values. Do NOT commit the filled .env.
|
|
3
|
+
|
|
4
|
+
# Required credentials
|
|
5
|
+
USERNAME=your_steam_username
|
|
6
|
+
PASSWORD=your_steam_password
|
|
7
|
+
|
|
8
|
+
# Optional: Steam Web API key
|
|
9
|
+
STEAM_API_KEY=
|
|
10
|
+
|
|
11
|
+
# Optional: authenticated Steam browser cookies for community page scraping
|
|
12
|
+
# Use JSON, for example:
|
|
13
|
+
# STEAM_WEB_COOKIES={"steamLoginSecure":"...","sessionid":"...","steamCountry":"..."}
|
|
14
|
+
STEAM_WEB_COOKIES={}
|
|
15
|
+
|
|
16
|
+
# Game settings
|
|
17
|
+
# Accepts JSON list (recommended) or CSV list
|
|
18
|
+
GAME_APP_IDS=[570,730]
|
|
19
|
+
FILTER_TRADING_CARDS=true
|
|
20
|
+
FILTER_COMPLETED_CARD_DROPS=true
|
|
21
|
+
EXCLUDE_APP_IDS=[]
|
|
22
|
+
USE_OWNED_GAMES=true
|
|
23
|
+
MAX_GAMES_TO_IDLE=30
|
|
24
|
+
# Seconds between re-running the game-selection pipeline while idling (min 10)
|
|
25
|
+
REFRESH_INTERVAL_SECONDS=600
|
|
26
|
+
# Write a JSON/Markdown checkpoint every N minutes while idling (0 disables)
|
|
27
|
+
CHECKPOINT_MINUTES=0
|
|
28
|
+
# Stop idling after N minutes (0 runs until interrupted)
|
|
29
|
+
DURATION_MINUTES=0
|
|
30
|
+
# Re-scrape card counts this many seconds after stopping, to catch lagging badge updates (0 disables)
|
|
31
|
+
POST_RUN_VERIFY_SECONDS=0
|
|
32
|
+
|
|
33
|
+
# Idling backend
|
|
34
|
+
# python: current steam.client backend
|
|
35
|
+
# steam_utility: delegates idling to ../steam-utility-multiplataform (or STEAM_UTILITY_PATH)
|
|
36
|
+
IDLING_BACKEND=python
|
|
37
|
+
STEAM_UTILITY_PATH=
|
|
38
|
+
|
|
39
|
+
# Logging
|
|
40
|
+
LOG_LEVEL=INFO
|
|
41
|
+
|
|
42
|
+
# Performance tuning
|
|
43
|
+
API_TIMEOUT=10
|
|
44
|
+
# Base delay (seconds) between Steam API calls. Auto-increases on HTTP 429.
|
|
45
|
+
# Raise it (e.g. 2.0) for very large libraries to avoid throttling.
|
|
46
|
+
RATE_LIMIT_DELAY=1.0
|
|
47
|
+
|
|
48
|
+
# Trading-card cache
|
|
49
|
+
ENABLE_CARD_CACHE=true
|
|
50
|
+
CARD_CACHE_PATH=.cache/trading_cards.json
|
|
51
|
+
CARD_CACHE_TTL_DAYS=30
|
|
52
|
+
# No-drop cache: games confirmed without remaining card drops are skipped on
|
|
53
|
+
# future runs (a finished game never regains drops). Gated by ENABLE_CARD_CACHE.
|
|
54
|
+
DROP_CACHE_PATH=.cache/no_drop_cards.json
|
|
55
|
+
DROP_CACHE_TTL_DAYS=90
|
|
56
|
+
|
|
57
|
+
# Browser cookie recovery: if the configured STEAM_WEB_COOKIES are not a valid
|
|
58
|
+
# community session (e.g. a store-only/expired token), pull fresh cookies from a
|
|
59
|
+
# browser you're logged into Steam with. Set the browser or leave "auto".
|
|
60
|
+
AUTO_BROWSER_COOKIES=true
|
|
61
|
+
BROWSER_COOKIES_BROWSER=auto
|
|
62
|
+
|
|
63
|
+
# Filtering controls
|
|
64
|
+
# Optional: cap number of card checks; keep commented to disable
|
|
65
|
+
# MAX_CHECKS=100
|
|
66
|
+
SKIP_FAILURES=false
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Prevent committing local secrets or environment-specific files
|
|
3
|
+
|
|
4
|
+
set -euo pipefail
|
|
5
|
+
|
|
6
|
+
# Block committing config.py explicitly
|
|
7
|
+
if git diff --cached --name-only | grep -qE '^config\.py$'; then
|
|
8
|
+
echo "Error: Refusing to commit config.py (contains secrets)." >&2
|
|
9
|
+
echo "Tip: Keep credentials in config.py (not tracked) or use env vars." >&2
|
|
10
|
+
exit 1
|
|
11
|
+
fi
|
|
12
|
+
|
|
13
|
+
# Basic sanity: prevent large accidentally added venv or cache
|
|
14
|
+
if git diff --cached --name-only | grep -qE '(^|/)__pycache__/|(^|/)venv/|(^|/)\.vscode/'; then
|
|
15
|
+
echo "Warning: You are staging cache/venv/.vscode files. Please unstage them." >&2
|
|
16
|
+
echo "Run: git restore --staged <paths>" >&2
|
|
17
|
+
exit 1
|
|
18
|
+
fi
|
|
19
|
+
|
|
20
|
+
exit 0
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
version: 2
|
|
2
|
+
updates:
|
|
3
|
+
- package-ecosystem: "github-actions"
|
|
4
|
+
directory: "/"
|
|
5
|
+
schedule:
|
|
6
|
+
interval: "weekly"
|
|
7
|
+
open-pull-requests-limit: 5
|
|
8
|
+
- package-ecosystem: "pip"
|
|
9
|
+
directory: "/"
|
|
10
|
+
schedule:
|
|
11
|
+
interval: "weekly"
|
|
12
|
+
open-pull-requests-limit: 5
|
|
13
|
+
allow:
|
|
14
|
+
- dependency-type: "all"
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
# Principle of least privilege for GITHUB_TOKEN
|
|
10
|
+
permissions:
|
|
11
|
+
contents: read
|
|
12
|
+
|
|
13
|
+
# Cancel previous runs of the same ref to save minutes
|
|
14
|
+
concurrency:
|
|
15
|
+
group: ci-${{ github.workflow }}-${{ github.ref }}
|
|
16
|
+
cancel-in-progress: true
|
|
17
|
+
|
|
18
|
+
jobs:
|
|
19
|
+
lint-and-test:
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
timeout-minutes: 30
|
|
22
|
+
strategy:
|
|
23
|
+
fail-fast: false
|
|
24
|
+
matrix:
|
|
25
|
+
python-version: ["3.12", "3.13", "3.14"]
|
|
26
|
+
steps:
|
|
27
|
+
- name: Checkout repository
|
|
28
|
+
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
29
|
+
|
|
30
|
+
- name: Set up UV
|
|
31
|
+
uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
|
|
32
|
+
|
|
33
|
+
- name: Set up Python
|
|
34
|
+
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
|
35
|
+
with:
|
|
36
|
+
python-version: ${{ matrix.python-version }}
|
|
37
|
+
|
|
38
|
+
- name: Cache UV
|
|
39
|
+
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
40
|
+
with:
|
|
41
|
+
path: ~/.cache/uv
|
|
42
|
+
key: ${{ runner.os }}-uv-${{ hashFiles('**/pyproject.toml', '**/uv.lock') }}
|
|
43
|
+
restore-keys: |
|
|
44
|
+
${{ runner.os }}-uv-
|
|
45
|
+
|
|
46
|
+
- name: Install dependencies
|
|
47
|
+
run: uv sync --dev
|
|
48
|
+
|
|
49
|
+
- name: Lint (ruff)
|
|
50
|
+
run: |
|
|
51
|
+
uv run ruff check .
|
|
52
|
+
uv run ruff format --check .
|
|
53
|
+
|
|
54
|
+
- name: Type-check (mypy)
|
|
55
|
+
run: uv run mypy src
|
|
56
|
+
|
|
57
|
+
- name: Install xvfb
|
|
58
|
+
run: sudo apt-get update && sudo apt-get install -y xvfb
|
|
59
|
+
|
|
60
|
+
- name: Run tests (pytest + coverage)
|
|
61
|
+
env:
|
|
62
|
+
# Minimal credentials for Settings model in tests when needed
|
|
63
|
+
USERNAME: test_user
|
|
64
|
+
PASSWORD: test_pass
|
|
65
|
+
DISPLAY: ":99"
|
|
66
|
+
run: |
|
|
67
|
+
Xvfb :99 -screen 0 1024x768x24 &
|
|
68
|
+
sleep 1
|
|
69
|
+
uv run pytest -q --cov=src/steam_idle_bot --cov-report=xml --cov-fail-under=85
|
|
70
|
+
|
|
71
|
+
- name: Upload coverage report
|
|
72
|
+
if: always()
|
|
73
|
+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
74
|
+
with:
|
|
75
|
+
name: coverage-xml-${{ matrix.python-version }}
|
|
76
|
+
path: coverage.xml
|
|
77
|
+
retention-days: 7
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# Tag-triggered release: re-verifies the tagged commit, builds distributable
|
|
2
|
+
# artifacts, and publishes a GitHub Release with notes pulled straight from
|
|
3
|
+
# CHANGELOG.md. The maintainer prepares the release by hand (bump
|
|
4
|
+
# pyproject.toml, move [Unreleased] to a dated version section — see
|
|
5
|
+
# RELEASING.md) and pushes a `vX.Y.Z` tag; this workflow never mutates the
|
|
6
|
+
# repo, it only validates and ships what was already committed.
|
|
7
|
+
name: Release
|
|
8
|
+
|
|
9
|
+
on:
|
|
10
|
+
push:
|
|
11
|
+
tags:
|
|
12
|
+
- "v*.*.*"
|
|
13
|
+
workflow_dispatch:
|
|
14
|
+
inputs:
|
|
15
|
+
tag:
|
|
16
|
+
description: "Existing tag to (re-)release, e.g. v1.0.0"
|
|
17
|
+
required: true
|
|
18
|
+
|
|
19
|
+
# Least privilege by default; only the publish job elevates to contents:write.
|
|
20
|
+
permissions:
|
|
21
|
+
contents: read
|
|
22
|
+
|
|
23
|
+
concurrency:
|
|
24
|
+
group: release-${{ github.workflow }}-${{ github.ref }}
|
|
25
|
+
cancel-in-progress: false
|
|
26
|
+
|
|
27
|
+
jobs:
|
|
28
|
+
# ---- Re-verify the tagged commit before shipping anything from it ----
|
|
29
|
+
verify:
|
|
30
|
+
name: Verify version & test suite
|
|
31
|
+
runs-on: ubuntu-latest
|
|
32
|
+
timeout-minutes: 20
|
|
33
|
+
outputs:
|
|
34
|
+
version: ${{ steps.version.outputs.version }}
|
|
35
|
+
steps:
|
|
36
|
+
- name: Checkout repository
|
|
37
|
+
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
38
|
+
with:
|
|
39
|
+
ref: ${{ inputs.tag || github.ref_name }}
|
|
40
|
+
|
|
41
|
+
- name: Verify tag matches pyproject.toml version
|
|
42
|
+
id: version
|
|
43
|
+
run: |
|
|
44
|
+
set -euo pipefail
|
|
45
|
+
ref="${{ inputs.tag || github.ref_name }}"
|
|
46
|
+
tag_version="${ref#v}"
|
|
47
|
+
pyproject_version=$(grep -m1 '^version = ' pyproject.toml | sed -E 's/version = "(.*)"/\1/')
|
|
48
|
+
if [ "$tag_version" != "$pyproject_version" ]; then
|
|
49
|
+
echo "::error::Tag $ref (version $tag_version) does not match pyproject.toml version $pyproject_version"
|
|
50
|
+
exit 1
|
|
51
|
+
fi
|
|
52
|
+
echo "version=$pyproject_version" >> "$GITHUB_OUTPUT"
|
|
53
|
+
|
|
54
|
+
- name: Set up UV
|
|
55
|
+
uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
|
|
56
|
+
|
|
57
|
+
- name: Set up Python
|
|
58
|
+
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
|
|
59
|
+
with:
|
|
60
|
+
python-version: "3.12"
|
|
61
|
+
|
|
62
|
+
- name: Install dependencies
|
|
63
|
+
run: uv sync --dev
|
|
64
|
+
|
|
65
|
+
- name: Lint (ruff)
|
|
66
|
+
run: |
|
|
67
|
+
uv run ruff check .
|
|
68
|
+
uv run ruff format --check .
|
|
69
|
+
|
|
70
|
+
- name: Type-check (mypy)
|
|
71
|
+
run: uv run mypy src
|
|
72
|
+
|
|
73
|
+
- name: Install xvfb
|
|
74
|
+
run: sudo apt-get update && sudo apt-get install -y xvfb
|
|
75
|
+
|
|
76
|
+
- name: Run tests
|
|
77
|
+
env:
|
|
78
|
+
USERNAME: test_user
|
|
79
|
+
PASSWORD: test_pass
|
|
80
|
+
DISPLAY: ":99"
|
|
81
|
+
run: |
|
|
82
|
+
Xvfb :99 -screen 0 1024x768x24 &
|
|
83
|
+
sleep 1
|
|
84
|
+
uv run pytest -q --cov=src/steam_idle_bot --cov-fail-under=85
|
|
85
|
+
|
|
86
|
+
# ---- Build the artifacts attached to the release ----
|
|
87
|
+
build:
|
|
88
|
+
name: Build distribution artifacts
|
|
89
|
+
needs: verify
|
|
90
|
+
runs-on: ubuntu-latest
|
|
91
|
+
timeout-minutes: 15
|
|
92
|
+
steps:
|
|
93
|
+
- name: Checkout repository
|
|
94
|
+
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
95
|
+
with:
|
|
96
|
+
ref: ${{ inputs.tag || github.ref_name }}
|
|
97
|
+
|
|
98
|
+
- name: Set up UV
|
|
99
|
+
uses: astral-sh/setup-uv@11f9893b081a58869d3b5fccaea48c9e9e46f990 # v8.3.2
|
|
100
|
+
|
|
101
|
+
- name: Build Python package (sdist + wheel)
|
|
102
|
+
run: uv build
|
|
103
|
+
|
|
104
|
+
- name: Set up Node
|
|
105
|
+
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
106
|
+
with:
|
|
107
|
+
node-version: "22"
|
|
108
|
+
cache: "npm"
|
|
109
|
+
cache-dependency-path: frontend/package-lock.json
|
|
110
|
+
|
|
111
|
+
- name: Build frontend
|
|
112
|
+
working-directory: frontend
|
|
113
|
+
run: |
|
|
114
|
+
npm ci
|
|
115
|
+
npm run build
|
|
116
|
+
|
|
117
|
+
- name: Package frontend build
|
|
118
|
+
run: |
|
|
119
|
+
set -euo pipefail
|
|
120
|
+
version="${{ needs.verify.outputs.version }}"
|
|
121
|
+
mkdir -p web-dist
|
|
122
|
+
(cd frontend/dist && zip -rq "../../web-dist/steam-idle-bot-web-${version}.zip" .)
|
|
123
|
+
|
|
124
|
+
- name: Upload build artifacts
|
|
125
|
+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
126
|
+
with:
|
|
127
|
+
name: release-dist
|
|
128
|
+
# Kept in separate dirs so the PyPI publish job can point twine at
|
|
129
|
+
# dist/ alone — it chokes on non-package files like the zip.
|
|
130
|
+
path: |
|
|
131
|
+
dist/
|
|
132
|
+
web-dist/
|
|
133
|
+
retention-days: 7
|
|
134
|
+
|
|
135
|
+
# ---- Publish the GitHub Release with changelog-derived notes ----
|
|
136
|
+
publish:
|
|
137
|
+
name: Publish GitHub Release
|
|
138
|
+
needs: [verify, build]
|
|
139
|
+
runs-on: ubuntu-latest
|
|
140
|
+
timeout-minutes: 10
|
|
141
|
+
permissions:
|
|
142
|
+
contents: write
|
|
143
|
+
steps:
|
|
144
|
+
- name: Checkout repository
|
|
145
|
+
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
146
|
+
with:
|
|
147
|
+
ref: ${{ inputs.tag || github.ref_name }}
|
|
148
|
+
|
|
149
|
+
- name: Download build artifacts
|
|
150
|
+
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
151
|
+
with:
|
|
152
|
+
name: release-dist
|
|
153
|
+
|
|
154
|
+
- name: Extract changelog section
|
|
155
|
+
run: |
|
|
156
|
+
set -euo pipefail
|
|
157
|
+
version="${{ needs.verify.outputs.version }}"
|
|
158
|
+
awk -v ver="$version" '
|
|
159
|
+
/^## \[/ {
|
|
160
|
+
if (found) exit
|
|
161
|
+
if ($0 ~ "^## \\[" ver "\\]") { found = 1; next }
|
|
162
|
+
next
|
|
163
|
+
}
|
|
164
|
+
found { print }
|
|
165
|
+
' CHANGELOG.md > /tmp/release_notes.md
|
|
166
|
+
if [ ! -s /tmp/release_notes.md ]; then
|
|
167
|
+
echo "::error::No CHANGELOG.md section found for [$version]. Move [Unreleased] to [$version] - <date> before tagging (see RELEASING.md)."
|
|
168
|
+
exit 1
|
|
169
|
+
fi
|
|
170
|
+
|
|
171
|
+
- name: Create GitHub Release
|
|
172
|
+
uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2
|
|
173
|
+
env:
|
|
174
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
175
|
+
with:
|
|
176
|
+
tag_name: ${{ inputs.tag || github.ref_name }}
|
|
177
|
+
name: "v${{ needs.verify.outputs.version }}"
|
|
178
|
+
body_path: /tmp/release_notes.md
|
|
179
|
+
prerelease: ${{ contains(needs.verify.outputs.version, '-') }}
|
|
180
|
+
files: |
|
|
181
|
+
dist/*
|
|
182
|
+
web-dist/*
|
|
183
|
+
|
|
184
|
+
# ---- Publish the wheel/sdist to PyPI via Trusted Publishing (OIDC, no ----
|
|
185
|
+
# ---- stored secrets). Independent of the GitHub Release job so a not- ----
|
|
186
|
+
# ---- yet-configured PyPI trusted publisher never blocks the release. ----
|
|
187
|
+
publish-pypi:
|
|
188
|
+
name: Publish to PyPI
|
|
189
|
+
needs: [verify, build]
|
|
190
|
+
runs-on: ubuntu-latest
|
|
191
|
+
timeout-minutes: 10
|
|
192
|
+
environment:
|
|
193
|
+
name: pypi
|
|
194
|
+
url: https://pypi.org/project/steam-idle-bot/${{ needs.verify.outputs.version }}/
|
|
195
|
+
permissions:
|
|
196
|
+
id-token: write # required for OIDC trusted publishing
|
|
197
|
+
steps:
|
|
198
|
+
- name: Download build artifacts
|
|
199
|
+
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
200
|
+
with:
|
|
201
|
+
name: release-dist
|
|
202
|
+
|
|
203
|
+
- name: Publish to PyPI
|
|
204
|
+
uses: pypa/gh-action-pypi-publish@ba38be9e461d3875417946c167d0b5f3d385a247 # v1.14.1
|
|
205
|
+
with:
|
|
206
|
+
packages-dir: dist/
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Build and deploy the documentation site to GitHub Pages.
|
|
2
|
+
name: Deploy docs to Pages
|
|
3
|
+
|
|
4
|
+
on:
|
|
5
|
+
# On pushes to main, build AND deploy. On pull requests, only build (validation):
|
|
6
|
+
# the protected github-pages environment permits `main` only, so the deploy job
|
|
7
|
+
# is skipped on PRs while the build job still catches broken layouts/assets.
|
|
8
|
+
push:
|
|
9
|
+
branches: ["main"]
|
|
10
|
+
paths:
|
|
11
|
+
- "docs/**"
|
|
12
|
+
- ".github/workflows/static.yml"
|
|
13
|
+
pull_request:
|
|
14
|
+
branches: ["main"]
|
|
15
|
+
paths:
|
|
16
|
+
- "docs/**"
|
|
17
|
+
- ".github/workflows/static.yml"
|
|
18
|
+
workflow_dispatch:
|
|
19
|
+
|
|
20
|
+
# Least privilege: only the deploy job needs pages/id-token.
|
|
21
|
+
permissions:
|
|
22
|
+
contents: read
|
|
23
|
+
|
|
24
|
+
# One deployment at a time; let in-flight production deploys finish.
|
|
25
|
+
concurrency:
|
|
26
|
+
group: "pages"
|
|
27
|
+
cancel-in-progress: false
|
|
28
|
+
|
|
29
|
+
jobs:
|
|
30
|
+
# ---- Build the static site and validate the output ----
|
|
31
|
+
build:
|
|
32
|
+
name: Build site
|
|
33
|
+
runs-on: ubuntu-latest
|
|
34
|
+
timeout-minutes: 10
|
|
35
|
+
steps:
|
|
36
|
+
- name: Checkout
|
|
37
|
+
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
38
|
+
|
|
39
|
+
- name: Setup Pages
|
|
40
|
+
uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0
|
|
41
|
+
|
|
42
|
+
- name: Build site with Jekyll
|
|
43
|
+
uses: actions/jekyll-build-pages@44a6e6beabd48582f863aeeb6cb2151cc1716697 # v1.0.13
|
|
44
|
+
with:
|
|
45
|
+
source: ./docs
|
|
46
|
+
destination: ./_site
|
|
47
|
+
|
|
48
|
+
- name: Validate build output
|
|
49
|
+
run: |
|
|
50
|
+
set -euo pipefail
|
|
51
|
+
test -f ./_site/index.html || { echo "::error::index.html missing from build"; exit 1; }
|
|
52
|
+
for asset in banner.svg architecture.svg pipeline.svg css/style.css; do
|
|
53
|
+
test -f "./_site/assets/$asset" || { echo "::error::missing asset: $asset"; exit 1; }
|
|
54
|
+
done
|
|
55
|
+
pages=$(find ./_site -name '*.html' | wc -l)
|
|
56
|
+
size=$(du -sh ./_site | cut -f1)
|
|
57
|
+
{
|
|
58
|
+
echo "### 📄 Docs site built"
|
|
59
|
+
echo ""
|
|
60
|
+
echo "- **HTML pages:** $pages"
|
|
61
|
+
echo "- **Total size:** $size"
|
|
62
|
+
echo "- **Assets verified:** banner · architecture · pipeline · stylesheet"
|
|
63
|
+
} >> "$GITHUB_STEP_SUMMARY"
|
|
64
|
+
|
|
65
|
+
- name: Upload artifact
|
|
66
|
+
uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
|
|
67
|
+
with:
|
|
68
|
+
path: "./_site"
|
|
69
|
+
|
|
70
|
+
# ---- Deploy the built artifact to GitHub Pages (main only) ----
|
|
71
|
+
deploy:
|
|
72
|
+
name: Deploy to Pages
|
|
73
|
+
needs: build
|
|
74
|
+
if: github.event_name != 'pull_request'
|
|
75
|
+
runs-on: ubuntu-latest
|
|
76
|
+
timeout-minutes: 10
|
|
77
|
+
permissions:
|
|
78
|
+
pages: write
|
|
79
|
+
id-token: write
|
|
80
|
+
environment:
|
|
81
|
+
name: github-pages
|
|
82
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
83
|
+
steps:
|
|
84
|
+
- name: Deploy to GitHub Pages
|
|
85
|
+
id: deployment
|
|
86
|
+
uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
config.py
|
|
2
|
+
.env
|
|
3
|
+
.cache/
|
|
4
|
+
__pycache__/
|
|
5
|
+
venv/
|
|
6
|
+
.vscode/
|
|
7
|
+
.claude/
|
|
8
|
+
.venv/
|
|
9
|
+
.mypy_cache/
|
|
10
|
+
.serena/
|
|
11
|
+
|
|
12
|
+
# Python build/cache
|
|
13
|
+
*.pyc
|
|
14
|
+
.ruff_cache/
|
|
15
|
+
.pytest_cache/
|
|
16
|
+
.coverage
|
|
17
|
+
coverage.xml
|
|
18
|
+
logs/
|
|
19
|
+
*.log
|
|
20
|
+
*.log.*
|
|
21
|
+
*.egg-info/
|
|
22
|
+
|
|
23
|
+
# Frontend
|
|
24
|
+
frontend/node_modules/
|
|
25
|
+
frontend/dist/
|
|
26
|
+
|
|
27
|
+
# Local AI tooling caches
|
|
28
|
+
.headroom/
|
|
29
|
+
.tokensave/
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.13
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# 📋 Product Backlog & Roadmap
|
|
2
|
+
|
|
3
|
+
Living, scrum-style backlog for **Steam Idle Bot**. Tracks shipped work, the prioritized
|
|
4
|
+
queue, known defects, and risks. Source of truth for "what's next"; narrative history lives
|
|
5
|
+
in [`CHANGELOG.md`](CHANGELOG.md).
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Legend
|
|
10
|
+
|
|
11
|
+
| Field | Values |
|
|
12
|
+
|-------|--------|
|
|
13
|
+
| **Status** | ✅ Done · 🟡 In progress · ⬜ Todo · 🧊 Icebox |
|
|
14
|
+
| **Type** | ✨ Feature · 🐛 Bug · 🧱 Tech-debt · 📚 Docs · 🔐 Security · 🧪 Test |
|
|
15
|
+
| **Impact** | 🟥 High · 🟧 Medium · 🟩 Low (user/operational value) |
|
|
16
|
+
| **Effort** | `S` ≤½ day · `M` 1–3 days · `L` > 3 days |
|
|
17
|
+
| **Priority** | `P0` now · `P1` next · `P2` soon · `P3` later — derived from impact ÷ effort |
|
|
18
|
+
|
|
19
|
+
**Definition of Done:** code + tests green (`ruff` · `mypy` · `pytest` across 3.12–3.14),
|
|
20
|
+
docs/CHANGELOG updated, no committed secrets/artifacts, reviewed & merged to `main`.
|
|
21
|
+
|
|
22
|
+
**Cadence:** ~1-week sprints. Each closed sprint ships as a PR; backlog re-groomed at close.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## ✅ Shipped (sprints 1–5)
|
|
27
|
+
|
|
28
|
+
> Delivered in PRs [#30](https://github.com/bernardopg/steam-idler-python/pull/30)–[#34](https://github.com/bernardopg/steam-idler-python/pull/34). Test suite grew 204 → 242.
|
|
29
|
+
|
|
30
|
+
### Sprint 1 — Audit & hardening · PR #30
|
|
31
|
+
- [x] 🐛 🟥 Rotate the log file (`RotatingFileHandler`, 10 MB × 3) — fixes unbounded 1 GB log growth
|
|
32
|
+
- [x] 🐛 🟥 Live idle/session duration in the panel (was frozen at `0 min`)
|
|
33
|
+
- [x] ✨ 🟧 Configurable `--refresh-interval-seconds` / `REFRESH_INTERVAL_SECONDS`
|
|
34
|
+
- [x] 🐛 🟧 Tolerate malformed Steam `appdetails` payloads (e.g. app `2321720`)
|
|
35
|
+
- [x] 📚 🟥 Correct inverted `eventemitter` dependency note in `CLAUDE.md`
|
|
36
|
+
|
|
37
|
+
### Sprint 2 — Graceful shutdown · PR #31
|
|
38
|
+
- [x] 🐛 🟥 `SIGINT`/`SIGTERM` graceful stop that still emits the session report
|
|
39
|
+
|
|
40
|
+
### Sprint 3 — Privacy · PR #32
|
|
41
|
+
- [x] 🔐 🟧 Consistent account-name redaction across both backends (`utils.redaction`)
|
|
42
|
+
|
|
43
|
+
### Sprint 4 — Follow-ups · PR #33
|
|
44
|
+
- [x] ✨ 🟧 Preflight warnings (Steam not running / no graphical session) for `steam_utility`
|
|
45
|
+
- [x] 🐛 🟧 Backfill drained final card counts to `0` in the session report
|
|
46
|
+
- [x] ✨ 🟧 steam-utility idle reconciliation (reuse / dedup / report via `/proc`)
|
|
47
|
+
|
|
48
|
+
### Sprint 5 — Backlog close-out · PR #34
|
|
49
|
+
- [x] ✨ 🟧 Checkpoint report mode (`--checkpoint-minutes` → JSON + Markdown) + `--duration-minutes`
|
|
50
|
+
- [x] ✨ 🟩 Delayed post-run verification (`--post-run-verify-seconds`)
|
|
51
|
+
- [x] ✨ 🟩 `--stop-app-ids` maintenance mode
|
|
52
|
+
- [x] 🧪 🟧 run.sh signal-forwarding integration tests
|
|
53
|
+
- [x] 📚 🟩 Document expected empty Badge-API card-drop responses
|
|
54
|
+
|
|
55
|
+
### Sprint 6 — Full audit (2026-07-03)
|
|
56
|
+
- [x] 🐛 🟥 Terminal color was silently disabled under `run.sh` (FIFO ≠ TTY → rich dropped ANSI). Fixed via `FORCE_COLOR=1` + ANSI-stripped run log
|
|
57
|
+
- [x] ✨ 🟧 A2: exponential backoff + jitter on reconnect storms (10s → 5 min cap, reset on success)
|
|
58
|
+
- [x] ✨ 🟩 Status panel reprinted every refresh so idle-time columns stay live
|
|
59
|
+
- [x] 🧪 🟧 G1: CI coverage floor (`--cov-fail-under=85`; suite currently at 90%)
|
|
60
|
+
- [x] 🧪 🟧 G2: `pytest-timeout` (120s, thread method — signal timeouts don't fire in gevent)
|
|
61
|
+
- [x] 🧱 🟩 G3: `ruff check` + `ruff format --check` + `mypy` as CI gates (CI previously ran *only* pytest)
|
|
62
|
+
- [x] ✨ 🟥 F1 groundwork: hatchling build system, `steam-idle-bot` entry point, full PyPI metadata
|
|
63
|
+
- [x] 🧱 🟧 Bounded `DetailedLogger` outputs (last 500 append entries; 50 filtering snapshots)
|
|
64
|
+
|
|
65
|
+
### Sprint 7 — Web UI (2026-07-03)
|
|
66
|
+
- [x] ✨ 🟥 FastAPI + React web UI (`--web` / `run-web.sh`): live WebSocket dashboard, full settings
|
|
67
|
+
editor (.env persist, masked password), streamed logs, session report, Steam Guard dialog.
|
|
68
|
+
Emerald/dark-green minimalist theme. React 19 + Vite 8 + TS 6 + Tailwind 4; deps at latest.
|
|
69
|
+
- [x] 🧪 🟧 20 backend tests (`test_webapi.py`) incl. React-form ⇄ `Settings` parity check
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## 🎯 Product backlog (prioritized)
|
|
74
|
+
|
|
75
|
+
### EPIC A — Reliability & resilience
|
|
76
|
+
| ID | Type | Item | Impact | Effort | Priority | Status |
|
|
77
|
+
|----|------|------|:------:|:------:|:--------:|:------:|
|
|
78
|
+
| A1 | 🧪 | Raise coverage on `client.py` (79%), `inventory.py` (80%), `games.py` (83%) | 🟧 | M | P2 | ⬜ |
|
|
79
|
+
| A2 | ✨ | Exponential backoff + jitter on reconnect storms | 🟧 | S | — | ✅ |
|
|
80
|
+
| A3 | 🧱 | Structured health metrics (uptime, reconnects, drops/hr) exposed to the panel | 🟩 | M | P3 | ⬜ |
|
|
81
|
+
| A4 | 🐛 | Detect & recover from a silently-killed steam-utility child mid-session | 🟧 | M | P2 | ⬜ |
|
|
82
|
+
|
|
83
|
+
### EPIC B — Card-drop accuracy
|
|
84
|
+
| ID | Type | Item | Impact | Effort | Priority | Status |
|
|
85
|
+
|----|------|------|:------:|:------:|:--------:|:------:|
|
|
86
|
+
| B1 | ✨ | Honor Steam's weekly 3-drop / playtime gate in the planner | 🟥 | L | P1 | ⬜ |
|
|
87
|
+
| B2 | ✨ | Cross-check badge-API vs scraper counts; warn on divergence | 🟧 | M | P2 | ⬜ |
|
|
88
|
+
| B3 | 🧱 | Cache TTL auto-tuning from observed drop cadence | 🟩 | M | P3 | 🧊 |
|
|
89
|
+
|
|
90
|
+
### EPIC C — Backends & idling
|
|
91
|
+
| ID | Type | Item | Impact | Effort | Priority | Status |
|
|
92
|
+
|----|------|------|:------:|:------:|:--------:|:------:|
|
|
93
|
+
| C1 | ✨ | Multi-account / account-rotation support | 🟥 | L | P2 | 🧊 |
|
|
94
|
+
| C2 | ✨ | Windows-native steam-utility process detection (no `/proc`) | 🟧 | M | P2 | ⬜ |
|
|
95
|
+
| C3 | 🧱 | Formalize the backend `Protocol` and add a conformance test suite | 🟧 | S | P1 | ⬜ |
|
|
96
|
+
|
|
97
|
+
### EPIC D — UX (terminal & GUI)
|
|
98
|
+
| ID | Type | Item | Impact | Effort | Priority | Status |
|
|
99
|
+
|----|------|------|:------:|:------:|:--------:|:------:|
|
|
100
|
+
| D1 | 🧱 | Split `gui.py` (1 359 lines, 93% covered) into view/controller modules | 🟧 | M | P2 | ⬜ |
|
|
101
|
+
| D2 | ✨ | Live in-place terminal panel via `rich.live.Live` writing to `/dev/tty` (survives the run.sh FIFO redirect; falls back to periodic reprint when no TTY) | 🟩 | M | P3 | ⬜ |
|
|
102
|
+
| D3 | ✨ | GUI: per-game drop progress bars + ETA | 🟩 | M | P3 | 🧊 |
|
|
103
|
+
| D4 | 🧱 | Default `LOG_FILE` (`steam_card_idler.log`) should live under `logs/`, not the repo root | 🟩 | S | P2 | ⬜ |
|
|
104
|
+
|
|
105
|
+
### EPIC E — Observability & reporting
|
|
106
|
+
| ID | Type | Item | Impact | Effort | Priority | Status |
|
|
107
|
+
|----|------|------|:------:|:------:|:--------:|:------:|
|
|
108
|
+
| E1 | ✨ | HTML/Markdown session summary with charts | 🟩 | M | P3 | ⬜ |
|
|
109
|
+
| E2 | ✨ | Prometheus/JSON metrics endpoint for long-running hosts | 🟩 | M | P3 | 🧊 |
|
|
110
|
+
|
|
111
|
+
### EPIC F — Packaging & distribution
|
|
112
|
+
| ID | Type | Item | Impact | Effort | Priority | Status |
|
|
113
|
+
|----|------|------|:------:|:------:|:--------:|:------:|
|
|
114
|
+
| F1 | ✨ | Publish to PyPI (`pipx install steam-idle-bot`) — packaging metadata + entry point shipped in Sprint 6; remaining: release workflow + first upload | 🟥 | S | P1 | 🟡 |
|
|
115
|
+
| F2 | ✨ | Official Docker image + compose for headless 24/7 idling | 🟧 | M | P2 | ⬜ |
|
|
116
|
+
| F3 | 📚 | Release automation (tagged builds, changelog extraction) | 🟧 | S | P2 | ⬜ |
|
|
117
|
+
|
|
118
|
+
### EPIC G — Quality & testing
|
|
119
|
+
| ID | Type | Item | Impact | Effort | Priority | Status |
|
|
120
|
+
|----|------|------|:------:|:------:|:--------:|:------:|
|
|
121
|
+
| G1 | 🧪 | Enforce a coverage floor in CI (`--cov-fail-under=85`) | 🟧 | S | — | ✅ |
|
|
122
|
+
| G2 | 🧪 | Add `pytest-timeout` to kill hung tests deterministically | 🟧 | S | — | ✅ |
|
|
123
|
+
| G3 | 🧱 | `ruff check` + `ruff format --check` + `mypy` as CI gates | 🟩 | S | — | ✅ |
|
|
124
|
+
|
|
125
|
+
### EPIC H — Security & privacy
|
|
126
|
+
| ID | Type | Item | Impact | Effort | Priority | Status |
|
|
127
|
+
|----|------|------|:------:|:------:|:--------:|:------:|
|
|
128
|
+
| H1 | 🔐 | Optional OS-keyring credential storage (drop plaintext `.env`) | 🟥 | M | P1 | ⬜ |
|
|
129
|
+
| H2 | 🔐 | Redact steam_id/cookies/tokens in `DetailedLogger` JSON dumps (growth caps shipped in Sprint 6; redaction still open) | 🟧 | S | P1 | ⬜ |
|
|
130
|
+
| H3 | 🔐 | Document & track the accepted `protobuf<4` advisory exceptions | 🟩 | S | P2 | ✅ |
|
|
131
|
+
|
|
132
|
+
### EPIC I — Documentation
|
|
133
|
+
| ID | Type | Item | Impact | Effort | Priority | Status |
|
|
134
|
+
|----|------|------|:------:|:------:|:--------:|:------:|
|
|
135
|
+
| I1 | 📚 | Architecture deep-dive page linked from the README diagrams | 🟧 | S | P2 | ⬜ |
|
|
136
|
+
| I2 | 📚 | Troubleshooting matrix (auth failures, 429s, no drops) | 🟧 | S | P1 | ⬜ |
|
|
137
|
+
| I3 | 📚 | Animated demo (asciinema/GIF) in the README | 🟩 | S | P3 | ⬜ |
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## 🐛 Known defects / risks
|
|
142
|
+
|
|
143
|
+
| ID | Severity | Description | Mitigation | Status |
|
|
144
|
+
|----|:--------:|-------------|-----------|:------:|
|
|
145
|
+
| R1 | 🟧 | steam-utility reconciliation is Linux-only (`/proc`) | No-op elsewhere; tracked as C2 | ⬜ |
|
|
146
|
+
| R2 | 🟩 | `protobuf<4` pin blocks GHSA-8qvm-5x2c-j2w7 / GHSA-7gcm-g887-7qv7 | Accepted: only trusted Steam data deserialized | ✅ |
|
|
147
|
+
| R3 | 🟧 | Card-drop scraping depends on a short-lived community cookie | Auto browser-cookie recovery; session verify | ✅ |
|
|
148
|
+
| R4 | 🟩 | ~~GUI is largely untested~~ GUI now at 93% coverage (171 tests); remaining risk is the 1 359-line monolith | Tracked as D1 | ✅ |
|
|
149
|
+
| R5 | 🟩 | `logs/` accumulates per-run artifacts (`runs/`, checkpoints, reports) with no retention policy beyond DetailedLogger caps | Periodic manual cleanup; candidate for a small janitor on startup | ⬜ |
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## 📌 Next sprint candidate (groomed 2026-07-03)
|
|
154
|
+
|
|
155
|
+
Top of queue by impact ÷ effort: **F1 finish** (release workflow + first PyPI upload),
|
|
156
|
+
**C3** (backend conformance tests — `protocol.py` already exists, only the suite is missing),
|
|
157
|
+
**H2** (redact logger dumps), **I2** (troubleshooting matrix), **D4** (default log into `logs/`).
|
|
158
|
+
Next after that: **B1** (weekly drop-gate planner — highest-impact open feature) and **D1**
|
|
159
|
+
(split `gui.py`).
|