alita-sdk 0.3.257__py3-none-any.whl → 0.3.562__py3-none-any.whl
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.
- alita_sdk/cli/__init__.py +10 -0
- alita_sdk/cli/__main__.py +17 -0
- alita_sdk/cli/agent/__init__.py +5 -0
- alita_sdk/cli/agent/default.py +258 -0
- alita_sdk/cli/agent_executor.py +155 -0
- alita_sdk/cli/agent_loader.py +215 -0
- alita_sdk/cli/agent_ui.py +228 -0
- alita_sdk/cli/agents.py +3601 -0
- alita_sdk/cli/callbacks.py +647 -0
- alita_sdk/cli/cli.py +168 -0
- alita_sdk/cli/config.py +306 -0
- alita_sdk/cli/context/__init__.py +30 -0
- alita_sdk/cli/context/cleanup.py +198 -0
- alita_sdk/cli/context/manager.py +731 -0
- alita_sdk/cli/context/message.py +285 -0
- alita_sdk/cli/context/strategies.py +289 -0
- alita_sdk/cli/context/token_estimation.py +127 -0
- alita_sdk/cli/formatting.py +182 -0
- alita_sdk/cli/input_handler.py +419 -0
- alita_sdk/cli/inventory.py +1073 -0
- alita_sdk/cli/mcp_loader.py +315 -0
- alita_sdk/cli/toolkit.py +327 -0
- alita_sdk/cli/toolkit_loader.py +85 -0
- alita_sdk/cli/tools/__init__.py +43 -0
- alita_sdk/cli/tools/approval.py +224 -0
- alita_sdk/cli/tools/filesystem.py +1751 -0
- alita_sdk/cli/tools/planning.py +389 -0
- alita_sdk/cli/tools/terminal.py +414 -0
- alita_sdk/community/__init__.py +72 -12
- alita_sdk/community/inventory/__init__.py +236 -0
- alita_sdk/community/inventory/config.py +257 -0
- alita_sdk/community/inventory/enrichment.py +2137 -0
- alita_sdk/community/inventory/extractors.py +1469 -0
- alita_sdk/community/inventory/ingestion.py +3172 -0
- alita_sdk/community/inventory/knowledge_graph.py +1457 -0
- alita_sdk/community/inventory/parsers/__init__.py +218 -0
- alita_sdk/community/inventory/parsers/base.py +295 -0
- alita_sdk/community/inventory/parsers/csharp_parser.py +907 -0
- alita_sdk/community/inventory/parsers/go_parser.py +851 -0
- alita_sdk/community/inventory/parsers/html_parser.py +389 -0
- alita_sdk/community/inventory/parsers/java_parser.py +593 -0
- alita_sdk/community/inventory/parsers/javascript_parser.py +629 -0
- alita_sdk/community/inventory/parsers/kotlin_parser.py +768 -0
- alita_sdk/community/inventory/parsers/markdown_parser.py +362 -0
- alita_sdk/community/inventory/parsers/python_parser.py +604 -0
- alita_sdk/community/inventory/parsers/rust_parser.py +858 -0
- alita_sdk/community/inventory/parsers/swift_parser.py +832 -0
- alita_sdk/community/inventory/parsers/text_parser.py +322 -0
- alita_sdk/community/inventory/parsers/yaml_parser.py +370 -0
- alita_sdk/community/inventory/patterns/__init__.py +61 -0
- alita_sdk/community/inventory/patterns/ast_adapter.py +380 -0
- alita_sdk/community/inventory/patterns/loader.py +348 -0
- alita_sdk/community/inventory/patterns/registry.py +198 -0
- alita_sdk/community/inventory/presets.py +535 -0
- alita_sdk/community/inventory/retrieval.py +1403 -0
- alita_sdk/community/inventory/toolkit.py +173 -0
- alita_sdk/community/inventory/toolkit_utils.py +176 -0
- alita_sdk/community/inventory/visualize.py +1370 -0
- alita_sdk/configurations/__init__.py +11 -0
- alita_sdk/configurations/ado.py +148 -2
- alita_sdk/configurations/azure_search.py +1 -1
- alita_sdk/configurations/bigquery.py +1 -1
- alita_sdk/configurations/bitbucket.py +94 -2
- alita_sdk/configurations/browser.py +18 -0
- alita_sdk/configurations/carrier.py +19 -0
- alita_sdk/configurations/confluence.py +130 -1
- alita_sdk/configurations/delta_lake.py +1 -1
- alita_sdk/configurations/figma.py +76 -5
- alita_sdk/configurations/github.py +65 -1
- alita_sdk/configurations/gitlab.py +81 -0
- alita_sdk/configurations/google_places.py +17 -0
- alita_sdk/configurations/jira.py +103 -0
- alita_sdk/configurations/openapi.py +111 -0
- alita_sdk/configurations/postman.py +1 -1
- alita_sdk/configurations/qtest.py +72 -3
- alita_sdk/configurations/report_portal.py +115 -0
- alita_sdk/configurations/salesforce.py +19 -0
- alita_sdk/configurations/service_now.py +1 -12
- alita_sdk/configurations/sharepoint.py +167 -0
- alita_sdk/configurations/sonar.py +18 -0
- alita_sdk/configurations/sql.py +20 -0
- alita_sdk/configurations/testio.py +101 -0
- alita_sdk/configurations/testrail.py +88 -0
- alita_sdk/configurations/xray.py +94 -1
- alita_sdk/configurations/zephyr_enterprise.py +94 -1
- alita_sdk/configurations/zephyr_essential.py +95 -0
- alita_sdk/runtime/clients/artifact.py +21 -4
- alita_sdk/runtime/clients/client.py +458 -67
- alita_sdk/runtime/clients/mcp_discovery.py +342 -0
- alita_sdk/runtime/clients/mcp_manager.py +262 -0
- alita_sdk/runtime/clients/sandbox_client.py +352 -0
- alita_sdk/runtime/langchain/_constants_bkup.py +1318 -0
- alita_sdk/runtime/langchain/assistant.py +183 -43
- alita_sdk/runtime/langchain/constants.py +647 -1
- alita_sdk/runtime/langchain/document_loaders/AlitaDocxMammothLoader.py +315 -3
- alita_sdk/runtime/langchain/document_loaders/AlitaExcelLoader.py +209 -31
- alita_sdk/runtime/langchain/document_loaders/AlitaImageLoader.py +1 -1
- alita_sdk/runtime/langchain/document_loaders/AlitaJSONLinesLoader.py +77 -0
- alita_sdk/runtime/langchain/document_loaders/AlitaJSONLoader.py +10 -3
- alita_sdk/runtime/langchain/document_loaders/AlitaMarkdownLoader.py +66 -0
- alita_sdk/runtime/langchain/document_loaders/AlitaPDFLoader.py +79 -10
- alita_sdk/runtime/langchain/document_loaders/AlitaPowerPointLoader.py +52 -15
- alita_sdk/runtime/langchain/document_loaders/AlitaPythonLoader.py +9 -0
- alita_sdk/runtime/langchain/document_loaders/AlitaTableLoader.py +1 -4
- alita_sdk/runtime/langchain/document_loaders/AlitaTextLoader.py +15 -2
- alita_sdk/runtime/langchain/document_loaders/ImageParser.py +30 -0
- alita_sdk/runtime/langchain/document_loaders/constants.py +189 -41
- alita_sdk/runtime/langchain/interfaces/llm_processor.py +4 -2
- alita_sdk/runtime/langchain/langraph_agent.py +407 -92
- alita_sdk/runtime/langchain/utils.py +102 -8
- alita_sdk/runtime/llms/preloaded.py +2 -6
- alita_sdk/runtime/models/mcp_models.py +61 -0
- alita_sdk/runtime/skills/__init__.py +91 -0
- alita_sdk/runtime/skills/callbacks.py +498 -0
- alita_sdk/runtime/skills/discovery.py +540 -0
- alita_sdk/runtime/skills/executor.py +610 -0
- alita_sdk/runtime/skills/input_builder.py +371 -0
- alita_sdk/runtime/skills/models.py +330 -0
- alita_sdk/runtime/skills/registry.py +355 -0
- alita_sdk/runtime/skills/skill_runner.py +330 -0
- alita_sdk/runtime/toolkits/__init__.py +28 -0
- alita_sdk/runtime/toolkits/application.py +14 -4
- alita_sdk/runtime/toolkits/artifact.py +24 -9
- alita_sdk/runtime/toolkits/datasource.py +13 -6
- alita_sdk/runtime/toolkits/mcp.py +780 -0
- alita_sdk/runtime/toolkits/planning.py +178 -0
- alita_sdk/runtime/toolkits/skill_router.py +238 -0
- alita_sdk/runtime/toolkits/subgraph.py +11 -6
- alita_sdk/runtime/toolkits/tools.py +314 -70
- alita_sdk/runtime/toolkits/vectorstore.py +11 -5
- alita_sdk/runtime/tools/__init__.py +24 -0
- alita_sdk/runtime/tools/application.py +16 -4
- alita_sdk/runtime/tools/artifact.py +367 -33
- alita_sdk/runtime/tools/data_analysis.py +183 -0
- alita_sdk/runtime/tools/function.py +100 -4
- alita_sdk/runtime/tools/graph.py +81 -0
- alita_sdk/runtime/tools/image_generation.py +218 -0
- alita_sdk/runtime/tools/llm.py +1013 -177
- alita_sdk/runtime/tools/loop.py +3 -1
- alita_sdk/runtime/tools/loop_output.py +3 -1
- alita_sdk/runtime/tools/mcp_inspect_tool.py +284 -0
- alita_sdk/runtime/tools/mcp_remote_tool.py +181 -0
- alita_sdk/runtime/tools/mcp_server_tool.py +3 -1
- alita_sdk/runtime/tools/planning/__init__.py +36 -0
- alita_sdk/runtime/tools/planning/models.py +246 -0
- alita_sdk/runtime/tools/planning/wrapper.py +607 -0
- alita_sdk/runtime/tools/router.py +2 -1
- alita_sdk/runtime/tools/sandbox.py +375 -0
- alita_sdk/runtime/tools/skill_router.py +776 -0
- alita_sdk/runtime/tools/tool.py +3 -1
- alita_sdk/runtime/tools/vectorstore.py +69 -65
- alita_sdk/runtime/tools/vectorstore_base.py +163 -90
- alita_sdk/runtime/utils/AlitaCallback.py +137 -21
- alita_sdk/runtime/utils/mcp_client.py +492 -0
- alita_sdk/runtime/utils/mcp_oauth.py +361 -0
- alita_sdk/runtime/utils/mcp_sse_client.py +434 -0
- alita_sdk/runtime/utils/mcp_tools_discovery.py +124 -0
- alita_sdk/runtime/utils/streamlit.py +41 -14
- alita_sdk/runtime/utils/toolkit_utils.py +28 -9
- alita_sdk/runtime/utils/utils.py +48 -0
- alita_sdk/tools/__init__.py +135 -37
- alita_sdk/tools/ado/__init__.py +2 -2
- alita_sdk/tools/ado/repos/__init__.py +15 -19
- alita_sdk/tools/ado/repos/repos_wrapper.py +12 -20
- alita_sdk/tools/ado/test_plan/__init__.py +26 -8
- alita_sdk/tools/ado/test_plan/test_plan_wrapper.py +56 -28
- alita_sdk/tools/ado/wiki/__init__.py +27 -12
- alita_sdk/tools/ado/wiki/ado_wrapper.py +114 -40
- alita_sdk/tools/ado/work_item/__init__.py +27 -12
- alita_sdk/tools/ado/work_item/ado_wrapper.py +95 -11
- alita_sdk/tools/advanced_jira_mining/__init__.py +12 -8
- alita_sdk/tools/aws/delta_lake/__init__.py +14 -11
- alita_sdk/tools/aws/delta_lake/tool.py +5 -1
- alita_sdk/tools/azure_ai/search/__init__.py +13 -8
- alita_sdk/tools/base/tool.py +5 -1
- alita_sdk/tools/base_indexer_toolkit.py +454 -110
- alita_sdk/tools/bitbucket/__init__.py +27 -19
- alita_sdk/tools/bitbucket/api_wrapper.py +285 -27
- alita_sdk/tools/bitbucket/cloud_api_wrapper.py +5 -5
- alita_sdk/tools/browser/__init__.py +41 -16
- alita_sdk/tools/browser/crawler.py +3 -1
- alita_sdk/tools/browser/utils.py +15 -6
- alita_sdk/tools/carrier/__init__.py +18 -17
- alita_sdk/tools/carrier/backend_reports_tool.py +8 -4
- alita_sdk/tools/carrier/excel_reporter.py +8 -4
- alita_sdk/tools/chunkers/__init__.py +3 -1
- alita_sdk/tools/chunkers/code/codeparser.py +1 -1
- alita_sdk/tools/chunkers/sematic/json_chunker.py +2 -1
- alita_sdk/tools/chunkers/sematic/markdown_chunker.py +97 -6
- alita_sdk/tools/chunkers/sematic/proposal_chunker.py +1 -1
- alita_sdk/tools/chunkers/universal_chunker.py +270 -0
- alita_sdk/tools/cloud/aws/__init__.py +11 -7
- alita_sdk/tools/cloud/azure/__init__.py +11 -7
- alita_sdk/tools/cloud/gcp/__init__.py +11 -7
- alita_sdk/tools/cloud/k8s/__init__.py +11 -7
- alita_sdk/tools/code/linter/__init__.py +9 -8
- alita_sdk/tools/code/loaders/codesearcher.py +3 -2
- alita_sdk/tools/code/sonar/__init__.py +20 -13
- alita_sdk/tools/code_indexer_toolkit.py +199 -0
- alita_sdk/tools/confluence/__init__.py +21 -14
- alita_sdk/tools/confluence/api_wrapper.py +197 -58
- alita_sdk/tools/confluence/loader.py +14 -2
- alita_sdk/tools/custom_open_api/__init__.py +11 -5
- alita_sdk/tools/elastic/__init__.py +10 -8
- alita_sdk/tools/elitea_base.py +546 -64
- alita_sdk/tools/figma/__init__.py +11 -8
- alita_sdk/tools/figma/api_wrapper.py +352 -153
- alita_sdk/tools/github/__init__.py +17 -17
- alita_sdk/tools/github/api_wrapper.py +9 -26
- alita_sdk/tools/github/github_client.py +81 -12
- alita_sdk/tools/github/schemas.py +2 -1
- alita_sdk/tools/github/tool.py +5 -1
- alita_sdk/tools/gitlab/__init__.py +18 -13
- alita_sdk/tools/gitlab/api_wrapper.py +224 -80
- alita_sdk/tools/gitlab_org/__init__.py +13 -10
- alita_sdk/tools/google/bigquery/__init__.py +13 -13
- alita_sdk/tools/google/bigquery/tool.py +5 -1
- alita_sdk/tools/google_places/__init__.py +20 -11
- alita_sdk/tools/jira/__init__.py +21 -11
- alita_sdk/tools/jira/api_wrapper.py +315 -168
- alita_sdk/tools/keycloak/__init__.py +10 -8
- alita_sdk/tools/localgit/__init__.py +8 -3
- alita_sdk/tools/localgit/local_git.py +62 -54
- alita_sdk/tools/localgit/tool.py +5 -1
- alita_sdk/tools/memory/__init__.py +38 -14
- alita_sdk/tools/non_code_indexer_toolkit.py +7 -2
- alita_sdk/tools/ocr/__init__.py +10 -8
- alita_sdk/tools/openapi/__init__.py +281 -108
- alita_sdk/tools/openapi/api_wrapper.py +883 -0
- alita_sdk/tools/openapi/tool.py +20 -0
- alita_sdk/tools/pandas/__init__.py +18 -11
- alita_sdk/tools/pandas/api_wrapper.py +40 -45
- alita_sdk/tools/pandas/dataframe/generator/base.py +3 -1
- alita_sdk/tools/postman/__init__.py +10 -11
- alita_sdk/tools/postman/api_wrapper.py +19 -8
- alita_sdk/tools/postman/postman_analysis.py +8 -1
- alita_sdk/tools/pptx/__init__.py +10 -10
- alita_sdk/tools/qtest/__init__.py +21 -14
- alita_sdk/tools/qtest/api_wrapper.py +1784 -88
- alita_sdk/tools/rally/__init__.py +12 -10
- alita_sdk/tools/report_portal/__init__.py +22 -16
- alita_sdk/tools/salesforce/__init__.py +21 -16
- alita_sdk/tools/servicenow/__init__.py +20 -16
- alita_sdk/tools/servicenow/api_wrapper.py +1 -1
- alita_sdk/tools/sharepoint/__init__.py +16 -14
- alita_sdk/tools/sharepoint/api_wrapper.py +179 -39
- alita_sdk/tools/sharepoint/authorization_helper.py +191 -1
- alita_sdk/tools/sharepoint/utils.py +8 -2
- alita_sdk/tools/slack/__init__.py +11 -7
- alita_sdk/tools/sql/__init__.py +21 -19
- alita_sdk/tools/sql/api_wrapper.py +71 -23
- alita_sdk/tools/testio/__init__.py +20 -13
- alita_sdk/tools/testrail/__init__.py +12 -11
- alita_sdk/tools/testrail/api_wrapper.py +214 -46
- alita_sdk/tools/utils/__init__.py +28 -4
- alita_sdk/tools/utils/content_parser.py +182 -62
- alita_sdk/tools/utils/text_operations.py +254 -0
- alita_sdk/tools/vector_adapters/VectorStoreAdapter.py +83 -27
- alita_sdk/tools/xray/__init__.py +17 -14
- alita_sdk/tools/xray/api_wrapper.py +58 -113
- alita_sdk/tools/yagmail/__init__.py +8 -3
- alita_sdk/tools/zephyr/__init__.py +11 -7
- alita_sdk/tools/zephyr_enterprise/__init__.py +15 -9
- alita_sdk/tools/zephyr_enterprise/api_wrapper.py +30 -15
- alita_sdk/tools/zephyr_essential/__init__.py +15 -10
- alita_sdk/tools/zephyr_essential/api_wrapper.py +297 -54
- alita_sdk/tools/zephyr_essential/client.py +6 -4
- alita_sdk/tools/zephyr_scale/__init__.py +12 -8
- alita_sdk/tools/zephyr_scale/api_wrapper.py +39 -31
- alita_sdk/tools/zephyr_squad/__init__.py +11 -7
- {alita_sdk-0.3.257.dist-info → alita_sdk-0.3.562.dist-info}/METADATA +184 -37
- alita_sdk-0.3.562.dist-info/RECORD +450 -0
- alita_sdk-0.3.562.dist-info/entry_points.txt +2 -0
- alita_sdk/tools/bitbucket/tools.py +0 -304
- alita_sdk-0.3.257.dist-info/RECORD +0 -343
- {alita_sdk-0.3.257.dist-info → alita_sdk-0.3.562.dist-info}/WHEEL +0 -0
- {alita_sdk-0.3.257.dist-info → alita_sdk-0.3.562.dist-info}/licenses/LICENSE +0 -0
- {alita_sdk-0.3.257.dist-info → alita_sdk-0.3.562.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Terminal command execution tools for CLI agents.
|
|
3
|
+
|
|
4
|
+
Provides secure shell command execution restricted to mounted directories
|
|
5
|
+
with blocked command patterns and path traversal protection.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import re
|
|
10
|
+
import subprocess
|
|
11
|
+
import shlex
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
from typing import Optional, List, Dict, Any
|
|
14
|
+
from langchain_core.tools import BaseTool
|
|
15
|
+
from pydantic import BaseModel, Field
|
|
16
|
+
import logging
|
|
17
|
+
|
|
18
|
+
logger = logging.getLogger(__name__)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# Default blocked command patterns (security)
|
|
22
|
+
DEFAULT_BLOCKED_PATTERNS = [
|
|
23
|
+
# Destructive commands
|
|
24
|
+
r"rm\s+-rf\s+/",
|
|
25
|
+
r"rm\s+-rf\s+~",
|
|
26
|
+
r"rm\s+-rf\s+\*",
|
|
27
|
+
r"rm\s+-rf\s+\.\.",
|
|
28
|
+
r"sudo\s+rm",
|
|
29
|
+
r"mkfs",
|
|
30
|
+
r"dd\s+if=",
|
|
31
|
+
r":\(\)\{\s*:\|:&\s*\};:", # Fork bomb
|
|
32
|
+
|
|
33
|
+
# Privilege escalation
|
|
34
|
+
r"sudo\s+su",
|
|
35
|
+
r"sudo\s+-i",
|
|
36
|
+
r"sudo\s+-s",
|
|
37
|
+
r"chmod\s+777",
|
|
38
|
+
r"chmod\s+-R\s+777",
|
|
39
|
+
r"chown\s+root",
|
|
40
|
+
|
|
41
|
+
# Data exfiltration
|
|
42
|
+
r"curl.*\|.*sh",
|
|
43
|
+
r"wget.*\|.*sh",
|
|
44
|
+
r"curl.*\|.*bash",
|
|
45
|
+
r"wget.*\|.*bash",
|
|
46
|
+
r"nc\s+-e",
|
|
47
|
+
r"/dev/tcp",
|
|
48
|
+
|
|
49
|
+
# System modification
|
|
50
|
+
r"shutdown",
|
|
51
|
+
r"reboot",
|
|
52
|
+
r"init\s+0",
|
|
53
|
+
r"init\s+6",
|
|
54
|
+
r"systemctl\s+stop",
|
|
55
|
+
r"systemctl\s+disable",
|
|
56
|
+
r"launchctl\s+unload",
|
|
57
|
+
|
|
58
|
+
# Path traversal attempts
|
|
59
|
+
r"\.\./\.\./\.\.",
|
|
60
|
+
r"/etc/passwd",
|
|
61
|
+
r"/etc/shadow",
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class TerminalRunCommandInput(BaseModel):
|
|
66
|
+
"""Input for running a terminal command."""
|
|
67
|
+
command: str = Field(description="Shell command to execute")
|
|
68
|
+
timeout: int = Field(default=300, description="Timeout in seconds (default: 300)")
|
|
69
|
+
directory: Optional[str] = Field(
|
|
70
|
+
default=None,
|
|
71
|
+
description="Working directory to execute the command in. Must be from the allowed directories list. If not specified, uses the default workspace directory."
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class TerminalRunCommandTool(BaseTool):
|
|
76
|
+
"""Execute shell commands in the mounted working directory."""
|
|
77
|
+
|
|
78
|
+
name: str = "terminal_run_command"
|
|
79
|
+
description: str = """Execute a shell command in the workspace directory.
|
|
80
|
+
|
|
81
|
+
Use this to run tests, build commands, git operations, package managers, etc.
|
|
82
|
+
Commands are executed in the mounted workspace directory or a specified allowed directory.
|
|
83
|
+
|
|
84
|
+
Examples:
|
|
85
|
+
- Run tests: `npm test`, `pytest`, `go test ./...`
|
|
86
|
+
- Build: `npm run build`, `cargo build`, `make`
|
|
87
|
+
- Git: `git status`, `git diff`, `git log --oneline -10`
|
|
88
|
+
- Package managers: `npm install`, `pip install -r requirements.txt`
|
|
89
|
+
|
|
90
|
+
The command runs with the workspace (or specified directory) as the current working directory.
|
|
91
|
+
Returns stdout, stderr, and exit code.
|
|
92
|
+
|
|
93
|
+
Use the 'directory' parameter to run commands in a specific allowed directory when working with multi-folder workspaces."""
|
|
94
|
+
args_schema: type[BaseModel] = TerminalRunCommandInput
|
|
95
|
+
|
|
96
|
+
work_dir: str = ""
|
|
97
|
+
allowed_directories: List[str] = []
|
|
98
|
+
blocked_patterns: List[str] = []
|
|
99
|
+
|
|
100
|
+
def __init__(self, work_dir: str, blocked_patterns: Optional[List[str]] = None,
|
|
101
|
+
allowed_directories: Optional[List[str]] = None, **kwargs):
|
|
102
|
+
super().__init__(**kwargs)
|
|
103
|
+
self.work_dir = str(Path(work_dir).resolve())
|
|
104
|
+
self.blocked_patterns = blocked_patterns or DEFAULT_BLOCKED_PATTERNS
|
|
105
|
+
# Build allowed directories list: always include work_dir, plus any additional allowed dirs
|
|
106
|
+
self.allowed_directories = [self.work_dir]
|
|
107
|
+
if allowed_directories:
|
|
108
|
+
for d in allowed_directories:
|
|
109
|
+
resolved = str(Path(d).resolve())
|
|
110
|
+
if resolved not in self.allowed_directories:
|
|
111
|
+
self.allowed_directories.append(resolved)
|
|
112
|
+
|
|
113
|
+
def _is_command_blocked(self, command: str) -> tuple[bool, str]:
|
|
114
|
+
"""Check if command matches any blocked patterns."""
|
|
115
|
+
command_lower = command.lower()
|
|
116
|
+
for pattern in self.blocked_patterns:
|
|
117
|
+
if re.search(pattern, command_lower, re.IGNORECASE):
|
|
118
|
+
return True, pattern
|
|
119
|
+
return False, ""
|
|
120
|
+
|
|
121
|
+
def _validate_directory(self, directory: Optional[str]) -> tuple[bool, str, str]:
|
|
122
|
+
"""
|
|
123
|
+
Validate that the requested directory is in the allowed list.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
directory: Requested directory path or None
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
Tuple of (is_valid, error_message, resolved_directory)
|
|
130
|
+
"""
|
|
131
|
+
if directory is None:
|
|
132
|
+
return True, "", self.work_dir
|
|
133
|
+
|
|
134
|
+
# Resolve the requested directory
|
|
135
|
+
try:
|
|
136
|
+
resolved = str(Path(directory).resolve())
|
|
137
|
+
except Exception as e:
|
|
138
|
+
return False, f"Invalid directory path: {e}", ""
|
|
139
|
+
|
|
140
|
+
# Check if directory exists
|
|
141
|
+
if not Path(resolved).is_dir():
|
|
142
|
+
return False, f"Directory does not exist: {directory}", ""
|
|
143
|
+
|
|
144
|
+
# Check if it's in the allowed list or is a subdirectory of an allowed directory
|
|
145
|
+
for allowed in self.allowed_directories:
|
|
146
|
+
if resolved == allowed or resolved.startswith(allowed + os.sep):
|
|
147
|
+
return True, "", resolved
|
|
148
|
+
|
|
149
|
+
allowed_list = "\n - ".join(self.allowed_directories)
|
|
150
|
+
return False, f"Directory not in allowed list: {directory}\n\nAllowed directories:\n - {allowed_list}", ""
|
|
151
|
+
|
|
152
|
+
def _validate_paths_in_command(self, command: str, target_dir: str) -> tuple[bool, str]:
|
|
153
|
+
"""
|
|
154
|
+
Validate that any paths referenced in the command don't escape allowed directories.
|
|
155
|
+
This is a best-effort check for obvious path traversal.
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
command: The command to validate
|
|
159
|
+
target_dir: The target directory where command will be executed
|
|
160
|
+
"""
|
|
161
|
+
# Check for obvious path traversal patterns
|
|
162
|
+
if "../../../" in command or "/.." in command:
|
|
163
|
+
return False, "Path traversal detected"
|
|
164
|
+
|
|
165
|
+
# Check for absolute paths outside allowed directories
|
|
166
|
+
try:
|
|
167
|
+
parts = shlex.split(command)
|
|
168
|
+
except ValueError:
|
|
169
|
+
# If we can't parse the command, skip path validation
|
|
170
|
+
parts = []
|
|
171
|
+
|
|
172
|
+
for part in parts:
|
|
173
|
+
if part.startswith("/"):
|
|
174
|
+
# Allow common system paths that are safe to reference
|
|
175
|
+
safe_prefixes = ["/dev/null", "/tmp", "/usr/bin", "/usr/local/bin", "/bin"]
|
|
176
|
+
if any(part.startswith(p) for p in safe_prefixes):
|
|
177
|
+
continue
|
|
178
|
+
# Check if it's within any allowed directory
|
|
179
|
+
is_allowed = False
|
|
180
|
+
for allowed in self.allowed_directories:
|
|
181
|
+
if part.startswith(allowed) or part == allowed:
|
|
182
|
+
is_allowed = True
|
|
183
|
+
break
|
|
184
|
+
if not is_allowed:
|
|
185
|
+
return False, f"Absolute path outside allowed directories: {part}"
|
|
186
|
+
|
|
187
|
+
return True, ""
|
|
188
|
+
|
|
189
|
+
def _run(self, command: str, timeout: int = 300, directory: Optional[str] = None) -> str:
|
|
190
|
+
"""Execute the command and return results."""
|
|
191
|
+
# Validate the requested directory
|
|
192
|
+
dir_valid, dir_error, target_dir = self._validate_directory(directory)
|
|
193
|
+
if not dir_valid:
|
|
194
|
+
return f"❌ Directory validation failed: {dir_error}"
|
|
195
|
+
|
|
196
|
+
# Check if command is blocked
|
|
197
|
+
is_blocked, pattern = self._is_command_blocked(command)
|
|
198
|
+
if is_blocked:
|
|
199
|
+
return f"❌ Command blocked for security reasons.\nMatched pattern: {pattern}\n\nThis command pattern is not allowed. Please use a safer alternative."
|
|
200
|
+
|
|
201
|
+
# Validate paths in command
|
|
202
|
+
path_valid, path_error = self._validate_paths_in_command(command, target_dir)
|
|
203
|
+
if not path_valid:
|
|
204
|
+
allowed_list = ", ".join(self.allowed_directories)
|
|
205
|
+
return f"❌ Command rejected: {path_error}\n\nCommands must operate within allowed directories: {allowed_list}"
|
|
206
|
+
|
|
207
|
+
try:
|
|
208
|
+
# Execute command in target_dir
|
|
209
|
+
result = subprocess.run(
|
|
210
|
+
command,
|
|
211
|
+
shell=True,
|
|
212
|
+
cwd=target_dir,
|
|
213
|
+
capture_output=True,
|
|
214
|
+
text=True,
|
|
215
|
+
timeout=timeout,
|
|
216
|
+
env={**os.environ, "PWD": target_dir}
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
output_parts = []
|
|
220
|
+
|
|
221
|
+
# Show which directory the command was executed in if not default
|
|
222
|
+
if target_dir != self.work_dir:
|
|
223
|
+
output_parts.append(f"[Executed in: {target_dir}]")
|
|
224
|
+
|
|
225
|
+
if result.stdout:
|
|
226
|
+
output_parts.append(f"stdout:\n{result.stdout}")
|
|
227
|
+
|
|
228
|
+
if result.stderr:
|
|
229
|
+
output_parts.append(f"stderr:\n{result.stderr}")
|
|
230
|
+
|
|
231
|
+
output_parts.append(f"exit_code: {result.returncode}")
|
|
232
|
+
|
|
233
|
+
# Add hint when search-like commands return empty results
|
|
234
|
+
if result.returncode == 0 and not result.stdout.strip():
|
|
235
|
+
if self._is_search_command(command):
|
|
236
|
+
hint = self._generate_empty_search_hint(command, target_dir)
|
|
237
|
+
output_parts.append(hint)
|
|
238
|
+
|
|
239
|
+
return "\n\n".join(output_parts)
|
|
240
|
+
|
|
241
|
+
except subprocess.TimeoutExpired:
|
|
242
|
+
return f"❌ Command timed out after {timeout} seconds.\n\nConsider:\n- Breaking into smaller operations\n- Using --timeout flag for longer operations\n- Running in background if appropriate"
|
|
243
|
+
except Exception as e:
|
|
244
|
+
return f"❌ Error executing command: {str(e)}"
|
|
245
|
+
|
|
246
|
+
def _is_search_command(self, command: str) -> bool:
|
|
247
|
+
"""Check if the command is a search/find operation."""
|
|
248
|
+
search_patterns = [
|
|
249
|
+
r'\bfind\b',
|
|
250
|
+
r'\bgrep\b',
|
|
251
|
+
r'\brg\b', # ripgrep
|
|
252
|
+
r'\bag\b', # silver searcher
|
|
253
|
+
r'\back\b',
|
|
254
|
+
r'\bfzf\b',
|
|
255
|
+
r'\blocate\b',
|
|
256
|
+
r'\bfd\b', # fd-find
|
|
257
|
+
r'\bxargs\s+grep',
|
|
258
|
+
]
|
|
259
|
+
command_lower = command.lower()
|
|
260
|
+
return any(re.search(pattern, command_lower) for pattern in search_patterns)
|
|
261
|
+
|
|
262
|
+
def _generate_empty_search_hint(self, command: str, target_dir: str) -> str:
|
|
263
|
+
"""Generate a helpful hint when a search command returns no results."""
|
|
264
|
+
hints = ["💡 **No results found.** Consider:"]
|
|
265
|
+
|
|
266
|
+
# Check if searching in the right directory
|
|
267
|
+
if len(self.allowed_directories) > 1:
|
|
268
|
+
other_dirs = [d for d in self.allowed_directories if d != target_dir]
|
|
269
|
+
hints.append(f" - **Wrong directory?** You searched in `{target_dir}`")
|
|
270
|
+
hints.append(f" Other allowed directories: {', '.join(f'`{d}`' for d in other_dirs)}")
|
|
271
|
+
else:
|
|
272
|
+
hints.append(f" - **Verify directory:** Currently searching in `{target_dir}`")
|
|
273
|
+
|
|
274
|
+
# Suggest adjusting search criteria
|
|
275
|
+
hints.append(" - **Adjust search pattern:** Try broader terms, different casing, or partial matches")
|
|
276
|
+
hints.append(" - **Check file extensions:** Ensure you're searching the right file types")
|
|
277
|
+
|
|
278
|
+
# Specific suggestions based on command patterns
|
|
279
|
+
if 'grep' in command.lower():
|
|
280
|
+
hints.append(" - **Grep tips:** Use `-i` for case-insensitive, `-r` for recursive, or try regex patterns")
|
|
281
|
+
if 'find' in command.lower() and '-name' in command:
|
|
282
|
+
hints.append(" - **Find tips:** Use wildcards like `*.py` or `-iname` for case-insensitive matching")
|
|
283
|
+
|
|
284
|
+
return "\n".join(hints)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def load_blocked_patterns(config_path: Optional[str] = None) -> List[str]:
|
|
288
|
+
"""
|
|
289
|
+
Load blocked command patterns from config file.
|
|
290
|
+
Falls back to defaults if file doesn't exist.
|
|
291
|
+
|
|
292
|
+
Args:
|
|
293
|
+
config_path: Path to blocked_commands.txt file
|
|
294
|
+
|
|
295
|
+
Returns:
|
|
296
|
+
List of regex patterns
|
|
297
|
+
"""
|
|
298
|
+
patterns = list(DEFAULT_BLOCKED_PATTERNS)
|
|
299
|
+
|
|
300
|
+
if config_path and Path(config_path).exists():
|
|
301
|
+
try:
|
|
302
|
+
content = Path(config_path).read_text()
|
|
303
|
+
for line in content.splitlines():
|
|
304
|
+
line = line.strip()
|
|
305
|
+
# Skip empty lines and comments
|
|
306
|
+
if line and not line.startswith("#"):
|
|
307
|
+
patterns.append(line)
|
|
308
|
+
logger.debug(f"Loaded {len(patterns)} blocked patterns from {config_path}")
|
|
309
|
+
except Exception as e:
|
|
310
|
+
logger.warning(f"Failed to load blocked patterns from {config_path}: {e}")
|
|
311
|
+
|
|
312
|
+
return patterns
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def get_terminal_tools(
|
|
316
|
+
work_dir: str,
|
|
317
|
+
blocked_patterns_path: Optional[str] = None,
|
|
318
|
+
allowed_directories: Optional[List[str]] = None
|
|
319
|
+
) -> List[BaseTool]:
|
|
320
|
+
"""
|
|
321
|
+
Get terminal execution tools for the given working directory.
|
|
322
|
+
|
|
323
|
+
Args:
|
|
324
|
+
work_dir: The default workspace directory (must be absolute path)
|
|
325
|
+
blocked_patterns_path: Optional path to custom blocked_commands.txt
|
|
326
|
+
allowed_directories: Optional list of additional directories where commands can be executed.
|
|
327
|
+
The work_dir is always included in the allowed list.
|
|
328
|
+
This enables multi-folder workspace support.
|
|
329
|
+
|
|
330
|
+
Returns:
|
|
331
|
+
List of terminal tools
|
|
332
|
+
"""
|
|
333
|
+
work_dir = str(Path(work_dir).resolve())
|
|
334
|
+
|
|
335
|
+
if not Path(work_dir).is_dir():
|
|
336
|
+
raise ValueError(f"Work directory does not exist: {work_dir}")
|
|
337
|
+
|
|
338
|
+
blocked_patterns = load_blocked_patterns(blocked_patterns_path)
|
|
339
|
+
|
|
340
|
+
# Validate and resolve allowed directories
|
|
341
|
+
validated_allowed_dirs = []
|
|
342
|
+
if allowed_directories:
|
|
343
|
+
for d in allowed_directories:
|
|
344
|
+
resolved = str(Path(d).resolve())
|
|
345
|
+
if Path(resolved).is_dir():
|
|
346
|
+
validated_allowed_dirs.append(resolved)
|
|
347
|
+
else:
|
|
348
|
+
logger.warning(f"Allowed directory does not exist, skipping: {d}")
|
|
349
|
+
|
|
350
|
+
return [
|
|
351
|
+
TerminalRunCommandTool(
|
|
352
|
+
work_dir=work_dir,
|
|
353
|
+
blocked_patterns=blocked_patterns,
|
|
354
|
+
allowed_directories=validated_allowed_dirs
|
|
355
|
+
)
|
|
356
|
+
]
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def create_default_blocked_patterns_file(config_dir: str) -> str:
|
|
360
|
+
"""
|
|
361
|
+
Create default blocked_commands.txt file in config directory.
|
|
362
|
+
|
|
363
|
+
Args:
|
|
364
|
+
config_dir: Directory to create the file in (e.g., $ALITA_DIR/security)
|
|
365
|
+
|
|
366
|
+
Returns:
|
|
367
|
+
Path to created file
|
|
368
|
+
"""
|
|
369
|
+
security_dir = Path(config_dir) / "security"
|
|
370
|
+
security_dir.mkdir(parents=True, exist_ok=True)
|
|
371
|
+
|
|
372
|
+
blocked_file = security_dir / "blocked_commands.txt"
|
|
373
|
+
|
|
374
|
+
if not blocked_file.exists():
|
|
375
|
+
content = """# Blocked Command Patterns for Alita CLI
|
|
376
|
+
# Each line is a regex pattern. Lines starting with # are comments.
|
|
377
|
+
# These patterns are checked against commands before execution.
|
|
378
|
+
|
|
379
|
+
# === Destructive Commands ===
|
|
380
|
+
rm\\s+-rf\\s+/
|
|
381
|
+
rm\\s+-rf\\s+~
|
|
382
|
+
rm\\s+-rf\\s+\\*
|
|
383
|
+
sudo\\s+rm
|
|
384
|
+
mkfs
|
|
385
|
+
dd\\s+if=
|
|
386
|
+
|
|
387
|
+
# === Privilege Escalation ===
|
|
388
|
+
sudo\\s+su
|
|
389
|
+
sudo\\s+-i
|
|
390
|
+
chmod\\s+777
|
|
391
|
+
chown\\s+root
|
|
392
|
+
|
|
393
|
+
# === Data Exfiltration ===
|
|
394
|
+
curl.*\\|.*sh
|
|
395
|
+
wget.*\\|.*sh
|
|
396
|
+
nc\\s+-e
|
|
397
|
+
|
|
398
|
+
# === System Modification ===
|
|
399
|
+
shutdown
|
|
400
|
+
reboot
|
|
401
|
+
init\\s+0
|
|
402
|
+
systemctl\\s+stop
|
|
403
|
+
|
|
404
|
+
# === Path Traversal ===
|
|
405
|
+
\\.\\./\\.\\./\\.\\.
|
|
406
|
+
/etc/passwd
|
|
407
|
+
/etc/shadow
|
|
408
|
+
|
|
409
|
+
# Add your custom patterns below:
|
|
410
|
+
"""
|
|
411
|
+
blocked_file.write_text(content)
|
|
412
|
+
logger.info(f"Created default blocked patterns file: {blocked_file}")
|
|
413
|
+
|
|
414
|
+
return str(blocked_file)
|
alita_sdk/community/__init__.py
CHANGED
|
@@ -6,12 +6,15 @@ Includes analysis tools, browser automation, research tools, and exploratory dat
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import importlib
|
|
9
|
+
import logging
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
9
12
|
|
|
10
13
|
# Import available community modules
|
|
11
14
|
__all__ = []
|
|
12
15
|
|
|
13
16
|
# Standard module imports with fallback
|
|
14
|
-
_modules = ['utils', 'analysis', 'deep_researcher', 'eda']
|
|
17
|
+
_modules = ['utils', 'analysis', 'deep_researcher', 'eda', 'inventory']
|
|
15
18
|
|
|
16
19
|
for module_name in _modules:
|
|
17
20
|
try:
|
|
@@ -26,7 +29,8 @@ _toolkits = [
|
|
|
26
29
|
('analysis.jira_analyse', 'AnalyseJira'),
|
|
27
30
|
('analysis.ado_analyse', 'AnalyseAdo'),
|
|
28
31
|
('analysis.gitlab_analyse', 'AnalyseGitLab'),
|
|
29
|
-
('analysis.github_analyse', 'AnalyseGithub')
|
|
32
|
+
('analysis.github_analyse', 'AnalyseGithub'),
|
|
33
|
+
('inventory.toolkit', 'InventoryToolkit')
|
|
30
34
|
]
|
|
31
35
|
|
|
32
36
|
for module_path, class_name in _toolkits:
|
|
@@ -38,6 +42,44 @@ for module_path, class_name in _toolkits:
|
|
|
38
42
|
except (ImportError, AttributeError):
|
|
39
43
|
pass
|
|
40
44
|
|
|
45
|
+
|
|
46
|
+
def _register_community_toolkits():
|
|
47
|
+
"""Register community toolkits into the central tools registry."""
|
|
48
|
+
try:
|
|
49
|
+
from alita_sdk.tools import AVAILABLE_TOOLS, AVAILABLE_TOOLKITS, FAILED_IMPORTS
|
|
50
|
+
except ImportError:
|
|
51
|
+
logger.debug("Could not import tools registry, skipping community toolkit registration")
|
|
52
|
+
return
|
|
53
|
+
|
|
54
|
+
# Community toolkit definitions: (tool_name, module_path, get_tools_func_name, toolkit_class_name)
|
|
55
|
+
_community_toolkit_defs = [
|
|
56
|
+
('inventory', 'inventory', 'get_tools', 'InventoryRetrievalToolkit'),
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
for tool_name, module_path, get_tools_name, toolkit_class_name in _community_toolkit_defs:
|
|
60
|
+
try:
|
|
61
|
+
module = importlib.import_module(f'.{module_path}', package=__name__)
|
|
62
|
+
|
|
63
|
+
imported = {}
|
|
64
|
+
if get_tools_name and hasattr(module, get_tools_name):
|
|
65
|
+
imported['get_tools'] = getattr(module, get_tools_name)
|
|
66
|
+
|
|
67
|
+
if toolkit_class_name and hasattr(module, toolkit_class_name):
|
|
68
|
+
imported['toolkit_class'] = getattr(module, toolkit_class_name)
|
|
69
|
+
AVAILABLE_TOOLKITS[toolkit_class_name] = getattr(module, toolkit_class_name)
|
|
70
|
+
|
|
71
|
+
if imported:
|
|
72
|
+
AVAILABLE_TOOLS[tool_name] = imported
|
|
73
|
+
logger.debug(f"Successfully registered community toolkit: {tool_name}")
|
|
74
|
+
|
|
75
|
+
except Exception as e:
|
|
76
|
+
FAILED_IMPORTS[tool_name] = str(e)
|
|
77
|
+
logger.debug(f"Failed to register community toolkit {tool_name}: {e}")
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
# Register community toolkits on module import
|
|
81
|
+
_register_community_toolkits()
|
|
82
|
+
|
|
41
83
|
def get_toolkits():
|
|
42
84
|
"""Get all available community toolkits configurations."""
|
|
43
85
|
toolkits = []
|
|
@@ -54,27 +96,45 @@ def get_toolkits():
|
|
|
54
96
|
def get_tools(tools_list: list, alita_client, llm) -> list:
|
|
55
97
|
"""Get community tools based on the tools list configuration."""
|
|
56
98
|
tools = []
|
|
57
|
-
|
|
99
|
+
|
|
58
100
|
# Tool type to class mapping
|
|
59
101
|
_tool_mapping = {
|
|
60
102
|
'analyse_jira': 'AnalyseJira',
|
|
61
103
|
'analyse_ado': 'AnalyseAdo',
|
|
62
|
-
'analyse_gitlab': 'AnalyseGitLab',
|
|
63
|
-
'analyse_github': 'AnalyseGithub'
|
|
104
|
+
'analyse_gitlab': 'AnalyseGitLab',
|
|
105
|
+
'analyse_github': 'AnalyseGithub',
|
|
106
|
+
'inventory': 'InventoryToolkit'
|
|
64
107
|
}
|
|
65
|
-
|
|
108
|
+
|
|
66
109
|
for tool in tools_list:
|
|
67
|
-
|
|
110
|
+
if isinstance(tool, dict):
|
|
111
|
+
tool_type = tool.get('type')
|
|
112
|
+
else:
|
|
113
|
+
logger.error(f"Community tools received non-dict tool: {tool} (type: {type(tool)})")
|
|
114
|
+
continue
|
|
68
115
|
class_name = _tool_mapping.get(tool_type)
|
|
69
116
|
|
|
70
117
|
if class_name and class_name in globals():
|
|
71
118
|
try:
|
|
72
119
|
toolkit_class = globals()[class_name]
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
120
|
+
|
|
121
|
+
# Special handling for inventory toolkit - pass sibling configs
|
|
122
|
+
if tool_type == 'inventory':
|
|
123
|
+
from .inventory import get_tools as inventory_get_tools
|
|
124
|
+
# inventory_get_tools returns a list of tools directly
|
|
125
|
+
inventory_tools = inventory_get_tools(tool, tools_list=tools_list)
|
|
126
|
+
# Inject alita and llm into api_wrapper for each tool
|
|
127
|
+
for t in inventory_tools:
|
|
128
|
+
if hasattr(t, 'api_wrapper'):
|
|
129
|
+
t.api_wrapper.alita = alita_client
|
|
130
|
+
t.api_wrapper.llm = llm
|
|
131
|
+
tools.extend(inventory_tools)
|
|
132
|
+
else:
|
|
133
|
+
toolkit = toolkit_class.get_toolkit(
|
|
134
|
+
client=alita_client,
|
|
135
|
+
**tool['settings']
|
|
136
|
+
)
|
|
137
|
+
tools.extend(toolkit.get_tools())
|
|
78
138
|
except Exception:
|
|
79
139
|
pass # Fail silently for robustness
|
|
80
140
|
|