alita-sdk 0.3.257__py3-none-any.whl → 0.3.584__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.

Potentially problematic release.


This version of alita-sdk might be problematic. Click here for more details.

Files changed (281) 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 +155 -0
  6. alita_sdk/cli/agent_loader.py +215 -0
  7. alita_sdk/cli/agent_ui.py +228 -0
  8. alita_sdk/cli/agents.py +3794 -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/toolkit.py +327 -0
  23. alita_sdk/cli/toolkit_loader.py +85 -0
  24. alita_sdk/cli/tools/__init__.py +43 -0
  25. alita_sdk/cli/tools/approval.py +224 -0
  26. alita_sdk/cli/tools/filesystem.py +1751 -0
  27. alita_sdk/cli/tools/planning.py +389 -0
  28. alita_sdk/cli/tools/terminal.py +414 -0
  29. alita_sdk/community/__init__.py +72 -12
  30. alita_sdk/community/inventory/__init__.py +236 -0
  31. alita_sdk/community/inventory/config.py +257 -0
  32. alita_sdk/community/inventory/enrichment.py +2137 -0
  33. alita_sdk/community/inventory/extractors.py +1469 -0
  34. alita_sdk/community/inventory/ingestion.py +3172 -0
  35. alita_sdk/community/inventory/knowledge_graph.py +1457 -0
  36. alita_sdk/community/inventory/parsers/__init__.py +218 -0
  37. alita_sdk/community/inventory/parsers/base.py +295 -0
  38. alita_sdk/community/inventory/parsers/csharp_parser.py +907 -0
  39. alita_sdk/community/inventory/parsers/go_parser.py +851 -0
  40. alita_sdk/community/inventory/parsers/html_parser.py +389 -0
  41. alita_sdk/community/inventory/parsers/java_parser.py +593 -0
  42. alita_sdk/community/inventory/parsers/javascript_parser.py +629 -0
  43. alita_sdk/community/inventory/parsers/kotlin_parser.py +768 -0
  44. alita_sdk/community/inventory/parsers/markdown_parser.py +362 -0
  45. alita_sdk/community/inventory/parsers/python_parser.py +604 -0
  46. alita_sdk/community/inventory/parsers/rust_parser.py +858 -0
  47. alita_sdk/community/inventory/parsers/swift_parser.py +832 -0
  48. alita_sdk/community/inventory/parsers/text_parser.py +322 -0
  49. alita_sdk/community/inventory/parsers/yaml_parser.py +370 -0
  50. alita_sdk/community/inventory/patterns/__init__.py +61 -0
  51. alita_sdk/community/inventory/patterns/ast_adapter.py +380 -0
  52. alita_sdk/community/inventory/patterns/loader.py +348 -0
  53. alita_sdk/community/inventory/patterns/registry.py +198 -0
  54. alita_sdk/community/inventory/presets.py +535 -0
  55. alita_sdk/community/inventory/retrieval.py +1403 -0
  56. alita_sdk/community/inventory/toolkit.py +173 -0
  57. alita_sdk/community/inventory/toolkit_utils.py +176 -0
  58. alita_sdk/community/inventory/visualize.py +1370 -0
  59. alita_sdk/configurations/__init__.py +11 -0
  60. alita_sdk/configurations/ado.py +148 -2
  61. alita_sdk/configurations/azure_search.py +1 -1
  62. alita_sdk/configurations/bigquery.py +1 -1
  63. alita_sdk/configurations/bitbucket.py +94 -2
  64. alita_sdk/configurations/browser.py +18 -0
  65. alita_sdk/configurations/carrier.py +19 -0
  66. alita_sdk/configurations/confluence.py +130 -1
  67. alita_sdk/configurations/delta_lake.py +1 -1
  68. alita_sdk/configurations/figma.py +76 -5
  69. alita_sdk/configurations/github.py +65 -1
  70. alita_sdk/configurations/gitlab.py +81 -0
  71. alita_sdk/configurations/google_places.py +17 -0
  72. alita_sdk/configurations/jira.py +103 -0
  73. alita_sdk/configurations/openapi.py +323 -0
  74. alita_sdk/configurations/postman.py +1 -1
  75. alita_sdk/configurations/qtest.py +72 -3
  76. alita_sdk/configurations/report_portal.py +115 -0
  77. alita_sdk/configurations/salesforce.py +19 -0
  78. alita_sdk/configurations/service_now.py +1 -12
  79. alita_sdk/configurations/sharepoint.py +167 -0
  80. alita_sdk/configurations/sonar.py +18 -0
  81. alita_sdk/configurations/sql.py +20 -0
  82. alita_sdk/configurations/testio.py +101 -0
  83. alita_sdk/configurations/testrail.py +88 -0
  84. alita_sdk/configurations/xray.py +94 -1
  85. alita_sdk/configurations/zephyr_enterprise.py +94 -1
  86. alita_sdk/configurations/zephyr_essential.py +95 -0
  87. alita_sdk/runtime/clients/artifact.py +21 -4
  88. alita_sdk/runtime/clients/client.py +458 -67
  89. alita_sdk/runtime/clients/mcp_discovery.py +342 -0
  90. alita_sdk/runtime/clients/mcp_manager.py +262 -0
  91. alita_sdk/runtime/clients/sandbox_client.py +352 -0
  92. alita_sdk/runtime/langchain/_constants_bkup.py +1318 -0
  93. alita_sdk/runtime/langchain/assistant.py +183 -43
  94. alita_sdk/runtime/langchain/constants.py +647 -1
  95. alita_sdk/runtime/langchain/document_loaders/AlitaDocxMammothLoader.py +315 -3
  96. alita_sdk/runtime/langchain/document_loaders/AlitaExcelLoader.py +209 -31
  97. alita_sdk/runtime/langchain/document_loaders/AlitaImageLoader.py +1 -1
  98. alita_sdk/runtime/langchain/document_loaders/AlitaJSONLinesLoader.py +77 -0
  99. alita_sdk/runtime/langchain/document_loaders/AlitaJSONLoader.py +10 -3
  100. alita_sdk/runtime/langchain/document_loaders/AlitaMarkdownLoader.py +66 -0
  101. alita_sdk/runtime/langchain/document_loaders/AlitaPDFLoader.py +79 -10
  102. alita_sdk/runtime/langchain/document_loaders/AlitaPowerPointLoader.py +52 -15
  103. alita_sdk/runtime/langchain/document_loaders/AlitaPythonLoader.py +9 -0
  104. alita_sdk/runtime/langchain/document_loaders/AlitaTableLoader.py +1 -4
  105. alita_sdk/runtime/langchain/document_loaders/AlitaTextLoader.py +15 -2
  106. alita_sdk/runtime/langchain/document_loaders/ImageParser.py +30 -0
  107. alita_sdk/runtime/langchain/document_loaders/constants.py +189 -41
  108. alita_sdk/runtime/langchain/interfaces/llm_processor.py +4 -2
  109. alita_sdk/runtime/langchain/langraph_agent.py +493 -105
  110. alita_sdk/runtime/langchain/utils.py +118 -8
  111. alita_sdk/runtime/llms/preloaded.py +2 -6
  112. alita_sdk/runtime/models/mcp_models.py +61 -0
  113. alita_sdk/runtime/skills/__init__.py +91 -0
  114. alita_sdk/runtime/skills/callbacks.py +498 -0
  115. alita_sdk/runtime/skills/discovery.py +540 -0
  116. alita_sdk/runtime/skills/executor.py +610 -0
  117. alita_sdk/runtime/skills/input_builder.py +371 -0
  118. alita_sdk/runtime/skills/models.py +330 -0
  119. alita_sdk/runtime/skills/registry.py +355 -0
  120. alita_sdk/runtime/skills/skill_runner.py +330 -0
  121. alita_sdk/runtime/toolkits/__init__.py +28 -0
  122. alita_sdk/runtime/toolkits/application.py +14 -4
  123. alita_sdk/runtime/toolkits/artifact.py +25 -9
  124. alita_sdk/runtime/toolkits/datasource.py +13 -6
  125. alita_sdk/runtime/toolkits/mcp.py +782 -0
  126. alita_sdk/runtime/toolkits/planning.py +178 -0
  127. alita_sdk/runtime/toolkits/skill_router.py +238 -0
  128. alita_sdk/runtime/toolkits/subgraph.py +11 -6
  129. alita_sdk/runtime/toolkits/tools.py +314 -70
  130. alita_sdk/runtime/toolkits/vectorstore.py +11 -5
  131. alita_sdk/runtime/tools/__init__.py +24 -0
  132. alita_sdk/runtime/tools/application.py +16 -4
  133. alita_sdk/runtime/tools/artifact.py +367 -33
  134. alita_sdk/runtime/tools/data_analysis.py +183 -0
  135. alita_sdk/runtime/tools/function.py +100 -4
  136. alita_sdk/runtime/tools/graph.py +81 -0
  137. alita_sdk/runtime/tools/image_generation.py +218 -0
  138. alita_sdk/runtime/tools/llm.py +1032 -177
  139. alita_sdk/runtime/tools/loop.py +3 -1
  140. alita_sdk/runtime/tools/loop_output.py +3 -1
  141. alita_sdk/runtime/tools/mcp_inspect_tool.py +284 -0
  142. alita_sdk/runtime/tools/mcp_remote_tool.py +181 -0
  143. alita_sdk/runtime/tools/mcp_server_tool.py +3 -1
  144. alita_sdk/runtime/tools/planning/__init__.py +36 -0
  145. alita_sdk/runtime/tools/planning/models.py +246 -0
  146. alita_sdk/runtime/tools/planning/wrapper.py +607 -0
  147. alita_sdk/runtime/tools/router.py +2 -1
  148. alita_sdk/runtime/tools/sandbox.py +375 -0
  149. alita_sdk/runtime/tools/skill_router.py +776 -0
  150. alita_sdk/runtime/tools/tool.py +3 -1
  151. alita_sdk/runtime/tools/vectorstore.py +69 -65
  152. alita_sdk/runtime/tools/vectorstore_base.py +163 -90
  153. alita_sdk/runtime/utils/AlitaCallback.py +137 -21
  154. alita_sdk/runtime/utils/constants.py +5 -1
  155. alita_sdk/runtime/utils/mcp_client.py +492 -0
  156. alita_sdk/runtime/utils/mcp_oauth.py +361 -0
  157. alita_sdk/runtime/utils/mcp_sse_client.py +434 -0
  158. alita_sdk/runtime/utils/mcp_tools_discovery.py +124 -0
  159. alita_sdk/runtime/utils/streamlit.py +41 -14
  160. alita_sdk/runtime/utils/toolkit_utils.py +28 -9
  161. alita_sdk/runtime/utils/utils.py +48 -0
  162. alita_sdk/tools/__init__.py +135 -37
  163. alita_sdk/tools/ado/__init__.py +2 -2
  164. alita_sdk/tools/ado/repos/__init__.py +16 -19
  165. alita_sdk/tools/ado/repos/repos_wrapper.py +12 -20
  166. alita_sdk/tools/ado/test_plan/__init__.py +27 -8
  167. alita_sdk/tools/ado/test_plan/test_plan_wrapper.py +56 -28
  168. alita_sdk/tools/ado/wiki/__init__.py +28 -12
  169. alita_sdk/tools/ado/wiki/ado_wrapper.py +114 -40
  170. alita_sdk/tools/ado/work_item/__init__.py +28 -12
  171. alita_sdk/tools/ado/work_item/ado_wrapper.py +95 -11
  172. alita_sdk/tools/advanced_jira_mining/__init__.py +13 -8
  173. alita_sdk/tools/aws/delta_lake/__init__.py +15 -11
  174. alita_sdk/tools/aws/delta_lake/tool.py +5 -1
  175. alita_sdk/tools/azure_ai/search/__init__.py +14 -8
  176. alita_sdk/tools/base/tool.py +5 -1
  177. alita_sdk/tools/base_indexer_toolkit.py +454 -110
  178. alita_sdk/tools/bitbucket/__init__.py +28 -19
  179. alita_sdk/tools/bitbucket/api_wrapper.py +285 -27
  180. alita_sdk/tools/bitbucket/cloud_api_wrapper.py +5 -5
  181. alita_sdk/tools/browser/__init__.py +41 -16
  182. alita_sdk/tools/browser/crawler.py +3 -1
  183. alita_sdk/tools/browser/utils.py +15 -6
  184. alita_sdk/tools/carrier/__init__.py +18 -17
  185. alita_sdk/tools/carrier/backend_reports_tool.py +8 -4
  186. alita_sdk/tools/carrier/excel_reporter.py +8 -4
  187. alita_sdk/tools/chunkers/__init__.py +3 -1
  188. alita_sdk/tools/chunkers/code/codeparser.py +1 -1
  189. alita_sdk/tools/chunkers/sematic/json_chunker.py +2 -1
  190. alita_sdk/tools/chunkers/sematic/markdown_chunker.py +97 -6
  191. alita_sdk/tools/chunkers/sematic/proposal_chunker.py +1 -1
  192. alita_sdk/tools/chunkers/universal_chunker.py +270 -0
  193. alita_sdk/tools/cloud/aws/__init__.py +12 -7
  194. alita_sdk/tools/cloud/azure/__init__.py +12 -7
  195. alita_sdk/tools/cloud/gcp/__init__.py +12 -7
  196. alita_sdk/tools/cloud/k8s/__init__.py +12 -7
  197. alita_sdk/tools/code/linter/__init__.py +10 -8
  198. alita_sdk/tools/code/loaders/codesearcher.py +3 -2
  199. alita_sdk/tools/code/sonar/__init__.py +21 -13
  200. alita_sdk/tools/code_indexer_toolkit.py +199 -0
  201. alita_sdk/tools/confluence/__init__.py +22 -14
  202. alita_sdk/tools/confluence/api_wrapper.py +197 -58
  203. alita_sdk/tools/confluence/loader.py +14 -2
  204. alita_sdk/tools/custom_open_api/__init__.py +12 -5
  205. alita_sdk/tools/elastic/__init__.py +11 -8
  206. alita_sdk/tools/elitea_base.py +546 -64
  207. alita_sdk/tools/figma/__init__.py +60 -11
  208. alita_sdk/tools/figma/api_wrapper.py +1400 -167
  209. alita_sdk/tools/figma/figma_client.py +73 -0
  210. alita_sdk/tools/figma/toon_tools.py +2748 -0
  211. alita_sdk/tools/github/__init__.py +18 -17
  212. alita_sdk/tools/github/api_wrapper.py +9 -26
  213. alita_sdk/tools/github/github_client.py +81 -12
  214. alita_sdk/tools/github/schemas.py +2 -1
  215. alita_sdk/tools/github/tool.py +5 -1
  216. alita_sdk/tools/gitlab/__init__.py +19 -13
  217. alita_sdk/tools/gitlab/api_wrapper.py +256 -80
  218. alita_sdk/tools/gitlab_org/__init__.py +14 -10
  219. alita_sdk/tools/google/bigquery/__init__.py +14 -13
  220. alita_sdk/tools/google/bigquery/tool.py +5 -1
  221. alita_sdk/tools/google_places/__init__.py +21 -11
  222. alita_sdk/tools/jira/__init__.py +22 -11
  223. alita_sdk/tools/jira/api_wrapper.py +315 -168
  224. alita_sdk/tools/keycloak/__init__.py +11 -8
  225. alita_sdk/tools/localgit/__init__.py +9 -3
  226. alita_sdk/tools/localgit/local_git.py +62 -54
  227. alita_sdk/tools/localgit/tool.py +5 -1
  228. alita_sdk/tools/memory/__init__.py +38 -14
  229. alita_sdk/tools/non_code_indexer_toolkit.py +7 -2
  230. alita_sdk/tools/ocr/__init__.py +11 -8
  231. alita_sdk/tools/openapi/__init__.py +491 -106
  232. alita_sdk/tools/openapi/api_wrapper.py +1357 -0
  233. alita_sdk/tools/openapi/tool.py +20 -0
  234. alita_sdk/tools/pandas/__init__.py +20 -12
  235. alita_sdk/tools/pandas/api_wrapper.py +40 -45
  236. alita_sdk/tools/pandas/dataframe/generator/base.py +3 -1
  237. alita_sdk/tools/postman/__init__.py +11 -11
  238. alita_sdk/tools/postman/api_wrapper.py +19 -8
  239. alita_sdk/tools/postman/postman_analysis.py +8 -1
  240. alita_sdk/tools/pptx/__init__.py +11 -10
  241. alita_sdk/tools/qtest/__init__.py +22 -14
  242. alita_sdk/tools/qtest/api_wrapper.py +1784 -88
  243. alita_sdk/tools/rally/__init__.py +13 -10
  244. alita_sdk/tools/report_portal/__init__.py +23 -16
  245. alita_sdk/tools/salesforce/__init__.py +22 -16
  246. alita_sdk/tools/servicenow/__init__.py +21 -16
  247. alita_sdk/tools/servicenow/api_wrapper.py +1 -1
  248. alita_sdk/tools/sharepoint/__init__.py +17 -14
  249. alita_sdk/tools/sharepoint/api_wrapper.py +179 -39
  250. alita_sdk/tools/sharepoint/authorization_helper.py +191 -1
  251. alita_sdk/tools/sharepoint/utils.py +8 -2
  252. alita_sdk/tools/slack/__init__.py +13 -8
  253. alita_sdk/tools/sql/__init__.py +22 -19
  254. alita_sdk/tools/sql/api_wrapper.py +71 -23
  255. alita_sdk/tools/testio/__init__.py +21 -13
  256. alita_sdk/tools/testrail/__init__.py +13 -11
  257. alita_sdk/tools/testrail/api_wrapper.py +214 -46
  258. alita_sdk/tools/utils/__init__.py +28 -4
  259. alita_sdk/tools/utils/content_parser.py +241 -55
  260. alita_sdk/tools/utils/text_operations.py +254 -0
  261. alita_sdk/tools/vector_adapters/VectorStoreAdapter.py +83 -27
  262. alita_sdk/tools/xray/__init__.py +18 -14
  263. alita_sdk/tools/xray/api_wrapper.py +58 -113
  264. alita_sdk/tools/yagmail/__init__.py +9 -3
  265. alita_sdk/tools/zephyr/__init__.py +12 -7
  266. alita_sdk/tools/zephyr_enterprise/__init__.py +16 -9
  267. alita_sdk/tools/zephyr_enterprise/api_wrapper.py +30 -15
  268. alita_sdk/tools/zephyr_essential/__init__.py +16 -10
  269. alita_sdk/tools/zephyr_essential/api_wrapper.py +297 -54
  270. alita_sdk/tools/zephyr_essential/client.py +6 -4
  271. alita_sdk/tools/zephyr_scale/__init__.py +13 -8
  272. alita_sdk/tools/zephyr_scale/api_wrapper.py +39 -31
  273. alita_sdk/tools/zephyr_squad/__init__.py +12 -7
  274. {alita_sdk-0.3.257.dist-info → alita_sdk-0.3.584.dist-info}/METADATA +184 -37
  275. alita_sdk-0.3.584.dist-info/RECORD +452 -0
  276. alita_sdk-0.3.584.dist-info/entry_points.txt +2 -0
  277. alita_sdk/tools/bitbucket/tools.py +0 -304
  278. alita_sdk-0.3.257.dist-info/RECORD +0 -343
  279. {alita_sdk-0.3.257.dist-info → alita_sdk-0.3.584.dist-info}/WHEEL +0 -0
  280. {alita_sdk-0.3.257.dist-info → alita_sdk-0.3.584.dist-info}/licenses/LICENSE +0 -0
  281. {alita_sdk-0.3.257.dist-info → alita_sdk-0.3.584.dist-info}/top_level.txt +0 -0
alita_sdk/cli/cli.py ADDED
@@ -0,0 +1,168 @@
1
+ """
2
+ Main CLI application for Alita SDK.
3
+
4
+ Provides command-line interface for testing agents and toolkits,
5
+ using the same .env authentication as SDK tests and Streamlit interface.
6
+ """
7
+
8
+ # Suppress warnings FIRST before any other imports
9
+ import warnings
10
+ warnings.filterwarnings('ignore', category=DeprecationWarning)
11
+ warnings.filterwarnings('ignore', category=UserWarning)
12
+ warnings.filterwarnings('ignore', message='Unverified HTTPS request')
13
+
14
+ import click
15
+ import logging
16
+ import sys
17
+ from typing import Optional
18
+
19
+ from .config import get_config
20
+ from .formatting import get_formatter
21
+
22
+ # Configure logging
23
+ logging.basicConfig(
24
+ level=logging.WARNING,
25
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
26
+ )
27
+ logger = logging.getLogger(__name__)
28
+
29
+
30
+ @click.group()
31
+ @click.option('--env-file', default='.env', help='Path to .env file')
32
+ @click.option('--debug', is_flag=True, help='Enable debug logging')
33
+ @click.option('--verbose', '-v', is_flag=True, help='Enable verbose/info logging (shows timing)')
34
+ @click.option('--output', type=click.Choice(['text', 'json']), default='text',
35
+ help='Output format')
36
+ @click.pass_context
37
+ def cli(ctx, env_file: str, debug: bool, verbose: bool, output: str):
38
+ """
39
+ Alita SDK CLI - Test agents and toolkits from the command line.
40
+
41
+ Credentials are loaded from .env file with variables:
42
+ - DEPLOYMENT_URL: Alita deployment URL
43
+ - PROJECT_ID: Project ID
44
+ - API_KEY: API authentication key
45
+
46
+ Example .env file:
47
+
48
+ DEPLOYMENT_URL=https://api.elitea.ai
49
+ PROJECT_ID=123
50
+ API_KEY=your_api_key_here
51
+ """
52
+ ctx.ensure_object(dict)
53
+
54
+ # Enable debug logging if requested
55
+ if debug:
56
+ logging.getLogger('alita_sdk').setLevel(logging.DEBUG)
57
+ logger.setLevel(logging.DEBUG)
58
+ logger.debug("Debug logging enabled")
59
+ elif verbose:
60
+ # Verbose mode shows INFO level (timing info)
61
+ logging.getLogger('alita_sdk').setLevel(logging.INFO)
62
+ logger.setLevel(logging.INFO)
63
+ logger.info("Verbose logging enabled")
64
+
65
+ # Load configuration
66
+ config = get_config(env_file=env_file)
67
+ ctx.obj['config'] = config
68
+ ctx.obj['formatter'] = get_formatter(output)
69
+ ctx.obj['debug'] = debug
70
+ ctx.obj['verbose'] = verbose
71
+
72
+ # Check if configuration is valid (but don't fail yet - some commands don't need it)
73
+ if not config.is_configured():
74
+ missing = config.get_missing_config()
75
+ ctx.obj['config_error'] = f"Missing required configuration: {', '.join(missing)}"
76
+ logger.debug(f"Configuration incomplete: {missing}")
77
+ else:
78
+ ctx.obj['config_error'] = None
79
+ logger.debug(f"Configuration loaded from {env_file}")
80
+
81
+
82
+ def get_client(ctx):
83
+ """
84
+ Get configured AlitaClient from context.
85
+
86
+ Raises click.ClickException if configuration is invalid.
87
+ """
88
+ if ctx.obj.get('config_error'):
89
+ raise click.ClickException(
90
+ f"{ctx.obj['config_error']}\n\n"
91
+ "Please ensure your .env file contains:\n"
92
+ " DEPLOYMENT_URL=https://api.elitea.ai\n"
93
+ " PROJECT_ID=123\n"
94
+ " API_KEY=your_api_key_here"
95
+ )
96
+
97
+ # Import here to avoid loading SDK if not needed
98
+ from alita_sdk.runtime.clients.client import AlitaClient
99
+
100
+ config = ctx.obj['config']
101
+
102
+ try:
103
+ client = AlitaClient(
104
+ base_url=config.deployment_url,
105
+ project_id=config.project_id,
106
+ auth_token=config.api_key
107
+ )
108
+ logger.debug(f"AlitaClient initialized for project {config.project_id}")
109
+ return client
110
+ except Exception as e:
111
+ raise click.ClickException(f"Failed to initialize AlitaClient: {str(e)}")
112
+
113
+
114
+ @cli.command()
115
+ @click.pass_context
116
+ def config(ctx):
117
+ """Show current configuration (credentials masked)."""
118
+ config_obj = ctx.obj['config']
119
+ formatter = ctx.obj['formatter']
120
+
121
+ if formatter.__class__.__name__ == 'JSONFormatter':
122
+ click.echo(formatter._dump(config_obj.to_dict()))
123
+ else:
124
+ click.echo("\nCurrent configuration:\n")
125
+ for key, value in config_obj.to_dict().items():
126
+ click.echo(f" {key}: {value}")
127
+
128
+ if not config_obj.is_configured():
129
+ missing = config_obj.get_missing_config()
130
+ click.echo(f"\n⚠ Missing: {', '.join(missing)}")
131
+ else:
132
+ click.echo("\n✓ Configuration is complete")
133
+
134
+
135
+ # Import subcommands
136
+ from . import toolkit
137
+ from . import agents
138
+ from . import inventory
139
+
140
+ # Register subcommands
141
+ cli.add_command(toolkit.toolkit)
142
+ cli.add_command(agents.agent)
143
+ cli.add_command(inventory.inventory)
144
+
145
+ # Add top-level 'chat' command as alias to 'agent chat'
146
+ cli.add_command(agents.agent_chat, name='chat')
147
+
148
+
149
+ def main():
150
+ """Entry point for CLI."""
151
+ # Suppress warnings at entry point
152
+ warnings.filterwarnings('ignore', category=DeprecationWarning)
153
+ warnings.filterwarnings('ignore', category=UserWarning)
154
+ warnings.filterwarnings('ignore', message='Unverified HTTPS request')
155
+
156
+ try:
157
+ cli()
158
+ except KeyboardInterrupt:
159
+ click.echo("\n\nInterrupted by user", err=True)
160
+ sys.exit(130)
161
+ except Exception as e:
162
+ logger.exception("Unexpected error")
163
+ click.echo(f"\nError: {str(e)}", err=True)
164
+ sys.exit(1)
165
+
166
+
167
+ if __name__ == '__main__':
168
+ main()
@@ -0,0 +1,306 @@
1
+ """
2
+ Configuration management for Alita CLI.
3
+
4
+ Loads credentials and settings from .env files using the same pattern
5
+ as the SDK tests and Streamlit interface.
6
+ """
7
+
8
+ import os
9
+ import re
10
+ import json
11
+ from typing import Optional, Dict, Any
12
+ from dotenv import load_dotenv
13
+ import logging
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ class CLIConfig:
19
+ """Configuration manager for Alita CLI."""
20
+
21
+ def __init__(self, env_file: Optional[str] = None):
22
+ """
23
+ Initialize CLI configuration.
24
+
25
+ Args:
26
+ env_file: Path to .env file. If None, checks ALITA_ENV_FILE env var,
27
+ then falls back to .alita/.env or .env in current directory
28
+ """
29
+ self._config_json: Dict[str, Any] = {}
30
+
31
+ if env_file:
32
+ self.env_file = env_file
33
+ else:
34
+ # Check ALITA_ENV_FILE environment variable first
35
+ alita_env_file = os.getenv('ALITA_ENV_FILE')
36
+ if alita_env_file:
37
+ # Expand ~ and resolve path
38
+ expanded_path = os.path.expanduser(alita_env_file)
39
+ if os.path.exists(expanded_path):
40
+ self.env_file = expanded_path
41
+ else:
42
+ logger.warning(f"ALITA_ENV_FILE set to {alita_env_file} but file not found")
43
+ self.env_file = expanded_path # Still use it, will warn later
44
+ elif os.path.exists(os.path.expanduser('~/.alita/.env')):
45
+ self.env_file = os.path.expanduser('~/.alita/.env')
46
+ elif os.path.exists('.alita/.env'):
47
+ self.env_file = '.alita/.env'
48
+ else:
49
+ self.env_file = '.env'
50
+ self._load_env()
51
+ self._load_config_json()
52
+
53
+ def _load_env(self):
54
+ """Load environment variables from .env file."""
55
+ if os.path.exists(self.env_file):
56
+ # Use override=True to ensure .env values take precedence
57
+ load_dotenv(self.env_file, override=True)
58
+ logger.debug(f"Loaded environment from {self.env_file}")
59
+ else:
60
+ logger.debug(f"No .env file found at {self.env_file}, using system environment")
61
+
62
+ def _load_config_json(self):
63
+ """Load configuration from $ALITA_DIR/config.json as fallback."""
64
+ # Try ALITA_DIR from env, then ~/.alita, then .alita
65
+ alita_dir = os.getenv('ALITA_DIR')
66
+ if alita_dir:
67
+ config_path = os.path.join(os.path.expanduser(alita_dir), 'config.json')
68
+ else:
69
+ # Try ~/.alita/config.json first, then .alita/config.json
70
+ home_config = os.path.expanduser('~/.alita/config.json')
71
+ local_config = '.alita/config.json'
72
+ if os.path.exists(home_config):
73
+ config_path = home_config
74
+ elif os.path.exists(local_config):
75
+ config_path = local_config
76
+ else:
77
+ config_path = home_config # Default path even if doesn't exist
78
+
79
+ if os.path.exists(config_path):
80
+ try:
81
+ with open(config_path, 'r') as f:
82
+ self._config_json = json.load(f)
83
+ logger.debug(f"Loaded config from {config_path}")
84
+
85
+ # Load env section into environment variables
86
+ self._load_env_section()
87
+ except (json.JSONDecodeError, IOError) as e:
88
+ logger.warning(f"Failed to load config.json: {e}")
89
+
90
+ def _load_env_section(self):
91
+ """Load variables from config.json 'env' section into environment."""
92
+ env_vars = self._config_json.get('env', {})
93
+ if isinstance(env_vars, dict):
94
+ for key, value in env_vars.items():
95
+ # Only set if not already in environment (env vars take precedence)
96
+ if key not in os.environ and value is not None:
97
+ os.environ[key] = str(value)
98
+ logger.debug(f"Set {key} from config.json env section")
99
+
100
+ def _get_config_value(self, env_key: str, json_key: Optional[str] = None) -> Optional[str]:
101
+ """
102
+ Get config value from environment first, then config.json fallback.
103
+
104
+ Args:
105
+ env_key: Environment variable name
106
+ json_key: Key in config.json (defaults to lowercase of env_key)
107
+ """
108
+ # Try environment variable first
109
+ value = os.getenv(env_key)
110
+ if value:
111
+ return value
112
+
113
+ # Fallback to config.json
114
+ if json_key is None:
115
+ json_key = env_key.lower()
116
+
117
+ return self._config_json.get(json_key)
118
+
119
+ @property
120
+ def deployment_url(self) -> Optional[str]:
121
+ """Get deployment URL from environment or config.json."""
122
+ return self._get_config_value('DEPLOYMENT_URL', 'deployment_url')
123
+
124
+ @property
125
+ def project_id(self) -> Optional[int]:
126
+ """Get project ID from environment or config.json."""
127
+ try:
128
+ value = self._get_config_value('PROJECT_ID', 'project_id')
129
+ return int(value) if value else None
130
+ except (TypeError, ValueError):
131
+ return None
132
+
133
+ @property
134
+ def api_key(self) -> Optional[str]:
135
+ """Get API key from environment or config.json."""
136
+ return self._get_config_value('API_KEY', 'api_key')
137
+
138
+ @property
139
+ def default_model(self) -> Optional[str]:
140
+ """Get default model from environment or config.json."""
141
+ return self._get_config_value('ALITA_DEFAULT_MODEL', 'default_model')
142
+
143
+ @property
144
+ def default_temperature(self) -> Optional[float]:
145
+ """Get default temperature from environment or config.json."""
146
+ try:
147
+ value = self._get_config_value('ALITA_DEFAULT_TEMPERATURE', 'default_temperature')
148
+ return float(value) if value is not None else None
149
+ except (TypeError, ValueError):
150
+ return None
151
+
152
+ @property
153
+ def default_max_tokens(self) -> Optional[int]:
154
+ """Get default max tokens from environment or config.json."""
155
+ try:
156
+ value = self._get_config_value('ALITA_DEFAULT_MAX_TOKENS', 'default_max_tokens')
157
+ return int(value) if value is not None else None
158
+ except (TypeError, ValueError):
159
+ return None
160
+
161
+ @property
162
+ def alita_dir(self) -> str:
163
+ """Get Alita directory from environment (defaults to .alita)."""
164
+ return os.getenv('ALITA_DIR', '.alita')
165
+
166
+ @property
167
+ def agents_dir(self) -> str:
168
+ """Get agents directory (derived from ALITA_DIR)."""
169
+ alita_agents = os.path.join(self.alita_dir, 'agents')
170
+ # Fallback to .github/agents if .alita/agents doesn't exist
171
+ if self.alita_dir == '.alita' and not os.path.exists(alita_agents):
172
+ if os.path.exists('.github/agents'):
173
+ return '.github/agents'
174
+ return alita_agents
175
+
176
+ @property
177
+ def tools_dir(self) -> str:
178
+ """Get tools directory (derived from ALITA_DIR)."""
179
+ return os.path.join(self.alita_dir, 'tools')
180
+
181
+ @property
182
+ def mcp_config_path(self) -> str:
183
+ """Get MCP configuration path (derived from ALITA_DIR)."""
184
+ alita_mcp = os.path.join(self.alita_dir, 'mcp.json')
185
+ # Fallback to mcp.json in current directory
186
+ if not os.path.exists(alita_mcp) and os.path.exists('mcp.json'):
187
+ return 'mcp.json'
188
+ return alita_mcp
189
+
190
+ @property
191
+ def context_management(self) -> Dict[str, Any]:
192
+ """
193
+ Get context management configuration from config.json.
194
+
195
+ Returns configuration for chat history context management with defaults:
196
+ - enabled: True - Enable context management
197
+ - max_context_tokens: 8000 - Maximum tokens in context
198
+ - preserve_recent_messages: 5 - Always keep N most recent messages
199
+ - pruning_method: 'oldest_first' - Strategy for pruning (oldest_first, importance_based)
200
+ - enable_summarization: True - Generate summaries of pruned messages
201
+ - summary_trigger_ratio: 0.8 - Trigger summarization at 80% context fill
202
+ - summaries_limit_count: 5 - Maximum number of summaries to keep
203
+ - session_max_age_days: 30 - Purge sessions older than N days
204
+ - max_sessions: 50 - Maximum number of sessions to keep
205
+ """
206
+ defaults = {
207
+ 'enabled': True,
208
+ 'max_context_tokens': 8000,
209
+ 'preserve_recent_messages': 5,
210
+ 'pruning_method': 'oldest_first',
211
+ 'enable_summarization': True,
212
+ 'summary_trigger_ratio': 0.8,
213
+ 'summaries_limit_count': 5,
214
+ 'session_max_age_days': 30,
215
+ 'max_sessions': 50,
216
+ 'weights': {
217
+ 'recency': 1.0,
218
+ 'importance': 1.0,
219
+ 'user_messages': 1.2,
220
+ 'thread_continuity': 1.0,
221
+ },
222
+ }
223
+
224
+ # Get from config.json
225
+ config = self._config_json.get('context_management', {})
226
+
227
+ # Merge with defaults
228
+ result = defaults.copy()
229
+ if isinstance(config, dict):
230
+ result.update(config)
231
+
232
+ return result
233
+
234
+ @property
235
+ def sessions_dir(self) -> str:
236
+ """Get sessions directory (derived from ALITA_DIR)."""
237
+ return os.path.join(self.alita_dir, 'sessions')
238
+
239
+ def is_configured(self) -> bool:
240
+ """Check if all required configuration is present."""
241
+ return all([
242
+ self.deployment_url,
243
+ self.project_id is not None,
244
+ self.api_key
245
+ ])
246
+
247
+ def get_missing_config(self) -> list[str]:
248
+ """Get list of missing configuration items."""
249
+ missing = []
250
+ if not self.deployment_url:
251
+ missing.append('DEPLOYMENT_URL')
252
+ if self.project_id is None:
253
+ missing.append('PROJECT_ID')
254
+ if not self.api_key:
255
+ missing.append('API_KEY')
256
+ return missing
257
+
258
+ def to_dict(self) -> Dict[str, Any]:
259
+ """Convert configuration to dictionary."""
260
+ return {
261
+ 'deployment_url': self.deployment_url,
262
+ 'project_id': self.project_id,
263
+ 'api_key': '***' if self.api_key else None # Masked for security
264
+ }
265
+
266
+
267
+ def get_config(env_file: Optional[str] = None) -> CLIConfig:
268
+ """
269
+ Get CLI configuration instance.
270
+
271
+ Args:
272
+ env_file: Optional path to .env file
273
+
274
+ Returns:
275
+ CLIConfig instance
276
+ """
277
+ return CLIConfig(env_file=env_file)
278
+
279
+
280
+ def substitute_env_vars(text: str) -> str:
281
+ """
282
+ Substitute environment variables in text.
283
+
284
+ Supports both ${VAR} and $VAR syntax.
285
+
286
+ Args:
287
+ text: Text containing environment variable references
288
+
289
+ Returns:
290
+ Text with environment variables substituted
291
+ """
292
+ # Replace ${VAR} syntax
293
+ def replace_braced(match):
294
+ var_name = match.group(1)
295
+ return os.getenv(var_name, match.group(0))
296
+
297
+ text = re.sub(r'\$\{([^}]+)\}', replace_braced, text)
298
+
299
+ # Replace $VAR syntax (word boundaries)
300
+ def replace_simple(match):
301
+ var_name = match.group(1)
302
+ return os.getenv(var_name, match.group(0))
303
+
304
+ text = re.sub(r'\$([A-Za-z_][A-Za-z0-9_]*)', replace_simple, text)
305
+
306
+ return text
@@ -0,0 +1,30 @@
1
+ """
2
+ Context management for CLI chat history.
3
+
4
+ Provides token-aware context pruning, summarization, and session management
5
+ to optimize LLM context usage during CLI conversations.
6
+ """
7
+
8
+ from .manager import CLIContextManager, sanitize_message_history
9
+ from .message import CLIMessage
10
+ from .token_estimation import estimate_tokens, calculate_total_tokens
11
+ from .strategies import (
12
+ PruningStrategy,
13
+ OldestFirstStrategy,
14
+ ImportanceBasedStrategy,
15
+ PruningStrategyFactory,
16
+ )
17
+ from .cleanup import purge_old_sessions
18
+
19
+ __all__ = [
20
+ 'CLIContextManager',
21
+ 'CLIMessage',
22
+ 'estimate_tokens',
23
+ 'calculate_total_tokens',
24
+ 'PruningStrategy',
25
+ 'OldestFirstStrategy',
26
+ 'ImportanceBasedStrategy',
27
+ 'PruningStrategyFactory',
28
+ 'purge_old_sessions',
29
+ 'sanitize_message_history',
30
+ ]
@@ -0,0 +1,198 @@
1
+ """
2
+ Session cleanup utilities for CLI context management.
3
+
4
+ Handles purging old sessions to prevent disk space issues.
5
+ """
6
+
7
+ import os
8
+ import shutil
9
+ import logging
10
+ from datetime import datetime, timezone
11
+ from pathlib import Path
12
+ from typing import List, Optional, Tuple
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ def get_sessions_dir(alita_dir: str = '.alita') -> Path:
18
+ """
19
+ Get the sessions directory path.
20
+
21
+ Args:
22
+ alita_dir: Base Alita directory
23
+
24
+ Returns:
25
+ Path to sessions directory
26
+ """
27
+ # Expand ~ for home directory
28
+ if alita_dir.startswith('~'):
29
+ alita_dir = os.path.expanduser(alita_dir)
30
+ return Path(alita_dir) / 'sessions'
31
+
32
+
33
+ def list_sessions_with_age(alita_dir: str = '.alita') -> List[Tuple[str, float, datetime]]:
34
+ """
35
+ List all sessions with their age in days.
36
+
37
+ Args:
38
+ alita_dir: Base Alita directory
39
+
40
+ Returns:
41
+ List of tuples: (session_id, age_days, modified_time)
42
+ """
43
+ sessions_dir = get_sessions_dir(alita_dir)
44
+
45
+ if not sessions_dir.exists():
46
+ return []
47
+
48
+ sessions = []
49
+ now = datetime.now(timezone.utc)
50
+
51
+ for session_path in sessions_dir.iterdir():
52
+ if not session_path.is_dir():
53
+ continue
54
+
55
+ session_id = session_path.name
56
+
57
+ # Get modification time (most recent file in session)
58
+ try:
59
+ mtime = max(
60
+ f.stat().st_mtime
61
+ for f in session_path.rglob('*')
62
+ if f.is_file()
63
+ )
64
+ modified = datetime.fromtimestamp(mtime, tz=timezone.utc)
65
+ age_days = (now - modified).total_seconds() / 86400
66
+ sessions.append((session_id, age_days, modified))
67
+ except (ValueError, OSError):
68
+ # No files or error accessing - use directory mtime
69
+ try:
70
+ mtime = session_path.stat().st_mtime
71
+ modified = datetime.fromtimestamp(mtime, tz=timezone.utc)
72
+ age_days = (now - modified).total_seconds() / 86400
73
+ sessions.append((session_id, age_days, modified))
74
+ except OSError:
75
+ continue
76
+
77
+ # Sort by modification time (oldest first)
78
+ sessions.sort(key=lambda x: x[2])
79
+
80
+ return sessions
81
+
82
+
83
+ def purge_old_sessions(
84
+ max_age_days: int = 30,
85
+ max_sessions: int = 50,
86
+ alita_dir: str = '.alita',
87
+ dry_run: bool = False
88
+ ) -> Tuple[int, int]:
89
+ """
90
+ Purge old sessions based on age and count limits.
91
+
92
+ Args:
93
+ max_age_days: Maximum age in days before a session is purged
94
+ max_sessions: Maximum number of sessions to keep
95
+ alita_dir: Base Alita directory
96
+ dry_run: If True, only report what would be deleted
97
+
98
+ Returns:
99
+ Tuple of (sessions_deleted, bytes_freed)
100
+ """
101
+ sessions_dir = get_sessions_dir(alita_dir)
102
+
103
+ if not sessions_dir.exists():
104
+ return 0, 0
105
+
106
+ sessions = list_sessions_with_age(alita_dir)
107
+
108
+ if not sessions:
109
+ return 0, 0
110
+
111
+ to_delete = []
112
+
113
+ # Mark sessions older than max_age_days for deletion
114
+ for session_id, age_days, modified in sessions:
115
+ if age_days > max_age_days:
116
+ to_delete.append(session_id)
117
+
118
+ # If we still have too many sessions, delete oldest ones
119
+ remaining = [s for s in sessions if s[0] not in to_delete]
120
+ if len(remaining) > max_sessions:
121
+ # Sort remaining by age (oldest first) and mark excess for deletion
122
+ excess_count = len(remaining) - max_sessions
123
+ for session_id, age_days, modified in remaining[:excess_count]:
124
+ if session_id not in to_delete:
125
+ to_delete.append(session_id)
126
+
127
+ # Delete marked sessions
128
+ deleted_count = 0
129
+ bytes_freed = 0
130
+
131
+ for session_id in to_delete:
132
+ session_path = sessions_dir / session_id
133
+
134
+ if not session_path.exists():
135
+ continue
136
+
137
+ # Calculate size before deletion
138
+ try:
139
+ session_size = sum(
140
+ f.stat().st_size
141
+ for f in session_path.rglob('*')
142
+ if f.is_file()
143
+ )
144
+ except OSError:
145
+ session_size = 0
146
+
147
+ if dry_run:
148
+ logger.info(f"Would delete session: {session_id} ({session_size} bytes)")
149
+ else:
150
+ try:
151
+ shutil.rmtree(session_path)
152
+ deleted_count += 1
153
+ bytes_freed += session_size
154
+ logger.debug(f"Deleted session: {session_id}")
155
+ except OSError as e:
156
+ logger.warning(f"Failed to delete session {session_id}: {e}")
157
+
158
+ if deleted_count > 0:
159
+ logger.info(f"Purged {deleted_count} old sessions, freed {bytes_freed} bytes")
160
+
161
+ return deleted_count, bytes_freed
162
+
163
+
164
+ def get_session_disk_usage(alita_dir: str = '.alita') -> Tuple[int, int]:
165
+ """
166
+ Get disk usage statistics for sessions.
167
+
168
+ Args:
169
+ alita_dir: Base Alita directory
170
+
171
+ Returns:
172
+ Tuple of (session_count, total_bytes)
173
+ """
174
+ sessions_dir = get_sessions_dir(alita_dir)
175
+
176
+ if not sessions_dir.exists():
177
+ return 0, 0
178
+
179
+ session_count = 0
180
+ total_bytes = 0
181
+
182
+ for session_path in sessions_dir.iterdir():
183
+ if not session_path.is_dir():
184
+ continue
185
+
186
+ session_count += 1
187
+
188
+ try:
189
+ session_size = sum(
190
+ f.stat().st_size
191
+ for f in session_path.rglob('*')
192
+ if f.is_file()
193
+ )
194
+ total_bytes += session_size
195
+ except OSError:
196
+ continue
197
+
198
+ return session_count, total_bytes