Files
ani-cli-web/README.md
T

8.5 KiB

ani-cli web

Small local web UI for a system-wide ani-cli install.

Current version: 0.34.0

Project Layout

ani-cli-web/
  app.py                 Main entrypoint and runtime wiring.
  app_support.py         Shared config, state, and helper utilities.
  Dockerfile             Container image build.
  docker-compose.yaml    Example Compose service.
  queue_jobs.py          Download queue and watchlist refresh workers.
  watchlist_identity.py  Download-to-watchlist identity matching.
  title_matching.py      Shared title normalization and matching.
  http_handler.py        HTTP routing and access control.
  web_templates.py       Shared page aggregation.
  search_page.py         Search page template.
  config_page.py         Config page template.
  watchlist_page.py      Watchlist page template.
  docker-entrypoint.sh   Container startup wrapper.
  ani-cli-web            Launcher script.
  test_app.py            Regression tests.
  VERSION                Project version.
  CHANGELOG.md           Change history.
  README.md              Project documentation.

Quick Start

Run the app:

./ani-cli-web

Run with debug logging:

./ani-cli-web --debug

Open:

http://127.0.0.1:8421/

Default bind address is 127.0.0.1:8421.

Docker

The image installs ani-cli system-wide, downloads the latest upstream yt-dlp release binary from GitHub, and clones ani-cli-web into /app during build.

Default build args:

ANI_CLI=https://github.com/pystardust/ani-cli.git
ANI_CLI_BRANCH=master
ANI_CLI_WEB=https://gitea.coreplay.eu/Dymas/ani-cli-web.git

Build:

docker build -t ani-cli-web .

Build with a custom ani-cli branch:

docker build \
  --build-arg ANI_CLI_BRANCH=my-branch \
  --build-arg ANI_CLI=https://github.com/pystardust/ani-cli.git \
  --build-arg ANI_CLI_WEB=https://gitea.coreplay.eu/Dymas/ani-cli-web.git \
  -t ani-cli-web .

Run:

docker run --rm \
  -p 8421:8421 \
  -e UPDATE_ON_START=true \
  -e USER_UID="$(id -u)" \
  -e USER_GID="$(id -g)" \
  -v "$(pwd)/downloads:/downloads" \
  -v "$(pwd)/.ani-cli-web:/app/.ani-cli-web" \
  ani-cli-web

Compose:

docker compose up --build -d

Use host ownership for mounted downloads:

USER_UID="$(id -u)" USER_GID="$(id -g)" docker compose up --build -d

Container notes:

  • ani-cli is installed at /usr/local/bin/ani-cli.
  • yt-dlp is installed at /usr/local/bin/yt-dlp from the latest GitHub release during build.
  • ani-cli-web runs from /app.
  • Downloads go to /downloads.
  • App state lives in /app/.ani-cli-web.
  • The container binds 0.0.0.0:8421.
  • UPDATE_ON_START=true runs sudo ani-cli --update before startup.
  • USER_UID and USER_GID keep mounted files from being owned by root.

What It Does

  • Search AllAnime in sub or dub.
  • Queue downloads with editable folder, quality, season, and episode range.
  • Save defaults on /config.
  • Track shows on /watchlist.
  • Refresh watchlist episode counts in the background.
  • Schedule periodic watchlist refresh jobs.
  • Send Discord webhook notifications for refresh discoveries and failures.
  • Send Discord webhook notifications for completed downloads.
  • Cache watchlist thumbnails locally.
  • Sync successful downloads back into the watchlist.
  • Keep queue and watchlist data in SQLite under .ani-cli-web/.

Pages

Search Page

Path: /

Use it to:

  1. Search for an anime.
  2. Load episodes.
  3. Adjust folder, quality, season, and episode range.
  4. Add the show to the queue or watchlist.

Highlights:

  • Live queue view with automatic polling.
  • Queue log shows newest lines first.
  • Search polling avoids overlapping requests and stale responses.

Config Page

Path: /config

Use it to set:

  1. Default download folder.
  2. Default sub or dub mode.
  3. Default quality.
  4. Scheduled watchlist refresh enable or disable.
  5. Scheduled refresh period in minutes.
  6. Delay between shows during Refresh All.
  7. Discord webhook URL.
  8. Discord notification events plus a test action.

Saved settings are written to .ani-cli-web/config.json.

Discord notification events:

  • Download completed.
  • Refresh finished and found new episodes.
  • Refresh failed or finished with per-show refresh errors.
  • Refresh interrupted by restart or shutdown.
  • Unexpected runtime errors.

Watchlist

Path: /watchlist

Categories:

  • Watching
  • Planned
  • Finished
  • Dropped

Ways to add shows:

  1. From the Search page.
  2. Manually on /watchlist.

Main behavior:

  1. Refresh updates one show.
  2. Refresh All runs in the background.
  3. Open jumps back to Search.
  4. Clicking the title opens the source page.
  5. Remove deletes the entry and cached poster.
  6. Each card has an inline category selector.
  7. Summary cards show total tracked shows plus sub and dub counts.
  8. Pagination shows 30 cards per page.

Status behavior:

  • Sub and Dub pills turn green when that track looks complete.
  • Cards with both sub and dub fully ready get a thin green border.
  • Finished cards show a small check badge.
  • Green check means downloaded through the app.
  • Gray check means finished manually.

Download sync:

  • Existing watchlist entries move to Finished when a download succeeds.
  • Missing entries are added to Finished.
  • If ani-cli resolves a different show_id, the app can reconcile a unique title match instead of creating a duplicate finished row.

Thumbnail behavior:

  • Thumbnails are served from the local /api/watchlist/thumb/... route.
  • Cached thumbnails are reused.
  • Missing covers are warmed in small batches.
  • Failed lookups back off before retrying.
  • You can force Reload or use Upload when no thumbnail exists.

Refresh behavior:

  • Per-show refreshes are queued in the background.
  • Queued refresh state survives restarts.
  • Duplicate refresh queueing is avoided.
  • Scheduled refresh runs log start, stop, success, and failure to stdout.
  • Refresh All works one show at a time with a configurable delay.
  • When a Discord webhook is configured, new-episode refresh results include one notification card per anime with sub and dub counts and a cached thumbnail attachment when available.

Access Guard

By default the app only serves loopback clients such as 127.0.0.1 and ::1.

To allow LAN or remote access:

ANI_CLI_WEB_ALLOW_REMOTE=1
ANI_CLI_WEB_REMOTE_TOKEN=choose-a-long-random-token

Non-local clients must send one of:

  • Authorization: Bearer <token>
  • X-AniCli-Web-Token: <token>

Browser bootstrap also works:

http://YOUR-HOST:8421/?token=your-token-here

The app stores the token in an HTTP-only cookie and redirects to the clean URL.

Download Queue

The queue is stored in SQLite and shown 10 jobs at a time.

Available actions:

  • Retry
  • Cancel
  • Remove
  • Retry failed
  • Remove finished

Downloads are staged first, then moved into a media-friendly layout:

ANI_CLI_DOWNLOAD_DIR/anime_name/Season 01/anime_name - S01E01.mp4

Runtime State

All local state is stored under:

ani-cli-web/.ani-cli-web/

Main contents:

  • config.json
  • state.sqlite3
  • thumbnails/
  • AniDB title cache data
  • staging files for active downloads

Legacy state from older locations is migrated on startup.

Configuration

Environment variables:

  • ANI_CLI_BIN: path or command name for ani-cli
  • ANI_CLI_DOWNLOAD_DIR: initial default download folder
  • ANI_CLI_MODE: initial mode, sub or dub
  • ANI_CLI_QUALITY: initial quality such as best, 1080, or 720
  • ANI_CLI_WEB_HOST: server host, default 127.0.0.1
  • ANI_CLI_WEB_PORT: server port, default 8421
  • ANI_CLI_WEB_ALLOW_REMOTE: allow non-loopback clients
  • ANI_CLI_WEB_REMOTE_TOKEN: shared secret for remote access
  • ANI_CLI_WEB_DEBUG: enable debug logging
  • ANI_CLI_WEB_STATE_ROOT: override the project-local state directory
  • UPDATE_ON_START: run sudo ani-cli --update in the container before startup
  • USER_UID and USER_GID: run the container as a specific host user/group

Saved config fields include:

  • discord_webhook_url
  • discord_webhook_events
  • watchlist_auto_refresh_enabled
  • watchlist_auto_refresh_minutes
  • watchlist_refresh_delay_seconds

Testing

Run the regression suite with:

python3 -m unittest test_app.py

Notes

  • Runtime boot is lazy. Importing app.py does not immediately start worker threads.
  • Queue and watchlist data share the same SQLite database: .ani-cli-web/state.sqlite3.
  • Runtime folders such as .ani-cli-web/ and downloads/ are ignored by git.
  • The Docker image clones both upstream repositories during build.