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,92 @@
1
+ # app/core/log.py
2
+ """
3
+ Core logging configuration for the application.
4
+
5
+ This module sets up structlog to provide structured, context-aware logging.
6
+ It supports both human-readable console output for development and JSON
7
+ output for production environments.
8
+ """
9
+
10
+ import logging
11
+ import sys
12
+
13
+ import structlog
14
+ from app.core.config import settings
15
+ from structlog.types import Processor
16
+
17
+ # A global logger instance for easy access throughout the application
18
+ logger: structlog.stdlib.BoundLogger = structlog.get_logger()
19
+
20
+
21
+ def setup_logging() -> None:
22
+ """
23
+ Configures logging for the entire application.
24
+
25
+ This function sets up structlog with processors for structured logging.
26
+ It routes all standard library logging through structlog to ensure
27
+ consistent log formats. The output format is determined by the APP_ENV
28
+ setting (dev-friendly console format or production-ready JSON format).
29
+ """
30
+ # Type hint for the list of processors
31
+ shared_processors: list[Processor] = [
32
+ structlog.stdlib.add_logger_name,
33
+ structlog.stdlib.add_log_level,
34
+ structlog.stdlib.PositionalArgumentsFormatter(),
35
+ structlog.processors.TimeStamper(fmt="iso"),
36
+ structlog.processors.StackInfoRenderer(),
37
+ structlog.processors.format_exc_info,
38
+ ]
39
+
40
+ # Configure structlog
41
+ structlog.configure(
42
+ processors=shared_processors
43
+ + [
44
+ # Prepare event dict for `ProcessorFormatter`.
45
+ structlog.stdlib.ProcessorFormatter.wrap_for_formatter,
46
+ ],
47
+ logger_factory=structlog.stdlib.LoggerFactory(),
48
+ wrapper_class=structlog.stdlib.BoundLogger,
49
+ cache_logger_on_first_use=True,
50
+ )
51
+
52
+ # Define the formatter based on the environment
53
+ if settings.APP_ENV == "dev":
54
+ formatter = structlog.stdlib.ProcessorFormatter(
55
+ # The final processor formats the log entry for console output.
56
+ processor=structlog.dev.ConsoleRenderer(colors=True),
57
+ )
58
+ else:
59
+ formatter = structlog.stdlib.ProcessorFormatter(
60
+ # The final processor formats the log entry as JSON.
61
+ processor=structlog.processors.JSONRenderer(),
62
+ # Remove metadata added by ProcessorFormatter
63
+ foreign_pre_chain=shared_processors,
64
+ )
65
+
66
+ # Configure the root logger
67
+ handler = logging.StreamHandler(sys.stdout)
68
+ handler.setFormatter(formatter)
69
+ root_logger = logging.getLogger()
70
+
71
+ # CRITICAL: Set log level BEFORE adding handler
72
+ # This ensures all loggers (including import-time loggers) respect the level
73
+ log_level = settings.LOG_LEVEL.upper()
74
+ root_logger.setLevel(getattr(logging, log_level))
75
+
76
+ # Add handler after level is set
77
+ root_logger.addHandler(handler)
78
+
79
+ # Adjust log levels for noisy third-party libraries
80
+ logging.getLogger("flet_core").setLevel(logging.INFO)
81
+ logging.getLogger("flet_runtime").setLevel(logging.INFO)
82
+ logging.getLogger("flet_fastapi").setLevel(logging.INFO)
83
+ logging.getLogger("uvicorn.access").setLevel(logging.WARNING)
84
+
85
+ log_format = "DEV" if settings.APP_ENV == "dev" else "JSON"
86
+ logger.info(
87
+ "Logging setup complete",
88
+ level=log_level,
89
+ log_format=log_format,
90
+ root_level=root_logger.level,
91
+ effective_level=root_logger.getEffectiveLevel(),
92
+ )
@@ -0,0 +1,62 @@
1
+ """Security utilities for authentication and authorization."""
2
+
3
+ from datetime import UTC, datetime, timedelta
4
+ from typing import Any
5
+
6
+ from app.core.config import settings
7
+ from jose import JWTError, jwt
8
+ from passlib.context import CryptContext
9
+
10
+ pwd_context = CryptContext(
11
+ schemes=["bcrypt"],
12
+ deprecated="auto",
13
+ )
14
+
15
+
16
+ def _truncate_password(password: str) -> str:
17
+ """Truncate password to 72 bytes for bcrypt compatibility.
18
+
19
+ bcrypt has a 72-byte limit. We explicitly truncate here rather than
20
+ rely on library defaults for clarity and consistency.
21
+ """
22
+ return password[:72]
23
+
24
+
25
+ def create_access_token(
26
+ data: dict[str, Any], expires_delta: timedelta | None = None
27
+ ) -> str:
28
+ """Create a JWT access token."""
29
+ to_encode = data.copy()
30
+ if expires_delta:
31
+ expire = datetime.now(UTC) + expires_delta
32
+ else:
33
+ expire = datetime.now(UTC) + timedelta(
34
+ minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES
35
+ )
36
+
37
+ to_encode.update({"exp": expire})
38
+ encoded_jwt = jwt.encode(
39
+ to_encode, settings.SECRET_KEY, algorithm=settings.JWT_ALGORITHM
40
+ )
41
+ return encoded_jwt
42
+
43
+
44
+ def verify_token(token: str) -> dict[str, Any] | None:
45
+ """Verify and decode a JWT token."""
46
+ try:
47
+ payload = jwt.decode(
48
+ token, settings.SECRET_KEY, algorithms=[settings.JWT_ALGORITHM]
49
+ )
50
+ return payload
51
+ except JWTError:
52
+ return None
53
+
54
+
55
+ def get_password_hash(password: str) -> str:
56
+ """Hash a password."""
57
+ return pwd_context.hash(_truncate_password(password))
58
+
59
+
60
+ def verify_password(plain_password: str, hashed_password: str) -> bool:
61
+ """Verify a password against its hash."""
62
+ return pwd_context.verify(_truncate_password(plain_password), hashed_password)
@@ -0,0 +1 @@
1
+ # Entry points for different Aegis Stack execution modes
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Scheduler entrypoint for {{ project_name }}.
4
+
5
+ This entrypoint starts the scheduler component.
6
+ """
7
+
8
+ import asyncio
9
+
10
+ from app.components.scheduler.main import run_scheduler
11
+ from app.core.log import setup_logging
12
+
13
+
14
+ async def main() -> None:
15
+ """Main scheduler entry point"""
16
+ setup_logging()
17
+ await run_scheduler()
18
+
19
+
20
+ if __name__ == "__main__":
21
+ asyncio.run(main())
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Web server entry point for Aegis Stack.
4
+ Runs FastAPI + Flet only (clean separation of concerns).
5
+ """
6
+
7
+ import uvicorn
8
+ from app.core.config import settings
9
+ from app.core.log import logger, setup_logging
10
+ from app.integrations.main import create_integrated_app
11
+
12
+
13
+ def main() -> None:
14
+ """Main webserver entry point"""
15
+ setup_logging()
16
+ logger.info("Starting Aegis Stack Web Server...")
17
+
18
+ # Run the web server
19
+ if settings.AUTO_RELOAD:
20
+ # When reload is enabled, uvicorn requires an import string
21
+ uvicorn.run(
22
+ "app.integrations.main:create_integrated_app",
23
+ factory=True,
24
+ host="0.0.0.0",
25
+ port=settings.PORT,
26
+ reload=True,
27
+ )
28
+ else:
29
+ # Use the integration layer (handles webserver hooks, service discovery, etc.)
30
+ app = create_integrated_app()
31
+ uvicorn.run(
32
+ app,
33
+ host="0.0.0.0",
34
+ port=settings.PORT,
35
+ )
36
+
37
+
38
+ if __name__ == "__main__":
39
+ main()
@@ -0,0 +1,61 @@
1
+ from collections.abc import AsyncGenerator
2
+ from contextlib import asynccontextmanager
3
+
4
+ import flet.fastapi as flet_fastapi
5
+ from app.components.backend.hooks import backend_hooks
6
+ from app.components.backend.main import create_backend_app
7
+ from app.components.frontend.main import create_frontend_app
8
+ from app.core.config import settings
9
+ from app.core.log import logger
10
+ from fastapi import FastAPI
11
+
12
+
13
+ @asynccontextmanager
14
+ async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]:
15
+ """
16
+ Application lifespan manager.
17
+ Handles startup/shutdown concerns using component-specific hooks.
18
+ """
19
+ # --- STARTUP ---
20
+ logger.info("--- Running application startup ---")
21
+
22
+ # Discover startup and shutdown hooks
23
+ await backend_hooks.discover_lifespan_hooks()
24
+
25
+ # Start Flet app manager
26
+ await flet_fastapi.app_manager.start()
27
+
28
+ # Execute backend startup hooks
29
+ await backend_hooks.execute_startup_hooks()
30
+
31
+ logger.info("--- Application startup complete ---")
32
+
33
+ yield
34
+
35
+ # --- SHUTDOWN ---
36
+ logger.info("--- Running application shutdown ---")
37
+
38
+ # Execute backend shutdown hooks
39
+ await backend_hooks.execute_shutdown_hooks()
40
+
41
+ # Stop Flet app manager
42
+ await flet_fastapi.app_manager.shutdown()
43
+
44
+ logger.info("--- Application shutdown complete ---")
45
+
46
+
47
+ def create_integrated_app() -> FastAPI:
48
+ """
49
+ Creates the integrated Flet+FastAPI application using the officially
50
+ recommended pattern and component-specific hooks.
51
+ """
52
+ app = FastAPI(lifespan=lifespan)
53
+
54
+ create_backend_app(app)
55
+ # Create and mount the Flet app using the flet.fastapi module
56
+ # First, get the actual session handler function from the factory
57
+ session_handler = create_frontend_app()
58
+ flet_app = flet_fastapi.app(session_handler, assets_dir=settings.FLET_ASSETS_DIR)
59
+ # Mount Flet at /dashboard to avoid intercepting FastAPI routes like /health
60
+ app.mount("/dashboard", flet_app)
61
+ return app
@@ -0,0 +1 @@
1
+ """Data models for the application."""
@@ -0,0 +1,44 @@
1
+ """User data models."""
2
+
3
+ from datetime import UTC, datetime
4
+
5
+ from pydantic import EmailStr
6
+ from sqlmodel import Field, SQLModel
7
+
8
+
9
+ class UserBase(SQLModel):
10
+ """Base user model with shared fields."""
11
+
12
+ email: EmailStr = Field(unique=True, index=True)
13
+ full_name: str | None = None
14
+ is_active: bool = Field(default=True)
15
+
16
+
17
+ class User(UserBase, table=True):
18
+ """User database model."""
19
+
20
+ id: int | None = Field(default=None, primary_key=True)
21
+ hashed_password: str
22
+ created_at: datetime = Field(default_factory=lambda: datetime.now(UTC))
23
+ updated_at: datetime | None = None
24
+
25
+
26
+ class UserCreate(UserBase):
27
+ """User creation model."""
28
+
29
+ password: str = Field(min_length=8)
30
+
31
+
32
+ class UserLogin(SQLModel):
33
+ """User login model."""
34
+
35
+ email: str
36
+ password: str
37
+
38
+
39
+ class UserResponse(UserBase):
40
+ """User response model (excludes sensitive data)."""
41
+
42
+ id: int
43
+ created_at: datetime
44
+ updated_at: datetime | None = None
@@ -0,0 +1,8 @@
1
+ """
2
+ AI service module.
3
+
4
+ Provides AI chatbot functionality with PydanticAI engine.
5
+ """
6
+
7
+ # AI service exports will be added in ticket #159
8
+ __all__ = []
@@ -0,0 +1,130 @@
1
+ """
2
+ AI service configuration models.
3
+
4
+ Configuration management for AI service providers, models, and settings.
5
+ Integrates with main application settings through app.core.config.
6
+ """
7
+
8
+ from typing import Any
9
+
10
+ from pydantic import BaseModel, Field
11
+
12
+ from .models import (
13
+ AIProvider,
14
+ ProviderConfig,
15
+ get_provider_capabilities,
16
+ )
17
+
18
+
19
+ class AIServiceConfig(BaseModel):
20
+ """
21
+ AI service configuration that integrates with main app settings.
22
+
23
+ This class provides convenience methods and validation for AI service
24
+ configuration while the actual settings live in app.core.config.Settings.
25
+ """
26
+
27
+ enabled: bool = True
28
+ provider: AIProvider = (
29
+ AIProvider.PUBLIC
30
+ ) # Default to public endpoints (no API key required)
31
+ model: str = "gpt-3.5-turbo" # Default to widely supported model
32
+ temperature: float = Field(default=0.7, ge=0.0, le=2.0)
33
+ max_tokens: int = Field(default=1000, gt=0, le=8000)
34
+ timeout_seconds: float = Field(default=30.0, gt=0)
35
+
36
+ class Config:
37
+ use_enum_values = True
38
+
39
+ @classmethod
40
+ def from_settings(cls, settings: Any) -> "AIServiceConfig":
41
+ """Create configuration from main application settings."""
42
+ return cls(
43
+ enabled=getattr(settings, "AI_ENABLED", True),
44
+ provider=AIProvider(getattr(settings, "AI_PROVIDER", "public")),
45
+ model=getattr(settings, "AI_MODEL", "gpt-3.5-turbo"),
46
+ temperature=getattr(settings, "AI_TEMPERATURE", 0.7),
47
+ max_tokens=getattr(settings, "AI_MAX_TOKENS", 1000),
48
+ timeout_seconds=getattr(settings, "AI_TIMEOUT_SECONDS", 30.0),
49
+ )
50
+
51
+ def get_provider_config(self, settings: Any) -> ProviderConfig:
52
+ """Get provider-specific configuration."""
53
+ # Get API key based on provider
54
+ api_key_mapping = {
55
+ AIProvider.OPENAI: getattr(settings, "OPENAI_API_KEY", None),
56
+ AIProvider.ANTHROPIC: getattr(settings, "ANTHROPIC_API_KEY", None),
57
+ AIProvider.GOOGLE: getattr(settings, "GOOGLE_API_KEY", None),
58
+ AIProvider.GROQ: getattr(settings, "GROQ_API_KEY", None),
59
+ AIProvider.MISTRAL: getattr(settings, "MISTRAL_API_KEY", None),
60
+ AIProvider.COHERE: getattr(settings, "COHERE_API_KEY", None),
61
+ AIProvider.PUBLIC: None, # No API key required for public endpoints
62
+ }
63
+
64
+ return ProviderConfig(
65
+ name=self.provider,
66
+ api_key=api_key_mapping.get(self.provider),
67
+ max_tokens=self.max_tokens,
68
+ temperature=self.temperature,
69
+ timeout_seconds=self.timeout_seconds,
70
+ )
71
+
72
+ def validate_configuration(self, settings: Any) -> list[str]:
73
+ """
74
+ Validate AI service configuration and return list of issues.
75
+
76
+ Returns:
77
+ List of validation error messages (empty if valid)
78
+ """
79
+ errors = []
80
+
81
+ if not self.enabled:
82
+ return errors # Skip validation if disabled
83
+
84
+ # Check if provider is supported
85
+ capabilities = get_provider_capabilities(self.provider)
86
+ if not capabilities:
87
+ errors.append(f"Unsupported provider: {self.provider}")
88
+
89
+ # Check API key requirement (only PUBLIC provider requires no API key)
90
+ provider_config = self.get_provider_config(settings)
91
+
92
+ if self.provider != AIProvider.PUBLIC and not provider_config.api_key:
93
+ errors.append(
94
+ f"Missing API key for {self.provider} provider. "
95
+ f"Set {self.provider.upper()}_API_KEY environment variable."
96
+ )
97
+
98
+ # Note: Token limits vary by model within each provider,
99
+ # so we don't validate them here
100
+
101
+ return errors
102
+
103
+ def is_provider_available(self, settings: Any) -> bool:
104
+ """Check if the configured provider is available and properly configured."""
105
+ errors = self.validate_configuration(settings)
106
+ return len(errors) == 0
107
+
108
+ def get_available_providers(self, settings: Any) -> list[AIProvider]:
109
+ """Get list of providers that are properly configured."""
110
+ available = []
111
+
112
+ for provider in AIProvider:
113
+ # Temporarily check each provider
114
+ temp_config = AIServiceConfig(
115
+ enabled=True,
116
+ provider=provider,
117
+ model=self.model,
118
+ temperature=self.temperature,
119
+ max_tokens=self.max_tokens,
120
+ )
121
+
122
+ if len(temp_config.validate_configuration(settings)) == 0:
123
+ available.append(provider)
124
+
125
+ return available
126
+
127
+
128
+ def get_ai_config(settings: Any) -> AIServiceConfig:
129
+ """Get AI service configuration from application settings."""
130
+ return AIServiceConfig.from_settings(settings)
@@ -0,0 +1,213 @@
1
+ """
2
+ AI conversation management.
3
+
4
+ In-memory conversation storage and management for AI chat sessions.
5
+ This provides conversation persistence during application runtime.
6
+ """
7
+
8
+ import uuid
9
+ from datetime import UTC, datetime
10
+ from typing import Any
11
+
12
+ from app.core.log import logger
13
+
14
+ from .models import AIProvider, Conversation
15
+
16
+
17
+ class ConversationManager:
18
+ """
19
+ Manages AI conversations in memory.
20
+
21
+ This is a simple in-memory implementation. In production, you might want
22
+ to use a database or external storage for persistence across restarts.
23
+ """
24
+
25
+ def __init__(self):
26
+ """Initialize conversation manager with empty storage."""
27
+ self.conversations: dict[str, Conversation] = {}
28
+ # logger.info("Conversation manager initialized")
29
+
30
+ def create_conversation(
31
+ self,
32
+ provider: AIProvider,
33
+ model: str,
34
+ user_id: str = "default",
35
+ conversation_id: str | None = None,
36
+ ) -> Conversation:
37
+ """
38
+ Create a new conversation.
39
+
40
+ Args:
41
+ provider: AI provider being used
42
+ model: Model name
43
+ user_id: User identifier
44
+ conversation_id: Optional custom conversation ID
45
+
46
+ Returns:
47
+ Conversation: The created conversation
48
+ """
49
+ if conversation_id is None:
50
+ conversation_id = str(uuid.uuid4())
51
+
52
+ conversation = Conversation(
53
+ id=conversation_id,
54
+ provider=provider,
55
+ model=model,
56
+ metadata={"user_id": user_id, "created_by": "ai_service"},
57
+ )
58
+
59
+ self.conversations[conversation_id] = conversation
60
+ # logger.debug(f"Created conversation {conversation_id} for user {user_id}")
61
+
62
+ return conversation
63
+
64
+ def get_conversation(self, conversation_id: str) -> Conversation | None:
65
+ """
66
+ Get a conversation by ID.
67
+
68
+ Args:
69
+ conversation_id: The conversation identifier
70
+
71
+ Returns:
72
+ Conversation | None: The conversation if found, None otherwise
73
+ """
74
+ return self.conversations.get(conversation_id)
75
+
76
+ def save_conversation(self, conversation: Conversation) -> None:
77
+ """
78
+ Save a conversation (update in memory storage).
79
+
80
+ Args:
81
+ conversation: The conversation to save
82
+ """
83
+ conversation.updated_at = datetime.now(UTC)
84
+ self.conversations[conversation.id] = conversation
85
+ # logger.debug(f"Saved conversation {conversation.id}")
86
+
87
+ def list_conversations(self, user_id: str | None = None) -> list[Conversation]:
88
+ """
89
+ List conversations, optionally filtered by user.
90
+
91
+ Args:
92
+ user_id: Optional user ID to filter by
93
+
94
+ Returns:
95
+ list[Conversation]: List of conversations
96
+ """
97
+ conversations = list(self.conversations.values())
98
+
99
+ if user_id:
100
+ conversations = [
101
+ conv
102
+ for conv in conversations
103
+ if conv.metadata.get("user_id") == user_id
104
+ ]
105
+
106
+ # Sort by most recent activity
107
+ conversations.sort(key=lambda c: c.updated_at, reverse=True)
108
+ return conversations
109
+
110
+ def delete_conversation(self, conversation_id: str) -> bool:
111
+ """
112
+ Delete a conversation.
113
+
114
+ Args:
115
+ conversation_id: The conversation identifier
116
+
117
+ Returns:
118
+ bool: True if conversation was deleted, False if not found
119
+ """
120
+ if conversation_id in self.conversations:
121
+ del self.conversations[conversation_id]
122
+ # logger.debug(f"Deleted conversation {conversation_id}")
123
+ return True
124
+ return False
125
+
126
+ def get_conversation_count(self, user_id: str | None = None) -> int:
127
+ """
128
+ Get count of conversations.
129
+
130
+ Args:
131
+ user_id: Optional user ID to filter by
132
+
133
+ Returns:
134
+ int: Number of conversations
135
+ """
136
+ if user_id:
137
+ return len(
138
+ [
139
+ conv
140
+ for conv in self.conversations.values()
141
+ if conv.metadata.get("user_id") == user_id
142
+ ]
143
+ )
144
+ return len(self.conversations)
145
+
146
+ def get_recent_conversations(
147
+ self, limit: int = 10, user_id: str | None = None
148
+ ) -> list[Conversation]:
149
+ """
150
+ Get recent conversations.
151
+
152
+ Args:
153
+ limit: Maximum number of conversations to return
154
+ user_id: Optional user ID to filter by
155
+
156
+ Returns:
157
+ list[Conversation]: Recent conversations
158
+ """
159
+ conversations = self.list_conversations(user_id)
160
+ return conversations[:limit]
161
+
162
+ def cleanup_old_conversations(self, max_age_hours: int = 24) -> int:
163
+ """
164
+ Clean up old conversations.
165
+
166
+ Args:
167
+ max_age_hours: Maximum age in hours before cleanup
168
+
169
+ Returns:
170
+ int: Number of conversations cleaned up
171
+ """
172
+ cutoff_time = datetime.now(UTC).timestamp() - (max_age_hours * 3600)
173
+ to_delete = []
174
+
175
+ for conv_id, conversation in self.conversations.items():
176
+ if conversation.updated_at.timestamp() < cutoff_time:
177
+ to_delete.append(conv_id)
178
+
179
+ for conv_id in to_delete:
180
+ del self.conversations[conv_id]
181
+
182
+ if to_delete:
183
+ logger.info(f"Cleaned up {len(to_delete)} old conversations")
184
+
185
+ return len(to_delete)
186
+
187
+ def get_stats(self) -> dict[str, Any]:
188
+ """
189
+ Get conversation manager statistics.
190
+
191
+ Returns:
192
+ dict: Statistics about conversations
193
+ """
194
+ total_conversations = len(self.conversations)
195
+ total_messages = sum(
196
+ conv.get_message_count() for conv in self.conversations.values()
197
+ )
198
+
199
+ # Get user breakdown
200
+ users = set()
201
+ for conv in self.conversations.values():
202
+ user_id = conv.metadata.get("user_id")
203
+ if user_id:
204
+ users.add(user_id)
205
+
206
+ return {
207
+ "total_conversations": total_conversations,
208
+ "total_messages": total_messages,
209
+ "unique_users": len(users),
210
+ "average_messages_per_conversation": (
211
+ total_messages / total_conversations if total_conversations > 0 else 0
212
+ ),
213
+ }