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.
- {omnimancer_cli-0.2.22/omnimancer_cli.egg-info → omnimancer_cli-0.2.26}/PKG-INFO +68 -7
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/README.md +60 -4
- omnimancer_cli-0.2.26/omn_fleet_hook.py +215 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/__init__.py +12 -4
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/cli/agent_loop.py +84 -39
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/cli/approval_integration.py +54 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/cli/command_dispatch.py +326 -12
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/cli/commands.py +13 -10
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/cli/completion.py +83 -67
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/cli/display.py +55 -12
- omnimancer_cli-0.2.26/omnimancer/cli/file_mentions.py +170 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/cli/headless.py +155 -50
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/cli/interface.py +372 -84
- omnimancer_cli-0.2.26/omnimancer/cli/prompt_input.py +217 -0
- omnimancer_cli-0.2.26/omnimancer/cli/pt_completion.py +172 -0
- omnimancer_cli-0.2.26/omnimancer/cli/session.py +41 -0
- omnimancer_cli-0.2.26/omnimancer/cli/subagent.py +176 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/cli/tool_handler.py +39 -15
- omnimancer_cli-0.2.26/omnimancer/cli/turn_notify.py +170 -0
- omnimancer_cli-0.2.26/omnimancer/cli/usage.py +27 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/agent/program_executor.py +17 -5
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/agent/status_core.py +5 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/agent/status_manager.py +42 -10
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/agent_engine.py +162 -5
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/agent_managers.py +12 -1
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/config_manager.py +16 -2
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/config_migration.py +28 -2
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/config_validator.py +4 -1
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/engine.py +38 -1
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/hooks.py +4 -1
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/models.py +81 -4
- omnimancer_cli-0.2.26/omnimancer/core/prompt_enhancer.py +326 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/provider_capabilities.py +24 -8
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/provider_initializer.py +26 -7
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/security/audit_logger.py +7 -0
- omnimancer_cli-0.2.26/omnimancer/events/__init__.py +1 -0
- omnimancer_cli-0.2.26/omnimancer/events/emitter.py +502 -0
- omnimancer_cli-0.2.26/omnimancer/events/jsonl_writer.py +362 -0
- omnimancer_cli-0.2.26/omnimancer/events/schema.py +143 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/providers/__init__.py +2 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/providers/factory.py +26 -4
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/providers/openai.py +257 -17
- omnimancer_cli-0.2.26/omnimancer/providers/openai_compatible.py +143 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/providers/openrouter.py +2 -1
- omnimancer_cli-0.2.26/omnimancer/tui/__init__.py +5 -0
- omnimancer_cli-0.2.26/omnimancer/tui/fleet/__init__.py +1 -0
- omnimancer_cli-0.2.26/omnimancer/tui/fleet/app.py +882 -0
- omnimancer_cli-0.2.26/omnimancer/tui/fleet/cli.py +146 -0
- omnimancer_cli-0.2.26/omnimancer/tui/fleet/models.py +243 -0
- omnimancer_cli-0.2.26/omnimancer/tui/fleet/sources.py +309 -0
- omnimancer_cli-0.2.26/omnimancer/tui/fleet/widgets.py +222 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/ui/streaming_display.py +41 -8
- omnimancer_cli-0.2.26/omnimancer/ui/turn_activity.py +100 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26/omnimancer_cli.egg-info}/PKG-INFO +68 -7
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer_cli.egg-info/SOURCES.txt +25 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer_cli.egg-info/entry_points.txt +1 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer_cli.egg-info/requires.txt +8 -2
- omnimancer_cli-0.2.26/omnimancer_cli.egg-info/top_level.txt +2 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/pyproject.toml +29 -3
- omnimancer_cli-0.2.26/tests/test_config_migration_passthrough.py +88 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_hooks.py +42 -1
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_import_validation.py +23 -0
- omnimancer_cli-0.2.26/tests/test_openai_compatible_provider.py +209 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_openai_provider_tools.py +39 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_openai_request_timeout.py +24 -0
- omnimancer_cli-0.2.26/tests/test_openai_streaming.py +252 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_program_executor.py +4 -3
- omnimancer_cli-0.2.26/tests/test_provider_aliases.py +117 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_provider_discovery.py +1 -0
- omnimancer_cli-0.2.22/omnimancer/cli/subagent.py +0 -125
- omnimancer_cli-0.2.22/omnimancer_cli.egg-info/top_level.txt +0 -1
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/LICENSE +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/__main__.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/cli/__init__.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/cli/approval_formatter.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/cli/approval_prompt.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/cli/dynamic_commands.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/cli/handlers/__init__.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/cli/system_prompts.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/__init__.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/agent/__init__.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/agent/approval_context.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/agent/approval_interface.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/agent/approval_manager.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/agent/config.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/agent/file_system_manager.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/agent/read_before_write_errors.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/agent/tool_definitions.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/agent/types.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/agent/web_client.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/agent/workflow_orchestrator.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/agent_mode_manager.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/chat_manager.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/conversation_manager.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/env_loader.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/fallback_manager.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/health_monitor.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/history_manager.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/mcp_integration_layer.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/provider_registry.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/rate_limit_fallback.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/security/__init__.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/security/approval_workflow.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/security/permission_controller.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/security/permission_rules.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/security/sandbox_manager.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/security/security_manager.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/core/signal_handler.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/mcp/__init__.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/mcp/client.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/mcp/manager.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/providers/azure.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/providers/base.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/providers/bedrock.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/providers/claude.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/providers/claude_code.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/providers/claude_credentials.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/providers/cohere.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/providers/digitalocean.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/providers/gemini.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/providers/mistral.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/providers/ollama.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/providers/perplexity.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/providers/vertex.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/providers/xai.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/ui/__init__.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/ui/cancellation_handler.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/ui/progress_indicator.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/utils/__init__.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/utils/error_handler.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/utils/errors.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer/utils/retry.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/omnimancer_cli.egg-info/dependency_links.txt +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/setup.cfg +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_approval_integration_compatibility.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_approval_preview_format.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_approval_remember.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_approval_system.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_azure_provider.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_bedrock_provider.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_claude_code_provider.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_claude_credentials.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_claude_provider.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_cohere_provider.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_command_approval_security.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_config_comprehensive.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_conversation_history.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_conversation_manager.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_core_engine.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_digitalocean_provider.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_enhanced_model_info.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_env_loader.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_error_handling_robustness.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_execution_timeout_errors.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_fallback_manager.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_fixtures.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_gemini_provider.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_httpx_compatibility.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_interface_smoke_tests.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_mcp_integration.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_mcp_integration_layer.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_mcp_management_commands.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_mistral_provider.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_models.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_ollama_provider.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_openai_context_fit.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_openrouter_provider.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_permission_rules.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_perplexity_provider.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_provider_base.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_provider_capability_consistency.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_provider_integration.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_provider_registry.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_rate_limit_fallback.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_retry.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_security_validation.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_system_integration.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_system_prompts.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_tool_calling_integration.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_tool_handler.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_vertex_provider.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_web_request_markers.py +0 -0
- {omnimancer_cli-0.2.22 → omnimancer_cli-0.2.26}/tests/test_workflows_comprehensive.py +0 -0
- {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.
|
|
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>=
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
|