omnimancer-cli 0.2.22__tar.gz → 0.2.26__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 (184) hide show
  1. {omnimancer_cli-0.2.22/omnimancer_cli.egg-info → omnimancer_cli-0.2.26}/PKG-INFO +68 -7
  2. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/README.md +60 -4
  3. omnimancer_cli-0.2.26/omn_fleet_hook.py +215 -0
  4. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/__init__.py +12 -4
  5. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/cli/agent_loop.py +84 -39
  6. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/cli/approval_integration.py +54 -0
  7. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/cli/command_dispatch.py +326 -12
  8. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/cli/commands.py +13 -10
  9. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/cli/completion.py +83 -67
  10. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/cli/display.py +55 -12
  11. omnimancer_cli-0.2.26/omnimancer/cli/file_mentions.py +170 -0
  12. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/cli/headless.py +155 -50
  13. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/cli/interface.py +372 -84
  14. omnimancer_cli-0.2.26/omnimancer/cli/prompt_input.py +217 -0
  15. omnimancer_cli-0.2.26/omnimancer/cli/pt_completion.py +172 -0
  16. omnimancer_cli-0.2.26/omnimancer/cli/session.py +41 -0
  17. omnimancer_cli-0.2.26/omnimancer/cli/subagent.py +176 -0
  18. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/cli/tool_handler.py +39 -15
  19. omnimancer_cli-0.2.26/omnimancer/cli/turn_notify.py +170 -0
  20. omnimancer_cli-0.2.26/omnimancer/cli/usage.py +27 -0
  21. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/agent/program_executor.py +17 -5
  22. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/agent/status_core.py +5 -0
  23. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/agent/status_manager.py +42 -10
  24. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/agent_engine.py +162 -5
  25. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/agent_managers.py +12 -1
  26. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/config_manager.py +16 -2
  27. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/config_migration.py +28 -2
  28. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/config_validator.py +4 -1
  29. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/engine.py +38 -1
  30. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/hooks.py +4 -1
  31. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/models.py +81 -4
  32. omnimancer_cli-0.2.26/omnimancer/core/prompt_enhancer.py +326 -0
  33. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/provider_capabilities.py +24 -8
  34. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/provider_initializer.py +26 -7
  35. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/security/audit_logger.py +7 -0
  36. omnimancer_cli-0.2.26/omnimancer/events/__init__.py +1 -0
  37. omnimancer_cli-0.2.26/omnimancer/events/emitter.py +502 -0
  38. omnimancer_cli-0.2.26/omnimancer/events/jsonl_writer.py +362 -0
  39. omnimancer_cli-0.2.26/omnimancer/events/schema.py +143 -0
  40. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/providers/__init__.py +2 -0
  41. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/providers/factory.py +26 -4
  42. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/providers/openai.py +257 -17
  43. omnimancer_cli-0.2.26/omnimancer/providers/openai_compatible.py +143 -0
  44. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/providers/openrouter.py +2 -1
  45. omnimancer_cli-0.2.26/omnimancer/tui/__init__.py +5 -0
  46. omnimancer_cli-0.2.26/omnimancer/tui/fleet/__init__.py +1 -0
  47. omnimancer_cli-0.2.26/omnimancer/tui/fleet/app.py +882 -0
  48. omnimancer_cli-0.2.26/omnimancer/tui/fleet/cli.py +146 -0
  49. omnimancer_cli-0.2.26/omnimancer/tui/fleet/models.py +243 -0
  50. omnimancer_cli-0.2.26/omnimancer/tui/fleet/sources.py +309 -0
  51. omnimancer_cli-0.2.26/omnimancer/tui/fleet/widgets.py +222 -0
  52. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/ui/streaming_display.py +41 -8
  53. omnimancer_cli-0.2.26/omnimancer/ui/turn_activity.py +100 -0
  54. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26/omnimancer_cli.egg-info}/PKG-INFO +68 -7
  55. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer_cli.egg-info/SOURCES.txt +25 -0
  56. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer_cli.egg-info/entry_points.txt +1 -0
  57. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer_cli.egg-info/requires.txt +8 -2
  58. omnimancer_cli-0.2.26/omnimancer_cli.egg-info/top_level.txt +2 -0
  59. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/pyproject.toml +29 -3
  60. omnimancer_cli-0.2.26/tests/test_config_migration_passthrough.py +88 -0
  61. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_hooks.py +42 -1
  62. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_import_validation.py +23 -0
  63. omnimancer_cli-0.2.26/tests/test_openai_compatible_provider.py +209 -0
  64. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_openai_provider_tools.py +39 -0
  65. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_openai_request_timeout.py +24 -0
  66. omnimancer_cli-0.2.26/tests/test_openai_streaming.py +252 -0
  67. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_program_executor.py +4 -3
  68. omnimancer_cli-0.2.26/tests/test_provider_aliases.py +117 -0
  69. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_provider_discovery.py +1 -0
  70. omnimancer_cli-0.2.22/omnimancer/cli/subagent.py +0 -125
  71. omnimancer_cli-0.2.22/omnimancer_cli.egg-info/top_level.txt +0 -1
  72. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/LICENSE +0 -0
  73. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/__main__.py +0 -0
  74. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/cli/__init__.py +0 -0
  75. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/cli/approval_formatter.py +0 -0
  76. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/cli/approval_prompt.py +0 -0
  77. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/cli/dynamic_commands.py +0 -0
  78. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/cli/handlers/__init__.py +0 -0
  79. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/cli/system_prompts.py +0 -0
  80. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/__init__.py +0 -0
  81. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/agent/__init__.py +0 -0
  82. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/agent/approval_context.py +0 -0
  83. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/agent/approval_interface.py +0 -0
  84. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/agent/approval_manager.py +0 -0
  85. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/agent/config.py +0 -0
  86. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/agent/file_system_manager.py +0 -0
  87. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/agent/read_before_write_errors.py +0 -0
  88. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/agent/tool_definitions.py +0 -0
  89. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/agent/types.py +0 -0
  90. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/agent/web_client.py +0 -0
  91. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/agent/workflow_orchestrator.py +0 -0
  92. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/agent_mode_manager.py +0 -0
  93. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/chat_manager.py +0 -0
  94. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/conversation_manager.py +0 -0
  95. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/env_loader.py +0 -0
  96. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/fallback_manager.py +0 -0
  97. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/health_monitor.py +0 -0
  98. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/history_manager.py +0 -0
  99. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/mcp_integration_layer.py +0 -0
  100. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/provider_registry.py +0 -0
  101. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/rate_limit_fallback.py +0 -0
  102. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/security/__init__.py +0 -0
  103. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/security/approval_workflow.py +0 -0
  104. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/security/permission_controller.py +0 -0
  105. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/security/permission_rules.py +0 -0
  106. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/security/sandbox_manager.py +0 -0
  107. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/security/security_manager.py +0 -0
  108. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/signal_handler.py +0 -0
  109. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/mcp/__init__.py +0 -0
  110. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/mcp/client.py +0 -0
  111. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/mcp/manager.py +0 -0
  112. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/providers/azure.py +0 -0
  113. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/providers/base.py +0 -0
  114. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/providers/bedrock.py +0 -0
  115. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/providers/claude.py +0 -0
  116. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/providers/claude_code.py +0 -0
  117. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/providers/claude_credentials.py +0 -0
  118. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/providers/cohere.py +0 -0
  119. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/providers/digitalocean.py +0 -0
  120. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/providers/gemini.py +0 -0
  121. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/providers/mistral.py +0 -0
  122. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/providers/ollama.py +0 -0
  123. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/providers/perplexity.py +0 -0
  124. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/providers/vertex.py +0 -0
  125. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/providers/xai.py +0 -0
  126. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/ui/__init__.py +0 -0
  127. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/ui/cancellation_handler.py +0 -0
  128. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/ui/progress_indicator.py +0 -0
  129. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/utils/__init__.py +0 -0
  130. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/utils/error_handler.py +0 -0
  131. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/utils/errors.py +0 -0
  132. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/utils/retry.py +0 -0
  133. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer_cli.egg-info/dependency_links.txt +0 -0
  134. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/setup.cfg +0 -0
  135. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_approval_integration_compatibility.py +0 -0
  136. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_approval_preview_format.py +0 -0
  137. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_approval_remember.py +0 -0
  138. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_approval_system.py +0 -0
  139. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_azure_provider.py +0 -0
  140. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_bedrock_provider.py +0 -0
  141. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_claude_code_provider.py +0 -0
  142. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_claude_credentials.py +0 -0
  143. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_claude_provider.py +0 -0
  144. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_cohere_provider.py +0 -0
  145. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_command_approval_security.py +0 -0
  146. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_config_comprehensive.py +0 -0
  147. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_conversation_history.py +0 -0
  148. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_conversation_manager.py +0 -0
  149. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_core_engine.py +0 -0
  150. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_digitalocean_provider.py +0 -0
  151. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_enhanced_model_info.py +0 -0
  152. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_env_loader.py +0 -0
  153. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_error_handling_robustness.py +0 -0
  154. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_execution_timeout_errors.py +0 -0
  155. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_fallback_manager.py +0 -0
  156. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_fixtures.py +0 -0
  157. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_gemini_provider.py +0 -0
  158. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_httpx_compatibility.py +0 -0
  159. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_interface_smoke_tests.py +0 -0
  160. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_mcp_integration.py +0 -0
  161. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_mcp_integration_layer.py +0 -0
  162. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_mcp_management_commands.py +0 -0
  163. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_mistral_provider.py +0 -0
  164. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_models.py +0 -0
  165. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_ollama_provider.py +0 -0
  166. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_openai_context_fit.py +0 -0
  167. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_openrouter_provider.py +0 -0
  168. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_permission_rules.py +0 -0
  169. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_perplexity_provider.py +0 -0
  170. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_provider_base.py +0 -0
  171. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_provider_capability_consistency.py +0 -0
  172. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_provider_integration.py +0 -0
  173. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_provider_registry.py +0 -0
  174. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_rate_limit_fallback.py +0 -0
  175. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_retry.py +0 -0
  176. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_security_validation.py +0 -0
  177. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_system_integration.py +0 -0
  178. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_system_prompts.py +0 -0
  179. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_tool_calling_integration.py +0 -0
  180. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_tool_handler.py +0 -0
  181. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_vertex_provider.py +0 -0
  182. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_web_request_markers.py +0 -0
  183. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_workflows_comprehensive.py +0 -0
  184. {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_xai_provider.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: omnimancer-cli
3
- Version: 0.2.22
3
+ Version: 0.2.26
4
4
  Summary: A unified command-line interface for multiple AI language models
5
5
  Author-email: Kellan Strong <kellan@jite.ai>
6
6
  License: MIT
@@ -22,7 +22,7 @@ Requires-Python: >=3.10
22
22
  Description-Content-Type: text/markdown
23
23
  License-File: LICENSE
24
24
  Requires-Dist: click>=8.0.0
25
- Requires-Dist: rich>=13.0.0
25
+ Requires-Dist: rich>=15.0.0
26
26
  Requires-Dist: httpx>=0.24.0
27
27
  Requires-Dist: pydantic>=2.0.0
28
28
  Requires-Dist: cryptography>=41.0.0
@@ -35,9 +35,13 @@ Requires-Dist: beautifulsoup4>=4.12.0
35
35
  Requires-Dist: html2text>=2020.1.16
36
36
  Requires-Dist: jsonschema>=4.17.0
37
37
  Requires-Dist: readability-lxml>=0.8.1
38
+ Requires-Dist: lxml_html_clean>=0.1.0
38
39
  Requires-Dist: PyYAML>=6.0.0
39
40
  Requires-Dist: toml>=0.10.2
40
- Requires-Dist: mcp>=1.0.0
41
+ Requires-Dist: mcp<2,>=1.0.0
42
+ Requires-Dist: prompt-toolkit<4.0,>=3.0.43
43
+ Provides-Extra: tui
44
+ Requires-Dist: textual>=8.0; extra == "tui"
41
45
  Provides-Extra: dev
42
46
  Requires-Dist: pytest>=7.0.0; extra == "dev"
43
47
  Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
@@ -47,11 +51,14 @@ Requires-Dist: black>=23.0.0; extra == "dev"
47
51
  Requires-Dist: isort>=5.12.0; extra == "dev"
48
52
  Requires-Dist: flake8>=6.0.0; extra == "dev"
49
53
  Requires-Dist: mypy>=1.0.0; extra == "dev"
54
+ Requires-Dist: textual>=8.0; extra == "dev"
50
55
  Dynamic: license-file
51
56
 
52
- # Omnimancer
57
+ <p align="center">
58
+ <img src="assets/omnimancer.png" alt="Omnimancer" width="600">
59
+ </p>
53
60
 
54
- A multi-model coding agent for the terminal. One tool, any LLM.
61
+ <p align="center"><strong>A multi-model coding agent for the terminal. One tool, any LLM.</strong></p>
55
62
 
56
63
  Omnimancer works like `claude -p` but isn't locked to a single provider. Point it at Claude, OpenAI, Gemini, Bedrock, Ollama, or any of 13+ supported backends and get a coding agent that reads files, writes code, runs commands, and iterates autonomously — with streaming responses, token/cost tracking, and structured JSON output for pipeline integration.
57
64
 
@@ -62,6 +69,10 @@ Beyond the basics, it ships with:
62
69
  - **Permission rules** — declarative allow/deny/ask rules per tool with regex matchers
63
70
  - **Subagents** — scoped child agents with their own prompt, tool whitelist, and model
64
71
  - **Layered security** — approval workflow, sensitive-path protection, and project-boundary enforcement
72
+ - **Named endpoint aliases** — run several OpenAI-compatible endpoints (vLLM, llama.cpp, LM Studio) side by side as first-class providers, keyless if the endpoint is
73
+ - **Modern input layer** — prompt_toolkit prompt with multiline editing, fuzzy `@file` mentions with content injection, live completion for providers/models, Ctrl-R history search
74
+ - **Prompt enhancement** — `e:` prefix or `/enhance` rewrites your draft with a configurable small model before sending (PromptFoundry meta-prompts)
75
+ - **Session approval modes** — `/accept edits|all` (or Shift+Tab) for claude-code-style auto-accept, always below your permission rules
65
76
 
66
77
  ## Install
67
78
 
@@ -122,7 +133,8 @@ object — including the tool calls the agent made along the way:
122
133
  ],
123
134
  "usage": {"input_tokens": 1523, "output_tokens": 892, "total_cost_usd": 0.04},
124
135
  "total_cost_usd": 0.04,
125
- "stop_reason": "end_turn"
136
+ "stop_reason": "end_turn",
137
+ "stop_cause": "done"
126
138
  }
127
139
  ```
128
140
 
@@ -132,6 +144,14 @@ On failure it stays valid JSON (stdout), with the error and any tool calls made:
132
144
  {"type": "result", "subtype": "error", "is_error": true, "error": "…", "tool_calls": [...]}
133
145
  ```
134
146
 
147
+ `stop_cause` reports why the run ended: `done` (model declared completion), `nudge_exhausted` (model stopped acting without declaring completion), `max_iterations` (tool-iteration cap hit), or `repeat_abort` (aborted after repeating an identical tool call). Exit codes:
148
+
149
+ | Exit code | Meaning |
150
+ |---|---|
151
+ | 0 | Run completed (`stop_cause` of `done` or `nudge_exhausted`) |
152
+ | 1 | Provider or engine error (`subtype: "error"`) |
153
+ | 3 | Run truncated: `max_iterations` or `repeat_abort` — the result is partial |
154
+
135
155
  For a live, line-by-line stream of what the agent is doing (assistant text,
136
156
  each `tool_use`, each `tool_result`, then the final `result`), use
137
157
  `--output-format stream-json`.
@@ -166,6 +186,7 @@ Streaming is automatic for providers that support it. Other providers fall back
166
186
  | Provider | Streaming |
167
187
  |----------|:---------:|
168
188
  | Claude (Anthropic) | Yes |
189
+ | OpenAI / openai-compatible / DigitalOcean | Yes (SSE, incl. tool-call deltas) |
169
190
  | All others | Fallback (full response) |
170
191
 
171
192
  Streaming works in both regular chat and agent mode (tool calling flow). The display uses a live-updating terminal panel that refreshes at 15fps.
@@ -244,7 +265,8 @@ Tool names match the agent's toolset (`Read`, `Write`, `Edit`, `Bash`, `Glob`, `
244
265
  | `/quit` | Exit (also: `/exit`, Ctrl+D) |
245
266
  | `/clear` | Clear terminal screen |
246
267
  | `/switch <provider> [model]` | Switch provider or model (uncataloged models are registered on the fly) |
247
- | `/models [filter]` | List available models (alias: `/model`) |
268
+ | `/models [filter]` | List available models; `/models refresh [provider]` pulls live catalogs |
269
+ | `/model` | Interactive model picker for the current provider |
248
270
  | `/providers` | List all providers with status |
249
271
  | `/agent on\|off\|status` | Toggle agent mode |
250
272
  | `/subagents [run <name> <task>]` | List or run scoped child agents |
@@ -253,6 +275,8 @@ Tool names match the agent's toolset (`Read`, `Write`, `Edit`, `Bash`, `Glob`, `
253
275
  | `/config remove-provider <name>` | Remove a provider |
254
276
  | `/hooks [list\|on\|off\|add\|remove]` | Manage lifecycle hooks |
255
277
  | `/permissions [list\|on\|off\|allow\|deny\|ask\|remove]` | Manage permission rules |
278
+ | `/accept [edits\|all\|off]` | Session approval mode (Shift+Tab cycles it at the prompt) |
279
+ | `/enhance [chat\|code\|image\|research] <draft>` | Rewrite a draft prompt with the enhancement model, confirm, send (the `e:` message prefix does it inline) |
256
280
  | `/save [name]` | Save conversation |
257
281
  | `/load [name]` | Load conversation |
258
282
  | `/list` | List saved conversations |
@@ -426,6 +450,39 @@ several endpoints side by side and switch between them with `/switch <provider>`
426
450
  Any OpenAI-compatible service (local proxy, gateway, self-hosted model) works by
427
451
  overriding `base_url` on the `openai` provider.
428
452
 
453
+
454
+ ### Self-hosted endpoints as named providers
455
+
456
+ Any number of OpenAI-compatible endpoints (vLLM, llama.cpp server, LM Studio, proxies) can be configured side by side under names you choose. `provider_type` picks the implementation; the entry name is yours:
457
+
458
+ ```bash
459
+ >>> /config set-provider gateway --type openai-compatible \
460
+ --base-url http://vllm-gateway.internal:8888/v1 --model qwen3-coder-30b
461
+ >>> /config set-provider local --type openai-compatible \
462
+ --base-url http://localhost:8000/v1 --model qwen3-coder-30b
463
+ >>> /switch local
464
+ ```
465
+
466
+ The `openai-compatible` type is keyless by default (`auth_type: "none"`), takes its model catalog from the endpoint's `/v1/models` (including each model's served context size — see `/models refresh`), and raises timeout errors with a cold-start hint since self-hosted gateways may load a model on first request. Set `providers.<name>.timeout` generously for such endpoints (e.g. `360`).
467
+
468
+ ### Prompt enhancement (optional)
469
+
470
+ `/enhance` and the `e:` message prefix rewrite a draft prompt with a dedicated model before sending, using the [PromptFoundry](https://github.com/gitshipdone/promptfoundry) meta-prompts (same author; that repo is the canonical source for the prompt texts and is being prepared for public release).
471
+
472
+ The feature is **opt-in**: it activates only when your config carries an `enhancement` block. Without one, `e:` is not intercepted and `/enhance` points you here.
473
+
474
+ ```json
475
+ "enhancement": {
476
+ "provider": "gateway",
477
+ "model": "qwen3-8b",
478
+ "temperature": 0.4,
479
+ "default_profile": "code",
480
+ "enabled": true
481
+ }
482
+ ```
483
+
484
+ `provider` must name an entry in `providers`; `default_profile` is one of `chat`, `code`, `image`, `research`. Failsafe chain: the configured model is tried first; if it is unreachable, the model you are currently chatting with rewrites the draft instead; if everything fails, the original draft is sent unchanged — enhancement never blocks a message.
485
+
429
486
  ### Environment variable overrides
430
487
 
431
488
  Environment variables take precedence over the saved config and are applied at
@@ -571,3 +628,7 @@ Tests follow TDD. 1,460 tests across providers, CLI, streaming, agent operations
571
628
  ## License
572
629
 
573
630
  MIT
631
+
632
+ ---
633
+
634
+ *This documentation was reviewed by a local Qwen model as part of OMN testing harness.*
@@ -1,6 +1,8 @@
1
- # Omnimancer
1
+ <p align="center">
2
+ <img src="assets/omnimancer.png" alt="Omnimancer" width="600">
3
+ </p>
2
4
 
3
- A multi-model coding agent for the terminal. One tool, any LLM.
5
+ <p align="center"><strong>A multi-model coding agent for the terminal. One tool, any LLM.</strong></p>
4
6
 
5
7
  Omnimancer works like `claude -p` but isn't locked to a single provider. Point it at Claude, OpenAI, Gemini, Bedrock, Ollama, or any of 13+ supported backends and get a coding agent that reads files, writes code, runs commands, and iterates autonomously — with streaming responses, token/cost tracking, and structured JSON output for pipeline integration.
6
8
 
@@ -11,6 +13,10 @@ Beyond the basics, it ships with:
11
13
  - **Permission rules** — declarative allow/deny/ask rules per tool with regex matchers
12
14
  - **Subagents** — scoped child agents with their own prompt, tool whitelist, and model
13
15
  - **Layered security** — approval workflow, sensitive-path protection, and project-boundary enforcement
16
+ - **Named endpoint aliases** — run several OpenAI-compatible endpoints (vLLM, llama.cpp, LM Studio) side by side as first-class providers, keyless if the endpoint is
17
+ - **Modern input layer** — prompt_toolkit prompt with multiline editing, fuzzy `@file` mentions with content injection, live completion for providers/models, Ctrl-R history search
18
+ - **Prompt enhancement** — `e:` prefix or `/enhance` rewrites your draft with a configurable small model before sending (PromptFoundry meta-prompts)
19
+ - **Session approval modes** — `/accept edits|all` (or Shift+Tab) for claude-code-style auto-accept, always below your permission rules
14
20
 
15
21
  ## Install
16
22
 
@@ -71,7 +77,8 @@ object — including the tool calls the agent made along the way:
71
77
  ],
72
78
  "usage": {"input_tokens": 1523, "output_tokens": 892, "total_cost_usd": 0.04},
73
79
  "total_cost_usd": 0.04,
74
- "stop_reason": "end_turn"
80
+ "stop_reason": "end_turn",
81
+ "stop_cause": "done"
75
82
  }
76
83
  ```
77
84
 
@@ -81,6 +88,14 @@ On failure it stays valid JSON (stdout), with the error and any tool calls made:
81
88
  {"type": "result", "subtype": "error", "is_error": true, "error": "…", "tool_calls": [...]}
82
89
  ```
83
90
 
91
+ `stop_cause` reports why the run ended: `done` (model declared completion), `nudge_exhausted` (model stopped acting without declaring completion), `max_iterations` (tool-iteration cap hit), or `repeat_abort` (aborted after repeating an identical tool call). Exit codes:
92
+
93
+ | Exit code | Meaning |
94
+ |---|---|
95
+ | 0 | Run completed (`stop_cause` of `done` or `nudge_exhausted`) |
96
+ | 1 | Provider or engine error (`subtype: "error"`) |
97
+ | 3 | Run truncated: `max_iterations` or `repeat_abort` — the result is partial |
98
+
84
99
  For a live, line-by-line stream of what the agent is doing (assistant text,
85
100
  each `tool_use`, each `tool_result`, then the final `result`), use
86
101
  `--output-format stream-json`.
@@ -115,6 +130,7 @@ Streaming is automatic for providers that support it. Other providers fall back
115
130
  | Provider | Streaming |
116
131
  |----------|:---------:|
117
132
  | Claude (Anthropic) | Yes |
133
+ | OpenAI / openai-compatible / DigitalOcean | Yes (SSE, incl. tool-call deltas) |
118
134
  | All others | Fallback (full response) |
119
135
 
120
136
  Streaming works in both regular chat and agent mode (tool calling flow). The display uses a live-updating terminal panel that refreshes at 15fps.
@@ -193,7 +209,8 @@ Tool names match the agent's toolset (`Read`, `Write`, `Edit`, `Bash`, `Glob`, `
193
209
  | `/quit` | Exit (also: `/exit`, Ctrl+D) |
194
210
  | `/clear` | Clear terminal screen |
195
211
  | `/switch <provider> [model]` | Switch provider or model (uncataloged models are registered on the fly) |
196
- | `/models [filter]` | List available models (alias: `/model`) |
212
+ | `/models [filter]` | List available models; `/models refresh [provider]` pulls live catalogs |
213
+ | `/model` | Interactive model picker for the current provider |
197
214
  | `/providers` | List all providers with status |
198
215
  | `/agent on\|off\|status` | Toggle agent mode |
199
216
  | `/subagents [run <name> <task>]` | List or run scoped child agents |
@@ -202,6 +219,8 @@ Tool names match the agent's toolset (`Read`, `Write`, `Edit`, `Bash`, `Glob`, `
202
219
  | `/config remove-provider <name>` | Remove a provider |
203
220
  | `/hooks [list\|on\|off\|add\|remove]` | Manage lifecycle hooks |
204
221
  | `/permissions [list\|on\|off\|allow\|deny\|ask\|remove]` | Manage permission rules |
222
+ | `/accept [edits\|all\|off]` | Session approval mode (Shift+Tab cycles it at the prompt) |
223
+ | `/enhance [chat\|code\|image\|research] <draft>` | Rewrite a draft prompt with the enhancement model, confirm, send (the `e:` message prefix does it inline) |
205
224
  | `/save [name]` | Save conversation |
206
225
  | `/load [name]` | Load conversation |
207
226
  | `/list` | List saved conversations |
@@ -375,6 +394,39 @@ several endpoints side by side and switch between them with `/switch <provider>`
375
394
  Any OpenAI-compatible service (local proxy, gateway, self-hosted model) works by
376
395
  overriding `base_url` on the `openai` provider.
377
396
 
397
+
398
+ ### Self-hosted endpoints as named providers
399
+
400
+ Any number of OpenAI-compatible endpoints (vLLM, llama.cpp server, LM Studio, proxies) can be configured side by side under names you choose. `provider_type` picks the implementation; the entry name is yours:
401
+
402
+ ```bash
403
+ >>> /config set-provider gateway --type openai-compatible \
404
+ --base-url http://vllm-gateway.internal:8888/v1 --model qwen3-coder-30b
405
+ >>> /config set-provider local --type openai-compatible \
406
+ --base-url http://localhost:8000/v1 --model qwen3-coder-30b
407
+ >>> /switch local
408
+ ```
409
+
410
+ The `openai-compatible` type is keyless by default (`auth_type: "none"`), takes its model catalog from the endpoint's `/v1/models` (including each model's served context size — see `/models refresh`), and raises timeout errors with a cold-start hint since self-hosted gateways may load a model on first request. Set `providers.<name>.timeout` generously for such endpoints (e.g. `360`).
411
+
412
+ ### Prompt enhancement (optional)
413
+
414
+ `/enhance` and the `e:` message prefix rewrite a draft prompt with a dedicated model before sending, using the [PromptFoundry](https://github.com/gitshipdone/promptfoundry) meta-prompts (same author; that repo is the canonical source for the prompt texts and is being prepared for public release).
415
+
416
+ The feature is **opt-in**: it activates only when your config carries an `enhancement` block. Without one, `e:` is not intercepted and `/enhance` points you here.
417
+
418
+ ```json
419
+ "enhancement": {
420
+ "provider": "gateway",
421
+ "model": "qwen3-8b",
422
+ "temperature": 0.4,
423
+ "default_profile": "code",
424
+ "enabled": true
425
+ }
426
+ ```
427
+
428
+ `provider` must name an entry in `providers`; `default_profile` is one of `chat`, `code`, `image`, `research`. Failsafe chain: the configured model is tried first; if it is unreachable, the model you are currently chatting with rewrites the draft instead; if everything fails, the original draft is sent unchanged — enhancement never blocks a message.
429
+
378
430
  ### Environment variable overrides
379
431
 
380
432
  Environment variables take precedence over the saved config and are applied at
@@ -520,3 +572,7 @@ Tests follow TDD. 1,460 tests across providers, CLI, streaming, agent operations
520
572
  ## License
521
573
 
522
574
  MIT
575
+
576
+ ---
577
+
578
+ *This documentation was reviewed by a local Qwen model as part of OMN testing harness.*
@@ -0,0 +1,215 @@
1
+ """Claude Code hook adapter for the omnimancer fleet event feed.
2
+
3
+ Registered in ``~/.claude/settings.json`` (all events, ``async: true``) as
4
+ the ``omn-fleet-hook`` console script. Each invocation reads ONE hook
5
+ payload from stdin, maps it to an omn.event.v1 line, and appends it to
6
+ ``~/.omnimancer/events/omn-<session_id>.jsonl`` — the same files the
7
+ ``omn fleet`` dashboard tails, so Claude Code sessions appear on the board
8
+ next to omn and codex agents.
9
+
10
+ DELIBERATELY a top-level, stdlib-only module: importing the omnimancer
11
+ package pulls pydantic and friends, and this process starts once per hook
12
+ event. Schema constants are duplicated from ``omnimancer/events/schema.py``
13
+ (the owner of the omn.event.v1 vocabulary) — keep them in sync.
14
+
15
+ Never blocks, never fails: any error exits 0 silently (a monitoring hook
16
+ must not be able to break a Claude session). ``OMNIMANCER_EVENTS=0``
17
+ disables it; ``OMNIMANCER_EVENTS_DIR`` overrides the output directory.
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import json
23
+ import os
24
+ import re
25
+ import sys
26
+ from datetime import datetime, timezone
27
+ from typing import Any, Dict, Optional
28
+
29
+ # Owner of these values: omnimancer/events/schema.py (omn.event.v1).
30
+ SCHEMA_VERSION = 1
31
+ PREVIEW_CHARS = 200
32
+ MESSAGE_PREVIEW_CHARS = 500
33
+
34
+ MAX_STDIN_BYTES = 1_000_000
35
+ # Strict uuid shape: anything looser produces filenames that fall outside
36
+ # SESSION_FILE_RE and would escape the retention/budget sweeps entirely.
37
+ SESSION_ID_RE = re.compile(
38
+ r"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-" r"[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"
39
+ )
40
+ TARGET_KEYS = ("command", "file_path", "path", "url", "pattern")
41
+
42
+ # Claude hook_event_name -> omn.event.v1 event name.
43
+ EVENT_MAP = {
44
+ "SessionStart": "session_start",
45
+ "UserPromptSubmit": "turn_start",
46
+ "PreToolUse": "tool_start",
47
+ "PostToolUse": "tool_end",
48
+ "PostToolUseFailure": "tool_end",
49
+ "Stop": "turn_end",
50
+ "SessionEnd": "session_end",
51
+ }
52
+
53
+
54
+ def _truncate(text: str, limit: int) -> str:
55
+ """Truncate to limit chars with a single-char ellipsis (schema rule)."""
56
+ if len(text) <= limit:
57
+ return text
58
+ return text[: limit - 1] + "…"
59
+
60
+
61
+ def _utc_now_iso() -> str:
62
+ """UTC ISO8601 with millisecond precision (schema rule)."""
63
+ return datetime.now(timezone.utc).isoformat(timespec="milliseconds")
64
+
65
+
66
+ def _events_dir() -> str:
67
+ """Resolve the events directory (env override for tests/relocation)."""
68
+ override = os.environ.get("OMNIMANCER_EVENTS_DIR")
69
+ if override:
70
+ return override
71
+ return os.path.join(os.path.expanduser("~"), ".omnimancer", "events")
72
+
73
+
74
+ def _target_from(tool_input: Any) -> Optional[str]:
75
+ """First actionable string from a tool_input dict, clipped."""
76
+ if not isinstance(tool_input, dict):
77
+ return None
78
+ for key in TARGET_KEYS:
79
+ value = tool_input.get(key)
80
+ if isinstance(value, str) and value:
81
+ return _truncate(value, PREVIEW_CHARS)
82
+ return None
83
+
84
+
85
+ def _build_data(payload: Dict[str, Any], event: str) -> Dict[str, Any]:
86
+ """Per-event data payload (fields mirror the omn emitters)."""
87
+ hook_event = payload.get("hook_event_name")
88
+ if event == "session_start":
89
+ return {
90
+ "harness": "claude",
91
+ "model": payload.get("model"),
92
+ "source": payload.get("source"),
93
+ "permission_mode": payload.get("permission_mode"),
94
+ }
95
+ if event == "turn_start":
96
+ # Live payloads carry "prompt"; "prompt_text" kept as a fallback
97
+ # for older documented shapes (live-verified 2026-08-01).
98
+ prompt = payload.get("prompt") or payload.get("prompt_text")
99
+ return {
100
+ "prompt_preview": (
101
+ _truncate(prompt, PREVIEW_CHARS) if isinstance(prompt, str) else None
102
+ ),
103
+ }
104
+ if event == "tool_start":
105
+ return {
106
+ "tool": payload.get("tool_name"),
107
+ "target": _target_from(payload.get("tool_input")),
108
+ "op_id": payload.get("tool_use_id"),
109
+ "invocation": "claude",
110
+ }
111
+ if event == "tool_end":
112
+ data: Dict[str, Any] = {
113
+ "tool": payload.get("tool_name"),
114
+ "op_id": payload.get("tool_use_id"),
115
+ "success": hook_event == "PostToolUse",
116
+ }
117
+ if hook_event == "PostToolUseFailure":
118
+ # Live payloads carry "error" (+ "is_interrupt" for user
119
+ # cancellation); "tool_output" kept as a fallback for older
120
+ # documented shapes (live-verified 2026-08-01).
121
+ error = payload.get("error") or payload.get("tool_output")
122
+ if isinstance(error, str):
123
+ data["error"] = _truncate(error, MESSAGE_PREVIEW_CHARS)
124
+ if payload.get("is_interrupt"):
125
+ data["was_cancelled"] = True
126
+ return data
127
+ if event == "turn_end":
128
+ message = payload.get("last_assistant_message")
129
+ return {
130
+ "last_message_preview": (
131
+ _truncate(message, MESSAGE_PREVIEW_CHARS)
132
+ if isinstance(message, str)
133
+ else None
134
+ ),
135
+ }
136
+ if event == "session_end":
137
+ return {"reason": payload.get("reason")}
138
+ return {}
139
+
140
+
141
+ def _agent_identity(payload: Dict[str, Any]) -> tuple:
142
+ """(agent_id, parent_id): subagent payloads carry agent_id/agent_type."""
143
+ agent_id = payload.get("agent_id")
144
+ if isinstance(agent_id, str) and agent_id:
145
+ agent_type = payload.get("agent_type") or "agent"
146
+ return f"subagent-{agent_type}-{agent_id[:8]}", "main"
147
+ return "main", None
148
+
149
+
150
+ def build_line(payload: Dict[str, Any]) -> Optional[str]:
151
+ """Map one Claude hook payload to an omn.event.v1 JSON line, or None."""
152
+ event = EVENT_MAP.get(str(payload.get("hook_event_name")))
153
+ if event is None:
154
+ return None
155
+ session_id = payload.get("session_id")
156
+ # fullmatch, not match: $ tolerates a trailing newline, which would
157
+ # mint a filename that escapes the retention/budget sweeps.
158
+ if not isinstance(session_id, str) or not SESSION_ID_RE.fullmatch(session_id):
159
+ return None
160
+ agent_id, parent_id = _agent_identity(payload)
161
+ envelope = {
162
+ "v": SCHEMA_VERSION,
163
+ "ts": _utc_now_iso(),
164
+ "event": event,
165
+ "session_id": session_id.lower(),
166
+ "agent_id": agent_id,
167
+ "parent_id": parent_id,
168
+ "pid": os.getpid(),
169
+ # -1: out-of-band producer (no shared counter across hook processes);
170
+ # consumers order by file-append position and ts.
171
+ "seq": -1,
172
+ "mode": "claude",
173
+ "cwd": payload.get("cwd"),
174
+ "data": _build_data(payload, event),
175
+ }
176
+ return json.dumps(envelope, ensure_ascii=False, separators=(",", ":"))
177
+
178
+
179
+ def main() -> int:
180
+ """Read one payload from stdin, append one line; always exit 0."""
181
+ try:
182
+ if os.environ.get("OMNIMANCER_EVENTS", "") == "0":
183
+ return 0
184
+ raw = sys.stdin.buffer.read(MAX_STDIN_BYTES)
185
+ payload = json.loads(raw.decode("utf-8", errors="replace"))
186
+ if not isinstance(payload, dict):
187
+ return 0
188
+ line = build_line(payload)
189
+ if line is None:
190
+ return 0
191
+ directory = _events_dir()
192
+ os.makedirs(directory, mode=0o700, exist_ok=True)
193
+ # mode= only applies at creation: repair pre-existing permissive
194
+ # dirs/files too — these paths carry prompt/command previews.
195
+ os.chmod(directory, 0o700)
196
+ path = os.path.join(directory, f"omn-{payload['session_id'].lower()}.jsonl")
197
+ fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0o600)
198
+ try:
199
+ if hasattr(os, "fchmod"):
200
+ os.fchmod(fd, 0o600)
201
+ else:
202
+ # Windows has no fchmod; best-effort by path so the write
203
+ # below still happens instead of dying on AttributeError.
204
+ os.chmod(path, 0o600)
205
+ os.write(fd, (line + "\n").encode("utf-8"))
206
+ finally:
207
+ os.close(fd)
208
+ except Exception:
209
+ # A monitoring hook must never surface an error into the session.
210
+ pass
211
+ return 0
212
+
213
+
214
+ if __name__ == "__main__":
215
+ sys.exit(main())
@@ -10,10 +10,18 @@ try:
10
10
 
11
11
  __version__ = _get_version("omnimancer-cli")
12
12
  except Exception:
13
- with open("pyproject.toml", "r") as f:
14
- project = f.readlines()
15
- version = [i for i in project if "version" in i].pop(0).strip()
16
- __version__ = version.split().pop().strip("\"'")
13
+ # Uninstalled checkout (PYTHONPATH usage): read pyproject.toml next to
14
+ # the package, never relative to cwd — the CLI can be invoked from any
15
+ # working directory.
16
+ try:
17
+ from pathlib import Path
18
+
19
+ _pyproject = Path(__file__).resolve().parent.parent / "pyproject.toml"
20
+ project = _pyproject.read_text().splitlines()
21
+ version = [i for i in project if "version" in i].pop(0).strip()
22
+ __version__ = version.split().pop().strip("\"'")
23
+ except Exception:
24
+ __version__ = "unknown"
17
25
  __author__ = "Omnimancer Team"
18
26
  __description__ = "Unified CLI for multiple AI language models"
19
27