ani-cli web
Small local web UI for a system-wide ani-cli install.
Current version: 0.45.4
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.
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:
./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-cliis installed at/usr/local/bin/ani-cli.yt-dlpis installed at/usr/local/bin/yt-dlpfrom the latest GitHub release during build.ani-cli-webruns 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=truerunssudo ani-cli --updatebefore startup.USER_UIDandUSER_GIDkeep mounted files from being owned byroot.
What It Does
- Search AllAnime in
subordub. - Queue downloads with editable folder, quality, media type, season, and episode range.
- Save defaults on
/config. - Track shows on
/watchlist. - Refresh watchlist episode counts in the background.
- Schedule periodic watchlist refresh jobs.
- Auto-download newly discovered episodes for
Watchingentries after later refreshes. - Send Discord webhook notifications for refresh discoveries and failures.
- Send Discord webhook notifications for completed downloads.
- Optionally move fully completed watchlist libraries into separate Jellyfin TV and movie folders.
- Cache watchlist thumbnails locally.
- Sync successful downloads back into the watchlist.
- Keep queue and watchlist data in SQLite under
.ani-cli-web/. - Browse host folders directly from Config page path fields when choosing library locations.
Pages
Search Page
Path: /
Use it to:
- Search for an anime.
- Load episodes.
- Adjust folder, quality, media type, season, and episode range.
- 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:
- Monitor active, pending, failed, and finished download jobs.
- Inspect recent per-job logs with newest lines first.
- Retry failed jobs.
- Remove finished jobs.
- 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:
- Default download folder.
- Default
subordubmode. - Default quality.
- Scheduled watchlist refresh enable or disable.
- Scheduled refresh period in minutes.
- Delay between shows during
Refresh All. - Global auto-download enable or disable.
- Default auto-download language and quality.
- Jellyfin automatic handoff enable or disable.
- Jellyfin TV and movie library paths plus a manual trigger action.
- Discord webhook URL.
- Discord notification events plus a test action.
Layout:
Dependenciesstays at the top as a full-width runtime status card.- The remaining Config sections use two columns on larger screens and collapse to one column at
1080pxwidth and below. - Runtime info shows both the
ani-cli-webversion and the installedani-cliversion. - Path fields include
Browsebuttons that open a host-side folder picker modal in the browser.
Saved settings are written to .ani-cli-web/config.json.
Jellyfin handoff:
- Automatic Jellyfin moves run after a watchlist refresh updates an entry and the show now looks complete.
- TV entries only move after the anime is marked
Finishedand at least one downloaded language covers the expected episode total. - Movie entries move after they are downloaded and the local movie library folder exists.
- The manual
Run nowaction on/configscans the whole watchlist and moves anything already eligible.
Discord notification events:
- Download completed.
- Jellyfin move succeeded.
- Jellyfin move failed.
- Refresh finished and found new episodes.
- Refresh failed or finished with per-show refresh errors.
- Refresh interrupted by restart or shutdown.
- Unexpected runtime errors.
Webhook details:
- Download-completed notifications include the full saved file list, split across extra Discord embeds when needed.
- Jellyfin handoff notifications fire when a finished library is moved successfully and also on actionable move failures such as missing targets or move errors.
- Watchlist entries only move to
Finishedautomatically when the downloaded mode itself is complete, and Jellyfin handoff for TV entries follows that same mode-aware completion rule. - Queue jobs that exit successfully but produce no downloaded files are marked as failed with a clearer explanation instead of looking like a generic rename problem.
- Watchlist-driven downloads no longer force
ani-cli -S <index>from API search ordering, which avoids bad result mismatches whenani-cli’s own search order differs from the web app’s result list.
Watchlist
Path: /watchlist
Categories:
WatchingPlannedFinishedDropped
Ways to add shows:
- From the Search page.
- Manually on
/watchlist.
Main behavior:
Refreshupdates one show.Refresh Allruns in the background.Download allexpands an inline picker forSuborDuband then queues every currently available episode directly.- Clicking the title opens the source page.
Removedeletes the entry and cached poster.- Each card has inline category and media-type selectors.
- Summary cards show total tracked shows plus
subanddubcounts. - Pagination shows 30 cards per page.
Alternative titlesopens a centered overlay with English alternate titles from AniDB when the watchlist refresh can match the show to an AniDB entry.Auto-downloadopens a centered overlay with per-series settings for language, quality, series number, optional episode offset, and the sanitized library name used for automatically queued episodes. Auto-download layout note:Name sourceandNameshare a wider half-and-half row inside the modal for easier editing.- Adding a show from Search reuses the current
Seasonfield as the initial auto-download series value for that new watchlist entry. - Each watchlist entry can be tagged as
TVorMovie, which changes where finished downloads are stored.
Episode offset:
- Leave it empty to keep normal episode numbering.
- Set it to a starting number such as
13when a split season continues inside a new folder, so downloaded files are named likeShow Name - S02E13,Show Name - S02E14, and so on.
Status behavior:
SubandDubpills turn green when that track looks complete.- Cards with both
subanddubfully 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
Finishedwhen 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
Reloador useUploadwhen no thumbnail exists.
Refresh behavior:
- Per-show refreshes are queued in the background.
- Queued refresh state survives restarts.
- Duplicate refresh queueing is avoided.
- Auto-download does not trigger when a show is first added to the watchlist.
- Auto-download only runs for entries in
Watchingafter later manual refreshes or scheduled refreshes. - Auto-download uses stored downloaded-episode history per language, so it can queue any still-missing episodes after a later refresh, including backlog episodes that existed before the show was first tracked.
- Scheduled refresh runs log start, stop, success, and failure to stdout.
Refresh Allworks 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_AUTH_USERNAME=admin
ANI_CLI_WEB_AUTH_PASSWORD=choose-a-long-random-password
Non-local browser visits are redirected to a sign-in form automatically when no valid session cookie is present.
API clients can authenticate with:
Authorization: Basic <base64(username:password)>
The browser login stores a persistent HTTP-only session cookie, so you usually only need to sign in once per browser.
You can also set the same credentials directly in .ani-cli-web/config.json:
{
"auth_username": "admin",
"auth_password": "choose-a-long-random-password"
}
Environment variables take precedence over values saved in config.json.
Download Queue
Path: /queue
The queue is stored in SQLite, shown 10 jobs at a time, and managed from its dedicated page.
Available actions:
RetryCancelRemoveRetry failedRemove finished
Downloads are staged first, then moved into a media-friendly layout:
ANI_CLI_DOWNLOAD_DIR/tv/anime_name/Season 01/anime_name - S01E01.mp4
ANI_CLI_DOWNLOAD_DIR/movie/anime_name/anime_name.mp4
Runtime State
All local state is stored under:
ani-cli-web/.ani-cli-web/
Main contents:
config.jsonstate.sqlite3thumbnails/- 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 forani-cliANI_CLI_DOWNLOAD_DIR: initial default download folderANI_CLI_MODE: initial mode,subordubANI_CLI_QUALITY: initial quality such asbest,1080, or720ANI_CLI_WEB_HOST: server host, default127.0.0.1ANI_CLI_WEB_PORT: server port, default8421ANI_CLI_WEB_ALLOW_REMOTE: allow non-loopback clientsANI_CLI_WEB_AUTH_USERNAME: single remote-login usernameANI_CLI_WEB_AUTH_PASSWORD: single remote-login passwordANI_CLI_WEB_DEBUG: enable debug loggingANI_CLI_WEB_STATE_ROOT: override the project-local state directoryUPDATE_ON_START: runsudo ani-cli --updatein the container before startupUSER_UIDandUSER_GID: run the container as a specific host user/group
Saved config fields include:
auth_usernameauth_passwordjellyfin_sync_enabledjellyfin_tv_dirjellyfin_movie_dirdiscord_webhook_urldiscord_webhook_eventswatchlist_auto_refresh_enabledwatchlist_auto_refresh_minuteswatchlist_refresh_delay_seconds
Testing
Run the regression suite with:
python3 -m unittest test_app.py
Notes
- Runtime boot is lazy. Importing
app.pydoes 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/anddownloads/are ignored by git. - The Docker image clones both upstream repositories during build.