Localhost port watcher: detects a newly bound dev-server port via bounded TCP probing, opens an iframe preview pane, and captures a one-click timestamped screenshot receipt via headless Chrome. No Claude Code changes, stdlib-only, 16 passing tests plus a real end-to-end dogfood (demo.py) with a genuine recorded screenshot. Landing page at ventures/preview-sidecar/site/index.html uses a receipt/ticket visual identity (narrow thermal-paper column, dotted rules, wax stamp, monospace totals) distinct from the finishline venture, with one above-the-fold signup CTA and an honest free/paid pricing section. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
94 lines
4.3 KiB
Python
94 lines
4.3 KiB
Python
#!/usr/bin/env python3
|
|
"""Real end-to-end dogfood: start a throwaway dev server, detect it with
|
|
sidecar.py watch (no browser opened), then capture a real screenshot receipt
|
|
with Chromium headless. No mocks. Cleans up only its own temp resources.
|
|
|
|
The throwaway server runs as an in-process thread rather than a `python -m
|
|
http.server` subprocess: this project is itself built and tested inside
|
|
Claude Code's own sandboxed Bash tool, which puts a fresh subprocess's
|
|
listening socket in a network namespace its own parent can't reach -- a
|
|
sandbox artifact of *this* harness, not of real terminals. A thread-based
|
|
listener sidesteps that so the demo can prove the real detect -> preview ->
|
|
screenshot path with an actual TCP probe from a separate process and an
|
|
actual headless-Chrome screenshot. On a normal dev machine, `npm run dev` /
|
|
`vite` / etc. are plain reachable subprocesses and this workaround is moot.
|
|
"""
|
|
import http.server
|
|
import json
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import threading
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).parent.resolve()
|
|
SIDECAR = ROOT / "sidecar.py"
|
|
|
|
|
|
class _QuietHandler(http.server.SimpleHTTPRequestHandler):
|
|
def log_message(self, *args):
|
|
pass
|
|
|
|
|
|
def start_demo_server(site_dir):
|
|
handler = lambda *a, **kw: _QuietHandler(*a, directory=site_dir, **kw)
|
|
httpd = http.server.HTTPServer(("127.0.0.1", 0), handler)
|
|
thread = threading.Thread(target=httpd.serve_forever, daemon=True)
|
|
thread.start()
|
|
return httpd
|
|
|
|
|
|
def main():
|
|
with tempfile.TemporaryDirectory(prefix="preview-sidecar-demo-") as site_dir, \
|
|
tempfile.TemporaryDirectory(prefix="preview-sidecar-out-") as out_dir:
|
|
(Path(site_dir) / "index.html").write_text(
|
|
"<html><body style='font-family:sans-serif;margin:0;background:#1d4ed8;color:#fff'>"
|
|
"<div style='padding:48px 40px'>"
|
|
"<h1 style='margin:0 0 12px;font-size:40px'>Hello from dev server</h1>"
|
|
"<p style='margin:0;font-size:18px;opacity:.9'>Started by preview-sidecar demo.py</p>"
|
|
"</div></body></html>")
|
|
|
|
httpd = start_demo_server(site_dir)
|
|
port = httpd.server_port
|
|
print(f"$ (throwaway dev server listening on 127.0.0.1:{port}, serving a one-page site)")
|
|
try:
|
|
print(f"$ python3 sidecar.py watch --ports {port} --interval 0.3 --max-cycles 3 --no-open --out {out_dir}")
|
|
watch = subprocess.run(
|
|
[sys.executable, str(SIDECAR), "watch", "--ports", str(port),
|
|
"--interval", "0.3", "--max-cycles", "3", "--no-open", "--out", out_dir],
|
|
capture_output=True, text=True, timeout=30)
|
|
print(watch.stdout.strip())
|
|
assert f"detected 127.0.0.1:{port}" in watch.stdout, watch.stdout
|
|
events = (Path(out_dir) / "events.jsonl").read_text().strip().splitlines()
|
|
assert len(events) == 1, events
|
|
event = json.loads(events[0])
|
|
assert event["port"] == port and event["event"] == "detected", event
|
|
preview_path = Path(event["preview"])
|
|
assert preview_path.exists()
|
|
assert f"http://127.0.0.1:{port}" in preview_path.read_text()
|
|
print(f" preview pane written: {preview_path.name} (iframe -> http://127.0.0.1:{port})")
|
|
|
|
chrome = shutil.which("google-chrome") or shutil.which("chromium") or \
|
|
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"
|
|
print(f"$ python3 sidecar.py screenshot --port {port} --out {out_dir} --chrome '{chrome}'")
|
|
shot = subprocess.run(
|
|
[sys.executable, str(SIDECAR), "screenshot", "--port", str(port),
|
|
"--out", out_dir, "--chrome", chrome],
|
|
capture_output=True, text=True, timeout=30)
|
|
print(shot.stdout.strip() or shot.stderr.strip())
|
|
if shot.returncode == 0:
|
|
receipt = json.loads(shot.stdout)
|
|
assert Path(receipt["png"]).stat().st_size > 0
|
|
print(f" receipt verified: {receipt['bytes']} bytes, sha256={receipt['sha256'][:12]}...")
|
|
else:
|
|
print(" (no Chromium-family browser on this host; screenshot step skipped honestly)")
|
|
finally:
|
|
httpd.shutdown()
|
|
httpd.server_close()
|
|
print(f"$ (dev server on {port} stopped)")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|