alita-sdk 0.3.379__py3-none-any.whl → 0.3.627__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.
Files changed (278) hide show
  1. alita_sdk/cli/__init__.py +10 -0
  2. alita_sdk/cli/__main__.py +17 -0
  3. alita_sdk/cli/agent/__init__.py +5 -0
  4. alita_sdk/cli/agent/default.py +258 -0
  5. alita_sdk/cli/agent_executor.py +156 -0
  6. alita_sdk/cli/agent_loader.py +245 -0
  7. alita_sdk/cli/agent_ui.py +228 -0
  8. alita_sdk/cli/agents.py +3113 -0
  9. alita_sdk/cli/callbacks.py +647 -0
  10. alita_sdk/cli/cli.py +168 -0
  11. alita_sdk/cli/config.py +306 -0
  12. alita_sdk/cli/context/__init__.py +30 -0
  13. alita_sdk/cli/context/cleanup.py +198 -0
  14. alita_sdk/cli/context/manager.py +731 -0
  15. alita_sdk/cli/context/message.py +285 -0
  16. alita_sdk/cli/context/strategies.py +289 -0
  17. alita_sdk/cli/context/token_estimation.py +127 -0
  18. alita_sdk/cli/formatting.py +182 -0
  19. alita_sdk/cli/input_handler.py +419 -0
  20. alita_sdk/cli/inventory.py +1073 -0
  21. alita_sdk/cli/mcp_loader.py +315 -0
  22. alita_sdk/cli/testcases/__init__.py +94 -0
  23. alita_sdk/cli/testcases/data_generation.py +119 -0
  24. alita_sdk/cli/testcases/discovery.py +96 -0
  25. alita_sdk/cli/testcases/executor.py +84 -0
  26. alita_sdk/cli/testcases/logger.py +85 -0
  27. alita_sdk/cli/testcases/parser.py +172 -0
  28. alita_sdk/cli/testcases/prompts.py +91 -0
  29. alita_sdk/cli/testcases/reporting.py +125 -0
  30. alita_sdk/cli/testcases/setup.py +108 -0
  31. alita_sdk/cli/testcases/test_runner.py +282 -0
  32. alita_sdk/cli/testcases/utils.py +39 -0
  33. alita_sdk/cli/testcases/validation.py +90 -0
  34. alita_sdk/cli/testcases/workflow.py +196 -0
  35. alita_sdk/cli/toolkit.py +327 -0
  36. alita_sdk/cli/toolkit_loader.py +85 -0
  37. alita_sdk/cli/tools/__init__.py +43 -0
  38. alita_sdk/cli/tools/approval.py +224 -0
  39. alita_sdk/cli/tools/filesystem.py +1751 -0
  40. alita_sdk/cli/tools/planning.py +389 -0
  41. alita_sdk/cli/tools/terminal.py +414 -0
  42. alita_sdk/community/__init__.py +72 -12
  43. alita_sdk/community/inventory/__init__.py +236 -0
  44. alita_sdk/community/inventory/config.py +257 -0
  45. alita_sdk/community/inventory/enrichment.py +2137 -0
  46. alita_sdk/community/inventory/extractors.py +1469 -0
  47. alita_sdk/community/inventory/ingestion.py +3172 -0
  48. alita_sdk/community/inventory/knowledge_graph.py +1457 -0
  49. alita_sdk/community/inventory/parsers/__init__.py +218 -0
  50. alita_sdk/community/inventory/parsers/base.py +295 -0
  51. alita_sdk/community/inventory/parsers/csharp_parser.py +907 -0
  52. alita_sdk/community/inventory/parsers/go_parser.py +851 -0
  53. alita_sdk/community/inventory/parsers/html_parser.py +389 -0
  54. alita_sdk/community/inventory/parsers/java_parser.py +593 -0
  55. alita_sdk/community/inventory/parsers/javascript_parser.py +629 -0
  56. alita_sdk/community/inventory/parsers/kotlin_parser.py +768 -0
  57. alita_sdk/community/inventory/parsers/markdown_parser.py +362 -0
  58. alita_sdk/community/inventory/parsers/python_parser.py +604 -0
  59. alita_sdk/community/inventory/parsers/rust_parser.py +858 -0
  60. alita_sdk/community/inventory/parsers/swift_parser.py +832 -0
  61. alita_sdk/community/inventory/parsers/text_parser.py +322 -0
  62. alita_sdk/community/inventory/parsers/yaml_parser.py +370 -0
  63. alita_sdk/community/inventory/patterns/__init__.py +61 -0
  64. alita_sdk/community/inventory/patterns/ast_adapter.py +380 -0
  65. alita_sdk/community/inventory/patterns/loader.py +348 -0
  66. alita_sdk/community/inventory/patterns/registry.py +198 -0
  67. alita_sdk/community/inventory/presets.py +535 -0
  68. alita_sdk/community/inventory/retrieval.py +1403 -0
  69. alita_sdk/community/inventory/toolkit.py +173 -0
  70. alita_sdk/community/inventory/toolkit_utils.py +176 -0
  71. alita_sdk/community/inventory/visualize.py +1370 -0
  72. alita_sdk/configurations/__init__.py +1 -1
  73. alita_sdk/configurations/ado.py +141 -20
  74. alita_sdk/configurations/bitbucket.py +94 -2
  75. alita_sdk/configurations/confluence.py +130 -1
  76. alita_sdk/configurations/figma.py +76 -0
  77. alita_sdk/configurations/gitlab.py +91 -0
  78. alita_sdk/configurations/jira.py +103 -0
  79. alita_sdk/configurations/openapi.py +329 -0
  80. alita_sdk/configurations/qtest.py +72 -1
  81. alita_sdk/configurations/report_portal.py +96 -0
  82. alita_sdk/configurations/sharepoint.py +148 -0
  83. alita_sdk/configurations/testio.py +83 -0
  84. alita_sdk/configurations/testrail.py +88 -0
  85. alita_sdk/configurations/xray.py +93 -0
  86. alita_sdk/configurations/zephyr_enterprise.py +93 -0
  87. alita_sdk/configurations/zephyr_essential.py +75 -0
  88. alita_sdk/runtime/clients/artifact.py +3 -3
  89. alita_sdk/runtime/clients/client.py +388 -46
  90. alita_sdk/runtime/clients/mcp_discovery.py +342 -0
  91. alita_sdk/runtime/clients/mcp_manager.py +262 -0
  92. alita_sdk/runtime/clients/sandbox_client.py +8 -21
  93. alita_sdk/runtime/langchain/_constants_bkup.py +1318 -0
  94. alita_sdk/runtime/langchain/assistant.py +157 -39
  95. alita_sdk/runtime/langchain/constants.py +647 -1
  96. alita_sdk/runtime/langchain/document_loaders/AlitaDocxMammothLoader.py +315 -3
  97. alita_sdk/runtime/langchain/document_loaders/AlitaExcelLoader.py +103 -60
  98. alita_sdk/runtime/langchain/document_loaders/AlitaJSONLinesLoader.py +77 -0
  99. alita_sdk/runtime/langchain/document_loaders/AlitaJSONLoader.py +10 -4
  100. alita_sdk/runtime/langchain/document_loaders/AlitaPowerPointLoader.py +226 -7
  101. alita_sdk/runtime/langchain/document_loaders/AlitaTextLoader.py +5 -2
  102. alita_sdk/runtime/langchain/document_loaders/constants.py +40 -19
  103. alita_sdk/runtime/langchain/langraph_agent.py +405 -84
  104. alita_sdk/runtime/langchain/utils.py +106 -7
  105. alita_sdk/runtime/llms/preloaded.py +2 -6
  106. alita_sdk/runtime/models/mcp_models.py +61 -0
  107. alita_sdk/runtime/skills/__init__.py +91 -0
  108. alita_sdk/runtime/skills/callbacks.py +498 -0
  109. alita_sdk/runtime/skills/discovery.py +540 -0
  110. alita_sdk/runtime/skills/executor.py +610 -0
  111. alita_sdk/runtime/skills/input_builder.py +371 -0
  112. alita_sdk/runtime/skills/models.py +330 -0
  113. alita_sdk/runtime/skills/registry.py +355 -0
  114. alita_sdk/runtime/skills/skill_runner.py +330 -0
  115. alita_sdk/runtime/toolkits/__init__.py +31 -0
  116. alita_sdk/runtime/toolkits/application.py +29 -10
  117. alita_sdk/runtime/toolkits/artifact.py +20 -11
  118. alita_sdk/runtime/toolkits/datasource.py +13 -6
  119. alita_sdk/runtime/toolkits/mcp.py +783 -0
  120. alita_sdk/runtime/toolkits/mcp_config.py +1048 -0
  121. alita_sdk/runtime/toolkits/planning.py +178 -0
  122. alita_sdk/runtime/toolkits/skill_router.py +238 -0
  123. alita_sdk/runtime/toolkits/subgraph.py +251 -6
  124. alita_sdk/runtime/toolkits/tools.py +356 -69
  125. alita_sdk/runtime/toolkits/vectorstore.py +11 -5
  126. alita_sdk/runtime/tools/__init__.py +10 -3
  127. alita_sdk/runtime/tools/application.py +27 -6
  128. alita_sdk/runtime/tools/artifact.py +511 -28
  129. alita_sdk/runtime/tools/data_analysis.py +183 -0
  130. alita_sdk/runtime/tools/function.py +67 -35
  131. alita_sdk/runtime/tools/graph.py +10 -4
  132. alita_sdk/runtime/tools/image_generation.py +148 -46
  133. alita_sdk/runtime/tools/llm.py +1003 -128
  134. alita_sdk/runtime/tools/loop.py +3 -1
  135. alita_sdk/runtime/tools/loop_output.py +3 -1
  136. alita_sdk/runtime/tools/mcp_inspect_tool.py +284 -0
  137. alita_sdk/runtime/tools/mcp_remote_tool.py +181 -0
  138. alita_sdk/runtime/tools/mcp_server_tool.py +8 -5
  139. alita_sdk/runtime/tools/planning/__init__.py +36 -0
  140. alita_sdk/runtime/tools/planning/models.py +246 -0
  141. alita_sdk/runtime/tools/planning/wrapper.py +607 -0
  142. alita_sdk/runtime/tools/router.py +2 -4
  143. alita_sdk/runtime/tools/sandbox.py +65 -48
  144. alita_sdk/runtime/tools/skill_router.py +776 -0
  145. alita_sdk/runtime/tools/tool.py +3 -1
  146. alita_sdk/runtime/tools/vectorstore.py +9 -3
  147. alita_sdk/runtime/tools/vectorstore_base.py +70 -14
  148. alita_sdk/runtime/utils/AlitaCallback.py +137 -21
  149. alita_sdk/runtime/utils/constants.py +5 -1
  150. alita_sdk/runtime/utils/mcp_client.py +492 -0
  151. alita_sdk/runtime/utils/mcp_oauth.py +361 -0
  152. alita_sdk/runtime/utils/mcp_sse_client.py +434 -0
  153. alita_sdk/runtime/utils/mcp_tools_discovery.py +124 -0
  154. alita_sdk/runtime/utils/serialization.py +155 -0
  155. alita_sdk/runtime/utils/streamlit.py +40 -13
  156. alita_sdk/runtime/utils/toolkit_utils.py +30 -9
  157. alita_sdk/runtime/utils/utils.py +36 -0
  158. alita_sdk/tools/__init__.py +134 -35
  159. alita_sdk/tools/ado/repos/__init__.py +51 -32
  160. alita_sdk/tools/ado/repos/repos_wrapper.py +148 -89
  161. alita_sdk/tools/ado/test_plan/__init__.py +25 -9
  162. alita_sdk/tools/ado/test_plan/test_plan_wrapper.py +23 -1
  163. alita_sdk/tools/ado/utils.py +1 -18
  164. alita_sdk/tools/ado/wiki/__init__.py +25 -12
  165. alita_sdk/tools/ado/wiki/ado_wrapper.py +291 -22
  166. alita_sdk/tools/ado/work_item/__init__.py +26 -13
  167. alita_sdk/tools/ado/work_item/ado_wrapper.py +73 -11
  168. alita_sdk/tools/advanced_jira_mining/__init__.py +11 -8
  169. alita_sdk/tools/aws/delta_lake/__init__.py +13 -9
  170. alita_sdk/tools/aws/delta_lake/tool.py +5 -1
  171. alita_sdk/tools/azure_ai/search/__init__.py +11 -8
  172. alita_sdk/tools/azure_ai/search/api_wrapper.py +1 -1
  173. alita_sdk/tools/base/tool.py +5 -1
  174. alita_sdk/tools/base_indexer_toolkit.py +271 -84
  175. alita_sdk/tools/bitbucket/__init__.py +17 -11
  176. alita_sdk/tools/bitbucket/api_wrapper.py +59 -11
  177. alita_sdk/tools/bitbucket/cloud_api_wrapper.py +49 -35
  178. alita_sdk/tools/browser/__init__.py +5 -4
  179. alita_sdk/tools/carrier/__init__.py +5 -6
  180. alita_sdk/tools/carrier/backend_reports_tool.py +6 -6
  181. alita_sdk/tools/carrier/run_ui_test_tool.py +6 -6
  182. alita_sdk/tools/carrier/ui_reports_tool.py +5 -5
  183. alita_sdk/tools/chunkers/__init__.py +3 -1
  184. alita_sdk/tools/chunkers/code/treesitter/treesitter.py +37 -13
  185. alita_sdk/tools/chunkers/sematic/json_chunker.py +1 -0
  186. alita_sdk/tools/chunkers/sematic/markdown_chunker.py +97 -6
  187. alita_sdk/tools/chunkers/sematic/proposal_chunker.py +1 -1
  188. alita_sdk/tools/chunkers/universal_chunker.py +270 -0
  189. alita_sdk/tools/cloud/aws/__init__.py +10 -7
  190. alita_sdk/tools/cloud/azure/__init__.py +10 -7
  191. alita_sdk/tools/cloud/gcp/__init__.py +10 -7
  192. alita_sdk/tools/cloud/k8s/__init__.py +10 -7
  193. alita_sdk/tools/code/linter/__init__.py +10 -8
  194. alita_sdk/tools/code/loaders/codesearcher.py +3 -2
  195. alita_sdk/tools/code/sonar/__init__.py +11 -8
  196. alita_sdk/tools/code_indexer_toolkit.py +82 -22
  197. alita_sdk/tools/confluence/__init__.py +22 -16
  198. alita_sdk/tools/confluence/api_wrapper.py +107 -30
  199. alita_sdk/tools/confluence/loader.py +14 -2
  200. alita_sdk/tools/custom_open_api/__init__.py +12 -5
  201. alita_sdk/tools/elastic/__init__.py +11 -8
  202. alita_sdk/tools/elitea_base.py +493 -30
  203. alita_sdk/tools/figma/__init__.py +58 -11
  204. alita_sdk/tools/figma/api_wrapper.py +1235 -143
  205. alita_sdk/tools/figma/figma_client.py +73 -0
  206. alita_sdk/tools/figma/toon_tools.py +2748 -0
  207. alita_sdk/tools/github/__init__.py +14 -15
  208. alita_sdk/tools/github/github_client.py +224 -100
  209. alita_sdk/tools/github/graphql_client_wrapper.py +119 -33
  210. alita_sdk/tools/github/schemas.py +14 -5
  211. alita_sdk/tools/github/tool.py +5 -1
  212. alita_sdk/tools/github/tool_prompts.py +9 -22
  213. alita_sdk/tools/gitlab/__init__.py +16 -11
  214. alita_sdk/tools/gitlab/api_wrapper.py +218 -48
  215. alita_sdk/tools/gitlab_org/__init__.py +10 -9
  216. alita_sdk/tools/gitlab_org/api_wrapper.py +63 -64
  217. alita_sdk/tools/google/bigquery/__init__.py +13 -12
  218. alita_sdk/tools/google/bigquery/tool.py +5 -1
  219. alita_sdk/tools/google_places/__init__.py +11 -8
  220. alita_sdk/tools/google_places/api_wrapper.py +1 -1
  221. alita_sdk/tools/jira/__init__.py +17 -10
  222. alita_sdk/tools/jira/api_wrapper.py +92 -41
  223. alita_sdk/tools/keycloak/__init__.py +11 -8
  224. alita_sdk/tools/localgit/__init__.py +9 -3
  225. alita_sdk/tools/localgit/local_git.py +62 -54
  226. alita_sdk/tools/localgit/tool.py +5 -1
  227. alita_sdk/tools/memory/__init__.py +12 -4
  228. alita_sdk/tools/non_code_indexer_toolkit.py +1 -0
  229. alita_sdk/tools/ocr/__init__.py +11 -8
  230. alita_sdk/tools/openapi/__init__.py +491 -106
  231. alita_sdk/tools/openapi/api_wrapper.py +1368 -0
  232. alita_sdk/tools/openapi/tool.py +20 -0
  233. alita_sdk/tools/pandas/__init__.py +20 -12
  234. alita_sdk/tools/pandas/api_wrapper.py +38 -25
  235. alita_sdk/tools/pandas/dataframe/generator/base.py +3 -1
  236. alita_sdk/tools/postman/__init__.py +10 -9
  237. alita_sdk/tools/pptx/__init__.py +11 -10
  238. alita_sdk/tools/pptx/pptx_wrapper.py +1 -1
  239. alita_sdk/tools/qtest/__init__.py +31 -11
  240. alita_sdk/tools/qtest/api_wrapper.py +2135 -86
  241. alita_sdk/tools/rally/__init__.py +10 -9
  242. alita_sdk/tools/rally/api_wrapper.py +1 -1
  243. alita_sdk/tools/report_portal/__init__.py +12 -8
  244. alita_sdk/tools/salesforce/__init__.py +10 -8
  245. alita_sdk/tools/servicenow/__init__.py +17 -15
  246. alita_sdk/tools/servicenow/api_wrapper.py +1 -1
  247. alita_sdk/tools/sharepoint/__init__.py +10 -7
  248. alita_sdk/tools/sharepoint/api_wrapper.py +129 -38
  249. alita_sdk/tools/sharepoint/authorization_helper.py +191 -1
  250. alita_sdk/tools/sharepoint/utils.py +8 -2
  251. alita_sdk/tools/slack/__init__.py +10 -7
  252. alita_sdk/tools/slack/api_wrapper.py +2 -2
  253. alita_sdk/tools/sql/__init__.py +12 -9
  254. alita_sdk/tools/testio/__init__.py +10 -7
  255. alita_sdk/tools/testrail/__init__.py +11 -10
  256. alita_sdk/tools/testrail/api_wrapper.py +1 -1
  257. alita_sdk/tools/utils/__init__.py +9 -4
  258. alita_sdk/tools/utils/content_parser.py +103 -18
  259. alita_sdk/tools/utils/text_operations.py +410 -0
  260. alita_sdk/tools/utils/tool_prompts.py +79 -0
  261. alita_sdk/tools/vector_adapters/VectorStoreAdapter.py +30 -13
  262. alita_sdk/tools/xray/__init__.py +13 -9
  263. alita_sdk/tools/yagmail/__init__.py +9 -3
  264. alita_sdk/tools/zephyr/__init__.py +10 -7
  265. alita_sdk/tools/zephyr_enterprise/__init__.py +11 -7
  266. alita_sdk/tools/zephyr_essential/__init__.py +10 -7
  267. alita_sdk/tools/zephyr_essential/api_wrapper.py +30 -13
  268. alita_sdk/tools/zephyr_essential/client.py +2 -2
  269. alita_sdk/tools/zephyr_scale/__init__.py +11 -8
  270. alita_sdk/tools/zephyr_scale/api_wrapper.py +2 -2
  271. alita_sdk/tools/zephyr_squad/__init__.py +10 -7
  272. {alita_sdk-0.3.379.dist-info → alita_sdk-0.3.627.dist-info}/METADATA +154 -8
  273. alita_sdk-0.3.627.dist-info/RECORD +468 -0
  274. alita_sdk-0.3.627.dist-info/entry_points.txt +2 -0
  275. alita_sdk-0.3.379.dist-info/RECORD +0 -360
  276. {alita_sdk-0.3.379.dist-info → alita_sdk-0.3.627.dist-info}/WHEEL +0 -0
  277. {alita_sdk-0.3.379.dist-info → alita_sdk-0.3.627.dist-info}/licenses/LICENSE +0 -0
  278. {alita_sdk-0.3.379.dist-info → alita_sdk-0.3.627.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)
@@ -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
- tool_type = tool.get('type')
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
- toolkit = toolkit_class.get_toolkit(
74
- client=alita_client,
75
- **tool['settings']
76
- )
77
- tools.extend(toolkit.get_tools())
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