aegis-stack 0.2.0rc2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (392) hide show
  1. aegis/__init__.py +5 -0
  2. aegis/__main__.py +51 -0
  3. aegis/cli/__init__.py +6 -0
  4. aegis/cli/callbacks.py +114 -0
  5. aegis/cli/interactive.py +611 -0
  6. aegis/cli/utils.py +70 -0
  7. aegis/cli/validators.py +34 -0
  8. aegis/commands/__init__.py +6 -0
  9. aegis/commands/add.py +353 -0
  10. aegis/commands/add_service.py +332 -0
  11. aegis/commands/components.py +35 -0
  12. aegis/commands/init.py +370 -0
  13. aegis/commands/remove.py +227 -0
  14. aegis/commands/services.py +52 -0
  15. aegis/commands/update.py +252 -0
  16. aegis/commands/version.py +12 -0
  17. aegis/config/__init__.py +1 -0
  18. aegis/config/shared_files.py +136 -0
  19. aegis/core/CLAUDE.md +377 -0
  20. aegis/core/__init__.py +6 -0
  21. aegis/core/component_files.py +228 -0
  22. aegis/core/component_utils.py +220 -0
  23. aegis/core/components.py +127 -0
  24. aegis/core/copier_manager.py +315 -0
  25. aegis/core/copier_updater.py +475 -0
  26. aegis/core/dependency_resolver.py +119 -0
  27. aegis/core/manual_updater.py +554 -0
  28. aegis/core/post_gen_tasks.py +547 -0
  29. aegis/core/service_resolver.py +261 -0
  30. aegis/core/services.py +157 -0
  31. aegis/core/template_generator.py +266 -0
  32. aegis/core/version_compatibility.py +259 -0
  33. aegis/templates/CLAUDE.md +591 -0
  34. aegis/templates/cookiecutter-aegis-project/cookiecutter.json +39 -0
  35. aegis/templates/cookiecutter-aegis-project/hooks/post_gen_project.py +214 -0
  36. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/.dockerignore +71 -0
  37. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/.env.example.j2 +130 -0
  38. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/.gitignore +131 -0
  39. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/Dockerfile +53 -0
  40. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/Makefile +236 -0
  41. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/README.md.j2 +196 -0
  42. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/alembic/alembic.ini.j2 +111 -0
  43. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/alembic/env.py.j2 +91 -0
  44. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/alembic/script.py.mako +25 -0
  45. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/alembic/versions/001_initial_auth.py.j2 +51 -0
  46. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/__init__.py +5 -0
  47. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/__init__.py +6 -0
  48. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/ai.py.j2 +700 -0
  49. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/ai_rendering.py +361 -0
  50. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/auth.py.j2 +253 -0
  51. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/health.py.j2 +419 -0
  52. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/load_test.py.j2 +656 -0
  53. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/main.py.j2 +65 -0
  54. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/marko_terminal_renderer.py +489 -0
  55. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/tasks.py.j2 +328 -0
  56. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/cli/{% if cookiecutter.include_scheduler == /"yes/" %}tasks.py{% endif %}" +340 -0
  57. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/__init__.py +0 -0
  58. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/__init__.py +0 -0
  59. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/ai/__init__.py +8 -0
  60. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/ai/router.py +329 -0
  61. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/auth/__init__.py +1 -0
  62. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/auth/router.py +64 -0
  63. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/deps.py +58 -0
  64. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/health.py +163 -0
  65. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/models.py.j2 +280 -0
  66. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/routing.py.j2 +32 -0
  67. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/scheduler.py.j2 +121 -0
  68. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/api/worker.py.j2 +478 -0
  69. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/hooks.py +144 -0
  70. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/main.py +31 -0
  71. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/middleware/__init__.py +1 -0
  72. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/middleware/cors.py +20 -0
  73. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/shutdown/__init__.py +1 -0
  74. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/shutdown/cleanup.py +14 -0
  75. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/startup/__init__.py +1 -0
  76. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/startup/component_health.py.j2 +418 -0
  77. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/backend/startup/database_init.py.j2 +83 -0
  78. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/__init__.py +5 -0
  79. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/controls/__init__.py +27 -0
  80. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/controls/table.py +78 -0
  81. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/controls/text.py +142 -0
  82. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/__init__.py.j2 +47 -0
  83. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/ai_card.py +287 -0
  84. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/auth_card.py +198 -0
  85. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/base_card.py +256 -0
  86. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/card_factory.py +227 -0
  87. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/card_utils.py +333 -0
  88. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/database_card.py +420 -0
  89. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/fastapi_card.py +328 -0
  90. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/flet_card.py +267 -0
  91. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/redis_card.py +322 -0
  92. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/scheduler_card.py +352 -0
  93. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/services_card.py +233 -0
  94. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/dashboard/cards/worker_card.py +684 -0
  95. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/main.py.j2 +653 -0
  96. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/frontend/theme.py +48 -0
  97. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/scheduler/__init__.py +1 -0
  98. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/scheduler/main.py.j2 +156 -0
  99. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/CLAUDE.md.j2 +213 -0
  100. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/__init__.py +6 -0
  101. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/constants.py.j2 +30 -0
  102. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/pools.py +97 -0
  103. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/queues/__init__.py +1 -0
  104. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/queues/load_test.py +55 -0
  105. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/queues/media.py +49 -0
  106. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/queues/system.py +44 -0
  107. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/registry.py +139 -0
  108. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/__init__.py +120 -0
  109. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/load_tasks.py +507 -0
  110. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/simple_system_tasks.py +33 -0
  111. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/components/worker/tasks/system_tasks.py +281 -0
  112. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/config.py.j2 +178 -0
  113. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/constants.py +58 -0
  114. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/db.py.j2 +176 -0
  115. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/log.py +92 -0
  116. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/core/security.py +62 -0
  117. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/entrypoints/__init__.py +1 -0
  118. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/entrypoints/webserver.py +40 -0
  119. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/entrypoints/{% if cookiecutter.include_scheduler == /"yes/" %}scheduler.py{% endif %}" +21 -0
  120. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/integrations/__init__.py +0 -0
  121. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/integrations/main.py +62 -0
  122. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/models/__init__.py +1 -0
  123. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/models/user.py +44 -0
  124. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/py.typed +0 -0
  125. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/__init__.py +1 -0
  126. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/__init__.py +8 -0
  127. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/config.py +130 -0
  128. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/conversation.py +213 -0
  129. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/health.py +96 -0
  130. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/models.py +229 -0
  131. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/providers.py +370 -0
  132. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/ai/service.py +388 -0
  133. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/auth/__init__.py +1 -0
  134. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/auth/auth_service.py +41 -0
  135. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/auth/health.py +164 -0
  136. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/auth/user_service.py +83 -0
  137. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/backend/middleware_inspector.py.j2 +223 -0
  138. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/backend/models.py.j2 +70 -0
  139. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/backend/route_inspector.py.j2 +155 -0
  140. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/load_test.py +679 -0
  141. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/load_test_models.py +266 -0
  142. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/scheduler/__init__.py.j2 +21 -0
  143. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/scheduler/models.py.j2 +119 -0
  144. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/scheduler/scheduled_task_manager.py.j2 +273 -0
  145. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/scheduler/task_monitor.py.j2 +189 -0
  146. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/shared/__init__.py +15 -0
  147. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/shared/models.py +26 -0
  148. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/__init__.py +52 -0
  149. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/alerts.py +94 -0
  150. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/backup.py.j2 +119 -0
  151. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/health.py.j2 +1333 -0
  152. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/models.py +243 -0
  153. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/app/services/system/ui.py +52 -0
  154. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/assets/aegis-manifesto-dark.png +0 -0
  155. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/assets/aegis-manifesto-square-backup.png +0 -0
  156. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/assets/aegis-manifesto.png +0 -0
  157. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/.dockerignore +71 -0
  158. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/.env.example.j2 +64 -0
  159. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/.gitignore +131 -0
  160. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/Dockerfile +53 -0
  161. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/Makefile +211 -0
  162. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/README.md.j2 +172 -0
  163. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/docker-compose.yml.j2 +78 -0
  164. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/mkdocs.yml.j2 +62 -0
  165. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/pyproject.toml.j2 +120 -0
  166. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/clean-validation/uv.lock +1673 -0
  167. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docker-compose.yml.j2 +200 -0
  168. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/api.md +191 -0
  169. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/components/scheduler.md +0 -0
  170. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/components/scheduler.md.j2 +621 -0
  171. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/development.md +215 -0
  172. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/health.md +240 -0
  173. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/javascripts/mermaid-config.js +62 -0
  174. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/docs/stylesheets/mermaid.css +95 -0
  175. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/mkdocs.yml.j2 +62 -0
  176. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/pyproject.toml.j2 +131 -0
  177. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/scripts/entrypoint.sh +87 -0
  178. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/scripts/entrypoint.sh.j2 +93 -0
  179. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/scripts/gen_docs.py +16 -0
  180. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/api/__init__.py +1 -0
  181. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/api/test_auth_endpoints.py.j2 +307 -0
  182. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/api/test_health_endpoints.py.j2 +262 -0
  183. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/api/test_scheduler_endpoints.py.j2 +214 -0
  184. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/api/test_worker_endpoints.py.j2 +165 -0
  185. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/cli/test_ai_rendering.py +427 -0
  186. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/cli/test_conversation_memory.py +465 -0
  187. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/components/test_scheduler.py +43 -0
  188. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/conftest.py.j2 +195 -0
  189. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/__init__.py +1 -0
  190. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/ai/__init__.py +1 -0
  191. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/ai/conftest.py +78 -0
  192. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/ai/test_health.py +157 -0
  193. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/ai/test_models.py +164 -0
  194. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/ai/test_service.py +198 -0
  195. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_auth_integration.py.j2 +528 -0
  196. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_component_integration.py.j2 +387 -0
  197. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_conversation_persistence.py +342 -0
  198. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_health_logic.py.j2 +663 -0
  199. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_load_test_models.py +619 -0
  200. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_load_test_service.py +603 -0
  201. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_middleware_inspector.py.j2 +248 -0
  202. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_scheduled_task_manager.py.j2 +292 -0
  203. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_system_service.py +98 -0
  204. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/services/test_worker_health_registration.py.j2 +257 -0
  205. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/tests/test_core.py +49 -0
  206. aegis/templates/cookiecutter-aegis-project/{{cookiecutter.project_slug}}/uv.lock +1673 -0
  207. aegis/templates/copier-aegis-project/{{ project_slug }}/.copier-answers.yml.jinja +21 -0
  208. aegis/templates/copier-aegis-project/{{ project_slug }}/.dockerignore +71 -0
  209. aegis/templates/copier-aegis-project/{{ project_slug }}/.env.example.jinja +130 -0
  210. aegis/templates/copier-aegis-project/{{ project_slug }}/.gitignore +131 -0
  211. aegis/templates/copier-aegis-project/{{ project_slug }}/Dockerfile +53 -0
  212. aegis/templates/copier-aegis-project/{{ project_slug }}/Makefile.jinja +236 -0
  213. aegis/templates/copier-aegis-project/{{ project_slug }}/README.md.jinja +196 -0
  214. aegis/templates/copier-aegis-project/{{ project_slug }}/alembic/alembic.ini.jinja +111 -0
  215. aegis/templates/copier-aegis-project/{{ project_slug }}/alembic/env.py.jinja +91 -0
  216. aegis/templates/copier-aegis-project/{{ project_slug }}/alembic/script.py.mako +25 -0
  217. aegis/templates/copier-aegis-project/{{ project_slug }}/alembic/versions/001_initial_auth.py.jinja +51 -0
  218. aegis/templates/copier-aegis-project/{{ project_slug }}/app/__init__.py.jinja +5 -0
  219. aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/__init__.py.jinja +6 -0
  220. aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/ai.py.jinja +700 -0
  221. aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/ai_rendering.py +360 -0
  222. aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/auth.py.jinja +253 -0
  223. aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/health.py.jinja +419 -0
  224. aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/load_test.py.jinja +656 -0
  225. aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/main.py.jinja +65 -0
  226. aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/marko_terminal_renderer.py +489 -0
  227. aegis/templates/copier-aegis-project/{{ project_slug }}/app/cli/tasks.py.jinja +328 -0
  228. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/__init__.py +0 -0
  229. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/__init__.py +0 -0
  230. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/ai/__init__.py +8 -0
  231. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/ai/router.py +329 -0
  232. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/auth/__init__.py +1 -0
  233. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/auth/router.py +64 -0
  234. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/deps.py +58 -0
  235. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/health.py.jinja +163 -0
  236. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/models.py.jinja +280 -0
  237. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/routing.py.jinja +32 -0
  238. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/scheduler.py.jinja +121 -0
  239. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/api/worker.py.jinja +478 -0
  240. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/hooks.py +144 -0
  241. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/main.py +31 -0
  242. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/middleware/__init__.py +1 -0
  243. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/middleware/cors.py +20 -0
  244. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/shutdown/__init__.py +1 -0
  245. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/shutdown/cleanup.py +14 -0
  246. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/startup/__init__.py +1 -0
  247. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/startup/component_health.py.jinja +418 -0
  248. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/backend/startup/database_init.py.jinja +83 -0
  249. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/__init__.py +5 -0
  250. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/controls/__init__.py +27 -0
  251. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/controls/table.py +78 -0
  252. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/controls/text.py +142 -0
  253. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/__init__.py.jinja +47 -0
  254. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/ai_card.py +287 -0
  255. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/auth_card.py +198 -0
  256. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/base_card.py +256 -0
  257. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/card_factory.py +227 -0
  258. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/card_utils.py +333 -0
  259. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/database_card.py +420 -0
  260. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/fastapi_card.py +328 -0
  261. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/flet_card.py +267 -0
  262. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/redis_card.py +322 -0
  263. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/scheduler_card.py +352 -0
  264. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/services_card.py +233 -0
  265. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/dashboard/cards/worker_card.py +684 -0
  266. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/main.py.jinja +653 -0
  267. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/frontend/theme.py +48 -0
  268. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/scheduler/__init__.py +1 -0
  269. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/scheduler/main.py.jinja +156 -0
  270. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/CLAUDE.md.jinja +213 -0
  271. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/__init__.py +6 -0
  272. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/constants.py.jinja +30 -0
  273. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/pools.py +97 -0
  274. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/queues/__init__.py +1 -0
  275. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/queues/load_test.py +55 -0
  276. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/queues/media.py +49 -0
  277. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/queues/system.py +44 -0
  278. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/registry.py +139 -0
  279. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/tasks/__init__.py +120 -0
  280. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/tasks/load_tasks.py +507 -0
  281. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/tasks/simple_system_tasks.py +33 -0
  282. aegis/templates/copier-aegis-project/{{ project_slug }}/app/components/worker/tasks/system_tasks.py +281 -0
  283. aegis/templates/copier-aegis-project/{{ project_slug }}/app/core/config.py.jinja +178 -0
  284. aegis/templates/copier-aegis-project/{{ project_slug }}/app/core/constants.py +58 -0
  285. aegis/templates/copier-aegis-project/{{ project_slug }}/app/core/db.py.jinja +176 -0
  286. aegis/templates/copier-aegis-project/{{ project_slug }}/app/core/log.py +92 -0
  287. aegis/templates/copier-aegis-project/{{ project_slug }}/app/core/security.py +62 -0
  288. aegis/templates/copier-aegis-project/{{ project_slug }}/app/entrypoints/__init__.py +1 -0
  289. aegis/templates/copier-aegis-project/{{ project_slug }}/app/entrypoints/scheduler.py.jinja +21 -0
  290. aegis/templates/copier-aegis-project/{{ project_slug }}/app/entrypoints/webserver.py +39 -0
  291. aegis/templates/copier-aegis-project/{{ project_slug }}/app/integrations/__init__.py +0 -0
  292. aegis/templates/copier-aegis-project/{{ project_slug }}/app/integrations/main.py +61 -0
  293. aegis/templates/copier-aegis-project/{{ project_slug }}/app/models/__init__.py +1 -0
  294. aegis/templates/copier-aegis-project/{{ project_slug }}/app/models/user.py +44 -0
  295. aegis/templates/copier-aegis-project/{{ project_slug }}/app/py.typed +0 -0
  296. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/__init__.py +1 -0
  297. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/__init__.py +8 -0
  298. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/config.py +130 -0
  299. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/conversation.py +213 -0
  300. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/health.py +96 -0
  301. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/models.py +229 -0
  302. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/providers.py.jinja +370 -0
  303. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/ai/service.py +387 -0
  304. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/auth/__init__.py +1 -0
  305. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/auth/auth_service.py +40 -0
  306. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/auth/health.py +162 -0
  307. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/auth/user_service.py +82 -0
  308. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/backend/middleware_inspector.py.jinja +223 -0
  309. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/backend/models.py.jinja +70 -0
  310. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/backend/route_inspector.py.jinja +155 -0
  311. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/load_test.py +678 -0
  312. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/load_test_models.py +265 -0
  313. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/scheduler/__init__.py.jinja +21 -0
  314. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/scheduler/models.py.jinja +119 -0
  315. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/scheduler/scheduled_task_manager.py.jinja +273 -0
  316. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/scheduler/task_monitor.py.jinja +189 -0
  317. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/shared/__init__.py +15 -0
  318. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/shared/models.py +26 -0
  319. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/system/__init__.py +52 -0
  320. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/system/alerts.py +94 -0
  321. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/system/backup.py.jinja +119 -0
  322. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/system/health.py.jinja +1333 -0
  323. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/system/models.py +243 -0
  324. aegis/templates/copier-aegis-project/{{ project_slug }}/app/services/system/ui.py +52 -0
  325. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57223!aegis-manifesto.png +0 -0
  326. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57224!aegis-manifesto-dark.png +0 -0
  327. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57225!aegis-manifesto-square-backup.png +0 -0
  328. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57533!aegis-manifesto.png +0 -0
  329. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57534!aegis-manifesto-dark.png +0 -0
  330. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57538!aegis-manifesto-square-backup.png +0 -0
  331. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57897!aegis-manifesto.png +0 -0
  332. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57898!aegis-manifesto-dark.png +0 -0
  333. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!57904!aegis-manifesto-square-backup.png +0 -0
  334. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!58315!aegis-manifesto.png +0 -0
  335. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!58316!aegis-manifesto-dark.png +0 -0
  336. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!58324!aegis-manifesto-square-backup.png +0 -0
  337. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!58837!aegis-manifesto.png +0 -0
  338. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!58838!aegis-manifesto-dark.png +0 -0
  339. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/.!58849!aegis-manifesto-square-backup.png +0 -0
  340. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/aegis-manifesto-dark.png +0 -0
  341. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/aegis-manifesto-square-backup.png +0 -0
  342. aegis/templates/copier-aegis-project/{{ project_slug }}/assets/aegis-manifesto.png +0 -0
  343. aegis/templates/copier-aegis-project/{{ project_slug }}/clean-validation/.env.example.jinja +64 -0
  344. aegis/templates/copier-aegis-project/{{ project_slug }}/clean-validation/README.md.jinja +172 -0
  345. aegis/templates/copier-aegis-project/{{ project_slug }}/clean-validation/docker-compose.yml.jinja +78 -0
  346. aegis/templates/copier-aegis-project/{{ project_slug }}/clean-validation/mkdocs.yml.jinja +62 -0
  347. aegis/templates/copier-aegis-project/{{ project_slug }}/clean-validation/pyproject.toml.jinja +120 -0
  348. aegis/templates/copier-aegis-project/{{ project_slug }}/docker-compose.yml.jinja +200 -0
  349. aegis/templates/copier-aegis-project/{{ project_slug }}/docs/api.md.jinja +191 -0
  350. aegis/templates/copier-aegis-project/{{ project_slug }}/docs/components/scheduler.md +0 -0
  351. aegis/templates/copier-aegis-project/{{ project_slug }}/docs/components/scheduler.md.jinja +621 -0
  352. aegis/templates/copier-aegis-project/{{ project_slug }}/docs/development.md.jinja +215 -0
  353. aegis/templates/copier-aegis-project/{{ project_slug }}/docs/health.md.jinja +240 -0
  354. aegis/templates/copier-aegis-project/{{ project_slug }}/docs/javascripts/mermaid-config.js +62 -0
  355. aegis/templates/copier-aegis-project/{{ project_slug }}/docs/stylesheets/mermaid.css +95 -0
  356. aegis/templates/copier-aegis-project/{{ project_slug }}/mkdocs.yml.jinja +62 -0
  357. aegis/templates/copier-aegis-project/{{ project_slug }}/pyproject.toml.jinja +131 -0
  358. aegis/templates/copier-aegis-project/{{ project_slug }}/scripts/entrypoint.sh +87 -0
  359. aegis/templates/copier-aegis-project/{{ project_slug }}/scripts/entrypoint.sh.jinja +93 -0
  360. aegis/templates/copier-aegis-project/{{ project_slug }}/scripts/gen_docs.py +16 -0
  361. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/api/__init__.py +1 -0
  362. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/api/test_auth_endpoints.py.jinja +307 -0
  363. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/api/test_health_endpoints.py.jinja +262 -0
  364. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/api/test_scheduler_endpoints.py.jinja +214 -0
  365. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/api/test_worker_endpoints.py.jinja +165 -0
  366. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/cli/test_ai_rendering.py +427 -0
  367. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/cli/test_conversation_memory.py +465 -0
  368. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/components/test_scheduler.py +43 -0
  369. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/conftest.py.jinja +195 -0
  370. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/__init__.py +1 -0
  371. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/ai/__init__.py +1 -0
  372. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/ai/conftest.py +78 -0
  373. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/ai/test_health.py +157 -0
  374. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/ai/test_models.py +164 -0
  375. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/ai/test_service.py +198 -0
  376. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_auth_integration.py.jinja +528 -0
  377. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_component_integration.py.jinja +387 -0
  378. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_conversation_persistence.py +342 -0
  379. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_health_logic.py.jinja +663 -0
  380. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_load_test_models.py +619 -0
  381. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_load_test_service.py +603 -0
  382. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_middleware_inspector.py.jinja +248 -0
  383. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_scheduled_task_manager.py.jinja +292 -0
  384. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_system_service.py +98 -0
  385. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/services/test_worker_health_registration.py.jinja +257 -0
  386. aegis/templates/copier-aegis-project/{{ project_slug }}/tests/test_core.py +49 -0
  387. aegis/templates/copier-aegis-project/{{ project_slug }}/uv.lock +1673 -0
  388. aegis_stack-0.2.0rc2.dist-info/METADATA +165 -0
  389. aegis_stack-0.2.0rc2.dist-info/RECORD +392 -0
  390. aegis_stack-0.2.0rc2.dist-info/WHEEL +4 -0
  391. aegis_stack-0.2.0rc2.dist-info/entry_points.txt +3 -0
  392. aegis_stack-0.2.0rc2.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,261 @@
1
+ """
2
+ Service dependency resolution for Aegis Stack.
3
+
4
+ This module handles service dependency resolution, converting service selections
5
+ to their required components and validating service-to-component compatibility.
6
+ """
7
+
8
+ from .dependency_resolver import DependencyResolver
9
+ from .services import SERVICES, get_service_dependencies
10
+
11
+
12
+ class ServiceResolver:
13
+ """Handles service dependency resolution and validation."""
14
+
15
+ @staticmethod
16
+ def resolve_service_dependencies(
17
+ selected_services: list[str],
18
+ ) -> tuple[list[str], list[str]]:
19
+ """
20
+ Resolve services to their required components.
21
+
22
+ Args:
23
+ selected_services: List of service names selected by user
24
+
25
+ Returns:
26
+ Tuple of (resolved_components, service_added_components)
27
+ - resolved_components: All components needed (including auto-added)
28
+ - service_added_components: Components that were added due to services
29
+
30
+ Raises:
31
+ ValueError: If any selected services are invalid
32
+ """
33
+ # Validate services first
34
+ errors = ServiceResolver.validate_services(selected_services)
35
+ if errors:
36
+ raise ValueError(f"Invalid services: {'; '.join(errors)}")
37
+
38
+ # Collect all components required by services
39
+ service_required_components = set()
40
+ for service_name in selected_services:
41
+ service_deps = get_service_dependencies(service_name)
42
+ service_required_components.update(service_deps)
43
+
44
+ # Convert to list and resolve component-to-component dependencies
45
+ component_list = list(service_required_components)
46
+ resolved_components = DependencyResolver.resolve_dependencies(component_list)
47
+
48
+ # The service_added_components are what services directly require
49
+ # (This represents what gets auto-added when services are selected)
50
+ service_added_components = sorted(service_required_components)
51
+
52
+ return resolved_components, service_added_components
53
+
54
+ @staticmethod
55
+ def validate_services(services: list[str]) -> list[str]:
56
+ """
57
+ Validate service selection and return errors.
58
+
59
+ Args:
60
+ services: List of service names to validate
61
+
62
+ Returns:
63
+ List of error messages (empty if valid)
64
+ """
65
+ errors = []
66
+
67
+ for service in services:
68
+ if service not in SERVICES:
69
+ errors.append(f"Unknown service: {service}")
70
+ continue
71
+
72
+ spec = SERVICES[service]
73
+
74
+ # Check service conflicts
75
+ if spec.conflicts:
76
+ for conflict in spec.conflicts:
77
+ if conflict in services:
78
+ errors.append(
79
+ f"Service '{service}' conflicts with service '{conflict}'"
80
+ )
81
+
82
+ # Check for service-to-service dependencies
83
+ for service in services:
84
+ if service not in SERVICES:
85
+ continue # Already reported above
86
+
87
+ spec = SERVICES[service]
88
+ if spec.required_services:
89
+ for required_service in spec.required_services:
90
+ if required_service not in services:
91
+ errors.append(
92
+ f"Service '{service}' requires service '{required_service}'"
93
+ )
94
+
95
+ return errors
96
+
97
+ @staticmethod
98
+ def get_missing_components_for_services(
99
+ selected_services: list[str], available_components: list[str]
100
+ ) -> list[str]:
101
+ """
102
+ Get components that need to be added for the selected services.
103
+
104
+ Args:
105
+ selected_services: List of service names
106
+ available_components: List of already available components
107
+
108
+ Returns:
109
+ List of component names that need to be added
110
+ """
111
+ if not selected_services:
112
+ return []
113
+
114
+ try:
115
+ resolved_components, _ = ServiceResolver.resolve_service_dependencies(
116
+ selected_services
117
+ )
118
+ missing = set(resolved_components) - set(available_components)
119
+ return sorted(missing)
120
+ except ValueError:
121
+ # If services are invalid, return empty list
122
+ return []
123
+
124
+ @staticmethod
125
+ def validate_service_component_compatibility(
126
+ selected_services: list[str], available_components: list[str]
127
+ ) -> list[str]:
128
+ """
129
+ Validate that all required components are available for selected services.
130
+
131
+ Args:
132
+ selected_services: List of service names
133
+ available_components: List of available component names
134
+
135
+ Returns:
136
+ List of error messages (empty if compatible)
137
+ """
138
+ errors = []
139
+
140
+ # First validate the services themselves
141
+ service_errors = ServiceResolver.validate_services(selected_services)
142
+ errors.extend(service_errors)
143
+
144
+ # Then check component dependencies
145
+ for service_name in selected_services:
146
+ if service_name not in SERVICES:
147
+ continue # Already reported in service validation
148
+
149
+ service_deps = get_service_dependencies(service_name)
150
+ for required_comp in service_deps:
151
+ if required_comp not in available_components:
152
+ errors.append(
153
+ f"Service '{service_name}' requires component '{required_comp}'"
154
+ )
155
+
156
+ return errors
157
+
158
+ @staticmethod
159
+ def get_service_component_summary(
160
+ selected_services: list[str],
161
+ ) -> dict[str, list[str]]:
162
+ """
163
+ Get a summary of what components each service requires.
164
+
165
+ Args:
166
+ selected_services: List of service names
167
+
168
+ Returns:
169
+ Dictionary mapping service names to their required components
170
+ """
171
+ summary = {}
172
+
173
+ for service_name in selected_services:
174
+ if service_name in SERVICES:
175
+ summary[service_name] = get_service_dependencies(service_name)
176
+ else:
177
+ summary[service_name] = [] # Unknown service
178
+
179
+ return summary
180
+
181
+ @staticmethod
182
+ def recommend_components_for_services(selected_services: list[str]) -> list[str]:
183
+ """
184
+ Get recommended (but not required) components for selected services.
185
+
186
+ Args:
187
+ selected_services: List of service names
188
+
189
+ Returns:
190
+ List of recommended component names
191
+ """
192
+ recommendations = set()
193
+
194
+ for service_name in selected_services:
195
+ if service_name not in SERVICES:
196
+ continue
197
+
198
+ spec = SERVICES[service_name]
199
+ if spec.recommended_components:
200
+ recommendations.update(spec.recommended_components)
201
+
202
+ return sorted(recommendations)
203
+
204
+ @staticmethod
205
+ def get_services_requiring_component(component_name: str) -> list[str]:
206
+ """
207
+ Get list of services that require a specific component.
208
+
209
+ Args:
210
+ component_name: Name of the component
211
+
212
+ Returns:
213
+ List of service names that require this component
214
+ """
215
+ requiring_services = []
216
+
217
+ for service_name, spec in SERVICES.items():
218
+ if component_name in spec.required_components:
219
+ requiring_services.append(service_name)
220
+
221
+ return requiring_services
222
+
223
+ @staticmethod
224
+ def merge_service_and_component_selections(
225
+ selected_services: list[str], selected_components: list[str]
226
+ ) -> tuple[list[str], list[str], list[str]]:
227
+ """
228
+ Merge service and component selections, resolving all dependencies.
229
+
230
+ Args:
231
+ selected_services: List of service names
232
+ selected_components: List of component names
233
+
234
+ Returns:
235
+ Tuple of (final_components, auto_added_from_services, auto_added_from_components)
236
+ - final_components: Final list of all components
237
+ - auto_added_from_services: Components added due to service requirements
238
+ - auto_added_from_components: Components added due to component dependencies
239
+
240
+ Raises:
241
+ ValueError: If services or components are invalid
242
+ """
243
+ # Resolve service dependencies first
244
+ service_components, service_auto_added = (
245
+ ServiceResolver.resolve_service_dependencies(selected_services)
246
+ )
247
+
248
+ # Combine with explicitly selected components
249
+ all_components = list(set(selected_components + service_components))
250
+
251
+ # Resolve final component dependencies
252
+ final_components = DependencyResolver.resolve_dependencies(all_components)
253
+
254
+ # Calculate what was auto-added from component dependencies
255
+ component_auto_added = set(final_components) - set(all_components)
256
+
257
+ return (
258
+ final_components,
259
+ sorted(service_auto_added),
260
+ sorted(component_auto_added),
261
+ )
aegis/core/services.py ADDED
@@ -0,0 +1,157 @@
1
+ """
2
+ Service registry and specifications for Aegis Stack.
3
+
4
+ This module defines all available services (auth, payment, AI, etc.), their dependencies,
5
+ and metadata used for project generation and validation.
6
+ """
7
+
8
+ from dataclasses import dataclass, field
9
+ from enum import Enum
10
+
11
+
12
+ class ServiceType(Enum):
13
+ """Service type classifications."""
14
+
15
+ AUTH = "auth" # Authentication and authorization
16
+ PAYMENT = "payment" # Payment processing
17
+ AI = "ai" # AI and ML integrations
18
+ NOTIFICATION = "notification" # Email, SMS, push notifications
19
+ ANALYTICS = "analytics" # Usage analytics and metrics
20
+ STORAGE = "storage" # File storage and CDN
21
+
22
+
23
+ @dataclass
24
+ class ServiceSpec:
25
+ """Specification for a single service."""
26
+
27
+ name: str
28
+ type: ServiceType
29
+ description: str
30
+ required_components: list[str] = field(
31
+ default_factory=list
32
+ ) # Components this service needs
33
+ recommended_components: list[str] = field(
34
+ default_factory=list
35
+ ) # Soft component dependencies
36
+ required_services: list[str] = field(
37
+ default_factory=list
38
+ ) # Other services this service needs
39
+ conflicts: list[str] = field(default_factory=list) # Mutual exclusions
40
+ pyproject_deps: list[str] = field(
41
+ default_factory=list
42
+ ) # Python packages for this service
43
+ template_files: list[str] = field(default_factory=list) # Template files to include
44
+
45
+
46
+ # Service registry - single source of truth for all available services
47
+ SERVICES: dict[str, ServiceSpec] = {
48
+ "auth": ServiceSpec(
49
+ name="auth",
50
+ type=ServiceType.AUTH,
51
+ description="User authentication and authorization with JWT tokens",
52
+ required_components=["backend", "database"],
53
+ pyproject_deps=[
54
+ "python-jose[cryptography]==3.3.0",
55
+ "passlib[bcrypt]==1.7.4",
56
+ "python-multipart==0.0.9", # For form data parsing
57
+ ],
58
+ template_files=[
59
+ "app/components/backend/api/auth/",
60
+ "app/models/user.py",
61
+ "app/services/auth/",
62
+ "app/core/security.py",
63
+ ],
64
+ ),
65
+ "ai": ServiceSpec(
66
+ name="ai",
67
+ type=ServiceType.AI,
68
+ description="AI chatbot service with PydanticAI engine",
69
+ required_components=["backend"],
70
+ pyproject_deps=[
71
+ "pydantic-ai-slim[{AI_PROVIDERS}]==1.0.10", # Dynamic providers
72
+ "httpx>=0.27.0", # For API providers
73
+ ],
74
+ template_files=[
75
+ "app/services/ai/",
76
+ "app/cli/ai.py",
77
+ "app/components/backend/api/ai/",
78
+ ],
79
+ ),
80
+ # Future services will be added here:
81
+ # "payment_stripe": ServiceSpec(...),
82
+ # "notification_email": ServiceSpec(...),
83
+ }
84
+
85
+
86
+ def get_service(name: str) -> ServiceSpec:
87
+ """Get service specification by name."""
88
+ if name not in SERVICES:
89
+ raise ValueError(f"Unknown service: {name}")
90
+ return SERVICES[name]
91
+
92
+
93
+ def get_services_by_type(service_type: ServiceType) -> dict[str, ServiceSpec]:
94
+ """Get all services of a specific type."""
95
+ return {name: spec for name, spec in SERVICES.items() if spec.type == service_type}
96
+
97
+
98
+ def list_available_services() -> list[str]:
99
+ """Get list of all available service names."""
100
+ return list(SERVICES.keys())
101
+
102
+
103
+ def get_service_dependencies(service_name: str) -> list[str]:
104
+ """
105
+ Get all required components for a service.
106
+
107
+ Args:
108
+ service_name: Name of the service
109
+
110
+ Returns:
111
+ List of component names required by this service
112
+ """
113
+ if service_name not in SERVICES:
114
+ return []
115
+
116
+ service = SERVICES[service_name]
117
+ return service.required_components.copy()
118
+
119
+
120
+ def validate_service_dependencies(
121
+ selected_services: list[str], available_components: list[str]
122
+ ) -> list[str]:
123
+ """
124
+ Validate that all required components are available for selected services.
125
+
126
+ Args:
127
+ selected_services: List of service names to validate
128
+ available_components: List of available component names
129
+
130
+ Returns:
131
+ List of error messages (empty if valid)
132
+ """
133
+ errors = []
134
+
135
+ for service_name in selected_services:
136
+ if service_name not in SERVICES:
137
+ errors.append(f"Unknown service: {service_name}")
138
+ continue
139
+
140
+ service = SERVICES[service_name]
141
+
142
+ # Check required components
143
+ for required_comp in service.required_components:
144
+ if required_comp not in available_components:
145
+ errors.append(
146
+ f"Service '{service_name}' requires component '{required_comp}'"
147
+ )
148
+
149
+ # Check service conflicts
150
+ if service.conflicts:
151
+ for conflict in service.conflicts:
152
+ if conflict in selected_services:
153
+ errors.append(
154
+ f"Service '{service_name}' conflicts with service '{conflict}'"
155
+ )
156
+
157
+ return errors
@@ -0,0 +1,266 @@
1
+ """
2
+ Template generation and context building for Aegis Stack projects.
3
+
4
+ This module handles the generation of cookiecutter context and manages
5
+ the template rendering process based on selected components.
6
+ """
7
+
8
+ from pathlib import Path
9
+ from typing import Any
10
+
11
+ from .component_utils import extract_base_component_name, extract_engine_info
12
+ from .components import COMPONENTS, CORE_COMPONENTS, SchedulerBackend
13
+ from .services import SERVICES
14
+
15
+
16
+ class TemplateGenerator:
17
+ """Handles template context generation for cookiecutter."""
18
+
19
+ def __init__(
20
+ self,
21
+ project_name: str,
22
+ selected_components: list[str],
23
+ scheduler_backend: str = SchedulerBackend.MEMORY.value,
24
+ selected_services: list[str] | None = None,
25
+ ):
26
+ """
27
+ Initialize template generator.
28
+
29
+ Args:
30
+ project_name: Name of the project being generated
31
+ selected_components: List of component names to include
32
+ scheduler_backend: Scheduler backend: memory, sqlite, or postgres
33
+ selected_services: List of service names to include
34
+ """
35
+ self.project_name = project_name
36
+ self.project_slug = project_name.lower().replace(" ", "-").replace("_", "-")
37
+ self.scheduler_backend = scheduler_backend
38
+ self.selected_services = selected_services or []
39
+
40
+ # Always include core components
41
+ all_components = CORE_COMPONENTS + selected_components
42
+
43
+ # Add required components from selected services
44
+ for service_name in self.selected_services:
45
+ if service_name in SERVICES:
46
+ service_spec = SERVICES[service_name]
47
+ all_components.extend(service_spec.required_components)
48
+
49
+ # Remove duplicates, preserve order
50
+ self.components = list(dict.fromkeys(all_components))
51
+
52
+ # Extract database engine from database[engine] format for template context
53
+ self.database_engine = None
54
+ for component in self.components:
55
+ if extract_base_component_name(component) == "database":
56
+ self.database_engine = extract_engine_info(component)
57
+ if self.database_engine:
58
+ break
59
+
60
+ # Extract scheduler backend from scheduler[backend] format or use passed param
61
+ # If scheduler[backend] syntax is used, it overrides the passed parameter
62
+ for component in self.components:
63
+ if extract_base_component_name(component) == "scheduler":
64
+ backend = extract_engine_info(component)
65
+ if backend:
66
+ self.scheduler_backend = backend
67
+ break
68
+
69
+ # Build component specs using base names
70
+ self.component_specs = {}
71
+ for name in self.components:
72
+ base_name = extract_base_component_name(name)
73
+ if base_name in COMPONENTS:
74
+ self.component_specs[base_name] = COMPONENTS[base_name]
75
+
76
+ def get_template_context(self) -> dict[str, Any]:
77
+ """
78
+ Generate cookiecutter context from components.
79
+
80
+ Returns:
81
+ Dictionary containing all template variables
82
+ """
83
+ # Store the originally selected components (without core)
84
+ selected_only = [c for c in self.components if c not in CORE_COMPONENTS]
85
+
86
+ # Check for components using base names
87
+ has_database = any(c.startswith("database") for c in self.components)
88
+
89
+ return {
90
+ "project_name": self.project_name,
91
+ "project_slug": self.project_slug,
92
+ # Component flags for template conditionals - cookiecutter needs yes/no
93
+ "include_redis": "yes" if "redis" in self.components else "no",
94
+ "include_worker": "yes" if "worker" in self.components else "no",
95
+ "include_scheduler": "yes"
96
+ if any(c.startswith("scheduler") for c in self.components)
97
+ else "no",
98
+ "include_database": "yes" if has_database else "no",
99
+ # Database engine selection
100
+ "database_engine": self.database_engine or "sqlite",
101
+ # Scheduler backend selection
102
+ "scheduler_backend": self.scheduler_backend,
103
+ # Legacy scheduler persistence flag for backwards compatibility
104
+ "scheduler_with_persistence": (
105
+ "yes"
106
+ if self.scheduler_backend != SchedulerBackend.MEMORY.value
107
+ else "no"
108
+ ),
109
+ # Derived flags for template logic
110
+ "has_background_infrastructure": any(
111
+ name in self.components for name in ["worker", "scheduler"]
112
+ ),
113
+ "needs_redis": "redis" in self.components,
114
+ # Service flags for template conditionals
115
+ "include_auth": "yes" if "auth" in self.selected_services else "no",
116
+ "include_ai": "yes" if "ai" in self.selected_services else "no",
117
+ # AI provider selection for dynamic dependency generation
118
+ "ai_providers": self._get_ai_providers_string(),
119
+ # Dependency lists for templates
120
+ "selected_components": selected_only, # Original selection for context
121
+ "docker_services": self._get_docker_services(),
122
+ "pyproject_dependencies": self._get_pyproject_deps(),
123
+ }
124
+
125
+ def _get_docker_services(self) -> list[str]:
126
+ """
127
+ Collect all docker services needed.
128
+
129
+ Returns:
130
+ List of docker service names
131
+ """
132
+ services = []
133
+ for component_name in self.components:
134
+ if component_name in self.component_specs:
135
+ spec = self.component_specs[component_name]
136
+ if spec.docker_services:
137
+ services.extend(spec.docker_services)
138
+ return list(dict.fromkeys(services)) # Preserve order, remove duplicates
139
+
140
+ def _get_pyproject_deps(self) -> list[str]:
141
+ """
142
+ Collect all Python dependencies.
143
+
144
+ Returns:
145
+ Sorted list of Python package dependencies
146
+ """
147
+ deps = []
148
+ # Collect component dependencies
149
+ for component_name in self.components:
150
+ if component_name in self.component_specs:
151
+ spec = self.component_specs[component_name]
152
+ if spec.pyproject_deps:
153
+ deps.extend(spec.pyproject_deps)
154
+
155
+ # Collect service dependencies
156
+ for service_name in self.selected_services:
157
+ if service_name in SERVICES:
158
+ service_spec = SERVICES[service_name]
159
+ if service_spec.pyproject_deps:
160
+ # Process service dependencies with dynamic substitution
161
+ for dep in service_spec.pyproject_deps:
162
+ if service_name == "ai" and "{AI_PROVIDERS}" in dep:
163
+ # Substitute AI providers dynamically
164
+ providers = self._get_ai_providers_string()
165
+ dep = dep.replace("{AI_PROVIDERS}", providers)
166
+ deps.append(dep)
167
+
168
+ return sorted(set(deps)) # Sort and deduplicate
169
+
170
+ def get_template_files(self) -> list[str]:
171
+ """
172
+ Get list of template files that should be included.
173
+
174
+ Returns:
175
+ List of template file paths
176
+ """
177
+ files = []
178
+ # Collect component template files
179
+ for component_name in self.components:
180
+ base_name = extract_base_component_name(component_name)
181
+ if base_name in self.component_specs:
182
+ spec = self.component_specs[base_name]
183
+ if spec.template_files:
184
+ files.extend(spec.template_files)
185
+
186
+ # Collect service template files
187
+ for service_name in self.selected_services:
188
+ if service_name in SERVICES:
189
+ service_spec = SERVICES[service_name]
190
+ if service_spec.template_files:
191
+ files.extend(service_spec.template_files)
192
+
193
+ return list(dict.fromkeys(files)) # Preserve order, remove duplicates
194
+
195
+ def _get_ai_providers_string(self) -> str:
196
+ """
197
+ Get AI providers as comma-separated string for pydantic-ai-slim dependency.
198
+
199
+ Returns:
200
+ Comma-separated string of provider names (e.g., "openai,anthropic,google")
201
+ """
202
+ if "ai" not in self.selected_services:
203
+ return "openai" # Default for PUBLIC provider
204
+
205
+ # Import here to avoid circular imports
206
+ from ..cli.interactive import get_ai_provider_selection
207
+
208
+ providers = get_ai_provider_selection("ai")
209
+ return ",".join(providers)
210
+
211
+ def get_entrypoints(self) -> list[str]:
212
+ """
213
+ Get list of entrypoints that will be created.
214
+
215
+ Returns:
216
+ List of entrypoint file paths
217
+ """
218
+ entrypoints = ["app/entrypoints/webserver.py"] # Always included
219
+
220
+ # Check component specs for actual entrypoint files
221
+ for component_name in self.components:
222
+ base_name = extract_base_component_name(component_name)
223
+ if base_name in self.component_specs:
224
+ spec = self.component_specs[base_name]
225
+ if spec.template_files:
226
+ for template_file in spec.template_files:
227
+ if (
228
+ template_file.startswith("app/entrypoints/")
229
+ and template_file not in entrypoints
230
+ ):
231
+ entrypoints.append(template_file)
232
+
233
+ return entrypoints
234
+
235
+ def get_worker_queues(self) -> list[str]:
236
+ """
237
+ Get list of worker queue files that will be created.
238
+
239
+ Returns:
240
+ List of worker queue file paths
241
+ """
242
+ queues: list[str] = []
243
+
244
+ # Only check if worker component is included
245
+ if not any(c.startswith("worker") for c in self.components):
246
+ return queues
247
+
248
+ # Discover queue files from the template directory
249
+ template_root = (
250
+ Path(__file__).parent.parent / "templates" / "cookiecutter-aegis-project"
251
+ )
252
+ worker_queues_dir = (
253
+ template_root
254
+ / "{{cookiecutter.project_slug}}"
255
+ / "app"
256
+ / "components"
257
+ / "worker"
258
+ / "queues"
259
+ )
260
+
261
+ if worker_queues_dir.exists():
262
+ for queue_file in worker_queues_dir.glob("*.py"):
263
+ if queue_file.stem != "__init__":
264
+ queues.append(f"app/components/worker/queues/{queue_file.name}")
265
+
266
+ return sorted(queues)