alita-sdk 0.3.365__py3-none-any.whl → 0.3.462__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 (118) hide show
  1. alita_sdk/cli/__init__.py +10 -0
  2. alita_sdk/cli/__main__.py +17 -0
  3. alita_sdk/cli/agent_executor.py +144 -0
  4. alita_sdk/cli/agent_loader.py +197 -0
  5. alita_sdk/cli/agent_ui.py +166 -0
  6. alita_sdk/cli/agents.py +1069 -0
  7. alita_sdk/cli/callbacks.py +576 -0
  8. alita_sdk/cli/cli.py +159 -0
  9. alita_sdk/cli/config.py +153 -0
  10. alita_sdk/cli/formatting.py +182 -0
  11. alita_sdk/cli/mcp_loader.py +315 -0
  12. alita_sdk/cli/toolkit.py +330 -0
  13. alita_sdk/cli/toolkit_loader.py +55 -0
  14. alita_sdk/cli/tools/__init__.py +9 -0
  15. alita_sdk/cli/tools/filesystem.py +905 -0
  16. alita_sdk/configurations/bitbucket.py +95 -0
  17. alita_sdk/configurations/confluence.py +96 -1
  18. alita_sdk/configurations/gitlab.py +79 -0
  19. alita_sdk/configurations/jira.py +103 -0
  20. alita_sdk/configurations/testrail.py +88 -0
  21. alita_sdk/configurations/xray.py +93 -0
  22. alita_sdk/configurations/zephyr_enterprise.py +93 -0
  23. alita_sdk/configurations/zephyr_essential.py +75 -0
  24. alita_sdk/runtime/clients/artifact.py +1 -1
  25. alita_sdk/runtime/clients/client.py +47 -10
  26. alita_sdk/runtime/clients/mcp_discovery.py +342 -0
  27. alita_sdk/runtime/clients/mcp_manager.py +262 -0
  28. alita_sdk/runtime/clients/sandbox_client.py +373 -0
  29. alita_sdk/runtime/langchain/assistant.py +70 -41
  30. alita_sdk/runtime/langchain/constants.py +6 -1
  31. alita_sdk/runtime/langchain/document_loaders/AlitaDocxMammothLoader.py +315 -3
  32. alita_sdk/runtime/langchain/document_loaders/AlitaJSONLoader.py +4 -1
  33. alita_sdk/runtime/langchain/document_loaders/constants.py +73 -100
  34. alita_sdk/runtime/langchain/langraph_agent.py +164 -38
  35. alita_sdk/runtime/langchain/utils.py +43 -7
  36. alita_sdk/runtime/models/mcp_models.py +61 -0
  37. alita_sdk/runtime/toolkits/__init__.py +24 -0
  38. alita_sdk/runtime/toolkits/application.py +8 -1
  39. alita_sdk/runtime/toolkits/artifact.py +5 -6
  40. alita_sdk/runtime/toolkits/mcp.py +895 -0
  41. alita_sdk/runtime/toolkits/tools.py +140 -50
  42. alita_sdk/runtime/tools/__init__.py +7 -2
  43. alita_sdk/runtime/tools/application.py +7 -0
  44. alita_sdk/runtime/tools/function.py +94 -5
  45. alita_sdk/runtime/tools/graph.py +10 -4
  46. alita_sdk/runtime/tools/image_generation.py +104 -8
  47. alita_sdk/runtime/tools/llm.py +204 -114
  48. alita_sdk/runtime/tools/mcp_inspect_tool.py +284 -0
  49. alita_sdk/runtime/tools/mcp_remote_tool.py +166 -0
  50. alita_sdk/runtime/tools/mcp_server_tool.py +3 -1
  51. alita_sdk/runtime/tools/sandbox.py +180 -79
  52. alita_sdk/runtime/tools/vectorstore.py +22 -21
  53. alita_sdk/runtime/tools/vectorstore_base.py +79 -26
  54. alita_sdk/runtime/utils/mcp_oauth.py +164 -0
  55. alita_sdk/runtime/utils/mcp_sse_client.py +405 -0
  56. alita_sdk/runtime/utils/streamlit.py +34 -3
  57. alita_sdk/runtime/utils/toolkit_utils.py +14 -4
  58. alita_sdk/runtime/utils/utils.py +1 -0
  59. alita_sdk/tools/__init__.py +48 -31
  60. alita_sdk/tools/ado/repos/__init__.py +1 -0
  61. alita_sdk/tools/ado/test_plan/__init__.py +1 -1
  62. alita_sdk/tools/ado/wiki/__init__.py +1 -5
  63. alita_sdk/tools/ado/work_item/__init__.py +1 -5
  64. alita_sdk/tools/ado/work_item/ado_wrapper.py +17 -8
  65. alita_sdk/tools/base_indexer_toolkit.py +194 -112
  66. alita_sdk/tools/bitbucket/__init__.py +1 -0
  67. alita_sdk/tools/chunkers/sematic/proposal_chunker.py +1 -1
  68. alita_sdk/tools/code/sonar/__init__.py +1 -1
  69. alita_sdk/tools/code_indexer_toolkit.py +15 -5
  70. alita_sdk/tools/confluence/__init__.py +2 -2
  71. alita_sdk/tools/confluence/api_wrapper.py +110 -63
  72. alita_sdk/tools/confluence/loader.py +10 -0
  73. alita_sdk/tools/elitea_base.py +22 -22
  74. alita_sdk/tools/github/__init__.py +2 -2
  75. alita_sdk/tools/gitlab/__init__.py +2 -1
  76. alita_sdk/tools/gitlab/api_wrapper.py +11 -7
  77. alita_sdk/tools/gitlab_org/__init__.py +1 -2
  78. alita_sdk/tools/google_places/__init__.py +2 -1
  79. alita_sdk/tools/jira/__init__.py +1 -0
  80. alita_sdk/tools/jira/api_wrapper.py +1 -1
  81. alita_sdk/tools/memory/__init__.py +1 -1
  82. alita_sdk/tools/non_code_indexer_toolkit.py +2 -2
  83. alita_sdk/tools/openapi/__init__.py +10 -1
  84. alita_sdk/tools/pandas/__init__.py +1 -1
  85. alita_sdk/tools/postman/__init__.py +2 -1
  86. alita_sdk/tools/postman/api_wrapper.py +18 -8
  87. alita_sdk/tools/postman/postman_analysis.py +8 -1
  88. alita_sdk/tools/pptx/__init__.py +2 -2
  89. alita_sdk/tools/qtest/__init__.py +3 -3
  90. alita_sdk/tools/qtest/api_wrapper.py +1708 -76
  91. alita_sdk/tools/rally/__init__.py +1 -2
  92. alita_sdk/tools/report_portal/__init__.py +1 -0
  93. alita_sdk/tools/salesforce/__init__.py +1 -0
  94. alita_sdk/tools/servicenow/__init__.py +2 -3
  95. alita_sdk/tools/sharepoint/__init__.py +1 -0
  96. alita_sdk/tools/sharepoint/api_wrapper.py +125 -34
  97. alita_sdk/tools/sharepoint/authorization_helper.py +191 -1
  98. alita_sdk/tools/sharepoint/utils.py +8 -2
  99. alita_sdk/tools/slack/__init__.py +1 -0
  100. alita_sdk/tools/sql/__init__.py +2 -1
  101. alita_sdk/tools/sql/api_wrapper.py +71 -23
  102. alita_sdk/tools/testio/__init__.py +1 -0
  103. alita_sdk/tools/testrail/__init__.py +1 -3
  104. alita_sdk/tools/utils/__init__.py +17 -0
  105. alita_sdk/tools/utils/content_parser.py +35 -24
  106. alita_sdk/tools/vector_adapters/VectorStoreAdapter.py +67 -21
  107. alita_sdk/tools/xray/__init__.py +2 -1
  108. alita_sdk/tools/zephyr/__init__.py +2 -1
  109. alita_sdk/tools/zephyr_enterprise/__init__.py +1 -0
  110. alita_sdk/tools/zephyr_essential/__init__.py +1 -0
  111. alita_sdk/tools/zephyr_scale/__init__.py +1 -0
  112. alita_sdk/tools/zephyr_squad/__init__.py +1 -0
  113. {alita_sdk-0.3.365.dist-info → alita_sdk-0.3.462.dist-info}/METADATA +8 -2
  114. {alita_sdk-0.3.365.dist-info → alita_sdk-0.3.462.dist-info}/RECORD +118 -93
  115. alita_sdk-0.3.462.dist-info/entry_points.txt +2 -0
  116. {alita_sdk-0.3.365.dist-info → alita_sdk-0.3.462.dist-info}/WHEEL +0 -0
  117. {alita_sdk-0.3.365.dist-info → alita_sdk-0.3.462.dist-info}/licenses/LICENSE +0 -0
  118. {alita_sdk-0.3.365.dist-info → alita_sdk-0.3.462.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,1069 @@
1
+ """
2
+ Agent commands for Alita CLI.
3
+
4
+ Provides commands to work with agents interactively or in handoff mode,
5
+ supporting both platform agents and local agent definition files.
6
+ """
7
+
8
+ import asyncio
9
+ import click
10
+ import json
11
+ import logging
12
+ import sqlite3
13
+ import sys
14
+ from typing import Optional, Dict, Any, List
15
+ from pathlib import Path
16
+ import yaml
17
+
18
+ from rich.console import Console
19
+ from rich.panel import Panel
20
+ from rich.table import Table
21
+ from rich.markdown import Markdown
22
+ from rich import box
23
+ from rich.text import Text
24
+ from rich.status import Status
25
+ from rich.live import Live
26
+
27
+ from .cli import get_client
28
+ # Import from refactored modules
29
+ from .agent_ui import print_welcome, print_help, display_output, extract_output_from_result
30
+ from .agent_loader import load_agent_definition
31
+ from .agent_executor import create_llm_instance, create_agent_executor, create_agent_executor_with_mcp
32
+ from .toolkit_loader import load_toolkit_config, load_toolkit_configs
33
+ from .callbacks import create_cli_callback, CLICallbackHandler
34
+
35
+ logger = logging.getLogger(__name__)
36
+
37
+ # Create a rich console for beautiful output
38
+ console = Console()
39
+
40
+
41
+ def _load_mcp_tools(agent_def: Dict[str, Any], mcp_config_path: str) -> List[Dict[str, Any]]:
42
+ """Load MCP tools from agent definition with tool-level filtering.
43
+
44
+ Args:
45
+ agent_def: Agent definition dictionary containing mcps list
46
+ mcp_config_path: Path to mcp.json configuration file (workspace-level)
47
+
48
+ Returns:
49
+ List of toolkit configurations for MCP servers
50
+ """
51
+ from .mcp_loader import load_mcp_tools
52
+ return load_mcp_tools(agent_def, mcp_config_path)
53
+
54
+
55
+ def _setup_local_agent_executor(client, agent_def: Dict[str, Any], toolkit_config: tuple,
56
+ config, model: Optional[str], temperature: Optional[float],
57
+ max_tokens: Optional[int], memory, work_dir: Optional[str]):
58
+ """Setup local agent executor with all configurations.
59
+
60
+ Returns:
61
+ Tuple of (agent_executor, mcp_session_manager, llm, llm_model, filesystem_tools)
62
+ """
63
+ # Load toolkit configs
64
+ toolkit_configs = load_toolkit_configs(agent_def, toolkit_config)
65
+
66
+ # Load MCP tools
67
+ mcp_toolkit_configs = _load_mcp_tools(agent_def, config.mcp_config_path)
68
+ toolkit_configs.extend(mcp_toolkit_configs)
69
+
70
+ # Create LLM instance
71
+ llm, llm_model, llm_temperature, llm_max_tokens = create_llm_instance(
72
+ client, model, agent_def, temperature, max_tokens
73
+ )
74
+
75
+ # Add filesystem tools if --dir is provided
76
+ filesystem_tools = None
77
+ if work_dir:
78
+ from .tools import get_filesystem_tools
79
+ preset = agent_def.get('filesystem_tools_preset')
80
+ include_tools = agent_def.get('filesystem_tools_include')
81
+ exclude_tools = agent_def.get('filesystem_tools_exclude')
82
+ filesystem_tools = get_filesystem_tools(work_dir, include_tools, exclude_tools, preset)
83
+
84
+ tool_count = len(filesystem_tools)
85
+ access_msg = f"✓ Granted filesystem access to: {work_dir} ({tool_count} tools)"
86
+ if preset:
87
+ access_msg += f" [preset: {preset}]"
88
+ if include_tools:
89
+ access_msg += f" [include: {', '.join(include_tools)}]"
90
+ if exclude_tools:
91
+ access_msg += f" [exclude: {', '.join(exclude_tools)}]"
92
+ console.print(f"[dim]{access_msg}[/dim]")
93
+
94
+ # Check if we have tools
95
+ has_tools = bool(agent_def.get('tools') or toolkit_configs or filesystem_tools)
96
+ has_mcp = any(tc.get('toolkit_type') == 'mcp' for tc in toolkit_configs)
97
+
98
+ if not has_tools:
99
+ return None, None, llm, llm_model, filesystem_tools
100
+
101
+ # Create agent executor with or without MCP
102
+ mcp_session_manager = None
103
+ if has_mcp:
104
+ # Create persistent event loop for MCP tools
105
+ from alita_sdk.runtime.tools.llm import LLMNode
106
+ if not hasattr(LLMNode, '_persistent_loop') or \
107
+ LLMNode._persistent_loop is None or \
108
+ LLMNode._persistent_loop.is_closed():
109
+ LLMNode._persistent_loop = asyncio.new_event_loop()
110
+ console.print("[dim]Created persistent event loop for MCP tools[/dim]")
111
+
112
+ # Load MCP tools using persistent loop
113
+ loop = LLMNode._persistent_loop
114
+ asyncio.set_event_loop(loop)
115
+ agent_executor, mcp_session_manager = loop.run_until_complete(
116
+ create_agent_executor_with_mcp(
117
+ client, agent_def, toolkit_configs,
118
+ llm, llm_model, llm_temperature, llm_max_tokens, memory,
119
+ filesystem_tools=filesystem_tools
120
+ )
121
+ )
122
+ else:
123
+ agent_executor = create_agent_executor(
124
+ client, agent_def, toolkit_configs,
125
+ llm, llm_model, llm_temperature, llm_max_tokens, memory,
126
+ filesystem_tools=filesystem_tools
127
+ )
128
+
129
+ return agent_executor, mcp_session_manager, llm, llm_model, filesystem_tools
130
+
131
+
132
+ def _select_agent_interactive(client, config) -> Optional[str]:
133
+ """
134
+ Show interactive menu to select an agent from platform and local agents.
135
+
136
+ Returns:
137
+ Agent source (name/id for platform, file path for local) or None if cancelled
138
+ """
139
+ from .config import CLIConfig
140
+
141
+ console.print("\n🤖 [bold cyan]Select an agent to chat with:[/bold cyan]\n")
142
+
143
+ agents_list = []
144
+
145
+ # Load platform agents
146
+ try:
147
+ platform_agents = client.get_list_of_apps()
148
+ for agent in platform_agents:
149
+ agents_list.append({
150
+ 'type': 'platform',
151
+ 'name': agent['name'],
152
+ 'source': agent['name'],
153
+ 'description': agent.get('description', '')[:60]
154
+ })
155
+ except Exception as e:
156
+ logger.debug(f"Failed to load platform agents: {e}")
157
+
158
+ # Load local agents
159
+ agents_dir = config.agents_dir
160
+ search_dir = Path(agents_dir)
161
+
162
+ if search_dir.exists():
163
+ for pattern in ['*.agent.md', '*.agent.yaml', '*.agent.yml', '*.agent.json']:
164
+ for file_path in search_dir.rglob(pattern):
165
+ try:
166
+ agent_def = load_agent_definition(str(file_path))
167
+ agents_list.append({
168
+ 'type': 'local',
169
+ 'name': agent_def.get('name', file_path.stem),
170
+ 'source': str(file_path),
171
+ 'description': agent_def.get('description', '')[:60]
172
+ })
173
+ except Exception as e:
174
+ logger.debug(f"Failed to load {file_path}: {e}")
175
+
176
+ if not agents_list:
177
+ console.print("[yellow]No agents found. Create an agent first or check your configuration.[/yellow]")
178
+ return None
179
+
180
+ # Display agents with numbers using rich
181
+ for i, agent in enumerate(agents_list, 1):
182
+ agent_type = "📦 Platform" if agent['type'] == 'platform' else "📁 Local"
183
+ console.print(f"{i}. [[bold]{agent_type}[/bold]] [cyan]{agent['name']}[/cyan]")
184
+ if agent['description']:
185
+ console.print(f" [dim]{agent['description']}[/dim]")
186
+
187
+ console.print(f"\n[dim]0. Cancel[/dim]")
188
+
189
+ # Get user selection
190
+ while True:
191
+ try:
192
+ choice = input("\nSelect agent number: ").strip()
193
+
194
+ if choice == '0':
195
+ return None
196
+
197
+ idx = int(choice) - 1
198
+ if 0 <= idx < len(agents_list):
199
+ selected = agents_list[idx]
200
+ console.print(f"\n✓ [green]Selected:[/green] [bold]{selected['name']}[/bold]")
201
+ return selected['source']
202
+ else:
203
+ console.print(f"[yellow]Invalid selection. Please enter a number between 0 and {len(agents_list)}[/yellow]")
204
+ except ValueError:
205
+ console.print("[yellow]Please enter a valid number[/yellow]")
206
+ except (KeyboardInterrupt, EOFError):
207
+ console.print("\n\n[dim]Cancelled.[/dim]")
208
+ return None
209
+
210
+
211
+ @click.group()
212
+ def agent():
213
+ """Agent testing and interaction commands."""
214
+ pass
215
+
216
+
217
+ @agent.command('list')
218
+ @click.option('--local', is_flag=True, help='List local agent definition files')
219
+ @click.option('--directory', default=None, help='Directory to search for local agents (defaults to AGENTS_DIR from .env)')
220
+ @click.pass_context
221
+ def agent_list(ctx, local: bool, directory: Optional[str]):
222
+ """
223
+ List available agents.
224
+
225
+ By default, lists agents from the platform.
226
+ Use --local to list agent definition files in the local directory.
227
+ """
228
+ formatter = ctx.obj['formatter']
229
+ config = ctx.obj['config']
230
+
231
+ try:
232
+ if local:
233
+ # List local agent definition files
234
+ if directory is None:
235
+ directory = config.agents_dir
236
+ search_dir = Path(directory)
237
+
238
+ if not search_dir.exists():
239
+ console.print(f"[red]Directory not found: {directory}[/red]")
240
+ return
241
+
242
+ agents = []
243
+
244
+ # Find agent definition files
245
+ for pattern in ['*.agent.md', '*.agent.yaml', '*.agent.yml', '*.agent.json']:
246
+ for file_path in search_dir.rglob(pattern):
247
+ try:
248
+ agent_def = load_agent_definition(str(file_path))
249
+ # Use relative path if already relative, otherwise make it relative to cwd
250
+ try:
251
+ display_path = str(file_path.relative_to(Path.cwd()))
252
+ except ValueError:
253
+ display_path = str(file_path)
254
+
255
+ agents.append({
256
+ 'name': agent_def.get('name', file_path.stem),
257
+ 'file': display_path,
258
+ 'description': agent_def.get('description', '')[:80]
259
+ })
260
+ except Exception as e:
261
+ logger.debug(f"Failed to load {file_path}: {e}")
262
+
263
+ if not agents:
264
+ console.print(f"\n[yellow]No agent definition files found in {directory}[/yellow]")
265
+ return
266
+
267
+ # Display local agents in a table
268
+ table = Table(
269
+ title=f"Local Agent Definitions in {directory}",
270
+ show_header=True,
271
+ header_style="bold cyan",
272
+ border_style="cyan",
273
+ box=box.ROUNDED
274
+ )
275
+ table.add_column("Name", style="bold cyan", no_wrap=True)
276
+ table.add_column("File", style="dim")
277
+ table.add_column("Description", style="white")
278
+
279
+ for agent_info in sorted(agents, key=lambda x: x['name']):
280
+ table.add_row(
281
+ agent_info['name'],
282
+ agent_info['file'],
283
+ agent_info['description'] or "-"
284
+ )
285
+
286
+ console.print("\n")
287
+ console.print(table)
288
+ console.print(f"\n[green]Total: {len(agents)} local agents[/green]")
289
+
290
+ else:
291
+ # List platform agents
292
+ client = get_client(ctx)
293
+
294
+ agents = client.get_list_of_apps()
295
+
296
+ if formatter.__class__.__name__ == 'JSONFormatter':
297
+ click.echo(formatter._dump({'agents': agents, 'total': len(agents)}))
298
+ else:
299
+ table = Table(
300
+ title="Available Platform Agents",
301
+ show_header=True,
302
+ header_style="bold cyan",
303
+ border_style="cyan",
304
+ box=box.ROUNDED
305
+ )
306
+ table.add_column("ID", style="yellow", no_wrap=True)
307
+ table.add_column("Name", style="bold cyan")
308
+ table.add_column("Description", style="white")
309
+
310
+ for agent_info in agents:
311
+ table.add_row(
312
+ str(agent_info['id']),
313
+ agent_info['name'],
314
+ agent_info.get('description', '')[:80] or "-"
315
+ )
316
+
317
+ console.print("\n")
318
+ console.print(table)
319
+ console.print(f"\n[green]Total: {len(agents)} agents[/green]")
320
+
321
+ except Exception as e:
322
+ logger.exception("Failed to list agents")
323
+ error_panel = Panel(
324
+ str(e),
325
+ title="Error",
326
+ border_style="red",
327
+ box=box.ROUNDED
328
+ )
329
+ console.print(error_panel, style="red")
330
+ raise click.Abort()
331
+
332
+
333
+ @agent.command('show')
334
+ @click.argument('agent_source')
335
+ @click.option('--version', help='Agent version (for platform agents)')
336
+ @click.pass_context
337
+ def agent_show(ctx, agent_source: str, version: Optional[str]):
338
+ """
339
+ Show agent details.
340
+
341
+ AGENT_SOURCE can be:
342
+ - Platform agent ID or name (e.g., "123" or "my-agent")
343
+ - Path to local agent file (e.g., ".github/agents/sdk-dev.agent.md")
344
+ """
345
+ formatter = ctx.obj['formatter']
346
+
347
+ try:
348
+ # Check if it's a file path
349
+ if Path(agent_source).exists():
350
+ # Local agent file
351
+ agent_def = load_agent_definition(agent_source)
352
+
353
+ if formatter.__class__.__name__ == 'JSONFormatter':
354
+ click.echo(formatter._dump(agent_def))
355
+ else:
356
+ # Create details panel
357
+ details = Text()
358
+ details.append("File: ", style="bold")
359
+ details.append(f"{agent_source}\n", style="cyan")
360
+
361
+ if agent_def.get('description'):
362
+ details.append("\nDescription: ", style="bold")
363
+ details.append(f"{agent_def['description']}\n", style="white")
364
+
365
+ if agent_def.get('model'):
366
+ details.append("Model: ", style="bold")
367
+ details.append(f"{agent_def['model']}\n", style="cyan")
368
+
369
+ if agent_def.get('tools'):
370
+ details.append("Tools: ", style="bold")
371
+ details.append(f"{', '.join(agent_def['tools'])}\n", style="cyan")
372
+
373
+ if agent_def.get('temperature') is not None:
374
+ details.append("Temperature: ", style="bold")
375
+ details.append(f"{agent_def['temperature']}\n", style="cyan")
376
+
377
+ panel = Panel(
378
+ details,
379
+ title=f"Local Agent: {agent_def.get('name', 'Unknown')}",
380
+ title_align="left",
381
+ border_style="cyan",
382
+ box=box.ROUNDED
383
+ )
384
+ console.print("\n")
385
+ console.print(panel)
386
+
387
+ if agent_def.get('system_prompt'):
388
+ console.print("\n[bold]System Prompt:[/bold]")
389
+ console.print(Panel(agent_def['system_prompt'][:500] + "...", border_style="dim", box=box.ROUNDED))
390
+
391
+ else:
392
+ # Platform agent
393
+ client = get_client(ctx)
394
+
395
+ # Try to find agent by ID or name
396
+ agents = client.get_list_of_apps()
397
+
398
+ agent = None
399
+ try:
400
+ agent_id = int(agent_source)
401
+ agent = next((a for a in agents if a['id'] == agent_id), None)
402
+ except ValueError:
403
+ agent = next((a for a in agents if a['name'] == agent_source), None)
404
+
405
+ if not agent:
406
+ raise click.ClickException(f"Agent '{agent_source}' not found")
407
+
408
+ # Get details
409
+ details = client.get_app_details(agent['id'])
410
+
411
+ if formatter.__class__.__name__ == 'JSONFormatter':
412
+ click.echo(formatter._dump(details))
413
+ else:
414
+ # Create platform agent details panel
415
+ content = Text()
416
+ content.append("ID: ", style="bold")
417
+ content.append(f"{details['id']}\n", style="yellow")
418
+
419
+ if details.get('description'):
420
+ content.append("\nDescription: ", style="bold")
421
+ content.append(f"{details['description']}\n", style="white")
422
+
423
+ panel = Panel(
424
+ content,
425
+ title=f"Agent: {details['name']}",
426
+ title_align="left",
427
+ border_style="cyan",
428
+ box=box.ROUNDED
429
+ )
430
+ console.print("\n")
431
+ console.print(panel)
432
+
433
+ # Display versions in a table
434
+ if details.get('versions'):
435
+ console.print("\n[bold]Versions:[/bold]")
436
+ versions_table = Table(box=box.ROUNDED, border_style="dim")
437
+ versions_table.add_column("Name", style="cyan")
438
+ versions_table.add_column("ID", style="yellow")
439
+ for ver in details.get('versions', []):
440
+ versions_table.add_row(ver['name'], str(ver['id']))
441
+ console.print(versions_table)
442
+
443
+ except click.ClickException:
444
+ raise
445
+ except Exception as e:
446
+ logger.exception("Failed to show agent details")
447
+ error_panel = Panel(
448
+ str(e),
449
+ title="Error",
450
+ border_style="red",
451
+ box=box.ROUNDED
452
+ )
453
+ console.print(error_panel, style="red")
454
+ raise click.Abort()
455
+
456
+
457
+ @agent.command('chat')
458
+ @click.argument('agent_source', required=False)
459
+ @click.option('--version', help='Agent version (for platform agents)')
460
+ @click.option('--toolkit-config', multiple=True, type=click.Path(exists=True),
461
+ help='Toolkit configuration files (can specify multiple)')
462
+ @click.option('--thread-id', help='Continue existing conversation thread')
463
+ @click.option('--model', help='Override LLM model')
464
+ @click.option('--temperature', type=float, help='Override temperature')
465
+ @click.option('--max-tokens', type=int, help='Override max tokens')
466
+ @click.option('--dir', 'work_dir', type=click.Path(exists=True, file_okay=False, dir_okay=True),
467
+ help='Grant agent filesystem access to this directory')
468
+ @click.option('--verbose', '-v', type=click.Choice(['quiet', 'default', 'debug']), default='default',
469
+ help='Output verbosity level: quiet (final output only), default (tool calls + outputs), debug (all including LLM calls)')
470
+ @click.pass_context
471
+ def agent_chat(ctx, agent_source: Optional[str], version: Optional[str],
472
+ toolkit_config: tuple, thread_id: Optional[str],
473
+ model: Optional[str], temperature: Optional[float],
474
+ max_tokens: Optional[int], work_dir: Optional[str],
475
+ verbose: str):
476
+ """
477
+ Start interactive chat with an agent.
478
+
479
+ If AGENT_SOURCE is not provided, shows an interactive menu to select from
480
+ available agents (both platform and local).
481
+
482
+ AGENT_SOURCE can be:
483
+ - Platform agent ID or name
484
+ - Path to local agent file
485
+
486
+ Examples:
487
+
488
+ # Interactive selection
489
+ alita-cli agent chat
490
+
491
+ # Chat with platform agent
492
+ alita-cli agent chat my-agent
493
+
494
+ # Chat with local agent
495
+ alita-cli agent chat .github/agents/sdk-dev.agent.md
496
+
497
+ # With toolkit configurations
498
+ alita-cli agent chat my-agent \\
499
+ --toolkit-config jira-config.json \\
500
+ --toolkit-config github-config.json
501
+
502
+ # With filesystem access
503
+ alita-cli agent chat my-agent --dir ./workspace
504
+
505
+ # Continue previous conversation
506
+ alita-cli agent chat my-agent --thread-id abc123
507
+
508
+ # Quiet mode (hide tool calls and thinking)
509
+ alita-cli agent chat my-agent --verbose quiet
510
+
511
+ # Debug mode (show all including LLM calls)
512
+ alita-cli agent chat my-agent --verbose debug
513
+ """
514
+ formatter = ctx.obj['formatter']
515
+ config = ctx.obj['config']
516
+ client = get_client(ctx)
517
+
518
+ # Setup verbose level
519
+ show_verbose = verbose != 'quiet'
520
+ debug_mode = verbose == 'debug'
521
+
522
+ try:
523
+ # If no agent specified, show selection menu
524
+ if not agent_source:
525
+ agent_source = _select_agent_interactive(client, config)
526
+ if not agent_source:
527
+ console.print("[yellow]No agent selected. Exiting.[/yellow]")
528
+ return
529
+
530
+ # Load agent
531
+ is_local = Path(agent_source).exists()
532
+
533
+ if is_local:
534
+ agent_def = load_agent_definition(agent_source)
535
+ agent_name = agent_def.get('name', Path(agent_source).stem)
536
+ agent_type = "Local Agent"
537
+ else:
538
+ # Platform agent - find it
539
+ agents = client.get_list_of_apps()
540
+ agent = None
541
+
542
+ try:
543
+ agent_id = int(agent_source)
544
+ agent = next((a for a in agents if a['id'] == agent_id), None)
545
+ except ValueError:
546
+ agent = next((a for a in agents if a['name'] == agent_source), None)
547
+
548
+ if not agent:
549
+ raise click.ClickException(f"Agent '{agent_source}' not found")
550
+
551
+ agent_name = agent['name']
552
+ agent_type = "Platform Agent"
553
+
554
+ # Print nice welcome banner
555
+ print_welcome(agent_name, agent_type)
556
+
557
+ # Initialize conversation
558
+ chat_history = []
559
+
560
+ # Create memory for agent
561
+ from langgraph.checkpoint.sqlite import SqliteSaver
562
+ memory = SqliteSaver(sqlite3.connect(":memory:", check_same_thread=False))
563
+
564
+ # Create agent executor
565
+ if is_local:
566
+ # Display configuration
567
+ llm_model_display = model or agent_def.get('model', 'gpt-4o')
568
+ llm_temperature_display = temperature if temperature is not None else agent_def.get('temperature', 0.7)
569
+ console.print()
570
+ console.print(f"✓ [green]Using model:[/green] [bold]{llm_model_display}[/bold]")
571
+ console.print(f"✓ [green]Temperature:[/green] [bold]{llm_temperature_display}[/bold]")
572
+ if agent_def.get('tools'):
573
+ console.print(f"✓ [green]Tools:[/green] [bold]{', '.join(agent_def['tools'])}[/bold]")
574
+ console.print()
575
+
576
+ # Setup local agent executor (handles all config, tools, MCP, etc.)
577
+ try:
578
+ agent_executor, mcp_session_manager, llm, llm_model, filesystem_tools = _setup_local_agent_executor(
579
+ client, agent_def, toolkit_config, config, model, temperature, max_tokens, memory, work_dir
580
+ )
581
+ except Exception:
582
+ return
583
+ else:
584
+ # Platform agent
585
+ details = client.get_app_details(agent['id'])
586
+
587
+ if version:
588
+ version_obj = next((v for v in details['versions'] if v['name'] == version), None)
589
+ if not version_obj:
590
+ raise click.ClickException(f"Version '{version}' not found")
591
+ version_id = version_obj['id']
592
+ else:
593
+ # Use first version
594
+ version_id = details['versions'][0]['id']
595
+
596
+ # Display configuration
597
+ console.print()
598
+ console.print("✓ [green]Connected to platform agent[/green]")
599
+ console.print()
600
+
601
+ agent_executor = client.application(
602
+ application_id=agent['id'],
603
+ application_version_id=version_id,
604
+ memory=memory,
605
+ chat_history=chat_history
606
+ )
607
+ llm = None # Platform agents don't use direct LLM
608
+
609
+ # Interactive chat loop
610
+ while True:
611
+ try:
612
+ # Styled prompt
613
+ console.print("\n[bold bright_white]>[/bold bright_white] ", end="")
614
+ user_input = input().strip()
615
+
616
+ if not user_input:
617
+ continue
618
+
619
+ # Handle commands
620
+ if user_input.lower() in ['exit', 'quit']:
621
+ console.print("\n[bold cyan]👋 Goodbye![/bold cyan]\n")
622
+ break
623
+
624
+ if user_input == '/clear':
625
+ chat_history = []
626
+ console.print("[green]✓ Conversation history cleared.[/green]")
627
+ continue
628
+
629
+ if user_input == '/history':
630
+ if not chat_history:
631
+ console.print("[yellow]No conversation history yet.[/yellow]")
632
+ else:
633
+ console.print("\n[bold cyan]── Conversation History ──[/bold cyan]")
634
+ for i, msg in enumerate(chat_history, 1):
635
+ role = msg.get('role', 'unknown')
636
+ content = msg.get('content', '')
637
+ role_color = 'blue' if role == 'user' else 'green'
638
+ console.print(f"\n[bold {role_color}]{i}. {role.upper()}:[/bold {role_color}] {content[:100]}...")
639
+ continue
640
+
641
+ if user_input == '/save':
642
+ console.print("[yellow]Save to file (default: conversation.json):[/yellow] ", end="")
643
+ filename = input().strip()
644
+ filename = filename or "conversation.json"
645
+ with open(filename, 'w') as f:
646
+ json.dump({'history': chat_history}, f, indent=2)
647
+ console.print(f"[green]✓ Conversation saved to {filename}[/green]")
648
+ continue
649
+
650
+ if user_input == '/help':
651
+ print_help()
652
+ continue
653
+
654
+ # Execute agent
655
+ if is_local and agent_executor is None:
656
+ # Local agent without tools: use direct LLM call with streaming
657
+ system_prompt = agent_def.get('system_prompt', '')
658
+ messages = []
659
+ if system_prompt:
660
+ messages.append({"role": "system", "content": system_prompt})
661
+
662
+ # Add chat history
663
+ for msg in chat_history:
664
+ messages.append(msg)
665
+
666
+ # Add user message
667
+ messages.append({"role": "user", "content": user_input})
668
+
669
+ try:
670
+ # Try streaming if available
671
+ if hasattr(llm, 'stream'):
672
+ output_chunks = []
673
+ first_chunk = True
674
+
675
+ # Show spinner until first token arrives
676
+ status = console.status("[yellow]Thinking...[/yellow]", spinner="dots")
677
+ status.start()
678
+
679
+ # Stream the response token by token
680
+ for chunk in llm.stream(messages):
681
+ if hasattr(chunk, 'content'):
682
+ token = chunk.content
683
+ else:
684
+ token = str(chunk)
685
+
686
+ if token:
687
+ # Stop spinner and show agent name on first token
688
+ if first_chunk:
689
+ status.stop()
690
+ console.print(f"\n[bold bright_cyan]{agent_name}:[/bold bright_cyan]\n", end="")
691
+ first_chunk = False
692
+
693
+ console.print(token, end="", markup=False)
694
+ output_chunks.append(token)
695
+
696
+ # Stop status if still running (no tokens received)
697
+ if first_chunk:
698
+ status.stop()
699
+ console.print(f"\n[bold bright_cyan]{agent_name}:[/bold bright_cyan]\n", end="")
700
+
701
+ output = ''.join(output_chunks)
702
+ console.print() # New line after streaming
703
+ else:
704
+ # Fallback to non-streaming with spinner
705
+ with console.status("[yellow]Thinking...[/yellow]", spinner="dots"):
706
+ response = llm.invoke(messages)
707
+ if hasattr(response, 'content'):
708
+ output = response.content
709
+ else:
710
+ output = str(response)
711
+
712
+ # Display response after spinner stops
713
+ console.print(f"\n[bold bright_cyan]{agent_name}:[/bold bright_cyan]")
714
+ if any(marker in output for marker in ['```', '**', '##', '- ', '* ']):
715
+ console.print(Markdown(output))
716
+ else:
717
+ console.print(output)
718
+ except Exception as e:
719
+ console.print(f"\n[red]✗ Error: {e}[/red]\n")
720
+ continue
721
+ else:
722
+ # Agent with tools or platform agent: use agent executor
723
+ # Setup callback for verbose output
724
+ from langchain_core.runnables import RunnableConfig
725
+
726
+ invoke_config = None
727
+ if show_verbose:
728
+ cli_callback = create_cli_callback(verbose=True, debug=debug_mode)
729
+ invoke_config = RunnableConfig(callbacks=[cli_callback])
730
+
731
+ # Show status only when not verbose (verbose shows its own progress)
732
+ if not show_verbose:
733
+ with console.status("[yellow]Thinking...[/yellow]", spinner="dots"):
734
+ result = agent_executor.invoke(
735
+ {
736
+ "input": [user_input] if not is_local else user_input,
737
+ "chat_history": chat_history
738
+ },
739
+ config=invoke_config
740
+ )
741
+ else:
742
+ console.print() # Add spacing before tool calls
743
+ result = agent_executor.invoke(
744
+ {
745
+ "input": [user_input] if not is_local else user_input,
746
+ "chat_history": chat_history
747
+ },
748
+ config=invoke_config
749
+ )
750
+
751
+ # Extract output from result
752
+ output = extract_output_from_result(result)
753
+
754
+ # Display response
755
+ console.print(f"\n[bold bright_cyan]{agent_name}:[/bold bright_cyan]")
756
+ if any(marker in output for marker in ['```', '**', '##', '- ', '* ']):
757
+ console.print(Markdown(output))
758
+ else:
759
+ console.print(output)
760
+
761
+ # Update chat history
762
+ chat_history.append({"role": "user", "content": user_input})
763
+ chat_history.append({"role": "assistant", "content": output})
764
+
765
+ except KeyboardInterrupt:
766
+ console.print("\n\n[yellow]Interrupted. Type 'exit' to quit or continue chatting.[/yellow]")
767
+ continue
768
+ except EOFError:
769
+ console.print("\n\n[bold cyan]Goodbye! 👋[/bold cyan]")
770
+ break
771
+
772
+ except click.ClickException:
773
+ raise
774
+ except Exception as e:
775
+ logger.exception("Failed to start chat")
776
+ error_panel = Panel(
777
+ str(e),
778
+ title="Error",
779
+ border_style="red",
780
+ box=box.ROUNDED
781
+ )
782
+ console.print(error_panel, style="red")
783
+ raise click.Abort()
784
+
785
+
786
+ @agent.command('run')
787
+ @click.argument('agent_source')
788
+ @click.argument('message')
789
+ @click.option('--version', help='Agent version (for platform agents)')
790
+ @click.option('--toolkit-config', multiple=True, type=click.Path(exists=True),
791
+ help='Toolkit configuration files')
792
+ @click.option('--model', help='Override LLM model')
793
+ @click.option('--temperature', type=float, help='Override temperature')
794
+ @click.option('--max-tokens', type=int, help='Override max tokens')
795
+ @click.option('--save-thread', help='Save thread ID to file for continuation')
796
+ @click.option('--dir', 'work_dir', type=click.Path(exists=True, file_okay=False, dir_okay=True),
797
+ help='Grant agent filesystem access to this directory')
798
+ @click.option('--verbose', '-v', type=click.Choice(['quiet', 'default', 'debug']), default='default',
799
+ help='Output verbosity level: quiet (final output only), default (tool calls + outputs), debug (all including LLM calls)')
800
+ @click.pass_context
801
+ def agent_run(ctx, agent_source: str, message: str, version: Optional[str],
802
+ toolkit_config: tuple, model: Optional[str],
803
+ temperature: Optional[float], max_tokens: Optional[int],
804
+ save_thread: Optional[str], work_dir: Optional[str],
805
+ verbose: str):
806
+ """
807
+ Run agent with a single message (handoff mode).
808
+
809
+ AGENT_SOURCE can be:
810
+ - Platform agent ID or name
811
+ - Path to local agent file
812
+
813
+ MESSAGE is the input message to send to the agent.
814
+
815
+ Examples:
816
+
817
+ # Simple query
818
+ alita-cli agent run my-agent "What is the status of JIRA-123?"
819
+
820
+ # With local agent
821
+ alita-cli agent run .github/agents/sdk-dev.agent.md \\
822
+ "Create a new toolkit for Stripe API"
823
+
824
+ # With toolkit configs and JSON output
825
+ alita-cli --output json agent run my-agent "Search for bugs" \\
826
+ --toolkit-config jira-config.json
827
+
828
+ # With filesystem access
829
+ alita-cli agent run my-agent "Analyze the code in src/" --dir ./myproject
830
+
831
+ # Save thread for continuation
832
+ alita-cli agent run my-agent "Start task" \\
833
+ --save-thread thread.txt
834
+
835
+ # Quiet mode (hide tool calls and thinking)
836
+ alita-cli agent run my-agent "Query" --verbose quiet
837
+
838
+ # Debug mode (show all including LLM calls)
839
+ alita-cli agent run my-agent "Query" --verbose debug
840
+ """
841
+ formatter = ctx.obj['formatter']
842
+ client = get_client(ctx)
843
+
844
+ # Setup verbose level
845
+ show_verbose = verbose != 'quiet'
846
+ debug_mode = verbose == 'debug'
847
+
848
+ try:
849
+ # Load agent
850
+ is_local = Path(agent_source).exists()
851
+
852
+ if is_local:
853
+ agent_def = load_agent_definition(agent_source)
854
+ agent_name = agent_def.get('name', Path(agent_source).stem)
855
+
856
+ # Create memory for agent
857
+ from langgraph.checkpoint.sqlite import SqliteSaver
858
+ memory = SqliteSaver(sqlite3.connect(":memory:", check_same_thread=False))
859
+
860
+ # Setup local agent executor (reuses same logic as agent_chat)
861
+ try:
862
+ agent_executor, mcp_session_manager, llm, llm_model, filesystem_tools = _setup_local_agent_executor(
863
+ client, agent_def, toolkit_config, ctx.obj['config'], model, temperature, max_tokens, memory, work_dir
864
+ )
865
+ except Exception as e:
866
+ error_panel = Panel(
867
+ f"Failed to setup agent: {e}",
868
+ title="Error",
869
+ border_style="red",
870
+ box=box.ROUNDED
871
+ )
872
+ console.print(error_panel, style="red")
873
+ raise click.Abort()
874
+
875
+ # Execute agent
876
+ if agent_executor:
877
+ # Setup callback for verbose output
878
+ from langchain_core.runnables import RunnableConfig
879
+
880
+ invoke_config = None
881
+ if show_verbose:
882
+ cli_callback = create_cli_callback(verbose=True, debug=debug_mode)
883
+ invoke_config = RunnableConfig(callbacks=[cli_callback])
884
+
885
+ # Execute with spinner for non-JSON output
886
+ if formatter.__class__.__name__ == 'JSONFormatter':
887
+ # JSON output: always quiet, no callbacks
888
+ with console.status("[yellow]Processing...[/yellow]", spinner="dots"):
889
+ result = agent_executor.invoke({
890
+ "input": message,
891
+ "chat_history": []
892
+ })
893
+
894
+ click.echo(formatter._dump({
895
+ 'agent': agent_name,
896
+ 'message': message,
897
+ 'response': extract_output_from_result(result),
898
+ 'full_result': result
899
+ }))
900
+ else:
901
+ # Show status only when not verbose (verbose shows its own progress)
902
+ if not show_verbose:
903
+ with console.status("[yellow]Processing...[/yellow]", spinner="dots"):
904
+ result = agent_executor.invoke(
905
+ {
906
+ "input": message,
907
+ "chat_history": []
908
+ },
909
+ config=invoke_config
910
+ )
911
+ else:
912
+ console.print() # Add spacing before tool calls
913
+ result = agent_executor.invoke(
914
+ {
915
+ "input": message,
916
+ "chat_history": []
917
+ },
918
+ config=invoke_config
919
+ )
920
+
921
+ # Extract and display output
922
+ output = extract_output_from_result(result)
923
+ display_output(agent_name, message, output)
924
+ else:
925
+ # Simple LLM mode without tools
926
+ system_prompt = agent_def.get('system_prompt', '')
927
+ messages = []
928
+ if system_prompt:
929
+ messages.append({"role": "system", "content": system_prompt})
930
+ messages.append({"role": "user", "content": message})
931
+
932
+ # Execute with spinner for non-JSON output
933
+ if formatter.__class__.__name__ == 'JSONFormatter':
934
+ response = llm.invoke(messages)
935
+ if hasattr(response, 'content'):
936
+ output = response.content
937
+ else:
938
+ output = str(response)
939
+
940
+ click.echo(formatter._dump({
941
+ 'agent': agent_name,
942
+ 'message': message,
943
+ 'response': output
944
+ }))
945
+ else:
946
+ # Show spinner while executing
947
+ with console.status("[yellow]Processing...[/yellow]", spinner="dots"):
948
+ response = llm.invoke(messages)
949
+ if hasattr(response, 'content'):
950
+ output = response.content
951
+ else:
952
+ output = str(response)
953
+
954
+ # Display output
955
+ display_output(agent_name, message, output)
956
+
957
+ else:
958
+ # Platform agent
959
+ agents = client.get_list_of_apps()
960
+ agent = None
961
+
962
+ try:
963
+ agent_id = int(agent_source)
964
+ agent = next((a for a in agents if a['id'] == agent_id), None)
965
+ except ValueError:
966
+ agent = next((a for a in agents if a['name'] == agent_source), None)
967
+
968
+ if not agent:
969
+ raise click.ClickException(f"Agent '{agent_source}' not found")
970
+
971
+ # Get version
972
+ details = client.get_app_details(agent['id'])
973
+
974
+ if version:
975
+ version_obj = next((v for v in details['versions'] if v['name'] == version), None)
976
+ if not version_obj:
977
+ raise click.ClickException(f"Version '{version}' not found")
978
+ version_id = version_obj['id']
979
+ else:
980
+ version_id = details['versions'][0]['id']
981
+
982
+ # Load toolkit configs from CLI options
983
+ toolkit_configs = []
984
+ if toolkit_config:
985
+ for config_path in toolkit_config:
986
+ toolkit_configs.append(load_toolkit_config(config_path))
987
+
988
+ # Create memory
989
+ from langgraph.checkpoint.sqlite import SqliteSaver
990
+ memory = SqliteSaver(sqlite3.connect(":memory:", check_same_thread=False))
991
+
992
+ # Create agent executor
993
+ agent_executor = client.application(
994
+ application_id=agent['id'],
995
+ application_version_id=version_id,
996
+ memory=memory
997
+ )
998
+
999
+ # Setup callback for verbose output
1000
+ from langchain_core.runnables import RunnableConfig
1001
+
1002
+ invoke_config = None
1003
+ if show_verbose:
1004
+ cli_callback = create_cli_callback(verbose=True, debug=debug_mode)
1005
+ invoke_config = RunnableConfig(callbacks=[cli_callback])
1006
+
1007
+ # Execute with spinner for non-JSON output
1008
+ if formatter.__class__.__name__ == 'JSONFormatter':
1009
+ result = agent_executor.invoke({
1010
+ "input": [message],
1011
+ "chat_history": []
1012
+ })
1013
+
1014
+ click.echo(formatter._dump({
1015
+ 'agent': agent['name'],
1016
+ 'message': message,
1017
+ 'response': result.get('output', ''),
1018
+ 'full_result': result
1019
+ }))
1020
+ else:
1021
+ # Show status only when not verbose
1022
+ if not show_verbose:
1023
+ with console.status("[yellow]Processing...[/yellow]", spinner="dots"):
1024
+ result = agent_executor.invoke(
1025
+ {
1026
+ "input": [message],
1027
+ "chat_history": []
1028
+ },
1029
+ config=invoke_config
1030
+ )
1031
+ else:
1032
+ console.print() # Add spacing before tool calls
1033
+ result = agent_executor.invoke(
1034
+ {
1035
+ "input": [message],
1036
+ "chat_history": []
1037
+ },
1038
+ config=invoke_config
1039
+ )
1040
+
1041
+ # Display output
1042
+ response = result.get('output', 'No response')
1043
+ display_output(agent['name'], message, response)
1044
+
1045
+ # Save thread if requested
1046
+ if save_thread:
1047
+ thread_data = {
1048
+ 'agent_id': agent['id'],
1049
+ 'agent_name': agent['name'],
1050
+ 'version_id': version_id,
1051
+ 'thread_id': result.get('thread_id'),
1052
+ 'last_message': message
1053
+ }
1054
+ with open(save_thread, 'w') as f:
1055
+ json.dump(thread_data, f, indent=2)
1056
+ logger.info(f"Thread saved to {save_thread}")
1057
+
1058
+ except click.ClickException:
1059
+ raise
1060
+ except Exception as e:
1061
+ logger.exception("Failed to run agent")
1062
+ error_panel = Panel(
1063
+ str(e),
1064
+ title="Error",
1065
+ border_style="red",
1066
+ box=box.ROUNDED
1067
+ )
1068
+ console.print(error_panel, style="red")
1069
+ raise click.Abort()