quantum-framework 0.18.0__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 (333) hide show
  1. quantum/__init__.py +1 -0
  2. quantum/cli/__init__.py +2 -0
  3. quantum/cli/admin.py +131 -0
  4. quantum/cli/check.py +223 -0
  5. quantum/cli/commands/__init__.py +15 -0
  6. quantum/cli/commands/build.py +414 -0
  7. quantum/cli/commands/dev.py +185 -0
  8. quantum/cli/commands/docs.py +329 -0
  9. quantum/cli/commands/lint.py +523 -0
  10. quantum/cli/commands/migrate.py +548 -0
  11. quantum/cli/commands/new.py +622 -0
  12. quantum/cli/commands/serve.py +190 -0
  13. quantum/cli/commands/test.py +193 -0
  14. quantum/cli/deploy.py +810 -0
  15. quantum/cli/hot_reload.py +951 -0
  16. quantum/cli/jobs.py +356 -0
  17. quantum/cli/mq.py +582 -0
  18. quantum/cli/pkg.py +390 -0
  19. quantum/cli/runner.py +565 -0
  20. quantum/cli/server_process.py +159 -0
  21. quantum/cli/utils.py +334 -0
  22. quantum/compiler/__init__.py +30 -0
  23. quantum/compiler/base_generator.py +367 -0
  24. quantum/compiler/cli.py +295 -0
  25. quantum/compiler/expression_transformer.py +444 -0
  26. quantum/compiler/javascript/__init__.py +10 -0
  27. quantum/compiler/javascript/generator.py +659 -0
  28. quantum/compiler/optimizer.py +270 -0
  29. quantum/compiler/python/__init__.py +10 -0
  30. quantum/compiler/python/generator.py +883 -0
  31. quantum/compiler/python/runtime.py +863 -0
  32. quantum/compiler/transpiler.py +330 -0
  33. quantum/core/__init__.py +3 -0
  34. quantum/core/ast_nodes.py +2619 -0
  35. quantum/core/config_env.py +54 -0
  36. quantum/core/docs_links.py +8 -0
  37. quantum/core/expression_diagnostics.py +87 -0
  38. quantum/core/expression_stdlib.py +208 -0
  39. quantum/core/expressions.py +677 -0
  40. quantum/core/features/agents/src/__init__.py +30 -0
  41. quantum/core/features/agents/src/ast_node.py +540 -0
  42. quantum/core/features/conditionals/src/__init__.py +8 -0
  43. quantum/core/features/conditionals/src/ast_node.py +68 -0
  44. quantum/core/features/data_import/src/__init__.py +0 -0
  45. quantum/core/features/data_import/src/ast_node.py +292 -0
  46. quantum/core/features/data_import/src/runtime.py +533 -0
  47. quantum/core/features/dump/src/__init__.py +12 -0
  48. quantum/core/features/dump/src/ast_node.py +106 -0
  49. quantum/core/features/dump/src/parser.py +61 -0
  50. quantum/core/features/dump/src/runtime.py +246 -0
  51. quantum/core/features/functions/src/__init__.py +8 -0
  52. quantum/core/features/functions/src/ast_node.py +149 -0
  53. quantum/core/features/game_engine_2d/src/__init__.py +19 -0
  54. quantum/core/features/game_engine_2d/src/ast_nodes.py +1719 -0
  55. quantum/core/features/game_engine_2d/src/parser.py +983 -0
  56. quantum/core/features/invocation/src/__init__.py +0 -0
  57. quantum/core/features/invocation/src/ast_node.py +147 -0
  58. quantum/core/features/invocation/src/runtime.py +332 -0
  59. quantum/core/features/knowledge_base/src/__init__.py +6 -0
  60. quantum/core/features/knowledge_base/src/ast_node.py +113 -0
  61. quantum/core/features/knowledge_base/src/parser.py +82 -0
  62. quantum/core/features/logging/src/__init__.py +12 -0
  63. quantum/core/features/logging/src/ast_node.py +111 -0
  64. quantum/core/features/logging/src/parser.py +50 -0
  65. quantum/core/features/logging/src/runtime.py +190 -0
  66. quantum/core/features/loops/src/__init__.py +8 -0
  67. quantum/core/features/loops/src/ast_node.py +60 -0
  68. quantum/core/features/query/src/__init__.py +0 -0
  69. quantum/core/features/query/src/query_validators.py +20 -0
  70. quantum/core/features/state_management/src/__init__.py +11 -0
  71. quantum/core/features/state_management/src/ast_node.py +121 -0
  72. quantum/core/features/terminal_engine/src/__init__.py +21 -0
  73. quantum/core/features/terminal_engine/src/ast_nodes.py +560 -0
  74. quantum/core/features/terminal_engine/src/parser.py +361 -0
  75. quantum/core/features/testing_engine/src/__init__.py +41 -0
  76. quantum/core/features/testing_engine/src/ast_nodes.py +1212 -0
  77. quantum/core/features/testing_engine/src/parser.py +604 -0
  78. quantum/core/features/theming/src/__init__.py +48 -0
  79. quantum/core/features/theming/src/ast_node.py +137 -0
  80. quantum/core/features/theming/src/presets.py +405 -0
  81. quantum/core/features/ui_engine/src/__init__.py +20 -0
  82. quantum/core/features/ui_engine/src/ast_nodes.py +1913 -0
  83. quantum/core/features/ui_engine/src/parser.py +1192 -0
  84. quantum/core/features/websocket/src/__init__.py +24 -0
  85. quantum/core/features/websocket/src/ast_node.py +247 -0
  86. quantum/core/guards.py +53 -0
  87. quantum/core/html_compat.py +299 -0
  88. quantum/core/parser.py +1551 -0
  89. quantum/core/parser_registry.py +213 -0
  90. quantum/core/parsers/__init__.py +76 -0
  91. quantum/core/parsers/ai/__init__.py +12 -0
  92. quantum/core/parsers/ai/agent_parser.py +106 -0
  93. quantum/core/parsers/ai/knowledge_parser.py +86 -0
  94. quantum/core/parsers/ai/llm_parser.py +78 -0
  95. quantum/core/parsers/ai/team_parser.py +88 -0
  96. quantum/core/parsers/base.py +322 -0
  97. quantum/core/parsers/composition/__init__.py +10 -0
  98. quantum/core/parsers/composition/import_parser.py +50 -0
  99. quantum/core/parsers/composition/slot_parser.py +48 -0
  100. quantum/core/parsers/control_flow/__init__.py +11 -0
  101. quantum/core/parsers/control_flow/if_parser.py +71 -0
  102. quantum/core/parsers/control_flow/loop_parser.py +105 -0
  103. quantum/core/parsers/control_flow/set_parser.py +93 -0
  104. quantum/core/parsers/data/__init__.py +12 -0
  105. quantum/core/parsers/data/data_parser.py +229 -0
  106. quantum/core/parsers/data/invoke_parser.py +133 -0
  107. quantum/core/parsers/data/query_parser.py +213 -0
  108. quantum/core/parsers/data/transaction_parser.py +86 -0
  109. quantum/core/parsers/events/__init__.py +9 -0
  110. quantum/core/parsers/events/dispatch_event_parser.py +44 -0
  111. quantum/core/parsers/forms/__init__.py +11 -0
  112. quantum/core/parsers/forms/action_parser.py +79 -0
  113. quantum/core/parsers/forms/flash_parser.py +34 -0
  114. quantum/core/parsers/forms/redirect_parser.py +33 -0
  115. quantum/core/parsers/functions/__init__.py +11 -0
  116. quantum/core/parsers/functions/function_parser.py +98 -0
  117. quantum/core/parsers/functions/param_parser.py +73 -0
  118. quantum/core/parsers/functions/return_parser.py +31 -0
  119. quantum/core/parsers/html/__init__.py +10 -0
  120. quantum/core/parsers/html/component_call_parser.py +130 -0
  121. quantum/core/parsers/html/html_parser.py +115 -0
  122. quantum/core/parsers/jobs/__init__.py +11 -0
  123. quantum/core/parsers/jobs/job_parser.py +71 -0
  124. quantum/core/parsers/jobs/schedule_parser.py +62 -0
  125. quantum/core/parsers/jobs/thread_parser.py +57 -0
  126. quantum/core/parsers/messaging/__init__.py +17 -0
  127. quantum/core/parsers/messaging/message_ack_parser.py +30 -0
  128. quantum/core/parsers/messaging/message_nack_parser.py +30 -0
  129. quantum/core/parsers/messaging/message_parser.py +114 -0
  130. quantum/core/parsers/messaging/queue_parser.py +61 -0
  131. quantum/core/parsers/messaging/websocket_parser.py +121 -0
  132. quantum/core/parsers/routing/__init__.py +9 -0
  133. quantum/core/parsers/routing/route_parser.py +41 -0
  134. quantum/core/parsers/scripting/__init__.py +12 -0
  135. quantum/core/parsers/scripting/pyclass_parser.py +62 -0
  136. quantum/core/parsers/scripting/pydecorator_parser.py +68 -0
  137. quantum/core/parsers/scripting/pyimport_parser.py +52 -0
  138. quantum/core/parsers/scripting/python_parser.py +49 -0
  139. quantum/core/parsers/services/__init__.py +12 -0
  140. quantum/core/parsers/services/dump_parser.py +52 -0
  141. quantum/core/parsers/services/file_parser.py +48 -0
  142. quantum/core/parsers/services/log_parser.py +46 -0
  143. quantum/core/parsers/services/mail_parser.py +65 -0
  144. quantum/core/tiers.py +164 -0
  145. quantum/core/xml_lines.py +81 -0
  146. quantum/packages/__init__.py +28 -0
  147. quantum/packages/manager.py +413 -0
  148. quantum/packages/manifest.py +351 -0
  149. quantum/packages/registry.py +399 -0
  150. quantum/packages/resolver.py +336 -0
  151. quantum/plugins/__init__.py +33 -0
  152. quantum/plugins/hooks.py +329 -0
  153. quantum/plugins/loader.py +479 -0
  154. quantum/plugins/manifest.py +336 -0
  155. quantum/plugins/registry.py +371 -0
  156. quantum/runtime/__init__.py +25 -0
  157. quantum/runtime/action_handler.py +532 -0
  158. quantum/runtime/adapters/__init__.py +88 -0
  159. quantum/runtime/adapters/memory_adapter.py +690 -0
  160. quantum/runtime/adapters/rabbitmq_adapter.py +715 -0
  161. quantum/runtime/adapters/redis_adapter.py +582 -0
  162. quantum/runtime/adapters/sqlite_adapter.py +414 -0
  163. quantum/runtime/agent_service.py +1133 -0
  164. quantum/runtime/ast_cache.py +506 -0
  165. quantum/runtime/auth_service.py +267 -0
  166. quantum/runtime/component.py +1048 -0
  167. quantum/runtime/component_composer.py +133 -0
  168. quantum/runtime/component_resolver.py +174 -0
  169. quantum/runtime/conditions.py +54 -0
  170. quantum/runtime/database_service.py +595 -0
  171. quantum/runtime/db_schema.py +100 -0
  172. quantum/runtime/dev_panel.py +178 -0
  173. quantum/runtime/email_service.py +162 -0
  174. quantum/runtime/error_handler.py +295 -0
  175. quantum/runtime/execution_context.py +283 -0
  176. quantum/runtime/executor_registry.py +178 -0
  177. quantum/runtime/executors/__init__.py +71 -0
  178. quantum/runtime/executors/ai/__init__.py +12 -0
  179. quantum/runtime/executors/ai/agent_executor.py +217 -0
  180. quantum/runtime/executors/ai/knowledge_executor.py +114 -0
  181. quantum/runtime/executors/ai/llm_executor.py +153 -0
  182. quantum/runtime/executors/ai/team_executor.py +171 -0
  183. quantum/runtime/executors/base.py +264 -0
  184. quantum/runtime/executors/control_flow/__init__.py +12 -0
  185. quantum/runtime/executors/control_flow/if_executor.py +98 -0
  186. quantum/runtime/executors/control_flow/loop_executor.py +308 -0
  187. quantum/runtime/executors/control_flow/redirect_executor.py +43 -0
  188. quantum/runtime/executors/control_flow/set_executor.py +464 -0
  189. quantum/runtime/executors/data/__init__.py +12 -0
  190. quantum/runtime/executors/data/data_executor.py +156 -0
  191. quantum/runtime/executors/data/invoke_executor.py +228 -0
  192. quantum/runtime/executors/data/query_executor.py +290 -0
  193. quantum/runtime/executors/data/transaction_executor.py +91 -0
  194. quantum/runtime/executors/jobs/__init__.py +11 -0
  195. quantum/runtime/executors/jobs/job_executor.py +190 -0
  196. quantum/runtime/executors/jobs/schedule_executor.py +132 -0
  197. quantum/runtime/executors/jobs/thread_executor.py +127 -0
  198. quantum/runtime/executors/messaging/__init__.py +17 -0
  199. quantum/runtime/executors/messaging/message_ack_executor.py +51 -0
  200. quantum/runtime/executors/messaging/message_executor.py +174 -0
  201. quantum/runtime/executors/messaging/queue_executor.py +103 -0
  202. quantum/runtime/executors/messaging/websocket_executor.py +197 -0
  203. quantum/runtime/executors/scripting/__init__.py +11 -0
  204. quantum/runtime/executors/scripting/pyclass_executor.py +90 -0
  205. quantum/runtime/executors/scripting/pyimport_executor.py +81 -0
  206. quantum/runtime/executors/scripting/python_executor.py +249 -0
  207. quantum/runtime/executors/services/__init__.py +12 -0
  208. quantum/runtime/executors/services/dump_executor.py +72 -0
  209. quantum/runtime/executors/services/file_executor.py +89 -0
  210. quantum/runtime/executors/services/log_executor.py +77 -0
  211. quantum/runtime/executors/services/mail_executor.py +81 -0
  212. quantum/runtime/expression_cache.py +498 -0
  213. quantum/runtime/file_upload_service.py +326 -0
  214. quantum/runtime/function_registry.py +118 -0
  215. quantum/runtime/game_builder.py +166 -0
  216. quantum/runtime/game_code_generator.py +2371 -0
  217. quantum/runtime/game_templates.py +2006 -0
  218. quantum/runtime/godot_code_generator.py +4681 -0
  219. quantum/runtime/godot_templates.py +1449 -0
  220. quantum/runtime/job_executor.py +1599 -0
  221. quantum/runtime/knowledge_service.py +546 -0
  222. quantum/runtime/llm_cache.py +100 -0
  223. quantum/runtime/llm_providers.py +704 -0
  224. quantum/runtime/llm_service.py +287 -0
  225. quantum/runtime/local_server.py +28 -0
  226. quantum/runtime/logging_setup.py +141 -0
  227. quantum/runtime/message_broker.py +364 -0
  228. quantum/runtime/message_queue_service.py +571 -0
  229. quantum/runtime/param_validation.py +218 -0
  230. quantum/runtime/pypy_compat.py +317 -0
  231. quantum/runtime/python_bridge.py +698 -0
  232. quantum/runtime/query_validators.py +304 -0
  233. quantum/runtime/renderer.py +734 -0
  234. quantum/runtime/service_container.py +470 -0
  235. quantum/runtime/table_params.py +163 -0
  236. quantum/runtime/terminal_builder.py +76 -0
  237. quantum/runtime/terminal_code_generator.py +607 -0
  238. quantum/runtime/terminal_templates.py +243 -0
  239. quantum/runtime/testing_builder.py +77 -0
  240. quantum/runtime/testing_code_generator.py +833 -0
  241. quantum/runtime/testing_templates.py +85 -0
  242. quantum/runtime/ui_builder.py +222 -0
  243. quantum/runtime/ui_console.py +405 -0
  244. quantum/runtime/ui_desktop.py +56 -0
  245. quantum/runtime/ui_form_rules.py +193 -0
  246. quantum/runtime/ui_html_adapter.py +2567 -0
  247. quantum/runtime/ui_html_templates.py +2106 -0
  248. quantum/runtime/ui_mobile_adapter.py +1832 -0
  249. quantum/runtime/ui_mobile_templates.py +1003 -0
  250. quantum/runtime/ui_page_renderer.py +392 -0
  251. quantum/runtime/ui_pager.py +73 -0
  252. quantum/runtime/ui_table_edit.py +139 -0
  253. quantum/runtime/ui_textual_adapter.py +1866 -0
  254. quantum/runtime/ui_textual_templates.py +45 -0
  255. quantum/runtime/ui_tokens.py +465 -0
  256. quantum/runtime/ui_validator.py +365 -0
  257. quantum/runtime/ui_view.py +221 -0
  258. quantum/runtime/validators.py +256 -0
  259. quantum/runtime/web_server.py +2131 -0
  260. quantum/runtime/websocket_adapter.py +501 -0
  261. quantum/runtime/websocket_service.py +585 -0
  262. quantum/runtime/websocket_transport.py +289 -0
  263. quantum/runtime/wsgi.py +101 -0
  264. quantum/services.py +109 -0
  265. quantum/utils/__init__.py +1 -0
  266. quantum_admin/__init__.py +4 -0
  267. quantum_admin/assets/quantum-admin-connectors.js +123 -0
  268. quantum_admin/assets/quantum-admin.css +2058 -0
  269. quantum_admin/assets/quantum-source-highlight.js +106 -0
  270. quantum_admin/backend/__init__.py +4 -0
  271. quantum_admin/backend/audit_service.py +346 -0
  272. quantum_admin/backend/auth_service.py +394 -0
  273. quantum_admin/backend/component_discovery.py +766 -0
  274. quantum_admin/backend/config_generator.py +586 -0
  275. quantum_admin/backend/connector_service.py +1016 -0
  276. quantum_admin/backend/crud.py +557 -0
  277. quantum_admin/backend/database.py +226 -0
  278. quantum_admin/backend/db_setup_service.py +193 -0
  279. quantum_admin/backend/deploy_service.py +698 -0
  280. quantum_admin/backend/docker_service.py +422 -0
  281. quantum_admin/backend/environment_service.py +418 -0
  282. quantum_admin/backend/git_service.py +370 -0
  283. quantum_admin/backend/health_service.py +390 -0
  284. quantum_admin/backend/job_service.py +347 -0
  285. quantum_admin/backend/log_service.py +331 -0
  286. quantum_admin/backend/main.py +14426 -0
  287. quantum_admin/backend/models.py +986 -0
  288. quantum_admin/backend/pipeline_service.py +812 -0
  289. quantum_admin/backend/resource_manager.py +1118 -0
  290. quantum_admin/backend/schemas.py +419 -0
  291. quantum_admin/backend/secret_manager.py +277 -0
  292. quantum_admin/backend/settings_service.py +488 -0
  293. quantum_admin/backend/template_service.py +710 -0
  294. quantum_admin/backend/test_execution_service.py +364 -0
  295. quantum_admin/backend/test_generator.py +782 -0
  296. quantum_admin/backend/webhook_service.py +454 -0
  297. quantum_admin/backend/websocket_manager.py +360 -0
  298. quantum_admin/components/admin/_layout/AdminShell.q +121 -0
  299. quantum_admin/components/admin/agents.q +107 -0
  300. quantum_admin/components/admin/app/[name].q +558 -0
  301. quantum_admin/components/admin/applications.q +160 -0
  302. quantum_admin/components/admin/component/[...path].q +205 -0
  303. quantum_admin/components/admin/components.q +59 -0
  304. quantum_admin/components/admin/connectors.q +283 -0
  305. quantum_admin/components/admin/dashboard.q +86 -0
  306. quantum_admin/components/admin/database.q +95 -0
  307. quantum_admin/components/admin/features.q +69 -0
  308. quantum_admin/components/admin/index.q +12 -0
  309. quantum_admin/components/admin/jobs.q +75 -0
  310. quantum_admin/components/admin/login.q +65 -0
  311. quantum_admin/components/admin/logout.q +11 -0
  312. quantum_admin/components/admin/settings.q +174 -0
  313. quantum_admin/components/admin/source.q +45 -0
  314. quantum_admin/components/admin/tests.q +73 -0
  315. quantum_admin/run.py +13 -0
  316. quantum_admin/services/__init__.py +16 -0
  317. quantum_admin/services/_base.py +84 -0
  318. quantum_admin/services/_component_test_generator.py +205 -0
  319. quantum_admin/services/_yaml_editor.py +82 -0
  320. quantum_admin/services/apps.py +330 -0
  321. quantum_admin/services/auth.py +61 -0
  322. quantum_admin/services/components.py +210 -0
  323. quantum_admin/services/connectors.py +164 -0
  324. quantum_admin/services/projects.py +189 -0
  325. quantum_admin/services/repository.py +223 -0
  326. quantum_admin/services/settings.py +150 -0
  327. quantum_admin/services/yaml_import.py +118 -0
  328. quantum_framework-0.18.0.dist-info/METADATA +281 -0
  329. quantum_framework-0.18.0.dist-info/RECORD +333 -0
  330. quantum_framework-0.18.0.dist-info/WHEEL +5 -0
  331. quantum_framework-0.18.0.dist-info/entry_points.txt +2 -0
  332. quantum_framework-0.18.0.dist-info/licenses/LICENSE +21 -0
  333. quantum_framework-0.18.0.dist-info/top_level.txt +2 -0
@@ -0,0 +1,223 @@
1
+ """Serviços das telas de leitura do repositório.
2
+
3
+ admin.dashboard.stats dashboard.q
4
+ admin.features.list features.q
5
+ admin.agents.list agents.q
6
+ admin.source.read source.q
7
+ admin.databases.list database.q
8
+ admin.jobs.list jobs.q
9
+
10
+ Tudo só lê. O que as telas tinham de errado e mudou:
11
+ - dashboard.q e features.q procuravam src/core/features, src/core/parsers
12
+ e src/runtime/executors — pastas que não existem desde a troca de src/
13
+ para quantum/. O dashboard mostrava 0 features, 0 parsers e 0 executores,
14
+ e a tela de features vinha vazia. Agora lêem quantum/core/features e os
15
+ registros reais;
16
+ - source.q mostrava qualquer arquivo da raiz — .env, chaves, os YAML de
17
+ settings com as credenciais dos connectors. Agora esses são recusados, e
18
+ a checagem de caminho não aceita mais a pasta vizinha com o mesmo prefixo;
19
+ - database.q abria cada .db com sqlite3.connect normal (modo escrita, com
20
+ journal e trava, inclusive em bancos de aplicações rodando). Agora abre em
21
+ modo somente leitura.
22
+ """
23
+
24
+ import datetime
25
+ import re
26
+ import sqlite3
27
+ from pathlib import Path
28
+
29
+ import yaml
30
+
31
+ from quantum.services import service
32
+ from quantum_admin.services._base import raiz
33
+
34
+ PULAR = {".git", "__pycache__", "node_modules", ".venv", "venv", ".claude", "build", "dist"}
35
+ TIPOS = {".q": "Quantum", ".py": "Python", ".yaml": "YAML", ".yml": "YAML", ".json": "JSON", ".html": "HTML",
36
+ ".css": "CSS", ".js": "JavaScript", ".md": "Markdown", ".txt": "Text"}
37
+ ESTADOS_DE_JOB = ("pending", "running", "completed", "failed")
38
+ SENSIVEIS_NOMES = re.compile(r"(^\.env(\..*)?$|\.pem$|\.key$|^id_(rsa|ed25519|ecdsa)|\.db$|\.sqlite3?$)", re.I)
39
+
40
+
41
+ class RepositoryError(ValueError):
42
+ """Pedido inválido para as telas de leitura."""
43
+
44
+
45
+ def _arquivos(pasta: Path, padrao: str):
46
+ if not pasta.is_dir():
47
+ return []
48
+ return [p for p in sorted(pasta.rglob(padrao)) if not PULAR.intersection(p.relative_to(pasta).parts)]
49
+
50
+
51
+ def _tamanho(n: int) -> str:
52
+ if n >= 1048576:
53
+ return f"{n / 1048576:.1f} MB"
54
+ return f"{n / 1024:.1f} KB" if n >= 1024 else f"{n} B"
55
+
56
+
57
+ def _registros():
58
+ from quantum.core.parser import QuantumParser
59
+ from quantum.runtime.component import ComponentRuntime
60
+ parser = QuantumParser()
61
+ registro = parser._parser_registry
62
+ tags = sum(1 for tag in registro.registered_tags
63
+ if type(registro.get_parser(tag)).__name__ != "HTMLParser")
64
+ return tags, ComponentRuntime(config={})._executor_registry.executor_count
65
+
66
+
67
+ def _config_da_raiz() -> dict:
68
+ """quantum.config.yaml da raiz, ou {} se não houver ou não abrir."""
69
+ try:
70
+ return yaml.safe_load((raiz() / "quantum.config.yaml").read_text(encoding="utf-8")) or {}
71
+ except (OSError, yaml.YAMLError):
72
+ return {}
73
+
74
+
75
+ def _pasta_de_features():
76
+ import quantum
77
+ return Path(quantum.__file__).resolve().parent / "core" / "features"
78
+
79
+
80
+ @service("admin.dashboard.stats")
81
+ def dashboard_stats():
82
+ base = raiz()
83
+ componentes = [p.relative_to(base / "components").as_posix() for p in _arquivos(base / "components", "*.q")]
84
+ tags, executores = _registros()
85
+ return {
86
+ "components": len(componentes), "component_list": componentes,
87
+ "features": len(list_features()["features"]),
88
+ "tests": len(_arquivos(base / "tests", "test_*.py")),
89
+ "parser_tags": tags, "executors": executores,
90
+ "examples": len(_arquivos(base / "examples", "*.q")),
91
+ }
92
+
93
+
94
+ @service("admin.features.list")
95
+ def list_features():
96
+ """Features de quantum/core/features, pelo manifest.yaml de cada uma."""
97
+ features = []
98
+ base = raiz()
99
+ for manifesto in sorted(_pasta_de_features().glob("*/manifest.yaml")):
100
+ pasta = manifesto.parent.name
101
+ # Só dá para abrir no leitor de código quando o pacote está dentro da raiz.
102
+ fonte = manifesto.relative_to(base).as_posix() if base in manifesto.parents else None
103
+ try:
104
+ dados = yaml.safe_load(manifesto.read_text(encoding="utf-8")) or {}
105
+ feature = dados.get("feature") or {}
106
+ descricao = dados.get("description")
107
+ curta = descricao.get("short") if isinstance(descricao, dict) else (descricao or "").strip().split("\n")[0]
108
+ features.append({"name": feature.get("name", pasta), "display_name": feature.get("display_name", pasta),
109
+ "version": feature.get("version"), "status": feature.get("status", "unknown"),
110
+ "category": feature.get("category"), "short_desc": curta or "", "source": fonte})
111
+ except (OSError, yaml.YAMLError, AttributeError) as exc:
112
+ features.append({"name": pasta, "display_name": pasta, "version": None, "status": "error",
113
+ "category": None, "short_desc": f"manifest could not be read: {exc}", "source": fonte})
114
+ contagem = {}
115
+ for f in features:
116
+ contagem[f["status"]] = contagem.get(f["status"], 0) + 1
117
+ ativas, planejadas = contagem.get("active", 0), contagem.get("planned", 0)
118
+ return {"features": features, "by_status": contagem,
119
+ "summary": {"active": ativas, "planned": planejadas, "other": len(features) - ativas - planejadas}}
120
+
121
+
122
+ @service("admin.agents.list")
123
+ def list_agents():
124
+ """q:agent e q:team declarados em components/, examples/ e projects/."""
125
+ base = raiz()
126
+ atributos = re.compile(r'(\w+)\s*=\s*"([^"]*)"')
127
+ agentes, times = [], []
128
+ for pasta in ("components", "examples", "projects"):
129
+ for arquivo in _arquivos(base / pasta, "*.q"):
130
+ texto = arquivo.read_text(encoding="utf-8", errors="ignore")
131
+ origem = arquivo.relative_to(base).as_posix()
132
+ for m in re.finditer(r"<q:agent\s+([^>]*?)/?>", texto, re.S):
133
+ a = dict(atributos.findall(m.group(1)))
134
+ agentes.append({"name": a.get("name"), "model": a.get("model"),
135
+ "provider": a.get("provider"), "source": origem})
136
+ for m in re.finditer(r"<q:team\s+([^>]*?)/?>", texto, re.S):
137
+ a = dict(atributos.findall(m.group(1)))
138
+ times.append({"name": a.get("name"), "supervisor": a.get("supervisor"), "source": origem})
139
+ llm = _config_da_raiz().get("llm")
140
+ return {"agents": agentes, "teams": times,
141
+ "sources": sorted({a["source"] for a in agentes} | {t["source"] for t in times}),
142
+ "providers": sorted({a["provider"] for a in agentes if a["provider"]}),
143
+ # Só estes três campos: a seção llm pode ter api_key.
144
+ "llm": {"base_url": llm.get("base_url"), "default_model": llm.get("default_model"),
145
+ "timeout": llm.get("timeout", 60)} if isinstance(llm, dict) else None}
146
+
147
+
148
+ @service("admin.source.read")
149
+ def read_source(path: str):
150
+ relativo = (path or "").strip().replace("\\", "/")
151
+ if not relativo:
152
+ raise RepositoryError("a file path is required")
153
+ base = raiz()
154
+ arquivo = (base / relativo).resolve()
155
+ if base not in arquivo.parents:
156
+ raise RepositoryError(f"{relativo!r} is outside the project root")
157
+ partes = arquivo.relative_to(base).parts
158
+ if SENSIVEIS_NOMES.search(arquivo.name) or partes[:2] == ("quantum_admin", "settings"):
159
+ raise RepositoryError(f"{relativo} may contain credentials and is not shown here")
160
+ if not arquivo.is_file():
161
+ raise RepositoryError(f"file not found: {relativo}")
162
+ conteudo = arquivo.read_text(encoding="utf-8", errors="replace")
163
+ estado = arquivo.stat()
164
+ return {"path": arquivo.relative_to(base).as_posix(), "type": TIPOS.get(arquivo.suffix.lower(), "Unknown"),
165
+ "size": _tamanho(estado.st_size), "lines": conteudo.count("\n") + 1, "content": conteudo,
166
+ "modified": datetime.datetime.fromtimestamp(estado.st_mtime).strftime("%Y-%m-%d %H:%M")}
167
+
168
+
169
+ @service("admin.databases.list")
170
+ def list_databases():
171
+ """Arquivos .db da raiz, abertos SOMENTE LEITURA, com tabelas e linhas."""
172
+ base = raiz()
173
+ bancos = []
174
+ for arquivo in _arquivos(base, "*.db"):
175
+ tabelas, erro = [], None
176
+ try:
177
+ conexao = sqlite3.connect(f"{arquivo.resolve().as_uri()}?mode=ro", uri=True, timeout=2)
178
+ try:
179
+ for (nome,) in conexao.execute("SELECT name FROM sqlite_master WHERE type='table' ORDER BY name"):
180
+ try:
181
+ linhas = conexao.execute(f'SELECT COUNT(*) FROM "{nome}"').fetchone()[0]
182
+ except sqlite3.DatabaseError:
183
+ linhas = None
184
+ tabelas.append({"name": nome, "rows": linhas})
185
+ finally:
186
+ conexao.close()
187
+ except sqlite3.DatabaseError as exc:
188
+ erro = str(exc)
189
+ estado = arquivo.stat()
190
+ bancos.append({"path": arquivo.relative_to(base).as_posix(), "size": _tamanho(estado.st_size),
191
+ "modified": datetime.datetime.fromtimestamp(estado.st_mtime).strftime("%Y-%m-%d %H:%M"),
192
+ "tables": tabelas, "error": erro})
193
+ fontes = _config_da_raiz().get("datasources") or {}
194
+ # Nome, driver e banco — host, usuário e senha ficam de fora.
195
+ declaradas = [{"name": nome, "driver": (cfg or {}).get("driver") or (cfg or {}).get("type"),
196
+ "database": (cfg or {}).get("database")} for nome, cfg in sorted(fontes.items())
197
+ if isinstance(cfg, dict)] if isinstance(fontes, dict) else []
198
+ return {"databases": bancos, "total_tables": sum(len(b["tables"]) for b in bancos), "datasources": declaradas}
199
+
200
+
201
+ @service("admin.jobs.list")
202
+ def list_jobs(limit: int = 50):
203
+ """Fila de q:job (quantum_jobs.db na raiz), somente leitura."""
204
+ arquivo = raiz() / "quantum_jobs.db"
205
+ # Os quatro estados da fila sempre presentes, para a tela não testar chave.
206
+ vazio = {"found": False, "counts": dict.fromkeys(ESTADOS_DE_JOB, 0), "total": 0, "jobs": []}
207
+ if not arquivo.is_file():
208
+ return vazio
209
+ try:
210
+ conexao = sqlite3.connect(f"{arquivo.resolve().as_uri()}?mode=ro", uri=True, timeout=2)
211
+ conexao.row_factory = sqlite3.Row
212
+ try:
213
+ contagem = dict.fromkeys(ESTADOS_DE_JOB, 0)
214
+ contagem.update({r["status"]: r["cnt"] for r in
215
+ conexao.execute("SELECT status, COUNT(*) AS cnt FROM quantum_jobs GROUP BY status")})
216
+ jobs = [dict(r) for r in conexao.execute(
217
+ "SELECT id, name, queue, status, attempts, max_attempts, created_at, error "
218
+ "FROM quantum_jobs ORDER BY id DESC LIMIT ?", (int(limit),))]
219
+ finally:
220
+ conexao.close()
221
+ except sqlite3.DatabaseError as exc:
222
+ return {**vazio, "found": True, "error": str(exc)}
223
+ return {"found": True, "counts": contagem, "total": sum(contagem.values()), "jobs": jobs}
@@ -0,0 +1,150 @@
1
+ """Serviços da área Configurações (admin.settings.*, admin.system.*).
2
+
3
+ Duas configurações diferentes, que a interface antiga misturava sob o mesmo nome:
4
+
5
+ - quantum.config.yaml da raiz: a configuração do SERVIDOR Quantum que
6
+ `quantum start` usa. components/admin/settings.q editava esta.
7
+ - settings/global.yaml: as configurações do próprio admin
8
+ (settings_service do backend). O FastAPI editava esta.
9
+
10
+ O que settings.q fazia de errado e mudou:
11
+ - regravava quantum.config.yaml com yaml.dump, apagando todos os
12
+ comentários (incluindo os avisos de segurança) — agora só a linha do
13
+ valor muda (_yaml_editor, que confere o resultado antes de gravar);
14
+ - porta inválida virava 8080 em silêncio e erro de escrita era engolido —
15
+ agora é erro;
16
+ - aceitava debug com host fora do loopback, que o servidor recusa ao
17
+ subir — agora recusa já ao salvar;
18
+ - contava parsers e executores em src/core/parsers, pasta que não existe
19
+ mais: mostrava sempre 0 — agora pergunta aos registros reais.
20
+ """
21
+
22
+ import platform
23
+ import sys
24
+
25
+ import yaml
26
+
27
+ from quantum.services import service
28
+ from quantum_admin.services._base import raiz
29
+ from quantum_admin.services._yaml_editor import gravar_valores
30
+
31
+ NIVEIS_DE_LOG = ("DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL")
32
+ LOOPBACK = ("127.0.0.1", "localhost", "::1")
33
+
34
+
35
+ class SettingsError(ValueError):
36
+ """Valor de configuração inválido."""
37
+
38
+
39
+ def _arquivo():
40
+ return raiz() / "quantum.config.yaml"
41
+
42
+
43
+ @service("admin.settings.server_config")
44
+ def server_config():
45
+ """O quantum.config.yaml da raiz, como está no arquivo (sem expandir ${VAR})."""
46
+ arquivo = _arquivo()
47
+ if not arquivo.is_file():
48
+ return {"found": False, "path": str(arquivo)}
49
+ try:
50
+ cfg = yaml.safe_load(arquivo.read_text(encoding="utf-8")) or {}
51
+ except yaml.YAMLError as exc:
52
+ return {"found": True, "path": str(arquivo), "error": str(exc)}
53
+ servidor = cfg.get("server") or {}
54
+ return {
55
+ "found": True, "path": str(arquivo),
56
+ "server": {"port": servidor.get("port", 8080), "host": servidor.get("host", "127.0.0.1"),
57
+ "debug": bool(servidor.get("debug", False)), "reload": bool(servidor.get("reload", False))},
58
+ "paths": {"components": "./components", "static": "./static", "logs": "./logs", **(cfg.get("paths") or {})},
59
+ "performance": {"cache_templates": False, "cache_ttl": 0, "cache_max_size": 0, **(cfg.get("performance") or {})},
60
+ "security": {"xss_protection": False, "cors_enabled": False, "max_content_length": 0,
61
+ **(cfg.get("security") or {})},
62
+ "llm": cfg.get("llm") or {},
63
+ "logging": {"level": "INFO", "console": False, "file": False, **(cfg.get("logging") or {})},
64
+ "datasources": sorted((cfg.get("datasources") or {}).keys()),
65
+ "services": list(cfg.get("services") or []),
66
+ }
67
+
68
+
69
+ @service("admin.settings.save_server_config")
70
+ def save_server_config(port: int, host: str, debug: bool = False, reload: bool = False,
71
+ cache_templates: bool = False, cache_ttl: int = 300, log_level: str = "INFO",
72
+ xss_protection: bool = True, cors_enabled: bool = False):
73
+ """Grava os valores do formulário mudando só as linhas deles no arquivo."""
74
+ try:
75
+ porta = int(port)
76
+ except (TypeError, ValueError):
77
+ raise SettingsError(f"port must be a number, got {port!r}")
78
+ if not 1 <= porta <= 65535:
79
+ raise SettingsError(f"port must be between 1 and 65535, got {porta}")
80
+ endereco = (host or "").strip()
81
+ if not endereco:
82
+ raise SettingsError("host is required (127.0.0.1 keeps the server local)")
83
+ if debug and endereco not in LOOPBACK:
84
+ raise SettingsError(
85
+ f"debug cannot be on with host {endereco}: the debugger runs code for anyone who reaches "
86
+ f"the port, and quantum start refuses it. Use 127.0.0.1, or turn debug off.")
87
+ nivel = (log_level or "").upper()
88
+ if nivel not in NIVEIS_DE_LOG:
89
+ raise SettingsError(f"log_level must be one of {', '.join(NIVEIS_DE_LOG)}")
90
+ ttl = int(cache_ttl)
91
+ if ttl < 0:
92
+ raise SettingsError("cache_ttl cannot be negative")
93
+ gravar_valores(_arquivo(), {
94
+ ("server", "port"): porta, ("server", "host"): endereco,
95
+ ("server", "debug"): bool(debug), ("server", "reload"): bool(reload),
96
+ ("performance", "cache_templates"): bool(cache_templates), ("performance", "cache_ttl"): ttl,
97
+ ("logging", "level"): nivel,
98
+ ("security", "xss_protection"): bool(xss_protection), ("security", "cors_enabled"): bool(cors_enabled),
99
+ })
100
+ return server_config()
101
+
102
+
103
+ _CHAVE_SECRETA = ("password", "secret", "token", "api_key", "apikey", "private_key")
104
+
105
+
106
+ def _mascarar(valor, chave=""):
107
+ """Troca o valor de chaves que parecem segredo por '****' (uma referência ${VAR} fica visível)."""
108
+ if isinstance(valor, dict):
109
+ return {k: _mascarar(v, str(k)) for k, v in valor.items()}
110
+ if isinstance(valor, list):
111
+ return [_mascarar(v, chave) for v in valor]
112
+ if any(p in chave.lower() for p in _CHAVE_SECRETA) and valor not in (None, "") \
113
+ and not (isinstance(valor, str) and valor.startswith("${")):
114
+ return "****"
115
+ return valor
116
+
117
+
118
+ @service("admin.settings.global_get")
119
+ def global_get():
120
+ """settings/global.yaml do admin (settings_service), com segredos mascarados.
121
+
122
+ A biblioteca devolve os valores como estão no arquivo; senhas e tokens
123
+ escritos ali sairiam na página. Uma referência ${VAR} continua visível.
124
+ """
125
+ from quantum_admin.backend.settings_service import get_settings_service
126
+ return _mascarar(get_settings_service().get_global_settings(resolve_env=False))
127
+
128
+
129
+ @service("admin.system.info")
130
+ def system_info():
131
+ """Versões e o que a linguagem tem registrado — medido nos registros, não em pastas."""
132
+ from quantum.core.parser import QuantumParser
133
+ from quantum.runtime.component import ComponentRuntime
134
+ try:
135
+ from importlib.metadata import version
136
+ versao = version("quantum-framework")
137
+ except Exception: # noqa: BLE001 — rodando de um clone sem instalar
138
+ versao = None
139
+ parser = QuantumParser()
140
+ runtime = ComponentRuntime(config={})
141
+ return {
142
+ "quantum_version": versao,
143
+ "python_version": sys.version.split()[0],
144
+ "platform": platform.platform(),
145
+ "root": str(raiz()).replace("\\", "/"),
146
+ # só tags da linguagem: o registro também guarda os nomes de tags HTML
147
+ "parser_tags": sum(1 for tag in parser._parser_registry.registered_tags
148
+ if type(parser._parser_registry.get_parser(tag)).__name__ != "HTMLParser"),
149
+ "executors": runtime._executor_registry.executor_count,
150
+ }
@@ -0,0 +1,118 @@
1
+ """Importa os projetos do YAML antigo das telas .q para o banco (admin.import.*).
2
+
3
+ As telas .q gravavam projetos em quantum_admin/settings/projects.yaml; o
4
+ FastAPI (crud), no banco. Medido em 2026-09-10: nenhum projeto em comum — os
5
+ 14 reais estavam no YAML, e o banco só tinha os três de demonstração do
6
+ seed_db. O serviço admin.projects usa o banco; esta importação traz os
7
+ projetos para lá.
8
+
9
+ Connectors NÃO são importados: a biblioteca do backend (connector_service)
10
+ os mantém em settings/connectors.yaml, o mesmo arquivo das telas — não há
11
+ divisão. A tabela `connectors` do banco não é usada por nada. O que a
12
+ importação faz com connectors é só apontar os que estão presos a um projeto
13
+ pelo id antigo do YAML (um uuid), que connector_service não reconhece.
14
+
15
+ Garantias (os dados são do dono, não são recriáveis):
16
+ - antes de gravar, copia o arquivo do banco para quantum_admin/backups/;
17
+ - nunca altera nem apaga os YAML;
18
+ - idempotente: projeto já existente (mesmo nome, sem diferenciar
19
+ maiúsculas) é pulado.
20
+ """
21
+
22
+ import datetime
23
+ import shutil
24
+ from pathlib import Path
25
+
26
+ import yaml
27
+
28
+ from quantum.services import service
29
+ from quantum_admin.services._base import pasta_de_configuracao, raiz, sessao
30
+
31
+
32
+ def _ler_yaml(nome):
33
+ arquivo = pasta_de_configuracao() / nome
34
+ if not arquivo.is_file():
35
+ return []
36
+ dados = yaml.safe_load(arquivo.read_text(encoding="utf-8")) or []
37
+ return dados if isinstance(dados, list) else []
38
+
39
+
40
+ def _data(valor):
41
+ if isinstance(valor, datetime.datetime):
42
+ return valor
43
+ try:
44
+ return datetime.datetime.fromisoformat(str(valor).replace("Z", "+00:00")).replace(tzinfo=None)
45
+ except (TypeError, ValueError):
46
+ return None
47
+
48
+
49
+ def _models():
50
+ from quantum_admin.backend import models
51
+ return models
52
+
53
+
54
+ @service("admin.import.pending")
55
+ def pending():
56
+ """Projetos do YAML que o banco ainda não tem, e connectors presos a ids antigos."""
57
+ models = _models()
58
+ projetos = _ler_yaml("projects.yaml")
59
+ with sessao() as db:
60
+ nomes = {(p.name or "").lower() for p in db.query(models.Project).all()}
61
+ return {
62
+ "projects": [p.get("name") for p in projetos if p.get("name") and p["name"].lower() not in nomes],
63
+ "connectors_to_review": _connectors_com_id_antigo(projetos),
64
+ }
65
+
66
+
67
+ def _connectors_com_id_antigo(projetos):
68
+ ids_yaml = {str(p.get("id")): p.get("name") for p in projetos if p.get("id")}
69
+ revisar = []
70
+ for c in _ler_yaml("connectors.yaml"):
71
+ dono = c.get("application_id")
72
+ if dono is not None and str(dono) in ids_yaml:
73
+ revisar.append(f"{c.get('name')} (project {ids_yaml[str(dono)]})")
74
+ return revisar
75
+
76
+
77
+ def _backup():
78
+ from quantum_admin.backend import database
79
+ arquivo = database.engine.url.database
80
+ if database.engine.url.get_backend_name() != "sqlite" or not arquivo or not Path(arquivo).is_file():
81
+ return None
82
+ destino = raiz() / "quantum_admin" / "backups"
83
+ destino.mkdir(parents=True, exist_ok=True)
84
+ copia = destino / f"quantum_admin-{datetime.datetime.now():%Y%m%d-%H%M%S}.db"
85
+ database.engine.dispose() # nada pendente em conexões abertas
86
+ shutil.copy2(arquivo, copia)
87
+ return str(copia)
88
+
89
+
90
+ @service("admin.import.run")
91
+ def run():
92
+ """Importa os projetos que faltam; devolve o relatório. Não mexe nos YAML."""
93
+ models = _models()
94
+ projetos = _ler_yaml("projects.yaml")
95
+ faltando = pending()
96
+ relatorio = {"backup": None, "projects": [], "connectors_to_review": faltando["connectors_to_review"]}
97
+ if not faltando["projects"]:
98
+ return relatorio
99
+ relatorio["backup"] = _backup()
100
+ with sessao() as db:
101
+ nomes = {(p.name or "").lower() for p in db.query(models.Project).all()}
102
+ for item in projetos:
103
+ nome = (item.get("name") or "").strip()
104
+ if not nome or nome.lower() in nomes:
105
+ continue
106
+ projeto = models.Project(
107
+ name=nome, description=item.get("description") or "",
108
+ status=item.get("status") or "active",
109
+ source_path=(item.get("source_path") or "").replace("\\", "/"))
110
+ criado, alterado = _data(item.get("created_at")), _data(item.get("updated_at"))
111
+ if criado:
112
+ projeto.created_at = criado
113
+ if alterado:
114
+ projeto.updated_at = alterado
115
+ db.add(projeto)
116
+ nomes.add(nome.lower())
117
+ relatorio["projects"].append(nome)
118
+ return relatorio