# Alfred Media Organizer ๐ŸŽฌ An AI-powered agent for managing your local media library with natural language. Search, download, and organize movies and TV shows effortlessly through a conversational interface. [![Python 3.14](https://img.shields.io/badge/python-3.14-blue.svg)](https://www.python.org/downloads/) [![uv](https://img.shields.io/badge/dependency%20manager-uv-purple)](https://github.com/astral-sh/uv) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Code style: ruff](https://img.shields.io/badge/code%20style-ruff-000000.svg)](https://github.com/astral-sh/ruff) ## โœจ Features - ๐Ÿค– **Natural Language Interface** โ€” Talk to your media library in plain language - ๐Ÿ” **Smart Search** โ€” Find movies and TV shows via TMDB with rich metadata - ๐Ÿ“ฅ **Torrent Integration** โ€” Search and download via qBittorrent - ๐Ÿง  **Contextual Memory** โ€” Remembers your preferences and conversation history - ๐Ÿ“ **Auto-Organization** โ€” Moves and renames media files, resolves destinations, handles subtitles - ๐ŸŽž๏ธ **Subtitle Pipeline** โ€” Identifies, matches, and places subtitle tracks automatically - ๐Ÿ”„ **Workflow Engine** โ€” YAML-defined multi-step workflows (e.g. `organize_media`) - ๐ŸŒ **OpenAI-Compatible API** โ€” Works with any OpenAI-compatible client (LibreChat, OpenWebUI, etc.) - ๐Ÿ”’ **Secure by Default** โ€” Auto-generated secrets and encrypted credentials ## ๐Ÿ—๏ธ Architecture Built with **Domain-Driven Design (DDD)** principles for clean separation of concerns: ``` alfred/ โ”œโ”€โ”€ agent/ # AI agent orchestration โ”‚ โ”œโ”€โ”€ llm/ # LLM clients (Ollama, DeepSeek) โ”‚ โ”œโ”€โ”€ tools/ # Tool implementations (api, filesystem, language) โ”‚ โ””โ”€โ”€ workflows/ # YAML-defined multi-step workflows โ”œโ”€โ”€ application/ # Use cases & DTOs โ”‚ โ”œโ”€โ”€ movies/ # Movie search โ”‚ โ”œโ”€โ”€ torrents/ # Torrent management โ”‚ โ””โ”€โ”€ filesystem/ # File operations (move, list, subtitles, seed links) โ”œโ”€โ”€ domain/ # Business logic & entities โ”‚ โ”œโ”€โ”€ media/ # Release parsing โ”‚ โ”œโ”€โ”€ movies/ # Movie entities โ”‚ โ”œโ”€โ”€ tv_shows/ # TV show entities & value objects โ”‚ โ”œโ”€โ”€ subtitles/ # Subtitle scanner, services, knowledge base โ”‚ โ””โ”€โ”€ shared/ # Common value objects (ImdbId, FilePath, FileSize) โ””โ”€โ”€ infrastructure/ # External services & persistence โ”œโ”€โ”€ api/ # External API clients (TMDB, qBittorrent, Knaben) โ”œโ”€โ”€ filesystem/ # File manager (hard-link based, path-traversal safe) โ”œโ”€โ”€ persistence/ # Three-tier memory (LTM/STM/Episodic) + JSON repositories โ””โ”€โ”€ subtitle/ # Subtitle infrastructure ``` ### Key flows **Agent execution:** `agent.step(user_input)` โ†’ LLM call โ†’ if tool_calls, execute each via registry โ†’ loop until no tool calls or `max_tool_iterations` โ†’ return final response. **Media organization workflow:** 1. `resolve_destination` โ€” Determines target folder/filename from release name 2. `move_media` โ€” Hard-links file to library, deletes source 3. `manage_subtitles` โ€” Scans, classifies, and places subtitle tracks 4. `create_seed_links` โ€” Hard-links library file back to torrents/ for continued seeding **Memory tiers:** - **LTM** (`data/memory/ltm.json`) โ€” Persisted config, media library, watchlist - **STM** โ€” Conversation history (capped at `MAX_HISTORY_MESSAGES`) - **Episodic** โ€” Transient search results, active downloads, recent errors ## ๐Ÿš€ Quick Start ### Prerequisites - **Python 3.14+** - **uv** (dependency manager) - **Docker & Docker Compose** (recommended for full stack) - **API Keys:** - TMDB API key ([get one here](https://www.themoviedb.org/settings/api)) - Optional: DeepSeek or other LLM provider keys ### Installation ```bash # Clone the repository git clone https://github.com/francwa/alfred_media_organizer.git cd alfred_media_organizer # Install dependencies make install # Install pre-commit hooks make install-hooks # Bootstrap environment (generates .env with secure secrets) make bootstrap # Validate your .env against the schema make validate # Edit .env with your API keys nano .env ``` ### Running with Docker (Recommended) ```bash # Start all services (LibreChat + Alfred + MongoDB + Ollama) make up # Or start with specific profiles make up p=rag,meili # Include RAG and Meilisearch make up p=qbittorrent # Include qBittorrent make up p=full # Everything # View logs make logs # Stop all services make down ``` The web interface will be available at **http://localhost:3080** ### Running Locally (Development) ```bash uv run uvicorn alfred.app:app --reload --port 8000 ``` ## โš™๏ธ Configuration ### Settings system `settings.toml` is the single source of truth. The schema flows: ``` settings.toml โ†’ settings_schema.py โ†’ settings_bootstrap.py โ†’ .env + .env.make โ†’ settings.py ``` To add a setting: define it in `settings.toml`, run `make bootstrap`, then access via `settings.my_new_setting`. ```bash # First time setup make bootstrap # Validate existing .env against schema make validate # Re-run after settings.toml changes (existing secrets preserved) make bootstrap ``` **Never commit `.env` or `.env.make`** โ€” both are gitignored and auto-generated. ### Key settings (.env) ```bash # --- CORE --- MAX_HISTORY_MESSAGES=10 MAX_TOOL_ITERATIONS=10 # --- LLM --- DEFAULT_LLM_PROVIDER=local # local (Ollama) | deepseek OLLAMA_BASE_URL=http://ollama:11434 OLLAMA_MODEL=llama3.3:latest LLM_TEMPERATURE=0.2 # --- API KEYS --- TMDB_API_KEY=your-tmdb-key # Required for movie/show search DEEPSEEK_API_KEY= # Optional # --- SECURITY (auto-generated) --- JWT_SECRET= CREDS_KEY= MONGO_PASSWORD= ``` ## ๐Ÿณ Docker Services ### Docker Profiles | Profile | Extra services | Use case | |---------|---------------|----------| | (default) | โ€” | LibreChat + Alfred + MongoDB + Ollama | | `meili` | Meilisearch | Fast full-text search | | `rag` | RAG API + VectorDB (PostgreSQL) | Document retrieval | | `qbittorrent` | qBittorrent | Torrent downloads | | `full` | All of the above | Complete setup | ```bash make up # Start (default profile) make up p=full # Start with all services make down # Stop make restart # Restart make logs # Follow logs make ps # Container status ``` ## ๐Ÿ› ๏ธ Available Tools | Tool | Description | |------|-------------| | `find_media_imdb_id` | Search for movies/TV shows on TMDB by title | | `find_torrent` | Search for torrents across multiple indexers | | `get_torrent_by_index` | Get detailed info about a specific result | | `add_torrent_by_index` | Download a torrent from search results | | `add_torrent_to_qbittorrent` | Add a torrent via magnet link directly | | `resolve_destination` | Compute the target library path for a release | | `move_media` | Hard-link a file to its library destination | | `manage_subtitles` | Scan, classify, and place subtitle tracks | | `create_seed_links` | Prepare torrent folder so qBittorrent keeps seeding | | `learn` | Teach Alfred a new pattern (release group, naming convention) | | `set_path_for_folder` | Configure folder paths | | `list_folder` | List contents of a configured folder | | `set_language` | Set preferred language for the session | ## ๐Ÿ’ฌ Usage Examples ### Via Web Interface (LibreChat) Navigate to **http://localhost:3080** and start chatting: ``` You: Find Inception in 1080p Alfred: I found 3 torrents for Inception (2010): 1. Inception.2010.1080p.BluRay.x264 (150 seeders) - 2.1 GB 2. Inception.2010.1080p.WEB-DL.x265 (80 seeders) - 1.8 GB 3. Inception.2010.1080p.REMUX (45 seeders) - 25 GB You: Download the first one Alfred: โœ“ Added to qBittorrent! Download started. You: Organize the Breaking Bad S01 download Alfred: โœ“ Resolved destination: /tv_shows/Breaking.Bad/Season 01/ โœ“ Moved 6 episode files โœ“ Placed 6 subtitle tracks (fr, en) โœ“ Seed links created in /torrents/ ``` ### Via API ```bash # Health check curl http://localhost:8000/health # Chat (OpenAI-compatible) curl -X POST http://localhost:8000/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "alfred", "messages": [{"role": "user", "content": "Find The Matrix 4K"}] }' # List models curl http://localhost:8000/v1/models # View memory state curl http://localhost:8000/memory/state ``` Alfred is compatible with any OpenAI-compatible client. Point it at `http://localhost:8000/v1`, model `alfred`. ## ๐Ÿง  Memory System Alfred uses a three-tier memory system: | Tier | Storage | Contents | Lifetime | |------|---------|----------|----------| | **LTM** | JSON file (`data/memory/ltm.json`) | Config, library, watchlist, learned patterns | Permanent | | **STM** | RAM | Conversation history (capped) | Session | | **Episodic** | RAM | Search results, active downloads, errors | Short-lived | ## ๐Ÿงช Development ### Running Tests ```bash # Run full suite (parallel) make test # Run with coverage report make coverage # Run a single file uv run pytest tests/test_agent.py -v # Run a single class uv run pytest tests/test_agent.py::TestAgentInit -v # Skip slow tests uv run pytest -m "not slow" ``` ### Test coverage The suite covers: - **Agent loop** โ€” tool execution, history, max iterations, error handling - **Tool registry** โ€” OpenAI schema format, parameter extraction - **Prompts** โ€” system prompt building, tool inclusion - **Memory** โ€” LTM/STM/Episodic operations, persistence - **Filesystem tools** โ€” path traversal security, folder listing - **File manager** โ€” hard-link, move, seed links (real filesystem, no mocks) - **Application use cases** โ€” `resolve_destination`, `create_seed_links`, `list_folder`, `move_media` - **Domain** โ€” TV show/movie entities, shared value objects (`ImdbId`, `FilePath`, `FileSize`), subtitle scanner - **Repositories** โ€” JSON-backed movie, TV show, subtitle repos - **Bootstrap** โ€” secret generation, idempotency, URI construction - **Workflows** โ€” YAML loading, structure validation - **Configuration** โ€” boundary validation for all settings ### Code Quality ```bash make lint # Ruff check --fix make format # Ruff format + check --fix ``` ### Adding a New Tool 1. Implement the function in `alfred/agent/tools/`: ```python # alfred/agent/tools/api.py def my_new_tool(param: str) -> dict[str, Any]: """Short description shown to the LLM to decide when to call this tool.""" memory = get_memory() # ... return {"status": "ok", "data": result} ``` 2. Register it in `alfred/agent/registry.py`: ```python tool_functions = [ # ... existing tools ... api_tools.my_new_tool, ] ``` The registry auto-generates the JSON schema from the function signature and docstring. ### Adding a Workflow Create a YAML file in `alfred/agent/workflows/`: ```yaml name: my_workflow description: What this workflow does steps: - tool: resolve_destination description: Find where the file should go - tool: move_media description: Move the file ``` Workflows are loaded automatically at startup. ### Version Management ```bash # Must be on main branch make patch # 0.1.7 โ†’ 0.1.8 make minor # 0.1.7 โ†’ 0.2.0 make major # 0.1.7 โ†’ 1.0.0 ``` ## ๐Ÿ“š API Reference ### Endpoints | Method | Path | Description | |--------|------|-------------| | `GET` | `/health` | Health check | | `GET` | `/v1/models` | List models (OpenAI-compatible) | | `POST` | `/v1/chat/completions` | Chat (OpenAI-compatible, streaming supported) | | `GET` | `/memory/state` | Full memory dump (debug) | | `POST` | `/memory/clear-session` | Clear STM + Episodic | | `GET` | `/memory/episodic/search-results` | Current search results | ## ๐Ÿ”ง Troubleshooting ### Agent doesn't respond 1. Check API keys in `.env` 2. Verify the LLM is running: ```bash docker logs alfred-ollama docker exec alfred-ollama ollama list ``` 3. Check Alfred logs: `docker logs alfred-core` ### qBittorrent connection failed 1. Verify qBittorrent is running: `docker ps | grep qbittorrent` 2. Check credentials in `.env` (`QBITTORRENT_URL`, `QBITTORRENT_USERNAME`, `QBITTORRENT_PASSWORD`) ### Memory not persisting 1. Check `data/` directory is writable 2. Verify volume mounts in `docker-compose.yaml` ### Bootstrap fails ```bash make validate # Check what's wrong with .env make bootstrap # Regenerate (preserves existing secrets) ``` ### Tests failing ```bash uv run pytest tests/test_failing.py -v --tb=long ``` ## ๐Ÿค Contributing 1. Fork the repository 2. Create a feature branch: `git checkout -b feat/my-feature` 3. Make your changes + add tests 4. Run `make test && make lint && make format` 5. Commit with [Conventional Commits](https://www.conventionalcommits.org/): `feat:`, `fix:`, `docs:`, `refactor:`, `test:`, `chore:`, `infra:` 6. Open a Pull Request ## ๐Ÿ“„ License MIT License โ€” see [LICENSE](LICENSE) file for details. ## ๐Ÿ™ Acknowledgments - [LibreChat](https://github.com/danny-avila/LibreChat) โ€” Chat interface - [Ollama](https://ollama.ai/) โ€” Local LLM runtime - [DeepSeek](https://www.deepseek.com/) โ€” LLM provider - [TMDB](https://www.themoviedb.org/) โ€” Movie & TV database - [qBittorrent](https://www.qbittorrent.org/) โ€” Torrent client - [FastAPI](https://fastapi.tiangolo.com/) โ€” Web framework - [uv](https://github.com/astral-sh/uv) โ€” Fast Python package manager ---

Made with โค๏ธ by Francwa