voodoo-framework 1.2.0__tar.gz → 1.4.0__tar.gz

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 (163) hide show
  1. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/PKG-INFO +1 -1
  2. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/__init__.py +1 -1
  3. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/cli/__init__.py +2 -1
  4. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/cli/inspect.py +83 -37
  5. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/cli/new.py +0 -1
  6. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/cli/recover.py +12 -4
  7. voodoo_framework-1.4.0/src/voodoo/cli/tasks.py +114 -0
  8. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/cli/terminal.py +3 -1
  9. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/data/__init__.py +7 -4
  10. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/data/base.py +37 -12
  11. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/data/model.py +5 -7
  12. voodoo_framework-1.4.0/src/voodoo/queue.py +12 -0
  13. voodoo_framework-1.4.0/src/voodoo/storage/__init__.py +15 -0
  14. voodoo_framework-1.4.0/src/voodoo/storage/database/__init__.py +29 -0
  15. voodoo_framework-1.4.0/src/voodoo/storage/database/interfaces.py +80 -0
  16. voodoo_framework-1.4.0/src/voodoo/storage/database/sqlite.py +198 -0
  17. voodoo_framework-1.4.0/src/voodoo/storage/queue/__init__.py +31 -0
  18. voodoo_framework-1.4.0/src/voodoo/storage/queue/interfaces.py +231 -0
  19. voodoo_framework-1.4.0/src/voodoo/storage/queue/memory.py +326 -0
  20. voodoo_framework-1.4.0/src/voodoo/storage/queue/sqlite.py +364 -0
  21. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/workers/__init__.py +2 -5
  22. voodoo_framework-1.4.0/src/voodoo/workers/queue.py +160 -0
  23. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo_framework.egg-info/PKG-INFO +1 -1
  24. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo_framework.egg-info/SOURCES.txt +10 -1
  25. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/tests/test_integration.py +16 -11
  26. voodoo_framework-1.2.0/src/voodoo/queue.py +0 -22
  27. voodoo_framework-1.2.0/src/voodoo/workers/queue.py +0 -94
  28. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/README.md +0 -0
  29. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/pyproject.toml +0 -0
  30. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/setup.cfg +0 -0
  31. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/adapters/__init__.py +0 -0
  32. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/adapters/tailwind.py +0 -0
  33. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/adapters/voodoo_css.py +0 -0
  34. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/agent.py +0 -0
  35. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/ai/__init__.py +0 -0
  36. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/ai/agent.py +0 -0
  37. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/ai/providers/__init__.py +0 -0
  38. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/ai/providers/anthropic.py +0 -0
  39. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/ai/providers/gemini.py +0 -0
  40. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/ai/providers/mock.py +0 -0
  41. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/ai/providers/ollama.py +0 -0
  42. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/ai/providers/openai.py +0 -0
  43. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/ai/tools/__init__.py +0 -0
  44. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/ai/tools/registry.py +0 -0
  45. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/api.py +0 -0
  46. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/auth/__init__.py +0 -0
  47. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/auth/cookies.py +0 -0
  48. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/auth/guards.py +0 -0
  49. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/auth/jwt.py +0 -0
  50. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/auth/middleware.py +0 -0
  51. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/auth/passwords.py +0 -0
  52. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/auth/user.py +0 -0
  53. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/cli/__main__.py +0 -0
  54. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/cli/ai.py +0 -0
  55. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/cli/auth.py +0 -0
  56. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/cli/dev.py +0 -0
  57. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/cli/doctor.py +0 -0
  58. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/cli/generate.py +0 -0
  59. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/cli/routes.py +0 -0
  60. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/cli/scaffolding.py +0 -0
  61. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/cli/version.py +0 -0
  62. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/components.py +0 -0
  63. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/config.py +0 -0
  64. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/core/__init__.py +0 -0
  65. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/core/app.py +0 -0
  66. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/core/errors.py +0 -0
  67. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/core/events.py +0 -0
  68. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/core/render.py +0 -0
  69. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/core/routing.py +0 -0
  70. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/core/sitemap.py +0 -0
  71. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/core/state.py +0 -0
  72. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/i18n.py +0 -0
  73. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/mcp/__init__.py +0 -0
  74. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/mcp/client.py +0 -0
  75. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/mesh/__init__.py +0 -0
  76. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/mesh/client.py +0 -0
  77. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/primitives/__init__.py +0 -0
  78. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/primitives/capability.py +0 -0
  79. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/primitives/compute.py +0 -0
  80. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/primitives/constraint.py +0 -0
  81. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/primitives/effect.py +0 -0
  82. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/primitives/intent.py +0 -0
  83. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/primitives/resource.py +0 -0
  84. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/primitives/state.py +0 -0
  85. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/primitives/time.py +0 -0
  86. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/py.typed +0 -0
  87. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/routing/api.py +0 -0
  88. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/routing/pages.py +0 -0
  89. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/runtime/__init__.py +0 -0
  90. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/runtime/adaptive.py +0 -0
  91. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/runtime/capability.py +0 -0
  92. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/runtime/constraint.py +0 -0
  93. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/runtime/context.py +0 -0
  94. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/runtime/engine.py +0 -0
  95. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/runtime/errors.py +0 -0
  96. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/runtime/execution.py +0 -0
  97. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/runtime/graph.py +0 -0
  98. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/runtime/human.py +0 -0
  99. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/runtime/persistence.py +0 -0
  100. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/runtime/planner.py +0 -0
  101. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/runtime/task.py +0 -0
  102. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/runtime/workflow.py +0 -0
  103. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/security/__init__.py +0 -0
  104. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/security/cors.py +0 -0
  105. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/security/csrf.py +0 -0
  106. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/security/headers.py +0 -0
  107. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/security/passwords.py +0 -0
  108. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/security/rate_limit.py +0 -0
  109. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/seo.py +0 -0
  110. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/static/client.js +0 -0
  111. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/status.py +0 -0
  112. /voodoo_framework-1.2.0/src/voodoo/storage.py → /voodoo_framework-1.4.0/src/voodoo/storage/manager.py +0 -0
  113. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/telemetry/__init__.py +0 -0
  114. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/telemetry/api.py +0 -0
  115. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/telemetry/middleware.py +0 -0
  116. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/telemetry/store.py +0 -0
  117. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/theme.py +0 -0
  118. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/tools/__init__.py +0 -0
  119. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/tools/registry.py +0 -0
  120. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/ui/__init__.py +0 -0
  121. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/ui/component.py +0 -0
  122. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/ui/events.py +0 -0
  123. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/ui/library.py +0 -0
  124. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/ui/rendering.py +0 -0
  125. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/ui/state.py +0 -0
  126. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/ui/styles/__init__.py +0 -0
  127. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo/ui/styles/theme.py +0 -0
  128. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo_framework.egg-info/dependency_links.txt +0 -0
  129. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo_framework.egg-info/entry_points.txt +0 -0
  130. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo_framework.egg-info/requires.txt +0 -0
  131. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/src/voodoo_framework.egg-info/top_level.txt +0 -0
  132. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/tests/test_agent.py +0 -0
  133. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/tests/test_agent_runtime.py +0 -0
  134. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/tests/test_app.py +0 -0
  135. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/tests/test_auth.py +0 -0
  136. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/tests/test_cli.py +0 -0
  137. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/tests/test_cli_inspect.py +0 -0
  138. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/tests/test_components.py +0 -0
  139. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/tests/test_contract_api.py +0 -0
  140. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/tests/test_data.py +0 -0
  141. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/tests/test_events.py +0 -0
  142. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/tests/test_http_runtime.py +0 -0
  143. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/tests/test_human.py +0 -0
  144. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/tests/test_i18n.py +0 -0
  145. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/tests/test_mcp.py +0 -0
  146. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/tests/test_mcp_runtime.py +0 -0
  147. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/tests/test_mesh.py +0 -0
  148. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/tests/test_model.py +0 -0
  149. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/tests/test_persistence.py +0 -0
  150. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/tests/test_planner_adaptive.py +0 -0
  151. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/tests/test_primitives.py +0 -0
  152. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/tests/test_providers.py +0 -0
  153. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/tests/test_queue.py +0 -0
  154. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/tests/test_reactive.py +0 -0
  155. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/tests/test_runtime.py +0 -0
  156. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/tests/test_security.py +0 -0
  157. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/tests/test_seo.py +0 -0
  158. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/tests/test_state.py +0 -0
  159. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/tests/test_theme.py +0 -0
  160. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/tests/test_tools.py +0 -0
  161. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/tests/test_ui.py +0 -0
  162. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/tests/test_websocket.py +0 -0
  163. {voodoo_framework-1.2.0 → voodoo_framework-1.4.0}/tests/test_workers.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: voodoo-framework
3
- Version: 1.2.0
3
+ Version: 1.4.0
4
4
  Summary: Voodoo Framework
5
5
  Requires-Python: >=3.12
6
6
  Requires-Dist: starlette
@@ -76,7 +76,7 @@ from .ui.styles import (
76
76
  from .ui.styles.theme import Theme, ThemeColors, create_theme
77
77
  from .workers import task
78
78
 
79
- __version__ = "1.2.0"
79
+ __version__ = "1.4.0"
80
80
 
81
81
  __all__ = [
82
82
  # Core runtime
@@ -1,6 +1,6 @@
1
1
  import typer
2
2
 
3
- from voodoo.cli import ai, dev, doctor, generate, new, recover, routes, version
3
+ from voodoo.cli import ai, dev, doctor, generate, new, recover, routes, tasks, version
4
4
  from voodoo.cli.auth import auth_app
5
5
  from voodoo.cli.inspect import inspect_app
6
6
 
@@ -18,6 +18,7 @@ app.command()(generate.generate)
18
18
  app.add_typer(auth_app, name="auth")
19
19
  app.add_typer(ai.ai_app, name="ai")
20
20
  app.add_typer(inspect_app, name="inspect")
21
+ app.add_typer(tasks.tasks_app, name="tasks")
21
22
  app.command()(version.version)
22
23
  app.command()(doctor.doctor)
23
24
  app.command()(routes.routes)
@@ -52,13 +52,15 @@ def _load_app(app_str: str | None) -> Any:
52
52
  return getattr(mod, attr, None)
53
53
 
54
54
 
55
-
56
55
  def _is_json(json_mode: bool) -> bool:
57
56
  """JSON mode: explicit flag or global --json in argv."""
58
57
  return bool(json_mode) or terminal.is_json_mode()
59
58
 
59
+
60
60
  def _table(columns: list[str]) -> Table:
61
- table = Table(show_header=True, header_style="dim", border_style="#262626", pad_edge=False)
61
+ table = Table(
62
+ show_header=True, header_style="dim", border_style="#262626", pad_edge=False
63
+ )
62
64
  for c in columns:
63
65
  table.add_column(c)
64
66
  return table
@@ -75,9 +77,13 @@ def _emit(data: Any, json_mode: bool = False) -> None:
75
77
 
76
78
  @inspect_app.command("run")
77
79
  def inspect_run(
78
- execution_id: str = typer.Argument(None, help="Execution id (lists recent if omitted)"),
80
+ execution_id: str = typer.Argument(
81
+ None, help="Execution id (lists recent if omitted)"
82
+ ),
79
83
  app_str: str = typer.Option(None, "--app", help="App instance (e.g. main:app)"),
80
- json_mode: bool = typer.Option(False, "--json", help="Output machine-readable JSON"),
84
+ json_mode: bool = typer.Option(
85
+ False, "--json", help="Output machine-readable JSON"
86
+ ),
81
87
  ):
82
88
  """Show an execution — status, intent, capabilities, effects, cost."""
83
89
  from voodoo.runtime import engine
@@ -121,7 +127,10 @@ def inspect_run(
121
127
  ("intent", d["intent"] or "-"),
122
128
  ("actor", d["actor"]),
123
129
  ("cost", f"{d['cost']:.6f}"),
124
- ("duration", f"{d['duration_seconds']:.3f}s" if d["duration_seconds"] else "-"),
130
+ (
131
+ "duration",
132
+ f"{d['duration_seconds']:.3f}s" if d["duration_seconds"] else "-",
133
+ ),
125
134
  ]
126
135
  )
127
136
  if d["capabilities"]:
@@ -136,8 +145,12 @@ def inspect_run(
136
145
  @inspect_app.command("agent")
137
146
  def inspect_agent(
138
147
  app_str: str = typer.Option(None, "--app"),
139
- json_mode: bool = typer.Option(False, "--json", help="Output machine-readable JSON"),
140
- url: str = typer.Option(None, "--url", help="Live server URL (e.g. http://localhost:8000)"),
148
+ json_mode: bool = typer.Option(
149
+ False, "--json", help="Output machine-readable JSON"
150
+ ),
151
+ url: str = typer.Option(
152
+ None, "--url", help="Live server URL (e.g. http://localhost:8000)"
153
+ ),
141
154
  limit: int = typer.Option(10, "--limit"),
142
155
  ):
143
156
  """Recent agent runs: model, tokens, cost, tool calls, status."""
@@ -188,10 +201,16 @@ def inspect_agent(
188
201
 
189
202
  @inspect_app.command("plan")
190
203
  def inspect_plan(
191
- intent_name: str = typer.Argument(..., help="Intent name to plan (e.g. 'notify.customer')"),
192
- requires: str = typer.Option(None, "--requires", help="Comma-separated required capabilities"),
204
+ intent_name: str = typer.Argument(
205
+ ..., help="Intent name to plan (e.g. 'notify.customer')"
206
+ ),
207
+ requires: str = typer.Option(
208
+ None, "--requires", help="Comma-separated required capabilities"
209
+ ),
193
210
  app_str: str = typer.Option(None, "--app", help="App instance (e.g. main:app)"),
194
- json_mode: bool = typer.Option(False, "--json", help="Output machine-readable JSON"),
211
+ json_mode: bool = typer.Option(
212
+ False, "--json", help="Output machine-readable JSON"
213
+ ),
195
214
  ):
196
215
  """Plan an intent — show the planner's strategy + participant assignment."""
197
216
  from voodoo.primitives.intent import Intent
@@ -215,12 +234,14 @@ def inspect_plan(
215
234
  terminal.json_output(data)
216
235
  return
217
236
 
218
- terminal.status_block([
219
- ("intent", data["intent"]),
220
- ("strategy", data["strategy"]),
221
- ("steps", str(len(data["steps"]))),
222
- ("unresolved", str(len(data["unresolved"]))),
223
- ])
237
+ terminal.status_block(
238
+ [
239
+ ("intent", data["intent"]),
240
+ ("strategy", data["strategy"]),
241
+ ("steps", str(len(data["steps"]))),
242
+ ("unresolved", str(len(data["unresolved"]))),
243
+ ]
244
+ )
224
245
  if data["decisions"]:
225
246
  terminal.blank()
226
247
  for d in data["decisions"]:
@@ -234,7 +255,9 @@ def inspect_plan(
234
255
  @inspect_app.command("tool")
235
256
  def inspect_tool(
236
257
  app_str: str = typer.Option(None, "--app"),
237
- json_mode: bool = typer.Option(False, "--json", help="Output machine-readable JSON"),
258
+ json_mode: bool = typer.Option(
259
+ False, "--json", help="Output machine-readable JSON"
260
+ ),
238
261
  limit: int = typer.Option(15, "--limit"),
239
262
  ):
240
263
  """Recent tool calls: name, latency, error."""
@@ -263,7 +286,9 @@ def inspect_tool(
263
286
  @inspect_app.command("task")
264
287
  def inspect_task(
265
288
  app_str: str = typer.Option(None, "--app"),
266
- json_mode: bool = typer.Option(False, "--json", help="Output machine-readable JSON"),
289
+ json_mode: bool = typer.Option(
290
+ False, "--json", help="Output machine-readable JSON"
291
+ ),
267
292
  limit: int = typer.Option(20, "--limit"),
268
293
  ):
269
294
  """Executions whose actor is task/agent-driven (Task units)."""
@@ -294,7 +319,9 @@ def inspect_task(
294
319
  def inspect_workflow(
295
320
  trace_id: str = typer.Argument(None, help="Trace id (groups task executions)"),
296
321
  app_str: str = typer.Option(None, "--app"),
297
- json_mode: bool = typer.Option(False, "--json", help="Output machine-readable JSON"),
322
+ json_mode: bool = typer.Option(
323
+ False, "--json", help="Output machine-readable JSON"
324
+ ),
298
325
  ):
299
326
  """Executions grouped by trace — the observable workflow structure."""
300
327
  from voodoo.runtime import ExecutionGraph, engine
@@ -313,10 +340,15 @@ def inspect_workflow(
313
340
  terminal.muted("no executions recorded in this process")
314
341
  for root in described:
315
342
  terminal.heading(f"trace {root['trace_id'][:8]} · {root.get('intent') or '-'}")
316
- terminal.tree([f"{root['intent'] or root['id'][:8]} [{root['status']}]", *[
317
- f" {c.get('intent') or c['id'][:8]} [{c['status']}] (actor: {c.get('actor')})"
318
- for c in root.get("children", [])
319
- ]])
343
+ terminal.tree(
344
+ [
345
+ f"{root['intent'] or root['id'][:8]} [{root['status']}]",
346
+ *[
347
+ f" {c.get('intent') or c['id'][:8]} [{c['status']}] (actor: {c.get('actor')})"
348
+ for c in root.get("children", [])
349
+ ],
350
+ ]
351
+ )
320
352
  terminal.blank()
321
353
 
322
354
 
@@ -324,7 +356,9 @@ def inspect_workflow(
324
356
  def inspect_state(
325
357
  entity: str = typer.Argument(None, help="Entity kind (e.g. lead, order)"),
326
358
  app_str: str = typer.Option(None, "--app"),
327
- json_mode: bool = typer.Option(False, "--json", help="Output machine-readable JSON"),
359
+ json_mode: bool = typer.Option(
360
+ False, "--json", help="Output machine-readable JSON"
361
+ ),
328
362
  ):
329
363
  """Observable state changes recorded on executions."""
330
364
  from voodoo.runtime import engine
@@ -365,7 +399,9 @@ def inspect_state(
365
399
  @inspect_app.command("capabilities")
366
400
  def inspect_capabilities(
367
401
  app_str: str = typer.Option(None, "--app"),
368
- json_mode: bool = typer.Option(False, "--json", help="Output machine-readable JSON"),
402
+ json_mode: bool = typer.Option(
403
+ False, "--json", help="Output machine-readable JSON"
404
+ ),
369
405
  ):
370
406
  """Registered capability templates and tool permission requirements."""
371
407
  from voodoo.runtime import engine
@@ -387,10 +423,12 @@ def inspect_capabilities(
387
423
  if _is_json(json_mode):
388
424
  terminal.json_output({"capabilities": cap_data, "tool_permissions": tool_perms})
389
425
  return
390
- terminal.status_block([
391
- ("capabilities", str(len(cap_data["capabilities"]))),
392
- ("approval req.", str(len(cap_data["approval_required"]))),
393
- ])
426
+ terminal.status_block(
427
+ [
428
+ ("capabilities", str(len(cap_data["capabilities"]))),
429
+ ("approval req.", str(len(cap_data["approval_required"]))),
430
+ ]
431
+ )
394
432
  if cap_data["capabilities"]:
395
433
  terminal.label_value("names", ", ".join(cap_data["capabilities"]))
396
434
  if tool_perms:
@@ -405,7 +443,9 @@ def inspect_capabilities(
405
443
  @inspect_app.command("mesh")
406
444
  def inspect_mesh(
407
445
  app_str: str = typer.Option(None, "--app"),
408
- json_mode: bool = typer.Option(False, "--json", help="Output machine-readable JSON"),
446
+ json_mode: bool = typer.Option(
447
+ False, "--json", help="Output machine-readable JSON"
448
+ ),
409
449
  ):
410
450
  """Mesh surface: exposed functions, event handlers, active nodes."""
411
451
  from voodoo.mesh import mesh
@@ -420,11 +460,13 @@ def inspect_mesh(
420
460
  if _is_json(json_mode):
421
461
  terminal.json_output(data)
422
462
  return
423
- terminal.status_block([
424
- ("exposed", str(len(data["exposed"]))),
425
- ("event types", str(len(data["handlers"]))),
426
- ("active nodes", str(data["active_nodes"])),
427
- ])
463
+ terminal.status_block(
464
+ [
465
+ ("exposed", str(len(data["exposed"]))),
466
+ ("event types", str(len(data["handlers"]))),
467
+ ("active nodes", str(data["active_nodes"])),
468
+ ]
469
+ )
428
470
  if data["handlers"]:
429
471
  terminal.blank()
430
472
  table = _table(["event", "handlers"])
@@ -436,9 +478,13 @@ def inspect_mesh(
436
478
 
437
479
  @inspect_app.command("approvals")
438
480
  def inspect_approvals(
439
- pending_only: bool = typer.Option(False, "--pending", help="Only show pending approvals"),
481
+ pending_only: bool = typer.Option(
482
+ False, "--pending", help="Only show pending approvals"
483
+ ),
440
484
  app_str: str = typer.Option(None, "--app"),
441
- json_mode: bool = typer.Option(False, "--json", help="Output machine-readable JSON"),
485
+ json_mode: bool = typer.Option(
486
+ False, "--json", help="Output machine-readable JSON"
487
+ ),
442
488
  ):
443
489
  """Human approvals — pending and decided, with capability + requester."""
444
490
  from voodoo.runtime import engine
@@ -9,7 +9,6 @@ from rich.progress import Progress, SpinnerColumn, TextColumn
9
9
 
10
10
  from voodoo.cli import terminal
11
11
 
12
-
13
12
  # ---------------------------------------------------------------------------
14
13
  # Offline scaffold — used when no template repository is available.
15
14
  # Showcases Voodoo CSS (the default adapter), folder-based routing, and the
@@ -18,15 +18,21 @@ from voodoo.cli import terminal
18
18
 
19
19
  def recover(
20
20
  app_str: str = typer.Option(None, "--app", help="App instance (e.g. main:app)"),
21
- store_path: str = typer.Option(None, "--store", help="Path to the JSONL execution store"),
22
- json_mode: bool = typer.Option(False, "--json", help="Output machine-readable JSON"),
21
+ store_path: str = typer.Option(
22
+ None, "--store", help="Path to the JSONL execution store"
23
+ ),
24
+ json_mode: bool = typer.Option(
25
+ False, "--json", help="Output machine-readable JSON"
26
+ ),
23
27
  ):
24
28
  """Reload unfinished executions from the store into the engine."""
25
29
  from voodoo.runtime.engine import engine as runtime_engine
26
30
  from voodoo.runtime.persistence import JSONFileExecutionStore
27
31
 
28
32
  if store_path is None:
29
- store_path = os.environ.get("VOODOO_EXECUTION_STORE", ".voodoo/executions.jsonl")
33
+ store_path = os.environ.get(
34
+ "VOODOO_EXECUTION_STORE", ".voodoo/executions.jsonl"
35
+ )
30
36
 
31
37
  if app_str is not None:
32
38
  # Import the app first: it may attach its own store / register
@@ -57,7 +63,9 @@ def recover(
57
63
  terminal.status_block([("store", store_path), ("recovered", str(len(recovered)))])
58
64
  from rich.table import Table
59
65
 
60
- table = Table(show_header=True, header_style="dim", border_style="#262626", pad_edge=False)
66
+ table = Table(
67
+ show_header=True, header_style="dim", border_style="#262626", pad_edge=False
68
+ )
61
69
  for c in ("id", "intent", "status", "actor"):
62
70
  table.add_column(c)
63
71
  for ex in recovered:
@@ -0,0 +1,114 @@
1
+ """voodoo tasks — inspect and manage durable background tasks."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import typer
6
+
7
+ from voodoo.cli import terminal
8
+
9
+ tasks_app = typer.Typer(
10
+ name="tasks",
11
+ help="Inspect and manage durable background tasks.",
12
+ no_args_is_help=True,
13
+ rich_markup_mode="rich",
14
+ )
15
+
16
+
17
+ def _get_queue():
18
+ from voodoo.workers.queue import _get_queue
19
+
20
+ return _get_queue()
21
+
22
+
23
+ @tasks_app.command("list")
24
+ def list_tasks(
25
+ status: str = typer.Option(None, "--status", help="Filter by status"),
26
+ task_type: str = typer.Option(None, "--type", help="Filter by task type"),
27
+ json_mode: bool = typer.Option(False, "--json", help="Machine-readable JSON"),
28
+ ):
29
+ """List recent tasks."""
30
+ import asyncio
31
+
32
+ from voodoo.storage.queue import TaskStatus
33
+
34
+ async def run():
35
+ q = await _get_queue()
36
+ stat = TaskStatus(status) if status else None
37
+ tasks = await q.list(status=stat, task_type=task_type)
38
+ stats = await q.stats()
39
+ if json_mode or terminal.is_json_mode():
40
+ terminal.json_output(
41
+ {"stats": stats.describe(), "tasks": [t.describe() for t in tasks]}
42
+ )
43
+ return
44
+ terminal.wordmark()
45
+ terminal.blank()
46
+ terminal.status_block(
47
+ [
48
+ ("provider", q.capabilities().provider),
49
+ ("total", str(stats.total)),
50
+ ("pending", str(stats.pending)),
51
+ ("running", str(stats.running)),
52
+ ("retrying", str(stats.retrying)),
53
+ ("completed", str(stats.completed)),
54
+ ("failed", str(stats.failed)),
55
+ ]
56
+ )
57
+ terminal.blank()
58
+ if not tasks:
59
+ terminal.muted("no tasks found")
60
+ return
61
+ from rich.table import Table
62
+
63
+ table = Table(
64
+ show_header=True, header_style="dim", border_style="#262626", pad_edge=False
65
+ )
66
+ for c in ("id", "type", "status", "priority", "attempts", "created_at"):
67
+ table.add_column(c)
68
+ for t in tasks:
69
+ table.add_row(
70
+ str(t.id),
71
+ t.type,
72
+ t.status.value,
73
+ str(t.priority),
74
+ f"{t.attempts}/{t.max_attempts}",
75
+ t.created_at.strftime("%H:%M:%S") if t.created_at else "-",
76
+ )
77
+ terminal.console.print(table)
78
+ terminal.blank()
79
+
80
+ asyncio.run(run())
81
+
82
+
83
+ @tasks_app.command("retry")
84
+ def retry_task(
85
+ task_id: int = typer.Argument(..., help="Task ID to retry"),
86
+ json_mode: bool = typer.Option(False, "--json", help="Machine-readable JSON"),
87
+ ):
88
+ """Manually retry a failed task."""
89
+ import asyncio
90
+
91
+ async def run():
92
+ q = await _get_queue()
93
+ result = await q.retry(task_id)
94
+ if result is None:
95
+ if json_mode or terminal.is_json_mode():
96
+ terminal.json_output(
97
+ {"error": f"task {task_id} not found or not failed"}
98
+ )
99
+ return
100
+ terminal.wordmark()
101
+ terminal.blank()
102
+ terminal.muted(f"task {task_id} not found or not failed")
103
+ return
104
+ if json_mode or terminal.is_json_mode():
105
+ terminal.json_output(result.describe())
106
+ return
107
+ terminal.wordmark()
108
+ terminal.blank()
109
+ terminal.status_block(
110
+ [("task", str(result.id)), ("status", result.status.value)]
111
+ )
112
+ terminal.blank()
113
+
114
+ asyncio.run(run())
@@ -151,7 +151,9 @@ def json_output(data: Any) -> None:
151
151
  """
152
152
  import json
153
153
 
154
- console.print(json.dumps(data, indent=2, default=str), highlight=False, soft_wrap=True)
154
+ console.print(
155
+ json.dumps(data, indent=2, default=str), highlight=False, soft_wrap=True
156
+ )
155
157
 
156
158
 
157
159
  def is_json_mode() -> bool:
@@ -6,10 +6,13 @@ This package re-exports the names that previously lived in the flat
6
6
 
7
7
  Storage backend boundary
8
8
  -------------------------
9
- The default backend is SQLite (via :mod:`aiosqlite`). The functions
10
- ``init_db`` and ``get_db`` together with the module-level ``_db_connection``
11
- form the seam a future PostgreSQL adapter would replace; everything above
12
- them (``BaseModel``, ``Model`` and the hooks) is backend-agnostic.
9
+ The default backend is SQLite (via :mod:`aiosqlite`) managed by the
10
+ ``VoodooDatabase`` adapter in :mod:`voodoo.storage.database` — it owns
11
+ connection lifecycle, WAL pragmas and the ``schema_migrations`` ledger.
12
+ ``init_db`` publishes the raw aiosqlite connection as
13
+ ``_db_connection`` / ``get_db()``; everything above it (``BaseModel``,
14
+ ``Model`` and the hooks) is backend-agnostic. A future PostgreSQL
15
+ adapter replaces the adapter, not this package.
13
16
  """
14
17
 
15
18
  from voodoo.data.base import (
@@ -1,30 +1,54 @@
1
1
  import asyncio
2
- import os
3
2
  from collections.abc import Callable
4
3
  from typing import Any, get_type_hints
5
4
 
6
- import aiosqlite
5
+ from voodoo.storage.database import Migration, SQLiteDatabase, VoodooDatabase
7
6
 
8
7
  _db_connection = None
8
+ _database: SQLiteDatabase | None = None
9
9
  _triggers: dict[str, dict[str, list[Callable]]] = {}
10
10
  _rls_policies: dict[str, Callable] = {}
11
11
 
12
12
 
13
+ async def _ensure_user_tables(db: VoodooDatabase) -> None:
14
+ """Create tables for every registered user model (idempotent DDL)."""
15
+ for model in _models:
16
+ await model._create_table()
17
+
18
+
19
+ # Migration 0001: the user-model baseline. Runs once to enter the ledger,
20
+ # then re-runs its idempotent DDL on every ``init_db`` so models imported
21
+ # after the baseline still get their tables (preserves pre-migration
22
+ # behavior exactly).
23
+ USER_MODEL_BASELINE = Migration(
24
+ version=1,
25
+ name="user_model_baseline",
26
+ fn=_ensure_user_tables,
27
+ rerun=True,
28
+ )
29
+
30
+
13
31
  async def init_db(db_path: str = None):
32
+ """Open (and migrate) the SQLite database backing the data layer.
33
+
34
+ The raw :class:`aiosqlite.Connection` remains the value of
35
+ ``_db_connection`` / ``get_db()`` so all existing callers keep working;
36
+ schema management now flows through the ``SQLiteDatabase`` adapter and
37
+ its migration ledger.
38
+ """
14
39
  if db_path is None:
15
40
  from voodoo.config import config
16
41
 
17
42
  db_path = config.db_path
18
43
 
19
- db_dir = os.path.dirname(db_path)
20
- if db_dir:
21
- os.makedirs(db_dir, exist_ok=True)
22
-
23
- global _db_connection
24
- _db_connection = await aiosqlite.connect(db_path)
25
- _db_connection.row_factory = aiosqlite.Row
26
- for model in _models:
27
- await model._create_table()
44
+ global _db_connection, _database
45
+ database = SQLiteDatabase(db_path, migrations=(USER_MODEL_BASELINE,))
46
+ await database.connect()
47
+ _db_connection = database.connection
48
+ _database = database
49
+ # Migrate after the connection is published so rerunnable migration
50
+ # functions can use ``get_db()``.
51
+ await database.migrate()
28
52
 
29
53
 
30
54
  async def get_db():
@@ -40,12 +64,13 @@ async def close_db():
40
64
  unclosed connection would keep the interpreter alive at shutdown.
41
65
  No-op when no connection is open (keeps startup lazy).
42
66
  """
43
- global _db_connection
67
+ global _db_connection, _database
44
68
  if _db_connection is not None:
45
69
  try:
46
70
  await _db_connection.close()
47
71
  finally:
48
72
  _db_connection = None
73
+ _database = None
49
74
 
50
75
 
51
76
  def _get_table_name(cls_or_obj: Any) -> str:
@@ -8,13 +8,11 @@ storage layer and the ``on_insert`` / ``on_update`` hook system from
8
8
  Storage backend boundary
9
9
  -------------------------
10
10
  Everything in this module talks to the database through ``get_db()``
11
- and builds portable ANSI-SQL strings. Swapping SQLite for PostgreSQL
12
- (or another backend) only requires replacing the connection function
13
- and the DDL generated by ``BaseModel._create_table``; the facade stays
14
- the same. Migrations are an explicit extension point: today the only
15
- DDL emitted is ``CREATE TABLE IF NOT EXISTS`` (create-if-absent). A
16
- future migration runner can hook into ``init_db`` without changing any
17
- call-site here.
11
+ and builds portable ANSI-SQL strings. Schema management flows through
12
+ the ``VoodooDatabase`` adapter in :mod:`voodoo.storage.database`
13
+ (SQLite default); the user-model DDL is migration 0001 in its ledger.
14
+ Swapping backends means providing another adapter — no call-site here
15
+ changes.
18
16
  """
19
17
 
20
18
  from typing import Any, get_type_hints
@@ -0,0 +1,12 @@
1
+ """Compatibility alias — the durable queue lives in ``voodoo.workers.queue``.
2
+
3
+ This shim replaces itself with the real module in ``sys.modules`` so that
4
+ ``voodoo.queue is voodoo.workers.queue`` and mutable globals (``_workers``,
5
+ ``_worker_tasks``) are always current.
6
+ """
7
+
8
+ import sys
9
+
10
+ from voodoo.workers import queue # noqa: F401
11
+
12
+ sys.modules[__name__] = queue
@@ -0,0 +1,15 @@
1
+ """Voodoo storage package.
2
+
3
+ Two concerns live here:
4
+
5
+ - ``voodoo.storage.manager`` — the legacy ``StorageManager`` facade over
6
+ local-filesystem / S3-compatible uploads (import-compatible re-export of
7
+ the former flat ``voodoo/storage.py`` module).
8
+ - ``voodoo.storage.database`` — the ``VoodooDatabase`` capability adapters
9
+ (SQLite default, PostgreSQL later) that own all durable schema through
10
+ the migration runner.
11
+ """
12
+
13
+ from voodoo.storage.manager import StorageManager, storage
14
+
15
+ __all__ = ["StorageManager", "storage"]
@@ -0,0 +1,29 @@
1
+ """Voodoo database adapters (spec §11).
2
+
3
+ SQLite is the embedded default; PostgreSQL arrives behind the same
4
+ ``VoodooDatabase`` protocol as an optional extra. Migrations form one global
5
+ version namespace: 1 = user-model baseline (registered by ``voodoo.data``),
6
+ 2+ reserved by the framework, 100+ available to applications.
7
+ """
8
+
9
+ from voodoo.storage.database.interfaces import (
10
+ DatabaseCapabilities,
11
+ Migration,
12
+ VoodooDatabase,
13
+ )
14
+ from voodoo.storage.database.sqlite import (
15
+ FRAMEWORK_MIGRATIONS,
16
+ LEDGER_TABLE,
17
+ SQLiteDatabase,
18
+ register_framework_migration,
19
+ )
20
+
21
+ __all__ = [
22
+ "DatabaseCapabilities",
23
+ "FRAMEWORK_MIGRATIONS",
24
+ "LEDGER_TABLE",
25
+ "Migration",
26
+ "SQLiteDatabase",
27
+ "VoodooDatabase",
28
+ "register_framework_migration",
29
+ ]