PenguWatch
Know what to watch next, and pay only for the streaming services you actually use.
A self-hosted TV, anime and movie tracker with a streaming-subscription rotation planner, and two-way sync with your tracking account (SIMKL by default) or no account at all.
Screenshots of the made-up demo library (scripts/demo_library.py). More in the gallery.
PenguWatch is for households that follow shows, anime and movies across several streaming services and want one place that says what to watch next and which subscriptions are worth keeping. You run it yourself, on a home server, a NAS or just your own computer: one small program with one SQLite database, used from any browser on your network (and as an installable phone app). It keeps your library in step with a tracking account, the library service (SIMKL out of the box), or keeps it only in PenguWatch with no account at all. Each service is a driver; this README says "the library service" for whichever one holds your library.
Why
Most households pay for more streaming services than they watch in a given month, and lose track of where each show is. PenguWatch keeps your library in sync with your library service, shows the exact next episode of everything you're watching, and plans which services to keep, pause or cancel month by month, based on what you actually have left to watch on each.
It runs as one small container on a home server or NAS (or as a Python program installed with uv or pipx), keeps everything in a local SQLite database, and works from any browser on your network, including as an installable phone app.
Features
- Up Next: every show you're watching with its exact next episode (
S02E05 – Title), progress and episodes left. One tap marks it watched and sends that episode to the library service. - Episode-level tracking for shows, anime and movies: whole seasons, single episodes or "first N" at once; only the episodes that changed are sent to the library service.
- Two-way sync with the library service, fetching only what changed since the last sync, with an outbox that survives restarts and a review mode (on by default) where nothing is sent or applied until you approve it.
- Drivers: each service is a driver (docs/drivers.md): SIMKL (the default), a
localdriver that keeps your library only in PenguWatch, and a TVmaze driver that finds TV shows without any account. The guide shows how to write another one. - Subscription optimizer and rotation planner: ranks services by backlog hours, ready-to-binge seasons and ratings; plans the next 3, 6 or 12 months within a limit of active services; understands bundles (for example Disney+ & Hulu); tracks renewal dates, price history and cost per hour watched.
- Discover: trending titles and "because you liked X & Y" picks from the discovery service, marked with what you already have and which of your services carries them.
- English titles and forgiving search: anime stored under romaji titles show their English name first; search ignores accents, case, punctuation and long vowels ("shogun" finds Shōgun).
- Household: optionally two people, each title tagged with who wants to watch it; "both want it" titles count more in the plan. Names come from your configuration.
- Tonight's Pick, air schedule and an iCal feed for your calendar app, including renewal and "cancel by" reminders.
- History: what you watched over the last 7 days, 30 days or 12 months, compared with the period before: hours, episodes, titles started and finished, a chart per day, top shows, hours and cost per hour by service, genres and when in the week you watch.
- Settings in the app: household names, the sync schedule, the optimizer's weights, the daily digest, webhooks, the
theme and more are changed on the Settings tab, kept as versions you can restore;
config.yamlis read once and then keeps only where the app listens and where its database is (docs/configuration.md). - Alerts and insights: a daily ntfy or webhook digest, monthly hours, hours by service, top genres.
- Your own ratings: add rating sources (a site you follow, a friend, a spreadsheet), import them from CSV or the API, then show them as badges, sort and filter by them. The ratings the catalogue drivers provide are built in (SIMKL's and IMDb's, or MyAnimeList's for anime, from SIMKL; TVmaze's from TVmaze).
- Extensible from outside: a documented REST API (docs/api.md) and signed event webhooks (title added, status or rating changed, episode watched or airing today, sync failed); examples in docs/extending.md.
- Ask an AI assistant: an optional, read-only MCP endpoint lets Claude answer "what's up next?", "what should we watch tonight?", "which service should I cancel?" and "what did we watch this month?" from your library (docs/mcp.md); off by default.
- Light and dark, and seven themes (Nord, Dracula, Solarized, E-ink, High contrast, Blueprint and the standard look): pick one per browser in the header, or set the default for new browsers on the Settings tab. Light and dark follow your system until you pick one (themes).
- Built for a home network: optional access token, DNS-rebinding and cross-site protection, a strict CSP, a non-root read-only container, Prometheus metrics and optional OpenTelemetry export.
Quick start
Out of the box PenguWatch syncs with a free SIMKL account, but you can run without one. PenguWatch runs on your own computer, starting by itself in the background, or in a container: pick one.
Windows (PowerShell, no administrator rights):
irm https://github.com/pengupowered/penguwatch/releases/latest/download/install.ps1 | iex
macOS and Linux (asks for sudo once; end the line with sh -s -- --user instead to avoid it):
curl -fsSL https://github.com/pengupowered/penguwatch/releases/latest/download/install.sh | sh
The scripts check every download (pinned uv, hash-locked dependencies), install a private Python 3.12, and start
PenguWatch on http://127.0.0.1:8080, this computer only. Per-system details, upgrading and uninstalling:
Install.
With uv or pipx (Python 3.11 or newer):
uv tool install penguwatch # or: pipx install penguwatch
penguwatch init-config # optional: writes an example config.yaml (listen address, database location)
penguwatch # http://127.0.0.1:8080, this computer only
penguwatch service install --user # optional: start it by itself (systemd, launchd or a logon task)
With a container (Docker, or Podman with podman-compose; Intel/AMD and ARM):
mkdir penguwatch && cd penguwatch
curl -fsSL https://raw.githubusercontent.com/pengupowered/penguwatch/main/compose.release.yaml -o compose.yaml
curl -fsSL https://raw.githubusercontent.com/pengupowered/penguwatch/main/config.example.yaml -o config.yaml
docker compose up -d # or: podman compose up -d
Or build it yourself from a clone: cp config.example.yaml config.yaml, then docker compose up -d --build. Open
http://<server-ip>:8080 and set your household names on the Settings tab. Install has the details: where config and data live, upgrading, backups.
Then connect the default library service, SIMKL (the first run, with local and TVmaze instead, is in Getting started):
- Create a SIMKL app once at simkl.com/settings/developer and copy its client ID.
- In PenguWatch, open Services & Sync, enter the client ID under Enter Client ID / Token Manually, then choose Connect SIMKL with Device PIN and approve the code at simkl.com/pin. The first sync imports your library.
- Mark the services you subscribe to this month and check their prices.
Important: without an access token, anyone who can reach port 8080 can read and change your library, connect PenguWatch to their own library-service account (your later changes are then sent to them), approve queued changes, download a backup and trigger the webhook and notification tests. Both compose files publish
0.0.0.0:8080, so a container is reachable on your whole network from its first start: setPENGUWATCH_ACCESS_TOKENfirst (in.envfor the container, inpenguwatch.envnext toconfig.yamlfor a native install; see Install and Security). Over plain HTTP the token can be read by others on the network: use HTTPS through a reverse proxy if that matters. Thepenguwatchcommand listens on this computer only unless you pass--host 0.0.0.0.
Your data survives upgrades: it lives in the penguwatch_data volume (container) or your data folder
(penguwatch paths). To update, run the install script again (or uv tool upgrade penguwatch), or raise the image tag
and docker compose up -d;
database migrations run on start.
Without an account
SIMKL is the default library service, but PenguWatch works with no account. On the Settings tab, under Data sources (applies after a restart), choose:
- Library: Local (your library, watch history and ratings live only in PenguWatch)
- Catalogue: TVmaze (find and add TV shows at TVmaze; no account or key)
- Schedule: TVmaze (upcoming episodes)
- Discovery: none (no Discover tab)
You can still add shows, track episodes, rate them, get the air schedule and calendar feed, and use the subscription optimizer and planner. What you lose compared with a library service:
- Sync: nothing is sent to or read from an account, so an existing account library is not imported and no other device or app sees your changes. Back up the database (Operations).
- Discovery: TVmaze has no trending lists or recommendations, so Discover is off.
- Movies: TVmaze has no movies, so a movie cannot be searched for and added (the catalogue list can name more than
one driver:
[tvmaze, simkl]adds SIMKL's catalogue, which then needs a SIMKL app id). - Anime: TVmaze lists an anime as a TV show with the Anime genre, which PenguWatch types as anime; it has no sequels and no anime-specific data beyond that genre.
- Ratings: TVmaze's average only, with none of the scores a library service's catalogue brings (SIMKL's and IMDb's, with the default).
TVmaze's data is CC BY-SA 4.0, so PenguWatch credits TVmaze (in the page footer, on Services & Sync and in a title's details) as its licence asks. Drivers has the details, and the library on SIMKL with TVmaze first in the catalogue list mixes both.
Configuration in brief
Settings are on the Settings tab: your household, the sync schedule, the optimizer's weights, the daily digest and
event webhooks, the theme and time zone, the data sources and the advanced values. A change applies at once (a few say
"applies after a restart") and is kept in the database with a history you can restore from. Streaming services are on the
Services & Sync tab and your own rating sources on the Ratings tab. The addresses and signing secrets you enter for
notifications and webhooks are kept in secrets.json next to the database, never shown again, so back that file up with
the database.
config.yaml is optional and keeps only where the app listens and where its database is. If you are upgrading from 0.15,
the first start reads your config.yaml once and moves its settings onto the tabs; after that the file is ignored for
them. .env holds secrets and deployment settings (PENGUWATCH_*); a variable such as PENGUWATCH_TZ pins its setting
and the tab shows it read-only. Everything is described in
Configuration.
Documentation
New here? Getting started walks through the first run. docs/README.md is the index of everything.
| Topic | What it covers |
|---|---|
| Getting started | The first run end to end: install, services, the library service or local, the first sync |
| FAQ and troubleshooting | Port in use, expired login, nothing syncs, wrong service, time zone, reverse proxy, backup, upgrade, rollback |
| Install | Windows, macOS, Linux, Raspberry Pi, uv or pipx, container, the background service, upgrading, backups |
| Configuration | The Settings tab, the bootstrap config.yaml, environment variables, the one-time import, services and bundles, time zone |
| Using PenguWatch | The tabs, the optimizer and planner, the calendar feed, shortcuts, the phone app |
| Sync | How the sync engine works (outbox, review mode, removals), then the SIMKL driver's rules: API usage, login renewal |
| Drivers | The drivers that ship (SIMKL, local, TVmaze), switching, and a guide to writing one |
| API | Authentication, the stable endpoints and their shapes, errors, limits |
| MCP | The read-only endpoint for AI assistants: turning it on, the tools, security |
| Extending | Your own rating sources, event webhooks, drivers, adding a component |
| Security | Access token, request guards, the container, what is exposed |
| Operations | Monitoring, Prometheus, OpenTelemetry, notifications, backups and exports |
| Architecture | The ordered list of backend components, start-up failures, shutdown |
| Development | Project layout, running locally, tests |
| The web UI | How the page is built, theming, adding a tab, the rules the tests enforce |
| Releasing | CI, the release workflow, PyPI and the container registry |
| Supply chain | Dependency policy, hash-locked requirements, vendored code |
Security in one line: one shared access token protects the app, which is meant for a home network and not the open internet; set it, and read docs/security.md. Found a vulnerability? Report it privately (SECURITY.md).
Contributing
Bug reports, ideas and code are welcome: see CONTRIBUTING.md and the code of conduct.
Development
pip install --require-hashes --no-deps -r requirements-dev.txt
PYTHONPATH=src pytest -q
PYTHONPATH=src python -m penguwatch.main # http://localhost:8080, using ./config.yaml or the example
scripts/test.sh # the test suite in a container, nothing installed locally
Dependencies are hash-locked (requirements*.txt, generated from the .in files) and installed with
--require-hashes. scripts/deploy.sh is the maintainer's deploy for rootless Podman: it adds a dependency audit (the
separate depguard tool), a rollback image and a database backup before rebuilding. docker compose is all you need
to run PenguWatch.
License
MIT. PenguWatch is not affiliated with SIMKL or TVmaze; its default driver uses SIMKL's public API with your own client ID.
TVmaze credit: when the TVmaze driver is used, TV data comes from TVmaze under CC BY-SA 4.0, and PenguWatch says so in the page footer, on Services & Sync and in a title's details ("TV data from TVmaze", linked to tvmaze.com).
Metadata
Release files for penguwatch 0.16.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| penguwatch-0.16.0.tar.gz | 944.1 kB | With provenance | Details for penguwatch-0.16.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| penguwatch-0.16.0-py3-none-any.whl | Python 3 | none | any | Details for penguwatch-0.16.0-py3-none-any.whl |
Total release size: 2.0 MB
Release files / penguwatch-0.16.0.tar.gz
| Download URL | penguwatch-0.16.0.tar.gz |
|---|---|
| Size | 944.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
015a5af9f94407c7eadab483c93c00987a7a045beddd792ca3c77f8882302764
|
|
BLAKE2b-256 checksum How to use checksums |
754651cf1440abb2f15e946069ac95e6468abf6c5f15223ad32718d469096fa2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 8, 2026.
Transparency logRelease files / penguwatch-0.16.0-py3-none-any.whl
| Download URL | penguwatch-0.16.0-py3-none-any.whl |
|---|---|
| Size | 1.1 MB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
3599d37cb2c59b780437812b7f75b02e21cbad38e7ba85f6d53453edeb8446fd
|
|
BLAKE2b-256 checksum How to use checksums |
bb11d57989e8d10e00ac4325555fc52cc7f1d0c57b856d83fb3e9fb7c81a804b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 8, 2026.
Transparency logSecurity for penguwatch 0.16.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
Both files in this release have been uploaded with provenance metadata.
Attestation for penguwatch-0.16.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 8, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.