From 1f88e99e8b21eaa1ae1166aa974a166bb1bd6c6c Mon Sep 17 00:00:00 2001 From: Francwa Date: Wed, 24 Dec 2025 07:50:09 +0100 Subject: [PATCH 01/22] infra: reorganized repo --- brain/.bumpversion.toml => .bumpversion.toml | 0 brain/.dockerignore => .dockerignore | 0 brain/Dockerfile => Dockerfile | 0 brain/README.md => README.md | 0 {brain => alfred}/agent/__init__.py | 0 {brain => alfred}/agent/agent.py | 0 {brain => alfred}/agent/config.py | 0 {brain => alfred}/agent/llm/__init__.py | 0 {brain => alfred}/agent/llm/deepseek.py | 0 {brain => alfred}/agent/llm/exceptions.py | 0 {brain => alfred}/agent/llm/ollama.py | 0 {brain => alfred}/agent/parameters.py | 0 {brain => alfred}/agent/prompts.py | 0 {brain => alfred}/agent/registry.py | 0 {brain => alfred}/agent/tools/__init__.py | 0 {brain => alfred}/agent/tools/api.py | 0 {brain => alfred}/agent/tools/filesystem.py | 0 {brain => alfred}/agent/tools/language.py | 0 {brain => alfred}/app.py | 0 {brain => alfred}/application/__init__.py | 0 {brain => alfred}/application/filesystem/__init__.py | 0 {brain => alfred}/application/filesystem/dto.py | 0 {brain => alfred}/application/filesystem/list_folder.py | 0 {brain => alfred}/application/filesystem/set_folder_path.py | 0 {brain => alfred}/application/movies/__init__.py | 0 {brain => alfred}/application/movies/dto.py | 0 {brain => alfred}/application/movies/search_movie.py | 0 {brain => alfred}/application/torrents/__init__.py | 0 {brain => alfred}/application/torrents/add_torrent.py | 0 {brain => alfred}/application/torrents/dto.py | 0 {brain => alfred}/application/torrents/search_torrents.py | 0 {brain => alfred}/domain/__init__.py | 0 {brain => alfred}/domain/movies/__init__.py | 0 {brain => alfred}/domain/movies/entities.py | 0 {brain => alfred}/domain/movies/exceptions.py | 0 {brain => alfred}/domain/movies/repositories.py | 0 {brain => alfred}/domain/movies/services.py | 0 {brain => alfred}/domain/movies/value_objects.py | 0 {brain => alfred}/domain/shared/__init__.py | 0 {brain => alfred}/domain/shared/exceptions.py | 0 {brain => alfred}/domain/shared/value_objects.py | 0 {brain => alfred}/domain/subtitles/__init__.py | 0 {brain => alfred}/domain/subtitles/entities.py | 0 {brain => alfred}/domain/subtitles/exceptions.py | 0 {brain => alfred}/domain/subtitles/repositories.py | 0 {brain => alfred}/domain/subtitles/services.py | 0 {brain => alfred}/domain/subtitles/value_objects.py | 0 {brain => alfred}/domain/tv_shows/__init__.py | 0 {brain => alfred}/domain/tv_shows/entities.py | 0 {brain => alfred}/domain/tv_shows/exceptions.py | 0 {brain => alfred}/domain/tv_shows/repositories.py | 0 {brain => alfred}/domain/tv_shows/services.py | 0 {brain => alfred}/domain/tv_shows/value_objects.py | 0 {brain => alfred}/infrastructure/__init__.py | 0 {brain => alfred}/infrastructure/api/__init__.py | 0 {brain => alfred}/infrastructure/api/knaben/__init__.py | 0 {brain => alfred}/infrastructure/api/knaben/client.py | 0 {brain => alfred}/infrastructure/api/knaben/dto.py | 0 {brain => alfred}/infrastructure/api/knaben/exceptions.py | 0 {brain => alfred}/infrastructure/api/qbittorrent/__init__.py | 0 {brain => alfred}/infrastructure/api/qbittorrent/client.py | 0 {brain => alfred}/infrastructure/api/qbittorrent/dto.py | 0 {brain => alfred}/infrastructure/api/qbittorrent/exceptions.py | 0 {brain => alfred}/infrastructure/api/tmdb/__init__.py | 0 {brain => alfred}/infrastructure/api/tmdb/client.py | 0 {brain => alfred}/infrastructure/api/tmdb/dto.py | 0 {brain => alfred}/infrastructure/api/tmdb/exceptions.py | 0 {brain => alfred}/infrastructure/filesystem/__init__.py | 0 {brain => alfred}/infrastructure/filesystem/exceptions.py | 0 {brain => alfred}/infrastructure/filesystem/file_manager.py | 0 {brain => alfred}/infrastructure/filesystem/organizer.py | 0 {brain => alfred}/infrastructure/persistence/__init__.py | 0 {brain => alfred}/infrastructure/persistence/context.py | 0 {brain => alfred}/infrastructure/persistence/json/__init__.py | 0 .../infrastructure/persistence/json/movie_repository.py | 0 .../infrastructure/persistence/json/subtitle_repository.py | 0 .../infrastructure/persistence/json/tvshow_repository.py | 0 {brain => alfred}/infrastructure/persistence/memory.py | 0 {brain/docs => docs}/architecture_diagram.md | 0 {brain/docs => docs}/class_diagram.md | 0 {brain/docs => docs}/component_diagram.md | 0 {brain/docs => docs}/flowchart.md | 0 {brain/docs => docs}/sequence_diagram.md | 0 {brain/manifests => manifests}/add_torrent_by_index.json | 0 {brain/manifests => manifests}/find_media_imdb_id.json | 0 {brain/manifests => manifests}/find_torrent.json | 0 {brain/manifests => manifests}/set_language.json | 0 brain/poetry.lock => poetry.lock | 0 brain/pyproject.toml => pyproject.toml | 0 {brain/tests => tests}/conftest.py | 0 {brain/tests => tests}/test_agent.py | 0 {brain/tests => tests}/test_agent_critical.py | 0 {brain/tests => tests}/test_agent_edge_cases.py | 0 {brain/tests => tests}/test_agent_integration.py | 0 {brain/tests => tests}/test_api.py | 0 {brain/tests => tests}/test_api_clients_integration.py | 0 {brain/tests => tests}/test_api_edge_cases.py | 0 {brain/tests => tests}/test_config_critical.py | 0 {brain/tests => tests}/test_config_edge_cases.py | 0 {brain/tests => tests}/test_domain_edge_cases.py | 0 {brain/tests => tests}/test_llm_clients.py | 0 {brain/tests => tests}/test_memory.py | 0 {brain/tests => tests}/test_memory_edge_cases.py | 0 {brain/tests => tests}/test_prompts.py | 0 {brain/tests => tests}/test_prompts_critical.py | 0 {brain/tests => tests}/test_prompts_edge_cases.py | 0 {brain/tests => tests}/test_registry_critical.py | 0 {brain/tests => tests}/test_registry_edge_cases.py | 0 {brain/tests => tests}/test_repositories.py | 0 {brain/tests => tests}/test_repositories_edge_cases.py | 0 {brain/tests => tests}/test_tools_api.py | 0 {brain/tests => tests}/test_tools_edge_cases.py | 0 {brain/tests => tests}/test_tools_filesystem.py | 0 113 files changed, 0 insertions(+), 0 deletions(-) rename brain/.bumpversion.toml => .bumpversion.toml (100%) rename brain/.dockerignore => .dockerignore (100%) rename brain/Dockerfile => Dockerfile (100%) rename brain/README.md => README.md (100%) rename {brain => alfred}/agent/__init__.py (100%) rename {brain => alfred}/agent/agent.py (100%) rename {brain => alfred}/agent/config.py (100%) rename {brain => alfred}/agent/llm/__init__.py (100%) rename {brain => alfred}/agent/llm/deepseek.py (100%) rename {brain => alfred}/agent/llm/exceptions.py (100%) rename {brain => alfred}/agent/llm/ollama.py (100%) rename {brain => alfred}/agent/parameters.py (100%) rename {brain => alfred}/agent/prompts.py (100%) rename {brain => alfred}/agent/registry.py (100%) rename {brain => alfred}/agent/tools/__init__.py (100%) rename {brain => alfred}/agent/tools/api.py (100%) rename {brain => alfred}/agent/tools/filesystem.py (100%) rename {brain => alfred}/agent/tools/language.py (100%) rename {brain => alfred}/app.py (100%) rename {brain => alfred}/application/__init__.py (100%) rename {brain => alfred}/application/filesystem/__init__.py (100%) rename {brain => alfred}/application/filesystem/dto.py (100%) rename {brain => alfred}/application/filesystem/list_folder.py (100%) rename {brain => alfred}/application/filesystem/set_folder_path.py (100%) rename {brain => alfred}/application/movies/__init__.py (100%) rename {brain => alfred}/application/movies/dto.py (100%) rename {brain => alfred}/application/movies/search_movie.py (100%) rename {brain => alfred}/application/torrents/__init__.py (100%) rename {brain => alfred}/application/torrents/add_torrent.py (100%) rename {brain => alfred}/application/torrents/dto.py (100%) rename {brain => alfred}/application/torrents/search_torrents.py (100%) rename {brain => alfred}/domain/__init__.py (100%) rename {brain => alfred}/domain/movies/__init__.py (100%) rename {brain => alfred}/domain/movies/entities.py (100%) rename {brain => alfred}/domain/movies/exceptions.py (100%) rename {brain => alfred}/domain/movies/repositories.py (100%) rename {brain => alfred}/domain/movies/services.py (100%) rename {brain => alfred}/domain/movies/value_objects.py (100%) rename {brain => alfred}/domain/shared/__init__.py (100%) rename {brain => alfred}/domain/shared/exceptions.py (100%) rename {brain => alfred}/domain/shared/value_objects.py (100%) rename {brain => alfred}/domain/subtitles/__init__.py (100%) rename {brain => alfred}/domain/subtitles/entities.py (100%) rename {brain => alfred}/domain/subtitles/exceptions.py (100%) rename {brain => alfred}/domain/subtitles/repositories.py (100%) rename {brain => alfred}/domain/subtitles/services.py (100%) rename {brain => alfred}/domain/subtitles/value_objects.py (100%) rename {brain => alfred}/domain/tv_shows/__init__.py (100%) rename {brain => alfred}/domain/tv_shows/entities.py (100%) rename {brain => alfred}/domain/tv_shows/exceptions.py (100%) rename {brain => alfred}/domain/tv_shows/repositories.py (100%) rename {brain => alfred}/domain/tv_shows/services.py (100%) rename {brain => alfred}/domain/tv_shows/value_objects.py (100%) rename {brain => alfred}/infrastructure/__init__.py (100%) rename {brain => alfred}/infrastructure/api/__init__.py (100%) rename {brain => alfred}/infrastructure/api/knaben/__init__.py (100%) rename {brain => alfred}/infrastructure/api/knaben/client.py (100%) rename {brain => alfred}/infrastructure/api/knaben/dto.py (100%) rename {brain => alfred}/infrastructure/api/knaben/exceptions.py (100%) rename {brain => alfred}/infrastructure/api/qbittorrent/__init__.py (100%) rename {brain => alfred}/infrastructure/api/qbittorrent/client.py (100%) rename {brain => alfred}/infrastructure/api/qbittorrent/dto.py (100%) rename {brain => alfred}/infrastructure/api/qbittorrent/exceptions.py (100%) rename {brain => alfred}/infrastructure/api/tmdb/__init__.py (100%) rename {brain => alfred}/infrastructure/api/tmdb/client.py (100%) rename {brain => alfred}/infrastructure/api/tmdb/dto.py (100%) rename {brain => alfred}/infrastructure/api/tmdb/exceptions.py (100%) rename {brain => alfred}/infrastructure/filesystem/__init__.py (100%) rename {brain => alfred}/infrastructure/filesystem/exceptions.py (100%) rename {brain => alfred}/infrastructure/filesystem/file_manager.py (100%) rename {brain => alfred}/infrastructure/filesystem/organizer.py (100%) rename {brain => alfred}/infrastructure/persistence/__init__.py (100%) rename {brain => alfred}/infrastructure/persistence/context.py (100%) rename {brain => alfred}/infrastructure/persistence/json/__init__.py (100%) rename {brain => alfred}/infrastructure/persistence/json/movie_repository.py (100%) rename {brain => alfred}/infrastructure/persistence/json/subtitle_repository.py (100%) rename {brain => alfred}/infrastructure/persistence/json/tvshow_repository.py (100%) rename {brain => alfred}/infrastructure/persistence/memory.py (100%) rename {brain/docs => docs}/architecture_diagram.md (100%) rename {brain/docs => docs}/class_diagram.md (100%) rename {brain/docs => docs}/component_diagram.md (100%) rename {brain/docs => docs}/flowchart.md (100%) rename {brain/docs => docs}/sequence_diagram.md (100%) rename {brain/manifests => manifests}/add_torrent_by_index.json (100%) rename {brain/manifests => manifests}/find_media_imdb_id.json (100%) rename {brain/manifests => manifests}/find_torrent.json (100%) rename {brain/manifests => manifests}/set_language.json (100%) rename brain/poetry.lock => poetry.lock (100%) rename brain/pyproject.toml => pyproject.toml (100%) rename {brain/tests => tests}/conftest.py (100%) rename {brain/tests => tests}/test_agent.py (100%) rename {brain/tests => tests}/test_agent_critical.py (100%) rename {brain/tests => tests}/test_agent_edge_cases.py (100%) rename {brain/tests => tests}/test_agent_integration.py (100%) rename {brain/tests => tests}/test_api.py (100%) rename {brain/tests => tests}/test_api_clients_integration.py (100%) rename {brain/tests => tests}/test_api_edge_cases.py (100%) rename {brain/tests => tests}/test_config_critical.py (100%) rename {brain/tests => tests}/test_config_edge_cases.py (100%) rename {brain/tests => tests}/test_domain_edge_cases.py (100%) rename {brain/tests => tests}/test_llm_clients.py (100%) rename {brain/tests => tests}/test_memory.py (100%) rename {brain/tests => tests}/test_memory_edge_cases.py (100%) rename {brain/tests => tests}/test_prompts.py (100%) rename {brain/tests => tests}/test_prompts_critical.py (100%) rename {brain/tests => tests}/test_prompts_edge_cases.py (100%) rename {brain/tests => tests}/test_registry_critical.py (100%) rename {brain/tests => tests}/test_registry_edge_cases.py (100%) rename {brain/tests => tests}/test_repositories.py (100%) rename {brain/tests => tests}/test_repositories_edge_cases.py (100%) rename {brain/tests => tests}/test_tools_api.py (100%) rename {brain/tests => tests}/test_tools_edge_cases.py (100%) rename {brain/tests => tests}/test_tools_filesystem.py (100%) diff --git a/brain/.bumpversion.toml b/.bumpversion.toml similarity index 100% rename from brain/.bumpversion.toml rename to .bumpversion.toml diff --git a/brain/.dockerignore b/.dockerignore similarity index 100% rename from brain/.dockerignore rename to .dockerignore diff --git a/brain/Dockerfile b/Dockerfile similarity index 100% rename from brain/Dockerfile rename to Dockerfile diff --git a/brain/README.md b/README.md similarity index 100% rename from brain/README.md rename to README.md diff --git a/brain/agent/__init__.py b/alfred/agent/__init__.py similarity index 100% rename from brain/agent/__init__.py rename to alfred/agent/__init__.py diff --git a/brain/agent/agent.py b/alfred/agent/agent.py similarity index 100% rename from brain/agent/agent.py rename to alfred/agent/agent.py diff --git a/brain/agent/config.py b/alfred/agent/config.py similarity index 100% rename from brain/agent/config.py rename to alfred/agent/config.py diff --git a/brain/agent/llm/__init__.py b/alfred/agent/llm/__init__.py similarity index 100% rename from brain/agent/llm/__init__.py rename to alfred/agent/llm/__init__.py diff --git a/brain/agent/llm/deepseek.py b/alfred/agent/llm/deepseek.py similarity index 100% rename from brain/agent/llm/deepseek.py rename to alfred/agent/llm/deepseek.py diff --git a/brain/agent/llm/exceptions.py b/alfred/agent/llm/exceptions.py similarity index 100% rename from brain/agent/llm/exceptions.py rename to alfred/agent/llm/exceptions.py diff --git a/brain/agent/llm/ollama.py b/alfred/agent/llm/ollama.py similarity index 100% rename from brain/agent/llm/ollama.py rename to alfred/agent/llm/ollama.py diff --git a/brain/agent/parameters.py b/alfred/agent/parameters.py similarity index 100% rename from brain/agent/parameters.py rename to alfred/agent/parameters.py diff --git a/brain/agent/prompts.py b/alfred/agent/prompts.py similarity index 100% rename from brain/agent/prompts.py rename to alfred/agent/prompts.py diff --git a/brain/agent/registry.py b/alfred/agent/registry.py similarity index 100% rename from brain/agent/registry.py rename to alfred/agent/registry.py diff --git a/brain/agent/tools/__init__.py b/alfred/agent/tools/__init__.py similarity index 100% rename from brain/agent/tools/__init__.py rename to alfred/agent/tools/__init__.py diff --git a/brain/agent/tools/api.py b/alfred/agent/tools/api.py similarity index 100% rename from brain/agent/tools/api.py rename to alfred/agent/tools/api.py diff --git a/brain/agent/tools/filesystem.py b/alfred/agent/tools/filesystem.py similarity index 100% rename from brain/agent/tools/filesystem.py rename to alfred/agent/tools/filesystem.py diff --git a/brain/agent/tools/language.py b/alfred/agent/tools/language.py similarity index 100% rename from brain/agent/tools/language.py rename to alfred/agent/tools/language.py diff --git a/brain/app.py b/alfred/app.py similarity index 100% rename from brain/app.py rename to alfred/app.py diff --git a/brain/application/__init__.py b/alfred/application/__init__.py similarity index 100% rename from brain/application/__init__.py rename to alfred/application/__init__.py diff --git a/brain/application/filesystem/__init__.py b/alfred/application/filesystem/__init__.py similarity index 100% rename from brain/application/filesystem/__init__.py rename to alfred/application/filesystem/__init__.py diff --git a/brain/application/filesystem/dto.py b/alfred/application/filesystem/dto.py similarity index 100% rename from brain/application/filesystem/dto.py rename to alfred/application/filesystem/dto.py diff --git a/brain/application/filesystem/list_folder.py b/alfred/application/filesystem/list_folder.py similarity index 100% rename from brain/application/filesystem/list_folder.py rename to alfred/application/filesystem/list_folder.py diff --git a/brain/application/filesystem/set_folder_path.py b/alfred/application/filesystem/set_folder_path.py similarity index 100% rename from brain/application/filesystem/set_folder_path.py rename to alfred/application/filesystem/set_folder_path.py diff --git a/brain/application/movies/__init__.py b/alfred/application/movies/__init__.py similarity index 100% rename from brain/application/movies/__init__.py rename to alfred/application/movies/__init__.py diff --git a/brain/application/movies/dto.py b/alfred/application/movies/dto.py similarity index 100% rename from brain/application/movies/dto.py rename to alfred/application/movies/dto.py diff --git a/brain/application/movies/search_movie.py b/alfred/application/movies/search_movie.py similarity index 100% rename from brain/application/movies/search_movie.py rename to alfred/application/movies/search_movie.py diff --git a/brain/application/torrents/__init__.py b/alfred/application/torrents/__init__.py similarity index 100% rename from brain/application/torrents/__init__.py rename to alfred/application/torrents/__init__.py diff --git a/brain/application/torrents/add_torrent.py b/alfred/application/torrents/add_torrent.py similarity index 100% rename from brain/application/torrents/add_torrent.py rename to alfred/application/torrents/add_torrent.py diff --git a/brain/application/torrents/dto.py b/alfred/application/torrents/dto.py similarity index 100% rename from brain/application/torrents/dto.py rename to alfred/application/torrents/dto.py diff --git a/brain/application/torrents/search_torrents.py b/alfred/application/torrents/search_torrents.py similarity index 100% rename from brain/application/torrents/search_torrents.py rename to alfred/application/torrents/search_torrents.py diff --git a/brain/domain/__init__.py b/alfred/domain/__init__.py similarity index 100% rename from brain/domain/__init__.py rename to alfred/domain/__init__.py diff --git a/brain/domain/movies/__init__.py b/alfred/domain/movies/__init__.py similarity index 100% rename from brain/domain/movies/__init__.py rename to alfred/domain/movies/__init__.py diff --git a/brain/domain/movies/entities.py b/alfred/domain/movies/entities.py similarity index 100% rename from brain/domain/movies/entities.py rename to alfred/domain/movies/entities.py diff --git a/brain/domain/movies/exceptions.py b/alfred/domain/movies/exceptions.py similarity index 100% rename from brain/domain/movies/exceptions.py rename to alfred/domain/movies/exceptions.py diff --git a/brain/domain/movies/repositories.py b/alfred/domain/movies/repositories.py similarity index 100% rename from brain/domain/movies/repositories.py rename to alfred/domain/movies/repositories.py diff --git a/brain/domain/movies/services.py b/alfred/domain/movies/services.py similarity index 100% rename from brain/domain/movies/services.py rename to alfred/domain/movies/services.py diff --git a/brain/domain/movies/value_objects.py b/alfred/domain/movies/value_objects.py similarity index 100% rename from brain/domain/movies/value_objects.py rename to alfred/domain/movies/value_objects.py diff --git a/brain/domain/shared/__init__.py b/alfred/domain/shared/__init__.py similarity index 100% rename from brain/domain/shared/__init__.py rename to alfred/domain/shared/__init__.py diff --git a/brain/domain/shared/exceptions.py b/alfred/domain/shared/exceptions.py similarity index 100% rename from brain/domain/shared/exceptions.py rename to alfred/domain/shared/exceptions.py diff --git a/brain/domain/shared/value_objects.py b/alfred/domain/shared/value_objects.py similarity index 100% rename from brain/domain/shared/value_objects.py rename to alfred/domain/shared/value_objects.py diff --git a/brain/domain/subtitles/__init__.py b/alfred/domain/subtitles/__init__.py similarity index 100% rename from brain/domain/subtitles/__init__.py rename to alfred/domain/subtitles/__init__.py diff --git a/brain/domain/subtitles/entities.py b/alfred/domain/subtitles/entities.py similarity index 100% rename from brain/domain/subtitles/entities.py rename to alfred/domain/subtitles/entities.py diff --git a/brain/domain/subtitles/exceptions.py b/alfred/domain/subtitles/exceptions.py similarity index 100% rename from brain/domain/subtitles/exceptions.py rename to alfred/domain/subtitles/exceptions.py diff --git a/brain/domain/subtitles/repositories.py b/alfred/domain/subtitles/repositories.py similarity index 100% rename from brain/domain/subtitles/repositories.py rename to alfred/domain/subtitles/repositories.py diff --git a/brain/domain/subtitles/services.py b/alfred/domain/subtitles/services.py similarity index 100% rename from brain/domain/subtitles/services.py rename to alfred/domain/subtitles/services.py diff --git a/brain/domain/subtitles/value_objects.py b/alfred/domain/subtitles/value_objects.py similarity index 100% rename from brain/domain/subtitles/value_objects.py rename to alfred/domain/subtitles/value_objects.py diff --git a/brain/domain/tv_shows/__init__.py b/alfred/domain/tv_shows/__init__.py similarity index 100% rename from brain/domain/tv_shows/__init__.py rename to alfred/domain/tv_shows/__init__.py diff --git a/brain/domain/tv_shows/entities.py b/alfred/domain/tv_shows/entities.py similarity index 100% rename from brain/domain/tv_shows/entities.py rename to alfred/domain/tv_shows/entities.py diff --git a/brain/domain/tv_shows/exceptions.py b/alfred/domain/tv_shows/exceptions.py similarity index 100% rename from brain/domain/tv_shows/exceptions.py rename to alfred/domain/tv_shows/exceptions.py diff --git a/brain/domain/tv_shows/repositories.py b/alfred/domain/tv_shows/repositories.py similarity index 100% rename from brain/domain/tv_shows/repositories.py rename to alfred/domain/tv_shows/repositories.py diff --git a/brain/domain/tv_shows/services.py b/alfred/domain/tv_shows/services.py similarity index 100% rename from brain/domain/tv_shows/services.py rename to alfred/domain/tv_shows/services.py diff --git a/brain/domain/tv_shows/value_objects.py b/alfred/domain/tv_shows/value_objects.py similarity index 100% rename from brain/domain/tv_shows/value_objects.py rename to alfred/domain/tv_shows/value_objects.py diff --git a/brain/infrastructure/__init__.py b/alfred/infrastructure/__init__.py similarity index 100% rename from brain/infrastructure/__init__.py rename to alfred/infrastructure/__init__.py diff --git a/brain/infrastructure/api/__init__.py b/alfred/infrastructure/api/__init__.py similarity index 100% rename from brain/infrastructure/api/__init__.py rename to alfred/infrastructure/api/__init__.py diff --git a/brain/infrastructure/api/knaben/__init__.py b/alfred/infrastructure/api/knaben/__init__.py similarity index 100% rename from brain/infrastructure/api/knaben/__init__.py rename to alfred/infrastructure/api/knaben/__init__.py diff --git a/brain/infrastructure/api/knaben/client.py b/alfred/infrastructure/api/knaben/client.py similarity index 100% rename from brain/infrastructure/api/knaben/client.py rename to alfred/infrastructure/api/knaben/client.py diff --git a/brain/infrastructure/api/knaben/dto.py b/alfred/infrastructure/api/knaben/dto.py similarity index 100% rename from brain/infrastructure/api/knaben/dto.py rename to alfred/infrastructure/api/knaben/dto.py diff --git a/brain/infrastructure/api/knaben/exceptions.py b/alfred/infrastructure/api/knaben/exceptions.py similarity index 100% rename from brain/infrastructure/api/knaben/exceptions.py rename to alfred/infrastructure/api/knaben/exceptions.py diff --git a/brain/infrastructure/api/qbittorrent/__init__.py b/alfred/infrastructure/api/qbittorrent/__init__.py similarity index 100% rename from brain/infrastructure/api/qbittorrent/__init__.py rename to alfred/infrastructure/api/qbittorrent/__init__.py diff --git a/brain/infrastructure/api/qbittorrent/client.py b/alfred/infrastructure/api/qbittorrent/client.py similarity index 100% rename from brain/infrastructure/api/qbittorrent/client.py rename to alfred/infrastructure/api/qbittorrent/client.py diff --git a/brain/infrastructure/api/qbittorrent/dto.py b/alfred/infrastructure/api/qbittorrent/dto.py similarity index 100% rename from brain/infrastructure/api/qbittorrent/dto.py rename to alfred/infrastructure/api/qbittorrent/dto.py diff --git a/brain/infrastructure/api/qbittorrent/exceptions.py b/alfred/infrastructure/api/qbittorrent/exceptions.py similarity index 100% rename from brain/infrastructure/api/qbittorrent/exceptions.py rename to alfred/infrastructure/api/qbittorrent/exceptions.py diff --git a/brain/infrastructure/api/tmdb/__init__.py b/alfred/infrastructure/api/tmdb/__init__.py similarity index 100% rename from brain/infrastructure/api/tmdb/__init__.py rename to alfred/infrastructure/api/tmdb/__init__.py diff --git a/brain/infrastructure/api/tmdb/client.py b/alfred/infrastructure/api/tmdb/client.py similarity index 100% rename from brain/infrastructure/api/tmdb/client.py rename to alfred/infrastructure/api/tmdb/client.py diff --git a/brain/infrastructure/api/tmdb/dto.py b/alfred/infrastructure/api/tmdb/dto.py similarity index 100% rename from brain/infrastructure/api/tmdb/dto.py rename to alfred/infrastructure/api/tmdb/dto.py diff --git a/brain/infrastructure/api/tmdb/exceptions.py b/alfred/infrastructure/api/tmdb/exceptions.py similarity index 100% rename from brain/infrastructure/api/tmdb/exceptions.py rename to alfred/infrastructure/api/tmdb/exceptions.py diff --git a/brain/infrastructure/filesystem/__init__.py b/alfred/infrastructure/filesystem/__init__.py similarity index 100% rename from brain/infrastructure/filesystem/__init__.py rename to alfred/infrastructure/filesystem/__init__.py diff --git a/brain/infrastructure/filesystem/exceptions.py b/alfred/infrastructure/filesystem/exceptions.py similarity index 100% rename from brain/infrastructure/filesystem/exceptions.py rename to alfred/infrastructure/filesystem/exceptions.py diff --git a/brain/infrastructure/filesystem/file_manager.py b/alfred/infrastructure/filesystem/file_manager.py similarity index 100% rename from brain/infrastructure/filesystem/file_manager.py rename to alfred/infrastructure/filesystem/file_manager.py diff --git a/brain/infrastructure/filesystem/organizer.py b/alfred/infrastructure/filesystem/organizer.py similarity index 100% rename from brain/infrastructure/filesystem/organizer.py rename to alfred/infrastructure/filesystem/organizer.py diff --git a/brain/infrastructure/persistence/__init__.py b/alfred/infrastructure/persistence/__init__.py similarity index 100% rename from brain/infrastructure/persistence/__init__.py rename to alfred/infrastructure/persistence/__init__.py diff --git a/brain/infrastructure/persistence/context.py b/alfred/infrastructure/persistence/context.py similarity index 100% rename from brain/infrastructure/persistence/context.py rename to alfred/infrastructure/persistence/context.py diff --git a/brain/infrastructure/persistence/json/__init__.py b/alfred/infrastructure/persistence/json/__init__.py similarity index 100% rename from brain/infrastructure/persistence/json/__init__.py rename to alfred/infrastructure/persistence/json/__init__.py diff --git a/brain/infrastructure/persistence/json/movie_repository.py b/alfred/infrastructure/persistence/json/movie_repository.py similarity index 100% rename from brain/infrastructure/persistence/json/movie_repository.py rename to alfred/infrastructure/persistence/json/movie_repository.py diff --git a/brain/infrastructure/persistence/json/subtitle_repository.py b/alfred/infrastructure/persistence/json/subtitle_repository.py similarity index 100% rename from brain/infrastructure/persistence/json/subtitle_repository.py rename to alfred/infrastructure/persistence/json/subtitle_repository.py diff --git a/brain/infrastructure/persistence/json/tvshow_repository.py b/alfred/infrastructure/persistence/json/tvshow_repository.py similarity index 100% rename from brain/infrastructure/persistence/json/tvshow_repository.py rename to alfred/infrastructure/persistence/json/tvshow_repository.py diff --git a/brain/infrastructure/persistence/memory.py b/alfred/infrastructure/persistence/memory.py similarity index 100% rename from brain/infrastructure/persistence/memory.py rename to alfred/infrastructure/persistence/memory.py diff --git a/brain/docs/architecture_diagram.md b/docs/architecture_diagram.md similarity index 100% rename from brain/docs/architecture_diagram.md rename to docs/architecture_diagram.md diff --git a/brain/docs/class_diagram.md b/docs/class_diagram.md similarity index 100% rename from brain/docs/class_diagram.md rename to docs/class_diagram.md diff --git a/brain/docs/component_diagram.md b/docs/component_diagram.md similarity index 100% rename from brain/docs/component_diagram.md rename to docs/component_diagram.md diff --git a/brain/docs/flowchart.md b/docs/flowchart.md similarity index 100% rename from brain/docs/flowchart.md rename to docs/flowchart.md diff --git a/brain/docs/sequence_diagram.md b/docs/sequence_diagram.md similarity index 100% rename from brain/docs/sequence_diagram.md rename to docs/sequence_diagram.md diff --git a/brain/manifests/add_torrent_by_index.json b/manifests/add_torrent_by_index.json similarity index 100% rename from brain/manifests/add_torrent_by_index.json rename to manifests/add_torrent_by_index.json diff --git a/brain/manifests/find_media_imdb_id.json b/manifests/find_media_imdb_id.json similarity index 100% rename from brain/manifests/find_media_imdb_id.json rename to manifests/find_media_imdb_id.json diff --git a/brain/manifests/find_torrent.json b/manifests/find_torrent.json similarity index 100% rename from brain/manifests/find_torrent.json rename to manifests/find_torrent.json diff --git a/brain/manifests/set_language.json b/manifests/set_language.json similarity index 100% rename from brain/manifests/set_language.json rename to manifests/set_language.json diff --git a/brain/poetry.lock b/poetry.lock similarity index 100% rename from brain/poetry.lock rename to poetry.lock diff --git a/brain/pyproject.toml b/pyproject.toml similarity index 100% rename from brain/pyproject.toml rename to pyproject.toml diff --git a/brain/tests/conftest.py b/tests/conftest.py similarity index 100% rename from brain/tests/conftest.py rename to tests/conftest.py diff --git a/brain/tests/test_agent.py b/tests/test_agent.py similarity index 100% rename from brain/tests/test_agent.py rename to tests/test_agent.py diff --git a/brain/tests/test_agent_critical.py b/tests/test_agent_critical.py similarity index 100% rename from brain/tests/test_agent_critical.py rename to tests/test_agent_critical.py diff --git a/brain/tests/test_agent_edge_cases.py b/tests/test_agent_edge_cases.py similarity index 100% rename from brain/tests/test_agent_edge_cases.py rename to tests/test_agent_edge_cases.py diff --git a/brain/tests/test_agent_integration.py b/tests/test_agent_integration.py similarity index 100% rename from brain/tests/test_agent_integration.py rename to tests/test_agent_integration.py diff --git a/brain/tests/test_api.py b/tests/test_api.py similarity index 100% rename from brain/tests/test_api.py rename to tests/test_api.py diff --git a/brain/tests/test_api_clients_integration.py b/tests/test_api_clients_integration.py similarity index 100% rename from brain/tests/test_api_clients_integration.py rename to tests/test_api_clients_integration.py diff --git a/brain/tests/test_api_edge_cases.py b/tests/test_api_edge_cases.py similarity index 100% rename from brain/tests/test_api_edge_cases.py rename to tests/test_api_edge_cases.py diff --git a/brain/tests/test_config_critical.py b/tests/test_config_critical.py similarity index 100% rename from brain/tests/test_config_critical.py rename to tests/test_config_critical.py diff --git a/brain/tests/test_config_edge_cases.py b/tests/test_config_edge_cases.py similarity index 100% rename from brain/tests/test_config_edge_cases.py rename to tests/test_config_edge_cases.py diff --git a/brain/tests/test_domain_edge_cases.py b/tests/test_domain_edge_cases.py similarity index 100% rename from brain/tests/test_domain_edge_cases.py rename to tests/test_domain_edge_cases.py diff --git a/brain/tests/test_llm_clients.py b/tests/test_llm_clients.py similarity index 100% rename from brain/tests/test_llm_clients.py rename to tests/test_llm_clients.py diff --git a/brain/tests/test_memory.py b/tests/test_memory.py similarity index 100% rename from brain/tests/test_memory.py rename to tests/test_memory.py diff --git a/brain/tests/test_memory_edge_cases.py b/tests/test_memory_edge_cases.py similarity index 100% rename from brain/tests/test_memory_edge_cases.py rename to tests/test_memory_edge_cases.py diff --git a/brain/tests/test_prompts.py b/tests/test_prompts.py similarity index 100% rename from brain/tests/test_prompts.py rename to tests/test_prompts.py diff --git a/brain/tests/test_prompts_critical.py b/tests/test_prompts_critical.py similarity index 100% rename from brain/tests/test_prompts_critical.py rename to tests/test_prompts_critical.py diff --git a/brain/tests/test_prompts_edge_cases.py b/tests/test_prompts_edge_cases.py similarity index 100% rename from brain/tests/test_prompts_edge_cases.py rename to tests/test_prompts_edge_cases.py diff --git a/brain/tests/test_registry_critical.py b/tests/test_registry_critical.py similarity index 100% rename from brain/tests/test_registry_critical.py rename to tests/test_registry_critical.py diff --git a/brain/tests/test_registry_edge_cases.py b/tests/test_registry_edge_cases.py similarity index 100% rename from brain/tests/test_registry_edge_cases.py rename to tests/test_registry_edge_cases.py diff --git a/brain/tests/test_repositories.py b/tests/test_repositories.py similarity index 100% rename from brain/tests/test_repositories.py rename to tests/test_repositories.py diff --git a/brain/tests/test_repositories_edge_cases.py b/tests/test_repositories_edge_cases.py similarity index 100% rename from brain/tests/test_repositories_edge_cases.py rename to tests/test_repositories_edge_cases.py diff --git a/brain/tests/test_tools_api.py b/tests/test_tools_api.py similarity index 100% rename from brain/tests/test_tools_api.py rename to tests/test_tools_api.py diff --git a/brain/tests/test_tools_edge_cases.py b/tests/test_tools_edge_cases.py similarity index 100% rename from brain/tests/test_tools_edge_cases.py rename to tests/test_tools_edge_cases.py diff --git a/brain/tests/test_tools_filesystem.py b/tests/test_tools_filesystem.py similarity index 100% rename from brain/tests/test_tools_filesystem.py rename to tests/test_tools_filesystem.py -- 2.49.1 From d10c9160f334248ba0b48eac4fad1b7ca3f1e566 Mon Sep 17 00:00:00 2001 From: Francwa Date: Wed, 24 Dec 2025 08:09:26 +0100 Subject: [PATCH 02/22] infra: renamed broken references to alfred --- .env.example | 8 ++++---- .gitea/workflows/ci.yml | 1 - Dockerfile | 24 ++++++++++++------------ Makefile | 8 ++++---- docker-compose.yml | 18 ++++++++---------- librechat/librechat.yaml | 18 +++++++++--------- pyproject.toml | 2 +- tests/conftest.py | 3 ++- 8 files changed, 40 insertions(+), 42 deletions(-) diff --git a/.env.example b/.env.example index 2ea651e..7a4ccd0 100644 --- a/.env.example +++ b/.env.example @@ -41,10 +41,10 @@ RAG_EMBEDDINGS_MODEL=text-embedding-3-small # OpenAI API Key (required for RAG embeddings) OPENAI_API_KEY=your-openai-api-key-here -# Deepseek API Key (for LLM in agent-brain) +# Deepseek API Key (for LLM in alfred) DEEPSEEK_API_KEY=your-deepseek-api-key-here -# Agent Brain Configuration +# Alfred Configuration # LLM Provider (deepseek or ollama) LLM_PROVIDER=deepseek @@ -52,8 +52,8 @@ LLM_PROVIDER=deepseek # Memory storage directory (inside container) MEMORY_STORAGE_DIR=/data/memory -# API Key for agent-brain (used by LibreChat custom endpoint) -AGENT_BRAIN_API_KEY=agent-brain-secret-key +# API Key for alfred (used by LibreChat custom endpoint) +ALFRED_API_KEY=alfred-secret-key # External Services (Optional) # TMDB API Key (for movie metadata) diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index eb544f8..ee4a02c 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -63,7 +63,6 @@ jobs: uses: docker/build-push-action@v5 with: context: . - file: ./brain/Dockerfile push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} diff --git a/Dockerfile b/Dockerfile index 8de8721..2a01337 100644 --- a/Dockerfile +++ b/Dockerfile @@ -30,7 +30,7 @@ RUN --mount=type=cache,target=/root/.cache/pip \ WORKDIR /tmp # Copy dependency files -COPY brain/pyproject.toml brain/poetry.lock* brain/uv.lock* Makefile ./ +COPY pyproject.toml poetry.lock* uv.lock* Makefile ./ # Install dependencies as root (to avoid permission issues with system packages) RUN --mount=type=cache,target=/root/.cache/pip \ @@ -59,12 +59,12 @@ RUN --mount=type=cache,target=/root/.cache/pip \ uv pip install --system -e .[dev]; \ fi -COPY brain/agent/ ./agent/ -COPY brain/application/ ./application/ -COPY brain/domain/ ./domain/ -COPY brain/infrastructure/ ./infrastructure/ -COPY brain/tests/ ./tests/ -COPY brain/app.py . +COPY alfred/agent/ ./agent/ +COPY alfred/application/ ./application/ +COPY alfred/domain/ ./domain/ +COPY alfred/infrastructure/ ./infrastructure/ +COPY alfred/tests/ ./tests/ +COPY alfred/app.py . # =========================================== # Stage 3: Runtime @@ -103,11 +103,11 @@ COPY --from=builder /usr/local/lib/python${PYTHON_VERSION_SHORT}/site-packages / COPY --from=builder /usr/local/bin /usr/local/bin # Copy application code (already owned by appuser) -COPY --chown=appuser:appuser brain/agent/ ./agent/ -COPY --chown=appuser:appuser brain/application/ ./application/ -COPY --chown=appuser:appuser brain/domain/ ./domain/ -COPY --chown=appuser:appuser brain/infrastructure/ ./infrastructure/ -COPY --chown=appuser:appuser brain/app.py . +COPY --chown=appuser:appuser alfred/agent/ ./agent/ +COPY --chown=appuser:appuser alfred/application/ ./application/ +COPY --chown=appuser:appuser alfred/domain/ ./domain/ +COPY --chown=appuser:appuser alfred/infrastructure/ ./infrastructure/ +COPY --chown=appuser:appuser alfred/app.py . # Create volumes for persistent data VOLUME ["/data/memory", "/data/logs"] diff --git a/Makefile b/Makefile index 1097bf1..a0c3ac7 100644 --- a/Makefile +++ b/Makefile @@ -3,14 +3,14 @@ .DEFAULT_GOAL := help # --- SETTINGS --- -CORE_DIR = brain -IMAGE_NAME = agent_media +CORE_DIR = alfred +IMAGE_NAME = alfred_media_organizer # renovate: datasource=docker depName=python PYTHON_VERSION = $(shell grep "python" $(CORE_DIR)/pyproject.toml | head -n 1 | sed -E 's/.*[=<>^~"]+ *([0-9]+\.[0-9]+(\.[0-9]+)?).*/\1/') PYTHON_VERSION_SHORT = $(shell echo $(PYTHON_VERSION) | cut -d. -f1,2) # Change to 'uv' when ready. RUNNER ?= poetry -SERVICE_NAME = agent_media +SERVICE_NAME = alfred export IMAGE_NAME export PYTHON_VERSION @@ -157,7 +157,7 @@ init-dotenv: @sed -i.bak "s|CREDS_KEY=.*|CREDS_KEY=$$(openssl rand -hex 16)|" .env @sed -i.bak "s|CREDS_IV=.*|CREDS_IV=$$(openssl rand -hex 8)|" .env @sed -i.bak "s|MEILI_MASTER_KEY=.*|MEILI_MASTER_KEY=$$(openssl rand -base64 32)|" .env - @sed -i.bak "s|AGENT_BRAIN_API_KEY=.*|AGENT_BRAIN_API_KEY=$$(openssl rand -base64 24)|" .env + @sed -i.bak "s|ALFRED_API_KEY=.*|ALFRED_API_KEY=$$(openssl rand -base64 24)|" .env @rm -f .env.bak @echo "$(G)✅ .env created with generated secrets!$(R)" @echo "$(T)⚠️ Don't forget to add your API keys:$(R)" diff --git a/docker-compose.yml b/docker-compose.yml index b5d87af..731310e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,14 +1,12 @@ version: "3.4" services: - # Da brain - agent-brain: + alfred: build: - context: ./brain - dockerfile: Dockerfile + context: . args: RUNNER: ${RUNNER} # Get it from Makefile - container_name: agent-brain + container_name: alfred restart: unless-stopped env_file: .env ports: @@ -18,7 +16,7 @@ services: - agent-memory:/data/memory - agent-logs:/data/logs # Development: mount code for hot reload (comment out in production) - # - ./brain:/app + # - ./alfred:/app environment: # LLM Configuration LLM_PROVIDER: ${LLM_PROVIDER:-deepseek} @@ -46,7 +44,7 @@ services: - mongodb - meilisearch - rag_api - - agent-brain + - alfred env_file: .env environment: # MongoDB connection (no auth, matching LibreChat default) @@ -80,8 +78,8 @@ services: # Endpoints ENDPOINTS: custom - # Custom endpoint pointing to agent-brain - CUSTOM_API_KEY: ${AGENT_BRAIN_API_KEY:-agent-brain-secret-key} + # Custom endpoint pointing to alfred + CUSTOM_API_KEY: ${ALFRED_API_KEY:-alfred-secret-key} # Debug (optional) DEBUG_LOGGING: ${DEBUG_LOGGING:-false} @@ -195,7 +193,7 @@ volumes: librechat-logs: driver: local - # Agent Brain data + # Alfred data agent-memory: driver: local agent-logs: diff --git a/librechat/librechat.yaml b/librechat/librechat.yaml index 196c08d..2691484 100644 --- a/librechat/librechat.yaml +++ b/librechat/librechat.yaml @@ -22,7 +22,7 @@ endpoints: manifest: schema: type: openapi - url: "http://agent-brain:8000/manifests/find_media_imdb_id.json" + url: "http://alfred:8000/manifests/find_media_imdb_id.json" auth: type: none @@ -32,7 +32,7 @@ endpoints: manifest: schema: type: openapi - url: "http://agent-brain:8000/manifests/find_torrent.json" + url: "http://alfred:8000/manifests/find_torrent.json" auth: type: none @@ -42,7 +42,7 @@ endpoints: manifest: schema: type: openapi - url: "http://agent-brain:8000/manifests/add_torrent_by_index.json" + url: "http://alfred:8000/manifests/add_torrent_by_index.json" auth: type: none @@ -52,7 +52,7 @@ endpoints: manifest: schema: type: openapi - url: "http://agent-brain:8000/manifests/set_language.json" + url: "http://alfred:8000/manifests/set_language.json" auth: type: none @@ -60,7 +60,7 @@ endpoints: # Backend Local Agent - name: "Local Agent" apiKey: "dummy_key" - baseURL: "http://agent-brain:8000/v1" + baseURL: "http://alfred:8000/v1" models: default: ["local-deepseek-agent"] fetch: false @@ -75,7 +75,7 @@ endpoints: manifest: schema: type: openapi - url: "http://agent-brain:8000/manifests/find_media_imdb_id.json" + url: "http://alfred:8000/manifests/find_media_imdb_id.json" auth: type: none @@ -85,7 +85,7 @@ endpoints: manifest: schema: type: openapi - url: "http://agent-brain:8000/manifests/find_torrent.json" + url: "http://alfred:8000/manifests/find_torrent.json" auth: type: none @@ -95,7 +95,7 @@ endpoints: manifest: schema: type: openapi - url: "http://agent-brain:8000/manifests/add_torrent_by_index.json" + url: "http://alfred:8000/manifests/add_torrent_by_index.json" auth: type: none @@ -105,6 +105,6 @@ endpoints: manifest: schema: type: openapi - url: "http://agent-brain:8000/manifests/set_language.json" + url: "http://alfred:8000/manifests/set_language.json" auth: type: none diff --git a/pyproject.toml b/pyproject.toml index 69b6eaa..32de1a1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [tool.poetry] -name = "agent-media" +name = "alfred" version = "0.1.6" description = "AI agent for managing a local media library" authors = ["Francwa "] diff --git a/tests/conftest.py b/tests/conftest.py index c1448e4..67c6932 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,8 +3,9 @@ import sys from pathlib import Path +#TODO: Moved directory, should not be necessary anymore but need to check !! # Ajouter le dossier parent (brain) au PYTHONPATH -sys.path.insert(0, str(Path(__file__).parent.parent)) +# sys.path.insert(0, str(Path(__file__).parent.parent)) import shutil import tempfile -- 2.49.1 From c5e4a5e1a70e237c495a94a3a36362bb389b97a5 Mon Sep 17 00:00:00 2001 From: Francwa Date: Wed, 24 Dec 2025 08:11:30 +0100 Subject: [PATCH 03/22] infra: removed occurences of alfred_api_key (not implemented after all) --- .env.example | 3 --- Makefile | 1 - docker-compose.yml | 3 --- 3 files changed, 7 deletions(-) diff --git a/.env.example b/.env.example index 7a4ccd0..2e39f26 100644 --- a/.env.example +++ b/.env.example @@ -52,9 +52,6 @@ LLM_PROVIDER=deepseek # Memory storage directory (inside container) MEMORY_STORAGE_DIR=/data/memory -# API Key for alfred (used by LibreChat custom endpoint) -ALFRED_API_KEY=alfred-secret-key - # External Services (Optional) # TMDB API Key (for movie metadata) TMDB_API_KEY=your-tmdb-key diff --git a/Makefile b/Makefile index a0c3ac7..a01ab00 100644 --- a/Makefile +++ b/Makefile @@ -157,7 +157,6 @@ init-dotenv: @sed -i.bak "s|CREDS_KEY=.*|CREDS_KEY=$$(openssl rand -hex 16)|" .env @sed -i.bak "s|CREDS_IV=.*|CREDS_IV=$$(openssl rand -hex 8)|" .env @sed -i.bak "s|MEILI_MASTER_KEY=.*|MEILI_MASTER_KEY=$$(openssl rand -base64 32)|" .env - @sed -i.bak "s|ALFRED_API_KEY=.*|ALFRED_API_KEY=$$(openssl rand -base64 24)|" .env @rm -f .env.bak @echo "$(G)✅ .env created with generated secrets!$(R)" @echo "$(T)⚠️ Don't forget to add your API keys:$(R)" diff --git a/docker-compose.yml b/docker-compose.yml index 731310e..a9b7017 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -78,9 +78,6 @@ services: # Endpoints ENDPOINTS: custom - # Custom endpoint pointing to alfred - CUSTOM_API_KEY: ${ALFRED_API_KEY:-alfred-secret-key} - # Debug (optional) DEBUG_LOGGING: ${DEBUG_LOGGING:-false} DEBUG_CONSOLE: ${DEBUG_CONSOLE:-false} -- 2.49.1 From f8eee120cfe722e7277b4a4bc472f159b6e71873 Mon Sep 17 00:00:00 2001 From: Francwa Date: Wed, 24 Dec 2025 08:16:30 +0100 Subject: [PATCH 04/22] infra: backed up old deploy-compose --- docker-compose.yml => docker-compose.yml_bak | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docker-compose.yml => docker-compose.yml_bak (100%) diff --git a/docker-compose.yml b/docker-compose.yml_bak similarity index 100% rename from docker-compose.yml rename to docker-compose.yml_bak -- 2.49.1 From 156d1fe567cf3ca62baf8523c999713140801927 Mon Sep 17 00:00:00 2001 From: Francwa Date: Wed, 24 Dec 2025 08:17:12 +0100 Subject: [PATCH 05/22] fix: fixed Dockerfile --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 2a01337..17bc69d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -63,8 +63,8 @@ COPY alfred/agent/ ./agent/ COPY alfred/application/ ./application/ COPY alfred/domain/ ./domain/ COPY alfred/infrastructure/ ./infrastructure/ -COPY alfred/tests/ ./tests/ COPY alfred/app.py . +COPY tests/ ./tests/ # =========================================== # Stage 3: Runtime -- 2.49.1 From d8234b295841d0a51798c1ffa24b927050894691 Mon Sep 17 00:00:00 2001 From: Francwa Date: Wed, 24 Dec 2025 08:17:47 +0100 Subject: [PATCH 06/22] chore: cleaned up .env.example --- .env.example | 51 ++++----------------------------------------------- 1 file changed, 4 insertions(+), 47 deletions(-) diff --git a/.env.example b/.env.example index 2e39f26..e2c28f7 100644 --- a/.env.example +++ b/.env.example @@ -1,51 +1,8 @@ -# Agent Media - Environment Variables - -# LibreChat Security Keys -# Generate secure keys with: openssl rand -base64 32 -JWT_SECRET=your-super-secret-jwt-key-change-this-in-production -JWT_REFRESH_SECRET=your-super-secret-refresh-key-change-this-too - -# Generate with: openssl rand -hex 16 (for CREDS_KEY) -CREDS_KEY=your-32-character-secret-key-here - -# Generate with: openssl rand -hex 8 (for CREDS_IV) -CREDS_IV=your-16-character-iv-here - -# LibreChat Configuration -DOMAIN_CLIENT=http://localhost:3080 -DOMAIN_SERVER=http://localhost:3080 - -# Session expiry (in milliseconds) -# Default: 15 minutes -SESSION_EXPIRY=900000 - -# Refresh token expiry (in milliseconds) -# Default: 7 days -REFRESH_TOKEN_EXPIRY=604800000 - -# Meilisearch Configuration -# Master key for Meilisearch (generate with: openssl rand -base64 32) -MEILI_MASTER_KEY=DrhYf7zENyR6AlUCKmnz0eYASOQdl6zxH7s7MKFSfFU - -# PostgreSQL Configuration (for RAG API) -POSTGRES_DB=librechat_rag -POSTGRES_USER=postgres -POSTGRES_PASSWORD=postgres - -# RAG API Configuration (Vector Database) -RAG_COLLECTION_NAME=testcollection -RAG_EMBEDDINGS_PROVIDER=openai -RAG_EMBEDDINGS_MODEL=text-embedding-3-small - # API Keys -# OpenAI API Key (required for RAG embeddings) -OPENAI_API_KEY=your-openai-api-key-here - # Deepseek API Key (for LLM in alfred) -DEEPSEEK_API_KEY=your-deepseek-api-key-here +DEEPSEEK_API_KEY= # Alfred Configuration - # LLM Provider (deepseek or ollama) LLM_PROVIDER=deepseek @@ -54,12 +11,12 @@ MEMORY_STORAGE_DIR=/data/memory # External Services (Optional) # TMDB API Key (for movie metadata) -TMDB_API_KEY=your-tmdb-key +TMDB_API_KEY= # qBittorrent Configuration -QBITTORRENT_URL=http://localhost:8080 +QBITTORRENT_URL= QBITTORRENT_USERNAME=admin -QBITTORRENT_PASSWORD=adminpass +QBITTORRENT_PASSWORD=adminadmin # Debug Options DEBUG_LOGGING=false -- 2.49.1 From 26d90acc166a303c7e99a53d93f828dbd5216dc0 Mon Sep 17 00:00:00 2001 From: Francwa Date: Wed, 24 Dec 2025 09:46:25 +0100 Subject: [PATCH 07/22] infra: moved manifest files in librechat dir --- {manifests => librechat/manifests}/add_torrent_by_index.json | 0 {manifests => librechat/manifests}/find_media_imdb_id.json | 0 {manifests => librechat/manifests}/find_torrent.json | 0 {manifests => librechat/manifests}/set_language.json | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename {manifests => librechat/manifests}/add_torrent_by_index.json (100%) rename {manifests => librechat/manifests}/find_media_imdb_id.json (100%) rename {manifests => librechat/manifests}/find_torrent.json (100%) rename {manifests => librechat/manifests}/set_language.json (100%) diff --git a/manifests/add_torrent_by_index.json b/librechat/manifests/add_torrent_by_index.json similarity index 100% rename from manifests/add_torrent_by_index.json rename to librechat/manifests/add_torrent_by_index.json diff --git a/manifests/find_media_imdb_id.json b/librechat/manifests/find_media_imdb_id.json similarity index 100% rename from manifests/find_media_imdb_id.json rename to librechat/manifests/find_media_imdb_id.json diff --git a/manifests/find_torrent.json b/librechat/manifests/find_torrent.json similarity index 100% rename from manifests/find_torrent.json rename to librechat/manifests/find_torrent.json diff --git a/manifests/set_language.json b/librechat/manifests/set_language.json similarity index 100% rename from manifests/set_language.json rename to librechat/manifests/set_language.json -- 2.49.1 From 561796cec1ef5c217dfc3d2df57d6528e4bf3028 Mon Sep 17 00:00:00 2001 From: Francwa Date: Wed, 24 Dec 2025 11:28:28 +0100 Subject: [PATCH 08/22] infra: removed CORE_DIR variable from Makefile (not relevant anymore) --- Makefile | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/Makefile b/Makefile index a01ab00..250bbbd 100644 --- a/Makefile +++ b/Makefile @@ -3,10 +3,9 @@ .DEFAULT_GOAL := help # --- SETTINGS --- -CORE_DIR = alfred IMAGE_NAME = alfred_media_organizer # renovate: datasource=docker depName=python -PYTHON_VERSION = $(shell grep "python" $(CORE_DIR)/pyproject.toml | head -n 1 | sed -E 's/.*[=<>^~"]+ *([0-9]+\.[0-9]+(\.[0-9]+)?).*/\1/') +PYTHON_VERSION = $(shell grep "python" pyproject.toml | head -n 1 | sed -E 's/.*[=<>^~"]+ *([0-9]+\.[0-9]+(\.[0-9]+)?).*/\1/') PYTHON_VERSION_SHORT = $(shell echo $(PYTHON_VERSION) | cut -d. -f1,2) # Change to 'uv' when ready. RUNNER ?= poetry @@ -23,20 +22,19 @@ INSTALL_CMD = $(if $(filter uv,$(RUNNER)),sync,install) # --- MACROS --- ARGS = $(filter-out $@,$(MAKECMDGOALS)) -BUMP_CMD = cd $(CORE_DIR) && $(RUNNER) run bump-my-version bump +BUMP_CMD = $(RUNNER) run bump-my-version bump COMPOSE_CMD = docker-compose DOCKER_CMD = docker build \ --build-arg PYTHON_VERSION=$(PYTHON_VERSION) \ --build-arg PYTHON_VERSION_SHORT=$(PYTHON_VERSION_SHORT) \ --build-arg RUNNER=$(RUNNER) \ - -f $(CORE_DIR)/Dockerfile \ -t $(IMAGE_NAME):latest . -RUNNER_ADD = cd $(CORE_DIR) && $(RUNNER) add -RUNNER_HOOKS = cd $(CORE_DIR) && $(RUNNER) run pre-commit install -c ../.pre-commit-config.yaml -RUNNER_INSTALL = cd $(CORE_DIR) && $(RUNNER) $(INSTALL_CMD) -RUNNER_RUN = cd $(CORE_DIR) && $(RUNNER) run -RUNNER_UPDATE = cd $(CORE_DIR) && $(RUNNER) update +RUNNER_ADD = $(RUNNER) add +RUNNER_HOOKS = $(RUNNER) run pre-commit install -c ../.pre-commit-config.yaml +RUNNER_INSTALL = $(RUNNER) $(INSTALL_CMD) +RUNNER_RUN = $(RUNNER) run +RUNNER_UPDATE = $(RUNNER) update # --- STYLES --- B = \033[1m @@ -66,7 +64,6 @@ build-test: check-docker --build-arg RUNNER=$(RUNNER) \ --build-arg PYTHON_VERSION=$(PYTHON_VERSION) \ --build-arg PYTHON_VERSION_SHORT=$(PYTHON_VERSION_SHORT) \ - -f $(CORE_DIR)/Dockerfile \ --target test \ -t $(IMAGE_NAME):test . @echo "✅ Test image $(IMAGE_NAME):test ready." @@ -80,10 +77,10 @@ check-runner: clean: @echo "$(T)🧹 Cleaning caches...$(R)" - cd $(CORE_DIR) && rm -rf .ruff_cache __pycache__ .pytest_cache - find $(CORE_DIR) -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true - find $(CORE_DIR) -type d -name ".pytest_cache" -exec rm -rf {} + 2>/dev/null || true - find $(CORE_DIR) -type f -name "*.pyc" -delete 2>/dev/null || true + rm -rf .ruff_cache __pycache__ .pytest_cache + find . -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true + find . -type d -name ".pytest_cache" -exec rm -rf {} + 2>/dev/null || true + find . -type f -name "*.pyc" -delete 2>/dev/null || true @echo "✅ Caches cleaned." coverage: check-runner -- 2.49.1 From b132554631391b9ff92e739ee70611ff9037b827 Mon Sep 17 00:00:00 2001 From: Francwa Date: Sat, 27 Dec 2025 02:30:14 +0100 Subject: [PATCH 09/22] infra: updated .gitignore --- .gitignore | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.gitignore b/.gitignore index 2282737..69d7317 100644 --- a/.gitignore +++ b/.gitignore @@ -59,3 +59,9 @@ Thumbs.db # Backup files *.backup + +# Application data dir +data/* + +# Application logs +logs/* -- 2.49.1 From 6195abbaa56aa7f4cadd7c6a149d6c0206b12faf Mon Sep 17 00:00:00 2001 From: Francwa Date: Sat, 27 Dec 2025 19:39:36 +0100 Subject: [PATCH 10/22] chore: fixed imports and tests configuration --- .dockerignore | 4 - Dockerfile | 16 +--- alfred/agent/agent.py | 2 +- alfred/agent/prompts.py | 2 +- alfred/agent/tools/api.py | 12 +-- alfred/agent/tools/filesystem.py | 4 +- alfred/agent/tools/language.py | 2 +- alfred/app.py | 15 ++- alfred/application/filesystem/list_folder.py | 2 +- .../application/filesystem/set_folder_path.py | 2 +- alfred/application/movies/search_movie.py | 2 +- alfred/application/torrents/add_torrent.py | 2 +- .../application/torrents/search_torrents.py | 2 +- alfred/infrastructure/api/knaben/client.py | 7 +- .../infrastructure/api/qbittorrent/client.py | 6 +- alfred/infrastructure/api/tmdb/client.py | 5 +- .../infrastructure/filesystem/file_manager.py | 2 +- alfred/infrastructure/filesystem/organizer.py | 6 +- alfred/infrastructure/persistence/context.py | 2 +- .../persistence/json/movie_repository.py | 10 +- .../persistence/json/subtitle_repository.py | 10 +- .../persistence/json/tvshow_repository.py | 10 +- pyproject.toml | 2 + tests/conftest.py | 13 +-- tests/test_agent.py | 4 +- tests/test_agent_edge_cases.py | 12 +-- tests/test_api.py | 34 +++---- tests/test_api_edge_cases.py | 94 ++++++++++--------- tests/test_config_critical.py | 3 +- tests/test_config_edge_cases.py | 7 +- tests/test_domain_edge_cases.py | 20 ++-- tests/test_memory.py | 8 +- tests/test_memory_edge_cases.py | 5 +- tests/test_prompts.py | 4 +- tests/test_prompts_critical.py | 4 +- tests/test_prompts_edge_cases.py | 6 +- tests/test_registry_critical.py | 5 +- tests/test_registry_edge_cases.py | 3 +- tests/test_repositories.py | 16 ++-- tests/test_repositories_edge_cases.py | 16 ++-- tests/test_tools_api.py | 34 +++---- tests/test_tools_edge_cases.py | 36 ++++--- tests/test_tools_filesystem.py | 8 +- 43 files changed, 216 insertions(+), 243 deletions(-) diff --git a/.dockerignore b/.dockerignore index 3338a74..667224e 100644 --- a/.dockerignore +++ b/.dockerignore @@ -41,10 +41,6 @@ docs/ *.md !README.md -# Tests -tests/ -pytest.ini - # Data (will be mounted as volumes) memory_data/ logs/ diff --git a/Dockerfile b/Dockerfile index 17bc69d..15ab5f9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -59,12 +59,8 @@ RUN --mount=type=cache,target=/root/.cache/pip \ uv pip install --system -e .[dev]; \ fi -COPY alfred/agent/ ./agent/ -COPY alfred/application/ ./application/ -COPY alfred/domain/ ./domain/ -COPY alfred/infrastructure/ ./infrastructure/ -COPY alfred/app.py . -COPY tests/ ./tests/ +COPY alfred/ ./alfred +COPY tests/ ./tests # =========================================== # Stage 3: Runtime @@ -96,18 +92,14 @@ RUN mkdir -p /data/memory /data/logs \ USER appuser # Set working directory (owned by appuser) -WORKDIR /home/appuser/app +WORKDIR /home/appuser # Copy Python packages from builder stage COPY --from=builder /usr/local/lib/python${PYTHON_VERSION_SHORT}/site-packages /usr/local/lib/python${PYTHON_VERSION_SHORT}/site-packages COPY --from=builder /usr/local/bin /usr/local/bin # Copy application code (already owned by appuser) -COPY --chown=appuser:appuser alfred/agent/ ./agent/ -COPY --chown=appuser:appuser alfred/application/ ./application/ -COPY --chown=appuser:appuser alfred/domain/ ./domain/ -COPY --chown=appuser:appuser alfred/infrastructure/ ./infrastructure/ -COPY --chown=appuser:appuser alfred/app.py . +COPY --chown=appuser:appuser alfred/ ./alfred # Create volumes for persistent data VOLUME ["/data/memory", "/data/logs"] diff --git a/alfred/agent/agent.py b/alfred/agent/agent.py index 27e9cf0..770fbcf 100644 --- a/alfred/agent/agent.py +++ b/alfred/agent/agent.py @@ -5,7 +5,7 @@ import logging from collections.abc import AsyncGenerator from typing import Any -from infrastructure.persistence import get_memory +from alfred.infrastructure.persistence import get_memory from .config import settings from .prompts import PromptBuilder diff --git a/alfred/agent/prompts.py b/alfred/agent/prompts.py index 734e627..f43bc85 100644 --- a/alfred/agent/prompts.py +++ b/alfred/agent/prompts.py @@ -3,7 +3,7 @@ import json from typing import Any -from infrastructure.persistence import get_memory +from alfred.infrastructure.persistence import get_memory from .registry import Tool diff --git a/alfred/agent/tools/api.py b/alfred/agent/tools/api.py index 0898c60..ecca8e5 100644 --- a/alfred/agent/tools/api.py +++ b/alfred/agent/tools/api.py @@ -3,12 +3,12 @@ import logging from typing import Any -from application.movies import SearchMovieUseCase -from application.torrents import AddTorrentUseCase, SearchTorrentsUseCase -from infrastructure.api.knaben import knaben_client -from infrastructure.api.qbittorrent import qbittorrent_client -from infrastructure.api.tmdb import tmdb_client -from infrastructure.persistence import get_memory +from alfred.application.movies import SearchMovieUseCase +from alfred.application.torrents import AddTorrentUseCase, SearchTorrentsUseCase +from alfred.infrastructure.api.knaben import knaben_client +from alfred.infrastructure.api.qbittorrent import qbittorrent_client +from alfred.infrastructure.api.tmdb import tmdb_client +from alfred.infrastructure.persistence import get_memory logger = logging.getLogger(__name__) diff --git a/alfred/agent/tools/filesystem.py b/alfred/agent/tools/filesystem.py index cc7d547..017c339 100644 --- a/alfred/agent/tools/filesystem.py +++ b/alfred/agent/tools/filesystem.py @@ -2,8 +2,8 @@ from typing import Any -from application.filesystem import ListFolderUseCase, SetFolderPathUseCase -from infrastructure.filesystem import FileManager +from alfred.application.filesystem import ListFolderUseCase, SetFolderPathUseCase +from alfred.infrastructure.filesystem import FileManager def set_path_for_folder(folder_name: str, path_value: str) -> dict[str, Any]: diff --git a/alfred/agent/tools/language.py b/alfred/agent/tools/language.py index e7ea471..22b0098 100644 --- a/alfred/agent/tools/language.py +++ b/alfred/agent/tools/language.py @@ -3,7 +3,7 @@ import logging from typing import Any -from infrastructure.persistence import get_memory +from alfred.infrastructure.persistence import get_memory logger = logging.getLogger(__name__) diff --git a/alfred/app.py b/alfred/app.py index 2788a9f..8280b89 100644 --- a/alfred/app.py +++ b/alfred/app.py @@ -5,19 +5,18 @@ import logging import os import time import uuid -from typing import Any - from fastapi import FastAPI, HTTPException from fastapi.responses import JSONResponse, StreamingResponse from fastapi.staticfiles import StaticFiles from pydantic import BaseModel, Field, validator +from typing import Any -from agent.agent import Agent -from agent.config import settings -from agent.llm.deepseek import DeepSeekClient -from agent.llm.exceptions import LLMAPIError, LLMConfigurationError -from agent.llm.ollama import OllamaClient -from infrastructure.persistence import get_memory, init_memory +from alfred.agent.agent import Agent +from alfred.agent.config import settings +from alfred.agent.llm.deepseek import DeepSeekClient +from alfred.agent.llm.exceptions import LLMAPIError, LLMConfigurationError +from alfred.agent.llm.ollama import OllamaClient +from alfred.infrastructure.persistence import get_memory, init_memory logging.basicConfig( level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" diff --git a/alfred/application/filesystem/list_folder.py b/alfred/application/filesystem/list_folder.py index fdae123..02abfb0 100644 --- a/alfred/application/filesystem/list_folder.py +++ b/alfred/application/filesystem/list_folder.py @@ -2,7 +2,7 @@ import logging -from infrastructure.filesystem import FileManager +from alfred.infrastructure.filesystem import FileManager from .dto import ListFolderResponse diff --git a/alfred/application/filesystem/set_folder_path.py b/alfred/application/filesystem/set_folder_path.py index 2f3d0ea..277a35f 100644 --- a/alfred/application/filesystem/set_folder_path.py +++ b/alfred/application/filesystem/set_folder_path.py @@ -2,7 +2,7 @@ import logging -from infrastructure.filesystem import FileManager +from alfred.infrastructure.filesystem import FileManager from .dto import SetFolderPathResponse diff --git a/alfred/application/movies/search_movie.py b/alfred/application/movies/search_movie.py index 940fd0d..652d418 100644 --- a/alfred/application/movies/search_movie.py +++ b/alfred/application/movies/search_movie.py @@ -2,7 +2,7 @@ import logging -from infrastructure.api.tmdb import ( +from alfred.infrastructure.api.tmdb import ( TMDBAPIError, TMDBClient, TMDBConfigurationError, diff --git a/alfred/application/torrents/add_torrent.py b/alfred/application/torrents/add_torrent.py index d6fce1b..2d2214d 100644 --- a/alfred/application/torrents/add_torrent.py +++ b/alfred/application/torrents/add_torrent.py @@ -2,7 +2,7 @@ import logging -from infrastructure.api.qbittorrent import ( +from alfred.infrastructure.api.qbittorrent import ( QBittorrentAPIError, QBittorrentAuthError, QBittorrentClient, diff --git a/alfred/application/torrents/search_torrents.py b/alfred/application/torrents/search_torrents.py index 1dd9745..daad45e 100644 --- a/alfred/application/torrents/search_torrents.py +++ b/alfred/application/torrents/search_torrents.py @@ -2,7 +2,7 @@ import logging -from infrastructure.api.knaben import KnabenAPIError, KnabenClient, KnabenNotFoundError +from alfred.infrastructure.api.knaben import KnabenAPIError, KnabenClient, KnabenNotFoundError from .dto import SearchTorrentsResponse diff --git a/alfred/infrastructure/api/knaben/client.py b/alfred/infrastructure/api/knaben/client.py index b5300ff..359de66 100644 --- a/alfred/infrastructure/api/knaben/client.py +++ b/alfred/infrastructure/api/knaben/client.py @@ -1,13 +1,12 @@ """Knaben torrent search API client.""" -import logging -from typing import Any +import logging import requests from requests.exceptions import HTTPError, RequestException, Timeout +from typing import Any -from agent.config import Settings, settings - +from alfred.agent.config import Settings, settings from .dto import TorrentResult from .exceptions import KnabenAPIError, KnabenNotFoundError diff --git a/alfred/infrastructure/api/qbittorrent/client.py b/alfred/infrastructure/api/qbittorrent/client.py index 61dc25b..02d3124 100644 --- a/alfred/infrastructure/api/qbittorrent/client.py +++ b/alfred/infrastructure/api/qbittorrent/client.py @@ -1,13 +1,11 @@ """qBittorrent Web API client.""" import logging -from typing import Any - import requests from requests.exceptions import HTTPError, RequestException, Timeout +from typing import Any -from agent.config import Settings, settings - +from alfred.agent.config import Settings, settings from .dto import TorrentInfo from .exceptions import QBittorrentAPIError, QBittorrentAuthError diff --git a/alfred/infrastructure/api/tmdb/client.py b/alfred/infrastructure/api/tmdb/client.py index 5cd9ce1..208edbc 100644 --- a/alfred/infrastructure/api/tmdb/client.py +++ b/alfred/infrastructure/api/tmdb/client.py @@ -1,13 +1,12 @@ """TMDB (The Movie Database) API client.""" import logging -from typing import Any - import requests from requests.exceptions import HTTPError, RequestException, Timeout +from typing import Any -from agent.config import Settings, settings +from alfred.agent.config import Settings, settings from .dto import MediaResult from .exceptions import ( TMDBAPIError, diff --git a/alfred/infrastructure/filesystem/file_manager.py b/alfred/infrastructure/filesystem/file_manager.py index c36303b..854bf07 100644 --- a/alfred/infrastructure/filesystem/file_manager.py +++ b/alfred/infrastructure/filesystem/file_manager.py @@ -7,7 +7,7 @@ from enum import Enum from pathlib import Path from typing import Any -from infrastructure.persistence import get_memory +from alfred.infrastructure.persistence import get_memory from .exceptions import PathTraversalError diff --git a/alfred/infrastructure/filesystem/organizer.py b/alfred/infrastructure/filesystem/organizer.py index 60864ac..050b529 100644 --- a/alfred/infrastructure/filesystem/organizer.py +++ b/alfred/infrastructure/filesystem/organizer.py @@ -3,9 +3,9 @@ import logging from pathlib import Path -from domain.movies.entities import Movie -from domain.tv_shows.entities import Episode, Season, TVShow -from domain.tv_shows.value_objects import SeasonNumber +from alfred.domain.movies.entities import Movie +from alfred.domain.tv_shows.entities import Episode, Season, TVShow +from alfred.domain.tv_shows.value_objects import SeasonNumber logger = logging.getLogger(__name__) diff --git a/alfred/infrastructure/persistence/context.py b/alfred/infrastructure/persistence/context.py index 80bf142..8cddbd3 100644 --- a/alfred/infrastructure/persistence/context.py +++ b/alfred/infrastructure/persistence/context.py @@ -6,7 +6,7 @@ without passing it explicitly through all function calls. Usage: # At application startup - from infrastructure.persistence import init_memory, get_memory + from alfred.infrastructure.persistence import init_memory, get_memory init_memory("memory_data") diff --git a/alfred/infrastructure/persistence/json/movie_repository.py b/alfred/infrastructure/persistence/json/movie_repository.py index 243d425..46ace79 100644 --- a/alfred/infrastructure/persistence/json/movie_repository.py +++ b/alfred/infrastructure/persistence/json/movie_repository.py @@ -4,11 +4,11 @@ import logging from datetime import datetime from typing import Any -from domain.movies.entities import Movie -from domain.movies.repositories import MovieRepository -from domain.movies.value_objects import MovieTitle, Quality, ReleaseYear -from domain.shared.value_objects import FilePath, FileSize, ImdbId -from infrastructure.persistence import get_memory +from alfred.domain.movies.entities import Movie +from alfred.domain.movies.repositories import MovieRepository +from alfred.domain.movies.value_objects import MovieTitle, Quality, ReleaseYear +from alfred.domain.shared.value_objects import FilePath, FileSize, ImdbId +from alfred.infrastructure.persistence import get_memory logger = logging.getLogger(__name__) diff --git a/alfred/infrastructure/persistence/json/subtitle_repository.py b/alfred/infrastructure/persistence/json/subtitle_repository.py index f5c92f2..c0ce6c5 100644 --- a/alfred/infrastructure/persistence/json/subtitle_repository.py +++ b/alfred/infrastructure/persistence/json/subtitle_repository.py @@ -3,11 +3,11 @@ import logging from typing import Any -from domain.shared.value_objects import FilePath, ImdbId -from domain.subtitles.entities import Subtitle -from domain.subtitles.repositories import SubtitleRepository -from domain.subtitles.value_objects import Language, SubtitleFormat, TimingOffset -from infrastructure.persistence import get_memory +from alfred.domain.shared.value_objects import FilePath, ImdbId +from alfred.domain.subtitles.entities import Subtitle +from alfred.domain.subtitles.repositories import SubtitleRepository +from alfred.domain.subtitles.value_objects import Language, SubtitleFormat, TimingOffset +from alfred.infrastructure.persistence import get_memory logger = logging.getLogger(__name__) diff --git a/alfred/infrastructure/persistence/json/tvshow_repository.py b/alfred/infrastructure/persistence/json/tvshow_repository.py index 2cb9643..2e79836 100644 --- a/alfred/infrastructure/persistence/json/tvshow_repository.py +++ b/alfred/infrastructure/persistence/json/tvshow_repository.py @@ -4,11 +4,11 @@ import logging from datetime import datetime from typing import Any -from domain.shared.value_objects import ImdbId -from domain.tv_shows.entities import TVShow -from domain.tv_shows.repositories import TVShowRepository -from domain.tv_shows.value_objects import ShowStatus -from infrastructure.persistence import get_memory +from alfred.domain.shared.value_objects import ImdbId +from alfred.domain.tv_shows.entities import TVShow +from alfred.domain.tv_shows.repositories import TVShowRepository +from alfred.domain.tv_shows.value_objects import ShowStatus +from alfred.infrastructure.persistence import get_memory logger = logging.getLogger(__name__) diff --git a/pyproject.toml b/pyproject.toml index 32de1a1..967da51 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,6 +31,8 @@ build-backend = "poetry.core.masonry.api" [tool.pytest.ini_options] # Chemins où pytest cherche les tests testpaths = ["tests"] +# Ajouter le répertoire racine au PYTHONPATH pour les imports +pythonpath = ["."] # Patterns de fichiers/classes/fonctions à considérer comme tests python_files = ["test_*.py"] # Fichiers commençant par "test_" diff --git a/tests/conftest.py b/tests/conftest.py index 67c6932..ff87c6f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,20 +1,16 @@ """Pytest configuration and shared fixtures.""" -import sys -from pathlib import Path - -#TODO: Moved directory, should not be necessary anymore but need to check !! +# TODO: Moved directory, should not be necessary anymore but need to check !! # Ajouter le dossier parent (brain) au PYTHONPATH # sys.path.insert(0, str(Path(__file__).parent.parent)) - +import pytest import shutil +import sys import tempfile from pathlib import Path from unittest.mock import MagicMock, Mock -import pytest - -from infrastructure.persistence import Memory, set_memory +from alfred.infrastructure.persistence import Memory, set_memory @pytest.fixture @@ -255,7 +251,6 @@ def mock_deepseek(): def test_something(mock_deepseek): # Your test code here """ - import sys from unittest.mock import Mock # Save the original module if it exists diff --git a/tests/test_agent.py b/tests/test_agent.py index cf3c426..eb846c5 100644 --- a/tests/test_agent.py +++ b/tests/test_agent.py @@ -2,8 +2,8 @@ from unittest.mock import Mock -from agent.agent import Agent -from infrastructure.persistence import get_memory +from alfred.agent.agent import Agent +from alfred.infrastructure.persistence import get_memory class TestAgentInit: diff --git a/tests/test_agent_edge_cases.py b/tests/test_agent_edge_cases.py index df92abe..83d94ce 100644 --- a/tests/test_agent_edge_cases.py +++ b/tests/test_agent_edge_cases.py @@ -1,11 +1,9 @@ """Edge case tests for the Agent.""" - +import pytest from unittest.mock import Mock -import pytest - -from agent.agent import Agent -from infrastructure.persistence import get_memory +from alfred.agent.agent import Agent +from alfred.infrastructure.persistence import get_memory class TestExecuteToolCallEdgeCases: @@ -16,7 +14,7 @@ class TestExecuteToolCallEdgeCases: agent = Agent(llm=mock_llm) # Mock a tool that returns None - from agent.registry import Tool + from alfred.agent.registry import Tool agent.tools["test_tool"] = Tool( name="test_tool", description="Test", func=lambda: None, parameters={} @@ -34,7 +32,7 @@ class TestExecuteToolCallEdgeCases: """Should propagate KeyboardInterrupt.""" agent = Agent(llm=mock_llm) - from agent.registry import Tool + from alfred.agent.registry import Tool def raise_interrupt(): raise KeyboardInterrupt() diff --git a/tests/test_api.py b/tests/test_api.py index 85ea1c1..5f72b9a 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -10,7 +10,7 @@ class TestHealthEndpoint: def test_health_check(self, memory): """Should return healthy status.""" - from app import app + from alfred.app import app client = TestClient(app) @@ -25,7 +25,7 @@ class TestModelsEndpoint: def test_list_models(self, memory): """Should return model list.""" - from app import app + from alfred.app import app client = TestClient(app) @@ -43,7 +43,7 @@ class TestMemoryEndpoints: def test_get_memory_state(self, memory): """Should return full memory state.""" - from app import app + from alfred.app import app client = TestClient(app) @@ -57,7 +57,7 @@ class TestMemoryEndpoints: def test_get_search_results_empty(self, memory): """Should return empty when no search results.""" - from app import app + from alfred.app import app client = TestClient(app) @@ -69,7 +69,7 @@ class TestMemoryEndpoints: def test_get_search_results_with_data(self, memory_with_search_results): """Should return search results when available.""" - from app import app + from alfred.app import app client = TestClient(app) @@ -83,7 +83,7 @@ class TestMemoryEndpoints: def test_clear_session(self, memory_with_search_results): """Should clear session memories.""" - from app import app + from alfred.app import app client = TestClient(app) @@ -102,10 +102,10 @@ class TestChatCompletionsEndpoint: def test_chat_completion_success(self, memory): """Should return chat completion.""" - from app import app + from alfred.app import app # Patch the agent's step method directly - with patch("app.agent.step", return_value="Hello! How can I help?"): + with patch("alfred.app.agent.step", return_value="Hello! How can I help?"): client = TestClient(app) response = client.post( @@ -123,7 +123,7 @@ class TestChatCompletionsEndpoint: def test_chat_completion_no_user_message(self, memory): """Should return error if no user message.""" - from app import app + from alfred.app import app client = TestClient(app) @@ -146,7 +146,7 @@ class TestChatCompletionsEndpoint: def test_chat_completion_empty_messages(self, memory): """Should return error for empty messages.""" - from app import app + from alfred.app import app client = TestClient(app) @@ -162,7 +162,7 @@ class TestChatCompletionsEndpoint: def test_chat_completion_invalid_json(self, memory): """Should return error for invalid JSON.""" - from app import app + from alfred.app import app client = TestClient(app) @@ -176,9 +176,9 @@ class TestChatCompletionsEndpoint: def test_chat_completion_streaming(self, memory): """Should support streaming mode.""" - from app import app + from alfred.app import app - with patch("app.agent.step", return_value="Streaming response"): + with patch("alfred.app.agent.step", return_value="Streaming response"): client = TestClient(app) response = client.post( @@ -195,9 +195,9 @@ class TestChatCompletionsEndpoint: def test_chat_completion_extracts_last_user_message(self, memory): """Should use last user message.""" - from app import app + from alfred.app import app - with patch("app.agent.step", return_value="Response") as mock_step: + with patch("alfred.app.agent.step", return_value="Response") as mock_step: client = TestClient(app) response = client.post( @@ -218,9 +218,9 @@ class TestChatCompletionsEndpoint: def test_chat_completion_response_format(self, memory): """Should return OpenAI-compatible format.""" - from app import app + from alfred.app import app - with patch("app.agent.step", return_value="Test response"): + with patch("alfred.app.agent.step", return_value="Test response"): client = TestClient(app) response = client.post( diff --git a/tests/test_api_edge_cases.py b/tests/test_api_edge_cases.py index 562da55..5ffa798 100644 --- a/tests/test_api_edge_cases.py +++ b/tests/test_api_edge_cases.py @@ -10,7 +10,8 @@ class TestChatCompletionsEdgeCases: def test_very_long_message(self, memory): """Should handle very long user message.""" - from app import agent, app + from alfred.app import app + from alfred.agent import agent # Patch the agent's LLM directly mock_llm = Mock() @@ -32,7 +33,8 @@ class TestChatCompletionsEdgeCases: def test_unicode_message(self, memory): """Should handle unicode in message.""" - from app import agent, app + from alfred.app import app + from alfred.agent import agent mock_llm = Mock() mock_llm.complete.return_value = { @@ -57,7 +59,8 @@ class TestChatCompletionsEdgeCases: def test_special_characters_in_message(self, memory): """Should handle special characters.""" - from app import agent, app + from alfred.app import app + from alfred.agent import agent mock_llm = Mock() mock_llm.complete.return_value = {"role": "assistant", "content": "Response"} @@ -78,12 +81,12 @@ class TestChatCompletionsEdgeCases: def test_empty_content_in_message(self, memory): """Should handle empty content in message.""" - with patch("app.DeepSeekClient") as mock_llm_class: + with patch("alfred.app.DeepSeekClient") as mock_llm_class: mock_llm = Mock() mock_llm.complete.return_value = "Response" mock_llm_class.return_value = mock_llm - from app import app + from alfred.app import app client = TestClient(app) @@ -100,11 +103,11 @@ class TestChatCompletionsEdgeCases: def test_null_content_in_message(self, memory): """Should handle null content in message.""" - with patch("app.DeepSeekClient") as mock_llm_class: + with patch("alfred.app.DeepSeekClient") as mock_llm_class: mock_llm = Mock() mock_llm_class.return_value = mock_llm - from app import app + from alfred.app import app client = TestClient(app) @@ -120,11 +123,11 @@ class TestChatCompletionsEdgeCases: def test_missing_content_field(self, memory): """Should handle missing content field.""" - with patch("app.DeepSeekClient") as mock_llm_class: + with patch("alfred.app.DeepSeekClient") as mock_llm_class: mock_llm = Mock() mock_llm_class.return_value = mock_llm - from app import app + from alfred.app import app client = TestClient(app) @@ -141,11 +144,11 @@ class TestChatCompletionsEdgeCases: def test_missing_role_field(self, memory): """Should handle missing role field.""" - with patch("app.DeepSeekClient") as mock_llm_class: + with patch("alfred.app.DeepSeekClient") as mock_llm_class: mock_llm = Mock() mock_llm_class.return_value = mock_llm - from app import app + from alfred.app import app client = TestClient(app) @@ -162,12 +165,12 @@ class TestChatCompletionsEdgeCases: def test_invalid_role(self, memory): """Should handle invalid role.""" - with patch("app.DeepSeekClient") as mock_llm_class: + with patch("alfred.app.DeepSeekClient") as mock_llm_class: mock_llm = Mock() mock_llm.complete.return_value = "Response" mock_llm_class.return_value = mock_llm - from app import app + from alfred.app import app client = TestClient(app) @@ -184,7 +187,8 @@ class TestChatCompletionsEdgeCases: def test_many_messages(self, memory): """Should handle many messages in conversation.""" - from app import agent, app + from alfred.app import app + from alfred.agent import agent mock_llm = Mock() mock_llm.complete.return_value = {"role": "assistant", "content": "Response"} @@ -210,11 +214,11 @@ class TestChatCompletionsEdgeCases: def test_only_system_messages(self, memory): """Should reject if only system messages.""" - with patch("app.DeepSeekClient") as mock_llm_class: + with patch("alfred.app.DeepSeekClient") as mock_llm_class: mock_llm = Mock() mock_llm_class.return_value = mock_llm - from app import app + from alfred.app import app client = TestClient(app) @@ -233,11 +237,11 @@ class TestChatCompletionsEdgeCases: def test_only_assistant_messages(self, memory): """Should reject if only assistant messages.""" - with patch("app.DeepSeekClient") as mock_llm_class: + with patch("alfred.app.DeepSeekClient") as mock_llm_class: mock_llm = Mock() mock_llm_class.return_value = mock_llm - from app import app + from alfred.app import app client = TestClient(app) @@ -255,11 +259,11 @@ class TestChatCompletionsEdgeCases: def test_messages_not_array(self, memory): """Should reject if messages is not array.""" - with patch("app.DeepSeekClient") as mock_llm_class: + with patch("alfred.app.DeepSeekClient") as mock_llm_class: mock_llm = Mock() mock_llm_class.return_value = mock_llm - from app import app + from alfred.app import app client = TestClient(app) @@ -276,11 +280,11 @@ class TestChatCompletionsEdgeCases: def test_message_not_object(self, memory): """Should handle message that is not object.""" - with patch("app.DeepSeekClient") as mock_llm_class: + with patch("alfred.app.DeepSeekClient") as mock_llm_class: mock_llm = Mock() mock_llm_class.return_value = mock_llm - from app import app + from alfred.app import app client = TestClient(app) @@ -297,7 +301,8 @@ class TestChatCompletionsEdgeCases: def test_extra_fields_in_request(self, memory): """Should ignore extra fields in request.""" - from app import agent, app + from alfred.app import app + from alfred.agent import agent mock_llm = Mock() mock_llm.complete.return_value = {"role": "assistant", "content": "Response"} @@ -320,8 +325,9 @@ class TestChatCompletionsEdgeCases: def test_streaming_with_tool_call(self, memory, real_folder): """Should handle streaming with tool execution.""" - from app import agent, app - from infrastructure.persistence import get_memory + from alfred.app import app + from alfred.agent import agent + from alfred.infrastructure.persistence import get_memory mem = get_memory() mem.ltm.set_config("download_folder", str(real_folder["downloads"])) @@ -365,7 +371,8 @@ class TestChatCompletionsEdgeCases: def test_concurrent_requests_simulation(self, memory): """Should handle rapid sequential requests.""" - from app import agent, app + from alfred.app import app + from alfred.agent import agent mock_llm = Mock() mock_llm.complete.return_value = {"role": "assistant", "content": "Response"} @@ -385,7 +392,8 @@ class TestChatCompletionsEdgeCases: def test_llm_returns_json_in_response(self, memory): """Should handle LLM returning JSON in text response.""" - from app import agent, app + from alfred.app import app + from alfred.agent import agent mock_llm = Mock() mock_llm.complete.return_value = { @@ -414,9 +422,9 @@ class TestMemoryEndpointsEdgeCases: def test_memory_state_with_large_data(self, memory): """Should handle large memory state.""" - with patch("app.DeepSeekClient") as mock_llm: + with patch("alfred.app.DeepSeekClient") as mock_llm: mock_llm.return_value = Mock() - from app import app + from alfred.app import app # Add lots of data to memory for i in range(100): @@ -432,9 +440,9 @@ class TestMemoryEndpointsEdgeCases: def test_memory_state_with_unicode(self, memory): """Should handle unicode in memory state.""" - with patch("app.DeepSeekClient") as mock_llm: + with patch("alfred.app.DeepSeekClient") as mock_llm: mock_llm.return_value = Mock() - from app import app + from alfred.app import app memory.ltm.set_config("japanese", "日本語テスト") memory.stm.add_message("user", "🎬 Movie request") @@ -448,9 +456,9 @@ class TestMemoryEndpointsEdgeCases: def test_search_results_with_special_chars(self, memory): """Should handle special characters in search results.""" - with patch("app.DeepSeekClient") as mock_llm: + with patch("alfred.app.DeepSeekClient") as mock_llm: mock_llm.return_value = Mock() - from app import app + from alfred.app import app memory.episodic.store_search_results( "Test ", @@ -467,9 +475,9 @@ class TestMemoryEndpointsEdgeCases: def test_clear_session_idempotent(self, memory): """Should be idempotent - multiple clears should work.""" - with patch("app.DeepSeekClient") as mock_llm: + with patch("alfred.app.DeepSeekClient") as mock_llm: mock_llm.return_value = Mock() - from app import app + from alfred.app import app client = TestClient(app) @@ -480,9 +488,9 @@ class TestMemoryEndpointsEdgeCases: def test_clear_session_preserves_ltm(self, memory): """Should preserve LTM after clear.""" - with patch("app.DeepSeekClient") as mock_llm: + with patch("alfred.app.DeepSeekClient") as mock_llm: mock_llm.return_value = Mock() - from app import app + from alfred.app import app memory.ltm.set_config("important", "data") memory.stm.add_message("user", "Hello") @@ -502,9 +510,9 @@ class TestHealthEndpointEdgeCases: def test_health_returns_version(self, memory): """Should return version in health check.""" - with patch("app.DeepSeekClient") as mock_llm: + with patch("alfred.app.DeepSeekClient") as mock_llm: mock_llm.return_value = Mock() - from app import app + from alfred.app import app client = TestClient(app) @@ -515,9 +523,9 @@ class TestHealthEndpointEdgeCases: def test_health_with_query_params(self, memory): """Should ignore query parameters.""" - with patch("app.DeepSeekClient") as mock_llm: + with patch("alfred.app.DeepSeekClient") as mock_llm: mock_llm.return_value = Mock() - from app import app + from alfred.app import app client = TestClient(app) @@ -531,9 +539,9 @@ class TestModelsEndpointEdgeCases: def test_models_response_format(self, memory): """Should return OpenAI-compatible format.""" - with patch("app.DeepSeekClient") as mock_llm: + with patch("alfred.app.DeepSeekClient") as mock_llm: mock_llm.return_value = Mock() - from app import app + from alfred.app import app client = TestClient(app) diff --git a/tests/test_config_critical.py b/tests/test_config_critical.py index d149f2c..47e8b07 100644 --- a/tests/test_config_critical.py +++ b/tests/test_config_critical.py @@ -1,8 +1,7 @@ """Critical tests for configuration validation.""" - import pytest -from agent.config import ConfigurationError, Settings +from alfred.agent.config import ConfigurationError, Settings class TestConfigValidation: diff --git a/tests/test_config_edge_cases.py b/tests/test_config_edge_cases.py index 3076e85..01dc150 100644 --- a/tests/test_config_edge_cases.py +++ b/tests/test_config_edge_cases.py @@ -1,12 +1,11 @@ """Edge case tests for configuration and parameters.""" import os +import pytest from unittest.mock import patch -import pytest - -from agent.config import ConfigurationError, Settings -from agent.parameters import ( +from alfred.agent.config import ConfigurationError, Settings +from alfred.agent.parameters import ( REQUIRED_PARAMETERS, ParameterSchema, format_parameters_for_prompt, diff --git a/tests/test_domain_edge_cases.py b/tests/test_domain_edge_cases.py index 2e9afee..25a618d 100644 --- a/tests/test_domain_edge_cases.py +++ b/tests/test_domain_edge_cases.py @@ -1,17 +1,15 @@ """Edge case tests for domain entities and value objects.""" - +import pytest from datetime import datetime -import pytest - -from domain.movies.entities import Movie -from domain.movies.value_objects import MovieTitle, Quality, ReleaseYear -from domain.shared.exceptions import ValidationError -from domain.shared.value_objects import FilePath, FileSize, ImdbId -from domain.subtitles.entities import Subtitle -from domain.subtitles.value_objects import Language, SubtitleFormat, TimingOffset -from domain.tv_shows.entities import TVShow -from domain.tv_shows.value_objects import ShowStatus +from alfred.domain.movies.entities import Movie +from alfred.domain.movies.value_objects import MovieTitle, Quality, ReleaseYear +from alfred.domain.shared.exceptions import ValidationError +from alfred.domain.shared.value_objects import FilePath, FileSize, ImdbId +from alfred.domain.subtitles.entities import Subtitle +from alfred.domain.subtitles.value_objects import Language, SubtitleFormat, TimingOffset +from alfred.domain.tv_shows.entities import TVShow +from alfred.domain.tv_shows.value_objects import ShowStatus class TestImdbIdEdgeCases: diff --git a/tests/test_memory.py b/tests/test_memory.py index ad1ecfe..ce83aa3 100644 --- a/tests/test_memory.py +++ b/tests/test_memory.py @@ -1,10 +1,8 @@ """Tests for the Memory system.""" - +import pytest from datetime import datetime -import pytest - -from infrastructure.persistence import ( +from alfred.infrastructure.persistence import ( EpisodicMemory, LongTermMemory, Memory, @@ -13,7 +11,7 @@ from infrastructure.persistence import ( has_memory, init_memory, ) -from infrastructure.persistence.context import _memory_ctx +from alfred.infrastructure.persistence.context import _memory_ctx def is_iso_format(s: str) -> bool: diff --git a/tests/test_memory_edge_cases.py b/tests/test_memory_edge_cases.py index d320b00..356c90e 100644 --- a/tests/test_memory_edge_cases.py +++ b/tests/test_memory_edge_cases.py @@ -4,8 +4,7 @@ import json import os import pytest - -from infrastructure.persistence import ( +from alfred.infrastructure.persistence import ( EpisodicMemory, LongTermMemory, Memory, @@ -14,7 +13,7 @@ from infrastructure.persistence import ( init_memory, set_memory, ) -from infrastructure.persistence.context import _memory_ctx +from alfred.infrastructure.persistence.context import _memory_ctx class TestLongTermMemoryEdgeCases: diff --git a/tests/test_prompts.py b/tests/test_prompts.py index 28e77be..6cd7539 100644 --- a/tests/test_prompts.py +++ b/tests/test_prompts.py @@ -1,7 +1,7 @@ """Tests for PromptBuilder.""" -from agent.prompts import PromptBuilder -from agent.registry import make_tools +from alfred.agent.prompts import PromptBuilder +from alfred.agent.registry import make_tools class TestPromptBuilder: diff --git a/tests/test_prompts_critical.py b/tests/test_prompts_critical.py index 3ef683c..499514e 100644 --- a/tests/test_prompts_critical.py +++ b/tests/test_prompts_critical.py @@ -1,7 +1,7 @@ """Critical tests for prompt builder - Tests that would have caught bugs.""" -from agent.prompts import PromptBuilder -from agent.registry import make_tools +from alfred.agent.prompts import PromptBuilder +from alfred.agent.registry import make_tools class TestPromptBuilderToolsInjection: diff --git a/tests/test_prompts_edge_cases.py b/tests/test_prompts_edge_cases.py index 5e4a3e8..738b1d7 100644 --- a/tests/test_prompts_edge_cases.py +++ b/tests/test_prompts_edge_cases.py @@ -1,7 +1,7 @@ """Edge case tests for PromptBuilder.""" -from agent.prompts import PromptBuilder -from agent.registry import make_tools +from alfred.agent.prompts import PromptBuilder +from alfred.agent.registry import make_tools class TestPromptBuilderEdgeCases: @@ -266,7 +266,7 @@ class TestFormatToolsDescriptionEdgeCases: def test_format_with_complex_parameters(self, memory): """Should format complex parameter schemas.""" - from agent.registry import Tool + from alfred.agent.registry import Tool tools = { "complex_tool": Tool( diff --git a/tests/test_registry_critical.py b/tests/test_registry_critical.py index ec275e4..5f8a2c2 100644 --- a/tests/test_registry_critical.py +++ b/tests/test_registry_critical.py @@ -3,9 +3,8 @@ import inspect import pytest - -from agent.prompts import PromptBuilder -from agent.registry import Tool, _create_tool_from_function, make_tools +from alfred.agent.prompts import PromptBuilder +from alfred.agent.registry import Tool, _create_tool_from_function, make_tools class TestToolSpecFormat: diff --git a/tests/test_registry_edge_cases.py b/tests/test_registry_edge_cases.py index 9404bca..16a761b 100644 --- a/tests/test_registry_edge_cases.py +++ b/tests/test_registry_edge_cases.py @@ -1,8 +1,7 @@ """Edge case tests for tool registry.""" import pytest - -from agent.registry import Tool, make_tools +from alfred.agent.registry import Tool, make_tools class TestToolEdgeCases: diff --git a/tests/test_repositories.py b/tests/test_repositories.py index b904f4d..c6e9993 100644 --- a/tests/test_repositories.py +++ b/tests/test_repositories.py @@ -1,13 +1,13 @@ """Tests for JSON repositories.""" -from domain.movies.entities import Movie -from domain.movies.value_objects import MovieTitle, Quality, ReleaseYear -from domain.shared.value_objects import FilePath, FileSize, ImdbId -from domain.subtitles.entities import Subtitle -from domain.subtitles.value_objects import Language, SubtitleFormat, TimingOffset -from domain.tv_shows.entities import TVShow -from domain.tv_shows.value_objects import ShowStatus -from infrastructure.persistence.json import ( +from alfred.domain.movies.entities import Movie +from alfred.domain.movies.value_objects import MovieTitle, Quality, ReleaseYear +from alfred.domain.shared.value_objects import FilePath, FileSize, ImdbId +from alfred.domain.subtitles.entities import Subtitle +from alfred.domain.subtitles.value_objects import Language, SubtitleFormat, TimingOffset +from alfred.domain.tv_shows.entities import TVShow +from alfred.domain.tv_shows.value_objects import ShowStatus +from alfred.infrastructure.persistence.json import ( JsonMovieRepository, JsonSubtitleRepository, JsonTVShowRepository, diff --git a/tests/test_repositories_edge_cases.py b/tests/test_repositories_edge_cases.py index f77fe48..97700c6 100644 --- a/tests/test_repositories_edge_cases.py +++ b/tests/test_repositories_edge_cases.py @@ -2,14 +2,14 @@ from datetime import datetime -from domain.movies.entities import Movie -from domain.movies.value_objects import MovieTitle, Quality -from domain.shared.value_objects import FilePath, FileSize, ImdbId -from domain.subtitles.entities import Subtitle -from domain.subtitles.value_objects import Language, SubtitleFormat, TimingOffset -from domain.tv_shows.entities import TVShow -from domain.tv_shows.value_objects import ShowStatus -from infrastructure.persistence.json import ( +from alfred.domain.movies.entities import Movie +from alfred.domain.movies.value_objects import MovieTitle, Quality +from alfred.domain.shared.value_objects import FilePath, FileSize, ImdbId +from alfred.domain.subtitles.entities import Subtitle +from alfred.domain.subtitles.value_objects import Language, SubtitleFormat, TimingOffset +from alfred.domain.tv_shows.entities import TVShow +from alfred.domain.tv_shows.value_objects import ShowStatus +from alfred.infrastructure.persistence.json import ( JsonMovieRepository, JsonSubtitleRepository, JsonTVShowRepository, diff --git a/tests/test_tools_api.py b/tests/test_tools_api.py index 137d2e1..7a3ebb0 100644 --- a/tests/test_tools_api.py +++ b/tests/test_tools_api.py @@ -2,8 +2,8 @@ from unittest.mock import Mock, patch -from agent.tools import api as api_tools -from infrastructure.persistence import get_memory +from alfred.agent.tools import api as api_tools +from alfred.infrastructure.persistence import get_memory def create_mock_response(status_code, json_data=None, text=None): @@ -21,7 +21,7 @@ def create_mock_response(status_code, json_data=None, text=None): class TestFindMediaImdbId: """Tests for find_media_imdb_id tool.""" - @patch("infrastructure.api.tmdb.client.requests.get") + @patch("alfred.infrastructure.api.tmdb.client.requests.get") def test_success(self, mock_get, memory): """Should return movie info on success.""" @@ -56,7 +56,7 @@ class TestFindMediaImdbId: # Verify HTTP calls assert mock_get.call_count == 2 - @patch("infrastructure.api.tmdb.client.requests.get") + @patch("alfred.infrastructure.api.tmdb.client.requests.get") def test_stores_in_stm(self, mock_get, memory): """Should store result in STM on success.""" @@ -88,7 +88,7 @@ class TestFindMediaImdbId: assert entity["title"] == "Inception" assert mem.stm.current_topic == "searching_media" - @patch("infrastructure.api.tmdb.client.requests.get") + @patch("alfred.infrastructure.api.tmdb.client.requests.get") def test_not_found(self, mock_get, memory): """Should return error when not found.""" mock_get.return_value = create_mock_response(200, json_data={"results": []}) @@ -98,7 +98,7 @@ class TestFindMediaImdbId: assert result["status"] == "error" assert result["error"] == "not_found" - @patch("infrastructure.api.tmdb.client.requests.get") + @patch("alfred.infrastructure.api.tmdb.client.requests.get") def test_does_not_store_on_error(self, mock_get, memory): """Should not store in STM on error.""" mock_get.return_value = create_mock_response(200, json_data={"results": []}) @@ -112,7 +112,7 @@ class TestFindMediaImdbId: class TestFindTorrent: """Tests for find_torrent tool.""" - @patch("infrastructure.api.knaben.client.requests.post") + @patch("alfred.infrastructure.api.knaben.client.requests.post") def test_success(self, mock_post, memory): """Should return torrents on success.""" mock_post.return_value = create_mock_response( @@ -146,7 +146,7 @@ class TestFindTorrent: payload = mock_post.call_args[1]["json"] assert payload["query"] == "Inception 1080p" - @patch("infrastructure.api.knaben.client.requests.post") + @patch("alfred.infrastructure.api.knaben.client.requests.post") def test_stores_in_episodic(self, mock_post, memory): """Should store results in episodic memory.""" mock_post.return_value = create_mock_response( @@ -171,7 +171,7 @@ class TestFindTorrent: assert mem.episodic.last_search_results["query"] == "Inception" assert mem.stm.current_topic == "selecting_torrent" - @patch("infrastructure.api.knaben.client.requests.post") + @patch("alfred.infrastructure.api.knaben.client.requests.post") def test_results_have_indexes(self, mock_post, memory): """Should add indexes to results.""" mock_post.return_value = create_mock_response( @@ -211,7 +211,7 @@ class TestFindTorrent: assert results[1]["index"] == 2 assert results[2]["index"] == 3 - @patch("infrastructure.api.knaben.client.requests.post") + @patch("alfred.infrastructure.api.knaben.client.requests.post") def test_not_found(self, mock_post, memory): """Should return error when no torrents found.""" mock_post.return_value = create_mock_response(200, json_data={"hits": []}) @@ -286,7 +286,7 @@ class TestAddTorrentToQbittorrent: This is acceptable mocking because we're testing the TOOL logic, not the client. """ - @patch("agent.tools.api.qbittorrent_client") + @patch("alfred.agent.tools.api.qbittorrent_client") def test_success(self, mock_client, memory): """Should add torrent successfully and update memory.""" mock_client.add_torrent.return_value = True @@ -298,7 +298,7 @@ class TestAddTorrentToQbittorrent: # Verify client was called correctly mock_client.add_torrent.assert_called_once_with("magnet:?xt=urn:btih:abc123") - @patch("agent.tools.api.qbittorrent_client") + @patch("alfred.agent.tools.api.qbittorrent_client") def test_adds_to_active_downloads(self, mock_client, memory_with_search_results): """Should add to active downloads on success.""" mock_client.add_torrent.return_value = True @@ -313,7 +313,7 @@ class TestAddTorrentToQbittorrent: == "Inception.2010.1080p.BluRay.x264" ) - @patch("agent.tools.api.qbittorrent_client") + @patch("alfred.agent.tools.api.qbittorrent_client") def test_sets_topic_and_ends_workflow(self, mock_client, memory): """Should set topic and end workflow.""" mock_client.add_torrent.return_value = True @@ -326,10 +326,10 @@ class TestAddTorrentToQbittorrent: assert mem.stm.current_topic == "downloading" assert mem.stm.current_workflow is None - @patch("agent.tools.api.qbittorrent_client") + @patch("alfred.agent.tools.api.qbittorrent_client") def test_error_handling(self, mock_client, memory): """Should handle client errors correctly.""" - from infrastructure.api.qbittorrent.exceptions import QBittorrentAPIError + from alfred.infrastructure.api.qbittorrent.exceptions import QBittorrentAPIError mock_client.add_torrent.side_effect = QBittorrentAPIError("Connection failed") @@ -349,7 +349,7 @@ class TestAddTorrentByIndex: - Error handling for edge cases """ - @patch("agent.tools.api.qbittorrent_client") + @patch("alfred.agent.tools.api.qbittorrent_client") def test_success(self, mock_client, memory_with_search_results): """Should get torrent by index and add it.""" mock_client.add_torrent.return_value = True @@ -362,7 +362,7 @@ class TestAddTorrentByIndex: # Verify correct magnet was extracted and used mock_client.add_torrent.assert_called_once_with("magnet:?xt=urn:btih:abc123") - @patch("agent.tools.api.qbittorrent_client") + @patch("alfred.agent.tools.api.qbittorrent_client") def test_uses_correct_magnet(self, mock_client, memory_with_search_results): """Should extract correct magnet from index.""" mock_client.add_torrent.return_value = True diff --git a/tests/test_tools_edge_cases.py b/tests/test_tools_edge_cases.py index 23fb50e..ac24aa2 100644 --- a/tests/test_tools_edge_cases.py +++ b/tests/test_tools_edge_cases.py @@ -1,18 +1,16 @@ """Edge case tests for tools.""" - +import pytest from unittest.mock import Mock, patch -import pytest - -from agent.tools import api as api_tools -from agent.tools import filesystem as fs_tools -from infrastructure.persistence import get_memory +from alfred.agent.tools import api as api_tools +from alfred.agent.tools import filesystem as fs_tools +from alfred.infrastructure.persistence import get_memory class TestFindTorrentEdgeCases: """Edge case tests for find_torrent.""" - @patch("agent.tools.api.SearchTorrentsUseCase") + @patch("alfred.agent.tools.api.SearchTorrentsUseCase") def test_empty_query(self, mock_use_case_class, memory): """Should handle empty query.""" mock_response = Mock() @@ -28,7 +26,7 @@ class TestFindTorrentEdgeCases: assert result["status"] == "error" - @patch("agent.tools.api.SearchTorrentsUseCase") + @patch("alfred.agent.tools.api.SearchTorrentsUseCase") def test_very_long_query(self, mock_use_case_class, memory): """Should handle very long query.""" mock_response = Mock() @@ -47,7 +45,7 @@ class TestFindTorrentEdgeCases: # Should not crash assert "status" in result - @patch("agent.tools.api.SearchTorrentsUseCase") + @patch("alfred.agent.tools.api.SearchTorrentsUseCase") def test_special_characters_in_query(self, mock_use_case_class, memory): """Should handle special characters in query.""" mock_response = Mock() @@ -65,7 +63,7 @@ class TestFindTorrentEdgeCases: assert "status" in result - @patch("agent.tools.api.SearchTorrentsUseCase") + @patch("alfred.agent.tools.api.SearchTorrentsUseCase") def test_unicode_query(self, mock_use_case_class, memory): """Should handle unicode in query.""" mock_response = Mock() @@ -82,7 +80,7 @@ class TestFindTorrentEdgeCases: assert "status" in result - @patch("agent.tools.api.SearchTorrentsUseCase") + @patch("alfred.agent.tools.api.SearchTorrentsUseCase") def test_results_with_missing_fields(self, mock_use_case_class, memory): """Should handle results with missing fields.""" mock_response = Mock() @@ -104,7 +102,7 @@ class TestFindTorrentEdgeCases: mem = get_memory() assert len(mem.episodic.last_search_results["results"]) == 2 - @patch("agent.tools.api.SearchTorrentsUseCase") + @patch("alfred.agent.tools.api.SearchTorrentsUseCase") def test_api_timeout(self, mock_use_case_class, memory): """Should handle API timeout.""" mock_use_case = Mock() @@ -157,7 +155,7 @@ class TestGetTorrentByIndexEdgeCases: class TestAddTorrentEdgeCases: """Edge case tests for add_torrent functions.""" - @patch("agent.tools.api.AddTorrentUseCase") + @patch("alfred.agent.tools.api.AddTorrentUseCase") def test_invalid_magnet_link(self, mock_use_case_class, memory): """Should handle invalid magnet link.""" mock_response = Mock() @@ -173,7 +171,7 @@ class TestAddTorrentEdgeCases: assert result["status"] == "error" - @patch("agent.tools.api.AddTorrentUseCase") + @patch("alfred.agent.tools.api.AddTorrentUseCase") def test_empty_magnet_link(self, mock_use_case_class, memory): """Should handle empty magnet link.""" mock_response = Mock() @@ -189,7 +187,7 @@ class TestAddTorrentEdgeCases: assert result["status"] == "error" - @patch("agent.tools.api.AddTorrentUseCase") + @patch("alfred.agent.tools.api.AddTorrentUseCase") def test_very_long_magnet_link(self, mock_use_case_class, memory): """Should handle very long magnet link.""" mock_response = Mock() @@ -203,7 +201,7 @@ class TestAddTorrentEdgeCases: assert "status" in result - @patch("agent.tools.api.AddTorrentUseCase") + @patch("alfred.agent.tools.api.AddTorrentUseCase") def test_qbittorrent_connection_refused(self, mock_use_case_class, memory): """Should handle qBittorrent connection refused.""" mock_use_case = Mock() @@ -391,7 +389,7 @@ class TestFilesystemEdgeCases: class TestFindMediaImdbIdEdgeCases: """Edge case tests for find_media_imdb_id.""" - @patch("agent.tools.api.SearchMovieUseCase") + @patch("alfred.agent.tools.api.SearchMovieUseCase") def test_movie_with_same_name_different_years(self, mock_use_case_class, memory): """Should handle movies with same name.""" mock_response = Mock() @@ -409,7 +407,7 @@ class TestFindMediaImdbIdEdgeCases: assert result["status"] == "ok" - @patch("agent.tools.api.SearchMovieUseCase") + @patch("alfred.agent.tools.api.SearchMovieUseCase") def test_movie_with_special_title(self, mock_use_case_class, memory): """Should handle movies with special characters in title.""" mock_response = Mock() @@ -426,7 +424,7 @@ class TestFindMediaImdbIdEdgeCases: assert result["status"] == "ok" - @patch("agent.tools.api.SearchMovieUseCase") + @patch("alfred.agent.tools.api.SearchMovieUseCase") def test_tv_show_vs_movie(self, mock_use_case_class, memory): """Should distinguish TV shows from movies.""" mock_response = Mock() diff --git a/tests/test_tools_filesystem.py b/tests/test_tools_filesystem.py index 706a477..21c8836 100644 --- a/tests/test_tools_filesystem.py +++ b/tests/test_tools_filesystem.py @@ -1,11 +1,9 @@ """Tests for filesystem tools.""" - +import pytest from pathlib import Path -import pytest - -from agent.tools import filesystem as fs_tools -from infrastructure.persistence import get_memory +from alfred.agent.tools import filesystem as fs_tools +from alfred.infrastructure.persistence import get_memory class TestSetPathForFolder: -- 2.49.1 From 3880a4ec4971d73c91c0ecfbd8e4e59d5795d784 Mon Sep 17 00:00:00 2001 From: Francwa Date: Sat, 27 Dec 2025 19:41:22 +0100 Subject: [PATCH 11/22] chore: ran linter and formatter --- alfred/agent/prompts.py | 2 +- tests/test_prompts_critical.py | 6 +++--- tests/test_registry_critical.py | 18 +++++++++--------- tests/test_registry_edge_cases.py | 12 ++++++------ tests/test_repositories.py | 4 ++-- 5 files changed, 21 insertions(+), 21 deletions(-) diff --git a/alfred/agent/prompts.py b/alfred/agent/prompts.py index f43bc85..d568b9d 100644 --- a/alfred/agent/prompts.py +++ b/alfred/agent/prompts.py @@ -52,7 +52,7 @@ class PromptBuilder: # Show first 5 results for i, result in enumerate(result_list[:5]): name = result.get("name", "Unknown") - lines.append(f" {i+1}. {name}") + lines.append(f" {i + 1}. {name}") if len(result_list) > 5: lines.append(f" ... and {len(result_list) - 5} more") diff --git a/tests/test_prompts_critical.py b/tests/test_prompts_critical.py index 499514e..bd34041 100644 --- a/tests/test_prompts_critical.py +++ b/tests/test_prompts_critical.py @@ -15,9 +15,9 @@ class TestPromptBuilderToolsInjection: # Verify each tool is mentioned for tool_name in tools.keys(): - assert ( - tool_name in prompt - ), f"Tool {tool_name} not mentioned in system prompt" + assert tool_name in prompt, ( + f"Tool {tool_name} not mentioned in system prompt" + ) def test_tools_spec_contains_all_registered_tools(self, memory): """CRITICAL: Verify build_tools_spec() returns all tools.""" diff --git a/tests/test_registry_critical.py b/tests/test_registry_critical.py index 5f8a2c2..7143849 100644 --- a/tests/test_registry_critical.py +++ b/tests/test_registry_critical.py @@ -22,9 +22,9 @@ class TestToolSpecFormat: for spec in specs: # OpenAI format requires these fields - assert ( - spec["type"] == "function" - ), f"Tool type must be 'function', got {spec.get('type')}" + assert spec["type"] == "function", ( + f"Tool type must be 'function', got {spec.get('type')}" + ) assert "function" in spec, "Tool spec missing 'function' key" func = spec["function"] @@ -55,9 +55,9 @@ class TestToolSpecFormat: # Verify required vs optional assert "name" in tool.parameters["required"], "name should be required" assert "age" in tool.parameters["required"], "age should be required" - assert ( - "active" not in tool.parameters["required"] - ), "active has default, should not be required" + assert "active" not in tool.parameters["required"], ( + "active has default, should not be required" + ) def test_all_registered_tools_are_callable(self): """CRITICAL: Verify all registered tools are actually callable.""" @@ -127,9 +127,9 @@ class TestToolSpecFormat: properties = params.get("properties", {}) for param_name, param_spec in properties.items(): - assert ( - "description" in param_spec - ), f"Parameter {param_name} in {spec['function']['name']} missing description" + assert "description" in param_spec, ( + f"Parameter {param_name} in {spec['function']['name']} missing description" + ) def test_required_parameters_are_marked_correctly(self): """Verify required parameters are correctly identified.""" diff --git a/tests/test_registry_edge_cases.py b/tests/test_registry_edge_cases.py index 16a761b..10b45f7 100644 --- a/tests/test_registry_edge_cases.py +++ b/tests/test_registry_edge_cases.py @@ -181,9 +181,9 @@ class TestMakeToolsEdgeCases: params = tool.parameters if "required" in params and "properties" in params: for req in params["required"]: - assert ( - req in params["properties"] - ), f"Required param {req} not in properties for {tool.name}" + assert req in params["properties"], ( + f"Required param {req} not in properties for {tool.name}" + ) def test_make_tools_descriptions_not_empty(self, memory): """Should have non-empty descriptions.""" @@ -234,9 +234,9 @@ class TestMakeToolsEdgeCases: if "properties" in tool.parameters: for prop_name, prop_schema in tool.parameters["properties"].items(): if "type" in prop_schema: - assert ( - prop_schema["type"] in valid_types - ), f"Invalid type for {tool.name}.{prop_name}" + assert prop_schema["type"] in valid_types, ( + f"Invalid type for {tool.name}.{prop_name}" + ) def test_make_tools_enum_values(self, memory): """Should have valid enum values.""" diff --git a/tests/test_repositories.py b/tests/test_repositories.py index c6e9993..a323d60 100644 --- a/tests/test_repositories.py +++ b/tests/test_repositories.py @@ -226,13 +226,13 @@ class TestJsonTVShowRepository: [ShowStatus.ONGOING, ShowStatus.ENDED, ShowStatus.UNKNOWN] ): show = TVShow( - imdb_id=ImdbId(f"tt{i+1000000:07d}"), + imdb_id=ImdbId(f"tt{i + 1000000:07d}"), title=f"Show {status.value}", seasons_count=1, status=status, ) repo.save(show) - loaded = repo.find_by_imdb_id(ImdbId(f"tt{i+1000000:07d}")) + loaded = repo.find_by_imdb_id(ImdbId(f"tt{i + 1000000:07d}")) assert loaded.status == status -- 2.49.1 From fed83e7d79db0fd95f3d7e666d3573336cef2bed Mon Sep 17 00:00:00 2001 From: Francwa Date: Sat, 27 Dec 2025 19:42:16 +0100 Subject: [PATCH 12/22] chore: bumped fastapi version --- poetry.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/poetry.lock b/poetry.lock index 2e6481c..bb559a5 100644 --- a/poetry.lock +++ b/poetry.lock @@ -372,13 +372,13 @@ testing = ["hatch", "pre-commit", "pytest", "tox"] [[package]] name = "fastapi" -version = "0.127.0" +version = "0.127.1" description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production" optional = false python-versions = ">=3.9" files = [ - {file = "fastapi-0.127.0-py3-none-any.whl", hash = "sha256:725aa2bb904e2eff8031557cf4b9b77459bfedd63cae8427634744fd199f6a49"}, - {file = "fastapi-0.127.0.tar.gz", hash = "sha256:5a9246e03dcd1fdb19f1396db30894867c1d630f5107dc167dcbc5ed1ea7d259"}, + {file = "fastapi-0.127.1-py3-none-any.whl", hash = "sha256:31d670a4f9373cc6d7994420f98e4dc46ea693145207abc39696746c83a44430"}, + {file = "fastapi-0.127.1.tar.gz", hash = "sha256:946a87ee5d931883b562b6bada787d6c8178becee2683cb3f9b980d593206359"}, ] [package.dependencies] -- 2.49.1 From 20a113e3359917c28047a96eda53357abdf98488 Mon Sep 17 00:00:00 2001 From: Francwa Date: Sat, 27 Dec 2025 19:43:31 +0100 Subject: [PATCH 13/22] infra: updated .env.example --- .env.example | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.env.example b/.env.example index e2c28f7..ded7737 100644 --- a/.env.example +++ b/.env.example @@ -2,12 +2,15 @@ # Deepseek API Key (for LLM in alfred) DEEPSEEK_API_KEY= +OLLAMA_BASE_URL= +OLLAMA_MODEL= + # Alfred Configuration # LLM Provider (deepseek or ollama) LLM_PROVIDER=deepseek # Memory storage directory (inside container) -MEMORY_STORAGE_DIR=/data/memory +MEMORY_STORAGE_DIR=data/memory # External Services (Optional) # TMDB API Key (for movie metadata) @@ -21,3 +24,9 @@ QBITTORRENT_PASSWORD=adminadmin # Debug Options DEBUG_LOGGING=false DEBUG_CONSOLE=false + +# Required security keys +JWT_SECRET= +JWT_REFRESH_SECRET= +CREDS_KEY= +CREDS_IV= -- 2.49.1 From 253903a1e579e4161afe3e8bba8139fb0529b2bc Mon Sep 17 00:00:00 2001 From: Francwa Date: Sat, 27 Dec 2025 19:46:27 +0100 Subject: [PATCH 14/22] infra: made pyproject the SSOT --- pyproject.toml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 967da51..423ae5c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,6 +6,13 @@ authors = ["Francwa "] readme = "README.md" package-mode = false +[tool.alfred] +image_name = "alfred_media_organizer" +librechat_version = "v0.8.1" +rag_version = "v0.7.0" +runner = "poetry" +service_name = "alfred" + [tool.poetry.dependencies] python = "==3.14.2" python-dotenv = "^1.0.0" -- 2.49.1 From 261a1f3918fd179b25ced9bc30995333256da7c8 Mon Sep 17 00:00:00 2001 From: Francwa Date: Sat, 27 Dec 2025 19:48:13 +0100 Subject: [PATCH 15/22] fix: fixed real data directory being used in tests --- tests/conftest.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index ff87c6f..462d701 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -21,6 +21,16 @@ def temp_dir(): shutil.rmtree(dirpath) +@pytest.fixture(autouse=True) +def mock_memory_storage_dir(monkeypatch): + """Override MEMORY_STORAGE_DIR for all tests to use a temp directory.""" + test_dir = tempfile.mkdtemp() + monkeypatch.setenv("MEMORY_STORAGE_DIR", test_dir) + yield + # Cleanup + shutil.rmtree(test_dir, ignore_errors=True) + + @pytest.fixture def memory(temp_dir): """Create a fresh Memory instance for testing.""" -- 2.49.1 From 2441c2dc29129df16170637a51e375b865790c95 Mon Sep 17 00:00:00 2001 From: Francwa Date: Sat, 27 Dec 2025 19:49:43 +0100 Subject: [PATCH 16/22] infra: rewrote docker-compose for proper integration of librechat --- docker-compose.yaml | 100 ++++++++++++++++++++ docker-compose.yml_bak | 202 ----------------------------------------- 2 files changed, 100 insertions(+), 202 deletions(-) create mode 100644 docker-compose.yaml delete mode 100644 docker-compose.yml_bak diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..3b3080f --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,100 @@ +services: + alfred: + container_name: alfred-core + build: + context: . + args: + PYTHON_VERSION: ${PYTHON_VERSION} + PYTHON_VERSION_SHORT: ${PYTHON_VERSION_SHORT} + RUNNER: ${RUNNER} + depends_on: + - librechat + restart: unless-stopped + env_file: + - .env + environment: + # LLM Configuration + LLM_PROVIDER: ${LLM_PROVIDER:-deepseek} + DEEPSEEK_API_KEY: ${DEEPSEEK_API_KEY:-} + + # Memory storage + MEMORY_STORAGE_DIR: /data/memory + + # External services + TMDB_API_KEY: ${TMDB_API_KEY:-} + QBITTORRENT_URL: ${QBITTORRENT_URL:-} + QBITTORRENT_USERNAME: ${QBITTORRENT_USERNAME:-} + QBITTORRENT_PASSWORD: ${QBITTORRENT_PASSWORD:-} + volumes: + - ./data/memory:/data/memory + - ./logs:/data/logs + # TODO: Development: mount code for hot reload (comment out in production) + # - ./alfred:/app/alfred + + librechat: + container_name: alfred-librechat + image: ghcr.io/danny-avila/librechat:${LIBRECHAT_VERSION} + depends_on: + - mongodb + - meilisearch + - rag_api + restart: unless-stopped + env_file: + - .env + environment: + - HOST=0.0.0.0 + - MONGO_URI=mongodb://mongodb:27017/LibreChat + - MEILI_HOST=http://meilisearch:7700 + - RAG_PORT=${RAG_PORT:-8000} + - RAG_API_URL=http://rag_api:${RAG_PORT:-8000} + ports: + - "${LIBRECHAT_PORT:-3080}:3080" + volumes: + - ./data/librechat/images:/app/client/public/images + - ./data/librechat/uploads:/app/client/uploads + - ./logs:/app/api/logs + # Mount custom endpoint + - ./librechat/manifests:/app/manifests:ro + - ./librechat/librechat.yaml:/app/librechat.yaml:ro + + mongodb: + container_name: alfred-mongodb + image: mongo:latest + restart: unless-stopped + volumes: + - ./data/mongo:/data/db + command: mongod --noauth + + meilisearch: + container_name: alfred-meilisearch + image: getmeili/meilisearch:v1.12.3 + restart: unless-stopped + environment: + - MEILI_NO_ANALYTICS=true + volumes: + - ./data/meili:/meili_data + #profiles: ["meili", "full"] + + rag_api: + container_name: alfred-rag + image: ghcr.io/danny-avila/librechat-rag-api-dev-lite:${RAG_VERSION} + restart: unless-stopped + environment: + - RAG_PORT=${RAG_PORT:-8000} + ports: + - "${RAG_PORT:-8000}:${RAG_PORT:-8000}" + #profiles: ["rag", "full"] + + vectordb: + container_name: alfred-vectordb + image: pgvector/pgvector:0.8.0-pg16-bookworm + restart: unless-stopped + environment: + - POSTGRES_DB=${VECTOR_DB_NAME:-vectordb} + - POSTGRES_USER=${VECTOR_DB_USER:-postgres} + - POSTGRES_PASSWORD=${VECTOR_DB_PASSWORD:-postgres} + ports: + - "${VECTOR_DB_PORT:-5432}:5432" + volumes: + - ./data/vectordb:/var/lib/postgresql/data + #profiles: ["rag", "full"] \ No newline at end of file diff --git a/docker-compose.yml_bak b/docker-compose.yml_bak deleted file mode 100644 index a9b7017..0000000 --- a/docker-compose.yml_bak +++ /dev/null @@ -1,202 +0,0 @@ -version: "3.4" - -services: - alfred: - build: - context: . - args: - RUNNER: ${RUNNER} # Get it from Makefile - container_name: alfred - restart: unless-stopped - env_file: .env - ports: - - "8000:8000" - volumes: - # Persistent data volumes (outside container /app) - - agent-memory:/data/memory - - agent-logs:/data/logs - # Development: mount code for hot reload (comment out in production) - # - ./alfred:/app - environment: - # LLM Configuration - LLM_PROVIDER: ${LLM_PROVIDER:-deepseek} - DEEPSEEK_API_KEY: ${DEEPSEEK_API_KEY:-} - - # Memory storage - MEMORY_STORAGE_DIR: /data/memory - - # External services - TMDB_API_KEY: ${TMDB_API_KEY:-} - QBITTORRENT_URL: ${QBITTORRENT_URL:-} - QBITTORRENT_USERNAME: ${QBITTORRENT_USERNAME:-} - QBITTORRENT_PASSWORD: ${QBITTORRENT_PASSWORD:-} - networks: - - agent-network - - # Da face (LibreChat) - librechat: - image: ghcr.io/danny-avila/librechat-dev:latest - container_name: librechat-frontend - restart: unless-stopped - ports: - - "3080:3080" - depends_on: - - mongodb - - meilisearch - - rag_api - - alfred - env_file: .env - environment: - # MongoDB connection (no auth, matching LibreChat default) - MONGO_URI: mongodb://mongodb:27017/LibreChat - - # App configuration - HOST: 0.0.0.0 - PORT: 3080 - - # Security - JWT_SECRET: ${JWT_SECRET:-your-super-secret-jwt-key-change-this-in-production} - JWT_REFRESH_SECRET: ${JWT_REFRESH_SECRET:-your-super-secret-refresh-key-change-this-too} - CREDS_KEY: ${CREDS_KEY:-your-32-character-secret-key-here} - CREDS_IV: ${CREDS_IV:-your-16-character-iv-here} - - # Session - SESSION_EXPIRY: ${SESSION_EXPIRY:-1000 * 60 * 15} - REFRESH_TOKEN_EXPIRY: ${REFRESH_TOKEN_EXPIRY:-1000 * 60 * 60 * 24 * 7} - - # Domain - DOMAIN_CLIENT: ${DOMAIN_CLIENT:-http://localhost:3080} - DOMAIN_SERVER: ${DOMAIN_SERVER:-http://localhost:3080} - - # Meilisearch - MEILI_HOST: http://meilisearch:7700 - MEILI_MASTER_KEY: ${MEILI_MASTER_KEY:-DrhYf7zENyR6AlUCKmnz0eYASOQdl6zxH7s7MKFSfFU} - - # RAG API - RAG_API_URL: http://rag_api:8000 - - # Endpoints - ENDPOINTS: custom - - # Debug (optional) - DEBUG_LOGGING: ${DEBUG_LOGGING:-false} - DEBUG_CONSOLE: ${DEBUG_CONSOLE:-false} - volumes: - - ./librechat/librechat.yaml:/app/librechat.yaml:ro - - librechat-images:/app/client/public/images - - librechat-logs:/app/api/logs - networks: - - agent-network - - # MongoDB for LibreChat - mongodb: - image: mongo:latest - container_name: librechat-mongodb - restart: unless-stopped - volumes: - - mongodb-data:/data/db - command: mongod --noauth - ports: - - "27017:27017" - networks: - - agent-network - - # Meilisearch - Search engine for LibreChat - #TODO: Follow currently used version on librechat's github - meilisearch: - image: getmeili/meilisearch:v1.12.3 - container_name: librechat-meilisearch - restart: unless-stopped - volumes: - - meilisearch-data:/meili_data - environment: - MEILI_HOST: http://meilisearch:7700 - MEILI_HTTP_ADDR: meilisearch:7700 - MEILI_MASTER_KEY: ${MEILI_MASTER_KEY:-DrhYf7zENyR6AlUCKmnz0eYASOQdl6zxH7s7MKFSfFU} - ports: - - "7700:7700" - networks: - - agent-network - - # PostgreSQL with pgvector for RAG API - pgvector: - image: ankane/pgvector:latest - container_name: librechat-pgvector - restart: unless-stopped - environment: - POSTGRES_DB: ${POSTGRES_DB:-librechat_rag} - POSTGRES_USER: ${POSTGRES_USER:-postgres} - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres} - volumes: - - pgvector-data:/var/lib/postgresql/data - ports: - - "5432:5432" - networks: - - agent-network - - # RAG API - Vector database for LibreChat - rag_api: - image: ghcr.io/danny-avila/librechat-rag-api-dev-lite:latest - container_name: librechat-rag-api - restart: unless-stopped - depends_on: - - pgvector - environment: - PORT: 8000 - HOST: 0.0.0.0 - # PostgreSQL connection (multiple variable names for compatibility) - DB_HOST: pgvector - DB_PORT: 5432 - DB_NAME: ${POSTGRES_DB:-librechat_rag} - DB_USER: ${POSTGRES_USER:-postgres} - DB_PASSWORD: ${POSTGRES_PASSWORD:-postgres} - POSTGRES_DB: ${POSTGRES_DB:-librechat_rag} - POSTGRES_USER: ${POSTGRES_USER:-postgres} - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres} - # RAG configuration - COLLECTION_NAME: ${RAG_COLLECTION_NAME:-testcollection} - EMBEDDINGS_PROVIDER: ${RAG_EMBEDDINGS_PROVIDER:-openai} - EMBEDDINGS_MODEL: ${RAG_EMBEDDINGS_MODEL:-text-embedding-3-small} - OPENAI_API_KEY: ${OPENAI_API_KEY:-} - RAG_UPLOAD_DIR: /app/uploads - volumes: - - rag-uploads:/app/uploads - ports: - - "8001:8000" - networks: - - agent-network - -# Named volumes for persistent data -volumes: - # MongoDB data - mongodb-data: - driver: local - - # Meilisearch data - meilisearch-data: - driver: local - - # PostgreSQL pgvector data - pgvector-data: - driver: local - - # RAG API uploads - rag-uploads: - driver: local - - # LibreChat data - librechat-images: - driver: local - librechat-logs: - driver: local - - # Alfred data - agent-memory: - driver: local - agent-logs: - driver: local - -# Network for inter-service communication -networks: - agent-network: - driver: bridge -- 2.49.1 From 2cfe7a035bdeac871e34466ef722d103fee1e515 Mon Sep 17 00:00:00 2001 From: Francwa Date: Sat, 27 Dec 2025 19:51:40 +0100 Subject: [PATCH 17/22] infra: moved makefile logic to python and added .env setup --- Makefile | 401 ++++++++++++++++++++--------------------------- cli.py | 232 +++++++++++++++++++++++++++ generate-keys.sh | 27 ---- 3 files changed, 403 insertions(+), 257 deletions(-) create mode 100644 cli.py delete mode 100755 generate-keys.sh diff --git a/Makefile b/Makefile index 250bbbd..6734100 100644 --- a/Makefile +++ b/Makefile @@ -1,245 +1,155 @@ -.POSIX: -.SUFFIXES: .DEFAULT_GOAL := help -# --- SETTINGS --- -IMAGE_NAME = alfred_media_organizer -# renovate: datasource=docker depName=python -PYTHON_VERSION = $(shell grep "python" pyproject.toml | head -n 1 | sed -E 's/.*[=<>^~"]+ *([0-9]+\.[0-9]+(\.[0-9]+)?).*/\1/') -PYTHON_VERSION_SHORT = $(shell echo $(PYTHON_VERSION) | cut -d. -f1,2) -# Change to 'uv' when ready. -RUNNER ?= poetry -SERVICE_NAME = alfred +# --- Config --- +export IMAGE_NAME := alfred_media_organizer +export LIBRECHAT_VERSION := v0.8.1 +export PYTHON_VERSION := 3.14.2 +export PYTHON_VERSION_SHORT := 3.14 +export RAG_VERSION := v0.7.0 +export RUNNER := poetry +export SERVICE_NAME := alfred -export IMAGE_NAME -export PYTHON_VERSION -export PYTHON_VERSION_SHORT -export RUNNER +# --- Commands --- +CLI := python3 cli.py +DOCKER_COMPOSE := docker compose +DOCKER_BUILD := docker build \ + --build-arg PYTHON_VERSION=$(PYTHON_VERSION) \ + --build-arg PYTHON_VERSION_SHORT=$(PYTHON_VERSION_SHORT) \ + --build-arg RUNNER=$(RUNNER) -# --- ADAPTERS --- -# UV uses "sync", Poetry uses "install". Both install DEV deps by default. -INSTALL_CMD = $(if $(filter uv,$(RUNNER)),sync,install) +# --- Phony --- +.PHONY: setup status check +.PHONY: up down restart logs ps shell +.PHONY: build build-test +.PHONY: install update install-hooks +.PHONY: test coverage lint format clean prune +.PHONY: major minor patch +.PHONY: help -# --- MACROS --- -ARGS = $(filter-out $@,$(MAKECMDGOALS)) -BUMP_CMD = $(RUNNER) run bump-my-version bump -COMPOSE_CMD = docker-compose -DOCKER_CMD = docker build \ - --build-arg PYTHON_VERSION=$(PYTHON_VERSION) \ - --build-arg PYTHON_VERSION_SHORT=$(PYTHON_VERSION_SHORT) \ - --build-arg RUNNER=$(RUNNER) \ - -t $(IMAGE_NAME):latest . +# --- Setup --- +setup: + @echo "Initializing environment..." + @$(CLI) setup \ + && echo "✓ Environment ready" \ + || (echo "✗ Setup failed" && exit 1) -RUNNER_ADD = $(RUNNER) add -RUNNER_HOOKS = $(RUNNER) run pre-commit install -c ../.pre-commit-config.yaml -RUNNER_INSTALL = $(RUNNER) $(INSTALL_CMD) -RUNNER_RUN = $(RUNNER) run -RUNNER_UPDATE = $(RUNNER) update +status: + @$(CLI) status -# --- STYLES --- -B = \033[1m -G = \033[32m -T = \033[36m -R = \033[0m +check: + @$(CLI) check -# --- TARGETS --- -.PHONY: add build build-test check-docker check-runner clean coverage down format help init-dotenv install install-hooks lint logs major minor patch prune ps python-version restart run shell test up update _check_branch _ci-dump-config _ci-run-tests _push_tag +# --- Docker --- +up: check + @echo "Starting containers..." + @$(DOCKER_COMPOSE) up -d --remove-orphans \ + && echo "✓ Containers started" \ + || (echo "✗ Failed to start containers" && exit 1) -# Catch-all for args -%: - @: +down: + @echo "Stopping containers..." + @$(DOCKER_COMPOSE) down \ + && echo "✓ Containers stopped" \ + || (echo "✗ Failed to stop containers" && exit 1) -add: check-runner - @echo "$(T)➕ Adding dependency ($(RUNNER)): $(ARGS)$(R)" - $(RUNNER_ADD) $(ARGS) +restart: + @echo "Restarting containers..." + @$(DOCKER_COMPOSE) restart \ + && echo "✓ Containers restarted" \ + || (echo "✗ Failed to restart containers" && exit 1) -build: check-docker - @echo "$(T)🐳 Building Docker image...$(R)" - $(DOCKER_CMD) - @echo "✅ Image $(IMAGE_NAME):latest ready." +logs: + @echo "Following logs (Ctrl+C to exit)..." + @$(DOCKER_COMPOSE) logs -f -build-test: check-docker - @echo "$(T)🐳 Building test image (with dev deps)...$(R)" - docker build \ - --build-arg RUNNER=$(RUNNER) \ - --build-arg PYTHON_VERSION=$(PYTHON_VERSION) \ - --build-arg PYTHON_VERSION_SHORT=$(PYTHON_VERSION_SHORT) \ - --target test \ - -t $(IMAGE_NAME):test . - @echo "✅ Test image $(IMAGE_NAME):test ready." +ps: + @echo "Container status:" + @$(DOCKER_COMPOSE) ps -check-docker: - @command -v docker >/dev/null 2>&1 || { echo "$(R)❌ Docker not installed$(R)"; exit 1; } - @docker info >/dev/null 2>&1 || { echo "$(R)❌ Docker daemon not running$(R)"; exit 1; } +shell: + @echo "Opening shell in $(SERVICE_NAME)..." + @$(DOCKER_COMPOSE) exec $(SERVICE_NAME) /bin/bash -check-runner: - @command -v $(RUNNER) >/dev/null 2>&1 || { echo "$(R)❌ $(RUNNER) not installed$(R)"; exit 1; } +# --- Build --- +build: check + @echo "Building image $(IMAGE_NAME):latest ..." + @$(DOCKER_BUILD) -t $(IMAGE_NAME):latest . \ + && echo "✓ Build complete" \ + || (echo "✗ Build failed" && exit 1) + +build-test: check + @echo "Building test image $(IMAGE_NAME):test..." + @$(DOCKER_BUILD) --target test -t $(IMAGE_NAME):test . \ + && echo "✓ Test image built" \ + || (echo "✗ Build failed" && exit 1) + +# --- Dependencies --- +install: + @echo "Installing dependencies with $(RUNNER)..." + @$(RUNNER) install \ + && echo "✓ Dependencies installed" \ + || (echo "✗ Installation failed" && exit 1) + +update: + @echo "Updating dependencies with $(RUNNER)..." + @$(RUNNER) update \ + && echo "✓ Dependencies updated" \ + || (echo "✗ Update failed" && exit 1) + +install-hooks: + @echo "Installing pre-commit hooks..." + @$(RUNNER) run pre-commit install \ + && echo "✓ Hooks installed" \ + || (echo "✗ Hook installation failed" && exit 1) + +# --- Quality --- +test: + @echo "Running tests..." + @$(RUNNER) run pytest \ + && echo "✓ Tests passed" \ + || (echo "✗ Tests failed" && exit 1) + +coverage: + @echo "Running tests with coverage..." + @$(RUNNER) run pytest --cov=. --cov-report=html --cov-report=term \ + && echo "✓ Coverage report generated" \ + || (echo "✗ Coverage failed" && exit 1) + +lint: + @echo "Linting code..." + @$(RUNNER) run ruff check --fix . \ + && echo "✓ Linting complete" \ + || (echo "✗ Linting failed" && exit 1) + +format: + @echo "Formatting code..." + @$(RUNNER) run ruff format . && $(RUNNER) run ruff check --fix . \ + && echo "✓ Code formatted" \ + || (echo "✗ Formatting failed" && exit 1) clean: - @echo "$(T)🧹 Cleaning caches...$(R)" - rm -rf .ruff_cache __pycache__ .pytest_cache - find . -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true - find . -type d -name ".pytest_cache" -exec rm -rf {} + 2>/dev/null || true - find . -type f -name "*.pyc" -delete 2>/dev/null || true - @echo "✅ Caches cleaned." + @echo "Cleaning build artifacts..." + @rm -rf .ruff_cache __pycache__ .pytest_cache htmlcov .coverage + @find . -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true + @echo "✓ Cleanup complete" -coverage: check-runner - @echo "$(T)📊 Running tests with coverage...$(R)" - $(RUNNER_RUN) pytest --cov=. --cov-report=html --cov-report=term $(ARGS) - @echo "✅ Report generated in htmlcov/" +prune: + @echo "Pruning Docker system..." + @docker system prune -af \ + && echo "✓ Docker pruned" \ + || (echo "✗ Prune failed" && exit 1) -down: check-docker - @echo "$(T)🛑 Stopping containers...$(R)" - $(COMPOSE_CMD) down - @echo "✅ System stopped." +# --- Versioning --- +major minor patch: _check-main + @echo "Bumping $@ version..." + @$(RUNNER) run bump-my-version bump $@ \ + && echo "✓ Version bumped" \ + || (echo "✗ Version bump failed" && exit 1) -format: check-runner - @echo "$(T)✨ Formatting with Ruff...$(R)" - $(RUNNER_RUN) ruff format . - $(RUNNER_RUN) ruff check --fix . - @echo "✅ Code cleaned." - -help: - @echo "$(B)Available commands:$(R)" - @echo "" - @echo "$(G)Setup:$(R)" - @echo " $(T)check-docker $(R) Verify Docker is installed and running." - @echo " $(T)check-runner $(R) Verify package manager ($(RUNNER))." - @echo " $(T)init-dotenv $(R) Create .env from .env.example with generated secrets." - @echo " $(T)install $(R) Install ALL dependencies (Prod + Dev)." - @echo " $(T)install-hooks $(R) Install git pre-commit hooks." - @echo "" - @echo "$(G)Docker:$(R)" - @echo " $(T)build $(R) Build the docker image (production)." - @echo " $(T)build-test $(R) Build the docker image (with dev deps for testing)." - @echo " $(T)down $(R) Stop and remove containers." - @echo " $(T)logs $(R) Follow logs." - @echo " $(T)prune $(R) Clean Docker system." - @echo " $(T)ps $(R) Show container status." - @echo " $(T)restart $(R) Restart all containers." - @echo " $(T)shell $(R) Open shell in container." - @echo " $(T)up $(R) Start the agent." - @echo "" - @echo "$(G)Development:$(R)" - @echo " $(T)add ... $(R) Add dependency (use --group dev or --dev if needed)." - @echo " $(T)clean $(R) Clean caches." - @echo " $(T)coverage $(R) Run tests with coverage." - @echo " $(T)format $(R) Format code (Ruff)." - @echo " $(T)lint $(R) Lint code without fixing." - @echo " $(T)test ... $(R) Run tests (local with $(RUNNER))." - @echo " $(T)update $(R) Update dependencies." - @echo "" - @echo "$(G)Versioning:$(R)" - @echo " $(T)major/minor/patch $(R) Bump version and push tag (triggers CI/CD)." - -init-dotenv: - @echo "$(T)🔑 Initializing .env file...$(R)" - @if [ -f .env ]; then \ - echo "$(R)⚠️ .env already exists. Skipping.$(R)"; \ - exit 0; \ - fi - @if [ ! -f .env.example ]; then \ - echo "$(R)❌ .env.example not found$(R)"; \ - exit 1; \ - fi - @if ! command -v openssl >/dev/null 2>&1; then \ - echo "$(R)❌ openssl not found. Please install it first.$(R)"; \ - exit 1; \ - fi - @echo "$(T) → Copying .env.example...$(R)" - @cp .env.example .env - @echo "$(T) → Generating secrets...$(R)" - @sed -i.bak "s|JWT_SECRET=.*|JWT_SECRET=$$(openssl rand -base64 32)|" .env - @sed -i.bak "s|JWT_REFRESH_SECRET=.*|JWT_REFRESH_SECRET=$$(openssl rand -base64 32)|" .env - @sed -i.bak "s|CREDS_KEY=.*|CREDS_KEY=$$(openssl rand -hex 16)|" .env - @sed -i.bak "s|CREDS_IV=.*|CREDS_IV=$$(openssl rand -hex 8)|" .env - @sed -i.bak "s|MEILI_MASTER_KEY=.*|MEILI_MASTER_KEY=$$(openssl rand -base64 32)|" .env - @rm -f .env.bak - @echo "$(G)✅ .env created with generated secrets!$(R)" - @echo "$(T)⚠️ Don't forget to add your API keys:$(R)" - @echo " - OPENAI_API_KEY" - @echo " - DEEPSEEK_API_KEY" - @echo " - TMDB_API_KEY (optional)" - -install: check-runner - @echo "$(T)📦 Installing FULL environment ($(RUNNER))...$(R)" - $(RUNNER_INSTALL) - @echo "✅ Environment ready (Prod + Dev)." - -install-hooks: check-runner - @echo "$(T)🔧 Installing hooks...$(R)" - $(RUNNER_HOOKS) - @echo "✅ Hooks ready." - -lint: check-runner - @echo "$(T)🔍 Linting code...$(R)" - $(RUNNER_RUN) ruff check . - -logs: check-docker - @echo "$(T)📋 Following logs...$(R)" - $(COMPOSE_CMD) logs -f - -major: _check_branch - @echo "$(T)💥 Bumping major...$(R)" - SKIP=all $(BUMP_CMD) major - @$(MAKE) -s _push_tag - -minor: _check_branch - @echo "$(T)✨ Bumping minor...$(R)" - SKIP=all $(BUMP_CMD) minor - @$(MAKE) -s _push_tag - -patch: _check_branch - @echo "$(T)🚀 Bumping patch...$(R)" - SKIP=all $(BUMP_CMD) patch - @$(MAKE) -s _push_tag - -prune: check-docker - @echo "$(T)🗑️ Pruning Docker resources...$(R)" - docker system prune -af - @echo "✅ Docker cleaned." - -ps: check-docker - @echo "$(T)📋 Container status:$(R)" - @$(COMPOSE_CMD) ps - -python-version: - @echo "🔍 Reading pyproject.toml..." - @echo "✅ Python version : $(PYTHON_VERSION)" - @echo "ℹ️ Sera utilisé pour : FROM python:$(PYTHON_VERSION)-slim" - - -restart: check-docker - @echo "$(T)🔄 Restarting containers...$(R)" - $(COMPOSE_CMD) restart - @echo "✅ Containers restarted." - -run: check-runner - $(RUNNER_RUN) $(ARGS) - -shell: check-docker - @echo "$(T)🐚 Opening shell in $(SERVICE_NAME)...$(R)" - $(COMPOSE_CMD) exec $(SERVICE_NAME) /bin/sh - -test: check-runner - @echo "$(T)🧪 Running tests...$(R)" - $(RUNNER_RUN) pytest $(ARGS) - -up: check-docker - @echo "$(T)🚀 Starting Agent Media...$(R)" - $(COMPOSE_CMD) up -d - @echo "✅ System is up." - -update: check-runner - @echo "$(T)🔄 Updating dependencies...$(R)" - $(RUNNER_UPDATE) - @echo "✅ All packages up to date." - -_check_branch: - @curr=$$(git rev-parse --abbrev-ref HEAD); \ - if [ "$$curr" != "main" ]; then \ - echo "❌ Error: not on the main branch"; exit 1; \ - fi + @echo "Pushing tags..." + @git push --tags \ + && echo "✓ Tags pushed" \ + || (echo "✗ Push failed" && exit 1) _ci-dump-config: @echo "image_name=$(IMAGE_NAME)" @@ -248,15 +158,46 @@ _ci-dump-config: @echo "runner=$(RUNNER)" @echo "service_name=$(SERVICE_NAME)" -_ci-run-tests: build-test - @echo "$(T)🧪 Running tests in Docker...$(R)" +_ci-run-tests: + @echo "Running tests in Docker..." docker run --rm \ -e DEEPSEEK_API_KEY \ -e TMDB_API_KEY \ + -e QBITTORRENT_URL \ $(IMAGE_NAME):test pytest - @echo "✅ Tests passed." + @echo "✓ Tests passed." -_push_tag: - @echo "$(T)📦 Pushing tag...$(R)" - git push --tags - @echo "✅ Tag pushed. Check CI for build status." +_check-main: + @test "$$(git rev-parse --abbrev-ref HEAD)" = "main" \ + || (echo "✗ ERROR: Not on main branch" && exit 1) + +# --- Help --- +help: + @echo "Usage: make [target]" + @echo "" + @echo "Setup:" + @echo " setup Initialize .env" + @echo " status Show project status" + @echo "" + @echo "Docker:" + @echo " up Start containers" + @echo " down Stop containers" + @echo " restart Restart containers" + @echo " logs Follow logs" + @echo " ps Container status" + @echo " shell Shell into container" + @echo " build Build image" + @echo "" + @echo "Dev:" + @echo " install Install dependencies" + @echo " update Update dependencies" + @echo " test Run tests" + @echo " coverage Run tests with coverage" + @echo " lint Lint code" + @echo " format Format code" + @echo " clean Clean artifacts" + @echo "" + @echo "Release:" + @echo " patch Bump patch version" + @echo " minor Bump minor version" + @echo " major Bump major version" \ No newline at end of file diff --git a/cli.py b/cli.py new file mode 100644 index 0000000..a762bdf --- /dev/null +++ b/cli.py @@ -0,0 +1,232 @@ +#!/usr/bin/env python3 +import os +import secrets +import shutil +import subprocess +import sys +from datetime import datetime +from enum import StrEnum +from pathlib import Path +from typing import NoReturn + +REQUIRED_VARS = ["DEEPSEEK_API_KEY", "TMDB_API_KEY", "QBITTORRENT_URL"] + +# Size in bytes +KEYS_TO_GENERATE = { + "JWT_SECRET": 32, + "JWT_REFRESH_SECRET": 32, + "CREDS_KEY": 32, + "CREDS_IV": 16, +} + + +class Style(StrEnum): + """ANSI codes for styling output. + Usage: f"{Style.RED}Error{Style.RESET}" + """ + + RESET = "\033[0m" + BOLD = "\033[1m" + RED = "\033[31m" + GREEN = "\033[32m" + YELLOW = "\033[33m" + CYAN = "\033[36m" + DIM = "\033[2m" + + +# Only for terminals and if not specified otherwise +USE_COLORS = sys.stdout.isatty() and "NO_COLOR" not in os.environ + + +def styled(text: str, color_code: str) -> str: + """Apply color only if supported by the terminal.""" + if USE_COLORS: + return f"{color_code}{text}{Style.RESET}" + return text + + +def log(msg: str, color: str | None = None, prefix="") -> None: + """Print a formatted message.""" + formatted_msg = styled(msg, color) if color else msg + print(f"{prefix}{formatted_msg}") + + +def error_exit(msg: str) -> NoReturn: + """Print an error message in red and exit.""" + log(f"❌ {msg}", Style.RED) + sys.exit(1) + + +def is_docker_running() -> bool: + """ "Check if Docker is available and responsive.""" + if shutil.which("docker") is None: + error_exit("Docker is not installed.") + + result = subprocess.run( + ["docker", "info"], + # Redirect stdout/stderr to keep output clean on success + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + # Prevent exception being raised + check=False, + ) + return result.returncode == 0 + + +def parse_env(content: str) -> dict[str, str]: + """Parses existing keys and values into a dict (ignoring comments).""" + env_vars = {} + for raw_line in content.splitlines(): + line = raw_line.strip() + if line and not line.startswith("#") and "=" in line: + key, value = line.split("=", 1) + env_vars[key.strip()] = value.strip() + + return env_vars + + +def dump_env(content: str, data: dict[str, str]) -> str: + new_content: list[str] = [] + processed_keys = set() + + for raw_line in content.splitlines(): + line = raw_line.strip() + # Fast line (empty, comment or not an assignation) + if len(line) == 0 or line.startswith("#") or "=" not in line: + new_content.append(raw_line) + continue + + # Slow line (inline comment to be kept) + key_chunk, value_chunk = raw_line.split("=", 1) + key = key_chunk.strip() + + # Not in the update list + if key not in data: + new_content.append(raw_line) + continue + + processed_keys.add(key) + new_value = data[key] + + if " #" not in value_chunk: + new_line = f"{key_chunk}={new_value}" + else: + _, comment = value_chunk.split(" #", 1) + new_line = f"{key_chunk}={new_value} #{comment}" + + new_content.append(new_line) + + for key, value in data.items(): + if key not in processed_keys: + new_content.append(f"{key}={value}") + + return "\n".join(new_content) + "\n" + + +def ensure_env() -> None: + """Manage .env lifecycle: creation, secret generation, prompts.""" + env_path = Path(".env") + env_example_path = Path(".env.example") + updated: bool = False + + # Read .env if exists + if env_path.exists(): + content: str = env_path.read_text(encoding="utf-8") + else: + content: str = env_example_path.read_text(encoding="utf-8") + + existing_vars: dict[str, str] = parse_env(content) + + # Generate missing secrets + for key, length in KEYS_TO_GENERATE.items(): + if key not in existing_vars or not existing_vars[key]: + log(f"Generating {key}...", Style.GREEN, prefix=" ") + existing_vars[key] = secrets.token_hex(length) + updated = True + log("Done", Style.GREEN, prefix=" ") + + # Prompt for missing mandatory keys + color = Style.YELLOW if USE_COLORS else "" + reset = Style.RESET if USE_COLORS else "" + for key in REQUIRED_VARS: + if key not in existing_vars or not existing_vars[key]: + try: + existing_vars[key] = input( + f" {color}Enter value for {key}: {reset}" + ).strip() + updated = True + except KeyboardInterrupt: + print() + error_exit("Aborted by user.") + + # Write to disk + if updated: + # But backup original first + if env_path.exists(): + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + backup_path = Path(f"{env_path}.{timestamp}.bak") + shutil.copy(env_path, backup_path) + log(f"Backup created: {backup_path}", Style.DIM) + + new_content = dump_env(content, existing_vars) + env_path.write_text(new_content, encoding="utf-8") + log(".env updated successfully.", Style.GREEN) + else: + log("Configuration is up to date.", Style.GREEN) + + +def setup() -> None: + """Orchestrate initialization.""" + is_docker_running() + ensure_env() + #mkdir + + +def status() -> None: + """Display simple dashboard.""" + # Hardcoded bold style for title if colors are enabled + title_style = Style.BOLD if USE_COLORS else "" + reset_style = Style.RESET if USE_COLORS else "" + + print(f"\n{title_style}ALFRED STATUS{reset_style}") + print(f"{title_style}==============={reset_style}\n") + + # Docker Check + if is_docker_running(): + print(f" Docker: {styled('✓ running', Style.GREEN)}") + else: + print(f" Docker: {styled('✗ stopped', Style.RED)}") + + # Env Check + if Path(".env").exists(): + print(f" .env: {styled('✓ present', Style.GREEN)}") + else: + print(f" .env: {styled('✗ missing', Style.RED)}") + + print("") + + +def check() -> None: + """Silent check for prerequisites (used by 'make up').""" + setup() + + +def main() -> None: + if len(sys.argv) < 2: + print("Usage: python cli.py [setup|check|status]") + sys.exit(1) + + cmd = sys.argv[1] + + if cmd == "setup": + setup() + elif cmd == "check": + check() + elif cmd == "status": + status() + else: + error_exit(f"Unknown command: {cmd}") + + +if __name__ == "__main__": + main() diff --git a/generate-keys.sh b/generate-keys.sh deleted file mode 100755 index 3923201..0000000 --- a/generate-keys.sh +++ /dev/null @@ -1,27 +0,0 @@ -#!/bin/bash - -# Script to generate secure keys for LibreChat -# Run this script to generate random secure keys for your .env file - -echo "===================================" -echo "LibreChat Security Keys Generator" -echo "===================================" -echo "" - -echo "# MongoDB Password" -echo "MONGO_PASSWORD=$(openssl rand -base64 24)" -echo "" - -echo "# JWT Secrets" -echo "JWT_SECRET=$(openssl rand -base64 32)" -echo "JWT_REFRESH_SECRET=$(openssl rand -base64 32)" -echo "" - -echo "# Credentials Encryption Keys" -echo "CREDS_KEY=$(openssl rand -hex 16)" -echo "CREDS_IV=$(openssl rand -hex 8)" -echo "" - -echo "===================================" -echo "Copy these values to your .env file" -echo "===================================" -- 2.49.1 From 52f025ae32e692f1281b4d99761f23482dc9ae29 Mon Sep 17 00:00:00 2001 From: Francwa Date: Sat, 27 Dec 2025 20:07:48 +0100 Subject: [PATCH 18/22] chore: ran linter & formatter again --- alfred/app.py | 4 ++-- alfred/application/torrents/search_torrents.py | 6 +++++- alfred/infrastructure/api/knaben/client.py | 7 ++++--- alfred/infrastructure/api/qbittorrent/client.py | 6 ++++-- alfred/infrastructure/api/tmdb/client.py | 5 +++-- cli.py | 1 - tests/conftest.py | 3 ++- tests/test_agent_edge_cases.py | 4 +++- tests/test_api_edge_cases.py | 16 ++++++++-------- tests/test_config_critical.py | 1 + tests/test_config_edge_cases.py | 3 ++- tests/test_domain_edge_cases.py | 4 +++- tests/test_memory.py | 4 +++- tests/test_memory_edge_cases.py | 1 + tests/test_registry_critical.py | 1 + tests/test_registry_edge_cases.py | 1 + tests/test_tools_edge_cases.py | 4 +++- tests/test_tools_filesystem.py | 4 +++- 18 files changed, 49 insertions(+), 26 deletions(-) diff --git a/alfred/app.py b/alfred/app.py index 8280b89..2532054 100644 --- a/alfred/app.py +++ b/alfred/app.py @@ -5,11 +5,12 @@ import logging import os import time import uuid +from typing import Any + from fastapi import FastAPI, HTTPException from fastapi.responses import JSONResponse, StreamingResponse from fastapi.staticfiles import StaticFiles from pydantic import BaseModel, Field, validator -from typing import Any from alfred.agent.agent import Agent from alfred.agent.config import settings @@ -40,7 +41,6 @@ else: ) # Initialize memory context at startup -# Use /data/memory in Docker, fallback to memory_data for local dev storage_dir = os.getenv("MEMORY_STORAGE_DIR", "memory_data") init_memory(storage_dir=storage_dir) logger.info(f"Memory context initialized (storage: {storage_dir})") diff --git a/alfred/application/torrents/search_torrents.py b/alfred/application/torrents/search_torrents.py index daad45e..0b83c11 100644 --- a/alfred/application/torrents/search_torrents.py +++ b/alfred/application/torrents/search_torrents.py @@ -2,7 +2,11 @@ import logging -from alfred.infrastructure.api.knaben import KnabenAPIError, KnabenClient, KnabenNotFoundError +from alfred.infrastructure.api.knaben import ( + KnabenAPIError, + KnabenClient, + KnabenNotFoundError, +) from .dto import SearchTorrentsResponse diff --git a/alfred/infrastructure/api/knaben/client.py b/alfred/infrastructure/api/knaben/client.py index 359de66..acd399d 100644 --- a/alfred/infrastructure/api/knaben/client.py +++ b/alfred/infrastructure/api/knaben/client.py @@ -1,12 +1,13 @@ """Knaben torrent search API client.""" - import logging -import requests -from requests.exceptions import HTTPError, RequestException, Timeout from typing import Any +import requests +from requests.exceptions import HTTPError, RequestException, Timeout + from alfred.agent.config import Settings, settings + from .dto import TorrentResult from .exceptions import KnabenAPIError, KnabenNotFoundError diff --git a/alfred/infrastructure/api/qbittorrent/client.py b/alfred/infrastructure/api/qbittorrent/client.py index 02d3124..b7bf165 100644 --- a/alfred/infrastructure/api/qbittorrent/client.py +++ b/alfred/infrastructure/api/qbittorrent/client.py @@ -1,11 +1,13 @@ """qBittorrent Web API client.""" import logging -import requests -from requests.exceptions import HTTPError, RequestException, Timeout from typing import Any +import requests +from requests.exceptions import HTTPError, RequestException, Timeout + from alfred.agent.config import Settings, settings + from .dto import TorrentInfo from .exceptions import QBittorrentAPIError, QBittorrentAuthError diff --git a/alfred/infrastructure/api/tmdb/client.py b/alfred/infrastructure/api/tmdb/client.py index 208edbc..1ada28d 100644 --- a/alfred/infrastructure/api/tmdb/client.py +++ b/alfred/infrastructure/api/tmdb/client.py @@ -1,12 +1,13 @@ """TMDB (The Movie Database) API client.""" import logging -import requests -from requests.exceptions import HTTPError, RequestException, Timeout from typing import Any +import requests +from requests.exceptions import HTTPError, RequestException, Timeout from alfred.agent.config import Settings, settings + from .dto import MediaResult from .exceptions import ( TMDBAPIError, diff --git a/cli.py b/cli.py index a762bdf..41cf333 100644 --- a/cli.py +++ b/cli.py @@ -179,7 +179,6 @@ def setup() -> None: """Orchestrate initialization.""" is_docker_running() ensure_env() - #mkdir def status() -> None: diff --git a/tests/conftest.py b/tests/conftest.py index 462d701..6326a10 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,13 +3,14 @@ # TODO: Moved directory, should not be necessary anymore but need to check !! # Ajouter le dossier parent (brain) au PYTHONPATH # sys.path.insert(0, str(Path(__file__).parent.parent)) -import pytest import shutil import sys import tempfile from pathlib import Path from unittest.mock import MagicMock, Mock +import pytest + from alfred.infrastructure.persistence import Memory, set_memory diff --git a/tests/test_agent_edge_cases.py b/tests/test_agent_edge_cases.py index 83d94ce..5083684 100644 --- a/tests/test_agent_edge_cases.py +++ b/tests/test_agent_edge_cases.py @@ -1,7 +1,9 @@ """Edge case tests for the Agent.""" -import pytest + from unittest.mock import Mock +import pytest + from alfred.agent.agent import Agent from alfred.infrastructure.persistence import get_memory diff --git a/tests/test_api_edge_cases.py b/tests/test_api_edge_cases.py index 5ffa798..e61c30e 100644 --- a/tests/test_api_edge_cases.py +++ b/tests/test_api_edge_cases.py @@ -10,8 +10,8 @@ class TestChatCompletionsEdgeCases: def test_very_long_message(self, memory): """Should handle very long user message.""" - from alfred.app import app from alfred.agent import agent + from alfred.app import app # Patch the agent's LLM directly mock_llm = Mock() @@ -33,8 +33,8 @@ class TestChatCompletionsEdgeCases: def test_unicode_message(self, memory): """Should handle unicode in message.""" - from alfred.app import app from alfred.agent import agent + from alfred.app import app mock_llm = Mock() mock_llm.complete.return_value = { @@ -59,8 +59,8 @@ class TestChatCompletionsEdgeCases: def test_special_characters_in_message(self, memory): """Should handle special characters.""" - from alfred.app import app from alfred.agent import agent + from alfred.app import app mock_llm = Mock() mock_llm.complete.return_value = {"role": "assistant", "content": "Response"} @@ -187,8 +187,8 @@ class TestChatCompletionsEdgeCases: def test_many_messages(self, memory): """Should handle many messages in conversation.""" - from alfred.app import app from alfred.agent import agent + from alfred.app import app mock_llm = Mock() mock_llm.complete.return_value = {"role": "assistant", "content": "Response"} @@ -301,8 +301,8 @@ class TestChatCompletionsEdgeCases: def test_extra_fields_in_request(self, memory): """Should ignore extra fields in request.""" - from alfred.app import app from alfred.agent import agent + from alfred.app import app mock_llm = Mock() mock_llm.complete.return_value = {"role": "assistant", "content": "Response"} @@ -325,8 +325,8 @@ class TestChatCompletionsEdgeCases: def test_streaming_with_tool_call(self, memory, real_folder): """Should handle streaming with tool execution.""" - from alfred.app import app from alfred.agent import agent + from alfred.app import app from alfred.infrastructure.persistence import get_memory mem = get_memory() @@ -371,8 +371,8 @@ class TestChatCompletionsEdgeCases: def test_concurrent_requests_simulation(self, memory): """Should handle rapid sequential requests.""" - from alfred.app import app from alfred.agent import agent + from alfred.app import app mock_llm = Mock() mock_llm.complete.return_value = {"role": "assistant", "content": "Response"} @@ -392,8 +392,8 @@ class TestChatCompletionsEdgeCases: def test_llm_returns_json_in_response(self, memory): """Should handle LLM returning JSON in text response.""" - from alfred.app import app from alfred.agent import agent + from alfred.app import app mock_llm = Mock() mock_llm.complete.return_value = { diff --git a/tests/test_config_critical.py b/tests/test_config_critical.py index 47e8b07..02432d6 100644 --- a/tests/test_config_critical.py +++ b/tests/test_config_critical.py @@ -1,4 +1,5 @@ """Critical tests for configuration validation.""" + import pytest from alfred.agent.config import ConfigurationError, Settings diff --git a/tests/test_config_edge_cases.py b/tests/test_config_edge_cases.py index 01dc150..70fa5e7 100644 --- a/tests/test_config_edge_cases.py +++ b/tests/test_config_edge_cases.py @@ -1,9 +1,10 @@ """Edge case tests for configuration and parameters.""" import os -import pytest from unittest.mock import patch +import pytest + from alfred.agent.config import ConfigurationError, Settings from alfred.agent.parameters import ( REQUIRED_PARAMETERS, diff --git a/tests/test_domain_edge_cases.py b/tests/test_domain_edge_cases.py index 25a618d..a38aa67 100644 --- a/tests/test_domain_edge_cases.py +++ b/tests/test_domain_edge_cases.py @@ -1,7 +1,9 @@ """Edge case tests for domain entities and value objects.""" -import pytest + from datetime import datetime +import pytest + from alfred.domain.movies.entities import Movie from alfred.domain.movies.value_objects import MovieTitle, Quality, ReleaseYear from alfred.domain.shared.exceptions import ValidationError diff --git a/tests/test_memory.py b/tests/test_memory.py index ce83aa3..2ecca6e 100644 --- a/tests/test_memory.py +++ b/tests/test_memory.py @@ -1,7 +1,9 @@ """Tests for the Memory system.""" -import pytest + from datetime import datetime +import pytest + from alfred.infrastructure.persistence import ( EpisodicMemory, LongTermMemory, diff --git a/tests/test_memory_edge_cases.py b/tests/test_memory_edge_cases.py index 356c90e..12d6295 100644 --- a/tests/test_memory_edge_cases.py +++ b/tests/test_memory_edge_cases.py @@ -4,6 +4,7 @@ import json import os import pytest + from alfred.infrastructure.persistence import ( EpisodicMemory, LongTermMemory, diff --git a/tests/test_registry_critical.py b/tests/test_registry_critical.py index 7143849..11f5535 100644 --- a/tests/test_registry_critical.py +++ b/tests/test_registry_critical.py @@ -3,6 +3,7 @@ import inspect import pytest + from alfred.agent.prompts import PromptBuilder from alfred.agent.registry import Tool, _create_tool_from_function, make_tools diff --git a/tests/test_registry_edge_cases.py b/tests/test_registry_edge_cases.py index 10b45f7..952cfad 100644 --- a/tests/test_registry_edge_cases.py +++ b/tests/test_registry_edge_cases.py @@ -1,6 +1,7 @@ """Edge case tests for tool registry.""" import pytest + from alfred.agent.registry import Tool, make_tools diff --git a/tests/test_tools_edge_cases.py b/tests/test_tools_edge_cases.py index ac24aa2..6ccc1e4 100644 --- a/tests/test_tools_edge_cases.py +++ b/tests/test_tools_edge_cases.py @@ -1,7 +1,9 @@ """Edge case tests for tools.""" -import pytest + from unittest.mock import Mock, patch +import pytest + from alfred.agent.tools import api as api_tools from alfred.agent.tools import filesystem as fs_tools from alfred.infrastructure.persistence import get_memory diff --git a/tests/test_tools_filesystem.py b/tests/test_tools_filesystem.py index 21c8836..8245594 100644 --- a/tests/test_tools_filesystem.py +++ b/tests/test_tools_filesystem.py @@ -1,7 +1,9 @@ """Tests for filesystem tools.""" -import pytest + from pathlib import Path +import pytest + from alfred.agent.tools import filesystem as fs_tools from alfred.infrastructure.persistence import get_memory -- 2.49.1 From b15161dad7918107a6ef53225bb2d372e52c1de5 Mon Sep 17 00:00:00 2001 From: Francwa Date: Sun, 28 Dec 2025 06:04:08 +0100 Subject: [PATCH 19/22] fix: provided OpenAI API key and fixed docker-compose configuration to enable RAG service --- .env.example | 11 +++++++---- docker-compose.yaml | 10 ++++++---- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/.env.example b/.env.example index ded7737..6c435d3 100644 --- a/.env.example +++ b/.env.example @@ -2,6 +2,9 @@ # Deepseek API Key (for LLM in alfred) DEEPSEEK_API_KEY= +# - ChatGPT/Open API +OPENAI_API_KEY= + OLLAMA_BASE_URL= OLLAMA_MODEL= @@ -25,8 +28,8 @@ QBITTORRENT_PASSWORD=adminadmin DEBUG_LOGGING=false DEBUG_CONSOLE=false -# Required security keys -JWT_SECRET= -JWT_REFRESH_SECRET= -CREDS_KEY= +# Postgres (RAG) +POSTGRES_DB= +POSTGRES_USER= +POSTGRES_PASSWORD= CREDS_IV= diff --git a/docker-compose.yaml b/docker-compose.yaml index 3b3080f..e0bc461 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -79,7 +79,11 @@ services: container_name: alfred-rag image: ghcr.io/danny-avila/librechat-rag-api-dev-lite:${RAG_VERSION} restart: unless-stopped + env_file: + - .env environment: + - DB_HOST=vectordb + - DB_PORT=5432 - RAG_PORT=${RAG_PORT:-8000} ports: - "${RAG_PORT:-8000}:${RAG_PORT:-8000}" @@ -89,10 +93,8 @@ services: container_name: alfred-vectordb image: pgvector/pgvector:0.8.0-pg16-bookworm restart: unless-stopped - environment: - - POSTGRES_DB=${VECTOR_DB_NAME:-vectordb} - - POSTGRES_USER=${VECTOR_DB_USER:-postgres} - - POSTGRES_PASSWORD=${VECTOR_DB_PASSWORD:-postgres} + env_file: + - .env ports: - "${VECTOR_DB_PORT:-5432}:5432" volumes: -- 2.49.1 From 9958b8e848270bc7e58b51218789ca9c4dba776e Mon Sep 17 00:00:00 2001 From: Francwa Date: Sun, 28 Dec 2025 06:06:28 +0100 Subject: [PATCH 20/22] feat: created placeholders for various API models --- .env.example | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/.env.example b/.env.example index 6c435d3..c6ef529 100644 --- a/.env.example +++ b/.env.example @@ -1,24 +1,41 @@ # API Keys # Deepseek API Key (for LLM in alfred) +# Keys +# - Deepseek API DEEPSEEK_API_KEY= +# - Google API +GOOGLE_API_KEY= +#GOOGLE_MODELS=gemini-2.5-pro,gemini-2.5-flash,gemini-2.5-flash-lite,gemini-2.0-flash,gemini-2.0-flash-lite #TODO:Update models + +# - Anthropic API +ANTHROPIC_API_KEY= + +# - Kimi API +KIMI_API_KEY= + # - ChatGPT/Open API OPENAI_API_KEY= +# - Themoviedb.org API (media metadata) +TMDB_API_KEY= + +# - Security keys +JWT_SECRET= +JWT_REFRESH_SECRET= +CREDS_KEY= +CREDS_IV= + +# Local LLM OLLAMA_BASE_URL= OLLAMA_MODEL= # Alfred Configuration -# LLM Provider (deepseek or ollama) LLM_PROVIDER=deepseek # Memory storage directory (inside container) MEMORY_STORAGE_DIR=data/memory -# External Services (Optional) -# TMDB API Key (for movie metadata) -TMDB_API_KEY= - # qBittorrent Configuration QBITTORRENT_URL= QBITTORRENT_USERNAME=admin @@ -32,4 +49,3 @@ DEBUG_CONSOLE=false POSTGRES_DB= POSTGRES_USER= POSTGRES_PASSWORD= -CREDS_IV= -- 2.49.1 From 1052c1b619badb071dcc97314e04d350f22397e1 Mon Sep 17 00:00:00 2001 From: Francwa Date: Sun, 28 Dec 2025 06:07:34 +0100 Subject: [PATCH 21/22] infra: added modules version to .env.example --- .env.example | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.env.example b/.env.example index c6ef529..ece1a0c 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,7 @@ -# API Keys -# Deepseek API Key (for LLM in alfred) +# Configuration +LIBRECHAT_VERSION=v0.8.1 +RAG_VERSION=v0.7.0 + # Keys # - Deepseek API DEEPSEEK_API_KEY= @@ -34,7 +36,7 @@ OLLAMA_MODEL= LLM_PROVIDER=deepseek # Memory storage directory (inside container) -MEMORY_STORAGE_DIR=data/memory +MEMORY_STORAGE_DIR=/data/memory # qBittorrent Configuration QBITTORRENT_URL= -- 2.49.1 From 84799879bb3fae9e385106857716932ef5bc802d Mon Sep 17 00:00:00 2001 From: Francwa Date: Sun, 28 Dec 2025 12:02:44 +0100 Subject: [PATCH 22/22] fix: added data dir to .dockerignore --- .dockerignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.dockerignore b/.dockerignore index 667224e..3479072 100644 --- a/.dockerignore +++ b/.dockerignore @@ -41,7 +41,8 @@ docs/ *.md !README.md -# Data (will be mounted as volumes) +# Data +data/ memory_data/ logs/ *.log -- 2.49.1