exaai-agent 2.1.2__tar.gz → 2.2.0__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.
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/PKG-INFO +29 -3
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/README.md +28 -2
- exaai_agent-2.2.0/exaaiagnt/dashboard/server.py +99 -0
- exaai_agent-2.2.0/exaaiagnt/dashboard/templates/index.html +232 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/interface/cli.py +11 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/llm/llm.py +43 -8
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/telemetry/tracer.py +17 -1
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/tools/executor.py +15 -3
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/tools/k8s_scanner/k8s_actions.py +6 -0
- exaai_agent-2.2.0/exaaiagnt/tools/k8s_scanner/k8s_actions_schema.xml +36 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/tools/prompt_injection/prompt_injection_actions.py +74 -0
- exaai_agent-2.2.0/exaaiagnt/tools/prompt_injection/prompt_injection_actions_schema.xml +28 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/tools/python/python_instance.py +5 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/tools/reporting/reporting_actions.py +34 -5
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/tools/web_search/web_search_actions.py +4 -2
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/pyproject.toml +1 -1
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/LICENSE +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/__init__.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/agents/ExaaiAgent/__init__.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/agents/ExaaiAgent/exaai_agent.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/agents/ExaaiAgent/system_prompt.jinja +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/agents/__init__.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/agents/agent_supervisor.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/agents/base_agent.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/agents/scan_modes.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/agents/shared_memory.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/agents/state.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/interface/__init__.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/interface/assets/tui_styles.tcss +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/interface/main.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/interface/tool_components/__init__.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/interface/tool_components/agents_graph_renderer.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/interface/tool_components/base_renderer.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/interface/tool_components/browser_renderer.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/interface/tool_components/file_edit_renderer.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/interface/tool_components/finish_renderer.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/interface/tool_components/notes_renderer.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/interface/tool_components/proxy_renderer.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/interface/tool_components/python_renderer.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/interface/tool_components/registry.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/interface/tool_components/reporting_renderer.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/interface/tool_components/scan_info_renderer.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/interface/tool_components/terminal_renderer.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/interface/tool_components/thinking_renderer.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/interface/tool_components/user_message_renderer.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/interface/tool_components/web_search_renderer.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/interface/tui.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/interface/utils.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/llm/__init__.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/llm/config.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/llm/fallback.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/llm/llm_traffic_controller.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/llm/memory_compressor.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/llm/output_processor.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/llm/request_queue.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/llm/utils.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/prompts/README.md +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/prompts/__init__.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/prompts/auto_loader.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/prompts/cloud/.gitkeep +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/prompts/cloud/aws_cloud_security.jinja +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/prompts/cloud/azure_cloud_security.jinja +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/prompts/cloud/gcp_cloud_security.jinja +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/prompts/cloud/kubernetes_security.jinja +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/prompts/coordination/root_agent.jinja +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/prompts/custom/.gitkeep +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/prompts/frameworks/fastapi.jinja +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/prompts/frameworks/modern_js_frameworks.jinja +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/prompts/frameworks/nextjs.jinja +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/prompts/protocols/graphql.jinja +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/prompts/reconnaissance/.gitkeep +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/prompts/technologies/firebase_firestore.jinja +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/prompts/technologies/supabase.jinja +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/prompts/vulnerabilities/advanced_recon.jinja +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/prompts/vulnerabilities/api_security.jinja +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/prompts/vulnerabilities/authentication_jwt.jinja +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/prompts/vulnerabilities/broken_function_level_authorization.jinja +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/prompts/vulnerabilities/business_logic.jinja +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/prompts/vulnerabilities/cache_poisoning.jinja +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/prompts/vulnerabilities/cloud_security.jinja +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/prompts/vulnerabilities/csrf.jinja +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/prompts/vulnerabilities/deserialization.jinja +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/prompts/vulnerabilities/graphql_security.jinja +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/prompts/vulnerabilities/high_impact_bugs.jinja +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/prompts/vulnerabilities/http_smuggling.jinja +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/prompts/vulnerabilities/idor.jinja +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/prompts/vulnerabilities/information_disclosure.jinja +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/prompts/vulnerabilities/insecure_file_uploads.jinja +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/prompts/vulnerabilities/mass_assignment.jinja +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/prompts/vulnerabilities/oauth_oidc.jinja +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/prompts/vulnerabilities/open_redirect.jinja +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/prompts/vulnerabilities/path_traversal_lfi_rfi.jinja +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/prompts/vulnerabilities/post_exploitation.jinja +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/prompts/vulnerabilities/privilege_escalation.jinja +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/prompts/vulnerabilities/prompt_injection.jinja +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/prompts/vulnerabilities/prototype_pollution.jinja +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/prompts/vulnerabilities/race_conditions.jinja +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/prompts/vulnerabilities/rce.jinja +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/prompts/vulnerabilities/react2shell.jinja +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/prompts/vulnerabilities/reconnaissance_osint.jinja +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/prompts/vulnerabilities/sql_injection.jinja +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/prompts/vulnerabilities/ssrf.jinja +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/prompts/vulnerabilities/ssti.jinja +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/prompts/vulnerabilities/subdomain_takeover.jinja +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/prompts/vulnerabilities/waf_bypass.jinja +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/prompts/vulnerabilities/websocket_security.jinja +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/prompts/vulnerabilities/xss.jinja +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/prompts/vulnerabilities/xxe.jinja +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/runtime/__init__.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/runtime/docker_runtime.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/runtime/runtime.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/runtime/tool_manager.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/runtime/tool_server.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/telemetry/__init__.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/tools/__init__.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/tools/agents_graph/__init__.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/tools/agents_graph/agents_graph_actions.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/tools/agents_graph/agents_graph_actions_schema.xml +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/tools/argument_parser.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/tools/browser/__init__.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/tools/browser/browser_actions.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/tools/browser/browser_actions_schema.xml +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/tools/browser/browser_instance.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/tools/browser/tab_manager.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/tools/file_edit/__init__.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/tools/file_edit/file_edit_actions.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/tools/file_edit/file_edit_actions_schema.xml +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/tools/finish/__init__.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/tools/finish/finish_actions.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/tools/finish/finish_actions_schema.xml +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/tools/k8s_scanner/__init__.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/tools/notes/__init__.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/tools/notes/notes_actions.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/tools/notes/notes_actions_schema.xml +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/tools/prompt_injection/__init__.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/tools/proxy/__init__.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/tools/proxy/proxy_actions.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/tools/proxy/proxy_actions_schema.xml +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/tools/proxy/proxy_manager.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/tools/python/__init__.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/tools/python/python_actions.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/tools/python/python_actions_schema.xml +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/tools/python/python_manager.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/tools/registry.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/tools/reporting/__init__.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/tools/reporting/reporting_actions_schema.xml +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/tools/response_analyzer.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/tools/smart_fuzzer.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/tools/terminal/__init__.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/tools/terminal/terminal_actions.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/tools/terminal/terminal_actions_schema.xml +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/tools/terminal/terminal_manager.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/tools/terminal/terminal_session.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/tools/thinking/__init__.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/tools/thinking/thinking_actions.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/tools/thinking/thinking_actions_schema.xml +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/tools/tool_prompts.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/tools/vuln_validator.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/tools/waf_bypass.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/tools/web_search/__init__.py +0 -0
- {exaai_agent-2.1.2 → exaai_agent-2.2.0}/exaaiagnt/tools/web_search/web_search_actions_schema.xml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: exaai-agent
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: ExaAi - Advanced AI Security Agent for Comprehensive Penetration Testing
|
|
5
5
|
License: Apache-2.0
|
|
6
6
|
License-File: LICENSE
|
|
@@ -151,8 +151,7 @@ ExaAiAgent is an elite AI-powered cybersecurity agent that acts like a real pene
|
|
|
151
151
|
# Install ExaAiAgent
|
|
152
152
|
|
|
153
153
|
# Method 1: Automated Script (Recommended)
|
|
154
|
-
|
|
155
|
-
|
|
154
|
+
pip install exaai-agent
|
|
156
155
|
# Method 2: pipx
|
|
157
156
|
pipx install exaai-agent
|
|
158
157
|
|
|
@@ -366,6 +365,33 @@ export PERPLEXITY_API_KEY="key" # For search capabilities
|
|
|
366
365
|
|
|
367
366
|
---
|
|
368
367
|
|
|
368
|
+
## 🛠️ Troubleshooting
|
|
369
|
+
|
|
370
|
+
### 🔧 Troubleshooting
|
|
371
|
+
|
|
372
|
+
#### Problem: "LLM Connection Failed" or Model Not Found
|
|
373
|
+
Modern models (like `gemini-3-pro-preview`) require the latest version of `litellm` to be recognized correctly.
|
|
374
|
+
|
|
375
|
+
**Solution: Update LiteLLM**
|
|
376
|
+
```bash
|
|
377
|
+
pip install -U litellm
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
**Linux/Debian Users (Externally Managed Environment):**
|
|
381
|
+
If you encounter permission errors or "externally-managed-environment", you may need to use a virtual environment (`venv`) or force a user install:
|
|
382
|
+
|
|
383
|
+
```bash
|
|
384
|
+
# Option 1: Virtual Environment (Recommended for Servers)
|
|
385
|
+
python3 -m venv venv
|
|
386
|
+
source venv/bin/activate
|
|
387
|
+
pip install exaai-agent
|
|
388
|
+
|
|
389
|
+
# Option 2: Force User Install
|
|
390
|
+
pip install -U litellm --user --break-system-packages
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
---
|
|
394
|
+
|
|
369
395
|
## 🤝 Contributing
|
|
370
396
|
|
|
371
397
|
We welcome contributions! Check out our [Contributing Guide](CONTRIBUTING.md).
|
|
@@ -109,8 +109,7 @@ ExaAiAgent is an elite AI-powered cybersecurity agent that acts like a real pene
|
|
|
109
109
|
# Install ExaAiAgent
|
|
110
110
|
|
|
111
111
|
# Method 1: Automated Script (Recommended)
|
|
112
|
-
|
|
113
|
-
|
|
112
|
+
pip install exaai-agent
|
|
114
113
|
# Method 2: pipx
|
|
115
114
|
pipx install exaai-agent
|
|
116
115
|
|
|
@@ -324,6 +323,33 @@ export PERPLEXITY_API_KEY="key" # For search capabilities
|
|
|
324
323
|
|
|
325
324
|
---
|
|
326
325
|
|
|
326
|
+
## 🛠️ Troubleshooting
|
|
327
|
+
|
|
328
|
+
### 🔧 Troubleshooting
|
|
329
|
+
|
|
330
|
+
#### Problem: "LLM Connection Failed" or Model Not Found
|
|
331
|
+
Modern models (like `gemini-3-pro-preview`) require the latest version of `litellm` to be recognized correctly.
|
|
332
|
+
|
|
333
|
+
**Solution: Update LiteLLM**
|
|
334
|
+
```bash
|
|
335
|
+
pip install -U litellm
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
**Linux/Debian Users (Externally Managed Environment):**
|
|
339
|
+
If you encounter permission errors or "externally-managed-environment", you may need to use a virtual environment (`venv`) or force a user install:
|
|
340
|
+
|
|
341
|
+
```bash
|
|
342
|
+
# Option 1: Virtual Environment (Recommended for Servers)
|
|
343
|
+
python3 -m venv venv
|
|
344
|
+
source venv/bin/activate
|
|
345
|
+
pip install exaai-agent
|
|
346
|
+
|
|
347
|
+
# Option 2: Force User Install
|
|
348
|
+
pip install -U litellm --user --break-system-packages
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
---
|
|
352
|
+
|
|
327
353
|
## 🤝 Contributing
|
|
328
354
|
|
|
329
355
|
We welcome contributions! Check out our [Contributing Guide](CONTRIBUTING.md).
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
from fastapi import FastAPI, WebSocket, WebSocketDisconnect
|
|
2
|
+
from fastapi.staticfiles import StaticFiles
|
|
3
|
+
from fastapi.responses import HTMLResponse
|
|
4
|
+
import uvicorn
|
|
5
|
+
import json
|
|
6
|
+
import asyncio
|
|
7
|
+
import logging
|
|
8
|
+
from typing import List, Dict, Any
|
|
9
|
+
import os
|
|
10
|
+
|
|
11
|
+
from exaaiagnt.telemetry.tracer import get_global_tracer
|
|
12
|
+
|
|
13
|
+
app = FastAPI(title="ExaAi Live Dashboard")
|
|
14
|
+
|
|
15
|
+
# Serve static files
|
|
16
|
+
static_dir = os.path.join(os.path.dirname(__file__), "static")
|
|
17
|
+
app.mount("/static", StaticFiles(directory=static_dir), name="static")
|
|
18
|
+
|
|
19
|
+
class ConnectionManager:
|
|
20
|
+
def __init__(self):
|
|
21
|
+
self.active_connections: List[WebSocket] = []
|
|
22
|
+
|
|
23
|
+
async def connect(self, websocket: WebSocket):
|
|
24
|
+
await websocket.accept()
|
|
25
|
+
self.active_connections.append(websocket)
|
|
26
|
+
|
|
27
|
+
def disconnect(self, websocket: WebSocket):
|
|
28
|
+
self.active_connections.remove(websocket)
|
|
29
|
+
|
|
30
|
+
async def broadcast(self, message: str):
|
|
31
|
+
for connection in self.active_connections:
|
|
32
|
+
try:
|
|
33
|
+
await connection.send_text(message)
|
|
34
|
+
except Exception:
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
manager = ConnectionManager()
|
|
38
|
+
|
|
39
|
+
@app.get("/")
|
|
40
|
+
async def get_dashboard():
|
|
41
|
+
html_path = os.path.join(os.path.dirname(__file__), "templates", "index.html")
|
|
42
|
+
with open(html_path, "r") as f:
|
|
43
|
+
return HTMLResponse(content=f.read())
|
|
44
|
+
|
|
45
|
+
@app.get("/api/stats")
|
|
46
|
+
async def get_stats():
|
|
47
|
+
tracer = get_global_tracer()
|
|
48
|
+
if not tracer:
|
|
49
|
+
return {"status": "Waiting for agent..."}
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
"agents_count": len(tracer.agents),
|
|
53
|
+
"vulnerabilities": len(tracer.vulnerability_reports),
|
|
54
|
+
"tool_calls": len(tracer.tool_executions),
|
|
55
|
+
"start_time": tracer.start_time,
|
|
56
|
+
"run_name": tracer.run_name
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
@app.get("/api/vulnerabilities")
|
|
60
|
+
async def get_vulns():
|
|
61
|
+
tracer = get_global_tracer()
|
|
62
|
+
if not tracer:
|
|
63
|
+
return []
|
|
64
|
+
return tracer.vulnerability_reports
|
|
65
|
+
|
|
66
|
+
@app.websocket("/ws")
|
|
67
|
+
async def websocket_endpoint(websocket: WebSocket):
|
|
68
|
+
await manager.connect(websocket)
|
|
69
|
+
try:
|
|
70
|
+
while True:
|
|
71
|
+
# Send updates every second
|
|
72
|
+
tracer = get_global_tracer()
|
|
73
|
+
if tracer:
|
|
74
|
+
data = {
|
|
75
|
+
"agents": tracer.agents,
|
|
76
|
+
"stats": {
|
|
77
|
+
"active": sum(1 for a in tracer.agents.values() if a.get("status") == "running"),
|
|
78
|
+
"completed": sum(1 for a in tracer.agents.values() if a.get("status") == "completed"),
|
|
79
|
+
"failed": sum(1 for a in tracer.agents.values() if a.get("status") == "failed"),
|
|
80
|
+
},
|
|
81
|
+
"recent_logs": tracer.chat_messages[-10:] if tracer.chat_messages else []
|
|
82
|
+
}
|
|
83
|
+
await websocket.send_json(data)
|
|
84
|
+
await asyncio.sleep(1)
|
|
85
|
+
except WebSocketDisconnect:
|
|
86
|
+
manager.disconnect(websocket)
|
|
87
|
+
except Exception as e:
|
|
88
|
+
logging.error(f"WebSocket error: {e}")
|
|
89
|
+
manager.disconnect(websocket)
|
|
90
|
+
|
|
91
|
+
def start_dashboard(host="0.0.0.0", port=8000):
|
|
92
|
+
"""Start the dashboard server in a background thread or process."""
|
|
93
|
+
config = uvicorn.Config(app, host=host, port=port, log_level="error")
|
|
94
|
+
server = uvicorn.Server(config)
|
|
95
|
+
# We'll run this in a thread from the main agent
|
|
96
|
+
import threading
|
|
97
|
+
t = threading.Thread(target=server.run, daemon=True)
|
|
98
|
+
t.start()
|
|
99
|
+
return t
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>ExaAi Mission Control</title>
|
|
7
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
8
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
9
|
+
<style>
|
|
10
|
+
body { background-color: #0f172a; color: #e2e8f0; font-family: 'Courier New', Courier, monospace; }
|
|
11
|
+
.neon-text { text-shadow: 0 0 5px #3b82f6, 0 0 10px #3b82f6; }
|
|
12
|
+
.neon-border { box-shadow: 0 0 5px #3b82f6; border-color: #3b82f6; }
|
|
13
|
+
.agent-card { transition: all 0.3s ease; }
|
|
14
|
+
.agent-card:hover { transform: translateY(-2px); box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); }
|
|
15
|
+
.vuln-critical { border-left: 4px solid #ef4444; background: rgba(239, 68, 68, 0.1); }
|
|
16
|
+
.vuln-high { border-left: 4px solid #f97316; background: rgba(249, 115, 22, 0.1); }
|
|
17
|
+
.vuln-medium { border-left: 4px solid #eab308; background: rgba(234, 179, 8, 0.1); }
|
|
18
|
+
::-webkit-scrollbar { width: 8px; }
|
|
19
|
+
::-webkit-scrollbar-track { background: #1e293b; }
|
|
20
|
+
::-webkit-scrollbar-thumb { background: #475569; border-radius: 4px; }
|
|
21
|
+
::-webkit-scrollbar-thumb:hover { background: #64748b; }
|
|
22
|
+
</style>
|
|
23
|
+
</head>
|
|
24
|
+
<body class="h-screen flex flex-col overflow-hidden">
|
|
25
|
+
<!-- Header -->
|
|
26
|
+
<header class="bg-slate-900 border-b border-slate-700 p-4 flex justify-between items-center shadow-lg z-10">
|
|
27
|
+
<div class="flex items-center gap-3">
|
|
28
|
+
<div class="w-3 h-3 rounded-full bg-green-500 animate-pulse"></div>
|
|
29
|
+
<h1 class="text-2xl font-bold tracking-wider text-blue-400 neon-text">EXAAI <span class="text-white">MISSION CONTROL</span></h1>
|
|
30
|
+
</div>
|
|
31
|
+
<div class="flex gap-6 text-sm">
|
|
32
|
+
<div class="flex flex-col items-end">
|
|
33
|
+
<span class="text-slate-400">STATUS</span>
|
|
34
|
+
<span class="text-green-400 font-bold">ONLINE</span>
|
|
35
|
+
</div>
|
|
36
|
+
<div class="flex flex-col items-end">
|
|
37
|
+
<span class="text-slate-400">AGENTS</span>
|
|
38
|
+
<span id="total-agents" class="text-blue-400 font-bold">0</span>
|
|
39
|
+
</div>
|
|
40
|
+
<div class="flex flex-col items-end">
|
|
41
|
+
<span class="text-slate-400">VULNERABILITIES</span>
|
|
42
|
+
<span id="total-vulns" class="text-red-500 font-bold neon-text">0</span>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
</header>
|
|
46
|
+
|
|
47
|
+
<!-- Main Content -->
|
|
48
|
+
<main class="flex-1 flex overflow-hidden">
|
|
49
|
+
<!-- Sidebar: Agent Tree -->
|
|
50
|
+
<aside class="w-1/4 bg-slate-900 border-r border-slate-700 flex flex-col">
|
|
51
|
+
<div class="p-3 border-b border-slate-700 bg-slate-800">
|
|
52
|
+
<h2 class="font-bold text-slate-300 flex items-center gap-2">
|
|
53
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"></path></svg>
|
|
54
|
+
Active Operations
|
|
55
|
+
</h2>
|
|
56
|
+
</div>
|
|
57
|
+
<div id="agent-list" class="flex-1 overflow-y-auto p-3 space-y-2">
|
|
58
|
+
<!-- Agents injected here -->
|
|
59
|
+
</div>
|
|
60
|
+
</aside>
|
|
61
|
+
|
|
62
|
+
<!-- Center: Live Feed & Stats -->
|
|
63
|
+
<section class="flex-1 flex flex-col bg-slate-900/50 relative">
|
|
64
|
+
<!-- Matrix Rain Background Effect (Optional/Subtle) -->
|
|
65
|
+
<div class="absolute inset-0 pointer-events-none opacity-5 bg-[url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI0IiBoZWlnaHQ9IjQiPgo8cmVjdCB3aWR0aD0iNCIgaGVpZ2h0PSI0IiBmaWxsPSIjMWUyOTNiIi8+CjxyZWN0IHdpZHRoPSIxIiBoZWlnaHQ9IjEiIGZpbGw9IiMzMzQxNTUiLz4KPC9zdmc+')]"></div>
|
|
66
|
+
|
|
67
|
+
<div class="h-1/2 border-b border-slate-700 flex">
|
|
68
|
+
<!-- Live Terminal Log -->
|
|
69
|
+
<div class="w-2/3 border-r border-slate-700 flex flex-col">
|
|
70
|
+
<div class="p-2 bg-slate-800 border-b border-slate-700 flex justify-between">
|
|
71
|
+
<span class="text-xs font-mono text-slate-400">>_ SYSTEM LOGS</span>
|
|
72
|
+
<span class="text-xs text-green-500">● LIVE</span>
|
|
73
|
+
</div>
|
|
74
|
+
<div id="console-logs" class="flex-1 overflow-y-auto p-4 font-mono text-xs text-slate-300 space-y-1 bg-black/40">
|
|
75
|
+
<!-- Logs injected here -->
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
<!-- Quick Stats Chart -->
|
|
80
|
+
<div class="w-1/3 p-4 bg-slate-800/30 flex flex-col items-center justify-center">
|
|
81
|
+
<canvas id="statusChart"></canvas>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
<!-- Vulnerabilities Panel -->
|
|
86
|
+
<div class="h-1/2 flex flex-col bg-slate-900">
|
|
87
|
+
<div class="p-3 border-b border-slate-700 bg-slate-800 flex justify-between items-center">
|
|
88
|
+
<h2 class="font-bold text-red-400 flex items-center gap-2">
|
|
89
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"></path></svg>
|
|
90
|
+
Detected Vulnerabilities
|
|
91
|
+
</h2>
|
|
92
|
+
<span class="text-xs text-slate-500">Auto-refreshing...</span>
|
|
93
|
+
</div>
|
|
94
|
+
<div id="vuln-list" class="flex-1 overflow-y-auto p-4 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
95
|
+
<!-- Vulns injected here -->
|
|
96
|
+
</div>
|
|
97
|
+
</div>
|
|
98
|
+
</section>
|
|
99
|
+
</main>
|
|
100
|
+
|
|
101
|
+
<script>
|
|
102
|
+
const wsProtocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
103
|
+
const socket = new WebSocket(`${wsProtocol}//${window.location.host}/ws`);
|
|
104
|
+
|
|
105
|
+
// Chart setup
|
|
106
|
+
const ctx = document.getElementById('statusChart').getContext('2d');
|
|
107
|
+
const statusChart = new Chart(ctx, {
|
|
108
|
+
type: 'doughnut',
|
|
109
|
+
data: {
|
|
110
|
+
labels: ['Active', 'Completed', 'Failed'],
|
|
111
|
+
datasets: [{
|
|
112
|
+
data: [0, 0, 0],
|
|
113
|
+
backgroundColor: ['#3b82f6', '#22c55e', '#ef4444'],
|
|
114
|
+
borderWidth: 0
|
|
115
|
+
}]
|
|
116
|
+
},
|
|
117
|
+
options: {
|
|
118
|
+
responsive: true,
|
|
119
|
+
plugins: {
|
|
120
|
+
legend: { position: 'bottom', labels: { color: '#94a3b8' } }
|
|
121
|
+
},
|
|
122
|
+
cutout: '70%'
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
socket.onmessage = function(event) {
|
|
127
|
+
const data = JSON.parse(event.data);
|
|
128
|
+
updateDashboard(data);
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
function updateDashboard(data) {
|
|
132
|
+
// Update Headers
|
|
133
|
+
document.getElementById('total-agents').textContent = Object.keys(data.agents).length;
|
|
134
|
+
|
|
135
|
+
// Update Agent List
|
|
136
|
+
const agentList = document.getElementById('agent-list');
|
|
137
|
+
agentList.innerHTML = '';
|
|
138
|
+
|
|
139
|
+
Object.values(data.agents).forEach(agent => {
|
|
140
|
+
const div = document.createElement('div');
|
|
141
|
+
div.className = `agent-card p-3 rounded bg-slate-800 border border-slate-700 ${agent.status === 'running' ? 'border-l-4 border-l-blue-500' : ''}`;
|
|
142
|
+
div.innerHTML = `
|
|
143
|
+
<div class="flex justify-between items-start mb-1">
|
|
144
|
+
<span class="font-bold text-xs text-slate-200 truncate" title="${agent.name}">${agent.name}</span>
|
|
145
|
+
<span class="text-[10px] px-1.5 py-0.5 rounded ${getStatusColor(agent.status)}">${agent.status}</span>
|
|
146
|
+
</div>
|
|
147
|
+
<div class="text-[10px] text-slate-500 truncate">${agent.id.substring(0, 8)}...</div>
|
|
148
|
+
<div class="text-[11px] text-slate-400 mt-1 line-clamp-2">${agent.task || 'No task description'}</div>
|
|
149
|
+
`;
|
|
150
|
+
agentList.appendChild(div);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// Update Logs
|
|
154
|
+
const logContainer = document.getElementById('console-logs');
|
|
155
|
+
logContainer.innerHTML = '';
|
|
156
|
+
data.recent_logs.forEach(log => {
|
|
157
|
+
const div = document.createElement('div');
|
|
158
|
+
const time = new Date(log.timestamp * 1000).toLocaleTimeString();
|
|
159
|
+
const color = log.role === 'assistant' ? 'text-blue-400' : 'text-slate-400';
|
|
160
|
+
div.innerHTML = `<span class="text-slate-600">[${time}]</span> <span class="${color}">${log.role.toUpperCase()}:</span> <span class="text-slate-300">${escapeHtml(log.content.substring(0, 150))}...</span>`;
|
|
161
|
+
logContainer.appendChild(div);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// Update Chart
|
|
165
|
+
statusChart.data.datasets[0].data = [
|
|
166
|
+
data.stats.active,
|
|
167
|
+
data.stats.completed,
|
|
168
|
+
data.stats.failed
|
|
169
|
+
];
|
|
170
|
+
statusChart.update();
|
|
171
|
+
|
|
172
|
+
// Update Vulns (Fetching separately or from WS if included)
|
|
173
|
+
fetchVulns();
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async function fetchVulns() {
|
|
177
|
+
try {
|
|
178
|
+
const response = await fetch('/api/vulnerabilities');
|
|
179
|
+
const vulns = await response.json();
|
|
180
|
+
document.getElementById('total-vulns').textContent = vulns.length;
|
|
181
|
+
|
|
182
|
+
const container = document.getElementById('vuln-list');
|
|
183
|
+
container.innerHTML = '';
|
|
184
|
+
|
|
185
|
+
if (vulns.length === 0) {
|
|
186
|
+
container.innerHTML = '<div class="col-span-full text-center text-slate-500 py-10">No vulnerabilities detected... yet.</div>';
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
vulns.forEach(v => {
|
|
191
|
+
const div = document.createElement('div');
|
|
192
|
+
const severityClass = `vuln-${v.severity.toLowerCase()}`;
|
|
193
|
+
div.className = `p-4 rounded bg-slate-800 border border-slate-700 ${severityClass}`;
|
|
194
|
+
div.innerHTML = `
|
|
195
|
+
<div class="flex justify-between items-center mb-2">
|
|
196
|
+
<h3 class="font-bold text-sm text-white">${v.title}</h3>
|
|
197
|
+
<span class="text-[10px] font-bold uppercase tracking-wider px-2 py-1 bg-black/30 rounded text-white">${v.severity}</span>
|
|
198
|
+
</div>
|
|
199
|
+
<p class="text-xs text-slate-400 line-clamp-3 mb-2">${v.content}</p>
|
|
200
|
+
<div class="text-[10px] text-slate-500">ID: ${v.report_id}</div>
|
|
201
|
+
`;
|
|
202
|
+
container.appendChild(div);
|
|
203
|
+
});
|
|
204
|
+
} catch (e) {
|
|
205
|
+
console.error("Failed to fetch vulns", e);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function getStatusColor(status) {
|
|
210
|
+
switch(status) {
|
|
211
|
+
case 'running': return 'bg-blue-900 text-blue-300';
|
|
212
|
+
case 'completed': return 'bg-green-900 text-green-300';
|
|
213
|
+
case 'failed': return 'bg-red-900 text-red-300';
|
|
214
|
+
default: return 'bg-slate-700 text-slate-300';
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function escapeHtml(text) {
|
|
219
|
+
if (!text) return "";
|
|
220
|
+
return text
|
|
221
|
+
.replace(/&/g, "&")
|
|
222
|
+
.replace(/</g, "<")
|
|
223
|
+
.replace(/>/g, ">")
|
|
224
|
+
.replace(/"/g, """)
|
|
225
|
+
.replace(/'/g, "'");
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Initial load
|
|
229
|
+
fetchVulns();
|
|
230
|
+
</script>
|
|
231
|
+
</body>
|
|
232
|
+
</html>
|
|
@@ -30,7 +30,18 @@ BANNER = r"""
|
|
|
30
30
|
"""
|
|
31
31
|
|
|
32
32
|
|
|
33
|
+
from exaaiagnt.dashboard.server import start_dashboard
|
|
34
|
+
|
|
33
35
|
async def run_cli(args: Any) -> None: # noqa: PLR0915
|
|
36
|
+
# Start the live dashboard
|
|
37
|
+
try:
|
|
38
|
+
start_dashboard()
|
|
39
|
+
console_temp = Console()
|
|
40
|
+
console_temp.print("[bold green]🚀 Live Dashboard available at: http://localhost:8000[/]")
|
|
41
|
+
except Exception as e:
|
|
42
|
+
import logging
|
|
43
|
+
logging.error(f"Failed to start dashboard: {e}")
|
|
44
|
+
|
|
34
45
|
# Detect if running in a real terminal or headless (pipe/background)
|
|
35
46
|
is_tty = sys.stdout.isatty()
|
|
36
47
|
console = Console(force_terminal=is_tty, no_color=not is_tty)
|
|
@@ -424,19 +424,54 @@ class LLM:
|
|
|
424
424
|
else:
|
|
425
425
|
completion_args["reasoning_effort"] = "high"
|
|
426
426
|
|
|
427
|
-
# Use Adaptive Traffic Controller for intelligent rate limiting
|
|
427
|
+
# Use Adaptive Traffic Controller for intelligent rate limiting with retries
|
|
428
428
|
controller = get_traffic_controller()
|
|
429
429
|
agent_id = self.agent_id or "unknown_agent"
|
|
430
430
|
|
|
431
431
|
async def do_request():
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
432
|
+
try:
|
|
433
|
+
from litellm import completion
|
|
434
|
+
return await completion(**completion_args, stream=False)
|
|
435
|
+
except litellm.RateLimitError:
|
|
436
|
+
# Let tenacity (in controller) handle retry
|
|
437
|
+
raise
|
|
438
|
+
except Exception as e:
|
|
439
|
+
# Log other transient errors for potential retry
|
|
440
|
+
logger.warning(f"Transient LLM error: {e}")
|
|
441
|
+
raise
|
|
442
|
+
|
|
443
|
+
# Wrap in retry logic
|
|
444
|
+
from tenacity import (
|
|
445
|
+
retry,
|
|
446
|
+
stop_after_attempt,
|
|
447
|
+
wait_exponential,
|
|
448
|
+
retry_if_exception_type,
|
|
449
|
+
)
|
|
450
|
+
import litellm
|
|
451
|
+
|
|
452
|
+
@retry(
|
|
453
|
+
retry=retry_if_exception_type((
|
|
454
|
+
litellm.RateLimitError,
|
|
455
|
+
litellm.ServiceUnavailableError,
|
|
456
|
+
litellm.APIConnectionError,
|
|
457
|
+
litellm.Timeout
|
|
458
|
+
)),
|
|
459
|
+
wait=wait_exponential(multiplier=1, min=4, max=60),
|
|
460
|
+
stop=stop_after_attempt(5),
|
|
461
|
+
reraise=True
|
|
439
462
|
)
|
|
463
|
+
async def execute_with_retries():
|
|
464
|
+
return await controller.queue_request(
|
|
465
|
+
do_request,
|
|
466
|
+
agent_id=agent_id,
|
|
467
|
+
priority=RequestPriority.NORMAL
|
|
468
|
+
)
|
|
469
|
+
|
|
470
|
+
try:
|
|
471
|
+
response = await execute_with_retries()
|
|
472
|
+
except Exception:
|
|
473
|
+
self._total_stats.failed_requests += 1
|
|
474
|
+
raise
|
|
440
475
|
|
|
441
476
|
self._total_stats.requests += 1
|
|
442
477
|
self._last_request_stats = RequestStats(requests=1)
|
|
@@ -137,19 +137,35 @@ class Tracer:
|
|
|
137
137
|
) -> int:
|
|
138
138
|
message_id = self._next_message_id
|
|
139
139
|
self._next_message_id += 1
|
|
140
|
+
|
|
141
|
+
# Ensure imports if missing
|
|
142
|
+
import time
|
|
140
143
|
|
|
141
144
|
message_data = {
|
|
142
145
|
"message_id": message_id,
|
|
143
146
|
"content": content,
|
|
144
147
|
"role": role,
|
|
145
148
|
"agent_id": agent_id,
|
|
146
|
-
"timestamp":
|
|
149
|
+
"timestamp": time.time(), # Use epoch time for easier frontend handling
|
|
147
150
|
"metadata": metadata or {},
|
|
148
151
|
}
|
|
149
152
|
|
|
150
153
|
self.chat_messages.append(message_data)
|
|
154
|
+
# Removed auto-save on every message to improve performance
|
|
151
155
|
return message_id
|
|
152
156
|
|
|
157
|
+
def get_dashboard_data(self) -> dict[str, Any]:
|
|
158
|
+
"""Return data formatted for the live dashboard."""
|
|
159
|
+
return {
|
|
160
|
+
"agents": self.agents,
|
|
161
|
+
"vulnerabilities": self.vulnerability_reports,
|
|
162
|
+
"stats": {
|
|
163
|
+
"active_agents": sum(1 for a in self.agents.values() if a.get("status") == "running"),
|
|
164
|
+
"total_requests": sum(1 for msg in self.chat_messages if msg["role"] == "assistant"),
|
|
165
|
+
"total_vulns": len(self.vulnerability_reports)
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
153
169
|
def log_tool_execution_start(self, agent_id: str, tool_name: str, args: dict[str, Any]) -> int:
|
|
154
170
|
execution_id = self._next_execution_id
|
|
155
171
|
self._next_execution_id += 1
|
|
@@ -90,11 +90,23 @@ async def _execute_tool_locally(tool_name: str, agent_state: Any | None, **kwarg
|
|
|
90
90
|
if needs_agent_state(tool_name):
|
|
91
91
|
if agent_state is None:
|
|
92
92
|
raise ValueError(f"Tool '{tool_name}' requires agent_state but none was provided.")
|
|
93
|
-
|
|
93
|
+
# Check if the tool function is a coroutine (async)
|
|
94
|
+
if inspect.iscoroutinefunction(tool_func):
|
|
95
|
+
result = await tool_func(agent_state=agent_state, **converted_kwargs)
|
|
96
|
+
else:
|
|
97
|
+
# Run synchronous blocking tools in a separate thread
|
|
98
|
+
import asyncio
|
|
99
|
+
result = await asyncio.to_thread(tool_func, agent_state=agent_state, **converted_kwargs)
|
|
94
100
|
else:
|
|
95
|
-
|
|
101
|
+
# Check if the tool function is a coroutine (async)
|
|
102
|
+
if inspect.iscoroutinefunction(tool_func):
|
|
103
|
+
result = await tool_func(**converted_kwargs)
|
|
104
|
+
else:
|
|
105
|
+
# Run synchronous blocking tools in a separate thread
|
|
106
|
+
import asyncio
|
|
107
|
+
result = await asyncio.to_thread(tool_func, **converted_kwargs)
|
|
96
108
|
|
|
97
|
-
return
|
|
109
|
+
return result
|
|
98
110
|
|
|
99
111
|
|
|
100
112
|
def validate_tool_availability(tool_name: str | None) -> tuple[bool, str]:
|
|
@@ -55,6 +55,9 @@ class SecurityFinding:
|
|
|
55
55
|
status: CheckStatus = CheckStatus.FAIL
|
|
56
56
|
|
|
57
57
|
|
|
58
|
+
from exaaiagnt.tools.registry import register_tool
|
|
59
|
+
|
|
60
|
+
@register_tool
|
|
58
61
|
class K8sScanner:
|
|
59
62
|
"""
|
|
60
63
|
Kubernetes Security Scanner.
|
|
@@ -292,6 +295,7 @@ class K8sScanner:
|
|
|
292
295
|
|
|
293
296
|
# === Convenience Functions ===
|
|
294
297
|
|
|
298
|
+
@register_tool
|
|
295
299
|
def scan_cluster(context: Optional[str] = None) -> Dict[str, Any]:
|
|
296
300
|
"""Run a full cluster scan."""
|
|
297
301
|
scanner = K8sScanner(context=context)
|
|
@@ -299,6 +303,7 @@ def scan_cluster(context: Optional[str] = None) -> Dict[str, Any]:
|
|
|
299
303
|
return scanner.export_report()
|
|
300
304
|
|
|
301
305
|
|
|
306
|
+
@register_tool
|
|
302
307
|
def check_rbac(namespace: str = "default") -> List[Dict[str, Any]]:
|
|
303
308
|
"""Run RBAC checks only."""
|
|
304
309
|
scanner = K8sScanner()
|
|
@@ -306,6 +311,7 @@ def check_rbac(namespace: str = "default") -> List[Dict[str, Any]]:
|
|
|
306
311
|
return scanner.export_report()["findings"]
|
|
307
312
|
|
|
308
313
|
|
|
314
|
+
@register_tool
|
|
309
315
|
def check_pod_security(namespace: str = "default") -> List[Dict[str, Any]]:
|
|
310
316
|
"""Run Pod Security checks only."""
|
|
311
317
|
scanner = K8sScanner()
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<tool name="scan_cluster">
|
|
2
|
+
<description>
|
|
3
|
+
Run a full security scan on the connected Kubernetes cluster.
|
|
4
|
+
Checks for RBAC misconfigurations, Pod Security Standards violations,
|
|
5
|
+
missing NetworkPolicies, and insecure secret management.
|
|
6
|
+
</description>
|
|
7
|
+
<parameters>
|
|
8
|
+
<parameter name="context" type="string" optional="true">
|
|
9
|
+
The kubeconfig context to use. Defaults to current context.
|
|
10
|
+
</parameter>
|
|
11
|
+
</parameters>
|
|
12
|
+
</tool>
|
|
13
|
+
|
|
14
|
+
<tool name="check_rbac">
|
|
15
|
+
<description>
|
|
16
|
+
Check for dangerous RBAC permissions in a specific namespace.
|
|
17
|
+
Detects wildcard permissions, cluster-admin abuse, and risky role bindings.
|
|
18
|
+
</description>
|
|
19
|
+
<parameters>
|
|
20
|
+
<parameter name="namespace" type="string" optional="true">
|
|
21
|
+
Target namespace. Defaults to "default".
|
|
22
|
+
</parameter>
|
|
23
|
+
</parameters>
|
|
24
|
+
</tool>
|
|
25
|
+
|
|
26
|
+
<tool name="check_pod_security">
|
|
27
|
+
<description>
|
|
28
|
+
Audit pods against Pod Security Standards (PSS).
|
|
29
|
+
Finds privileged containers, host namespace usage, and capability issues.
|
|
30
|
+
</description>
|
|
31
|
+
<parameters>
|
|
32
|
+
<parameter name="namespace" type="string" optional="true">
|
|
33
|
+
Target namespace. Defaults to "default".
|
|
34
|
+
</parameter>
|
|
35
|
+
</parameters>
|
|
36
|
+
</tool>
|