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,220 @@
1
+ """
2
+ Component name parsing utilities for Aegis Stack.
3
+
4
+ This module provides centralized utilities for parsing component names with engine
5
+ information (e.g., 'database[sqlite]') to eliminate code duplication and improve
6
+ robustness throughout the codebase.
7
+ """
8
+
9
+ import re
10
+
11
+
12
+ def parse_component_name(component: str) -> tuple[str, str | None]:
13
+ """
14
+ Parse a component name with optional engine information.
15
+
16
+ Args:
17
+ component: Component name like 'database[sqlite]' or 'scheduler'
18
+
19
+ Returns:
20
+ Tuple of (base_name, engine) where engine is None if not specified
21
+
22
+ Examples:
23
+ parse_component_name('database[sqlite]') -> ('database', 'sqlite')
24
+ parse_component_name('scheduler') -> ('scheduler', None)
25
+ parse_component_name('database[]') -> ('database', None)
26
+
27
+ Raises:
28
+ ValueError: If component name format is invalid
29
+ """
30
+ if not component or not isinstance(component, str):
31
+ raise ValueError(
32
+ f"Component name must be a non-empty string, got: {component!r}"
33
+ )
34
+
35
+ component = component.strip()
36
+ if not component:
37
+ raise ValueError("Component name cannot be empty or whitespace")
38
+
39
+ # Use regex for robust parsing
40
+ pattern = r"^([a-zA-Z][a-zA-Z0-9_-]*?)(?:\[([a-zA-Z0-9_-]*)\])?$"
41
+ match = re.match(pattern, component)
42
+
43
+ if not match:
44
+ raise ValueError(
45
+ f"Invalid component name format: '{component}'. "
46
+ "Expected format: 'name' or 'name[engine]'"
47
+ )
48
+
49
+ base_name, engine = match.groups()
50
+
51
+ # Handle empty engine brackets
52
+ if engine == "":
53
+ engine = None
54
+
55
+ return base_name, engine
56
+
57
+
58
+ def extract_base_component_name(component: str) -> str:
59
+ """
60
+ Extract the base component name without engine information.
61
+
62
+ Args:
63
+ component: Component name like 'database[sqlite]' or 'scheduler'
64
+
65
+ Returns:
66
+ Base component name
67
+
68
+ Examples:
69
+ extract_base_component_name('database[sqlite]') -> 'database'
70
+ extract_base_component_name('scheduler') -> 'scheduler'
71
+ """
72
+ base_name, _ = parse_component_name(component)
73
+ return base_name
74
+
75
+
76
+ def extract_engine_info(component: str) -> str | None:
77
+ """
78
+ Extract engine information from a component name.
79
+
80
+ Args:
81
+ component: Component name like 'database[sqlite]' or 'scheduler'
82
+
83
+ Returns:
84
+ Engine name or None if not specified
85
+
86
+ Examples:
87
+ extract_engine_info('database[sqlite]') -> 'sqlite'
88
+ extract_engine_info('scheduler') -> None
89
+ extract_engine_info('database[]') -> None
90
+ """
91
+ _, engine = parse_component_name(component)
92
+ return engine
93
+
94
+
95
+ def format_component_with_engine(base: str, engine: str | None) -> str:
96
+ """
97
+ Format a component name with engine information.
98
+
99
+ Args:
100
+ base: Base component name
101
+ engine: Engine name or None
102
+
103
+ Returns:
104
+ Formatted component name
105
+
106
+ Examples:
107
+ format_component_with_engine('database', 'sqlite') -> 'database[sqlite]'
108
+ format_component_with_engine('scheduler', None) -> 'scheduler'
109
+ """
110
+ if not base or not isinstance(base, str):
111
+ raise ValueError(
112
+ f"Base component name must be a non-empty string, got: {base!r}"
113
+ )
114
+
115
+ base = base.strip()
116
+ if not base:
117
+ raise ValueError("Base component name cannot be empty or whitespace")
118
+
119
+ if engine is not None:
120
+ if not isinstance(engine, str) or not engine.strip():
121
+ raise ValueError(f"Engine must be a non-empty string, got: {engine!r}")
122
+ return f"{base}[{engine.strip()}]"
123
+
124
+ return base
125
+
126
+
127
+ def clean_component_names(components: list[str]) -> list[str]:
128
+ """
129
+ Extract base component names from a list, removing engine information.
130
+
131
+ Args:
132
+ components: List of component names (some may have engine info)
133
+
134
+ Returns:
135
+ List of base component names
136
+
137
+ Examples:
138
+ clean_component_names(['redis', 'database[sqlite]']) -> ['redis', 'database']
139
+ """
140
+ return [extract_base_component_name(comp) for comp in components]
141
+
142
+
143
+ def restore_engine_info(
144
+ resolved_components: list[str], original_components: list[str]
145
+ ) -> list[str]:
146
+ """
147
+ Restore engine information from original components to resolved components.
148
+
149
+ This function matches resolved base component names with their original
150
+ engine-specific versions and restores the engine information.
151
+
152
+ Args:
153
+ resolved_components: List of resolved base component names
154
+ original_components: List of original components with engine info
155
+
156
+ Returns:
157
+ List with engine information restored where applicable
158
+
159
+ Examples:
160
+ resolved = ['redis', 'database', 'scheduler']
161
+ original = ['database[sqlite]', 'scheduler']
162
+ restore_engine_info(resolved, original)
163
+ -> ['redis', 'database[sqlite]', 'scheduler']
164
+ """
165
+ # Build mapping of base names to original components with engine info
166
+ engine_mapping: dict[str, str] = {}
167
+ for orig in original_components:
168
+ base_name = extract_base_component_name(orig)
169
+ if base_name != orig: # Has engine info
170
+ engine_mapping[base_name] = orig
171
+
172
+ # Restore engine info where available
173
+ result = []
174
+ for comp in resolved_components:
175
+ if comp in engine_mapping:
176
+ result.append(engine_mapping[comp])
177
+ else:
178
+ result.append(comp)
179
+
180
+ return result
181
+
182
+
183
+ def find_components_with_engine(components: list[str], base_name: str) -> list[str]:
184
+ """
185
+ Find all components that match a base name (with or without engine info).
186
+
187
+ Args:
188
+ components: List of component names to search
189
+ base_name: Base component name to match
190
+
191
+ Returns:
192
+ List of matching components
193
+
194
+ Examples:
195
+ components = ['redis', 'database[sqlite]', 'database[postgres]', 'scheduler']
196
+ find_components_with_engine(components, 'database')
197
+ -> ['database[sqlite]', 'database[postgres]']
198
+ """
199
+ matches = []
200
+ for comp in components:
201
+ if extract_base_component_name(comp) == base_name:
202
+ matches.append(comp)
203
+ return matches
204
+
205
+
206
+ def has_engine_info(component: str) -> bool:
207
+ """
208
+ Check if a component name includes engine information.
209
+
210
+ Args:
211
+ component: Component name to check
212
+
213
+ Returns:
214
+ True if component has engine info, False otherwise
215
+
216
+ Examples:
217
+ has_engine_info('database[sqlite]') -> True
218
+ has_engine_info('scheduler') -> False
219
+ """
220
+ return extract_engine_info(component) is not None
@@ -0,0 +1,127 @@
1
+ """
2
+ Component registry and specifications for Aegis Stack.
3
+
4
+ This module defines all available components, their dependencies, and metadata
5
+ used for project generation and validation.
6
+ """
7
+
8
+ from dataclasses import dataclass
9
+ from enum import Enum
10
+
11
+
12
+ class ComponentType(Enum):
13
+ """Component type classifications."""
14
+
15
+ CORE = "core" # Always included (backend, frontend)
16
+ INFRASTRUCTURE = "infra" # Redis, workers - foundation for services to use
17
+
18
+
19
+ class SchedulerBackend(str, Enum):
20
+ """Scheduler backend options for task persistence."""
21
+
22
+ MEMORY = "memory" # In-memory (no persistence, default)
23
+ SQLITE = "sqlite" # SQLite database (requires database component)
24
+ POSTGRES = "postgres" # PostgreSQL (future support)
25
+
26
+
27
+ # Core components that are always included in every project
28
+ CORE_COMPONENTS = ["backend", "frontend"]
29
+
30
+
31
+ @dataclass
32
+ class ComponentSpec:
33
+ """Specification for a single component."""
34
+
35
+ name: str
36
+ type: ComponentType
37
+ description: str
38
+ requires: list[str] | None = None # Hard dependencies
39
+ recommends: list[str] | None = None # Soft dependencies
40
+ conflicts: list[str] | None = None # Mutual exclusions
41
+ docker_services: list[str] | None = None
42
+ pyproject_deps: list[str] | None = None
43
+ template_files: list[str] | None = None
44
+
45
+ def __post_init__(self) -> None:
46
+ """Ensure all list fields are initialized."""
47
+ if self.requires is None:
48
+ self.requires = []
49
+ if self.recommends is None:
50
+ self.recommends = []
51
+ if self.conflicts is None:
52
+ self.conflicts = []
53
+ if self.docker_services is None:
54
+ self.docker_services = []
55
+ if self.pyproject_deps is None:
56
+ self.pyproject_deps = []
57
+ if self.template_files is None:
58
+ self.template_files = []
59
+
60
+
61
+ # Component registry - single source of truth
62
+ COMPONENTS: dict[str, ComponentSpec] = {
63
+ "backend": ComponentSpec(
64
+ name="backend",
65
+ type=ComponentType.CORE,
66
+ description="FastAPI backend server",
67
+ pyproject_deps=["fastapi==0.116.1", "uvicorn==0.35.0"],
68
+ template_files=["app/components/backend/"],
69
+ ),
70
+ "frontend": ComponentSpec(
71
+ name="frontend",
72
+ type=ComponentType.CORE,
73
+ description="Flet frontend interface",
74
+ pyproject_deps=["flet==0.28.3"],
75
+ template_files=["app/components/frontend/"],
76
+ ),
77
+ "redis": ComponentSpec(
78
+ name="redis",
79
+ type=ComponentType.INFRASTRUCTURE,
80
+ description="Redis cache and message broker",
81
+ docker_services=["redis"],
82
+ pyproject_deps=["redis==5.0.8"],
83
+ ),
84
+ "worker": ComponentSpec(
85
+ name="worker",
86
+ type=ComponentType.INFRASTRUCTURE,
87
+ description="Background task processing infrastructure with arq",
88
+ requires=["redis"], # Hard dependency
89
+ pyproject_deps=["arq==0.25.0"],
90
+ docker_services=["worker-system", "worker-load-test"],
91
+ template_files=["app/components/worker/"],
92
+ ),
93
+ "scheduler": ComponentSpec(
94
+ name="scheduler",
95
+ type=ComponentType.INFRASTRUCTURE,
96
+ description="Scheduled task execution infrastructure",
97
+ pyproject_deps=["apscheduler==3.10.4"],
98
+ docker_services=["scheduler"],
99
+ template_files=["app/components/scheduler.py", "app/entrypoints/scheduler.py"],
100
+ ),
101
+ "database": ComponentSpec(
102
+ name="database",
103
+ type=ComponentType.INFRASTRUCTURE,
104
+ description="SQLite database with SQLModel ORM",
105
+ pyproject_deps=["sqlmodel>=0.0.14", "sqlalchemy>=2.0.0", "aiosqlite>=0.19.0"],
106
+ template_files=["app/core/db.py"],
107
+ ),
108
+ }
109
+
110
+
111
+ def get_component(name: str) -> ComponentSpec:
112
+ """Get component specification by name."""
113
+ if name not in COMPONENTS:
114
+ raise ValueError(f"Unknown component: {name}")
115
+ return COMPONENTS[name]
116
+
117
+
118
+ def get_components_by_type(component_type: ComponentType) -> dict[str, ComponentSpec]:
119
+ """Get all components of a specific type."""
120
+ return {
121
+ name: spec for name, spec in COMPONENTS.items() if spec.type == component_type
122
+ }
123
+
124
+
125
+ def list_available_components() -> list[str]:
126
+ """Get list of all available component names."""
127
+ return list(COMPONENTS.keys())
@@ -0,0 +1,315 @@
1
+ """
2
+ Copier template engine integration.
3
+
4
+ This module provides Copier template generation functionality alongside
5
+ the existing Cookiecutter engine. It's designed to maintain feature parity
6
+ during the migration period.
7
+ """
8
+
9
+ from pathlib import Path
10
+ from typing import Any
11
+
12
+ import yaml
13
+ from copier import run_copy, run_update
14
+
15
+ from .post_gen_tasks import cleanup_components, run_post_generation_tasks
16
+ from .template_generator import TemplateGenerator
17
+
18
+
19
+ def generate_with_copier(template_gen: TemplateGenerator, output_dir: Path) -> Path:
20
+ """
21
+ Generate project using Copier template engine.
22
+
23
+ Args:
24
+ template_gen: Template generator with project configuration
25
+ output_dir: Directory to create the project in
26
+
27
+ Returns:
28
+ Path to the generated project
29
+
30
+ Note:
31
+ This function uses the Copier template which is currently incomplete
32
+ (missing conditional _exclude patterns). Projects will include all
33
+ components regardless of selection until template is fixed.
34
+ """
35
+ import subprocess
36
+
37
+ # Get cookiecutter context from template generator
38
+ cookiecutter_context = template_gen.get_template_context()
39
+
40
+ # Convert cookiecutter context to Copier data format
41
+ # Copier uses boolean values instead of "yes"/"no" strings
42
+ copier_data = {
43
+ "project_name": cookiecutter_context["project_name"],
44
+ "project_slug": cookiecutter_context["project_slug"],
45
+ "project_description": cookiecutter_context.get(
46
+ "project_description",
47
+ "A production-ready async Python application built with Aegis Stack",
48
+ ),
49
+ "author_name": cookiecutter_context.get("author_name", "Your Name"),
50
+ "author_email": cookiecutter_context.get(
51
+ "author_email", "your.email@example.com"
52
+ ),
53
+ "github_username": cookiecutter_context.get("github_username", "your-username"),
54
+ "version": cookiecutter_context.get("version", "0.1.0"),
55
+ "python_version": cookiecutter_context.get("python_version", "3.11"),
56
+ # Convert yes/no strings to booleans
57
+ "include_scheduler": cookiecutter_context["include_scheduler"] == "yes",
58
+ "scheduler_backend": cookiecutter_context["scheduler_backend"],
59
+ "scheduler_with_persistence": cookiecutter_context["scheduler_with_persistence"]
60
+ == "yes",
61
+ "include_worker": cookiecutter_context["include_worker"] == "yes",
62
+ "include_redis": cookiecutter_context["include_redis"] == "yes",
63
+ "include_database": cookiecutter_context["include_database"] == "yes",
64
+ "include_cache": False, # Default to no
65
+ "include_auth": cookiecutter_context.get("include_auth", "no") == "yes",
66
+ "include_ai": cookiecutter_context.get("include_ai", "no") == "yes",
67
+ "ai_providers": cookiecutter_context.get("ai_providers", "openai"),
68
+ }
69
+
70
+ # Get copier template path - point directly at template directory
71
+ # This prevents copying aegis-stack repo files into generated projects
72
+ # The repo root path is set later in .copier-answers.yml for git-aware updates
73
+ template_path = Path(__file__).parent.parent / "templates" / "copier-aegis-project"
74
+
75
+ # Generate project - Copier creates the project_slug directory automatically
76
+ # NOTE: _tasks removed from copier.yml - we run them ourselves below
77
+ run_copy(
78
+ str(
79
+ template_path
80
+ ), # Use template directory (not repo root) to avoid copying extra files
81
+ output_dir,
82
+ data=copier_data,
83
+ defaults=True, # Use template defaults, overridden by our explicit data
84
+ unsafe=False, # No tasks in copier.yml anymore - we run them ourselves
85
+ vcs_ref=None, # Don't use git for template versioning - prevents git submodule errors in CI
86
+ )
87
+
88
+ # Copier creates the project in output_dir/project_slug
89
+ project_path = output_dir / cookiecutter_context["project_slug"]
90
+
91
+ # Clean up unwanted component files based on selection
92
+ # This must happen BEFORE post-generation tasks (which run linting on the remaining files)
93
+ cleanup_components(project_path, copier_data)
94
+
95
+ # Run post-generation tasks with explicit working directory control
96
+ # This ensures consistent behavior with Cookiecutter
97
+ include_auth = copier_data.get("include_auth", False)
98
+ run_post_generation_tasks(project_path, include_auth=include_auth)
99
+
100
+ # Initialize git repository for Copier updates
101
+ # Copier requires a git-tracked project to perform updates
102
+
103
+ try:
104
+ # Configure git user in case CI environment doesn't have it set
105
+ # This is needed for commits to work in CI
106
+ subprocess.run(
107
+ ["git", "config", "user.name", "Aegis Stack"],
108
+ cwd=project_path,
109
+ capture_output=True,
110
+ )
111
+ subprocess.run(
112
+ ["git", "config", "user.email", "noreply@aegis-stack.dev"],
113
+ cwd=project_path,
114
+ capture_output=True,
115
+ )
116
+
117
+ subprocess.run(
118
+ ["git", "init"],
119
+ cwd=project_path,
120
+ check=True,
121
+ capture_output=True,
122
+ )
123
+ subprocess.run(
124
+ ["git", "add", "."],
125
+ cwd=project_path,
126
+ check=True,
127
+ capture_output=True,
128
+ )
129
+ subprocess.run(
130
+ ["git", "commit", "-m", "Initial commit from Aegis Stack"],
131
+ cwd=project_path,
132
+ check=True,
133
+ capture_output=True,
134
+ )
135
+ print("✅ Git repository initialized")
136
+ except subprocess.CalledProcessError as e:
137
+ print(f"⚠️ Failed to initialize git repository: {e}")
138
+ print("💡 Run 'git init && git add . && git commit' manually")
139
+
140
+ # CRITICAL: Update .copier-answers.yml for future updates to work
141
+ # We need to:
142
+ # 1. Store git commit hash (_commit) - tells Copier which template version was used
143
+ # 2. Update template path (_src_path) - point to repo root, not subdirectory
144
+ # (repo root has .git so Copier can detect version changes)
145
+ try:
146
+ # Get current commit hash from aegis-stack repo
147
+ template_root = Path(__file__).parent.parent.parent
148
+ result = subprocess.run(
149
+ ["git", "rev-parse", "HEAD"],
150
+ cwd=template_root,
151
+ capture_output=True,
152
+ text=True,
153
+ check=True,
154
+ )
155
+ commit_hash = result.stdout.strip()
156
+
157
+ # Update .copier-answers.yml with commit hash AND repo root path
158
+ answers_file = project_path / ".copier-answers.yml"
159
+ if answers_file.exists():
160
+ with open(answers_file) as f:
161
+ answers = yaml.safe_load(f)
162
+
163
+ # Update _commit field (was None, now has actual hash)
164
+ answers["_commit"] = commit_hash
165
+
166
+ # Update _src_path to point to repo root (where .git exists)
167
+ # The copier.yml at repo root has _subdirectory setting to find actual template
168
+ answers["_src_path"] = str(template_root)
169
+
170
+ with open(answers_file, "w") as f:
171
+ yaml.safe_dump(answers, f, default_flow_style=False, sort_keys=False)
172
+
173
+ # Commit the updated .copier-answers.yml
174
+ try:
175
+ subprocess.run(
176
+ ["git", "add", ".copier-answers.yml"],
177
+ cwd=project_path,
178
+ check=True,
179
+ capture_output=True,
180
+ )
181
+ subprocess.run(
182
+ [
183
+ "git",
184
+ "commit",
185
+ "-m",
186
+ "Update .copier-answers.yml with template version",
187
+ ],
188
+ cwd=project_path,
189
+ check=True,
190
+ capture_output=True,
191
+ )
192
+ except subprocess.CalledProcessError:
193
+ # If commit fails (e.g., no changes), that's OK
194
+ pass
195
+
196
+ except Exception:
197
+ # If we can't get commit hash, that's OK - updates won't work but
198
+ # project generation succeeded. This can happen in non-git environments.
199
+ pass
200
+
201
+ return project_path
202
+
203
+
204
+ def is_copier_project(project_path: Path) -> bool:
205
+ """
206
+ Check if a project was generated with Copier.
207
+
208
+ Args:
209
+ project_path: Path to the project directory
210
+
211
+ Returns:
212
+ True if project has .copier-answers.yml file
213
+ """
214
+ answers_file = project_path / ".copier-answers.yml"
215
+ return answers_file.exists()
216
+
217
+
218
+ def load_copier_answers(project_path: Path) -> dict[str, Any]:
219
+ """
220
+ Load existing Copier answers from a project.
221
+
222
+ Args:
223
+ project_path: Path to the project directory
224
+
225
+ Returns:
226
+ Dictionary of Copier answers
227
+
228
+ Raises:
229
+ FileNotFoundError: If .copier-answers.yml doesn't exist
230
+ yaml.YAMLError: If answers file is corrupted
231
+ """
232
+ answers_file = project_path / ".copier-answers.yml"
233
+
234
+ if not answers_file.exists():
235
+ raise FileNotFoundError(
236
+ f"No .copier-answers.yml found in {project_path}. "
237
+ "This doesn't appear to be a Copier-generated project."
238
+ )
239
+
240
+ try:
241
+ with open(answers_file) as f:
242
+ answers = yaml.safe_load(f)
243
+ if answers is None:
244
+ return {}
245
+ return answers
246
+ except yaml.YAMLError as e:
247
+ raise yaml.YAMLError(f"Failed to parse .copier-answers.yml: {e}") from e
248
+
249
+
250
+ def update_with_copier(
251
+ project_path: Path,
252
+ additional_data: dict[str, Any] | None = None,
253
+ conflict_mode: str = "rej",
254
+ ) -> None:
255
+ """
256
+ Update an existing Copier-generated project with new data.
257
+
258
+ This function uses Copier's update mechanism to add new components
259
+ or update existing project configuration.
260
+
261
+ Args:
262
+ project_path: Path to the existing project directory
263
+ additional_data: New data to merge (e.g., {"include_scheduler": True})
264
+ conflict_mode: How to handle conflicts - "rej" (separate files) or "inline" (markers)
265
+
266
+ Raises:
267
+ FileNotFoundError: If project doesn't have .copier-answers.yml
268
+ Exception: If Copier update fails
269
+
270
+ Example:
271
+ # Add scheduler component to existing project
272
+ update_with_copier(
273
+ Path("my-project"),
274
+ {"include_scheduler": True, "scheduler_backend": "memory"}
275
+ )
276
+ """
277
+ # Validate it's a Copier project
278
+ if not is_copier_project(project_path):
279
+ raise FileNotFoundError(
280
+ f"Project at {project_path} was not generated with Copier.\n"
281
+ f"The 'aegis add' command only works with Copier-generated projects.\n"
282
+ f"To add components, regenerate the project with the new components included."
283
+ )
284
+
285
+ # Load existing answers to validate state
286
+ try:
287
+ load_copier_answers(project_path)
288
+ except yaml.YAMLError as e:
289
+ raise Exception(
290
+ f"Failed to read project configuration: {e}\n"
291
+ f"The .copier-answers.yml file may be corrupted."
292
+ ) from e
293
+
294
+ # Prepare update data
295
+ update_data = additional_data or {}
296
+
297
+ # Run Copier update
298
+ # NOTE: We do NOT pass src_path - Copier will read it from .copier-answers.yml
299
+ # This is the key to making updates work!
300
+ try:
301
+ run_update(
302
+ dst_path=str(project_path),
303
+ data=update_data,
304
+ defaults=True, # Use existing answers as defaults
305
+ overwrite=True, # Allow overwriting files
306
+ conflict=conflict_mode, # How to handle conflicts
307
+ unsafe=True, # Allow running tasks (uv sync, make fix)
308
+ vcs_ref="HEAD", # Use latest template (no versioning needed yet)
309
+ )
310
+ except Exception as e:
311
+ raise Exception(
312
+ f"Failed to update project: {e}\n"
313
+ f"This may be due to conflicts with manually modified files.\n"
314
+ f"Check for .rej files in the project directory for details."
315
+ ) from e