specsmith 0.6.0.dev233__tar.gz → 0.7.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 (186) hide show
  1. {specsmith-0.6.0.dev233/src/specsmith.egg-info → specsmith-0.7.0}/PKG-INFO +1 -1
  2. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/pyproject.toml +1 -1
  3. specsmith-0.7.0/src/specsmith/agent/voice.py +135 -0
  4. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/cli.py +185 -100
  5. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/serve.py +58 -4
  6. {specsmith-0.6.0.dev233 → specsmith-0.7.0/src/specsmith.egg-info}/PKG-INFO +1 -1
  7. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith.egg-info/SOURCES.txt +3 -1
  8. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/tests/test_phase34_completion.py +14 -18
  9. specsmith-0.7.0/tests/test_warp_parity_followup.py +368 -0
  10. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/LICENSE +0 -0
  11. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/README.md +0 -0
  12. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/setup.cfg +0 -0
  13. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/epistemic/__init__.py +0 -0
  14. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/epistemic/belief.py +0 -0
  15. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/epistemic/certainty.py +0 -0
  16. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/epistemic/failure_graph.py +0 -0
  17. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/epistemic/py.typed +0 -0
  18. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/epistemic/recovery.py +0 -0
  19. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/epistemic/session.py +0 -0
  20. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/epistemic/stress_tester.py +0 -0
  21. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/epistemic/trace.py +0 -0
  22. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/__init__.py +0 -0
  23. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/__main__.py +0 -0
  24. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/agent/__init__.py +0 -0
  25. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/agent/broker.py +0 -0
  26. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/agent/chat_runner.py +0 -0
  27. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/agent/cleanup.py +0 -0
  28. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/agent/events.py +0 -0
  29. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/agent/indexer.py +0 -0
  30. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/agent/mcp.py +0 -0
  31. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/agent/memory.py +0 -0
  32. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/agent/orchestrator.py +0 -0
  33. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/agent/repl.py +0 -0
  34. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/agent/router.py +0 -0
  35. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/agent/rules.py +0 -0
  36. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/agent/safety.py +0 -0
  37. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/agent/suggester.py +0 -0
  38. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/agent/tools.py +0 -0
  39. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/agent/verifier.py +0 -0
  40. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/architect.py +0 -0
  41. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/auditor.py +0 -0
  42. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/auth.py +0 -0
  43. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/block_export.py +0 -0
  44. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/cloud_serve.py +0 -0
  45. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/commands/__init__.py +0 -0
  46. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/compressor.py +0 -0
  47. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/config.py +0 -0
  48. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/console_utils.py +0 -0
  49. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/credit_analyzer.py +0 -0
  50. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/credits.py +0 -0
  51. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/differ.py +0 -0
  52. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/doctor.py +0 -0
  53. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/drive.py +0 -0
  54. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/epistemic/__init__.py +0 -0
  55. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/epistemic/belief.py +0 -0
  56. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/epistemic/certainty.py +0 -0
  57. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/epistemic/failure_graph.py +0 -0
  58. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/epistemic/recovery.py +0 -0
  59. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/epistemic/stress_tester.py +0 -0
  60. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/executor.py +0 -0
  61. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/exporter.py +0 -0
  62. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/gui/__init__.py +0 -0
  63. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/gui/app.py +0 -0
  64. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/gui/main_window.py +0 -0
  65. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/gui/session_tab.py +0 -0
  66. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/gui/theme.py +0 -0
  67. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/gui/widgets/__init__.py +0 -0
  68. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/gui/widgets/chat_view.py +0 -0
  69. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/gui/widgets/input_bar.py +0 -0
  70. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/gui/widgets/provider_bar.py +0 -0
  71. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/gui/widgets/token_meter.py +0 -0
  72. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/gui/widgets/tool_panel.py +0 -0
  73. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/gui/widgets/update_checker.py +0 -0
  74. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/gui/worker.py +0 -0
  75. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/history_search.py +0 -0
  76. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/importer.py +0 -0
  77. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/integrations/__init__.py +0 -0
  78. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/integrations/agent_skill.py +0 -0
  79. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/integrations/aider.py +0 -0
  80. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/integrations/base.py +0 -0
  81. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/integrations/claude_code.py +0 -0
  82. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/integrations/copilot.py +0 -0
  83. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/integrations/cursor.py +0 -0
  84. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/integrations/gemini.py +0 -0
  85. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/integrations/windsurf.py +0 -0
  86. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/languages.py +0 -0
  87. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/ledger.py +0 -0
  88. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/patent.py +0 -0
  89. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/phase.py +0 -0
  90. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/plugins.py +0 -0
  91. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/profiles.py +0 -0
  92. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/rate_limits.py +0 -0
  93. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/releaser.py +0 -0
  94. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/requirements.py +0 -0
  95. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/requirements_parser.py +0 -0
  96. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/retrieval.py +0 -0
  97. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/scaffolder.py +0 -0
  98. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/session.py +0 -0
  99. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/skills.py +0 -0
  100. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/templates/agents.md.j2 +0 -0
  101. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/templates/community/bug_report.md.j2 +0 -0
  102. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/templates/community/code_of_conduct.md.j2 +0 -0
  103. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/templates/community/contributing.md.j2 +0 -0
  104. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/templates/community/feature_request.md.j2 +0 -0
  105. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/templates/community/license-Apache-2.0.j2 +0 -0
  106. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/templates/community/license-MIT.j2 +0 -0
  107. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/templates/community/pull_request_template.md.j2 +0 -0
  108. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/templates/community/security.md.j2 +0 -0
  109. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/templates/docs/architecture.md.j2 +0 -0
  110. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/templates/docs/mkdocs.yml.j2 +0 -0
  111. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/templates/docs/readthedocs.yaml.j2 +0 -0
  112. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/templates/docs/requirements.md.j2 +0 -0
  113. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/templates/docs/test-spec.md.j2 +0 -0
  114. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/templates/editorconfig.j2 +0 -0
  115. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/templates/gitattributes.j2 +0 -0
  116. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/templates/gitignore.j2 +0 -0
  117. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/templates/go/go.mod.j2 +0 -0
  118. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/templates/go/main.go.j2 +0 -0
  119. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/templates/governance/belief-registry.md.j2 +0 -0
  120. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/templates/governance/context-budget.md.j2 +0 -0
  121. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/templates/governance/drift-metrics.md.j2 +0 -0
  122. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/templates/governance/epistemic-axioms.md.j2 +0 -0
  123. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/templates/governance/failure-modes.md.j2 +0 -0
  124. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/templates/governance/lifecycle.md.j2 +0 -0
  125. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/templates/governance/roles.md.j2 +0 -0
  126. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/templates/governance/rules.md.j2 +0 -0
  127. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/templates/governance/session-protocol.md.j2 +0 -0
  128. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/templates/governance/uncertainty-map.md.j2 +0 -0
  129. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/templates/governance/verification.md.j2 +0 -0
  130. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/templates/js/package.json.j2 +0 -0
  131. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/templates/ledger.md.j2 +0 -0
  132. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/templates/python/cli.py.j2 +0 -0
  133. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/templates/python/init.py.j2 +0 -0
  134. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/templates/python/pyproject.toml.j2 +0 -0
  135. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/templates/readme.md.j2 +0 -0
  136. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/templates/rust/Cargo.toml.j2 +0 -0
  137. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/templates/rust/main.rs.j2 +0 -0
  138. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/templates/scripts/exec.cmd.j2 +0 -0
  139. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/templates/scripts/exec.sh.j2 +0 -0
  140. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/templates/scripts/run.cmd.j2 +0 -0
  141. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/templates/scripts/run.sh.j2 +0 -0
  142. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/templates/scripts/setup.cmd.j2 +0 -0
  143. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/templates/scripts/setup.sh.j2 +0 -0
  144. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/templates/workflows/release.yml.j2 +0 -0
  145. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/tool_installer.py +0 -0
  146. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/toolrules.py +0 -0
  147. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/tools.py +0 -0
  148. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/trace.py +0 -0
  149. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/updater.py +0 -0
  150. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/upgrader.py +0 -0
  151. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/validator.py +0 -0
  152. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/vcs/__init__.py +0 -0
  153. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/vcs/base.py +0 -0
  154. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/vcs/bitbucket.py +0 -0
  155. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/vcs/github.py +0 -0
  156. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/vcs/gitlab.py +0 -0
  157. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/vcs_commands.py +0 -0
  158. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/wireframes.py +0 -0
  159. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith/workspace.py +0 -0
  160. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith.egg-info/dependency_links.txt +0 -0
  161. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith.egg-info/entry_points.txt +0 -0
  162. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith.egg-info/requires.txt +0 -0
  163. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/src/specsmith.egg-info/top_level.txt +0 -0
  164. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/tests/test_CMD_001.py +0 -0
  165. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/tests/test_auditor.py +0 -0
  166. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/tests/test_chat_diff_decision.py +0 -0
  167. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/tests/test_chat_stdin_protocol.py +0 -0
  168. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/tests/test_cli.py +0 -0
  169. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/tests/test_cli_workflows_history_drive.py +0 -0
  170. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/tests/test_compressor.py +0 -0
  171. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/tests/test_e2e_nexus.py +0 -0
  172. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/tests/test_epistemic.py +0 -0
  173. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/tests/test_importer.py +0 -0
  174. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/tests/test_integrations.py +0 -0
  175. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/tests/test_mcp_client.py +0 -0
  176. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/tests/test_nexus.py +0 -0
  177. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/tests/test_phase1_4_new.py +0 -0
  178. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/tests/test_rate_limits.py +0 -0
  179. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/tests/test_scaffolder.py +0 -0
  180. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/tests/test_skill_marketplace.py +0 -0
  181. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/tests/test_smoke.py +0 -0
  182. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/tests/test_suggester.py +0 -0
  183. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/tests/test_tools.py +0 -0
  184. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/tests/test_validator.py +0 -0
  185. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/tests/test_vcs.py +0 -0
  186. {specsmith-0.6.0.dev233 → specsmith-0.7.0}/tests/test_warp_parity.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: specsmith
3
- Version: 0.6.0.dev233
3
+ Version: 0.7.0
4
4
  Summary: Applied Epistemic Engineering toolkit — AEE agent sessions, execution profiles, FPGA/HDL governance, tool installer, 50+ CLI commands.
5
5
  Author: BitConcepts
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "specsmith"
7
- version = "0.6.0.dev233"
7
+ version = "0.7.0"
8
8
  description = "Applied Epistemic Engineering toolkit — AEE agent sessions, execution profiles, FPGA/HDL governance, tool installer, 50+ CLI commands."
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -0,0 +1,135 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Copyright (c) 2026 BitConcepts, LLC. All rights reserved.
3
+ """Voice transcription wrapper (REQ-141).
4
+
5
+ Wraps the optional ``whisper_cpp_python`` library so the rest of specsmith
6
+ can call ``transcribe(path)`` without caring whether the extra is
7
+ installed. When the library is missing, ``transcribe`` raises
8
+ ``VoiceUnavailableError`` with a friendly install hint so the caller can
9
+ surface it to the user.
10
+
11
+ The wrapper supports three modes:
12
+
13
+ * **real** -- ``whisper_cpp_python`` is installed and a model file is
14
+ available (auto-located under ``~/.specsmith/voice/`` or pointed to via
15
+ ``SPECSMITH_VOICE_MODEL``). Real audio decoding.
16
+ * **stub** -- ``SPECSMITH_VOICE_STUB=<text>`` is set. Returns the literal
17
+ text without touching the audio file. Used by tests and CI so we don't
18
+ need to ship a 500MB model file.
19
+ * **unavailable** -- neither of the above. ``transcribe`` raises.
20
+
21
+ The CLI exposes this as ``specsmith voice transcribe <wav>``.
22
+ """
23
+
24
+ from __future__ import annotations
25
+
26
+ import os
27
+ from dataclasses import dataclass
28
+ from pathlib import Path
29
+
30
+
31
+ class VoiceUnavailableError(RuntimeError):
32
+ """Raised when whisper-cpp is not installed and no stub is set."""
33
+
34
+
35
+ @dataclass
36
+ class TranscribeResult:
37
+ text: str
38
+ backend: str # 'whisper-cpp', 'stub', 'unavailable'
39
+ model: str = ""
40
+ duration_s: float = 0.0
41
+
42
+ def to_dict(self) -> dict[str, object]:
43
+ return {
44
+ "text": self.text,
45
+ "backend": self.backend,
46
+ "model": self.model,
47
+ "duration_s": round(self.duration_s, 3),
48
+ }
49
+
50
+
51
+ def default_model_dir() -> Path:
52
+ return Path.home() / ".specsmith" / "voice"
53
+
54
+
55
+ def _resolve_model_path() -> Path | None:
56
+ """Return the on-disk model path, or None if no model is configured."""
57
+ env = os.environ.get("SPECSMITH_VOICE_MODEL", "").strip()
58
+ if env:
59
+ p = Path(env)
60
+ if p.is_file():
61
+ return p
62
+ # Auto-locate the smallest .bin under ~/.specsmith/voice/
63
+ voice_dir = default_model_dir()
64
+ if voice_dir.is_dir():
65
+ candidates = sorted(voice_dir.glob("*.bin"), key=lambda x: x.stat().st_size)
66
+ if candidates:
67
+ return candidates[0]
68
+ return None
69
+
70
+
71
+ def is_available() -> bool:
72
+ """Cheap probe: True iff transcription would succeed without raising."""
73
+ if os.environ.get("SPECSMITH_VOICE_STUB", "").strip():
74
+ return True
75
+ try:
76
+ import whisper_cpp_python # noqa: F401 (presence-only check)
77
+ except Exception: # noqa: BLE001
78
+ return False
79
+ return _resolve_model_path() is not None
80
+
81
+
82
+ def transcribe(path: Path) -> TranscribeResult:
83
+ """Transcribe a wav/flac/mp3 file to text.
84
+
85
+ Order of resolution:
86
+ 1. If ``SPECSMITH_VOICE_STUB`` is set, return its value verbatim. This
87
+ lets tests run without a model file.
88
+ 2. Otherwise import ``whisper_cpp_python`` and run a real transcription.
89
+ 3. If neither is available, raise :class:`VoiceUnavailableError` with
90
+ an actionable message.
91
+ """
92
+ import time as _time
93
+
94
+ if not path.exists():
95
+ raise FileNotFoundError(f"audio file not found: {path}")
96
+
97
+ stub = os.environ.get("SPECSMITH_VOICE_STUB", "").strip()
98
+ if stub:
99
+ return TranscribeResult(text=stub, backend="stub")
100
+
101
+ try:
102
+ import whisper_cpp_python
103
+ except Exception as exc: # noqa: BLE001
104
+ raise VoiceUnavailableError(
105
+ "whisper-cpp-python is not installed. Run "
106
+ "`pipx inject specsmith whisper-cpp-python` "
107
+ "(or `pip install specsmith[voice]`)."
108
+ ) from exc
109
+
110
+ model_path = _resolve_model_path()
111
+ if model_path is None:
112
+ raise VoiceUnavailableError(
113
+ "No whisper model found. Set SPECSMITH_VOICE_MODEL or place a "
114
+ f".bin model under {default_model_dir()}."
115
+ )
116
+
117
+ start = _time.perf_counter()
118
+ whisper = whisper_cpp_python.Whisper(model_path=str(model_path))
119
+ out = whisper.transcribe(str(path))
120
+ text = out.get("text") if isinstance(out, dict) else str(out)
121
+ return TranscribeResult(
122
+ text=str(text or "").strip(),
123
+ backend="whisper-cpp",
124
+ model=model_path.name,
125
+ duration_s=_time.perf_counter() - start,
126
+ )
127
+
128
+
129
+ __all__ = [
130
+ "TranscribeResult",
131
+ "VoiceUnavailableError",
132
+ "default_model_dir",
133
+ "is_available",
134
+ "transcribe",
135
+ ]
@@ -2796,12 +2796,23 @@ def run_cmd(
2796
2796
  @click.option("--model", default="", help="Model name (blank = provider default).")
2797
2797
  @click.option("--port", type=int, default=8421, help="HTTP port to listen on.")
2798
2798
  @click.option("--host", default="127.0.0.1", help="Bind address (use 0.0.0.0 for network access).")
2799
+ @click.option(
2800
+ "--auth-token",
2801
+ "auth_token",
2802
+ default="",
2803
+ help=(
2804
+ "Optional bearer token (REQ-137). When set, every /api/* request must "
2805
+ "present `Authorization: Bearer <token>`. /api/health stays open so "
2806
+ "liveness probes still work."
2807
+ ),
2808
+ )
2799
2809
  def serve_cmd(
2800
2810
  project_dir: str,
2801
2811
  provider: str,
2802
2812
  model: str,
2803
2813
  port: int,
2804
2814
  host: str,
2815
+ auth_token: str,
2805
2816
  ) -> None:
2806
2817
  """Start a persistent HTTP server for agent sessions.
2807
2818
 
@@ -2810,7 +2821,8 @@ def serve_cmd(
2810
2821
  POST /api/send.
2811
2822
 
2812
2823
  Example:
2813
- specsmith serve --port 8421 --provider ollama --model qwen2.5:14b
2824
+ specsmith serve --port 8421 --provider ollama --model qwen2.5:14b \
2825
+ --auth-token $(specsmith auth get serve)
2814
2826
  """
2815
2827
  from specsmith.serve import run_server
2816
2828
 
@@ -2820,6 +2832,7 @@ def serve_cmd(
2820
2832
  model=model,
2821
2833
  port=port,
2822
2834
  host=host,
2835
+ auth_token=auth_token,
2823
2836
  )
2824
2837
 
2825
2838
 
@@ -4444,9 +4457,28 @@ def info_cmd(as_json: bool, section: str) -> None:
4444
4457
  # specsmith chat-export-block — self-contained block share (REQ-134)
4445
4458
  # ---------------------------------------------------------------------------
4446
4459
  #
4447
- # This is exposed at the top level (rather than under ``chat``) because the
4448
- # existing ``specsmith chat <utterance>`` command takes a positional argument
4449
- # and cannot simultaneously act as a Click group.
4460
+ # Top-level alias kept for back-compat with v0.6.x which only exposed
4461
+ # ``specsmith chat-export-block``. The canonical 1.0 spelling is
4462
+ # ``specsmith chat export-block`` under the chat group below.
4463
+
4464
+
4465
+ def _do_chat_export_block(project_dir: str, session_id: str, block_id: str, fmt: str) -> None:
4466
+ from specsmith.block_export import export_block
4467
+
4468
+ try:
4469
+ out = export_block(
4470
+ Path(project_dir).resolve(),
4471
+ session_id,
4472
+ block_id,
4473
+ fmt=fmt,
4474
+ )
4475
+ except FileNotFoundError as exc:
4476
+ console.print(f"[red]{exc}[/red]")
4477
+ raise SystemExit(1) from exc
4478
+ except KeyError as exc:
4479
+ console.print(f"[red]{exc}[/red]")
4480
+ raise SystemExit(1) from exc
4481
+ click.echo(out)
4450
4482
 
4451
4483
 
4452
4484
  @main.command(name="chat-export-block")
@@ -4460,23 +4492,156 @@ def info_cmd(as_json: bool, section: str) -> None:
4460
4492
  default="md",
4461
4493
  )
4462
4494
  def chat_export_block_cmd(project_dir: str, session_id: str, block_id: str, fmt: str) -> None:
4463
- """Export one chat block as a self-contained snippet (REQ-134)."""
4464
- from specsmith.block_export import export_block
4495
+ """Export one chat block as a self-contained snippet (REQ-134, top-level alias)."""
4496
+ _do_chat_export_block(project_dir, session_id, block_id, fmt)
4497
+
4498
+
4499
+ # ---------------------------------------------------------------------------
4500
+ # specsmith voice transcribe — wav/flac transcription via whisper-cpp (REQ-141)
4501
+ # ---------------------------------------------------------------------------
4502
+
4503
+
4504
+ @main.group(name="voice")
4505
+ def voice_group() -> None:
4506
+ """Voice agent input (REQ-141). Requires the ``[voice]`` extra."""
4507
+
4508
+
4509
+ @voice_group.command(name="transcribe")
4510
+ @click.argument("audio_path", type=click.Path(exists=True))
4511
+ @click.option(
4512
+ "--json",
4513
+ "as_json",
4514
+ is_flag=True,
4515
+ default=False,
4516
+ help="Emit the full transcription record as JSON.",
4517
+ )
4518
+ def voice_transcribe_cmd(audio_path: str, as_json: bool) -> None:
4519
+ """Transcribe AUDIO_PATH to text using whisper-cpp.
4520
+
4521
+ Three resolution modes:
4522
+
4523
+ \b
4524
+ * SPECSMITH_VOICE_STUB=<text> — returns the literal text (used by tests)
4525
+ * whisper-cpp installed + model present — real transcription
4526
+ * neither — exits 2 with an actionable install hint
4527
+ """
4528
+ import json as _json
4529
+
4530
+ from specsmith.agent.voice import VoiceUnavailableError, transcribe
4465
4531
 
4466
4532
  try:
4467
- out = export_block(
4468
- Path(project_dir).resolve(),
4469
- session_id,
4470
- block_id,
4471
- fmt=fmt,
4472
- )
4533
+ result = transcribe(Path(audio_path))
4473
4534
  except FileNotFoundError as exc:
4474
4535
  console.print(f"[red]{exc}[/red]")
4475
4536
  raise SystemExit(1) from exc
4476
- except KeyError as exc:
4537
+ except VoiceUnavailableError as exc:
4477
4538
  console.print(f"[red]{exc}[/red]")
4539
+ raise SystemExit(2) from exc
4540
+
4541
+ if as_json:
4542
+ click.echo(_json.dumps(result.to_dict(), indent=2))
4543
+ else:
4544
+ click.echo(result.text)
4545
+
4546
+
4547
+ @voice_group.command(name="status")
4548
+ def voice_status_cmd() -> None:
4549
+ """Report whether voice transcription is available right now."""
4550
+ from specsmith.agent.voice import default_model_dir, is_available
4551
+
4552
+ if is_available():
4553
+ console.print("[green]\u2713[/green] voice available")
4554
+ console.print(f" model dir: {default_model_dir()}")
4555
+ else:
4556
+ console.print("[yellow]\u2014[/yellow] voice unavailable")
4557
+ console.print(
4558
+ " Install: [bold]pipx inject specsmith whisper-cpp-python[/bold] "
4559
+ "and place a model under ~/.specsmith/voice/."
4560
+ )
4561
+ raise SystemExit(2)
4562
+
4563
+
4564
+ # ---------------------------------------------------------------------------
4565
+ # specsmith cloud spawn — client side of the receiver (REQ-136)
4566
+ # ---------------------------------------------------------------------------
4567
+
4568
+
4569
+ @main.group(name="cloud")
4570
+ def cloud_group() -> None:
4571
+ """Cloud-agent receiver client (REQ-136)."""
4572
+
4573
+
4574
+ @cloud_group.command(name="spawn")
4575
+ @click.argument("manifest_path", type=click.Path(exists=True))
4576
+ @click.option(
4577
+ "--endpoint",
4578
+ default="http://127.0.0.1:9000",
4579
+ help="Cloud-serve base URL (default: http://127.0.0.1:9000).",
4580
+ )
4581
+ @click.option("--token", default="", help="Bearer token for the receiver.")
4582
+ @click.option(
4583
+ "--dry-run",
4584
+ is_flag=True,
4585
+ default=False,
4586
+ help="Validate the manifest locally and print what would be posted.",
4587
+ )
4588
+ def cloud_spawn_cmd(manifest_path: str, endpoint: str, token: str, dry_run: bool) -> None:
4589
+ """Post a manifest to a `specsmith cloud-serve` endpoint (REQ-136).
4590
+
4591
+ The MANIFEST_PATH is a YAML or JSON file describing the run. The CLI
4592
+ reads it, posts it to ``<endpoint>/spawn`` with optional bearer auth,
4593
+ and prints the response as JSON.
4594
+ """
4595
+ import json as _json
4596
+ import urllib.error
4597
+ import urllib.request
4598
+
4599
+ raw = Path(manifest_path).read_text(encoding="utf-8")
4600
+ payload: dict[str, object]
4601
+ if manifest_path.endswith((".yml", ".yaml")):
4602
+ try:
4603
+ import yaml as _yaml
4604
+
4605
+ payload = _yaml.safe_load(raw) or {}
4606
+ except Exception as exc: # noqa: BLE001
4607
+ console.print(f"[red]Invalid YAML manifest: {exc}[/red]")
4608
+ raise SystemExit(2) from exc
4609
+ else:
4610
+ try:
4611
+ payload = _json.loads(raw)
4612
+ except ValueError as exc:
4613
+ console.print(f"[red]Invalid JSON manifest: {exc}[/red]")
4614
+ raise SystemExit(2) from exc
4615
+
4616
+ if not isinstance(payload, dict):
4617
+ console.print("[red]Manifest must be a mapping (YAML/JSON object).[/red]")
4618
+ raise SystemExit(2)
4619
+
4620
+ if dry_run:
4621
+ click.echo(_json.dumps({"endpoint": endpoint, "manifest": payload}, indent=2))
4622
+ return
4623
+
4624
+ body = _json.dumps(payload).encode("utf-8")
4625
+ req = urllib.request.Request( # noqa: S310 - user-supplied endpoint
4626
+ endpoint.rstrip("/") + "/spawn",
4627
+ data=body,
4628
+ method="POST",
4629
+ headers={"Content-Type": "application/json"},
4630
+ )
4631
+ if token:
4632
+ req.add_header("Authorization", f"Bearer {token}")
4633
+ try:
4634
+ with urllib.request.urlopen(req, timeout=30) as resp: # noqa: S310
4635
+ response = _json.loads(resp.read().decode("utf-8"))
4636
+ except urllib.error.HTTPError as exc:
4637
+ body_text = exc.read().decode("utf-8") or "{}"
4638
+ console.print(f"[red]HTTP {exc.code}[/red]: {body_text}")
4639
+ raise SystemExit(1) from exc
4640
+ except urllib.error.URLError as exc:
4641
+ console.print(f"[red]Network error[/red]: {exc.reason}")
4478
4642
  raise SystemExit(1) from exc
4479
- click.echo(out)
4643
+
4644
+ click.echo(_json.dumps(response, indent=2))
4480
4645
 
4481
4646
 
4482
4647
  # ---------------------------------------------------------------------------
@@ -5808,93 +5973,13 @@ main.add_command(notebook_group)
5808
5973
 
5809
5974
 
5810
5975
  # ---------------------------------------------------------------------------
5811
- # Cloud — spawn an off-machine agent (REQ-126)
5976
+ # Cloud — REQ-126 placeholder (cloud spawn lives above under REQ-136).
5812
5977
  # ---------------------------------------------------------------------------
5813
-
5814
-
5815
- @main.group(name="cloud")
5816
- def cloud_group() -> None:
5817
- """Spawn cloud agents (stub for now — packages and posts work)."""
5818
-
5819
-
5820
- @cloud_group.command(name="spawn")
5821
- @click.argument("utterance")
5822
- @click.option("--project-dir", type=click.Path(exists=True), default=".")
5823
- @click.option(
5824
- "--endpoint",
5825
- default="",
5826
- help="Cloud endpoint URL (default: SPECSMITH_CLOUD_ENDPOINT env var).",
5827
- )
5828
- @click.option(
5829
- "--dry-run",
5830
- is_flag=True,
5831
- default=False,
5832
- help="Build the tarball + manifest but do not POST.",
5833
- )
5834
- def cloud_spawn(utterance: str, project_dir: str, endpoint: str, dry_run: bool) -> None:
5835
- """Tarball the working tree and POST to a cloud agent endpoint (REQ-126).
5836
-
5837
- Always emits a manifest at `.specsmith/cloud/<run_id>/manifest.json`
5838
- so the operation is auditable. When the endpoint is reachable the
5839
- response stream is tailed to stdout as JSONL events. Without an
5840
- endpoint, the command stays local: the manifest is recorded and a
5841
- helpful error is printed.
5842
- """
5843
- import json as _json
5844
- import os
5845
- import tarfile
5846
- import uuid as _uuid
5847
- from urllib import error as _urlerror
5848
- from urllib import request as _urlreq
5849
-
5850
- root = Path(project_dir).resolve()
5851
- run_id = f"cloud_{_uuid.uuid4().hex[:12]}"
5852
- run_dir = root / ".specsmith" / "cloud" / run_id
5853
- run_dir.mkdir(parents=True, exist_ok=True)
5854
-
5855
- tar_path = run_dir / "workspace.tar.gz"
5856
- skip = {".git", ".venv", "__pycache__", ".specsmith", "node_modules", "dist", "build"}
5857
- with tarfile.open(tar_path, "w:gz") as tar:
5858
- for item in sorted(root.iterdir()):
5859
- if item.name in skip:
5860
- continue
5861
- tar.add(item, arcname=item.name)
5862
-
5863
- manifest = {
5864
- "run_id": run_id,
5865
- "utterance": utterance,
5866
- "workspace": str(tar_path.relative_to(root)),
5867
- "endpoint": endpoint or os.environ.get("SPECSMITH_CLOUD_ENDPOINT", ""),
5868
- "dry_run": dry_run,
5869
- }
5870
- (run_dir / "manifest.json").write_text(_json.dumps(manifest, indent=2), encoding="utf-8")
5871
-
5872
- if dry_run or not manifest["endpoint"]:
5873
- console.print(
5874
- f"[yellow]Cloud endpoint not configured.[/yellow] Manifest written to {run_dir}."
5875
- )
5876
- console.print(
5877
- " Set [bold]SPECSMITH_CLOUD_ENDPOINT[/bold] or pass [bold]--endpoint[/bold] to POST."
5878
- )
5879
- return
5880
-
5881
- body = _json.dumps(manifest).encode("utf-8")
5882
- req = _urlreq.Request(
5883
- manifest["endpoint"],
5884
- data=body,
5885
- headers={"Content-Type": "application/json"},
5886
- method="POST",
5887
- )
5888
- try:
5889
- with _urlreq.urlopen(req, timeout=30) as resp: # noqa: S310 - user-configured
5890
- for line in resp:
5891
- click.echo(line.decode("utf-8", errors="replace").rstrip())
5892
- except (_urlerror.URLError, OSError) as exc:
5893
- console.print(f"[red]Cloud spawn failed:[/red] {exc}")
5894
- raise SystemExit(1) from None
5895
-
5896
-
5897
- main.add_command(cloud_group)
5978
+ # The original REQ-126 stub built a workspace tarball and posted to a free-
5979
+ # form endpoint with no auth. REQ-136 supersedes it with a manifest-based
5980
+ # command that posts to ``<endpoint>/spawn`` with optional bearer auth.
5981
+ # Keeping a single ``cloud spawn`` avoids surface drift; see
5982
+ # tests/test_warp_parity_followup.py for coverage.
5898
5983
 
5899
5984
 
5900
5985
  # ---------------------------------------------------------------------------
@@ -153,11 +153,29 @@ class _Handler(BaseHTTPRequestHandler):
153
153
 
154
154
  bus: _EventBus
155
155
  agent: _AgentThread
156
+ auth_token: str = "" # populated by run_server / make_server when set
156
157
 
157
158
  def log_message(self, format: str, *args: Any) -> None: # noqa: A002
158
159
  """Suppress default stderr logging."""
159
160
 
161
+ # ── Auth ─────────────────────────────────────────────────────────
162
+ # REQ-137: when run_server is started with --auth-token, every
163
+ # request must present `Authorization: Bearer <token>`. /api/health
164
+ # is the only unauthenticated endpoint so liveness probes still
165
+ # work behind a load balancer that strips Authorization.
166
+ def _authorize(self) -> bool:
167
+ token = type(self).auth_token
168
+ if not token:
169
+ return True
170
+ if self.path == "/api/health":
171
+ return True
172
+ header = self.headers.get("Authorization", "")
173
+ return header == f"Bearer {token}"
174
+
160
175
  def do_GET(self) -> None: # noqa: N802
176
+ if not self._authorize():
177
+ self._json_response({"error": "unauthorized"}, code=401)
178
+ return
161
179
  if self.path == "/api/events":
162
180
  self._sse()
163
181
  elif self.path == "/api/status":
@@ -168,6 +186,9 @@ class _Handler(BaseHTTPRequestHandler):
168
186
  self.send_error(404)
169
187
 
170
188
  def do_POST(self) -> None: # noqa: N802
189
+ if not self._authorize():
190
+ self._json_response({"error": "unauthorized"}, code=401)
191
+ return
171
192
  if self.path == "/api/send":
172
193
  body = self._read_json()
173
194
  text = body.get("text", "").strip() if body else ""
@@ -183,6 +204,9 @@ class _Handler(BaseHTTPRequestHandler):
183
204
  self.send_error(404)
184
205
 
185
206
  def do_DELETE(self) -> None: # noqa: N802
207
+ if not self._authorize():
208
+ self._json_response({"error": "unauthorized"}, code=401)
209
+ return
186
210
  if self.path == "/api/session":
187
211
  self.agent.send(None) # type: ignore[arg-type]
188
212
  self._json_response({"ok": True, "message": "session ending"})
@@ -246,38 +270,68 @@ class _Handler(BaseHTTPRequestHandler):
246
270
  self.wfile.write(body)
247
271
 
248
272
 
249
- def run_server(
273
+ def make_server(
250
274
  *,
251
275
  project_dir: str = ".",
252
276
  provider: str = "ollama",
253
277
  model: str = "",
254
278
  port: int = 8421,
255
279
  host: str = "127.0.0.1",
256
- ) -> None:
257
- """Start the specsmith HTTP server."""
280
+ auth_token: str = "",
281
+ ) -> tuple[_ThreadedHTTPServer, _AgentThread]:
282
+ """Build the HTTP server + agent thread without serving yet.
283
+
284
+ Used by tests so they can drive a fresh server inside the same
285
+ process. Production callers go through ``run_server`` which adds
286
+ the banner + serve_forever loop.
287
+ """
258
288
  project_dir = str(Path(project_dir).resolve())
259
289
  bus = _EventBus()
260
290
  agent = _AgentThread(project_dir, provider, model, bus)
261
291
 
262
- # Subclass to carry shared state into the handler
263
292
  class Handler(_Handler):
264
293
  pass
265
294
 
266
295
  Handler.bus = bus
267
296
  Handler.agent = agent
297
+ Handler.auth_token = auth_token
268
298
 
269
299
  server = _ThreadedHTTPServer((host, port), Handler)
300
+ return server, agent
301
+
302
+
303
+ def run_server(
304
+ *,
305
+ project_dir: str = ".",
306
+ provider: str = "ollama",
307
+ model: str = "",
308
+ port: int = 8421,
309
+ host: str = "127.0.0.1",
310
+ auth_token: str = "",
311
+ ) -> None:
312
+ """Start the specsmith HTTP server."""
313
+ server, agent = make_server(
314
+ project_dir=project_dir,
315
+ provider=provider,
316
+ model=model,
317
+ port=port,
318
+ host=host,
319
+ auth_token=auth_token,
320
+ )
270
321
  agent.start()
271
322
 
323
+ auth_note = " Auth: bearer-token required\n" if auth_token else ""
272
324
  print( # noqa: T201
273
325
  f"specsmith serve — http://{host}:{port}\n"
274
326
  f" Project: {project_dir}\n"
275
327
  f" Provider: {provider}/{model or '(default)'}\n"
328
+ f"{auth_note}"
276
329
  f" Endpoints:\n"
277
330
  f" GET /api/events — SSE event stream\n"
278
331
  f" POST /api/send — send a message\n"
279
332
  f" GET /api/status — session status\n"
280
333
  f" POST /api/stop — stop current turn\n"
334
+ f" GET /api/health — unauthenticated liveness\n"
281
335
  f" Press Ctrl+C to stop.\n",
282
336
  file=sys.stderr,
283
337
  )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: specsmith
3
- Version: 0.6.0.dev233
3
+ Version: 0.7.0
4
4
  Summary: Applied Epistemic Engineering toolkit — AEE agent sessions, execution profiles, FPGA/HDL governance, tool installer, 50+ CLI commands.
5
5
  Author: BitConcepts
6
6
  License-Expression: MIT
@@ -77,6 +77,7 @@ src/specsmith/agent/safety.py
77
77
  src/specsmith/agent/suggester.py
78
78
  src/specsmith/agent/tools.py
79
79
  src/specsmith/agent/verifier.py
80
+ src/specsmith/agent/voice.py
80
81
  src/specsmith/commands/__init__.py
81
82
  src/specsmith/epistemic/__init__.py
82
83
  src/specsmith/epistemic/belief.py
@@ -179,4 +180,5 @@ tests/test_suggester.py
179
180
  tests/test_tools.py
180
181
  tests/test_validator.py
181
182
  tests/test_vcs.py
182
- tests/test_warp_parity.py
183
+ tests/test_warp_parity.py
184
+ tests/test_warp_parity_followup.py