Focus watcher reporting on Steam Frame

This commit is contained in:
seanbetts committed 2026-06-24 10:47:19 +01:00
1 parent ed9f4500a2
commit 8d31b1d06d
7 files changed
+497 -17

No files matched your search

+49 -17
View File
@@ -1,16 +1,23 @@
---
name: steam-hardware-watch
description: Investigate rumored or upcoming Valve hardware launches, especially Steam Controller, Steam Machine, and Steam Frame. Use when the user wants recurring checks across Komodo, SteamKit/PICS, SteamDB, SteamTracking or GameTracking, Valve CDN or support endpoints, and customs or regulatory sources, with saved artifacts and an updated status ledger.
description: Investigate Steam Frame launch readiness, using Steam Controller and Steam Machine as baselines after their public launches. Use when the user wants recurring checks across SteamKit/PICS, Valve store/support endpoints, SteamDB, Komodo, SteamTracking or GameTracking, SteamVR, SteamOS, and customs or regulatory sources, with saved artifacts and an updated status ledger.
---
# Steam Hardware Watch
Use this skill for recurring evidence-gathering on Valve hardware launch status. Default focus is:
Use this skill for recurring evidence-gathering on Valve hardware launch status. Current default focus is Steam Frame launch readiness:
- `price`
- `release date`
- `same-time launch vs staggered launch`
- `which product is furthest along`
- `Frame price`
- `Frame release date`
- `Frame package or reservation visibility`
- `Frame staging on Valve, SteamDB, Komodo, SteamVR, SteamOS, and logistics sources`
Post-Steam Machine launch mode:
- treat Steam Frame as the active target
- keep Steam Machine and Steam Controller in the watcher as controls, precedent, and shared-infrastructure context
- do not elevate Machine post-launch churn as a Frame signal unless it mentions Frame, changes shared reservation infrastructure, or provides a direct launch-sequence comparator
- answer daily check requests from a Frame-first perspective by default
Keep the workflow tiered. Do the high-yield checks every run. Do slower or noisier checks when core sources move.
@@ -46,6 +53,7 @@ Use [scripts/init_run.sh](scripts/init_run.sh) to scaffold a run folder and run
Use the source helpers when available:
- [scripts/run_watch.sh](scripts/run_watch.sh)
- [scripts/run_frame_watch.sh](scripts/run_frame_watch.sh)
- [scripts/check_komodo.sh](scripts/check_komodo.sh)
- [scripts/check_steamkit_pics.sh](scripts/check_steamkit_pics.sh)
- [scripts/check_steamdb.sh](scripts/check_steamdb.sh)
@@ -68,9 +76,10 @@ Asset discovery should be dynamic:
Default agent entry point:
- use [scripts/run_watch.sh](scripts/run_watch.sh) unless there is a specific reason to run a source helper directly
- use [scripts/run_frame_watch.sh](scripts/run_frame_watch.sh) for normal Frame-focused runs
- use [scripts/run_watch.sh](scripts/run_watch.sh) when the user asks for the broader all-hardware view or when debugging a source helper directly
- treat the helper scripts as agent-owned implementation details
- summarize results for the human from `run-summary.md`, `status-draft.md`, and the comparison report
- summarize results for the human from `frame-focus.md`, `run-summary.md`, `status-draft.md`, and the comparison report
## Evidence Priority
@@ -78,17 +87,38 @@ Apply the rubric in [references/evidence-rubric.md](references/evidence-rubric.m
Default source order:
1. `Komodo`
2. `SteamKit / PICS`
1. `SteamKit / PICS`
2. `Valve support / store / CDN`
3. `SteamDB`
4. `SteamTracking / GameTracking`
5. `SteamVR depots`
6. `SteamOS package mirror`
7. `Valve support / store / CDN`
4. `Komodo`
5. `SteamTracking / GameTracking`
6. `SteamVR depots`
7. `SteamOS package mirror`
8. `customs / regulatory`
Treat price and exact release date as unconfirmed unless directly exposed by a primary source.
## Frame-Critical Signals
Prioritize these before interpreting lower-level churn:
- SteamKit/PICS movement for Frame app `4165890` or packages `1629484`, `1629486`
- Valve `appdetails` showing Frame packages, package groups, price, purchase or reservation state, or an exact release date
- Valve `packagedetails` for Frame packages moving from `success:false` to public data
- SteamDB package pages for `1629484` or `1629486` moving from existence-only records to details, new `Last Record Update`, or new changenumber
- Komodo Frame product timestamp changes, new Frame sections, media, manuals, videos, or support docs
- SteamTracking/GameTracking additions that name Frame setup, pairing, verification, OOBE, guided tours, Steam Link VR, wireless adapters, or Frame package IDs
- SteamVR or SteamOS additions that explicitly mention Frame, Deckard, Roy, XR, VR, ARM64, wireless adapter, or controller support in a Frame context
- shipment or filing rows explicitly labelled VR, headset, XR, wireless adapter, or known Frame supplier/importer patterns
Interpretation rules:
- SteamKit/PICS and Valve store/package APIs are the primary readiness sources.
- SteamDB is corroborating and useful for human-readable package history.
- Komodo is high-signal for public site staging, but Cloudflare blocks should not stop the rest of a run.
- SteamTracking, SteamVR, and SteamOS changes are context until they connect to Frame package/API movement, public pages, or shipment evidence.
- Machine and Controller changes are comparators after launch, not active launch targets.
## Core Workflow
### 1. Komodo
@@ -383,7 +413,8 @@ Use the seeded status files as the starting baseline rather than rediscovering t
## Wrapper Script
Use [scripts/run_watch.sh](scripts/run_watch.sh) for the normal path.
Use [scripts/run_frame_watch.sh](scripts/run_frame_watch.sh) for the normal Frame-focused path.
Use [scripts/run_watch.sh](scripts/run_watch.sh) when the user explicitly wants a broader all-hardware run.
It will:
@@ -392,10 +423,11 @@ It will:
3. auto-detect the previous run folder when possible
4. write a comparison report into the current run folder
5. save discovered visual assets into the current run folder
6. generate a draft status update into the current run folder
6. generate a Frame-focused report into the current run folder
7. generate a draft status update into the current run folder
Example:
```sh
~/.codex/skills/steam-hardware-watch/scripts/run_watch.sh 2026-04-25
scripts/run_frame_watch.sh 2026-06-24
```
+12
View File
@@ -45,6 +45,7 @@ def build_draft(run_dir: Path):
if compare_file:
compare_lines = [line for line in read_lines(compare_file) if line.startswith("- ")][:12]
frame_focus_lines = first_n_nonempty(reports / "frame-focus.md", 12)
komodo_mods = first_n_nonempty(reports / "komodo-product-modified.tsv", 10)
steamkit_lines = first_n_nonempty(reports / "steamkit-pics-key-lines.txt", 10)
steamdb_lines = first_n_nonempty(reports / "steamdb-key-lines.txt", 8)
@@ -104,6 +105,17 @@ def build_draft(run_dir: Path):
[
"",
"### Source Snapshot Draft",
"",
"#### Steam Frame Focus",
]
)
if frame_focus_lines:
lines.extend([f"- `{line}`" for line in frame_focus_lines])
else:
lines.append("- No Steam Frame focused report found.")
lines.extend(
[
"",
"#### Komodo",
]
+31
View File
@@ -0,0 +1,31 @@
#!/bin/sh
set -eu
if [ "$#" -lt 1 ]; then
echo "usage: $0 YYYY-MM-DD [base_dir] [tracking_dir]" >&2
exit 1
fi
RUN_DATE="$1"
SCRIPT_DIR="$(CDPATH= cd -- "$(dirname "$0")" && pwd)"
REPO_DIR="$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd)"
BASE_DIR="${2:-$REPO_DIR/runs}"
TRACKING_DIR="${3:-/tmp/SteamTracking-master}"
if [ "$#" -ge 3 ]; then
"$SCRIPT_DIR/run_watch.sh" "$RUN_DATE" "$BASE_DIR" "$TRACKING_DIR"
else
"$SCRIPT_DIR/run_watch.sh" "$RUN_DATE" "$BASE_DIR"
fi
RUN_DIR="$BASE_DIR/$RUN_DATE"
FRAME_FOCUS_OUT="$RUN_DIR/reports/frame-focus.md"
if [ ! -f "$FRAME_FOCUS_OUT" ]; then
python3 "$SCRIPT_DIR/write_frame_focus_report.py" \
--run-dir "$RUN_DIR" \
--output "$FRAME_FOCUS_OUT"
fi
printf '%s\n' "Frame focus report: $FRAME_FOCUS_OUT"
+6
View File
@@ -79,6 +79,12 @@ else
printf '%s\n' "No previous run folder found under $BASE_DIR"
fi
FRAME_FOCUS_OUT="$RUN_DIR/reports/frame-focus.md"
python3 "$SCRIPT_DIR/write_frame_focus_report.py" \
--run-dir "$RUN_DIR" \
--output "$FRAME_FOCUS_OUT"
printf '%s\n' "Frame focus report: $FRAME_FOCUS_OUT"
STATUS_DRAFT_OUT="$RUN_DIR/reports/status-draft.md"
python3 "$SCRIPT_DIR/draft_status_update.py" \
--run-dir "$RUN_DIR" \
+295
View File
@@ -0,0 +1,295 @@
#!/usr/bin/env python3
import argparse
import csv
from pathlib import Path
FRAME_APP_ID = "4165890"
FRAME_PACKAGE_IDS = {"1629484", "1629486"}
FRAME_KOMODO_BASELINE = "2026-05-27T16:53:46"
FRAME_STEAMDB_BASELINE_UPDATE = "5 May 2026 - 18:50:54 UTC"
MAX_LINE_LEN = 220
def read_text(path: Path):
if not path.exists():
return ""
return path.read_text(encoding="utf-8", errors="ignore")
def read_lines(path: Path):
return [line.rstrip("\n") for line in read_text(path).splitlines() if line.strip()]
def read_tsv(path: Path):
if not path.exists():
return []
with path.open("r", encoding="utf-8", errors="ignore", newline="") as handle:
return list(csv.DictReader(handle, delimiter="\t"))
def read_komodo_product_modified(path: Path):
rows = []
for line in read_lines(path):
parts = line.split("\t")
if parts[:2] == ["product", "modified"]:
continue
if len(parts) >= 2:
rows.append({"product": parts[0], "modified": parts[1]})
return rows
def concise(value: str):
compact = " ".join((value or "").split())
if len(compact) > MAX_LINE_LEN:
return compact[: MAX_LINE_LEN - 3] + "..."
return compact
def bullet_table_row(row, columns):
values = []
for column in columns:
value = concise(row.get(column, ""))
if value:
values.append(f"{column}={value}")
return "; ".join(values)
def frame_rows(rows, id_key="id"):
return [
row
for row in rows
if row.get("product") == "Steam Frame"
or row.get(id_key) == FRAME_APP_ID
or row.get(id_key) in FRAME_PACKAGE_IDS
or row.get("package_id") in FRAME_PACKAGE_IDS
]
def machine_rows(rows, id_key="id"):
return [row for row in rows if row.get("product") == "Steam Machine"]
def valve_frame_ready(rows):
for row in frame_rows(rows):
row_type = row.get("type")
row_id = row.get("id")
status = row.get("status")
details = row.get("details", "")
if row_type == "package" and row_id in FRAME_PACKAGE_IDS and status == "public":
return True
if row_type == "app" and row_id == FRAME_APP_ID:
has_packages = "packages=" in details and "packages=;" not in details
has_package_groups = "package_groups=0" not in details and "package_groups=" in details
has_price = "price=" in details and not details.endswith("price=")
if has_packages or has_package_groups or has_price:
return True
return False
def steamkit_frame_moved(rows):
for row in frame_rows(rows):
if row.get("changed_since_previous") == "yes":
return True
if row.get("type") == "package" and row.get("id") in FRAME_PACKAGE_IDS and row.get("status") == "available":
return True
return False
def steamdb_frame_moved(rows):
for row in frame_rows(rows, id_key="package_id"):
if row.get("status") and row.get("status") != "private_exists_only":
return True
if row.get("last_record_update") and row.get("last_record_update") != FRAME_STEAMDB_BASELINE_UPDATE:
return True
return False
def komodo_frame_moved(rows):
for row in rows:
if row.get("product") == "frame" and row.get("modified") and row.get("modified") != FRAME_KOMODO_BASELINE:
return True
return False
def key_lines(path: Path, terms, limit):
selected = []
for line in read_lines(path):
lowered = line.lower()
if any(term in lowered for term in terms):
selected.append(concise(line))
if len(selected) >= limit:
break
return selected
def customs_vr_lines(path: Path, limit):
selected = []
for line in read_lines(path):
lowered = line.lower()
if line.startswith("|") and any(term in lowered for term in ("virtual reality", "vr", "headset", "xr")):
selected.append(concise(line))
if len(selected) >= limit:
break
return selected
def latest_compare_file(reports: Path):
compare_files = sorted(reports.glob("compare-vs-*.md"))
if not compare_files:
return None
return compare_files[-1]
def build(run_dir: Path):
reports = run_dir / "reports"
valve_rows = read_tsv(reports / "valve-reservation-packages.tsv")
steamkit_rows = read_tsv(reports / "steamkit-pics-packages.tsv")
steamdb_rows = read_tsv(reports / "steamdb-reservation-packages.tsv")
komodo_rows = read_komodo_product_modified(reports / "komodo-product-modified.tsv")
valve_ready = valve_frame_ready(valve_rows)
steamkit_moved = steamkit_frame_moved(steamkit_rows)
steamdb_moved = steamdb_frame_moved(steamdb_rows)
komodo_moved = komodo_frame_moved(komodo_rows)
if valve_ready:
verdict = "Steam Frame public readiness detected."
reason = "Valve store/package data now exposes Frame package, package group, price, or purchase/reservation readiness."
elif steamkit_moved or steamdb_moved:
verdict = "Steam Frame backend movement detected, but public store readiness is not confirmed."
reason = "Frame app/package metadata moved in SteamKit/PICS or SteamDB, while Valve public endpoints still need confirmation."
elif komodo_moved:
verdict = "Steam Frame site staging detected, but store/package readiness is not confirmed."
reason = "Komodo Frame records moved without matching Valve package readiness."
else:
verdict = "No public Steam Frame readiness change detected."
reason = "Frame app/package, public Valve endpoint, SteamDB, and Komodo checks do not show a launch-readiness move."
lines = [
f"# Steam Frame Focus: {run_dir.name}",
"",
"## Verdict",
"",
f"- {verdict}",
f"- {reason}",
"",
"## Frame Critical Signals",
"",
f"- Frame app: `{FRAME_APP_ID}`",
"- Frame packages: `1629484`, `1629486`",
"",
"### Valve Store / Package API",
]
frame_valve_rows = frame_rows(valve_rows)
if frame_valve_rows:
for row in frame_valve_rows:
lines.append(
f"- `{bullet_table_row(row, ('type', 'product', 'id', 'status', 'name', 'release_or_price', 'details'))}`"
)
else:
lines.append("- No Frame rows found in `valve-reservation-packages.tsv`.")
lines.extend(["", "### SteamKit / PICS"])
frame_steamkit_rows = frame_rows(steamkit_rows)
if frame_steamkit_rows:
for row in frame_steamkit_rows:
lines.append(
f"- `{bullet_table_row(row, ('type', 'product', 'id', 'status', 'changenumber', 'previous_changenumber', 'changed_since_previous', 'name', 'details'))}`"
)
else:
lines.append("- No Frame rows found in `steamkit-pics-packages.tsv`.")
lines.extend(["", "### SteamDB"])
frame_steamdb_rows = frame_rows(steamdb_rows, id_key="package_id")
if frame_steamdb_rows:
for row in frame_steamdb_rows:
lines.append(
f"- `{bullet_table_row(row, ('product', 'package_id', 'last_record_update', 'last_changenumber', 'possible_apps', 'status'))}`"
)
else:
lines.append("- No Frame rows found in `steamdb-reservation-packages.tsv`.")
lines.extend(["", "### Komodo"])
frame_komodo = [row for row in komodo_rows if row.get("product") == "frame"]
if frame_komodo:
for row in frame_komodo:
lines.append(f"- `{bullet_table_row(row, ('product', 'modified'))}`")
else:
lines.append("- No Frame product timestamp row found.")
for source in ("komodo-frame-sections.tsv", "komodo-frame-media.tsv"):
path = reports / source
count = len(read_lines(path))
lines.append(f"- `{source}` rows: `{count}`")
tracking_terms = ("frame", "deckard", "roy", "xr", "vr", "wireless adapter", "steam link")
tracking_lines = key_lines(reports / "steamtracking-frame-signals-key-lines.txt", tracking_terms, 10)
lines.extend(["", "### SteamTracking / GameTracking"])
if tracking_lines:
lines.extend([f"- `{line}`" for line in tracking_lines])
else:
lines.append("- No Frame-focused SteamTracking key lines found.")
vr_terms = ("steam frame", "deckard", "roy", "wireless adapter", "steam link vr", "arm64", "linuxarm64")
steamvr_lines = key_lines(reports / "steamvr-depots-key-lines.txt", vr_terms, 6)
steamos_lines = key_lines(reports / "steamos-mirror-key-lines.txt", vr_terms, 6)
lines.extend(["", "### SteamVR / SteamOS"])
if steamvr_lines or steamos_lines:
lines.extend([f"- SteamVR: `{line}`" for line in steamvr_lines])
lines.extend([f"- SteamOS: `{line}`" for line in steamos_lines])
else:
lines.append("- No Frame-relevant SteamVR or SteamOS key lines found.")
customs_lines = customs_vr_lines(reports / "customs-shipments.md", 6)
lines.extend(["", "### Customs / Shipments"])
if customs_lines:
lines.extend([f"- `{line}`" for line in customs_lines])
elif (reports / "customs-shipments-errors.txt").exists():
lines.append("- Shipment check had errors; inspect `customs-shipments-errors.txt`.")
else:
lines.append("- No VR/XR/headset shipment rows surfaced in the shipment report.")
lines.extend(["", "## Background Comparator Signals", ""])
machine_valve_rows = machine_rows(valve_rows)
machine_steamkit_rows = [
row for row in machine_rows(steamkit_rows) if row.get("changed_since_previous") == "yes"
]
if machine_valve_rows:
lines.append("- Steam Machine is now a launched comparator, not the active target.")
for row in machine_valve_rows[:6]:
lines.append(
f"- Machine Valve: `{bullet_table_row(row, ('type', 'id', 'status', 'name', 'release_or_price', 'details'))}`"
)
if machine_steamkit_rows:
for row in machine_steamkit_rows[:6]:
lines.append(
f"- Machine SteamKit moved: `{bullet_table_row(row, ('type', 'id', 'changenumber', 'previous_changenumber', 'changed_since_previous', 'details'))}`"
)
if not machine_valve_rows and not machine_steamkit_rows:
lines.append("- No Machine comparator rows were available in the saved reports.")
compare_file = latest_compare_file(reports)
lines.extend(["", "## Next Checks", ""])
lines.append("- Recheck SteamKit/PICS and Valve `appdetails` / `packagedetails` first.")
lines.append("- Treat SteamTracking or Komodo Frame movement as staging context until package/API visibility changes.")
lines.append("- Keep monitoring VR-labelled shipment rows as corroboration, not launch confirmation.")
if compare_file:
lines.append(f"- Cross-check material deltas in `{compare_file}`.")
lines.append("")
return "\n".join(lines)
def main():
parser = argparse.ArgumentParser(description="Write a Steam Frame focused report from saved watch artifacts.")
parser.add_argument("--run-dir", required=True)
parser.add_argument("--output", help="Optional output path")
args = parser.parse_args()
run_dir = Path(args.run_dir)
output = Path(args.output) if args.output else run_dir / "reports" / "frame-focus.md"
output.parent.mkdir(parents=True, exist_ok=True)
output.write_text(build(run_dir), encoding="utf-8")
if __name__ == "__main__":
main()
+2
View File
@@ -145,6 +145,7 @@ def build(run_dir: Path):
f"- Retrieved assets: `{reports / 'retrieved-visual-assets.tsv'}`",
f"- Blocked assets: `{reports / 'blocked-visual-assets.tsv'}`",
f"- Manual retry URLs: `{reports / 'manual-asset-urls.txt'}`",
f"- Steam Frame focus: `{reports / 'frame-focus.md'}`",
f"- SteamKit/PICS packages: `{reports / 'steamkit-pics-packages.tsv'}`",
f"- SteamKit/PICS detail: `{reports / 'steamkit-pics-detail.md'}`",
f"- Customs shipments: `{reports / 'customs-shipments.md'}`",
@@ -156,6 +157,7 @@ def build(run_dir: Path):
lines.extend(["", "## Notable Lines", ""])
notable = []
notable.extend([f"- Frame focus: `{line}`" for line in filtered_first(reports / "frame-focus.md", 8)])
notable.extend([f"- SteamKit/PICS: `{line}`" for line in filtered_first(reports / "steamkit-pics-key-lines.txt", 8)])
notable.extend([f"- SteamDB: `{line}`" for line in filtered_first(reports / "steamdb-key-lines.txt", 5)])
notable.extend([f"- SteamTracking: `{line}`" for line in filtered_first(reports / "steamtracking-pairing-focus.txt", 5)])
+102
View File
@@ -0,0 +1,102 @@
import tempfile
import textwrap
import unittest
from pathlib import Path
from scripts.write_frame_focus_report import build
def write(path: Path, content: str):
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(textwrap.dedent(content).lstrip(), encoding="utf-8")
class FrameFocusReportTests(unittest.TestCase):
def make_run_dir(self):
tmp = tempfile.TemporaryDirectory()
self.addCleanup(tmp.cleanup)
run_dir = Path(tmp.name) / "2026-06-24"
(run_dir / "reports").mkdir(parents=True)
return run_dir
def write_baseline_reports(self, run_dir: Path):
reports = run_dir / "reports"
write(
reports / "valve-reservation-packages.tsv",
"""
type\tproduct\tid\tstatus\tname\trelease_or_price\tdetails
app\tSteam Frame\t4165890\tpublic\tSteam Frame\tComing soon\tpackages=; package_groups=0; coming_soon=True; price=
package\tSteam Frame\t1629484\tprivate\t\t\tpackagedetails success:false
package\tSteam Frame\t1629486\tprivate\t\t\tpackagedetails success:false
app\tSteam Machine\t4165910\tpublic\tSteam Machine\t$1,049.00\tpackages=1629447,1629458,1629446,1629460; package_groups=1; coming_soon=False; price=$1,049.00
""",
)
write(
reports / "steamkit-pics-packages.tsv",
"""
type\tproduct\tid\tstatus\tchangenumber\tprevious_changenumber\tchanged_since_previous\tsha_hash\tonly_public\tname\trelated_ids\tdetails
app\tSteam Frame\t4165890\tprivate_metadata_token_required\t35675573\t35675573\tno\tabc\tTrue\tSteam Frame\t\tdepots_count=0
package\tSteam Frame\t1629484\tprivate_metadata_token_required\t35672606\t35672606\tno\t\tFalse\t\t\tapps_count=0
package\tSteam Frame\t1629486\tprivate_metadata_token_required\t35672606\t35672606\tno\t\tFalse\t\t\tapps_count=0
package\tSteam Machine\t1629446\tavailable\t36785426\t36756963\tyes\tdef\tFalse\t\t\tapps_count=1
""",
)
write(
reports / "steamdb-reservation-packages.tsv",
"""
product\tpackage_id\tlast_record_update\tlast_changenumber\tpossible_apps\tstatus
Steam Frame\t1629484\t5 May 2026 - 18:50:54 UTC\t35672606\t4165890:Steam Frame\tprivate_exists_only
Steam Frame\t1629486\t5 May 2026 - 18:50:54 UTC\t35672606\t4165890:Steam Frame\tprivate_exists_only
""",
)
write(
reports / "komodo-product-modified.tsv",
"""
machine\t2026-06-24T16:12:21
frame\t2026-05-27T16:53:46
""",
)
write(
reports / "customs-shipments.md",
"""
| arrival_date | shipper | consignee | description |
| 2026-06-17 | TECH-FRONT | VALVE | VIRTUAL REALITY DEVICES |
""",
)
def test_baseline_private_frame_reports_no_public_readiness(self):
run_dir = self.make_run_dir()
self.write_baseline_reports(run_dir)
report = build(run_dir)
self.assertIn("No public Steam Frame readiness change detected.", report)
self.assertIn("Frame app: `4165890`", report)
self.assertIn("Frame packages: `1629484`, `1629486`", report)
self.assertIn("status=private", report)
self.assertIn("product=frame; modified=2026-05-27T16:53:46", report)
self.assertIn("Steam Machine is now a launched comparator", report)
self.assertIn("VIRTUAL REALITY DEVICES", report)
def test_public_frame_package_reports_public_readiness(self):
run_dir = self.make_run_dir()
self.write_baseline_reports(run_dir)
write(
run_dir / "reports" / "valve-reservation-packages.tsv",
"""
type\tproduct\tid\tstatus\tname\trelease_or_price\tdetails
app\tSteam Frame\t4165890\tpublic\tSteam Frame\t$499.00\tpackages=1629484; package_groups=1; coming_soon=False; price=$499.00
package\tSteam Frame\t1629484\tpublic\tSteam Frame 256GB\t\tapps=4165890:Steam Frame
package\tSteam Frame\t1629486\tprivate\t\t\tpackagedetails success:false
""",
)
report = build(run_dir)
self.assertIn("Steam Frame public readiness detected.", report)
self.assertIn("package_groups=1", report)
self.assertIn("status=public", report)
if __name__ == "__main__":
unittest.main()