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,182 @@
1
+ """
2
+ Output formatting utilities for Alita CLI.
3
+
4
+ Provides text and JSON formatters for displaying toolkit test results,
5
+ agent responses, and other CLI outputs.
6
+ """
7
+
8
+ import json
9
+ from typing import Any, Dict, List, Optional
10
+ from datetime import datetime
11
+
12
+
13
+ class OutputFormatter:
14
+ """Base class for output formatters."""
15
+
16
+ def format_toolkit_result(self, result: Dict[str, Any]) -> str:
17
+ """Format toolkit test result."""
18
+ raise NotImplementedError
19
+
20
+ def format_error(self, error: str) -> str:
21
+ """Format error message."""
22
+ raise NotImplementedError
23
+
24
+ def format_toolkit_list(self, toolkits: List[Dict[str, Any]]) -> str:
25
+ """Format list of available toolkits."""
26
+ raise NotImplementedError
27
+
28
+
29
+ class TextFormatter(OutputFormatter):
30
+ """Human-readable text formatter."""
31
+
32
+ def format_toolkit_result(self, result: Dict[str, Any]) -> str:
33
+ """Format toolkit test result as text."""
34
+ if not result.get('success', False):
35
+ return self.format_error(result.get('error', 'Unknown error'))
36
+
37
+ lines = [
38
+ "\n✓ Tool executed successfully\n",
39
+ f"Tool: {result.get('tool_name', 'unknown')}",
40
+ f"Toolkit: {result.get('toolkit_config', {}).get('type', 'unknown')}",
41
+ f"LLM Model: {result.get('llm_model', 'N/A')}",
42
+ f"Execution time: {result.get('execution_time_seconds', 0):.3f}s",
43
+ "",
44
+ "Result:",
45
+ ]
46
+
47
+ # Format result based on type
48
+ tool_result = result.get('result')
49
+ if isinstance(tool_result, str):
50
+ lines.append(f" {tool_result}")
51
+ elif isinstance(tool_result, dict):
52
+ for key, value in tool_result.items():
53
+ lines.append(f" {key}: {value}")
54
+ else:
55
+ lines.append(f" {str(tool_result)}")
56
+
57
+ # Add events if present
58
+ events = result.get('events_dispatched', [])
59
+ if events:
60
+ lines.extend([
61
+ "",
62
+ f"Events dispatched: {len(events)}"
63
+ ])
64
+ for event in events[:5]: # Limit to first 5 events
65
+ event_data = event.get('data', {})
66
+ message = event_data.get('message', str(event_data))
67
+ lines.append(f" - {event.get('name', 'event')}: {message}")
68
+
69
+ if len(events) > 5:
70
+ lines.append(f" ... and {len(events) - 5} more events")
71
+
72
+ return "\n".join(lines)
73
+
74
+ def format_error(self, error: str) -> str:
75
+ """Format error message as text."""
76
+ return f"\n✗ Error: {error}\n"
77
+
78
+ def format_toolkit_list(self, toolkits: List[Dict[str, Any]]) -> str:
79
+ """Format list of available toolkits as text."""
80
+ lines = ["\nAvailable toolkits:\n"]
81
+
82
+ for toolkit in sorted(toolkits, key=lambda x: x.get('name', '')):
83
+ name = toolkit.get('name', 'unknown')
84
+ class_name = toolkit.get('class_name', '')
85
+ lines.append(f" - {name}" + (f" ({class_name})" if class_name else ""))
86
+
87
+ lines.append(f"\nTotal: {len(toolkits)} toolkits")
88
+ return "\n".join(lines)
89
+
90
+ def format_toolkit_schema(self, toolkit_name: str, schema: Dict[str, Any]) -> str:
91
+ """Format toolkit schema as text."""
92
+ lines = [
93
+ f"\n{toolkit_name.title()} Toolkit Configuration Schema:\n",
94
+ ]
95
+
96
+ properties = schema.get('properties', {})
97
+ required = schema.get('required', [])
98
+
99
+ for field_name, field_schema in properties.items():
100
+ field_type = field_schema.get('type', 'any')
101
+ description = field_schema.get('description', '')
102
+ is_required = field_name in required
103
+ default = field_schema.get('default')
104
+
105
+ req_text = "required" if is_required else "optional"
106
+ lines.append(f" - {field_name} ({req_text}): {description}")
107
+ lines.append(f" Type: {field_type}")
108
+
109
+ if default is not None:
110
+ lines.append(f" Default: {default}")
111
+
112
+ # Show enum values if present
113
+ if 'enum' in field_schema:
114
+ lines.append(f" Options: {', '.join(map(str, field_schema['enum']))}")
115
+
116
+ # Handle nested objects
117
+ if field_type == 'object' and 'properties' in field_schema:
118
+ lines.append(f" Fields:")
119
+ for nested_name, nested_schema in field_schema['properties'].items():
120
+ nested_desc = nested_schema.get('description', '')
121
+ lines.append(f" - {nested_name}: {nested_desc}")
122
+
123
+ lines.append("")
124
+
125
+ return "\n".join(lines)
126
+
127
+
128
+ class JSONFormatter(OutputFormatter):
129
+ """JSON formatter for scripting and automation."""
130
+
131
+ def __init__(self, pretty: bool = True):
132
+ """
133
+ Initialize JSON formatter.
134
+
135
+ Args:
136
+ pretty: If True, format JSON with indentation
137
+ """
138
+ self.pretty = pretty
139
+
140
+ def _dump(self, data: Any) -> str:
141
+ """Dump data as JSON."""
142
+ if self.pretty:
143
+ return json.dumps(data, indent=2, default=str)
144
+ return json.dumps(data, default=str)
145
+
146
+ def format_toolkit_result(self, result: Dict[str, Any]) -> str:
147
+ """Format toolkit test result as JSON."""
148
+ return self._dump(result)
149
+
150
+ def format_error(self, error: str) -> str:
151
+ """Format error message as JSON."""
152
+ return self._dump({'success': False, 'error': error})
153
+
154
+ def format_toolkit_list(self, toolkits: List[Dict[str, Any]]) -> str:
155
+ """Format list of available toolkits as JSON."""
156
+ return self._dump({
157
+ 'toolkits': toolkits,
158
+ 'total': len(toolkits)
159
+ })
160
+
161
+ def format_toolkit_schema(self, toolkit_name: str, schema: Dict[str, Any]) -> str:
162
+ """Format toolkit schema as JSON."""
163
+ return self._dump({
164
+ 'toolkit': toolkit_name,
165
+ 'schema': schema
166
+ })
167
+
168
+
169
+ def get_formatter(output_format: str = 'text', pretty: bool = True) -> OutputFormatter:
170
+ """
171
+ Get output formatter by name.
172
+
173
+ Args:
174
+ output_format: Format type ('text' or 'json')
175
+ pretty: For JSON formatter, whether to pretty-print
176
+
177
+ Returns:
178
+ OutputFormatter instance
179
+ """
180
+ if output_format == 'json':
181
+ return JSONFormatter(pretty=pretty)
182
+ return TextFormatter()
@@ -0,0 +1,419 @@
1
+ """
2
+ Enhanced input handler with readline support.
3
+
4
+ Provides tab completion for commands, cursor movement, and input history.
5
+ """
6
+
7
+ import readline
8
+ import os
9
+ from pathlib import Path
10
+ from typing import Dict, List, Optional, Any, Callable
11
+
12
+ from rich.console import Console
13
+ from rich.text import Text
14
+
15
+ console = Console()
16
+
17
+ # Available commands for autocompletion
18
+ CHAT_COMMANDS = [
19
+ '/help',
20
+ '/clear',
21
+ '/history',
22
+ '/save',
23
+ '/agent',
24
+ '/model',
25
+ '/mode',
26
+ '/mode always',
27
+ '/mode auto',
28
+ '/mode yolo',
29
+ '/dir',
30
+ '/dir add',
31
+ '/dir rm',
32
+ '/dir remove',
33
+ '/inventory',
34
+ '/session',
35
+ '/session list',
36
+ '/session resume',
37
+ '/add_mcp',
38
+ '/add_toolkit',
39
+ '/rm_mcp',
40
+ '/rm_toolkit',
41
+ '/reload',
42
+ 'exit',
43
+ 'quit',
44
+ ]
45
+
46
+
47
+ # Callback to get dynamic toolkit names for completion
48
+ _toolkit_names_callback: Optional[Callable[[], List[str]]] = None
49
+
50
+ # Callback to get inventory .json files for completion
51
+ _inventory_files_callback: Optional[Callable[[], List[str]]] = None
52
+
53
+
54
+ def set_toolkit_names_callback(callback: Callable[[], List[str]]):
55
+ """
56
+ Set a callback function that returns available toolkit names.
57
+
58
+ This allows the input handler to provide dynamic completions
59
+ for /add_toolkit without having a direct dependency on config.
60
+ """
61
+ global _toolkit_names_callback
62
+ _toolkit_names_callback = callback
63
+
64
+
65
+ def set_inventory_files_callback(callback: Callable[[], List[str]]):
66
+ """
67
+ Set a callback function that returns available inventory .json files.
68
+
69
+ This allows the input handler to provide dynamic completions
70
+ for /inventory without having a direct dependency on agents.py.
71
+ """
72
+ global _inventory_files_callback
73
+ _inventory_files_callback = callback
74
+
75
+
76
+ def get_toolkit_names_for_completion() -> List[str]:
77
+ """Get toolkit names for tab completion."""
78
+ global _toolkit_names_callback
79
+ if _toolkit_names_callback:
80
+ try:
81
+ return _toolkit_names_callback()
82
+ except Exception:
83
+ return []
84
+ return []
85
+
86
+
87
+ def get_inventory_files_for_completion() -> List[str]:
88
+ """Get inventory .json files for tab completion."""
89
+ global _inventory_files_callback
90
+ if _inventory_files_callback:
91
+ try:
92
+ return _inventory_files_callback()
93
+ except Exception:
94
+ return []
95
+ return []
96
+
97
+
98
+ class ChatInputHandler:
99
+ """
100
+ Enhanced input handler with readline support for chat sessions.
101
+
102
+ Features:
103
+ - Tab completion for slash commands
104
+ - Arrow key navigation through input history
105
+ - Cursor movement with left/right arrows
106
+ - Ctrl+A (start of line), Ctrl+E (end of line)
107
+ - Persistent command history within session
108
+ - Material UI-style input prompt
109
+ """
110
+
111
+ def __init__(self):
112
+ self._setup_readline()
113
+ self._input_history: List[str] = []
114
+
115
+ def _setup_readline(self):
116
+ """Configure readline for enhanced input."""
117
+ # Set up tab completion
118
+ readline.set_completer(self._completer)
119
+
120
+ # Detect if we're using libedit (macOS) or GNU readline (Linux)
121
+ # libedit uses different syntax for parse_and_bind
122
+ if 'libedit' in readline.__doc__:
123
+ # macOS libedit syntax
124
+ readline.parse_and_bind('bind ^I rl_complete')
125
+ else:
126
+ # GNU readline syntax
127
+ readline.parse_and_bind('tab: complete')
128
+
129
+ # Enable emacs-style keybindings (Ctrl+A, Ctrl+E, etc.)
130
+ # This is usually the default on macOS/Linux
131
+ try:
132
+ if 'libedit' not in readline.__doc__:
133
+ readline.parse_and_bind('set editing-mode emacs')
134
+ except Exception:
135
+ pass # Some systems might not support this
136
+
137
+ # Set completion display style (GNU readline only)
138
+ try:
139
+ if 'libedit' not in readline.__doc__:
140
+ readline.parse_and_bind('set show-all-if-ambiguous on')
141
+ readline.parse_and_bind('set completion-ignore-case on')
142
+ except Exception:
143
+ pass
144
+
145
+ # Set delimiters for completion (space and common punctuation)
146
+ readline.set_completer_delims(' \t\n;')
147
+
148
+ def _completer(self, text: str, state: int) -> Optional[str]:
149
+ """
150
+ Readline completer function for slash commands.
151
+
152
+ Args:
153
+ text: The current text being completed
154
+ state: The state of completion (0 for first match, 1 for second, etc.)
155
+
156
+ Returns:
157
+ The next matching command or None
158
+ """
159
+ # Get the full line buffer
160
+ line = readline.get_line_buffer()
161
+
162
+ # Only complete at the start of the line or after whitespace
163
+ if line and not line.startswith('/') and text != line:
164
+ return None
165
+
166
+ matches = []
167
+
168
+ # Handle /add_toolkit <name> completion
169
+ if line.startswith('/add_toolkit '):
170
+ # Get partial toolkit name being typed
171
+ toolkit_prefix = text.lower()
172
+ toolkit_names = get_toolkit_names_for_completion()
173
+ matches = [f'/add_toolkit {name}' for name in toolkit_names
174
+ if name.lower().startswith(toolkit_prefix) or toolkit_prefix == '']
175
+ # Also match just the toolkit name if text doesn't start with /
176
+ if not text.startswith('/'):
177
+ matches = [name for name in toolkit_names if name.lower().startswith(toolkit_prefix)]
178
+ # Handle /inventory <path> completion
179
+ elif line.startswith('/inventory '):
180
+ # Get partial path being typed
181
+ path_prefix = text.lower()
182
+ inventory_files = get_inventory_files_for_completion()
183
+ matches = [f'/inventory {path}' for path in inventory_files
184
+ if path.lower().startswith(path_prefix) or path_prefix == '']
185
+ # Also match just the path if text doesn't start with /
186
+ if not text.startswith('/'):
187
+ matches = [path for path in inventory_files if path.lower().startswith(path_prefix)]
188
+ # Find matching commands
189
+ elif text.startswith('/'):
190
+ matches = [cmd for cmd in CHAT_COMMANDS if cmd.startswith(text)]
191
+ elif text == '' and line == '':
192
+ # Show all commands on empty tab
193
+ matches = [cmd for cmd in CHAT_COMMANDS if cmd.startswith('/')]
194
+ else:
195
+ matches = [cmd for cmd in CHAT_COMMANDS if cmd.startswith(text)]
196
+
197
+ # Return the match at the given state
198
+ if state < len(matches):
199
+ return matches[state]
200
+ return None
201
+
202
+ def get_input(self, prompt: str = "") -> str:
203
+ """
204
+ Get user input with enhanced readline features.
205
+
206
+ Args:
207
+ prompt: The prompt to display (note: for rich console, prompt is printed separately)
208
+
209
+ Returns:
210
+ The user's input string
211
+ """
212
+ try:
213
+ user_input = input(prompt)
214
+
215
+ # Add non-empty, non-duplicate inputs to history
216
+ if user_input.strip() and (not self._input_history or
217
+ self._input_history[-1] != user_input):
218
+ self._input_history.append(user_input)
219
+ readline.add_history(user_input)
220
+
221
+ return user_input
222
+ except (KeyboardInterrupt, EOFError):
223
+ raise
224
+
225
+ def clear_history(self):
226
+ """Clear the input history."""
227
+ self._input_history.clear()
228
+ readline.clear_history()
229
+
230
+ @property
231
+ def history(self) -> List[str]:
232
+ """Get the current input history."""
233
+ return self._input_history.copy()
234
+
235
+
236
+ # Global instance for use across the CLI
237
+ _input_handler: Optional[ChatInputHandler] = None
238
+
239
+
240
+ def get_input_handler() -> ChatInputHandler:
241
+ """Get or create the global input handler instance."""
242
+ global _input_handler
243
+ if _input_handler is None:
244
+ _input_handler = ChatInputHandler()
245
+ return _input_handler
246
+
247
+
248
+ def chat_input(prompt: str = "") -> str:
249
+ """
250
+ Convenience function for getting chat input with enhanced features.
251
+
252
+ Args:
253
+ prompt: The prompt to display
254
+
255
+ Returns:
256
+ The user's input string
257
+ """
258
+ return get_input_handler().get_input(prompt)
259
+
260
+
261
+ def styled_input(context_info: Optional[Dict[str, Any]] = None) -> str:
262
+ """
263
+ Get user input with a styled bordered prompt that works correctly with readline.
264
+
265
+ The prompt is passed directly to input() so readline can properly
266
+ handle cursor positioning and history navigation.
267
+
268
+ Args:
269
+ context_info: Optional context info dict with keys:
270
+ - used_tokens: Current tokens in context
271
+ - max_tokens: Maximum allowed tokens
272
+ - fill_ratio: Context fill ratio (0.0-1.0)
273
+ - pruned_count: Number of pruned messages
274
+
275
+ Returns:
276
+ The user's input string
277
+ """
278
+ # Get terminal width for the border
279
+ try:
280
+ width = console.width - 2
281
+ except Exception:
282
+ width = 78
283
+
284
+ # Build context indicator if provided
285
+ context_indicator = ""
286
+ if context_info and context_info.get('max_tokens', 0) > 0:
287
+ context_indicator = _format_context_indicator(
288
+ context_info.get('used_tokens', 0),
289
+ context_info.get('max_tokens', 8000),
290
+ context_info.get('fill_ratio', 0.0),
291
+ context_info.get('pruned_count', 0),
292
+ )
293
+
294
+ # Print the complete box frame first, then move cursor up to input line
295
+ console.print()
296
+ console.print(f"[dim]╭{'─' * width}╮[/dim]")
297
+ console.print(f"[dim]│[/dim]{' ' * width}[dim]│[/dim]")
298
+
299
+ # Bottom border with context indicator on the right
300
+ if context_indicator:
301
+ indicator_len = len(_strip_ansi(context_indicator))
302
+ padding = width - indicator_len - 1
303
+ console.print(f"[dim]╰{'─' * padding}[/dim]{context_indicator}[dim]─╯[/dim]")
304
+ else:
305
+ console.print(f"[dim]╰{'─' * width}╯[/dim]")
306
+
307
+ # Move cursor up 2 lines and to position after "│ > "
308
+ # \033[2A = move up 2 lines, \033[4C = move right 4 columns
309
+ prompt = "\033[2A\033[2C > "
310
+
311
+ user_input = get_input_handler().get_input(prompt)
312
+
313
+ # Move cursor down to after the box
314
+ console.print()
315
+
316
+ return user_input
317
+
318
+
319
+ def _format_context_indicator(
320
+ used_tokens: int,
321
+ max_tokens: int,
322
+ fill_ratio: float,
323
+ pruned_count: int = 0
324
+ ) -> str:
325
+ """
326
+ Format context usage indicator for display.
327
+
328
+ Shows: [1234/8000 ██████░░░░ 15%]
329
+ Color coded: green <70%, yellow 70-90%, red >90%
330
+
331
+ Args:
332
+ used_tokens: Current tokens in context
333
+ max_tokens: Maximum allowed tokens
334
+ fill_ratio: Context fill ratio (0.0-1.0)
335
+ pruned_count: Number of pruned messages
336
+
337
+ Returns:
338
+ Formatted indicator string with ANSI colors
339
+ """
340
+ # Determine color based on fill ratio
341
+ if fill_ratio < 0.7:
342
+ color = "green"
343
+ elif fill_ratio < 0.9:
344
+ color = "yellow"
345
+ else:
346
+ color = "red"
347
+
348
+ # Build progress bar (10 chars)
349
+ bar_width = 10
350
+ filled = int(fill_ratio * bar_width)
351
+ empty = bar_width - filled
352
+ bar = "█" * filled + "░" * empty
353
+
354
+ # Format percentage
355
+ percent = int(fill_ratio * 100)
356
+
357
+ # Build indicator
358
+ indicator = f"[{color}]{used_tokens}/{max_tokens} {bar} {percent}%[/{color}]"
359
+
360
+ # Add pruned count if any
361
+ if pruned_count > 0:
362
+ indicator += f" [dim]({pruned_count} pruned)[/dim]"
363
+
364
+ return indicator
365
+
366
+
367
+ def _strip_ansi(text: str) -> str:
368
+ """Strip ANSI escape codes and Rich markup from text for length calculation."""
369
+ import re
370
+ # Remove Rich markup tags like [green], [/green], [dim], etc.
371
+ text = re.sub(r'\[/?[^\]]+\]', '', text)
372
+ # Remove ANSI escape codes
373
+ text = re.sub(r'\x1b\[[0-9;]*m', '', text)
374
+ return text
375
+
376
+
377
+ def styled_selection_input(prompt_text: str = "Select") -> str:
378
+ """
379
+ Get user selection input with a styled bordered prompt.
380
+
381
+ Args:
382
+ prompt_text: The prompt text to show (e.g., "Select model number")
383
+
384
+ Returns:
385
+ The user's input string
386
+ """
387
+ # Get terminal width for the border
388
+ try:
389
+ width = console.width - 2
390
+ except Exception:
391
+ width = 78
392
+
393
+ # Print the complete box frame first, then move cursor up to input line
394
+ console.print()
395
+ console.print(f"[dim]╭{'─' * width}╮[/dim]")
396
+ console.print(f"[dim]│[/dim]{' ' * width}[dim]│[/dim]")
397
+ console.print(f"[dim]╰{'─' * width}╯[/dim]")
398
+
399
+ # Move cursor up 2 lines and to position after "│"
400
+ # \033[2A = move up 2 lines, \033[2C = move right 2 columns
401
+ prompt = f"\033[2A\033[2C {prompt_text}: "
402
+
403
+ try:
404
+ user_input = input(prompt)
405
+ except (KeyboardInterrupt, EOFError):
406
+ console.print()
407
+ raise
408
+
409
+ # Move cursor down to after the box
410
+ console.print()
411
+
412
+ return user_input.strip()
413
+
414
+
415
+ def print_input_prompt():
416
+ """Print a clean, modern input prompt."""
417
+ # Simple clean prompt with > indicator
418
+ console.print() # Empty line for spacing
419
+ console.print("[bold cyan]>[/bold cyan] ", end="")