omnimancer-cli 0.2.4__tar.gz → 0.2.7__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. {omnimancer_cli-0.2.4/omnimancer_cli.egg-info → omnimancer_cli-0.2.7}/PKG-INFO +176 -13
  2. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/README.md +174 -12
  3. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/cli/approval_integration.py +5 -0
  4. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/cli/command_dispatch.py +359 -0
  5. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/cli/commands.py +4 -0
  6. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/cli/completion.py +14 -0
  7. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/cli/display.py +33 -0
  8. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/cli/interface.py +57 -5
  9. omnimancer_cli-0.2.7/omnimancer/cli/subagent.py +125 -0
  10. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/cli/tool_handler.py +123 -9
  11. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/agent/file_system_manager.py +28 -1
  12. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/agent/tool_definitions.py +5 -1
  13. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/agent_engine.py +31 -716
  14. omnimancer_cli-0.2.7/omnimancer/core/agent_managers.py +725 -0
  15. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/engine.py +15 -0
  16. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/models.py +99 -8
  17. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/security/permission_controller.py +59 -15
  18. omnimancer_cli-0.2.7/omnimancer/core/security/permission_rules.py +85 -0
  19. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/security/security_manager.py +22 -6
  20. omnimancer_cli-0.2.7/omnimancer/mcp/client.py +379 -0
  21. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/mcp/manager.py +61 -0
  22. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7/omnimancer_cli.egg-info}/PKG-INFO +176 -13
  23. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer_cli.egg-info/SOURCES.txt +4 -0
  24. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer_cli.egg-info/requires.txt +1 -0
  25. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/pyproject.toml +2 -1
  26. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_hooks.py +1 -6
  27. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_mcp_integration.py +150 -278
  28. omnimancer_cli-0.2.7/tests/test_permission_rules.py +215 -0
  29. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_tool_handler.py +190 -0
  30. omnimancer_cli-0.2.4/omnimancer/mcp/client.py +0 -739
  31. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/LICENSE +0 -0
  32. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/__init__.py +0 -0
  33. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/__main__.py +0 -0
  34. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/cli/__init__.py +0 -0
  35. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/cli/agent_loop.py +0 -0
  36. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/cli/approval_formatter.py +0 -0
  37. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/cli/approval_prompt.py +0 -0
  38. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/cli/dynamic_commands.py +0 -0
  39. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/cli/handlers/__init__.py +0 -0
  40. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/cli/headless.py +0 -0
  41. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/cli/system_prompts.py +0 -0
  42. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/__init__.py +0 -0
  43. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/agent/__init__.py +0 -0
  44. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/agent/approval_context.py +0 -0
  45. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/agent/approval_interface.py +0 -0
  46. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/agent/approval_manager.py +0 -0
  47. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/agent/config.py +0 -0
  48. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/agent/program_executor.py +0 -0
  49. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/agent/read_before_write_errors.py +0 -0
  50. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/agent/status_core.py +0 -0
  51. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/agent/status_manager.py +0 -0
  52. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/agent/types.py +0 -0
  53. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/agent/web_client.py +0 -0
  54. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/agent/workflow_orchestrator.py +0 -0
  55. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/agent_mode_manager.py +0 -0
  56. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/chat_manager.py +0 -0
  57. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/config_manager.py +0 -0
  58. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/config_migration.py +0 -0
  59. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/config_validator.py +0 -0
  60. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/conversation_manager.py +0 -0
  61. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/env_loader.py +0 -0
  62. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/fallback_manager.py +0 -0
  63. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/health_monitor.py +0 -0
  64. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/history_manager.py +0 -0
  65. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/hooks.py +0 -0
  66. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/mcp_integration_layer.py +0 -0
  67. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/provider_capabilities.py +0 -0
  68. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/provider_initializer.py +0 -0
  69. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/provider_registry.py +0 -0
  70. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/security/__init__.py +0 -0
  71. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/security/approval_workflow.py +0 -0
  72. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/security/audit_logger.py +0 -0
  73. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/security/sandbox_manager.py +0 -0
  74. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/signal_handler.py +0 -0
  75. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/mcp/__init__.py +0 -0
  76. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/providers/__init__.py +0 -0
  77. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/providers/azure.py +0 -0
  78. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/providers/base.py +0 -0
  79. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/providers/bedrock.py +0 -0
  80. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/providers/claude.py +0 -0
  81. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/providers/claude_code.py +0 -0
  82. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/providers/claude_credentials.py +0 -0
  83. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/providers/cohere.py +0 -0
  84. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/providers/digitalocean.py +0 -0
  85. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/providers/factory.py +0 -0
  86. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/providers/gemini.py +0 -0
  87. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/providers/mistral.py +0 -0
  88. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/providers/ollama.py +0 -0
  89. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/providers/openai.py +0 -0
  90. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/providers/openrouter.py +0 -0
  91. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/providers/perplexity.py +0 -0
  92. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/providers/vertex.py +0 -0
  93. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/providers/xai.py +0 -0
  94. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/ui/__init__.py +0 -0
  95. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/ui/cancellation_handler.py +0 -0
  96. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/ui/progress_indicator.py +0 -0
  97. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/ui/streaming_display.py +0 -0
  98. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/utils/__init__.py +0 -0
  99. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/utils/error_handler.py +0 -0
  100. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/utils/errors.py +0 -0
  101. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/utils/retry.py +0 -0
  102. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer_cli.egg-info/dependency_links.txt +0 -0
  103. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer_cli.egg-info/entry_points.txt +0 -0
  104. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer_cli.egg-info/top_level.txt +0 -0
  105. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/setup.cfg +0 -0
  106. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_approval_integration_compatibility.py +0 -0
  107. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_approval_preview_format.py +0 -0
  108. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_approval_remember.py +0 -0
  109. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_approval_system.py +0 -0
  110. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_azure_provider.py +0 -0
  111. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_claude_credentials.py +0 -0
  112. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_claude_provider.py +0 -0
  113. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_cohere_provider.py +0 -0
  114. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_command_approval_security.py +0 -0
  115. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_config_comprehensive.py +0 -0
  116. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_conversation_history.py +0 -0
  117. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_conversation_manager.py +0 -0
  118. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_core_engine.py +0 -0
  119. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_digitalocean_provider.py +0 -0
  120. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_enhanced_model_info.py +0 -0
  121. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_env_loader.py +0 -0
  122. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_error_handling_robustness.py +0 -0
  123. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_execution_timeout_errors.py +0 -0
  124. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_fallback_manager.py +0 -0
  125. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_fixtures.py +0 -0
  126. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_gemini_provider.py +0 -0
  127. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_httpx_compatibility.py +0 -0
  128. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_import_validation.py +0 -0
  129. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_interface_smoke_tests.py +0 -0
  130. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_mcp_integration_layer.py +0 -0
  131. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_mistral_provider.py +0 -0
  132. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_models.py +0 -0
  133. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_ollama_provider.py +0 -0
  134. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_openai_context_fit.py +0 -0
  135. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_openai_provider_tools.py +0 -0
  136. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_perplexity_provider.py +0 -0
  137. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_program_executor.py +0 -0
  138. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_provider_base.py +0 -0
  139. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_provider_capability_consistency.py +0 -0
  140. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_provider_integration.py +0 -0
  141. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_provider_registry.py +0 -0
  142. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_retry.py +0 -0
  143. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_security_validation.py +0 -0
  144. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_system_integration.py +0 -0
  145. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_system_prompts.py +0 -0
  146. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_tool_calling_integration.py +0 -0
  147. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_workflows_comprehensive.py +0 -0
  148. {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/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.4
3
+ Version: 0.2.7
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
@@ -37,6 +37,7 @@ Requires-Dist: jsonschema>=4.17.0
37
37
  Requires-Dist: readability-lxml>=0.8.1
38
38
  Requires-Dist: PyYAML>=6.0.0
39
39
  Requires-Dist: toml>=0.10.2
40
+ Requires-Dist: mcp>=1.0.0
40
41
  Provides-Extra: dev
41
42
  Requires-Dist: pytest>=7.0.0; extra == "dev"
42
43
  Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
@@ -54,6 +55,14 @@ A multi-model coding agent for the terminal. One tool, any LLM.
54
55
 
55
56
  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.
56
57
 
58
+ Beyond the basics, it ships with:
59
+
60
+ - **MCP support** (stdio, SSE, and HTTP transports) for tools, resources, and prompts
61
+ - **Lifecycle hooks** — run shell commands on message/tool events, with blocking veto power
62
+ - **Permission rules** — declarative allow/deny/ask rules per tool with regex matchers
63
+ - **Subagents** — scoped child agents with their own prompt, tool whitelist, and model
64
+ - **Layered security** — approval workflow, sensitive-path protection, and project-boundary enforcement
65
+
57
66
  ## Install
58
67
 
59
68
  ```bash
@@ -84,8 +93,14 @@ omn -p "summarize this" --output-format stream-json # streaming JSON
84
93
 
85
94
  # Auto-approve all tool operations (CI/scripts)
86
95
  omn -p "fix the failing tests" --dangerously-skip-permissions
96
+
97
+ # Verbose output / explicit config file
98
+ omn -p "audit the security module" --verbose --config ~/.omnimancer/config.json
87
99
  ```
88
100
 
101
+ The `--provider`, `--model`, and `--base-url` flags are **session overrides**: they
102
+ modify the in-memory config for this run only and are never written back to disk.
103
+
89
104
  Headless mode with `--output-format json` emits a single structured result
90
105
  object — including the tool calls the agent made along the way:
91
106
 
@@ -121,9 +136,10 @@ each `tool_use`, each `tool_result`, then the final `result`), use
121
136
  ### Interactive mode
122
137
 
123
138
  ```bash
124
- omn # start interactive REPL
125
- omn --provider openai # start with a specific provider
126
- omn --no-approval # skip approval prompts
139
+ omn # start interactive REPL
140
+ omn --provider openai # start with a specific provider (session-only)
141
+ omn --no-approval # skip approval prompts
142
+ omn --dangerously-skip-permissions # auto-approve all tool operations
127
143
  ```
128
144
 
129
145
  Interactive mode gives you a REPL with streaming responses, token/cost display, and agent capabilities:
@@ -159,8 +175,9 @@ When agent mode is enabled (`/agent on`), the AI can autonomously:
159
175
  - **Execute shell commands** with security validation
160
176
  - **Search codebases** with fuzzy file matching (70% similarity threshold)
161
177
  - **Make HTTP requests** for API testing
178
+ - **Call MCP tools** from connected MCP servers
162
179
 
163
- All destructive operations require explicit approval. Reads and searches are auto-approved.
180
+ All destructive operations require explicit approval (or are governed by your [permission rules](#permission-rules)). Reads and searches are auto-approved.
164
181
 
165
182
  Providers that support native tool calling (Claude, OpenAI, Gemini) use structured function calls. Others fall back to operation markers parsed from the response text.
166
183
 
@@ -171,6 +188,31 @@ Providers that support native tool calling (Claude, OpenAI, Gemini) use structur
171
188
  tokens: 4210 in / 1893 out | ~$0.0412
172
189
  ```
173
190
 
191
+ ### Subagents
192
+
193
+ Subagents are scoped child agents with their own system prompt, tool whitelist, model override, and iteration cap. Each run uses an isolated conversation context, so a subagent never pollutes your main session — and its errors are caught and reported instead of crashing the REPL.
194
+
195
+ Define them in `config.json`:
196
+
197
+ ```json
198
+ "subagents": {
199
+ "reviewer": {
200
+ "description": "Reviews code for bugs and style issues",
201
+ "prompt": "You are a meticulous code reviewer...",
202
+ "tools": ["Read", "Grep", "Bash"],
203
+ "model": null,
204
+ "max_iterations": 10
205
+ }
206
+ }
207
+ ```
208
+
209
+ Tool names match the agent's toolset (`Read`, `Write`, `Edit`, `Bash`, `Glob`, `Grep`, `WebFetch`). `tools: null` inherits all tools; `model: null` inherits the session model.
210
+
211
+ ```
212
+ >>> /subagents # list configured subagents
213
+ >>> /subagents run reviewer check src/auth.py for security issues
214
+ ```
215
+
174
216
  ## Supported Providers
175
217
 
176
218
  | Provider | Tool Calling | Streaming | Notes |
@@ -199,22 +241,136 @@ Providers that support native tool calling (Claude, OpenAI, Gemini) use structur
199
241
  | `/quit` | Exit (also: `/exit`, Ctrl+D) |
200
242
  | `/clear` | Clear terminal screen |
201
243
  | `/switch <provider> [model]` | Switch provider or model |
202
- | `/models [filter]` | List available models |
244
+ | `/models [filter]` | List available models (alias: `/model`) |
203
245
  | `/providers` | List all providers with status |
204
246
  | `/agent on\|off\|status` | Toggle agent mode |
247
+ | `/subagents [run <name> <task>]` | List or run scoped child agents |
205
248
  | `/config show\|get\|set` | View or modify configuration |
206
249
  | `/config set-provider <name> [--api-key …] [--base-url …] [--model …]` | Create/update a provider |
207
250
  | `/config remove-provider <name>` | Remove a provider |
208
- | `/validate [provider]` | Validate provider configurations |
209
- | `/health [provider]` | Check provider health |
251
+ | `/hooks [list\|on\|off\|add\|remove]` | Manage lifecycle hooks |
252
+ | `/permissions [list\|on\|off\|allow\|deny\|ask\|remove]` | Manage permission rules |
210
253
  | `/save [name]` | Save conversation |
211
254
  | `/load [name]` | Load conversation |
212
255
  | `/list` | List saved conversations |
213
- | `/history` | Manage conversation history |
256
+ | `/history [recent\|search\|clear\|export\|stats]` | Manage conversation history |
214
257
  | `/tools` | Show available MCP tools |
215
- | `/mcp status\|health\|reload` | MCP server management |
258
+ | `/prompts [list\|<name> [key=value …]]` | List and render MCP prompts |
259
+ | `/mcp [status\|reload\|connect\|disconnect\|health] [server]` | MCP server management |
260
+ | `/add-model <name> <provider> [description]` | Register a custom model |
261
+ | `/remove-model <name> <provider>` | Unregister a custom model |
262
+ | `/list-custom-models` | List registered custom models |
216
263
  | `/status` | System status |
217
264
 
265
+ You can also define your own slash commands: drop `.json` or `.py` command
266
+ definitions into `~/.omnimancer/commands/` and they're loaded at startup.
267
+
268
+ ## MCP servers
269
+
270
+ Omnimancer's MCP layer is built on the official [`mcp` SDK](https://pypi.org/project/mcp/) and supports three transports — **stdio** (local subprocess), **SSE**, and **streamable HTTP** (remote) — and three MCP features: **tools** (exposed to the agent and listed via `/tools`), **resources**, and **prompts** (listed and rendered via `/prompts`).
271
+
272
+ Configure servers in `config.json`:
273
+
274
+ ```json
275
+ "mcp": {
276
+ "enabled": true,
277
+ "servers": {
278
+ "filesystem": {
279
+ "transport": "stdio",
280
+ "command": "npx",
281
+ "args": ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/project"],
282
+ "env": {},
283
+ "enabled": true,
284
+ "auto_approve": ["read_file"],
285
+ "timeout": 30
286
+ },
287
+ "remote-api": {
288
+ "transport": "http",
289
+ "url": "https://mcp.example.com/mcp",
290
+ "headers": {"Authorization": "Bearer ..."},
291
+ "enabled": true
292
+ }
293
+ }
294
+ }
295
+ ```
296
+
297
+ Stdio servers need `command` (plus optional `args`/`env`); SSE and HTTP servers need `url` (plus optional `headers` — e.g. a bearer token for authenticated servers). `auto_approve` lists tool names that skip the approval prompt for that server.
298
+
299
+ Manage servers at runtime with `/mcp status`, `/mcp connect [server]`, `/mcp disconnect [server]`, `/mcp reload [server]`, and `/mcp health [server]`.
300
+
301
+ MCP is optional: if the `mcp` package isn't installed, everything else still works — connecting to a server just tells you to `pip install mcp`.
302
+
303
+ ## Hooks
304
+
305
+ Hooks run shell commands at lifecycle events — for logging, notifications, linting, or policy enforcement. Four events are available:
306
+
307
+ | Event | Fires | Can block? |
308
+ |-------|-------|:---:|
309
+ | `pre_send_message` | Before a message is sent to the provider | Yes |
310
+ | `post_send_message` | After a successful provider response | No |
311
+ | `tool_use_request` | Before an agent tool/operation runs | Yes |
312
+ | `post_tool` | After a tool/operation completes | No |
313
+
314
+ A **blocking** hook vetoes the action if it exits non-zero (or times out). Hooks receive the event payload as JSON on stdin, plus `OMNIMANCER_HOOK_*` environment variables (`OMNIMANCER_HOOK_EVENT`, `OMNIMANCER_HOOK_MESSAGE`, …) for shell one-liners. Hook errors never crash the app.
315
+
316
+ Manage from the CLI:
317
+
318
+ ```
319
+ >>> /hooks # list configured hooks
320
+ >>> /hooks add post_tool notify --timeout 10 notify-send "tool ran"
321
+ >>> /hooks add tool_use_request guard --matcher "rm -rf" --blocking exit 1
322
+ >>> /hooks remove post_tool notify
323
+ >>> /hooks off # disable all hooks globally
324
+ ```
325
+
326
+ `--matcher` is a regex tested against the event's target (message text, file path, command) — the hook only fires on a match. Hooks are stored in `config.json` under `hooks`:
327
+
328
+ ```json
329
+ "hooks": {
330
+ "enabled": true,
331
+ "tool_use_request": [
332
+ {"name": "guard", "command": "exit 1", "matcher": "rm -rf", "blocking": true, "timeout": 30}
333
+ ]
334
+ }
335
+ ```
336
+
337
+ ## Permission rules
338
+
339
+ Permission rules decide what the agent may do before the approval prompt is ever shown. Each rule matches a tool (an operation type like `file_write`, `command_execute`, `web_request` — or `*` for any) plus an optional regex on the target (file path, command, URL).
340
+
341
+ Precedence: **deny > ask > allow > default** (normal approval workflow).
342
+
343
+ ```
344
+ >>> /permissions # list rules
345
+ >>> /permissions deny command_execute "rm -rf"
346
+ >>> /permissions ask file_write ".*prod.*"
347
+ >>> /permissions allow file_write "\.env$" # authorize project-local .env writes
348
+ >>> /permissions remove deny 1 # remove rule by index
349
+ >>> /permissions off # disable rules globally
350
+ ```
351
+
352
+ - `deny` rules refuse without prompting.
353
+ - `ask` rules force a prompt even if the operation was previously "remembered" as approved.
354
+ - `allow` rules auto-approve — and also authorize writes to sensitive-named files (like `.env`) inside the project.
355
+
356
+ Rules live in `config.json` under `permissions` (`always_deny` / `always_ask` / `always_allow` lists).
357
+
358
+ ## Security model
359
+
360
+ Three layers govern every agent operation, in order:
361
+
362
+ 1. **Permission rules** — your declarative deny/ask/allow rules (above).
363
+ 2. **Approval workflow** — interactive y/n with diff preview; approvals can be "remembered" per session.
364
+ 3. **Low-level security gate** — path and command validation that runs regardless of approval.
365
+
366
+ The security gate distinguishes:
367
+
368
+ - **Hard-restricted paths** — never writable, even with approval: system directories (`/etc`, `/sys`, `/proc`, `/boot`, `/usr/bin`, …), `~/.ssh`, `~/.aws/credentials`, `~/.config/gcloud`.
369
+ - **Sensitive name patterns** — denied by default but overridable by explicit approval or an `always_allow` rule: `.env*`, `*secret*`, `*key*`, `*token*`, `*password*`, `*credentials*`, `*.db`/`*.sqlite*`.
370
+ - **Project boundary** — writes must stay inside the directory `omn` was launched from (or a temp dir).
371
+
372
+ Plus: command whitelist/blacklist, sandboxed execution, read-before-write logic, and automatic backups of existing files before modification.
373
+
218
374
  ## Configuration
219
375
 
220
376
  ### API keys
@@ -358,17 +514,22 @@ export AZURE_OPENAI_ENDPOINT="https://your-resource.openai.azure.com/"
358
514
  omnimancer/
359
515
  ├── cli/ # CLI interface (modular)
360
516
  │ ├── interface.py # Core REPL loop, streaming integration
517
+ │ ├── headless.py # -p pipeline mode (text/json/stream-json)
361
518
  │ ├── command_dispatch.py # Slash command handlers
362
519
  │ ├── agent_loop.py # Marker-based agent workflow
363
520
  │ ├── tool_handler.py # Native tool call execution
521
+ │ ├── subagent.py # Scoped child agent runner
364
522
  │ ├── system_prompts.py # Prompt building
365
523
  │ ├── display.py # Terminal output & token status
366
524
  │ └── completion.py # Tab completion
367
525
  ├── core/ # Engine & business logic
368
526
  │ ├── engine.py # Provider abstraction & streaming delegation
369
527
  │ ├── agent_engine.py # Autonomous agent capabilities
528
+ │ ├── agent_managers.py # Executor/web/MCP/approval manager facades
529
+ │ ├── hooks.py # Lifecycle hooks manager
370
530
  │ ├── models.py # Data models (ChatResponse, StreamEvent, etc.)
371
- │ └── agent/ # File ops, approval, security
531
+ │ ├── agent/ # File ops, approval, tool definitions
532
+ │ └── security/ # Permission rules, path validation, sandboxing
372
533
  ├── providers/ # 13+ AI provider implementations
373
534
  │ ├── base.py # Provider interface (streaming fallback)
374
535
  │ ├── claude.py # Anthropic (native streaming & tool calling)
@@ -376,7 +537,9 @@ omnimancer/
376
537
  │ └── ...
377
538
  ├── ui/ # Terminal UI components
378
539
  │ └── streaming_display.py # Rich Live streaming display
379
- └── mcp/ # Model Context Protocol
540
+ └── mcp/ # Model Context Protocol (official mcp SDK)
541
+ ├── client.py # Per-server lifecycle (stdio/SSE/HTTP)
542
+ └── manager.py # Multi-server orchestration
380
543
  ```
381
544
 
382
545
  ### Streaming architecture
@@ -400,7 +563,7 @@ pip install -e ".[dev]"
400
563
  pytest tests/ -v
401
564
  ```
402
565
 
403
- Tests follow TDD. 1,260 tests across providers, CLI, streaming, agent operations, and integration scenarios.
566
+ Tests follow TDD. 1,460 tests across providers, CLI, streaming, agent operations, hooks, permissions, MCP, and integration scenarios (including a real MCP stdio server exercised end-to-end).
404
567
 
405
568
  ## License
406
569
 
@@ -4,6 +4,14 @@ A multi-model coding agent for the terminal. One tool, any LLM.
4
4
 
5
5
  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
6
 
7
+ Beyond the basics, it ships with:
8
+
9
+ - **MCP support** (stdio, SSE, and HTTP transports) for tools, resources, and prompts
10
+ - **Lifecycle hooks** — run shell commands on message/tool events, with blocking veto power
11
+ - **Permission rules** — declarative allow/deny/ask rules per tool with regex matchers
12
+ - **Subagents** — scoped child agents with their own prompt, tool whitelist, and model
13
+ - **Layered security** — approval workflow, sensitive-path protection, and project-boundary enforcement
14
+
7
15
  ## Install
8
16
 
9
17
  ```bash
@@ -34,8 +42,14 @@ omn -p "summarize this" --output-format stream-json # streaming JSON
34
42
 
35
43
  # Auto-approve all tool operations (CI/scripts)
36
44
  omn -p "fix the failing tests" --dangerously-skip-permissions
45
+
46
+ # Verbose output / explicit config file
47
+ omn -p "audit the security module" --verbose --config ~/.omnimancer/config.json
37
48
  ```
38
49
 
50
+ The `--provider`, `--model`, and `--base-url` flags are **session overrides**: they
51
+ modify the in-memory config for this run only and are never written back to disk.
52
+
39
53
  Headless mode with `--output-format json` emits a single structured result
40
54
  object — including the tool calls the agent made along the way:
41
55
 
@@ -71,9 +85,10 @@ each `tool_use`, each `tool_result`, then the final `result`), use
71
85
  ### Interactive mode
72
86
 
73
87
  ```bash
74
- omn # start interactive REPL
75
- omn --provider openai # start with a specific provider
76
- omn --no-approval # skip approval prompts
88
+ omn # start interactive REPL
89
+ omn --provider openai # start with a specific provider (session-only)
90
+ omn --no-approval # skip approval prompts
91
+ omn --dangerously-skip-permissions # auto-approve all tool operations
77
92
  ```
78
93
 
79
94
  Interactive mode gives you a REPL with streaming responses, token/cost display, and agent capabilities:
@@ -109,8 +124,9 @@ When agent mode is enabled (`/agent on`), the AI can autonomously:
109
124
  - **Execute shell commands** with security validation
110
125
  - **Search codebases** with fuzzy file matching (70% similarity threshold)
111
126
  - **Make HTTP requests** for API testing
127
+ - **Call MCP tools** from connected MCP servers
112
128
 
113
- All destructive operations require explicit approval. Reads and searches are auto-approved.
129
+ All destructive operations require explicit approval (or are governed by your [permission rules](#permission-rules)). Reads and searches are auto-approved.
114
130
 
115
131
  Providers that support native tool calling (Claude, OpenAI, Gemini) use structured function calls. Others fall back to operation markers parsed from the response text.
116
132
 
@@ -121,6 +137,31 @@ Providers that support native tool calling (Claude, OpenAI, Gemini) use structur
121
137
  tokens: 4210 in / 1893 out | ~$0.0412
122
138
  ```
123
139
 
140
+ ### Subagents
141
+
142
+ Subagents are scoped child agents with their own system prompt, tool whitelist, model override, and iteration cap. Each run uses an isolated conversation context, so a subagent never pollutes your main session — and its errors are caught and reported instead of crashing the REPL.
143
+
144
+ Define them in `config.json`:
145
+
146
+ ```json
147
+ "subagents": {
148
+ "reviewer": {
149
+ "description": "Reviews code for bugs and style issues",
150
+ "prompt": "You are a meticulous code reviewer...",
151
+ "tools": ["Read", "Grep", "Bash"],
152
+ "model": null,
153
+ "max_iterations": 10
154
+ }
155
+ }
156
+ ```
157
+
158
+ Tool names match the agent's toolset (`Read`, `Write`, `Edit`, `Bash`, `Glob`, `Grep`, `WebFetch`). `tools: null` inherits all tools; `model: null` inherits the session model.
159
+
160
+ ```
161
+ >>> /subagents # list configured subagents
162
+ >>> /subagents run reviewer check src/auth.py for security issues
163
+ ```
164
+
124
165
  ## Supported Providers
125
166
 
126
167
  | Provider | Tool Calling | Streaming | Notes |
@@ -149,22 +190,136 @@ Providers that support native tool calling (Claude, OpenAI, Gemini) use structur
149
190
  | `/quit` | Exit (also: `/exit`, Ctrl+D) |
150
191
  | `/clear` | Clear terminal screen |
151
192
  | `/switch <provider> [model]` | Switch provider or model |
152
- | `/models [filter]` | List available models |
193
+ | `/models [filter]` | List available models (alias: `/model`) |
153
194
  | `/providers` | List all providers with status |
154
195
  | `/agent on\|off\|status` | Toggle agent mode |
196
+ | `/subagents [run <name> <task>]` | List or run scoped child agents |
155
197
  | `/config show\|get\|set` | View or modify configuration |
156
198
  | `/config set-provider <name> [--api-key …] [--base-url …] [--model …]` | Create/update a provider |
157
199
  | `/config remove-provider <name>` | Remove a provider |
158
- | `/validate [provider]` | Validate provider configurations |
159
- | `/health [provider]` | Check provider health |
200
+ | `/hooks [list\|on\|off\|add\|remove]` | Manage lifecycle hooks |
201
+ | `/permissions [list\|on\|off\|allow\|deny\|ask\|remove]` | Manage permission rules |
160
202
  | `/save [name]` | Save conversation |
161
203
  | `/load [name]` | Load conversation |
162
204
  | `/list` | List saved conversations |
163
- | `/history` | Manage conversation history |
205
+ | `/history [recent\|search\|clear\|export\|stats]` | Manage conversation history |
164
206
  | `/tools` | Show available MCP tools |
165
- | `/mcp status\|health\|reload` | MCP server management |
207
+ | `/prompts [list\|<name> [key=value …]]` | List and render MCP prompts |
208
+ | `/mcp [status\|reload\|connect\|disconnect\|health] [server]` | MCP server management |
209
+ | `/add-model <name> <provider> [description]` | Register a custom model |
210
+ | `/remove-model <name> <provider>` | Unregister a custom model |
211
+ | `/list-custom-models` | List registered custom models |
166
212
  | `/status` | System status |
167
213
 
214
+ You can also define your own slash commands: drop `.json` or `.py` command
215
+ definitions into `~/.omnimancer/commands/` and they're loaded at startup.
216
+
217
+ ## MCP servers
218
+
219
+ Omnimancer's MCP layer is built on the official [`mcp` SDK](https://pypi.org/project/mcp/) and supports three transports — **stdio** (local subprocess), **SSE**, and **streamable HTTP** (remote) — and three MCP features: **tools** (exposed to the agent and listed via `/tools`), **resources**, and **prompts** (listed and rendered via `/prompts`).
220
+
221
+ Configure servers in `config.json`:
222
+
223
+ ```json
224
+ "mcp": {
225
+ "enabled": true,
226
+ "servers": {
227
+ "filesystem": {
228
+ "transport": "stdio",
229
+ "command": "npx",
230
+ "args": ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/project"],
231
+ "env": {},
232
+ "enabled": true,
233
+ "auto_approve": ["read_file"],
234
+ "timeout": 30
235
+ },
236
+ "remote-api": {
237
+ "transport": "http",
238
+ "url": "https://mcp.example.com/mcp",
239
+ "headers": {"Authorization": "Bearer ..."},
240
+ "enabled": true
241
+ }
242
+ }
243
+ }
244
+ ```
245
+
246
+ Stdio servers need `command` (plus optional `args`/`env`); SSE and HTTP servers need `url` (plus optional `headers` — e.g. a bearer token for authenticated servers). `auto_approve` lists tool names that skip the approval prompt for that server.
247
+
248
+ Manage servers at runtime with `/mcp status`, `/mcp connect [server]`, `/mcp disconnect [server]`, `/mcp reload [server]`, and `/mcp health [server]`.
249
+
250
+ MCP is optional: if the `mcp` package isn't installed, everything else still works — connecting to a server just tells you to `pip install mcp`.
251
+
252
+ ## Hooks
253
+
254
+ Hooks run shell commands at lifecycle events — for logging, notifications, linting, or policy enforcement. Four events are available:
255
+
256
+ | Event | Fires | Can block? |
257
+ |-------|-------|:---:|
258
+ | `pre_send_message` | Before a message is sent to the provider | Yes |
259
+ | `post_send_message` | After a successful provider response | No |
260
+ | `tool_use_request` | Before an agent tool/operation runs | Yes |
261
+ | `post_tool` | After a tool/operation completes | No |
262
+
263
+ A **blocking** hook vetoes the action if it exits non-zero (or times out). Hooks receive the event payload as JSON on stdin, plus `OMNIMANCER_HOOK_*` environment variables (`OMNIMANCER_HOOK_EVENT`, `OMNIMANCER_HOOK_MESSAGE`, …) for shell one-liners. Hook errors never crash the app.
264
+
265
+ Manage from the CLI:
266
+
267
+ ```
268
+ >>> /hooks # list configured hooks
269
+ >>> /hooks add post_tool notify --timeout 10 notify-send "tool ran"
270
+ >>> /hooks add tool_use_request guard --matcher "rm -rf" --blocking exit 1
271
+ >>> /hooks remove post_tool notify
272
+ >>> /hooks off # disable all hooks globally
273
+ ```
274
+
275
+ `--matcher` is a regex tested against the event's target (message text, file path, command) — the hook only fires on a match. Hooks are stored in `config.json` under `hooks`:
276
+
277
+ ```json
278
+ "hooks": {
279
+ "enabled": true,
280
+ "tool_use_request": [
281
+ {"name": "guard", "command": "exit 1", "matcher": "rm -rf", "blocking": true, "timeout": 30}
282
+ ]
283
+ }
284
+ ```
285
+
286
+ ## Permission rules
287
+
288
+ Permission rules decide what the agent may do before the approval prompt is ever shown. Each rule matches a tool (an operation type like `file_write`, `command_execute`, `web_request` — or `*` for any) plus an optional regex on the target (file path, command, URL).
289
+
290
+ Precedence: **deny > ask > allow > default** (normal approval workflow).
291
+
292
+ ```
293
+ >>> /permissions # list rules
294
+ >>> /permissions deny command_execute "rm -rf"
295
+ >>> /permissions ask file_write ".*prod.*"
296
+ >>> /permissions allow file_write "\.env$" # authorize project-local .env writes
297
+ >>> /permissions remove deny 1 # remove rule by index
298
+ >>> /permissions off # disable rules globally
299
+ ```
300
+
301
+ - `deny` rules refuse without prompting.
302
+ - `ask` rules force a prompt even if the operation was previously "remembered" as approved.
303
+ - `allow` rules auto-approve — and also authorize writes to sensitive-named files (like `.env`) inside the project.
304
+
305
+ Rules live in `config.json` under `permissions` (`always_deny` / `always_ask` / `always_allow` lists).
306
+
307
+ ## Security model
308
+
309
+ Three layers govern every agent operation, in order:
310
+
311
+ 1. **Permission rules** — your declarative deny/ask/allow rules (above).
312
+ 2. **Approval workflow** — interactive y/n with diff preview; approvals can be "remembered" per session.
313
+ 3. **Low-level security gate** — path and command validation that runs regardless of approval.
314
+
315
+ The security gate distinguishes:
316
+
317
+ - **Hard-restricted paths** — never writable, even with approval: system directories (`/etc`, `/sys`, `/proc`, `/boot`, `/usr/bin`, …), `~/.ssh`, `~/.aws/credentials`, `~/.config/gcloud`.
318
+ - **Sensitive name patterns** — denied by default but overridable by explicit approval or an `always_allow` rule: `.env*`, `*secret*`, `*key*`, `*token*`, `*password*`, `*credentials*`, `*.db`/`*.sqlite*`.
319
+ - **Project boundary** — writes must stay inside the directory `omn` was launched from (or a temp dir).
320
+
321
+ Plus: command whitelist/blacklist, sandboxed execution, read-before-write logic, and automatic backups of existing files before modification.
322
+
168
323
  ## Configuration
169
324
 
170
325
  ### API keys
@@ -308,17 +463,22 @@ export AZURE_OPENAI_ENDPOINT="https://your-resource.openai.azure.com/"
308
463
  omnimancer/
309
464
  ├── cli/ # CLI interface (modular)
310
465
  │ ├── interface.py # Core REPL loop, streaming integration
466
+ │ ├── headless.py # -p pipeline mode (text/json/stream-json)
311
467
  │ ├── command_dispatch.py # Slash command handlers
312
468
  │ ├── agent_loop.py # Marker-based agent workflow
313
469
  │ ├── tool_handler.py # Native tool call execution
470
+ │ ├── subagent.py # Scoped child agent runner
314
471
  │ ├── system_prompts.py # Prompt building
315
472
  │ ├── display.py # Terminal output & token status
316
473
  │ └── completion.py # Tab completion
317
474
  ├── core/ # Engine & business logic
318
475
  │ ├── engine.py # Provider abstraction & streaming delegation
319
476
  │ ├── agent_engine.py # Autonomous agent capabilities
477
+ │ ├── agent_managers.py # Executor/web/MCP/approval manager facades
478
+ │ ├── hooks.py # Lifecycle hooks manager
320
479
  │ ├── models.py # Data models (ChatResponse, StreamEvent, etc.)
321
- │ └── agent/ # File ops, approval, security
480
+ │ ├── agent/ # File ops, approval, tool definitions
481
+ │ └── security/ # Permission rules, path validation, sandboxing
322
482
  ├── providers/ # 13+ AI provider implementations
323
483
  │ ├── base.py # Provider interface (streaming fallback)
324
484
  │ ├── claude.py # Anthropic (native streaming & tool calling)
@@ -326,7 +486,9 @@ omnimancer/
326
486
  │ └── ...
327
487
  ├── ui/ # Terminal UI components
328
488
  │ └── streaming_display.py # Rich Live streaming display
329
- └── mcp/ # Model Context Protocol
489
+ └── mcp/ # Model Context Protocol (official mcp SDK)
490
+ ├── client.py # Per-server lifecycle (stdio/SSE/HTTP)
491
+ └── manager.py # Multi-server orchestration
330
492
  ```
331
493
 
332
494
  ### Streaming architecture
@@ -350,7 +512,7 @@ pip install -e ".[dev]"
350
512
  pytest tests/ -v
351
513
  ```
352
514
 
353
- Tests follow TDD. 1,260 tests across providers, CLI, streaming, agent operations, and integration scenarios.
515
+ Tests follow TDD. 1,460 tests across providers, CLI, streaming, agent operations, hooks, permissions, MCP, and integration scenarios (including a real MCP stdio server exercised end-to-end).
354
516
 
355
517
  ## License
356
518
 
@@ -307,6 +307,11 @@ class CLIApprovalIntegration:
307
307
  if not self.enable_auto_approval:
308
308
  return None
309
309
 
310
+ # An always_ask permission rule forces a prompt, overriding any
311
+ # remembered auto-approval for this operation.
312
+ if operation.data.get("_force_prompt"):
313
+ return None
314
+
310
315
  try:
311
316
  # Generate operation signature for matching
312
317
  operation_signature = self._generate_operation_signature(operation)