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,328 @@
1
+ {%- if scheduler_backend != "memory" -%}
2
+ """
3
+ Scheduled task CLI commands.
4
+
5
+ Provides command-line interface for managing scheduled jobs,
6
+ viewing job statistics, and manually triggering tasks.
7
+ """
8
+
9
+ import asyncio
10
+ import json
11
+ from datetime import datetime
12
+ from typing import Any
13
+
14
+ from rich import print as rprint
15
+ from rich.console import Console
16
+ from rich.panel import Panel
17
+ from rich.table import Table
18
+ import typer
19
+
20
+ from app.core.log import logger
21
+ from app.services.scheduler import ScheduledTaskManager
22
+ from app.services.scheduler.models import ScheduledTask
23
+
24
+ app = typer.Typer(
25
+ name="tasks",
26
+ help="Scheduled task management commands",
27
+ no_args_is_help=True,
28
+ )
29
+
30
+ console = Console()
31
+
32
+
33
+ @app.command("list")
34
+ def list_jobs() -> None:
35
+ """
36
+ List all scheduled jobs with their current status.
37
+
38
+ Shows job details including next run time, status, and configuration.
39
+ """
40
+
41
+ rprint("📋 [bold blue]Listing Scheduled Jobs[/bold blue]")
42
+
43
+ try:
44
+ tasks = asyncio.run(_get_scheduled_tasks())
45
+
46
+ if not tasks:
47
+ rprint("ℹ️ [yellow]No scheduled jobs found[/yellow]")
48
+ return
49
+
50
+ table = Table(
51
+ title="🕒 Scheduled Jobs",
52
+ show_header=True,
53
+ header_style="bold magenta",
54
+ )
55
+ table.add_column("Job ID", style="cyan", no_wrap=True)
56
+ table.add_column("Name", style="green")
57
+ table.add_column("Status", style="bold")
58
+ table.add_column("Next Run", style="yellow")
59
+ table.add_column("Trigger", style="dim")
60
+
61
+ for task in tasks:
62
+ status_color = "green" if task.is_active else "red"
63
+ next_run = (
64
+ task.next_run_time.strftime("%Y-%m-%d %H:%M:%S")
65
+ if task.next_run_time
66
+ else "Not scheduled"
67
+ )
68
+
69
+ table.add_row(
70
+ task.job_id,
71
+ task.name or task.job_id,
72
+ (
73
+ f"[{status_color}]{'Active' if task.is_active else 'Paused'}"
74
+ f"[/{status_color}]"
75
+ ),
76
+ next_run,
77
+ task.trigger_type or "Unknown"
78
+ )
79
+
80
+ console.print(table)
81
+ rprint(f"\n📊 [cyan]Total jobs:[/cyan] {len(tasks)}")
82
+
83
+ except Exception as e:
84
+ rprint(f"❌ [red]Failed to list jobs:[/red] {e}")
85
+ raise typer.Exit(1)
86
+
87
+
88
+ @app.command("stats")
89
+ def job_statistics(
90
+ job_id: str | None = typer.Argument(
91
+ None, help="Specific job ID to show stats for"
92
+ ),
93
+ json_output: bool = typer.Option(
94
+ False, "--json", help="Output stats in JSON format"
95
+ ),
96
+ ) -> None:
97
+ """
98
+ Show job execution statistics and performance metrics.
99
+
100
+ Without job_id: Shows overall scheduler statistics
101
+ With job_id: Shows detailed statistics for specific job
102
+ """
103
+
104
+ try:
105
+ if job_id:
106
+ rprint(f"📊 [bold blue]Job Statistics for: {job_id}[/bold blue]")
107
+ stats = asyncio.run(_get_job_statistics(job_id))
108
+ else:
109
+ rprint("📊 [bold blue]Overall Scheduler Statistics[/bold blue]")
110
+ stats = asyncio.run(_get_overall_statistics())
111
+
112
+ if json_output:
113
+ print(json.dumps(stats, indent=2, default=str))
114
+ return
115
+
116
+ _display_statistics(stats, job_id)
117
+
118
+ except Exception as e:
119
+ rprint(f"❌ [red]Failed to get statistics:[/red] {e}")
120
+ raise typer.Exit(1)
121
+
122
+
123
+ @app.command("history")
124
+ def job_history(
125
+ limit: int = typer.Option(
126
+ 20, "--limit", "-l", help="Number of recent executions to show", min=1, max=100
127
+ ),
128
+ job_id: str | None = typer.Option(
129
+ None, "--job-id", "-j", help="Filter by specific job ID"
130
+ ),
131
+ json_output: bool = typer.Option(
132
+ False, "--json", help="Output history in JSON format"
133
+ ),
134
+ ) -> None:
135
+ """
136
+ Show recent job execution history.
137
+
138
+ Displays execution times, durations, and results for recent job runs.
139
+ """
140
+
141
+ try:
142
+ if job_id:
143
+ rprint(
144
+ f"📚 [bold blue]Execution History for: {job_id}[/bold blue] "
145
+ f"(last {limit})"
146
+ )
147
+ else:
148
+ rprint(
149
+ f"📚 [bold blue]Recent Job Execution History[/bold blue] "
150
+ f"(last {limit})"
151
+ )
152
+
153
+ history = asyncio.run(_get_job_history(limit, job_id))
154
+
155
+ if json_output:
156
+ print(json.dumps(history, indent=2, default=str))
157
+ return
158
+
159
+ if not history:
160
+ rprint("ℹ️ [yellow]No execution history found[/yellow]")
161
+ return
162
+
163
+ table = Table(
164
+ title="🕰️ Job Execution History",
165
+ show_header=True,
166
+ header_style="bold magenta",
167
+ )
168
+ table.add_column("Timestamp", style="cyan")
169
+ table.add_column("Job ID", style="green")
170
+ table.add_column("Duration", style="yellow")
171
+ table.add_column("Status", style="bold")
172
+ table.add_column("Details", style="dim")
173
+
174
+ for execution in history:
175
+ status_color = "green" if execution.get("status") == "success" else "red"
176
+ duration = f"{execution.get('duration_ms', 0):.1f}ms"
177
+
178
+ status_value = execution.get('status', 'Unknown')
179
+ status_display = f"[{status_color}]{status_value}[/{status_color}]"
180
+ details = execution.get("details", "")[:50]
181
+ if len(execution.get("details", "")) > 50:
182
+ details += "..."
183
+
184
+ table.add_row(
185
+ execution.get("timestamp", "Unknown"),
186
+ execution.get("job_id", "Unknown"),
187
+ duration,
188
+ status_display,
189
+ details,
190
+ )
191
+
192
+ console.print(table)
193
+
194
+ except Exception as e:
195
+ rprint(f"❌ [red]Failed to get job history:[/red] {e}")
196
+ raise typer.Exit(1)
197
+
198
+
199
+ @app.command("trigger")
200
+ def trigger_job(
201
+ job_id: str = typer.Argument(..., help="Job ID to trigger manually"),
202
+ wait: bool = typer.Option(True, "--wait/--no-wait", help="Wait for job completion"),
203
+ timeout: int = typer.Option(
204
+ 30, "--timeout", help="Timeout for waiting (seconds)", min=1, max=300
205
+ ),
206
+ ) -> None:
207
+ """
208
+ Manually trigger a scheduled job.
209
+
210
+ Executes the specified job immediately, bypassing the normal schedule.
211
+ Useful for testing job functionality or running jobs on-demand.
212
+ """
213
+
214
+ rprint(f"🚀 [bold blue]Triggering job:[/bold blue] {job_id}")
215
+
216
+ try:
217
+ # First verify the job exists
218
+ tasks = asyncio.run(_get_scheduled_tasks())
219
+ job_exists = any(task.job_id == job_id for task in tasks)
220
+
221
+ if not job_exists:
222
+ rprint(f"❌ [red]Job not found:[/red] {job_id}")
223
+ rprint("💡 [yellow]Use 'tasks list' to see available jobs[/yellow]")
224
+ raise typer.Exit(1)
225
+
226
+ # Trigger the job
227
+ result = asyncio.run(_trigger_job_execution(job_id, wait, timeout))
228
+
229
+ if result.get("status") == "success":
230
+ rprint(f"✅ [green]Job triggered successfully![/green]")
231
+ if wait and result.get("execution_details"):
232
+ details = result["execution_details"]
233
+ duration_ms = details.get('duration_ms', 0)
234
+ rprint(f"⏱️ [cyan]Duration:[/cyan] {duration_ms:.1f}ms")
235
+ rprint(f"📋 [cyan]Result:[/cyan] {details.get('result', 'No result')}")
236
+ else:
237
+ error_msg = result.get('error', 'Unknown error')
238
+ rprint(f"❌ [red]Job execution failed:[/red] {error_msg}")
239
+
240
+ except Exception as e:
241
+ rprint(f"❌ [red]Failed to trigger job:[/red] {e}")
242
+ raise typer.Exit(1)
243
+
244
+
245
+ async def _get_scheduled_tasks() -> list[ScheduledTask]:
246
+ """Get list of scheduled tasks from the scheduler."""
247
+ manager = ScheduledTaskManager()
248
+ return await manager.list_tasks()
249
+
250
+
251
+ async def _get_job_statistics(job_id: str) -> dict[str, Any]:
252
+ """Get statistics for a specific job."""
253
+ manager = ScheduledTaskManager()
254
+ return await manager.get_job_statistics(job_id)
255
+
256
+
257
+ async def _get_overall_statistics() -> dict[str, Any]:
258
+ """Get overall scheduler statistics."""
259
+ manager = ScheduledTaskManager()
260
+ return await manager.get_overall_statistics()
261
+
262
+
263
+ async def _get_job_history(limit: int, job_id: str | None) -> list[dict[str, Any]]:
264
+ """Get recent job execution history."""
265
+ manager = ScheduledTaskManager()
266
+ return await manager.get_job_history(limit, job_id)
267
+
268
+
269
+ async def _trigger_job_execution(
270
+ job_id: str, wait: bool, timeout: int
271
+ ) -> dict[str, Any]:
272
+ """Trigger manual job execution."""
273
+ manager = ScheduledTaskManager()
274
+ return await manager.trigger_job(job_id, wait, timeout)
275
+
276
+
277
+ def _display_statistics(stats: dict[str, Any], job_id: str | None) -> None:
278
+ """Display formatted statistics."""
279
+
280
+ if job_id:
281
+ # Job-specific statistics
282
+ info_text = f"""
283
+ [bold cyan]Job Performance Metrics[/bold cyan]
284
+
285
+ 🏃 [yellow]Executions:[/yellow] {stats.get('total_executions', 0)}
286
+ ✅ [yellow]Successful:[/yellow] {stats.get('successful_executions', 0)}
287
+ ❌ [yellow]Failed:[/yellow] {stats.get('failed_executions', 0)}
288
+ 📊 [yellow]Success Rate:[/yellow] {stats.get('success_rate_percent', 0):.1f}%
289
+
290
+ ⏱️ [yellow]Avg Duration:[/yellow] {stats.get('avg_duration_ms', 0):.1f}ms
291
+ 🚀 [yellow]Min Duration:[/yellow] {stats.get('min_duration_ms', 0):.1f}ms
292
+ 🐌 [yellow]Max Duration:[/yellow] {stats.get('max_duration_ms', 0):.1f}ms
293
+
294
+ 🕐 [yellow]Last Execution:[/yellow] {stats.get('last_execution', 'Never')}
295
+ 🕑 [yellow]Next Scheduled:[/yellow] {stats.get('next_run', 'Not scheduled')}
296
+ """.strip()
297
+
298
+ title = f"📊 Statistics for {job_id}"
299
+ console.print(Panel(info_text, title=title, border_style="blue"))
300
+
301
+ else:
302
+ # Overall scheduler statistics
303
+ info_text = f"""
304
+ [bold cyan]Scheduler Overview[/bold cyan]
305
+
306
+ 📋 [yellow]Total Jobs:[/yellow] {stats.get('total_jobs', 0)}
307
+ 🟢 [yellow]Active Jobs:[/yellow] {stats.get('active_jobs', 0)}
308
+ 🔴 [yellow]Paused Jobs:[/yellow] {stats.get('paused_jobs', 0)}
309
+
310
+ 🏃 [yellow]Total Executions:[/yellow] {stats.get('total_executions', 0)}
311
+ ✅ [yellow]Successful:[/yellow] {stats.get('successful_executions', 0)}
312
+ ❌ [yellow]Failed:[/yellow] {stats.get('failed_executions', 0)}
313
+
314
+ 📊 [yellow]Overall Success Rate:[/yellow] {stats.get('success_rate_percent', 0):.1f}%
315
+ ⏱️ [yellow]Avg Execution Time:[/yellow] {stats.get('avg_duration_ms', 0):.1f}ms
316
+
317
+ 🕐 [yellow]Scheduler Uptime:[/yellow] {stats.get('uptime', 'Unknown')}
318
+ 🔄 [yellow]Last Activity:[/yellow] {stats.get('last_activity', 'No recent activity')}
319
+ """.strip()
320
+
321
+ console.print(
322
+ Panel(info_text, title="📊 Scheduler Statistics", border_style="green")
323
+ )
324
+
325
+
326
+ if __name__ == "__main__":
327
+ app()
328
+ {%- endif -%}
@@ -0,0 +1,8 @@
1
+ """
2
+ AI service API routes.
3
+
4
+ REST API endpoints for AI chat functionality.
5
+ """
6
+
7
+ # API routes will be exported here in ticket #159
8
+ __all__ = []
@@ -0,0 +1,329 @@
1
+ """
2
+ AI service API router.
3
+
4
+ FastAPI router for AI chat endpoints implementing core chat functionality,
5
+ conversation management, and service status.
6
+ """
7
+
8
+ import json
9
+ from collections.abc import AsyncIterator
10
+ from typing import Any
11
+
12
+ from app.core.config import settings
13
+ from app.services.ai.service import (
14
+ AIService,
15
+ AIServiceError,
16
+ ConversationError,
17
+ ProviderError,
18
+ )
19
+ from fastapi import APIRouter, HTTPException
20
+ from fastapi.responses import StreamingResponse
21
+ from pydantic import BaseModel
22
+
23
+ router = APIRouter(prefix="/ai", tags=["ai"])
24
+
25
+ # Initialize AI service
26
+ ai_service = AIService(settings)
27
+
28
+
29
+ # Request/Response models
30
+ class ChatRequest(BaseModel):
31
+ """Request model for chat messages."""
32
+
33
+ message: str
34
+ conversation_id: str | None = None
35
+ user_id: str = "api-user"
36
+
37
+
38
+ class ChatResponse(BaseModel):
39
+ """Response model for chat messages."""
40
+
41
+ message_id: str
42
+ content: str
43
+ conversation_id: str
44
+ response_time_ms: float | None = None
45
+
46
+
47
+ class ConversationSummary(BaseModel):
48
+ """Summary model for conversation listing."""
49
+
50
+ id: str
51
+ title: str | None
52
+ message_count: int
53
+ last_activity: str
54
+ provider: str
55
+ model: str
56
+
57
+
58
+ @router.post("/chat", response_model=ChatResponse)
59
+ async def chat(request: ChatRequest) -> ChatResponse:
60
+ """
61
+ Send a chat message and get AI response.
62
+
63
+ Args:
64
+ request: Chat request with message and optional conversation ID
65
+
66
+ Returns:
67
+ ChatResponse: AI response with conversation details
68
+
69
+ Raises:
70
+ HTTPException: If chat processing fails
71
+ """
72
+ try:
73
+ response_message = await ai_service.chat(
74
+ message=request.message,
75
+ conversation_id=request.conversation_id,
76
+ user_id=request.user_id,
77
+ )
78
+
79
+ # Get updated conversation for metadata
80
+ conversation_id = response_message.metadata.get("conversation_id")
81
+ conversation = (
82
+ ai_service.get_conversation(conversation_id) if conversation_id else None
83
+ )
84
+ response_time = None
85
+ if conversation and "last_response_time_ms" in conversation.metadata:
86
+ response_time = conversation.metadata["last_response_time_ms"]
87
+
88
+ return ChatResponse(
89
+ message_id=response_message.id,
90
+ content=response_message.content,
91
+ conversation_id=conversation.id if conversation else "unknown",
92
+ response_time_ms=response_time,
93
+ )
94
+
95
+ except AIServiceError as e:
96
+ raise HTTPException(status_code=503, detail=f"AI service error: {e}")
97
+ except ProviderError as e:
98
+ raise HTTPException(status_code=502, detail=f"AI provider error: {e}")
99
+ except ConversationError as e:
100
+ raise HTTPException(status_code=400, detail=f"Conversation error: {e}")
101
+ except Exception as e:
102
+ raise HTTPException(status_code=500, detail=f"Unexpected error: {e}")
103
+
104
+
105
+ @router.post("/chat/stream")
106
+ async def chat_stream(request: ChatRequest) -> StreamingResponse:
107
+ """
108
+ Stream a chat message with real-time Server-Sent Events.
109
+
110
+ Args:
111
+ request: Chat request with message and optional conversation ID
112
+
113
+ Returns:
114
+ StreamingResponse: SSE stream with real-time AI response
115
+
116
+ Raises:
117
+ HTTPException: If streaming fails
118
+ """
119
+
120
+ async def generate_sse_stream() -> AsyncIterator[str]:
121
+ """Generate Server-Sent Events stream for chat response."""
122
+ try:
123
+ # Send initial connection event
124
+ connect_data = {"status": "connected", "message": "Streaming started"}
125
+ yield f"event: connect\ndata: {json.dumps(connect_data)}\n\n"
126
+
127
+ # Stream the AI response
128
+ async for chunk in ai_service.stream_chat(
129
+ message=request.message,
130
+ conversation_id=request.conversation_id,
131
+ user_id=request.user_id,
132
+ stream_delta=True,
133
+ ):
134
+ # Format chunk as SSE event
135
+ event_data = {
136
+ "content": chunk.content,
137
+ "is_final": chunk.is_final,
138
+ "is_delta": chunk.is_delta,
139
+ "message_id": chunk.message_id,
140
+ "conversation_id": chunk.conversation_id,
141
+ "timestamp": chunk.timestamp.isoformat(),
142
+ }
143
+
144
+ # Add metadata for final chunk
145
+ if chunk.is_final:
146
+ event_data.update(chunk.metadata)
147
+
148
+ # Send chunk as SSE event
149
+ event_type = "final" if chunk.is_final else "chunk"
150
+ yield f"event: {event_type}\ndata: {json.dumps(event_data)}\n\n"
151
+
152
+ # Break after final chunk
153
+ if chunk.is_final:
154
+ break
155
+
156
+ # Send stream complete event
157
+ complete_data = {"status": "completed", "message": "Stream finished"}
158
+ yield f"event: complete\ndata: {json.dumps(complete_data)}\n\n"
159
+
160
+ except AIServiceError as e:
161
+ error_data = {"error": "AI service error", "detail": str(e)}
162
+ yield f"event: error\ndata: {json.dumps(error_data)}\n\n"
163
+ except ProviderError as e:
164
+ error_data = {"error": "AI provider error", "detail": str(e)}
165
+ yield f"event: error\ndata: {json.dumps(error_data)}\n\n"
166
+ except ConversationError as e:
167
+ error_data = {"error": "Conversation error", "detail": str(e)}
168
+ yield f"event: error\ndata: {json.dumps(error_data)}\n\n"
169
+ except Exception as e:
170
+ error_data = {"error": "Unexpected error", "detail": str(e)}
171
+ yield f"event: error\ndata: {json.dumps(error_data)}\n\n"
172
+
173
+ # Create streaming response with proper SSE headers
174
+ return StreamingResponse(
175
+ generate_sse_stream(),
176
+ media_type="text/event-stream",
177
+ headers={
178
+ "Cache-Control": "no-cache",
179
+ "Connection": "keep-alive",
180
+ "Access-Control-Allow-Origin": "*",
181
+ "Access-Control-Allow-Headers": "Content-Type",
182
+ },
183
+ )
184
+
185
+
186
+ @router.get("/conversations", response_model=list[ConversationSummary])
187
+ async def list_conversations(
188
+ user_id: str = "api-user", limit: int = 50
189
+ ) -> list[ConversationSummary]:
190
+ """
191
+ List conversations for a user.
192
+
193
+ Args:
194
+ user_id: User identifier
195
+ limit: Maximum number of conversations to return
196
+
197
+ Returns:
198
+ List of conversation summaries
199
+ """
200
+ try:
201
+ conversations = ai_service.list_conversations(user_id)[:limit]
202
+
203
+ return [
204
+ ConversationSummary(
205
+ id=conv.id,
206
+ title=conv.title,
207
+ message_count=conv.get_message_count(),
208
+ last_activity=conv.updated_at.isoformat(),
209
+ provider=conv.provider.value,
210
+ model=conv.model,
211
+ )
212
+ for conv in conversations
213
+ ]
214
+
215
+ except Exception as e:
216
+ raise HTTPException(
217
+ status_code=500, detail=f"Failed to list conversations: {e}"
218
+ )
219
+
220
+
221
+ @router.get("/conversations/{conversation_id}")
222
+ async def get_conversation(
223
+ conversation_id: str, user_id: str = "api-user"
224
+ ) -> dict[str, Any]:
225
+ """
226
+ Get a specific conversation with full message history.
227
+
228
+ Args:
229
+ conversation_id: The conversation identifier
230
+ user_id: User identifier for access control
231
+
232
+ Returns:
233
+ Full conversation details with messages
234
+
235
+ Raises:
236
+ HTTPException: If conversation not found or access denied
237
+ """
238
+ try:
239
+ conversation = ai_service.get_conversation(conversation_id)
240
+
241
+ if not conversation:
242
+ raise HTTPException(status_code=404, detail="Conversation not found")
243
+
244
+ # Check access (basic user matching)
245
+ if conversation.metadata.get("user_id") != user_id:
246
+ raise HTTPException(status_code=403, detail="Access denied")
247
+
248
+ return {
249
+ "id": conversation.id,
250
+ "title": conversation.title,
251
+ "provider": conversation.provider.value,
252
+ "model": conversation.model,
253
+ "created_at": conversation.created_at.isoformat(),
254
+ "updated_at": conversation.updated_at.isoformat(),
255
+ "message_count": conversation.get_message_count(),
256
+ "messages": [
257
+ {
258
+ "id": msg.id,
259
+ "role": msg.role.value,
260
+ "content": msg.content,
261
+ "timestamp": msg.timestamp.isoformat(),
262
+ }
263
+ for msg in conversation.messages
264
+ ],
265
+ "metadata": conversation.metadata,
266
+ }
267
+
268
+ except HTTPException:
269
+ raise
270
+ except Exception as e:
271
+ raise HTTPException(status_code=500, detail=f"Failed to get conversation: {e}")
272
+
273
+
274
+ @router.get("/health")
275
+ async def ai_health() -> dict[str, Any]:
276
+ """
277
+ AI service health endpoint.
278
+
279
+ Returns comprehensive health status including configuration,
280
+ conversation count, and service availability.
281
+ """
282
+ try:
283
+ status = ai_service.get_service_status()
284
+ validation_errors = ai_service.validate_service()
285
+
286
+ return {
287
+ "service": "ai",
288
+ "status": "healthy" if not validation_errors else "unhealthy",
289
+ "enabled": status["enabled"],
290
+ "provider": status["provider"],
291
+ "model": status["model"],
292
+ "agent_ready": status["agent_initialized"],
293
+ "total_conversations": status["total_conversations"],
294
+ "configuration_valid": status["configuration_valid"],
295
+ "validation_errors": validation_errors,
296
+ }
297
+
298
+ except Exception as e:
299
+ return {
300
+ "service": "ai",
301
+ "status": "error",
302
+ "error": str(e),
303
+ }
304
+
305
+
306
+ @router.get("/version")
307
+ async def ai_version() -> dict[str, Any]:
308
+ """AI service version and feature information."""
309
+ return {
310
+ "service": "ai",
311
+ "engine": "pydantic-ai",
312
+ "version": "1.0",
313
+ "features": [
314
+ "chat",
315
+ "conversation_management",
316
+ "multi_provider_support",
317
+ "health_monitoring",
318
+ "api_endpoints",
319
+ "cli_commands",
320
+ ],
321
+ "providers_supported": [
322
+ "openai",
323
+ "anthropic",
324
+ "google",
325
+ "groq",
326
+ "mistral",
327
+ "cohere",
328
+ ],
329
+ }