halfabyte-synapse 0.2.0__tar.gz → 0.3.7__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 (194) hide show
  1. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/PKG-INFO +48 -3
  2. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/README.md +44 -1
  3. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/halfabyte_synapse.egg-info/PKG-INFO +48 -3
  4. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/halfabyte_synapse.egg-info/SOURCES.txt +36 -0
  5. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/halfabyte_synapse.egg-info/requires.txt +4 -1
  6. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/pyproject.toml +4 -3
  7. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/__init__.py +3 -1
  8. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/alerts.py +16 -1
  9. halfabyte_synapse-0.3.7/synapse/api/cloud_routes.py +211 -0
  10. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/api/main.py +216 -8
  11. halfabyte_synapse-0.3.7/synapse/api/site/_redirects +1 -0
  12. halfabyte_synapse-0.3.7/synapse/api/site/assets/Graph3D-DVcdUeya.js +3 -0
  13. halfabyte_synapse-0.3.7/synapse/api/site/assets/HeroSilicon-ktono7T_.js +52 -0
  14. halfabyte_synapse-0.3.7/synapse/api/site/assets/LandingV2-3C75bSR_.js +38 -0
  15. halfabyte_synapse-0.3.7/synapse/api/site/assets/graph-B5pWpvAm.js +31 -0
  16. halfabyte_synapse-0.3.7/synapse/api/site/assets/graph-BnuhLJ6X.css +1 -0
  17. halfabyte_synapse-0.3.7/synapse/api/site/assets/index-B8DPmNS7.js +490 -0
  18. halfabyte_synapse-0.3.7/synapse/api/site/assets/index-XuVVhel1.css +1 -0
  19. halfabyte_synapse-0.3.7/synapse/api/site/assets/motion-DDvmupEl.js +45 -0
  20. halfabyte_synapse-0.3.7/synapse/api/site/assets/r3f-BtQsV2-H.js +513 -0
  21. halfabyte_synapse-0.3.7/synapse/api/site/assets/silicon-DIMlxluB.js +24 -0
  22. halfabyte_synapse-0.3.7/synapse/api/site/assets/three-Dc80Zr6c.js +3984 -0
  23. halfabyte_synapse-0.3.7/synapse/api/site/index.html +20 -0
  24. halfabyte_synapse-0.3.7/synapse/api/site/samples/blackbox_math_run.json +1885 -0
  25. halfabyte_synapse-0.3.7/synapse/api/site/samples/langfuse_support_agent.json +19 -0
  26. halfabyte_synapse-0.3.7/synapse/api/site/samples/otel_refund_agent.json +37 -0
  27. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/bot.py +13 -1
  28. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/chatops.py +43 -1
  29. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/cli.py +67 -2
  30. halfabyte_synapse-0.3.7/synapse/cloud.py +715 -0
  31. halfabyte_synapse-0.3.7/synapse/connect.py +365 -0
  32. halfabyte_synapse-0.3.7/synapse/correctness/evidence.py +143 -0
  33. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/demo.py +8 -3
  34. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/frontend/adapters/langgraph.py +17 -44
  35. halfabyte_synapse-0.3.7/synapse/frontend/adapters/state_trace.py +91 -0
  36. halfabyte_synapse-0.3.7/synapse/mail.py +143 -0
  37. halfabyte_synapse-0.3.7/synapse/mcp/annotations.py +41 -0
  38. halfabyte_synapse-0.3.7/synapse/mcp/cli_tools.py +169 -0
  39. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/mcp/registry.py +4 -1
  40. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/mcp/tools.py +40 -0
  41. halfabyte_synapse-0.3.7/synapse/openai.py +33 -0
  42. halfabyte_synapse-0.3.7/synapse/pathsetup.py +67 -0
  43. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/runtime/executor.py +31 -3
  44. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/runtime/scheduler.py +7 -0
  45. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/search.py +9 -1
  46. halfabyte_synapse-0.3.7/synapse/skills/__init__.py +113 -0
  47. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/skills/synapse/SKILL.md +17 -0
  48. halfabyte_synapse-0.3.7/synapse/slack.py +477 -0
  49. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_api.py +1 -1
  50. halfabyte_synapse-0.3.7/tests/test_cloud.py +498 -0
  51. halfabyte_synapse-0.3.7/tests/test_connect.py +196 -0
  52. halfabyte_synapse-0.3.7/tests/test_evidence.py +70 -0
  53. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_langgraph_adapter.py +28 -0
  54. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_mcp.py +20 -0
  55. halfabyte_synapse-0.3.7/tests/test_mcp_annotations.py +53 -0
  56. halfabyte_synapse-0.3.7/tests/test_pathsetup.py +34 -0
  57. halfabyte_synapse-0.3.7/tests/test_plugin.py +116 -0
  58. halfabyte_synapse-0.3.7/tests/test_real_runs.py +66 -0
  59. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_search.py +1 -1
  60. halfabyte_synapse-0.3.7/tests/test_slack.py +332 -0
  61. halfabyte_synapse-0.3.7/tests/test_state_trace.py +65 -0
  62. halfabyte_synapse-0.2.0/synapse/skills/__init__.py +0 -32
  63. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/LICENSE +0 -0
  64. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/halfabyte_synapse.egg-info/dependency_links.txt +0 -0
  65. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/halfabyte_synapse.egg-info/entry_points.txt +0 -0
  66. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/halfabyte_synapse.egg-info/top_level.txt +0 -0
  67. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/setup.cfg +0 -0
  68. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/__main__.py +0 -0
  69. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/api/__init__.py +0 -0
  70. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/capsule.py +0 -0
  71. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/chaos_panel.py +0 -0
  72. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/ci.py +0 -0
  73. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/core/__init__.py +0 -0
  74. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/core/agents.py +0 -0
  75. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/core/effects.py +0 -0
  76. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/core/hashing.py +0 -0
  77. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/core/lattice.py +0 -0
  78. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/core/retention.py +0 -0
  79. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/core/sir.py +0 -0
  80. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/core/store.py +0 -0
  81. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/core/store_mongo.py +0 -0
  82. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/correctness/__init__.py +0 -0
  83. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/correctness/audit.py +0 -0
  84. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/correctness/chaos.py +0 -0
  85. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/correctness/equivalence.py +0 -0
  86. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/correctness/firewall.py +0 -0
  87. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/correctness/net_rule.py +0 -0
  88. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/correctness/preconditions.py +0 -0
  89. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/correctness/receipt.py +0 -0
  90. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/correctness/runner.py +0 -0
  91. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/correctness/splits.py +0 -0
  92. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/correctness/verify.py +0 -0
  93. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/data/models.yaml +0 -0
  94. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/doctor.py +0 -0
  95. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/export.py +0 -0
  96. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/frontend/__init__.py +0 -0
  97. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/frontend/adapters/__init__.py +0 -0
  98. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/frontend/adapters/adk.py +0 -0
  99. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/frontend/adapters/crewai.py +0 -0
  100. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/frontend/dsl.py +0 -0
  101. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/frontend/importers/__init__.py +0 -0
  102. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/frontend/importers/_common.py +0 -0
  103. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/frontend/importers/blackbox.py +0 -0
  104. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/frontend/importers/langfuse.py +0 -0
  105. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/frontend/importers/otel.py +0 -0
  106. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/frontend/lift.py +0 -0
  107. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/insights.py +0 -0
  108. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/mcp/__init__.py +0 -0
  109. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/mcp/__main__.py +0 -0
  110. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/mcp/prompts.py +0 -0
  111. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/mcp/protocol.py +0 -0
  112. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/mcp/resources.py +0 -0
  113. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/mcp/server.py +0 -0
  114. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/menus.py +0 -0
  115. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/monitor.py +0 -0
  116. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/passes/__init__.py +0 -0
  117. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/passes/base.py +0 -0
  118. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/passes/batch.py +0 -0
  119. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/passes/constfold.py +0 -0
  120. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/passes/context_prune.py +0 -0
  121. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/passes/context_trim.py +0 -0
  122. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/passes/cse.py +0 -0
  123. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/passes/dce.py +0 -0
  124. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/passes/downgrade.py +0 -0
  125. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/passes/early_exit.py +0 -0
  126. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/passes/loop_cutoff.py +0 -0
  127. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/passes/manager.py +0 -0
  128. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/passes/memoize.py +0 -0
  129. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/passes/parallelize.py +0 -0
  130. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/passes/speculate.py +0 -0
  131. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/profile/__init__.py +0 -0
  132. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/profile/collect.py +0 -0
  133. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/profile/costmodel.py +0 -0
  134. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/profile/critical_path.py +0 -0
  135. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/profile/ledger.py +0 -0
  136. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/profile/presentation.py +0 -0
  137. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/profile/pricing.py +0 -0
  138. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/profile/waste.py +0 -0
  139. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/proxy.py +0 -0
  140. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/quarantine.py +0 -0
  141. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/runtime/__init__.py +0 -0
  142. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/runtime/cache.py +0 -0
  143. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/runtime/meter.py +0 -0
  144. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/runtime/providers.py +0 -0
  145. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/runtime/rate_gate.py +0 -0
  146. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/runtime/recording.py +0 -0
  147. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/runtime/semantic.py +0 -0
  148. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/synapse/skills/synapse/references/effects.md +0 -0
  149. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_ablation.py +0 -0
  150. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_adk_adapter.py +0 -0
  151. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_alerts.py +0 -0
  152. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_audit_gate.py +0 -0
  153. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_bench_tools.py +0 -0
  154. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_chaos.py +0 -0
  155. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_chatops.py +0 -0
  156. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_checkers.py +0 -0
  157. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_cli.py +0 -0
  158. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_contracts.py +0 -0
  159. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_corpus.py +0 -0
  160. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_correctness.py +0 -0
  161. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_cost_resources.py +0 -0
  162. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_crewai_adapter.py +0 -0
  163. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_dsl.py +0 -0
  164. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_effects.py +0 -0
  165. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_features.py +0 -0
  166. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_hashing.py +0 -0
  167. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_importers.py +0 -0
  168. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_lattice.py +0 -0
  169. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_ledger.py +0 -0
  170. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_lift.py +0 -0
  171. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_manager.py +0 -0
  172. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_monitor_menus.py +0 -0
  173. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_net_rule.py +0 -0
  174. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_passes.py +0 -0
  175. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_policy_study.py +0 -0
  176. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_profile.py +0 -0
  177. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_providers.py +0 -0
  178. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_recording.py +0 -0
  179. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_report.py +0 -0
  180. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_research_fanout.py +0 -0
  181. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_review_fixes.py +0 -0
  182. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_runner.py +0 -0
  183. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_runtime.py +0 -0
  184. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_sir.py +0 -0
  185. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_skill.py +0 -0
  186. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_speculation.py +0 -0
  187. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_splits.py +0 -0
  188. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_store_backends.py +0 -0
  189. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_suite.py +0 -0
  190. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_usp.py +0 -0
  191. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_usp_batch.py +0 -0
  192. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_verdict_labels.py +0 -0
  193. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_workflow_graphs.py +0 -0
  194. {halfabyte_synapse-0.2.0 → halfabyte_synapse-0.3.7}/tests/test_workflows.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: halfabyte-synapse
3
- Version: 0.2.0
3
+ Version: 0.3.7
4
4
  Summary: A compiler and runtime for AI agent workflows: faster and cheaper runs, with side effects protected and every change checked by a firewall.
5
5
  Author: Aryan Lomte, Radhesh, Aditya, Advay Chavan
6
6
  Maintainer: Half a Byte
@@ -46,13 +46,15 @@ Provides-Extra: langgraph
46
46
  Requires-Dist: langgraph<1.1,>=1.0; extra == "langgraph"
47
47
  Provides-Extra: adk
48
48
  Requires-Dist: google-adk<2.14,>=2.13; extra == "adk"
49
+ Provides-Extra: slack
50
+ Requires-Dist: slack_sdk<4,>=3.27; extra == "slack"
49
51
  Provides-Extra: dev
50
52
  Requires-Dist: pytest>=8; extra == "dev"
51
53
  Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
52
54
  Requires-Dist: ruff>=0.5; extra == "dev"
53
55
  Requires-Dist: mongomock>=4.1; extra == "dev"
54
56
  Provides-Extra: all
55
- Requires-Dist: halfabyte-synapse[bench,cloud,ml,ui]; extra == "all"
57
+ Requires-Dist: halfabyte-synapse[bench,cloud,ml,slack,ui]; extra == "all"
56
58
  Dynamic: license-file
57
59
 
58
60
  # Synapse
@@ -64,7 +66,18 @@ pip install halfabyte-synapse
64
66
  synapse demo
65
67
  ```
66
68
 
67
- No API key, GPU or model needed: the demo runs on a built-in fake provider. The package is `halfabyte-synapse`; the command and the import are both `synapse`.
69
+ That runs a built-in sample on a fake provider, with no key needed. **To use it on your own agent, change one line:**
70
+
71
+ ```python
72
+ import synapse
73
+ from openai import OpenAI
74
+
75
+ client = synapse.wrap(OpenAI()) # your calls behave as before
76
+ # ...run your agent...
77
+ print(client.synapse_report()) # real calls, tokens, repeats and waste
78
+ ```
79
+
80
+ Nothing leaves your process. Or point `base_url` at `synapse proxy`, or at a hosted workspace. The package is `halfabyte-synapse`; the command and the import are both `synapse`.
68
81
 
69
82
  Built by team **Half a Byte** for the BitNBuild 2026 "Around the World" final round (GDG On Campus, Fr. Conceicao Rodrigues College of Engineering), AI/ML problem statement 2.
70
83
 
@@ -186,6 +199,38 @@ def my_agent(question: str):
186
199
 
187
200
  Multi-agent workflows wrap steps in `with agent("researcher"):`, and they're still one graph.
188
201
 
202
+ ### If `synapse` isn't found
203
+
204
+ pip can't change your PATH. Run this once, then open a new terminal:
205
+
206
+ ```
207
+ python -m synapse path # on Windows: py -m synapse path
208
+ ```
209
+
210
+ ### Plugin for Claude Code, OpenClaw and Hermes
211
+
212
+ ```
213
+ synapse plugin --out synapse-plugin
214
+ ```
215
+
216
+ That writes the skill, slash commands and MCP server into one folder, and prints the install command for each agent.
217
+
218
+ ### Two ways to run it
219
+
220
+ | | **Synapse Local** | **Synapse Cloud** |
221
+ |---|---|---|
222
+ | Start | `pip install "halfabyte-synapse[ui]"`, `synapse ui` | Sign up at the site, make a project, copy its key |
223
+ | Your code | `synapse.wrap(OpenAI())` | `synapse.wrap(OpenAI(), api_key="syn_k_...")` |
224
+ | Data lives | On your machine, in SQLite | In our MongoDB Atlas: measurements only |
225
+ | Account | None | Email, password and mobile number |
226
+ | Only here | Nothing leaves your machine | Team roles, invites, read-only links, history across machines, daily token budgets, WhatsApp, mail and Slack alerts, activity log |
227
+
228
+ Prompts and answers never leave your process with `wrap`. A report holds model names, timings, token counts, a one-way hash of each request and the workflow's shape.
229
+
230
+ ### Your key, your model
231
+
232
+ Hosted, the Connect screen gives you a private address per provider, such as `https://HOST/w/TOKEN/groq/v1`. Your key goes through to your provider and is never stored. Prompts stay in memory for up to 24 hours, and never reach disk. It can also run the concurrency audit on your own model.
233
+
189
234
  ### Use it with no code changes
190
235
 
191
236
  ```bash
@@ -7,7 +7,18 @@ pip install halfabyte-synapse
7
7
  synapse demo
8
8
  ```
9
9
 
10
- No API key, GPU or model needed: the demo runs on a built-in fake provider. The package is `halfabyte-synapse`; the command and the import are both `synapse`.
10
+ That runs a built-in sample on a fake provider, with no key needed. **To use it on your own agent, change one line:**
11
+
12
+ ```python
13
+ import synapse
14
+ from openai import OpenAI
15
+
16
+ client = synapse.wrap(OpenAI()) # your calls behave as before
17
+ # ...run your agent...
18
+ print(client.synapse_report()) # real calls, tokens, repeats and waste
19
+ ```
20
+
21
+ Nothing leaves your process. Or point `base_url` at `synapse proxy`, or at a hosted workspace. The package is `halfabyte-synapse`; the command and the import are both `synapse`.
11
22
 
12
23
  Built by team **Half a Byte** for the BitNBuild 2026 "Around the World" final round (GDG On Campus, Fr. Conceicao Rodrigues College of Engineering), AI/ML problem statement 2.
13
24
 
@@ -129,6 +140,38 @@ def my_agent(question: str):
129
140
 
130
141
  Multi-agent workflows wrap steps in `with agent("researcher"):`, and they're still one graph.
131
142
 
143
+ ### If `synapse` isn't found
144
+
145
+ pip can't change your PATH. Run this once, then open a new terminal:
146
+
147
+ ```
148
+ python -m synapse path # on Windows: py -m synapse path
149
+ ```
150
+
151
+ ### Plugin for Claude Code, OpenClaw and Hermes
152
+
153
+ ```
154
+ synapse plugin --out synapse-plugin
155
+ ```
156
+
157
+ That writes the skill, slash commands and MCP server into one folder, and prints the install command for each agent.
158
+
159
+ ### Two ways to run it
160
+
161
+ | | **Synapse Local** | **Synapse Cloud** |
162
+ |---|---|---|
163
+ | Start | `pip install "halfabyte-synapse[ui]"`, `synapse ui` | Sign up at the site, make a project, copy its key |
164
+ | Your code | `synapse.wrap(OpenAI())` | `synapse.wrap(OpenAI(), api_key="syn_k_...")` |
165
+ | Data lives | On your machine, in SQLite | In our MongoDB Atlas: measurements only |
166
+ | Account | None | Email, password and mobile number |
167
+ | Only here | Nothing leaves your machine | Team roles, invites, read-only links, history across machines, daily token budgets, WhatsApp, mail and Slack alerts, activity log |
168
+
169
+ Prompts and answers never leave your process with `wrap`. A report holds model names, timings, token counts, a one-way hash of each request and the workflow's shape.
170
+
171
+ ### Your key, your model
172
+
173
+ Hosted, the Connect screen gives you a private address per provider, such as `https://HOST/w/TOKEN/groq/v1`. Your key goes through to your provider and is never stored. Prompts stay in memory for up to 24 hours, and never reach disk. It can also run the concurrency audit on your own model.
174
+
132
175
  ### Use it with no code changes
133
176
 
134
177
  ```bash
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: halfabyte-synapse
3
- Version: 0.2.0
3
+ Version: 0.3.7
4
4
  Summary: A compiler and runtime for AI agent workflows: faster and cheaper runs, with side effects protected and every change checked by a firewall.
5
5
  Author: Aryan Lomte, Radhesh, Aditya, Advay Chavan
6
6
  Maintainer: Half a Byte
@@ -46,13 +46,15 @@ Provides-Extra: langgraph
46
46
  Requires-Dist: langgraph<1.1,>=1.0; extra == "langgraph"
47
47
  Provides-Extra: adk
48
48
  Requires-Dist: google-adk<2.14,>=2.13; extra == "adk"
49
+ Provides-Extra: slack
50
+ Requires-Dist: slack_sdk<4,>=3.27; extra == "slack"
49
51
  Provides-Extra: dev
50
52
  Requires-Dist: pytest>=8; extra == "dev"
51
53
  Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
52
54
  Requires-Dist: ruff>=0.5; extra == "dev"
53
55
  Requires-Dist: mongomock>=4.1; extra == "dev"
54
56
  Provides-Extra: all
55
- Requires-Dist: halfabyte-synapse[bench,cloud,ml,ui]; extra == "all"
57
+ Requires-Dist: halfabyte-synapse[bench,cloud,ml,slack,ui]; extra == "all"
56
58
  Dynamic: license-file
57
59
 
58
60
  # Synapse
@@ -64,7 +66,18 @@ pip install halfabyte-synapse
64
66
  synapse demo
65
67
  ```
66
68
 
67
- No API key, GPU or model needed: the demo runs on a built-in fake provider. The package is `halfabyte-synapse`; the command and the import are both `synapse`.
69
+ That runs a built-in sample on a fake provider, with no key needed. **To use it on your own agent, change one line:**
70
+
71
+ ```python
72
+ import synapse
73
+ from openai import OpenAI
74
+
75
+ client = synapse.wrap(OpenAI()) # your calls behave as before
76
+ # ...run your agent...
77
+ print(client.synapse_report()) # real calls, tokens, repeats and waste
78
+ ```
79
+
80
+ Nothing leaves your process. Or point `base_url` at `synapse proxy`, or at a hosted workspace. The package is `halfabyte-synapse`; the command and the import are both `synapse`.
68
81
 
69
82
  Built by team **Half a Byte** for the BitNBuild 2026 "Around the World" final round (GDG On Campus, Fr. Conceicao Rodrigues College of Engineering), AI/ML problem statement 2.
70
83
 
@@ -186,6 +199,38 @@ def my_agent(question: str):
186
199
 
187
200
  Multi-agent workflows wrap steps in `with agent("researcher"):`, and they're still one graph.
188
201
 
202
+ ### If `synapse` isn't found
203
+
204
+ pip can't change your PATH. Run this once, then open a new terminal:
205
+
206
+ ```
207
+ python -m synapse path # on Windows: py -m synapse path
208
+ ```
209
+
210
+ ### Plugin for Claude Code, OpenClaw and Hermes
211
+
212
+ ```
213
+ synapse plugin --out synapse-plugin
214
+ ```
215
+
216
+ That writes the skill, slash commands and MCP server into one folder, and prints the install command for each agent.
217
+
218
+ ### Two ways to run it
219
+
220
+ | | **Synapse Local** | **Synapse Cloud** |
221
+ |---|---|---|
222
+ | Start | `pip install "halfabyte-synapse[ui]"`, `synapse ui` | Sign up at the site, make a project, copy its key |
223
+ | Your code | `synapse.wrap(OpenAI())` | `synapse.wrap(OpenAI(), api_key="syn_k_...")` |
224
+ | Data lives | On your machine, in SQLite | In our MongoDB Atlas: measurements only |
225
+ | Account | None | Email, password and mobile number |
226
+ | Only here | Nothing leaves your machine | Team roles, invites, read-only links, history across machines, daily token budgets, WhatsApp, mail and Slack alerts, activity log |
227
+
228
+ Prompts and answers never leave your process with `wrap`. A report holds model names, timings, token counts, a one-way hash of each request and the workflow's shape.
229
+
230
+ ### Your key, your model
231
+
232
+ Hosted, the Connect screen gives you a private address per provider, such as `https://HOST/w/TOKEN/groq/v1`. Your key goes through to your provider and is never stored. Prompts stay in memory for up to 24 hours, and never reach disk. It can also run the concurrency audit on your own model.
233
+
189
234
  ### Use it with no code changes
190
235
 
191
236
  ```bash
@@ -16,17 +16,40 @@ synapse/chaos_panel.py
16
16
  synapse/chatops.py
17
17
  synapse/ci.py
18
18
  synapse/cli.py
19
+ synapse/cloud.py
20
+ synapse/connect.py
19
21
  synapse/demo.py
20
22
  synapse/doctor.py
21
23
  synapse/export.py
22
24
  synapse/insights.py
25
+ synapse/mail.py
23
26
  synapse/menus.py
24
27
  synapse/monitor.py
28
+ synapse/openai.py
29
+ synapse/pathsetup.py
25
30
  synapse/proxy.py
26
31
  synapse/quarantine.py
27
32
  synapse/search.py
33
+ synapse/slack.py
28
34
  synapse/api/__init__.py
35
+ synapse/api/cloud_routes.py
29
36
  synapse/api/main.py
37
+ synapse/api/site/_redirects
38
+ synapse/api/site/index.html
39
+ synapse/api/site/assets/Graph3D-DVcdUeya.js
40
+ synapse/api/site/assets/HeroSilicon-ktono7T_.js
41
+ synapse/api/site/assets/LandingV2-3C75bSR_.js
42
+ synapse/api/site/assets/graph-B5pWpvAm.js
43
+ synapse/api/site/assets/graph-BnuhLJ6X.css
44
+ synapse/api/site/assets/index-B8DPmNS7.js
45
+ synapse/api/site/assets/index-XuVVhel1.css
46
+ synapse/api/site/assets/motion-DDvmupEl.js
47
+ synapse/api/site/assets/r3f-BtQsV2-H.js
48
+ synapse/api/site/assets/silicon-DIMlxluB.js
49
+ synapse/api/site/assets/three-Dc80Zr6c.js
50
+ synapse/api/site/samples/blackbox_math_run.json
51
+ synapse/api/site/samples/langfuse_support_agent.json
52
+ synapse/api/site/samples/otel_refund_agent.json
30
53
  synapse/core/__init__.py
31
54
  synapse/core/agents.py
32
55
  synapse/core/effects.py
@@ -40,6 +63,7 @@ synapse/correctness/__init__.py
40
63
  synapse/correctness/audit.py
41
64
  synapse/correctness/chaos.py
42
65
  synapse/correctness/equivalence.py
66
+ synapse/correctness/evidence.py
43
67
  synapse/correctness/firewall.py
44
68
  synapse/correctness/net_rule.py
45
69
  synapse/correctness/preconditions.py
@@ -55,6 +79,7 @@ synapse/frontend/adapters/__init__.py
55
79
  synapse/frontend/adapters/adk.py
56
80
  synapse/frontend/adapters/crewai.py
57
81
  synapse/frontend/adapters/langgraph.py
82
+ synapse/frontend/adapters/state_trace.py
58
83
  synapse/frontend/importers/__init__.py
59
84
  synapse/frontend/importers/_common.py
60
85
  synapse/frontend/importers/blackbox.py
@@ -62,6 +87,8 @@ synapse/frontend/importers/langfuse.py
62
87
  synapse/frontend/importers/otel.py
63
88
  synapse/mcp/__init__.py
64
89
  synapse/mcp/__main__.py
90
+ synapse/mcp/annotations.py
91
+ synapse/mcp/cli_tools.py
65
92
  synapse/mcp/prompts.py
66
93
  synapse/mcp/protocol.py
67
94
  synapse/mcp/registry.py
@@ -113,6 +140,8 @@ tests/test_chaos.py
113
140
  tests/test_chatops.py
114
141
  tests/test_checkers.py
115
142
  tests/test_cli.py
143
+ tests/test_cloud.py
144
+ tests/test_connect.py
116
145
  tests/test_contracts.py
117
146
  tests/test_corpus.py
118
147
  tests/test_correctness.py
@@ -120,6 +149,7 @@ tests/test_cost_resources.py
120
149
  tests/test_crewai_adapter.py
121
150
  tests/test_dsl.py
122
151
  tests/test_effects.py
152
+ tests/test_evidence.py
123
153
  tests/test_features.py
124
154
  tests/test_hashing.py
125
155
  tests/test_importers.py
@@ -129,12 +159,16 @@ tests/test_ledger.py
129
159
  tests/test_lift.py
130
160
  tests/test_manager.py
131
161
  tests/test_mcp.py
162
+ tests/test_mcp_annotations.py
132
163
  tests/test_monitor_menus.py
133
164
  tests/test_net_rule.py
134
165
  tests/test_passes.py
166
+ tests/test_pathsetup.py
167
+ tests/test_plugin.py
135
168
  tests/test_policy_study.py
136
169
  tests/test_profile.py
137
170
  tests/test_providers.py
171
+ tests/test_real_runs.py
138
172
  tests/test_recording.py
139
173
  tests/test_report.py
140
174
  tests/test_research_fanout.py
@@ -144,8 +178,10 @@ tests/test_runtime.py
144
178
  tests/test_search.py
145
179
  tests/test_sir.py
146
180
  tests/test_skill.py
181
+ tests/test_slack.py
147
182
  tests/test_speculation.py
148
183
  tests/test_splits.py
184
+ tests/test_state_trace.py
149
185
  tests/test_store_backends.py
150
186
  tests/test_suite.py
151
187
  tests/test_usp.py
@@ -8,7 +8,7 @@ psutil>=5.9
8
8
  google-adk<2.14,>=2.13
9
9
 
10
10
  [all]
11
- halfabyte-synapse[bench,cloud,ml,ui]
11
+ halfabyte-synapse[bench,cloud,ml,slack,ui]
12
12
 
13
13
  [bench]
14
14
  rank-bm25>=0.2
@@ -34,6 +34,9 @@ scikit-learn>=1.4
34
34
  lightgbm>=4
35
35
  numpy
36
36
 
37
+ [slack]
38
+ slack_sdk<4,>=3.27
39
+
37
40
  [ui]
38
41
  fastapi>=0.110
39
42
  uvicorn[standard]>=0.29
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "halfabyte-synapse" # the PyPI name; the import and the command are both `synapse`
7
- version = "0.2.0"
7
+ version = "0.3.7"
8
8
  description = "A compiler and runtime for AI agent workflows: faster and cheaper runs, with side effects protected and every change checked by a firewall."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -53,8 +53,9 @@ cloud = ["pymongo>=4.6"] # the optional shar
53
53
  crewai = ["crewai>=1.15,<1.16"]
54
54
  langgraph = ["langgraph>=1.0,<1.1"] # the LangGraph adapter; pinned like crewai
55
55
  adk = ["google-adk>=2.13,<2.14"] # the ADK adapter. NOT installable with crewai: ADK pins opentelemetry<=1.44, CrewAI >=1.45.1 # the CrewAI adapter; pinned, it follows CrewAI's context rules
56
+ slack = ["slack_sdk>=3.27,<4"] # `synapse slack listen` (Socket Mode); alerts need nothing
56
57
  dev = ["pytest>=8", "pytest-asyncio>=0.23", "ruff>=0.5", "mongomock>=4.1"]
57
- all = ["halfabyte-synapse[ui,ml,bench,cloud]"]
58
+ all = ["halfabyte-synapse[ui,ml,bench,cloud,slack]"]
58
59
 
59
60
  [project.scripts]
60
61
  synapse = "synapse.cli:main"
@@ -63,7 +64,7 @@ synapse = "synapse.cli:main"
63
64
  include = ["synapse*"]
64
65
 
65
66
  [tool.setuptools.package-data]
66
- "synapse.api" = ["static/*.html"]
67
+ "synapse.api" = ["site/**/*"]
67
68
  "synapse.skills" = ["synapse/*.md", "synapse/references/*.md"]
68
69
  "synapse" = ["data/models.yaml"]
69
70
 
@@ -17,8 +17,9 @@ Calling `research("...")` builds a `Graph` and executes nothing. See docs/IR_SPE
17
17
 
18
18
  from __future__ import annotations
19
19
 
20
- __version__ = "0.2.0"
20
+ __version__ = "0.3.7"
21
21
 
22
+ from synapse.connect import wrap # noqa: E402
22
23
  from synapse.core.sir import (
23
24
  SIR_VERSION,
24
25
  Determinism,
@@ -36,6 +37,7 @@ from synapse.core.sir import (
36
37
  from synapse.frontend.dsl import agent, model, node, tool, workflow
37
38
 
38
39
  __all__ = [
40
+ "wrap",
39
41
  "SIR_VERSION",
40
42
  "Determinism",
41
43
  "Effect",
@@ -8,7 +8,7 @@ command that raised the alert.
8
8
  Alerts go out only when asked (`--notify`) or from `synapse alerts test`. The dashboard runs the
9
9
  firewall whenever a page scrolls into view, so it must never page anyone.
10
10
 
11
- Slack SLACK_WEBHOOK_URL
11
+ Slack SLACK_BOT_TOKEN and SLACK_CHANNEL (the bot, synapse/slack.py), or SLACK_WEBHOOK_URL
12
12
  EmailJS EMAILJS_SERVICE_ID, EMAILJS_TEMPLATE_ID, EMAILJS_PUBLIC_KEY, EMAILJS_PRIVATE_KEY, ALERT_EMAIL_TO
13
13
  WhatsApp TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN, TWILIO_WHATSAPP_FROM, ALERT_WHATSAPP_TO
14
14
  """
@@ -100,10 +100,25 @@ class Channel:
100
100
 
101
101
 
102
102
  class Slack(Channel):
103
+ """The Synapse bot when it is set up (SLACK_BOT_TOKEN, SLACK_CHANNEL: see synapse/slack.py), so the
104
+ buttons reach `synapse slack listen`; otherwise an incoming webhook, whose buttons need the VM bot."""
105
+
103
106
  name = "slack"
104
107
  required = ("SLACK_WEBHOOK_URL",)
105
108
 
109
+ def missing(self) -> list[str]:
110
+ from synapse import slack
111
+
112
+ if slack.configured():
113
+ return []
114
+ return super().missing()
115
+
106
116
  def send(self, alert: Alert) -> str:
117
+ from synapse import slack
118
+
119
+ if slack.configured():
120
+ sent = slack.post(alert.subject, slack.alert_blocks(alert))
121
+ return f"posted by the bot to {sent.get('channel')}"
107
122
  payload: dict = {"text": alert.text}
108
123
  if alert.actions:
109
124
  payload["blocks"] = [
@@ -0,0 +1,211 @@
1
+ """The HTTP face of `synapse/cloud.py`. Sign-in uses a bearer session; apps report with a project key."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+
7
+ from fastapi import APIRouter, BackgroundTasks, Depends, Header, Request
8
+
9
+ from synapse import cloud, mail
10
+
11
+
12
+ def _hosted() -> bool:
13
+ """Accounts exist only on the hosted site. `synapse ui` on someone's machine has no login at all."""
14
+ return _store().mode == "mongodb" or os.environ.get("SYNAPSE_CLOUD_ENABLED") == "1"
15
+
16
+
17
+ def _hosted_only(request: Request) -> None:
18
+ if not _hosted() and not request.url.path.endswith(("/tier", "/local")):
19
+ raise cloud.CloudError(404, "local_tier", "This is Synapse Local. It has no accounts: everything stays here.")
20
+
21
+
22
+ router = APIRouter(prefix="/api/cloud", dependencies=[Depends(_hosted_only)])
23
+
24
+
25
+ def _store():
26
+ from synapse.api.main import store
27
+
28
+ return store()
29
+
30
+
31
+ def _bearer(authorization: str | None) -> str | None:
32
+ if authorization and authorization.lower().startswith("bearer "):
33
+ return authorization[7:].strip()
34
+ return None
35
+
36
+
37
+ def _account(authorization: str | None) -> dict:
38
+ return cloud.account_for(_store(), _bearer(authorization))
39
+
40
+
41
+ @router.get("/tier")
42
+ def tier():
43
+ return {"tier": "cloud" if _hosted() else "local"}
44
+
45
+
46
+ @router.post("/signup")
47
+ def signup(body: dict, background: BackgroundTasks):
48
+ made = cloud.signup(_store(), email=body.get("email"), password=body.get("password"), phone=body.get("phone"),
49
+ name=body.get("name", ""))
50
+ account = made["account"]
51
+ background.add_task(mail.send_quietly, account["email"], mail.welcome(account["name"], account["link_code"]))
52
+ return made
53
+
54
+
55
+ @router.post("/forgot")
56
+ def forgot(body: dict, background: BackgroundTasks):
57
+ found = cloud.forgot_password(_store(), body.get("email"))
58
+ if found:
59
+ background.add_task(mail.send_quietly, found[0], mail.password_reset(found[1]))
60
+ return {"ok": True}
61
+
62
+
63
+ @router.post("/reset")
64
+ def reset(body: dict):
65
+ cloud.reset_password(_store(), body.get("token"), body.get("password"))
66
+ return {"ok": True}
67
+
68
+
69
+ @router.post("/login")
70
+ def login(body: dict):
71
+ return cloud.login(_store(), email=body.get("email"), password=body.get("password"))
72
+
73
+
74
+ @router.post("/logout")
75
+ def logout(authorization: str | None = Header(None)):
76
+ token = _bearer(authorization)
77
+ if token:
78
+ cloud.logout(_store(), token)
79
+ return {"ok": True}
80
+
81
+
82
+ @router.get("/me")
83
+ def me(authorization: str | None = Header(None)):
84
+ account = _account(authorization)
85
+ return {"account": cloud.public_account(account), "projects": cloud.list_projects(_store(), account)}
86
+
87
+
88
+ @router.post("/me/link-code")
89
+ def link_code(authorization: str | None = Header(None)):
90
+ return cloud.refresh_link_code(_store(), _account(authorization))
91
+
92
+
93
+ @router.post("/projects")
94
+ def create_project(body: dict, authorization: str | None = Header(None)):
95
+ project = cloud.create_project(_store(), _account(authorization), body.get("name"))
96
+ return cloud.public_project(_store(), project, "owner")
97
+
98
+
99
+ @router.get("/projects/{pid}")
100
+ def project(pid: str, authorization: str | None = Header(None)):
101
+ account = _account(authorization)
102
+ found = cloud.project_for(_store(), account, pid)
103
+ role = found["members"][account["phone"]]
104
+ board = cloud.dashboard(_store(), pid, name=found["name"])
105
+ return {"project": cloud.public_project(_store(), found, role), "dashboard": board}
106
+
107
+
108
+ @router.patch("/projects/{pid}")
109
+ def settings(pid: str, body: dict, authorization: str | None = Header(None)):
110
+ return cloud.update_settings(_store(), _account(authorization), pid, body)
111
+
112
+
113
+ @router.get("/projects/{pid}/runs/{run_id}")
114
+ def run_detail(pid: str, run_id: str, authorization: str | None = Header(None)):
115
+ cloud.project_for(_store(), _account(authorization), pid)
116
+ return cloud.run_detail(_store(), pid, run_id)
117
+
118
+
119
+ @router.get("/projects/{pid}/audit")
120
+ def audit_log(pid: str, authorization: str | None = Header(None)):
121
+ cloud.project_for(_store(), _account(authorization), pid, at_least="member")
122
+ return {"events": cloud.audit_log(_store(), pid)}
123
+
124
+
125
+ @router.get("/projects/{pid}/export")
126
+ def export(pid: str, authorization: str | None = Header(None)):
127
+ cloud.project_for(_store(), _account(authorization), pid)
128
+ return cloud.export(_store(), pid)
129
+
130
+
131
+ @router.post("/projects/{pid}/keys")
132
+ def create_key(pid: str, body: dict, authorization: str | None = Header(None)):
133
+ return cloud.create_key(_store(), _account(authorization), pid, body.get("name", ""))
134
+
135
+
136
+ @router.delete("/projects/{pid}/keys/{key_id}")
137
+ def revoke_key(pid: str, key_id: str, authorization: str | None = Header(None)):
138
+ cloud.revoke_key(_store(), _account(authorization), pid, key_id)
139
+ return {"ok": True}
140
+
141
+
142
+ @router.post("/projects/{pid}/invites")
143
+ def invite(pid: str, body: dict, background: BackgroundTasks, authorization: str | None = Header(None)):
144
+ account = _account(authorization)
145
+ role = body.get("role", "viewer")
146
+ made = cloud.create_invite(_store(), account, pid, role)
147
+ if body.get("email"):
148
+ project = _store().get_artifact("cloud_project", pid)
149
+ message = mail.invite(project["name"], account["name"] or account["email"], role, made["token"])
150
+ background.add_task(mail.send_quietly, cloud.normalize_email(body["email"]), message)
151
+ return made
152
+
153
+
154
+ @router.post("/invites/{token}/accept")
155
+ def accept(token: str, authorization: str | None = Header(None)):
156
+ return cloud.accept_invite(_store(), _account(authorization), token)
157
+
158
+
159
+ @router.delete("/projects/{pid}/members/{phone}")
160
+ def remove_member(pid: str, phone: str, authorization: str | None = Header(None)):
161
+ cloud.remove_member(_store(), _account(authorization), pid, phone)
162
+ return {"ok": True}
163
+
164
+
165
+ @router.post("/projects/{pid}/shares")
166
+ def create_share(pid: str, authorization: str | None = Header(None)):
167
+ return cloud.create_share(_store(), _account(authorization), pid)
168
+
169
+
170
+ @router.delete("/projects/{pid}/shares/{share_id}")
171
+ def revoke_share(pid: str, share_id: str, authorization: str | None = Header(None)):
172
+ cloud.revoke_share(_store(), _account(authorization), pid, share_id)
173
+ return {"ok": True}
174
+
175
+
176
+ @router.get("/shared/{token}")
177
+ def shared(token: str):
178
+ return cloud.shared_dashboard(_store(), token)
179
+
180
+
181
+ @router.post("/ingest")
182
+ async def ingest(request: Request, background: BackgroundTasks, authorization: str | None = Header(None)):
183
+ pid = cloud.project_for_key(_store(), _bearer(authorization))
184
+ raw = await request.body()
185
+ if len(raw) > 5_000_000:
186
+ raise cloud.CloudError(413, "too_large", "That report is too large.")
187
+ message = cloud.ingest(_store(), pid, await request.json())
188
+ if message:
189
+ background.add_task(cloud.notify, _store(), pid, message, budget_figures=message.figures)
190
+ return {"ok": True}
191
+
192
+
193
+ @router.get("/key/dashboard")
194
+ def key_dashboard(authorization: str | None = Header(None)):
195
+ """Read-only, with a project key, for agents. It sees what its project reported, and can't manage anything."""
196
+ pid = cloud.project_for_key(_store(), _bearer(authorization))
197
+ project = _store().get_artifact("cloud_project", pid)
198
+ return cloud.dashboard(_store(), pid, name=project["name"])
199
+
200
+
201
+ @router.get("/key/runs/{run_id}")
202
+ def key_run(run_id: str, authorization: str | None = Header(None)):
203
+ return cloud.run_detail(_store(), cloud.project_for_key(_store(), _bearer(authorization)), run_id)
204
+
205
+
206
+ @router.get("/local")
207
+ def local_dashboard():
208
+ """The local tier's own view. Never offered on a shared store, where it would mix up other people's data."""
209
+ if _hosted():
210
+ raise cloud.CloudError(404, "not_local", "This server is the hosted tier. Sign in to see your projects.")
211
+ return cloud.dashboard(_store(), "local", name="This machine")