# ani-cli web Small local web UI for a system-wide `ani-cli` install. Current version: `0.38.0` ## Project Layout ```text 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. queue_page.py Queue 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: ```sh ./ani-cli-web ``` Run with debug logging: ```sh ./ani-cli-web --debug ``` Open: ```text 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: ```text 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: ```sh docker build -t ani-cli-web . ``` Build with a custom `ani-cli` branch: ```sh 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: ```sh 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: ```sh docker compose up --build -d ``` Use host ownership for mounted downloads: ```sh 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: - Search stays focused on finding shows, loading episodes, and queueing new downloads. - Added-to-queue notices now point to the dedicated Queue page for progress tracking. - Search request guards still avoid stale responses overwriting newer results. - The shared sidebar menu now shows one navigation entry per line for cleaner balance across all pages. ### Queue Page Path: `/queue` Use it to: 1. Monitor active, pending, failed, and finished download jobs. 2. Inspect recent per-job logs with newest lines first. 3. Retry failed jobs. 4. Remove finished jobs. 5. Cancel running or pending jobs. Highlights: - The queue list loads once, then only visible pending or running jobs are refreshed every 2.5 seconds. - Terminal jobs (`done`, `failed`, `canceled`) stop log polling automatically. - Dedicated queue pagination and cleanup controls. - Search and queue workflows are now separated into distinct pages. - Each queue job card can be collapsed, and finished jobs start collapsed by default to keep the page compact. ### 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. `Download all` expands an inline picker for `Sub` or `Dub` and then queues every currently available episode directly. 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. 9. `Alternative titles` expands English alternate titles from AniDB when the watchlist refresh can match the show to an AniDB entry. 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: ```sh 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 ` - `X-AniCli-Web-Token: ` Browser bootstrap also works: ```text http://YOUR-HOST:8421/?token=your-token-here ``` For normal browser visits, the app now shows a sign-in page automatically when the token cookie is missing. The app stores the token in an HTTP-only cookie and redirects to the clean URL. The cookie is persistent, so you usually only need to sign in once per browser. ## Download Queue Path: `/queue` The queue is stored in SQLite, shown 10 jobs at a time, and managed from its dedicated page. Available actions: - `Retry` - `Cancel` - `Remove` - `Retry failed` - `Remove finished` Downloads are staged first, then moved into a media-friendly layout: ```text ANI_CLI_DOWNLOAD_DIR/anime_name/Season 01/anime_name - S01E01.mp4 ``` ## Runtime State All local state is stored under: ```text 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: ```sh 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.