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.
- {omnimancer_cli-0.2.4/omnimancer_cli.egg-info → omnimancer_cli-0.2.7}/PKG-INFO +176 -13
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/README.md +174 -12
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/cli/approval_integration.py +5 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/cli/command_dispatch.py +359 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/cli/commands.py +4 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/cli/completion.py +14 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/cli/display.py +33 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/cli/interface.py +57 -5
- omnimancer_cli-0.2.7/omnimancer/cli/subagent.py +125 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/cli/tool_handler.py +123 -9
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/agent/file_system_manager.py +28 -1
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/agent/tool_definitions.py +5 -1
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/agent_engine.py +31 -716
- omnimancer_cli-0.2.7/omnimancer/core/agent_managers.py +725 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/engine.py +15 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/models.py +99 -8
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/security/permission_controller.py +59 -15
- omnimancer_cli-0.2.7/omnimancer/core/security/permission_rules.py +85 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/security/security_manager.py +22 -6
- omnimancer_cli-0.2.7/omnimancer/mcp/client.py +379 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/mcp/manager.py +61 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7/omnimancer_cli.egg-info}/PKG-INFO +176 -13
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer_cli.egg-info/SOURCES.txt +4 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer_cli.egg-info/requires.txt +1 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/pyproject.toml +2 -1
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_hooks.py +1 -6
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_mcp_integration.py +150 -278
- omnimancer_cli-0.2.7/tests/test_permission_rules.py +215 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_tool_handler.py +190 -0
- omnimancer_cli-0.2.4/omnimancer/mcp/client.py +0 -739
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/LICENSE +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/__init__.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/__main__.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/cli/__init__.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/cli/agent_loop.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/cli/approval_formatter.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/cli/approval_prompt.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/cli/dynamic_commands.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/cli/handlers/__init__.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/cli/headless.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/cli/system_prompts.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/__init__.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/agent/__init__.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/agent/approval_context.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/agent/approval_interface.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/agent/approval_manager.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/agent/config.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/agent/program_executor.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/agent/read_before_write_errors.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/agent/status_core.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/agent/status_manager.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/agent/types.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/agent/web_client.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/agent/workflow_orchestrator.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/agent_mode_manager.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/chat_manager.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/config_manager.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/config_migration.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/config_validator.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/conversation_manager.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/env_loader.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/fallback_manager.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/health_monitor.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/history_manager.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/hooks.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/mcp_integration_layer.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/provider_capabilities.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/provider_initializer.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/provider_registry.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/security/__init__.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/security/approval_workflow.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/security/audit_logger.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/security/sandbox_manager.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/core/signal_handler.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/mcp/__init__.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/providers/__init__.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/providers/azure.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/providers/base.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/providers/bedrock.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/providers/claude.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/providers/claude_code.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/providers/claude_credentials.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/providers/cohere.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/providers/digitalocean.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/providers/factory.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/providers/gemini.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/providers/mistral.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/providers/ollama.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/providers/openai.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/providers/openrouter.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/providers/perplexity.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/providers/vertex.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/providers/xai.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/ui/__init__.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/ui/cancellation_handler.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/ui/progress_indicator.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/ui/streaming_display.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/utils/__init__.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/utils/error_handler.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/utils/errors.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer/utils/retry.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer_cli.egg-info/dependency_links.txt +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer_cli.egg-info/entry_points.txt +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/omnimancer_cli.egg-info/top_level.txt +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/setup.cfg +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_approval_integration_compatibility.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_approval_preview_format.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_approval_remember.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_approval_system.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_azure_provider.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_claude_credentials.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_claude_provider.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_cohere_provider.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_command_approval_security.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_config_comprehensive.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_conversation_history.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_conversation_manager.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_core_engine.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_digitalocean_provider.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_enhanced_model_info.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_env_loader.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_error_handling_robustness.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_execution_timeout_errors.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_fallback_manager.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_fixtures.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_gemini_provider.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_httpx_compatibility.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_import_validation.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_interface_smoke_tests.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_mcp_integration_layer.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_mistral_provider.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_models.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_ollama_provider.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_openai_context_fit.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_openai_provider_tools.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_perplexity_provider.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_program_executor.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_provider_base.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_provider_capability_consistency.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_provider_integration.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_provider_registry.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_retry.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_security_validation.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_system_integration.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_system_prompts.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_tool_calling_integration.py +0 -0
- {omnimancer_cli-0.2.4 → omnimancer_cli-0.2.7}/tests/test_workflows_comprehensive.py +0 -0
- {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.
|
|
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
|
|
125
|
-
omn --provider openai
|
|
126
|
-
omn --no-approval
|
|
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
|
-
| `/
|
|
209
|
-
| `/
|
|
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
|
-
| `/
|
|
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
|
-
│
|
|
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,
|
|
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
|
|
75
|
-
omn --provider openai
|
|
76
|
-
omn --no-approval
|
|
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
|
-
| `/
|
|
159
|
-
| `/
|
|
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
|
-
| `/
|
|
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
|
-
│
|
|
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,
|
|
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)
|