omnimancer-cli 0.2.24__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.24/omnimancer_cli.egg-info → omnimancer_cli-0.2.26}/PKG-INFO +16 -3
  2. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/README.md +10 -1
  3. omnimancer_cli-0.2.26/omn_fleet_hook.py +215 -0
  4. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/cli/agent_loop.py +84 -39
  5. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/cli/display.py +3 -0
  6. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/cli/headless.py +154 -27
  7. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/cli/interface.py +235 -35
  8. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/cli/prompt_input.py +42 -11
  9. omnimancer_cli-0.2.26/omnimancer/cli/session.py +41 -0
  10. omnimancer_cli-0.2.26/omnimancer/cli/subagent.py +176 -0
  11. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/cli/tool_handler.py +39 -15
  12. omnimancer_cli-0.2.26/omnimancer/cli/turn_notify.py +170 -0
  13. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/agent/program_executor.py +17 -5
  14. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/agent/status_core.py +5 -0
  15. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/agent/status_manager.py +42 -10
  16. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/agent_engine.py +162 -5
  17. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/agent_managers.py +12 -1
  18. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/config_migration.py +1 -0
  19. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/engine.py +38 -1
  20. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/hooks.py +4 -1
  21. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/models.py +35 -0
  22. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/security/audit_logger.py +7 -0
  23. omnimancer_cli-0.2.26/omnimancer/events/__init__.py +1 -0
  24. omnimancer_cli-0.2.26/omnimancer/events/emitter.py +502 -0
  25. omnimancer_cli-0.2.26/omnimancer/events/jsonl_writer.py +362 -0
  26. omnimancer_cli-0.2.26/omnimancer/events/schema.py +143 -0
  27. omnimancer_cli-0.2.26/omnimancer/tui/__init__.py +5 -0
  28. omnimancer_cli-0.2.26/omnimancer/tui/fleet/__init__.py +1 -0
  29. omnimancer_cli-0.2.26/omnimancer/tui/fleet/app.py +882 -0
  30. omnimancer_cli-0.2.26/omnimancer/tui/fleet/cli.py +146 -0
  31. omnimancer_cli-0.2.26/omnimancer/tui/fleet/models.py +243 -0
  32. omnimancer_cli-0.2.26/omnimancer/tui/fleet/sources.py +309 -0
  33. omnimancer_cli-0.2.26/omnimancer/tui/fleet/widgets.py +222 -0
  34. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/ui/streaming_display.py +30 -7
  35. omnimancer_cli-0.2.26/omnimancer/ui/turn_activity.py +100 -0
  36. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26/omnimancer_cli.egg-info}/PKG-INFO +16 -3
  37. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer_cli.egg-info/SOURCES.txt +15 -0
  38. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer_cli.egg-info/entry_points.txt +1 -0
  39. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer_cli.egg-info/requires.txt +6 -1
  40. omnimancer_cli-0.2.26/omnimancer_cli.egg-info/top_level.txt +2 -0
  41. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/pyproject.toml +25 -2
  42. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_config_migration_passthrough.py +7 -0
  43. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_hooks.py +42 -1
  44. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_program_executor.py +4 -3
  45. omnimancer_cli-0.2.24/omnimancer/cli/subagent.py +0 -125
  46. omnimancer_cli-0.2.24/omnimancer_cli.egg-info/top_level.txt +0 -1
  47. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/LICENSE +0 -0
  48. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/__init__.py +0 -0
  49. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/__main__.py +0 -0
  50. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/cli/__init__.py +0 -0
  51. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/cli/approval_formatter.py +0 -0
  52. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/cli/approval_integration.py +0 -0
  53. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/cli/approval_prompt.py +0 -0
  54. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/cli/command_dispatch.py +0 -0
  55. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/cli/commands.py +0 -0
  56. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/cli/completion.py +0 -0
  57. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/cli/dynamic_commands.py +0 -0
  58. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/cli/file_mentions.py +0 -0
  59. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/cli/handlers/__init__.py +0 -0
  60. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/cli/pt_completion.py +0 -0
  61. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/cli/system_prompts.py +0 -0
  62. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/cli/usage.py +0 -0
  63. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/__init__.py +0 -0
  64. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/agent/__init__.py +0 -0
  65. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/agent/approval_context.py +0 -0
  66. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/agent/approval_interface.py +0 -0
  67. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/agent/approval_manager.py +0 -0
  68. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/agent/config.py +0 -0
  69. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/agent/file_system_manager.py +0 -0
  70. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/agent/read_before_write_errors.py +0 -0
  71. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/agent/tool_definitions.py +0 -0
  72. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/agent/types.py +0 -0
  73. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/agent/web_client.py +0 -0
  74. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/agent/workflow_orchestrator.py +0 -0
  75. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/agent_mode_manager.py +0 -0
  76. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/chat_manager.py +0 -0
  77. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/config_manager.py +0 -0
  78. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/config_validator.py +0 -0
  79. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/conversation_manager.py +0 -0
  80. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/env_loader.py +0 -0
  81. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/fallback_manager.py +0 -0
  82. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/health_monitor.py +0 -0
  83. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/history_manager.py +0 -0
  84. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/mcp_integration_layer.py +0 -0
  85. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/prompt_enhancer.py +0 -0
  86. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/provider_capabilities.py +0 -0
  87. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/provider_initializer.py +0 -0
  88. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/provider_registry.py +0 -0
  89. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/rate_limit_fallback.py +0 -0
  90. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/security/__init__.py +0 -0
  91. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/security/approval_workflow.py +0 -0
  92. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/security/permission_controller.py +0 -0
  93. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/security/permission_rules.py +0 -0
  94. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/security/sandbox_manager.py +0 -0
  95. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/security/security_manager.py +0 -0
  96. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/core/signal_handler.py +0 -0
  97. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/mcp/__init__.py +0 -0
  98. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/mcp/client.py +0 -0
  99. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/mcp/manager.py +0 -0
  100. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/providers/__init__.py +0 -0
  101. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/providers/azure.py +0 -0
  102. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/providers/base.py +0 -0
  103. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/providers/bedrock.py +0 -0
  104. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/providers/claude.py +0 -0
  105. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/providers/claude_code.py +0 -0
  106. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/providers/claude_credentials.py +0 -0
  107. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/providers/cohere.py +0 -0
  108. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/providers/digitalocean.py +0 -0
  109. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/providers/factory.py +0 -0
  110. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/providers/gemini.py +0 -0
  111. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/providers/mistral.py +0 -0
  112. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/providers/ollama.py +0 -0
  113. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/providers/openai.py +0 -0
  114. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/providers/openai_compatible.py +0 -0
  115. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/providers/openrouter.py +0 -0
  116. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/providers/perplexity.py +0 -0
  117. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/providers/vertex.py +0 -0
  118. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/providers/xai.py +0 -0
  119. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/ui/__init__.py +0 -0
  120. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/ui/cancellation_handler.py +0 -0
  121. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/ui/progress_indicator.py +0 -0
  122. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/utils/__init__.py +0 -0
  123. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/utils/error_handler.py +0 -0
  124. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/utils/errors.py +0 -0
  125. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer/utils/retry.py +0 -0
  126. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/omnimancer_cli.egg-info/dependency_links.txt +0 -0
  127. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/setup.cfg +0 -0
  128. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_approval_integration_compatibility.py +0 -0
  129. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_approval_preview_format.py +0 -0
  130. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_approval_remember.py +0 -0
  131. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_approval_system.py +0 -0
  132. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_azure_provider.py +0 -0
  133. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_bedrock_provider.py +0 -0
  134. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_claude_code_provider.py +0 -0
  135. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_claude_credentials.py +0 -0
  136. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_claude_provider.py +0 -0
  137. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_cohere_provider.py +0 -0
  138. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_command_approval_security.py +0 -0
  139. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_config_comprehensive.py +0 -0
  140. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_conversation_history.py +0 -0
  141. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_conversation_manager.py +0 -0
  142. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_core_engine.py +0 -0
  143. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_digitalocean_provider.py +0 -0
  144. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_enhanced_model_info.py +0 -0
  145. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_env_loader.py +0 -0
  146. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_error_handling_robustness.py +0 -0
  147. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_execution_timeout_errors.py +0 -0
  148. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_fallback_manager.py +0 -0
  149. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_fixtures.py +0 -0
  150. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_gemini_provider.py +0 -0
  151. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_httpx_compatibility.py +0 -0
  152. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_import_validation.py +0 -0
  153. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_interface_smoke_tests.py +0 -0
  154. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_mcp_integration.py +0 -0
  155. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_mcp_integration_layer.py +0 -0
  156. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_mcp_management_commands.py +0 -0
  157. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_mistral_provider.py +0 -0
  158. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_models.py +0 -0
  159. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_ollama_provider.py +0 -0
  160. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_openai_compatible_provider.py +0 -0
  161. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_openai_context_fit.py +0 -0
  162. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_openai_provider_tools.py +0 -0
  163. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_openai_request_timeout.py +0 -0
  164. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_openai_streaming.py +0 -0
  165. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_openrouter_provider.py +0 -0
  166. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_permission_rules.py +0 -0
  167. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_perplexity_provider.py +0 -0
  168. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_provider_aliases.py +0 -0
  169. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_provider_base.py +0 -0
  170. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_provider_capability_consistency.py +0 -0
  171. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_provider_discovery.py +0 -0
  172. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_provider_integration.py +0 -0
  173. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_provider_registry.py +0 -0
  174. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_rate_limit_fallback.py +0 -0
  175. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_retry.py +0 -0
  176. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_security_validation.py +0 -0
  177. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_system_integration.py +0 -0
  178. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_system_prompts.py +0 -0
  179. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_tool_calling_integration.py +0 -0
  180. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_tool_handler.py +0 -0
  181. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_vertex_provider.py +0 -0
  182. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_web_request_markers.py +0 -0
  183. {omnimancer_cli-0.2.24 → omnimancer_cli-0.2.26}/tests/test_workflows_comprehensive.py +0 -0
  184. {omnimancer_cli-0.2.24 → 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.24
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,10 +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
41
  Requires-Dist: mcp<2,>=1.0.0
41
42
  Requires-Dist: prompt-toolkit<4.0,>=3.0.43
43
+ Provides-Extra: tui
44
+ Requires-Dist: textual>=8.0; extra == "tui"
42
45
  Provides-Extra: dev
43
46
  Requires-Dist: pytest>=7.0.0; extra == "dev"
44
47
  Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
@@ -48,6 +51,7 @@ Requires-Dist: black>=23.0.0; extra == "dev"
48
51
  Requires-Dist: isort>=5.12.0; extra == "dev"
49
52
  Requires-Dist: flake8>=6.0.0; extra == "dev"
50
53
  Requires-Dist: mypy>=1.0.0; extra == "dev"
54
+ Requires-Dist: textual>=8.0; extra == "dev"
51
55
  Dynamic: license-file
52
56
 
53
57
  <p align="center">
@@ -129,7 +133,8 @@ object — including the tool calls the agent made along the way:
129
133
  ],
130
134
  "usage": {"input_tokens": 1523, "output_tokens": 892, "total_cost_usd": 0.04},
131
135
  "total_cost_usd": 0.04,
132
- "stop_reason": "end_turn"
136
+ "stop_reason": "end_turn",
137
+ "stop_cause": "done"
133
138
  }
134
139
  ```
135
140
 
@@ -139,6 +144,14 @@ On failure it stays valid JSON (stdout), with the error and any tool calls made:
139
144
  {"type": "result", "subtype": "error", "is_error": true, "error": "…", "tool_calls": [...]}
140
145
  ```
141
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
+
142
155
  For a live, line-by-line stream of what the agent is doing (assistant text,
143
156
  each `tool_use`, each `tool_result`, then the final `result`), use
144
157
  `--output-format stream-json`.
@@ -77,7 +77,8 @@ object — including the tool calls the agent made along the way:
77
77
  ],
78
78
  "usage": {"input_tokens": 1523, "output_tokens": 892, "total_cost_usd": 0.04},
79
79
  "total_cost_usd": 0.04,
80
- "stop_reason": "end_turn"
80
+ "stop_reason": "end_turn",
81
+ "stop_cause": "done"
81
82
  }
82
83
  ```
83
84
 
@@ -87,6 +88,14 @@ On failure it stays valid JSON (stdout), with the error and any tool calls made:
87
88
  {"type": "result", "subtype": "error", "is_error": true, "error": "…", "tool_calls": [...]}
88
89
  ```
89
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
+
90
99
  For a live, line-by-line stream of what the agent is doing (assistant text,
91
100
  each `tool_use`, each `tool_result`, then the final `result`), use
92
101
  `--output-format stream-json`.
@@ -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,13 +10,12 @@ import ipaddress
10
10
  import logging
11
11
  import re
12
12
  import shlex
13
- import subprocess
14
13
  from difflib import SequenceMatcher
15
14
  from pathlib import Path
16
- from typing import Any, Optional
15
+ from typing import Any, Optional, cast
17
16
  from urllib.parse import urlparse, urlunparse
18
17
 
19
- from ..core.agent.types import Operation, OperationType
18
+ from ..core.agent.types import Operation, OperationResult, OperationType
20
19
 
21
20
  # ---------------------------------------------------------------------------
22
21
  # Web request constants and helpers
@@ -238,6 +237,7 @@ class AgentLoopMixin:
238
237
  until complete.
239
238
  """
240
239
  current_response = initial_response
240
+ self._turn_final_response = initial_response
241
241
 
242
242
  while True:
243
243
  self._show_assistant_message(
@@ -307,6 +307,8 @@ class AgentLoopMixin:
307
307
  )
308
308
  break
309
309
 
310
+ self._turn_final_response = next_response
311
+
310
312
  done_indicators = [
311
313
  "task is complete",
312
314
  "analysis complete",
@@ -447,6 +449,44 @@ class AgentLoopMixin:
447
449
 
448
450
  return best_match
449
451
 
452
+ async def _execute_marker_command(
453
+ self,
454
+ command: str,
455
+ args: list[str],
456
+ description: str,
457
+ ) -> OperationResult:
458
+ """Execute a marker command through the shared permission gate.
459
+
460
+ Args:
461
+ command: Executable name.
462
+ args: Command arguments without shell interpolation.
463
+ description: Human-readable operation description.
464
+
465
+ Returns:
466
+ The gated command result, or a failure if no agent engine exists.
467
+ """
468
+ agent_engine = getattr(self.engine, "agent_engine", None)
469
+ if agent_engine is None:
470
+ return OperationResult(
471
+ success=False,
472
+ error="Agent engine not available - command execution disabled",
473
+ )
474
+ operation = Operation(
475
+ type=OperationType.COMMAND_EXECUTE,
476
+ description=description,
477
+ data={"command": command, "args": args},
478
+ requires_approval=False,
479
+ )
480
+ result = await agent_engine.execute_with_approval(operation)
481
+ return cast(OperationResult, result)
482
+
483
+ @staticmethod
484
+ def _marker_command_output(result: OperationResult) -> str:
485
+ """Extract displayable stdout from an operation result."""
486
+ if isinstance(result.data, dict):
487
+ return str(result.data.get("stdout", ""))
488
+ return str(result.data or "")
489
+
450
490
  async def _parse_and_execute_operations(self, response_content: str) -> str:
451
491
  """Parse model response for operation markers and execute them."""
452
492
  updated_response = response_content
@@ -608,23 +648,28 @@ class AgentLoopMixin:
608
648
  pattern = match.group(1).strip()
609
649
 
610
650
  try:
611
- result = subprocess.run(
612
- f"find . -name '{pattern}' -type f 2>/dev/null | head -20",
613
- shell=True,
614
- capture_output=True,
615
- text=True,
616
- timeout=10,
651
+ result = await self._execute_marker_command(
652
+ "find",
653
+ [".", "-name", pattern, "-type", "f"],
654
+ f"Find files matching: {pattern}",
617
655
  )
618
- files = result.stdout.strip()
619
- if files:
656
+ if not result.success:
620
657
  updated_response = updated_response.replace(
621
658
  match.group(0),
622
- f"🔍 Found files matching '{pattern}':\n{files}",
659
+ f"❌ Find failed: {result.error}",
623
660
  )
624
661
  else:
662
+ files = "\n".join(
663
+ self._marker_command_output(result).splitlines()[:20]
664
+ ).strip()
665
+ if files:
666
+ replacement = (
667
+ f"🔍 Found files matching '{pattern}':\n{files}"
668
+ )
669
+ else:
670
+ replacement = f"🔍 No files found matching '{pattern}'"
625
671
  updated_response = updated_response.replace(
626
- match.group(0),
627
- f"🔍 No files found matching '{pattern}'",
672
+ match.group(0), replacement
628
673
  )
629
674
  except Exception as e:
630
675
  updated_response = updated_response.replace(
@@ -638,23 +683,26 @@ class AgentLoopMixin:
638
683
  search_text = match.group(1).strip()
639
684
 
640
685
  try:
641
- result = subprocess.run(
642
- f"grep -r -n '{search_text}' . 2>/dev/null | head -20",
643
- shell=True,
644
- capture_output=True,
645
- text=True,
646
- timeout=10,
686
+ result = await self._execute_marker_command(
687
+ "grep",
688
+ ["-r", "-n", search_text, "."],
689
+ f"Search files for: {search_text}",
647
690
  )
648
- matches = result.stdout.strip()
649
- if matches:
691
+ if not result.success:
650
692
  updated_response = updated_response.replace(
651
693
  match.group(0),
652
- f"🔍 Found '{search_text}' in:\n{matches}",
694
+ f"❌ Search failed: {result.error}",
653
695
  )
654
696
  else:
697
+ matches = "\n".join(
698
+ self._marker_command_output(result).splitlines()[:20]
699
+ ).strip()
700
+ if matches:
701
+ replacement = f"🔍 Found '{search_text}' in:\n{matches}"
702
+ else:
703
+ replacement = f"🔍 No matches found for '{search_text}'"
655
704
  updated_response = updated_response.replace(
656
- match.group(0),
657
- f"🔍 No matches found for '{search_text}'",
705
+ match.group(0), replacement
658
706
  )
659
707
  except Exception as e:
660
708
  updated_response = updated_response.replace(
@@ -707,22 +755,19 @@ class AgentLoopMixin:
707
755
 
708
756
  if cmd_base in safe_commands:
709
757
  try:
710
- result = subprocess.run(
711
- command,
712
- shell=True,
713
- capture_output=True,
714
- text=True,
715
- timeout=10,
716
- executable="/bin/bash",
717
- )
718
- output = (
719
- result.stdout.strip()
720
- if result.returncode == 0
721
- else f"Error: {result.stderr}"
758
+ parts = shlex.split(command)
759
+ result = await self._execute_marker_command(
760
+ parts[0],
761
+ parts[1:],
762
+ f"Execute safe command: {command[:50]}",
722
763
  )
764
+ if result.success:
765
+ output = self._marker_command_output(result).strip()
766
+ replacement = f"✅ `{command}`\n{output}"
767
+ else:
768
+ replacement = f"❌ Failed: {result.error}"
723
769
  updated_response = updated_response.replace(
724
- match.group(0),
725
- f"✅ `{command}`\n{output}",
770
+ match.group(0), replacement
726
771
  )
727
772
  except Exception as e:
728
773
  updated_response = updated_response.replace(
@@ -412,6 +412,9 @@ Model available: {'Yes' if model_info else 'No'}"""
412
412
  self.display_manager.show_message(message, MessageType.WARNING)
413
413
 
414
414
  def _show_token_status(self, response: Any) -> None:
415
+ notifier = getattr(self, "turn_notifier", None)
416
+ if notifier is not None:
417
+ notifier.record_assistant(getattr(response, "content", None), response)
415
418
  usage = getattr(self, "usage", None)
416
419
  if usage is not None:
417
420
  usage.add(response)