diff --git a/CHANGELOG.md b/CHANGELOG.md
index f25493d..4c1d99a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,9 @@
# Changelog
+## 0.40.6 - 2026-05-25
+
+- Fixed the Config page `Dependencies` panel so it stretches across the full settings width, centered its dependency badges, and added the installed `ani-cli` version to the runtime info block.
+
## 0.40.5 - 2026-05-25
- Moved the Config page `Dependencies` panel to the top as a full-width card and changed the remaining settings panels to a two-column layout that collapses back to one column at `1080px` and below.
diff --git a/README.md b/README.md
index e2c6d72..6439a6d 100644
--- a/README.md
+++ b/README.md
@@ -191,6 +191,7 @@ Layout:
- `Dependencies` stays 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 `1080px` width and below.
+- Runtime info shows both the `ani-cli-web` version and the installed `ani-cli` version.
Saved settings are written to `.ani-cli-web/config.json`.
diff --git a/VERSION b/VERSION
index d3568f3..78deb9e 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-0.40.5
+0.40.6
diff --git a/app_support.py b/app_support.py
index acaa4ac..0d44e33 100644
--- a/app_support.py
+++ b/app_support.py
@@ -19,7 +19,7 @@ from uuid import uuid4
PROJECT_ROOT = Path(__file__).resolve().parent
ANI_CLI = os.environ.get("ANI_CLI_BIN") or shutil.which("ani-cli") or "ani-cli"
APP_NAME = "ani-cli-web"
-VERSION = "0.40.5"
+VERSION = "0.40.6"
ALLANIME_BASE = "allanime.day"
ALLANIME_API = f"https://api.{ALLANIME_BASE}"
ALLANIME_REFERER = "https://allmanga.to"
diff --git a/config_page.py b/config_page.py
index e1b9ab0..0ad774b 100644
--- a/config_page.py
+++ b/config_page.py
@@ -134,7 +134,7 @@ CONFIG_HTML = r"""
gap: 20px;
min-width: 0;
align-content: start;
- justify-items: start;
+ justify-items: stretch;
}
h1, h2, p { margin: 0; }
h1 {
@@ -280,17 +280,22 @@ CONFIG_HTML = r"""
flex-wrap: wrap;
align-items: center;
}
+ .deps {
+ justify-items: center;
+ text-align: center;
+ }
+ .deps .chips {
+ justify-content: center;
+ }
.settings-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 18px;
align-items: start;
+ width: 100%;
}
.settings-main, .deps {
- width: min(1180px, 100%);
- }
- .deps {
- width: min(1180px, 100%);
+ width: 100%;
}
@media (max-width: 900px) {
.app { grid-template-columns: 1fr; }
@@ -325,9 +330,10 @@ CONFIG_HTML = r"""
- Runtime info
+ Runtime info
Loading version...
+
Detecting ani-cli version...
Saved settings live in `.ani-cli-web/config.json` inside this project.
@@ -558,6 +564,7 @@ CONFIG_HTML = r"""
async function loadVersion() {
const data = await api("/api/version");
$("versionLine").textContent = `${data.name} ${data.version}`;
+ $("aniCliVersionLine").textContent = `ani-cli ${data.ani_cli_version || "Unavailable"}`;
}
$("saveConfigBtn").addEventListener("click", saveConfig);
diff --git a/http_handler.py b/http_handler.py
index 29687f7..611156e 100644
--- a/http_handler.py
+++ b/http_handler.py
@@ -6,7 +6,9 @@ import json
import mimetypes
import os
import shutil
+import subprocess
import traceback
+from functools import lru_cache
from dataclasses import dataclass
from http.cookies import SimpleCookie
from http import HTTPStatus
@@ -72,6 +74,22 @@ def dependency_status():
return result
+@lru_cache(maxsize=1)
+def installed_ani_cli_version():
+ try:
+ completed = subprocess.run(
+ [ANI_CLI, "--version"],
+ check=True,
+ capture_output=True,
+ text=True,
+ timeout=6,
+ )
+ except (OSError, subprocess.SubprocessError):
+ return "Unavailable"
+ version = str(completed.stdout or completed.stderr or "").strip()
+ return version or "Unavailable"
+
+
class Handler(BaseHTTPRequestHandler):
server_version = "AniCliWeb/1.0"
remote_token_cookie_name = "ani_cli_web_token"
@@ -380,7 +398,7 @@ class Handler(BaseHTTPRequestHandler):
elif parsed.path == "/api/config":
self.json(Handler._context(self).get_config_snapshot())
elif parsed.path == "/api/version":
- self.json({"name": APP_NAME, "version": VERSION})
+ self.json({"name": APP_NAME, "version": VERSION, "ani_cli_version": installed_ani_cli_version()})
elif parsed.path == "/api/dependencies":
self.json(dependency_status())
elif parsed.path == "/api/search":
diff --git a/test_app.py b/test_app.py
index 88b3c6a..d6a887e 100644
--- a/test_app.py
+++ b/test_app.py
@@ -1748,6 +1748,14 @@ class HandlerRouteTests(unittest.TestCase):
self.assertEqual(handler.json_payload["auto_download_mode"], "dub")
self.assertEqual(handler.json_payload["auto_download_quality"], "720")
+ def test_version_route_includes_ani_cli_version(self):
+ handler = DummyHandler("/api/version")
+ APP.Handler.do_GET(handler)
+ self.assertEqual(handler.json_status, HTTPStatus.OK)
+ self.assertEqual(handler.json_payload["name"], APP.APP_NAME)
+ self.assertEqual(handler.json_payload["version"], APP.VERSION)
+ self.assertIn("ani_cli_version", handler.json_payload)
+
def test_config_post_accepts_discord_webhook_fields(self):
body = b'{"download_dir":"/tmp/example","mode":"sub","quality":"best","discord_webhook_url":"https://discord.example/webhook","discord_webhook_events":["runtime_error","watchlist_refresh_failed","bad"]}'
handler = DummyHandler("/api/config", body=body, content_length=len(body))
@@ -1964,6 +1972,9 @@ class TemplateHelperTests(unittest.TestCase):
self.assertIn('
', APP.CONFIG_HTML)
self.assertLess(APP.CONFIG_HTML.index('
'), APP.CONFIG_HTML.index('
'))
self.assertIn("@media (max-width: 1080px)", APP.CONFIG_HTML)
+ self.assertIn("justify-items: stretch;", APP.CONFIG_HTML)
+ self.assertIn('id="aniCliVersionLine"', APP.CONFIG_HTML)
+ self.assertIn('ani-cli ${data.ani_cli_version || "Unavailable"}', APP.CONFIG_HTML)
def test_search_page_sends_watchlist_auto_download_series(self):
self.assertIn('auto_download_series: $("seasonInput").value || "1"', APP.INDEX_HTML)