e45465d52d
Destination resolution
- Replace the single ResolveDestinationUseCase with four dedicated
functions, one per release type:
resolve_season_destination (pack season, folder move)
resolve_episode_destination (single episode, file move)
resolve_movie_destination (movie, file move)
resolve_series_destination (multi-season pack, folder move)
- Each returns a dedicated DTO carrying only the fields relevant to
that release type — no more polymorphic ResolvedDestination with
half the fields unused depending on the case.
- Looser series folder matching: exact computed-name match is reused
silently; any deviation (different group, multiple candidates) now
prompts the user with all options including the computed name.
Agent tools
- Four new tools wrapping the use cases above; old resolve_destination
removed from the registry.
- New move_to_destination tool: create_folder + move, chained — used
after a resolve_* call to perform the actual relocation.
- Low-level filesystem_operations module (create_folder, move via mv)
for instant same-FS renames (ZFS).
Prompt & persona
- New PromptBuilder (alfred/agent/prompt.py) replacing prompts.py:
identity + personality block, situational expressions, memory
schema, episodic/STM/config context, tool catalogue.
- Per-user expression system: knowledge/users/common.yaml +
{username}.yaml are merged at runtime; one phrase per situation
(greeting/success/error/...) is sampled into the system prompt.
qBittorrent integration
- Credentials now come from settings (qbittorrent_url/username/password)
instead of hardcoded defaults.
- New client methods: find_by_name, set_location, recheck — the trio
needed to update a torrent's save path and re-verify after a move.
- Host→container path translation settings (qbittorrent_host_path /
qbittorrent_container_path) for docker-mounted setups.
Subtitles
- Identifier: strip parenthesized qualifiers (simplified, brazil…) at
tokenization; new _tokenize_suffix used for the episode_subfolder
pattern so episode-stem tokens no longer pollute language detection.
- Placer: extract _build_dest_name so it can be reused by the new
dry_run path in ManageSubtitlesUseCase.
- Knowledge: add yue, ell, ind, msa, rus, vie, heb, tam, tel, tha,
hin, ukr; add 'fre' to fra; add 'simplified'/'traditional' to zho.
Misc
- LTM workspace: add 'trash' folder slot.
- Default LLM provider switched to deepseek.
- testing/debug_release.py: CLI to parse a release, hit TMDB, and
dry-run the destination resolution end-to-end.
83 lines
2.6 KiB
Python
83 lines
2.6 KiB
Python
"""Shared configuration loader — reads build config from pyproject.toml."""
|
|
|
|
import re
|
|
import tomllib
|
|
from pathlib import Path
|
|
from typing import NamedTuple
|
|
|
|
|
|
class BuildConfig(NamedTuple):
|
|
"""Build configuration extracted from pyproject.toml."""
|
|
|
|
alfred_version: str
|
|
python_version: str
|
|
python_version_short: str
|
|
image_name: str
|
|
service_name: str
|
|
librechat_version: str
|
|
rag_version: str
|
|
uv_version: str
|
|
|
|
|
|
def extract_python_version(version_string: str) -> tuple[str, str]:
|
|
"""
|
|
Extract Python version from uv dependency string.
|
|
Examples:
|
|
"==3.14.2" -> ("3.14.2", "3.14")
|
|
"^3.14.2" -> ("3.14.2", "3.14")
|
|
"""
|
|
clean = re.sub(r"^[=^~><]+", "", version_string.strip())
|
|
parts = clean.split(".")
|
|
if len(parts) >= 2:
|
|
return clean, f"{parts[0]}.{parts[1]}"
|
|
raise ValueError(f"Invalid Python version format: {version_string}")
|
|
|
|
|
|
def load_build_config(base_dir: Path | None = None) -> BuildConfig:
|
|
"""Load build configuration from pyproject.toml."""
|
|
if base_dir is None:
|
|
base_dir = Path(__file__).resolve().parent.parent
|
|
|
|
toml_path = base_dir / "pyproject.toml"
|
|
if not toml_path.exists():
|
|
raise FileNotFoundError(f"pyproject.toml not found: {toml_path}")
|
|
|
|
with open(toml_path, "rb") as f:
|
|
data = tomllib.load(f)
|
|
|
|
project = data["project"]
|
|
alfred = data["tool"]["alfred"]
|
|
|
|
python_full, python_short = extract_python_version(project["requires-python"])
|
|
|
|
return BuildConfig(
|
|
alfred_version=project["version"],
|
|
python_version=python_full,
|
|
python_version_short=python_short,
|
|
image_name=alfred["image_name"],
|
|
service_name=alfred["service_name"],
|
|
librechat_version=alfred["librechat_version"],
|
|
rag_version=alfred["rag_version"],
|
|
uv_version=alfred["uv_version"],
|
|
)
|
|
|
|
|
|
def write_env_make(config: BuildConfig, base_dir: Path | None = None) -> None:
|
|
"""Write .env.make file for Makefile."""
|
|
if base_dir is None:
|
|
base_dir = Path(__file__).resolve().parent.parent
|
|
|
|
lines = [
|
|
"# Auto-generated from pyproject.toml — do not edit manually",
|
|
f"export ALFRED_VERSION={config.alfred_version}",
|
|
f"export PYTHON_VERSION={config.python_version}",
|
|
f"export IMAGE_NAME={config.image_name}",
|
|
f"export SERVICE_NAME={config.service_name}",
|
|
f"export LIBRECHAT_VERSION={config.librechat_version}",
|
|
f"export RAG_VERSION={config.rag_version}",
|
|
f"export UV_VERSION={config.uv_version}",
|
|
]
|
|
|
|
env_make_path = base_dir / ".env.make"
|
|
env_make_path.write_text("\n".join(lines) + "\n")
|