diff --git a/README.md b/README.md index e5c28b4..ddb3135 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,39 @@ scripts/run_frame_watch.sh YYYY-MM-DD /custom/run-base /path/to/SteamTracking Use `scripts/run_watch.sh` when you explicitly want the broader all-hardware view or when debugging an individual source helper. +For a quick Komodo-only recheck, use: + +```sh +scripts/run_frame_watch.sh --komodo-only YYYY-MM-DD +``` + +This initializes the dated run folder, runs `check_komodo.sh`, prints whether Komodo product timestamps changed since the previous run, reports any newly discovered Komodo asset URLs, writes `run-summary.md`, and skips SteamKit/PICS, SteamDB, SteamTracking, SteamVR, SteamOS, Valve endpoint, customs, comparison, and status-draft steps. + +Example output: + +```text +Run dir: /Users/sean/Coding/steam-hardware-watch/runs/2026-07-28 +Run note: /Users/sean/Coding/steam-hardware-watch/status/runs/2026-07-28.md +Saved Komodo API responses to /Users/sean/Coding/steam-hardware-watch/runs/2026-07-28/api/komodo +Saved Komodo summaries to /Users/sean/Coding/steam-hardware-watch/runs/2026-07-28/reports +Komodo updated: yes (changed since previous run 2026-07-27) +Komodo changes: +- frame: 2026-07-03T15:56:30 -> 2026-07-28T10:11:12 +Komodo timestamps: controller=2026-07-27T15:46:17, machine=2026-07-27T15:39:56, frame=2026-07-28T10:11:12 +Komodo new assets: yes (1 new, 3 current, since previous run 2026-07-27) +Komodo new asset URLs: +- https://komodostation.com/wp-content/uploads/2026/07/new-frame-asset.jpg +Run summary: /Users/sean/Coding/steam-hardware-watch/runs/2026-07-28/reports/run-summary.md +Komodo-only run complete. +``` + +If nothing changed, the key lines should read: + +```text +Komodo updated: no (matches previous run 2026-07-27) +Komodo new assets: no (2 current, matches previous run 2026-07-27) +``` + ## Repository Layout ```text @@ -120,6 +153,14 @@ scripts/close_komodo.sh That keeps Komodo access isolated from your normal Chrome profile and gives the agent a reusable trusted browser session when Komodo blocks normal automation. +For a focused Komodo recheck, use the same bootstrap with the Komodo-only flag: + +```sh +scripts/bootstrap_komodo.sh +scripts/run_frame_watch.sh --komodo-only 2026-07-28 +scripts/close_komodo.sh +``` + SteamDB can require the same live-browser treatment: ```sh @@ -218,6 +259,24 @@ Then use: - `RUN_DIR/reports/run-summary.md` - `RUN_DIR/reports/compare-vs-*.md` +## Agent Checklist + +For a fresh coding agent, the normal operating loop is: + +1. Read `SKILL.md` before running or editing anything. +2. Choose the narrowest relevant command: + - `scripts/run_frame_watch.sh YYYY-MM-DD` for the normal Frame-focused pass. + - `scripts/run_frame_watch.sh --komodo-only YYYY-MM-DD` for a quick Komodo recheck. + - `scripts/run_watch.sh YYYY-MM-DD` only for the broader all-hardware view or source-helper debugging. +3. Inspect `frame-focus.md` first for full runs, then `run-summary.md`, `status-draft.md`, and `compare-vs-*.md`. +4. Put primary package/store gates first when reporting: SteamKit/PICS, Valve APIs, SteamDB package pages, then Komodo. +5. Treat SteamTracking, SteamVR, SteamOS, and customs rows as secondary context unless they connect directly to Frame package/API, public-page, or shipment evidence. +6. Update `status/current.md` only for material movement or a new clean baseline. +7. Append `status/evidence.jsonl` only for evidence-grade findings, using `scripts/append_evidence.py`. +8. Keep raw artifacts under gitignored `runs/`; never commit `.local/`, browser profiles, sessions, cookies, or one-time auth codes. +9. Validate before finishing: parse `status/evidence.jsonl` if touched, run `git diff --check`, inspect staged scope, and finish with `git status --short`. +10. Commit only the intended files. + ## Agent Usage This repo is meant to be used by coding agents, not only by a human at a shell. @@ -472,6 +531,13 @@ Normal use: 3. inspect the comparison report if something changed 4. update `status/current.md` and `status/evidence.jsonl` only for material changes +Quick Komodo recheck: + +1. run `scripts/run_frame_watch.sh --komodo-only YYYY-MM-DD` +2. scan the CLI lines for product timestamp changes and new asset URLs +3. read `run-summary.md` and the Komodo reports under `reports/` +4. rerun the full Frame watcher only if Komodo exposes material movement + Escalation: 1. if Komodo blocks, continue the run diff --git a/SKILL.md b/SKILL.md index 92a1a06..a7d6436 100644 --- a/SKILL.md +++ b/SKILL.md @@ -77,6 +77,7 @@ Asset discovery should be dynamic: Default agent entry point: - use [scripts/run_frame_watch.sh](scripts/run_frame_watch.sh) for normal Frame-focused runs +- use `scripts/run_frame_watch.sh --komodo-only YYYY-MM-DD` when the user asks for a focused Komodo-only recheck - 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 `frame-focus.md`, `run-summary.md`, `status-draft.md`, and the comparison report @@ -125,6 +126,14 @@ Interpretation rules: Read [references/komodo-api-notes.md](references/komodo-api-notes.md) first. +For a quick Komodo-only check, prefer: + +```sh +scripts/run_frame_watch.sh --komodo-only YYYY-MM-DD +``` + +This initializes the run folder, runs only the Komodo helper, prints product timestamp changes and newly discovered asset URLs versus the previous run, writes `run-summary.md`, and skips the full watcher sources and comparison/status-draft steps. + Check: - product records for `Steam Controller`, `Steam Machine`, `Steam Frame` diff --git a/agents/openai.yaml b/agents/openai.yaml index ef9b02d..0a178bf 100644 --- a/agents/openai.yaml +++ b/agents/openai.yaml @@ -1,4 +1,4 @@ version: 1 display_name: Steam Hardware Watch -short_description: Reusable workflow for monitoring Valve hardware launch signals across Komodo, SteamDB, SteamTracking, Valve endpoints, and related evidence sources. -default_prompt: Check the current status of rumored or upcoming Valve hardware, save artifacts, and update the status ledger. +short_description: Frame-first workflow for monitoring Valve hardware launch signals across Komodo, SteamKit/PICS, SteamDB, SteamTracking, Valve endpoints, and related evidence sources. +default_prompt: Run the Steam Frame-focused hardware watch, or a Komodo-only recheck when requested, save artifacts, compare against the previous run, and update the status ledger only for material movement. diff --git a/scripts/run_frame_watch.sh b/scripts/run_frame_watch.sh index bd47a60..1efc513 100755 --- a/scripts/run_frame_watch.sh +++ b/scripts/run_frame_watch.sh @@ -1,8 +1,34 @@ #!/bin/sh set -eu +usage() { + echo "usage: $0 [--komodo-only] YYYY-MM-DD [base_dir] [tracking_dir]" >&2 +} + +KOMODO_ONLY=0 + +while [ "$#" -gt 0 ]; do + case "$1" in + --komodo-only) + KOMODO_ONLY=1 + shift + ;; + --) + shift + break + ;; + -*) + usage + exit 1 + ;; + *) + break + ;; + esac +done + if [ "$#" -lt 1 ]; then - echo "usage: $0 YYYY-MM-DD [base_dir] [tracking_dir]" >&2 + usage exit 1 fi @@ -13,6 +39,15 @@ REPO_DIR="$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd)" BASE_DIR="${2:-$REPO_DIR/runs}" TRACKING_DIR="${3:-/tmp/SteamTracking-master}" +if [ "$KOMODO_ONLY" = "1" ]; then + if [ "$#" -ge 3 ]; then + "$SCRIPT_DIR/run_watch.sh" --komodo-only "$RUN_DATE" "$BASE_DIR" "$TRACKING_DIR" + else + "$SCRIPT_DIR/run_watch.sh" --komodo-only "$RUN_DATE" "$BASE_DIR" + fi + exit 0 +fi + if [ "$#" -ge 3 ]; then "$SCRIPT_DIR/run_watch.sh" "$RUN_DATE" "$BASE_DIR" "$TRACKING_DIR" else diff --git a/scripts/run_watch.sh b/scripts/run_watch.sh index c908457..9620a01 100755 --- a/scripts/run_watch.sh +++ b/scripts/run_watch.sh @@ -1,8 +1,34 @@ #!/bin/sh set -eu +usage() { + echo "usage: $0 [--komodo-only] YYYY-MM-DD [base_dir] [tracking_dir]" >&2 +} + +KOMODO_ONLY=0 + +while [ "$#" -gt 0 ]; do + case "$1" in + --komodo-only) + KOMODO_ONLY=1 + shift + ;; + --) + shift + break + ;; + -*) + usage + exit 1 + ;; + *) + break + ;; + esac +done + if [ "$#" -lt 1 ]; then - echo "usage: $0 YYYY-MM-DD [base_dir] [tracking_dir]" >&2 + usage exit 1 fi @@ -31,6 +57,152 @@ cleanup() { trap cleanup EXIT INT TERM +find_previous_run_dir() { + previous_dir="" + for candidate in $(find "$BASE_DIR" -mindepth 1 -maxdepth 1 -type d -name '????-??-??' | sort); do + if [ "$candidate" = "$RUN_DIR" ]; then + continue + fi + case "$(basename "$candidate")" in + "$RUN_DATE") ;; + *) + if [ "$(basename "$candidate")" \< "$RUN_DATE" ]; then + previous_dir="$candidate" + fi + ;; + esac + done + printf '%s\n' "$previous_dir" +} + +komodo_timestamp_summary() { + awk -F '\t' 'NF >= 2 { + if (out != "") { + out = out ", " + } + out = out $1 "=" $2 + } END { + print out + }' "$1" +} + +komodo_timestamp_changes() { + awk -F '\t' 'NR == FNR { + previous[$1] = $2 + previous_order[++previous_count] = $1 + next + } + NF >= 2 { + current_seen[$1] = 1 + if (!($1 in previous)) { + print "- " $1 ": added " $2 + } else if (previous[$1] != $2) { + print "- " $1 ": " previous[$1] " -> " $2 + } + } + END { + for (i = 1; i <= previous_count; i++) { + key = previous_order[i] + if (!(key in current_seen)) { + print "- " key ": removed " previous[key] + } + } + }' "$1" "$2" +} + +komodo_asset_count() { + awk -F '\t' 'NF >= 3 && $3 ~ /^https?:/ { + count++ + } END { + print count + 0 + }' "$1" +} + +komodo_new_asset_urls() { + awk -F '\t' 'NR == FNR { + if (NF >= 3 && $3 ~ /^https?:/) { + previous[$3] = 1 + } + next + } + NF >= 3 && $3 ~ /^https?:/ && !($3 in previous) { + print "- " $3 + }' "$1" "$2" +} + +print_komodo_update_status() { + current_report="$RUN_DIR/reports/komodo-product-modified.tsv" + + if [ ! -s "$current_report" ]; then + printf '%s\n' "Komodo updated: unknown (current Komodo timestamps unavailable)" + return 0 + fi + + current_summary="$(komodo_timestamp_summary "$current_report")" + previous_dir="$(find_previous_run_dir)" + + if [ -z "$previous_dir" ]; then + printf '%s\n' "Komodo updated: unknown (no previous run found)" + printf '%s\n' "Komodo timestamps: $current_summary" + return 0 + fi + + previous_report="$previous_dir/reports/komodo-product-modified.tsv" + previous_date="$(basename "$previous_dir")" + + if [ ! -s "$previous_report" ]; then + printf '%s\n' "Komodo updated: unknown (previous run $previous_date has no Komodo timestamp report)" + printf '%s\n' "Komodo timestamps: $current_summary" + return 0 + fi + + if cmp -s "$previous_report" "$current_report"; then + printf '%s\n' "Komodo updated: no (matches previous run $previous_date)" + else + printf '%s\n' "Komodo updated: yes (changed since previous run $previous_date)" + printf '%s\n' "Komodo changes:" + komodo_timestamp_changes "$previous_report" "$current_report" + fi + + printf '%s\n' "Komodo timestamps: $current_summary" +} + +print_komodo_asset_status() { + current_report="$RUN_DIR/reports/komodo-visual-assets.tsv" + + if [ ! -s "$current_report" ]; then + printf '%s\n' "Komodo new assets: unknown (current Komodo asset report unavailable)" + return 0 + fi + + current_count="$(komodo_asset_count "$current_report")" + previous_dir="$(find_previous_run_dir)" + + if [ -z "$previous_dir" ]; then + printf '%s\n' "Komodo new assets: unknown ($current_count current, no previous run found)" + return 0 + fi + + previous_report="$previous_dir/reports/komodo-visual-assets.tsv" + previous_date="$(basename "$previous_dir")" + + if [ ! -s "$previous_report" ]; then + printf '%s\n' "Komodo new assets: unknown ($current_count current, previous run $previous_date has no Komodo asset report)" + return 0 + fi + + new_urls="$(komodo_new_asset_urls "$previous_report" "$current_report")" + new_count="$(printf '%s\n' "$new_urls" | sed '/^$/d' | wc -l | tr -d ' ')" + + if [ "$new_count" = "0" ]; then + printf '%s\n' "Komodo new assets: no ($current_count current, matches previous run $previous_date)" + else + printf '%s\n' "Komodo new assets: yes ($new_count new, $current_count current, since previous run $previous_date)" + printf '%s\n' "Komodo new asset URLs:" + printf '%s\n' "$new_urls" + fi +} + INIT_OUTPUT="$("$SCRIPT_DIR/init_run.sh" "$RUN_DATE" "$BASE_DIR")" RUN_DIR="$(printf '%s\n' "$INIT_OUTPUT" | awk -F= '/^RUN_DIR=/{print $2}')" RUN_NOTE="$(printf '%s\n' "$INIT_OUTPUT" | awk -F= '/^RUN_NOTE=/{print $2}')" @@ -44,6 +216,19 @@ printf '%s\n' "Run dir: $RUN_DIR" printf '%s\n' "Run note: $RUN_NOTE" "$SCRIPT_DIR/check_komodo.sh" "$RUN_DIR" + +if [ "$KOMODO_ONLY" = "1" ]; then + print_komodo_update_status + print_komodo_asset_status + RUN_SUMMARY_OUT="$RUN_DIR/reports/run-summary.md" + python3 "$SCRIPT_DIR/write_run_summary.py" \ + --run-dir "$RUN_DIR" \ + --output "$RUN_SUMMARY_OUT" + printf '%s\n' "Run summary: $RUN_SUMMARY_OUT" + printf '%s\n' "Komodo-only run complete." + exit 0 +fi + "$SCRIPT_DIR/check_steamkit_pics.sh" "$RUN_DIR" "$SCRIPT_DIR/check_steamdb.sh" "$RUN_DIR" "$SCRIPT_DIR/check_steamtracking.sh" "$RUN_DIR" "$TRACKING_DIR" @@ -54,19 +239,7 @@ printf '%s\n' "Run note: $RUN_NOTE" python3 "$SCRIPT_DIR/save_visual_assets.py" --run-dir "$RUN_DIR" --base-dir "$BASE_DIR" PREVIOUS_DIR="" -for candidate in $(find "$BASE_DIR" -mindepth 1 -maxdepth 1 -type d -name '????-??-??' | sort); do - if [ "$candidate" = "$RUN_DIR" ]; then - continue - fi - case "$(basename "$candidate")" in - "$RUN_DATE") ;; - *) - if [ "$(basename "$candidate")" \< "$RUN_DATE" ]; then - PREVIOUS_DIR="$candidate" - fi - ;; - esac -done +PREVIOUS_DIR="$(find_previous_run_dir)" if [ -n "$PREVIOUS_DIR" ]; then COMPARE_OUT="$RUN_DIR/reports/compare-vs-$(basename "$PREVIOUS_DIR").md" diff --git a/tests/test_run_watch_cli.py b/tests/test_run_watch_cli.py new file mode 100644 index 0000000..2a5d9c7 --- /dev/null +++ b/tests/test_run_watch_cli.py @@ -0,0 +1,244 @@ +import os +import subprocess +import tempfile +import textwrap +import unittest +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] + + +class RunWatchCliTests(unittest.TestCase): + def setUp(self): + self.tmp = tempfile.TemporaryDirectory() + self.addCleanup(self.tmp.cleanup) + self.repo = Path(self.tmp.name) + self.scripts = self.repo / "scripts" + self.scripts.mkdir() + (self.repo / "status" / "runs").mkdir(parents=True) + self.log = self.repo / "calls.log" + + os.symlink(ROOT / "scripts" / "run_watch.sh", self.scripts / "run_watch.sh") + os.symlink(ROOT / "scripts" / "run_frame_watch.sh", self.scripts / "run_frame_watch.sh") + + self._write_script( + "init_run.sh", + """ + #!/bin/sh + set -eu + run_date="$1" + base_dir="$2" + run_dir="$base_dir/$run_date" + mkdir -p "$run_dir/reports" "$run_dir/api" + run_note="$PWD/status/runs/$run_date.md" + printf 'init_run %s %s\\n' "$run_date" "$base_dir" >> "$CALL_LOG" + printf 'RUN_DIR=%s\\nRUN_NOTE=%s\\n' "$run_dir" "$run_note" + """, + ) + self._write_script( + "check_komodo.sh", + """ + #!/bin/sh + set -eu + printf 'check_komodo %s\\n' "$1" >> "$CALL_LOG" + mkdir -p "$1/reports" + { + printf 'controller\\t%s\\n' "${KOMODO_CONTROLLER_MODIFIED:-2026-07-27T15:46:17}" + printf 'machine\\t%s\\n' "${KOMODO_MACHINE_MODIFIED:-2026-07-27T15:39:56}" + printf 'frame\\t%s\\n' "${KOMODO_FRAME_MODIFIED:-2026-07-03T15:56:30}" + } > "$1/reports/komodo-product-modified.tsv" + { + printf '413804\\timage/png\\thttps://komodostation.com/wp-content/uploads/2025/11/Frame_BG.png\\n' + printf '456489\\timage/jpeg\\thttps://komodostation.com/wp-content/uploads/2025/11/source_SF_headsetControllers_front_2.jpg\\n' + if [ -n "${KOMODO_EXTRA_ASSET_URL:-}" ]; then + printf '999999\\timage/jpeg\\t%s\\n' "$KOMODO_EXTRA_ASSET_URL" + fi + } > "$1/reports/komodo-visual-assets.tsv" + """, + ) + self._write_python( + "write_run_summary.py", + """ + import sys + from pathlib import Path + Path(__import__("os").environ["CALL_LOG"]).open("a").write("write_run_summary " + " ".join(sys.argv[1:]) + "\\n") + output = Path(sys.argv[sys.argv.index("--output") + 1]) + output.parent.mkdir(parents=True, exist_ok=True) + output.write_text("# Summary\\n", encoding="utf-8") + """, + ) + + for name in [ + "check_steamkit_pics.sh", + "check_steamdb.sh", + "check_steamtracking.sh", + "check_steamvr_depots.sh", + "check_steamos_mirror.sh", + "check_valve_endpoints.sh", + "check_customs_shipments.sh", + "save_visual_assets.py", + "compare_runs.py", + "write_frame_focus_report.py", + "draft_status_update.py", + ]: + if name.endswith(".py"): + self._write_python(name, f"raise SystemExit('unexpected {name}')\n") + else: + self._write_script( + name, + f""" + #!/bin/sh + echo 'unexpected {name}' >&2 + exit 42 + """, + ) + + def _write_script(self, name, body): + path = self.scripts / name + path.write_text(textwrap.dedent(body).lstrip(), encoding="utf-8") + path.chmod(0o755) + + def _write_python(self, name, body): + path = self.scripts / name + path.write_text(textwrap.dedent(body).lstrip(), encoding="utf-8") + path.chmod(0o755) + + def run_script(self, *args): + env = os.environ.copy() + env["CALL_LOG"] = str(self.log) + return subprocess.run( + list(args), + cwd=self.repo, + env=env, + text=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + check=False, + ) + + def write_previous_komodo_report( + self, + run_date, + frame_modified, + controller_modified="2026-07-27T15:46:17", + machine_modified="2026-07-27T15:39:56", + ): + report = self.repo / "runs" / run_date / "reports" / "komodo-product-modified.tsv" + report.parent.mkdir(parents=True) + report.write_text( + "\n".join( + [ + f"controller\t{controller_modified}", + f"machine\t{machine_modified}", + f"frame\t{frame_modified}", + "", + ] + ), + encoding="utf-8", + ) + + def write_previous_komodo_assets(self, run_date, extra_urls=None): + report = self.repo / "runs" / run_date / "reports" / "komodo-visual-assets.tsv" + report.parent.mkdir(parents=True, exist_ok=True) + rows = [ + "413804\timage/png\thttps://komodostation.com/wp-content/uploads/2025/11/Frame_BG.png", + "456489\timage/jpeg\thttps://komodostation.com/wp-content/uploads/2025/11/source_SF_headsetControllers_front_2.jpg", + ] + for index, url in enumerate(extra_urls or [], start=1): + rows.append(f"99999{index}\timage/jpeg\t{url}") + report.write_text("\n".join(rows + [""]), encoding="utf-8") + + def test_run_watch_komodo_only_runs_komodo_and_skips_other_sources(self): + base_dir = self.repo / "runs" + self.write_previous_komodo_report("2099-01-01", "2026-07-03T15:56:30") + self.write_previous_komodo_assets("2099-01-01") + + result = self.run_script( + str(self.scripts / "run_watch.sh"), + "--komodo-only", + "2099-01-02", + str(base_dir), + "/tmp/not-used", + ) + + self.assertEqual("", result.stderr) + self.assertEqual(0, result.returncode) + self.assertIn(f"Run dir: {base_dir / '2099-01-02'}", result.stdout) + self.assertIn("Komodo updated: no (matches previous run 2099-01-01)", result.stdout) + self.assertIn( + "Komodo timestamps: controller=2026-07-27T15:46:17, machine=2026-07-27T15:39:56, frame=2026-07-03T15:56:30", + result.stdout, + ) + self.assertIn("Komodo new assets: no (2 current, matches previous run 2099-01-01)", result.stdout) + self.assertIn("Komodo-only run complete.", result.stdout) + calls = self.log.read_text(encoding="utf-8") + self.assertIn("check_komodo", calls) + self.assertIn("write_run_summary", calls) + self.assertNotIn("unexpected", calls) + + def test_run_frame_watch_komodo_only_does_not_write_frame_focus_report(self): + base_dir = self.repo / "runs" + + result = self.run_script( + str(self.scripts / "run_frame_watch.sh"), + "--komodo-only", + "2099-01-03", + str(base_dir), + ) + + self.assertEqual("", result.stderr) + self.assertEqual(0, result.returncode) + self.assertIn("Komodo-only run complete.", result.stdout) + calls = self.log.read_text(encoding="utf-8") + self.assertIn("check_komodo", calls) + self.assertNotIn("write_frame_focus_report", calls) + + def test_run_watch_komodo_only_reports_when_komodo_changed(self): + base_dir = self.repo / "runs" + self.write_previous_komodo_report("2099-01-03", "2026-07-03T15:56:30") + self.write_previous_komodo_assets("2099-01-03") + os.environ["KOMODO_FRAME_MODIFIED"] = "2026-07-28T10:11:12" + self.addCleanup(lambda: os.environ.pop("KOMODO_FRAME_MODIFIED", None)) + + result = self.run_script( + str(self.scripts / "run_watch.sh"), + "--komodo-only", + "2099-01-04", + str(base_dir), + ) + + self.assertEqual("", result.stderr) + self.assertEqual(0, result.returncode) + self.assertIn("Komodo updated: yes (changed since previous run 2099-01-03)", result.stdout) + self.assertIn("Komodo changes:", result.stdout) + self.assertIn("- frame: 2026-07-03T15:56:30 -> 2026-07-28T10:11:12", result.stdout) + self.assertIn( + "Komodo timestamps: controller=2026-07-27T15:46:17, machine=2026-07-27T15:39:56, frame=2026-07-28T10:11:12", + result.stdout, + ) + + def test_run_watch_komodo_only_reports_new_asset_urls(self): + base_dir = self.repo / "runs" + self.write_previous_komodo_report("2099-01-05", "2026-07-03T15:56:30") + self.write_previous_komodo_assets("2099-01-05") + new_url = "https://komodostation.com/wp-content/uploads/2026/07/new-frame-asset.jpg" + os.environ["KOMODO_EXTRA_ASSET_URL"] = new_url + self.addCleanup(lambda: os.environ.pop("KOMODO_EXTRA_ASSET_URL", None)) + + result = self.run_script( + str(self.scripts / "run_watch.sh"), + "--komodo-only", + "2099-01-06", + str(base_dir), + ) + + self.assertEqual("", result.stderr) + self.assertEqual(0, result.returncode) + self.assertIn("Komodo new assets: yes (1 new, 3 current, since previous run 2099-01-05)", result.stdout) + self.assertIn("Komodo new asset URLs:", result.stdout) + self.assertIn(f"- {new_url}", result.stdout) + + +if __name__ == "__main__": + unittest.main()