- JavaScript 94.2%
- CSS 3.2%
- HTML 1.4%
- Python 1%
- Dockerfile 0.1%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| .forgejo/workflows | ||
| assets | ||
| docs | ||
| k8s | ||
| mcp | ||
| scripts | ||
| shows | ||
| .dockerignore | ||
| .gitignore | ||
| app.js | ||
| CONTRACT.md | ||
| Dockerfile | ||
| entrypoint.sh | ||
| filter.js | ||
| index.html | ||
| nginx.conf | ||
| README.md | ||
| styles.css | ||
| SUBMISSIONS.md | ||
skipto.tv 📺
Rewatch guides for the shows you keep going back to — one app, many shows. Live at https://skipto.tv
Every show gets an interactive era chart (showrunners, arcs, big bads, rosters, real-world events…) sitting on top of every episode; every bar on the chart is a filter. Plus vibes, guest-character filters, full-text search, watched tracking, 🎲 Surprise me, shareable filter URLs, and a mobile filter drawer.
Successor to the single-show sites moose-and-squirrel (Supernatural), goat-gideon (Criminal Minds) and greater-fool (The Newsroom), which are imported here unchanged.
Layout
index.html, app.js, styles.css the shell: landing page at /, explorer at /<slug>/
filter.js the filter engine (matching + share-URL format), shared by app.js AND mcp/
mcp/server.js, load.js, test.js MCP server (Streamable HTTP at /mcp) exposing filters + show proposals
mcp/submissions.js validates declarative proposals and opens review PRs through a GitHub App
docs/index.html /docs/ — plain-language "use it from your AI assistant" page
shows/<slug>/show.json branding + metadata ┐
shows/<slug>/episodes.json TVmaze episodes │ one self-contained
shows/<slug>/cast.json TVmaze guest cast │ package per show
shows/<slug>/eras.js the chart (ERAS/ERA_CATS/…) │
shows/<slug>/tags.js curated vibes ┘
shows/index.json generated by scripts/build-index.py — the landing page reads it
Run locally
python3 scripts/dev-server.py 8770 # SPA fallback so /<slug>/ works; then open http://localhost:8770
Add a show
Read CONTRACT.md. In short: create shows/<slug>/ with the five files above (copy the scripts and
formats from an existing show), run python3 scripts/build-index.py, commit. CI fails if shows/index.json is stale.
MCP
https://skipto.tv/mcp — MCP Streamable HTTP, stateless, no user auth. Tools: list_shows, get_show,
find_episodes, get_episode, search_characters, surprise_me, next_episode, share_link,
get_submission_guide, submit_show; resources skipto://shows[/<slug>/{chart,tags,episodes}]. It loads the same
shows/ files the site serves and runs the same
filter.js, so tool results and the UI always agree, and every result carries a #… share URL the site understands
(incl. ep=S05E16 to open an episode card). Human docs at /docs/. submit_show accepts only declarative JSON,
generates the repository files on the server, and opens a GitHub PR for review; see SUBMISSIONS.md.
Tool calls are tracked in the same PostHog project as mcp_* events with surface: mcp.
cd mcp && npm ci && npm test # filter.js semantics + end-to-end MCP client run, no network needed
PORT=8081 node mcp/server.js # local server; SITE_ORIGIN / SHOWS_DIR / POSTHOG_KEY env override
Deploy
git.inevitable.fyi/jhgaylor/across-the-eras (node:24-alpine + nginx: nginx on :8080 serves the site and proxies /mcp to the
node MCP server on :8081 in the same container — see entrypoint.sh; /healthz, try_files … /index.html), built by
.forgejo/workflows/build.yml (Forgejo Actions) on push to main, which pins the new sha-* tag into k8s/deployment.yaml;
home-cloud's Flux reconciles k8s/ to skipto.tv. The legacy operational names remain in place to avoid a disruptive infrastructure migration.
Analytics
One PostHog project (US). Every event carries a show super-property
(landing on the picker). Autocapture is off; explicit events: show_selected, era_selected, season_selected,
vibe_selected, character_selected, searched, sort_changed, filters_reset, surprise_me, episode_opened,
watched_toggled, chart_toggled, filters_drawer_opened.