a4e 0.1.5__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 (70) hide show
  1. a4e/__init__.py +0 -0
  2. a4e/cli.py +47 -0
  3. a4e/cli_commands/__init__.py +5 -0
  4. a4e/cli_commands/add.py +376 -0
  5. a4e/cli_commands/deploy.py +149 -0
  6. a4e/cli_commands/dev.py +162 -0
  7. a4e/cli_commands/info.py +206 -0
  8. a4e/cli_commands/init.py +211 -0
  9. a4e/cli_commands/list.py +227 -0
  10. a4e/cli_commands/mcp.py +504 -0
  11. a4e/cli_commands/remove.py +197 -0
  12. a4e/cli_commands/update.py +285 -0
  13. a4e/cli_commands/validate.py +117 -0
  14. a4e/core.py +109 -0
  15. a4e/dev_runner.py +425 -0
  16. a4e/server.py +86 -0
  17. a4e/templates/agent.md.j2 +168 -0
  18. a4e/templates/agent.py.j2 +15 -0
  19. a4e/templates/agents.md.j2 +99 -0
  20. a4e/templates/metadata.json.j2 +20 -0
  21. a4e/templates/prompt.md.j2 +20 -0
  22. a4e/templates/prompts/agent.md.j2 +206 -0
  23. a4e/templates/skills/agents.md.j2 +110 -0
  24. a4e/templates/skills/skill.md.j2 +120 -0
  25. a4e/templates/support_module.py.j2 +84 -0
  26. a4e/templates/tool.py.j2 +60 -0
  27. a4e/templates/tools/agent.md.j2 +192 -0
  28. a4e/templates/view.tsx.j2 +21 -0
  29. a4e/templates/views/agent.md.j2 +219 -0
  30. a4e/tools/__init__.py +70 -0
  31. a4e/tools/agent_tools/__init__.py +12 -0
  32. a4e/tools/agent_tools/add_support_module.py +95 -0
  33. a4e/tools/agent_tools/add_tool.py +115 -0
  34. a4e/tools/agent_tools/list_tools.py +28 -0
  35. a4e/tools/agent_tools/remove_tool.py +69 -0
  36. a4e/tools/agent_tools/update_tool.py +123 -0
  37. a4e/tools/deploy/__init__.py +8 -0
  38. a4e/tools/deploy/deploy.py +59 -0
  39. a4e/tools/dev/__init__.py +10 -0
  40. a4e/tools/dev/check_environment.py +79 -0
  41. a4e/tools/dev/dev_start.py +30 -0
  42. a4e/tools/dev/dev_stop.py +26 -0
  43. a4e/tools/project/__init__.py +10 -0
  44. a4e/tools/project/get_agent_info.py +66 -0
  45. a4e/tools/project/get_instructions.py +216 -0
  46. a4e/tools/project/initialize_project.py +231 -0
  47. a4e/tools/schemas/__init__.py +8 -0
  48. a4e/tools/schemas/generate_schemas.py +278 -0
  49. a4e/tools/skills/__init__.py +12 -0
  50. a4e/tools/skills/add_skill.py +105 -0
  51. a4e/tools/skills/helpers.py +137 -0
  52. a4e/tools/skills/list_skills.py +54 -0
  53. a4e/tools/skills/remove_skill.py +74 -0
  54. a4e/tools/skills/update_skill.py +150 -0
  55. a4e/tools/validation/__init__.py +8 -0
  56. a4e/tools/validation/validate.py +389 -0
  57. a4e/tools/views/__init__.py +12 -0
  58. a4e/tools/views/add_view.py +40 -0
  59. a4e/tools/views/helpers.py +91 -0
  60. a4e/tools/views/list_views.py +27 -0
  61. a4e/tools/views/remove_view.py +73 -0
  62. a4e/tools/views/update_view.py +124 -0
  63. a4e/utils/dev_manager.py +253 -0
  64. a4e/utils/schema_generator.py +255 -0
  65. a4e-0.1.5.dist-info/METADATA +427 -0
  66. a4e-0.1.5.dist-info/RECORD +70 -0
  67. a4e-0.1.5.dist-info/WHEEL +5 -0
  68. a4e-0.1.5.dist-info/entry_points.txt +2 -0
  69. a4e-0.1.5.dist-info/licenses/LICENSE +21 -0
  70. a4e-0.1.5.dist-info/top_level.txt +1 -0
a4e/__init__.py ADDED
File without changes
a4e/cli.py ADDED
@@ -0,0 +1,47 @@
1
+ # Import dependencies
2
+ from typing import Optional
3
+ import typer
4
+ from .cli_commands import dev, init, add, list, validate, deploy, info, remove, update, mcp
5
+
6
+ # Version from pyproject.toml
7
+ __version__ = "0.1.5"
8
+
9
+
10
+ def version_callback(value: bool):
11
+ if value:
12
+ typer.echo(f"a4e {__version__}")
13
+ raise typer.Exit()
14
+
15
+
16
+ # Initialize the cli app
17
+ app = typer.Typer(
18
+ no_args_is_help=True,
19
+ add_completion=True,
20
+ help="A4E CLI - Create and manage conversational AI agents",
21
+ )
22
+
23
+
24
+ @app.callback()
25
+ def main(
26
+ version: Optional[bool] = typer.Option(
27
+ None, "--version", "-v", callback=version_callback, is_eager=True,
28
+ help="Show version and exit"
29
+ ),
30
+ ):
31
+ """A4E CLI - Create and manage conversational AI agents."""
32
+ pass
33
+
34
+ # Command groups for the cli
35
+ app.add_typer(dev.app, name="dev", help="Development server commands")
36
+ app.add_typer(init.app, name="init", help="Initialize a new agent project")
37
+ app.add_typer(add.app, name="add", help="Add tools, views, or skills")
38
+ app.add_typer(list.app, name="list", help="List tools, views, or skills")
39
+ app.add_typer(update.app, name="update", help="Update tools, views, or skills")
40
+ app.add_typer(remove.app, name="remove", help="Remove tools, views, or skills")
41
+ app.add_typer(validate.app, name="validate", help="Validate agent project")
42
+ app.add_typer(deploy.app, name="deploy", help="Deploy agent to production")
43
+ app.add_typer(info.app, name="info", help="Display agent information")
44
+ app.add_typer(mcp.app, name="mcp", help="Configure MCP server for IDEs")
45
+
46
+ if __name__ == "__main__":
47
+ app()
@@ -0,0 +1,5 @@
1
+ # a4e/cli_commands/__init__.py
2
+
3
+ from . import dev
4
+ from . import mcp
5
+ # from . import agent # Will be uncommented when agent.py is created
@@ -0,0 +1,376 @@
1
+ # a4e/cli_commands/add.py
2
+ """
3
+ Commands for adding tools, views, and skills to an agent.
4
+ """
5
+
6
+ import typer
7
+ import json
8
+ from pathlib import Path
9
+ from typing import Optional
10
+
11
+ from rich.console import Console
12
+ from rich.prompt import Prompt, Confirm
13
+ from rich.table import Table
14
+
15
+ console = Console()
16
+
17
+ # Create a 'Typer' app for the 'add' command group
18
+ app = typer.Typer(
19
+ no_args_is_help=True,
20
+ help="Add tools, views, or skills to your agent.",
21
+ )
22
+
23
+ TYPE_OPTIONS = ["string", "number", "integer", "boolean", "array", "object"]
24
+
25
+
26
+ def find_agent_dir(agent_name: Optional[str] = None) -> Optional[Path]:
27
+ """Find the agent directory from name or current working directory."""
28
+ cwd = Path.cwd()
29
+
30
+ if agent_name:
31
+ # Check if it's an absolute path
32
+ if Path(agent_name).is_absolute() and Path(agent_name).exists():
33
+ return Path(agent_name)
34
+ # Check relative to cwd
35
+ if (cwd / agent_name).exists():
36
+ return cwd / agent_name
37
+ # Check in agent-store
38
+ agent_store = cwd / "file-store" / "agent-store" / agent_name
39
+ if agent_store.exists():
40
+ return agent_store
41
+ return None
42
+
43
+ # Check if cwd is an agent directory (has agent.py and metadata.json)
44
+ if (cwd / "agent.py").exists() and (cwd / "metadata.json").exists():
45
+ return cwd
46
+
47
+ return None
48
+
49
+
50
+ def prompt_for_parameters() -> dict:
51
+ """Interactive prompt for tool/view parameters."""
52
+ parameters = {}
53
+
54
+ console.print("\n[bold]Define parameters[/bold] (press Enter with empty name to finish)")
55
+
56
+ while True:
57
+ param_name = Prompt.ask("Parameter name", default="")
58
+ if not param_name:
59
+ break
60
+
61
+ # Parameter type
62
+ console.print(" Type options: " + ", ".join(f"[{i+1}]{t}" for i, t in enumerate(TYPE_OPTIONS)))
63
+ type_choice = Prompt.ask(" Type", default="1")
64
+ try:
65
+ param_type = TYPE_OPTIONS[int(type_choice) - 1]
66
+ except (ValueError, IndexError):
67
+ param_type = type_choice if type_choice in TYPE_OPTIONS else "string"
68
+
69
+ # Description
70
+ param_desc = Prompt.ask(" Description", default=f"The {param_name} parameter")
71
+
72
+ # Required?
73
+ is_required = Confirm.ask(" Required?", default=False)
74
+
75
+ parameters[param_name] = {
76
+ "type": param_type,
77
+ "description": param_desc,
78
+ "required": is_required,
79
+ }
80
+
81
+ console.print(f" [green]✓ Added parameter '{param_name}'[/green]")
82
+
83
+ return parameters
84
+
85
+
86
+ @app.command("tool")
87
+ def add_tool(
88
+ tool_name: Optional[str] = typer.Argument(None, help="Name of the tool (snake_case)"),
89
+ description: Optional[str] = typer.Option(
90
+ None, "--description", "-d", help="What the tool does"
91
+ ),
92
+ parameters_json: Optional[str] = typer.Option(
93
+ None, "--parameters", "-p", help="Parameters as JSON string"
94
+ ),
95
+ agent_name: Optional[str] = typer.Option(
96
+ None, "--agent", "-a", help="Agent name/path (defaults to current directory)"
97
+ ),
98
+ non_interactive: bool = typer.Option(
99
+ False, "--yes", "-y", help="Skip interactive prompts"
100
+ ),
101
+ ) -> None:
102
+ """
103
+ Add a new tool to the agent.
104
+
105
+ Example:
106
+ a4e add tool calculate_bmi -d "Calculate BMI"
107
+ a4e add tool # Interactive mode
108
+ """
109
+ agent_dir = find_agent_dir(agent_name)
110
+ if not agent_dir:
111
+ console.print("[red]Error: Not in an agent directory. Use --agent to specify the agent.[/red]")
112
+ raise typer.Exit(code=1)
113
+
114
+ # Interactive prompts
115
+ if not non_interactive:
116
+ if not tool_name:
117
+ tool_name = Prompt.ask("[bold]Tool name[/bold] (snake_case)")
118
+
119
+ if not description:
120
+ description = Prompt.ask("[bold]Description[/bold]", default=f"A tool that {tool_name.replace('_', ' ')}")
121
+
122
+ if not parameters_json:
123
+ parameters = prompt_for_parameters()
124
+ else:
125
+ try:
126
+ parameters = json.loads(parameters_json)
127
+ except json.JSONDecodeError:
128
+ console.print("[red]Invalid JSON for parameters[/red]")
129
+ raise typer.Exit(code=1)
130
+ else:
131
+ if not all([tool_name, description]):
132
+ console.print("[red]Error: --yes requires tool_name and --description[/red]")
133
+ raise typer.Exit(code=1)
134
+ parameters = json.loads(parameters_json) if parameters_json else {}
135
+
136
+ # Validate tool name
137
+ if not tool_name.replace("_", "").isalnum():
138
+ console.print("[red]Error: Tool name must be alphanumeric with underscores[/red]")
139
+ raise typer.Exit(code=1)
140
+
141
+ try:
142
+ from ..tools.agent_tools.add_tool import add_tool as mcp_add_tool
143
+
144
+ result = mcp_add_tool(
145
+ tool_name=tool_name,
146
+ description=description,
147
+ parameters=parameters,
148
+ agent_name=str(agent_dir),
149
+ )
150
+
151
+ if result.get("success"):
152
+ console.print(f"\n[green]✓ Tool '{tool_name}' created![/green]")
153
+ console.print(f" Path: {result.get('path')}")
154
+ console.print("\n [dim]Implement your tool logic in the generated file.[/dim]")
155
+ else:
156
+ console.print(f"\n[red]Error: {result.get('error')}[/red]")
157
+ raise typer.Exit(code=1)
158
+
159
+ except ImportError as e:
160
+ console.print(f"[red]Error importing tools: {e}[/red]")
161
+ raise typer.Exit(code=1)
162
+
163
+
164
+ @app.command("view")
165
+ def add_view(
166
+ view_id: Optional[str] = typer.Argument(None, help="View ID (lowercase, hyphens)"),
167
+ description: Optional[str] = typer.Option(
168
+ None, "--description", "-d", help="View description"
169
+ ),
170
+ props_json: Optional[str] = typer.Option(
171
+ None, "--props", "-p", help="Props as JSON string"
172
+ ),
173
+ agent_name: Optional[str] = typer.Option(
174
+ None, "--agent", "-a", help="Agent name/path (defaults to current directory)"
175
+ ),
176
+ non_interactive: bool = typer.Option(
177
+ False, "--yes", "-y", help="Skip interactive prompts"
178
+ ),
179
+ ) -> None:
180
+ """
181
+ Add a new React view to the agent.
182
+
183
+ Example:
184
+ a4e add view results-display -d "Display search results"
185
+ a4e add view # Interactive mode
186
+ """
187
+ agent_dir = find_agent_dir(agent_name)
188
+ if not agent_dir:
189
+ console.print("[red]Error: Not in an agent directory. Use --agent to specify the agent.[/red]")
190
+ raise typer.Exit(code=1)
191
+
192
+ # Interactive prompts
193
+ if not non_interactive:
194
+ if not view_id:
195
+ view_id = Prompt.ask("[bold]View ID[/bold] (lowercase, hyphens)")
196
+
197
+ if not description:
198
+ description = Prompt.ask("[bold]Description[/bold]", default=f"A view for {view_id.replace('-', ' ')}")
199
+
200
+ if not props_json:
201
+ console.print("\n[bold]Define props[/bold] (press Enter with empty name to finish)")
202
+ props = {}
203
+
204
+ while True:
205
+ prop_name = Prompt.ask("Prop name", default="")
206
+ if not prop_name:
207
+ break
208
+
209
+ console.print(" Type options: " + ", ".join(f"[{i+1}]{t}" for i, t in enumerate(TYPE_OPTIONS)))
210
+ type_choice = Prompt.ask(" Type", default="1")
211
+ try:
212
+ prop_type = TYPE_OPTIONS[int(type_choice) - 1]
213
+ except (ValueError, IndexError):
214
+ prop_type = type_choice if type_choice in TYPE_OPTIONS else "string"
215
+
216
+ prop_desc = Prompt.ask(" Description", default=f"The {prop_name} prop")
217
+
218
+ props[prop_name] = {
219
+ "type": prop_type,
220
+ "description": prop_desc,
221
+ }
222
+ console.print(f" [green]✓ Added prop '{prop_name}'[/green]")
223
+ else:
224
+ try:
225
+ props = json.loads(props_json)
226
+ except json.JSONDecodeError:
227
+ console.print("[red]Invalid JSON for props[/red]")
228
+ raise typer.Exit(code=1)
229
+ else:
230
+ if not all([view_id, description]):
231
+ console.print("[red]Error: --yes requires view_id and --description[/red]")
232
+ raise typer.Exit(code=1)
233
+ props = json.loads(props_json) if props_json else {}
234
+
235
+ try:
236
+ from ..tools.views.add_view import add_view as mcp_add_view
237
+
238
+ result = mcp_add_view(
239
+ view_id=view_id,
240
+ description=description,
241
+ props=props,
242
+ agent_name=str(agent_dir),
243
+ )
244
+
245
+ if result.get("success"):
246
+ console.print(f"\n[green]✓ View '{view_id}' created![/green]")
247
+ console.print(f" Path: {result.get('path')}")
248
+ console.print("\n [dim]Customize the React component in view.tsx[/dim]")
249
+ else:
250
+ console.print(f"\n[red]Error: {result.get('error')}[/red]")
251
+ raise typer.Exit(code=1)
252
+
253
+ except ImportError as e:
254
+ console.print(f"[red]Error importing tools: {e}[/red]")
255
+ raise typer.Exit(code=1)
256
+
257
+
258
+ @app.command("skill")
259
+ def add_skill(
260
+ skill_id: Optional[str] = typer.Argument(None, help="Skill ID (lowercase, underscores)"),
261
+ name: Optional[str] = typer.Option(
262
+ None, "--name", "-n", help="Display name for the skill"
263
+ ),
264
+ description: Optional[str] = typer.Option(
265
+ None, "--description", "-d", help="Skill description"
266
+ ),
267
+ intent_triggers: Optional[str] = typer.Option(
268
+ None, "--triggers", "-t", help="Comma-separated list of intent triggers"
269
+ ),
270
+ output_view: Optional[str] = typer.Option(
271
+ None, "--view", "-v", help="Output view ID"
272
+ ),
273
+ internal_tools: Optional[str] = typer.Option(
274
+ None, "--tools", help="Comma-separated list of internal tools"
275
+ ),
276
+ requires_auth: bool = typer.Option(
277
+ False, "--auth", help="Requires authentication"
278
+ ),
279
+ agent_name: Optional[str] = typer.Option(
280
+ None, "--agent", "-a", help="Agent name/path (defaults to current directory)"
281
+ ),
282
+ non_interactive: bool = typer.Option(
283
+ False, "--yes", "-y", help="Skip interactive prompts"
284
+ ),
285
+ ) -> None:
286
+ """
287
+ Add a new skill to the agent.
288
+
289
+ Example:
290
+ a4e add skill show_results --name "Show Results" --view results-display
291
+ a4e add skill # Interactive mode
292
+ """
293
+ agent_dir = find_agent_dir(agent_name)
294
+ if not agent_dir:
295
+ console.print("[red]Error: Not in an agent directory. Use --agent to specify the agent.[/red]")
296
+ raise typer.Exit(code=1)
297
+
298
+ # Interactive prompts
299
+ if not non_interactive:
300
+ if not skill_id:
301
+ skill_id = Prompt.ask("[bold]Skill ID[/bold] (snake_case)")
302
+
303
+ if not name:
304
+ default_name = skill_id.replace("_", " ").title()
305
+ name = Prompt.ask("[bold]Display name[/bold]", default=default_name)
306
+
307
+ if not description:
308
+ description = Prompt.ask("[bold]Description[/bold]", default=f"A skill for {name.lower()}")
309
+
310
+ if not intent_triggers:
311
+ console.print("\n[bold]Enter intent triggers[/bold] (comma-separated phrases)")
312
+ triggers_input = Prompt.ask("Triggers", default=skill_id.replace("_", " "))
313
+ intent_triggers_list = [t.strip() for t in triggers_input.split(",") if t.strip()]
314
+ else:
315
+ intent_triggers_list = [t.strip() for t in intent_triggers.split(",")]
316
+
317
+ if not output_view:
318
+ # List available views
319
+ views_dir = agent_dir / "views"
320
+ available_views = [d.name for d in views_dir.iterdir() if d.is_dir()] if views_dir.exists() else []
321
+
322
+ if available_views:
323
+ console.print("\n[bold]Available views:[/bold]")
324
+ for i, v in enumerate(available_views, 1):
325
+ console.print(f" [{i}] {v}")
326
+
327
+ output_view = Prompt.ask("[bold]Output view[/bold]", default=available_views[0] if available_views else "welcome")
328
+
329
+ if not internal_tools:
330
+ # List available tools
331
+ tools_dir = agent_dir / "tools"
332
+ available_tools = [f.stem for f in tools_dir.glob("*.py") if f.stem != "__init__" and f.stem != "schemas"] if tools_dir.exists() else []
333
+
334
+ if available_tools:
335
+ console.print("\n[bold]Available tools:[/bold]")
336
+ for t in available_tools:
337
+ console.print(f" • {t}")
338
+
339
+ tools_input = Prompt.ask("[bold]Internal tools[/bold] (comma-separated, or empty)", default="")
340
+ internal_tools_list = [t.strip() for t in tools_input.split(",") if t.strip()]
341
+ else:
342
+ internal_tools_list = [t.strip() for t in internal_tools.split(",") if t.strip()]
343
+
344
+ requires_auth = Confirm.ask("Requires authentication?", default=False)
345
+ else:
346
+ if not all([skill_id, name, description, output_view]):
347
+ console.print("[red]Error: --yes requires skill_id, --name, --description, and --view[/red]")
348
+ raise typer.Exit(code=1)
349
+ intent_triggers_list = [t.strip() for t in (intent_triggers or "").split(",")] if intent_triggers else []
350
+ internal_tools_list = [t.strip() for t in (internal_tools or "").split(",")] if internal_tools else []
351
+
352
+ try:
353
+ from ..tools.skills.add_skill import add_skill as mcp_add_skill
354
+
355
+ result = mcp_add_skill(
356
+ skill_id=skill_id,
357
+ name=name,
358
+ description=description,
359
+ intent_triggers=intent_triggers_list,
360
+ output_view=output_view,
361
+ internal_tools=internal_tools_list,
362
+ requires_auth=requires_auth,
363
+ agent_name=str(agent_dir),
364
+ )
365
+
366
+ if result.get("success"):
367
+ console.print(f"\n[green]✓ Skill '{skill_id}' created![/green]")
368
+ console.print(f" Path: {result.get('path')}")
369
+ console.print("\n [dim]Review the generated SKILL.md for documentation.[/dim]")
370
+ else:
371
+ console.print(f"\n[red]Error: {result.get('error')}[/red]")
372
+ raise typer.Exit(code=1)
373
+
374
+ except ImportError as e:
375
+ console.print(f"[red]Error importing tools: {e}[/red]")
376
+ raise typer.Exit(code=1)
@@ -0,0 +1,149 @@
1
+ # a4e/cli_commands/deploy.py
2
+ """
3
+ Command for deploying an agent to production.
4
+ """
5
+
6
+ import typer
7
+ from pathlib import Path
8
+ from typing import Optional
9
+
10
+ from rich.console import Console
11
+ from rich.panel import Panel
12
+ from rich.prompt import Confirm
13
+ from rich.progress import Progress, SpinnerColumn, TextColumn
14
+
15
+ console = Console()
16
+
17
+ # Create a 'Typer' app for the 'deploy' command
18
+ app = typer.Typer(
19
+ no_args_is_help=False,
20
+ help="Deploy your agent to production.",
21
+ )
22
+
23
+
24
+ def find_agent_dir(agent_name: Optional[str] = None) -> Optional[Path]:
25
+ """Find the agent directory from name or current working directory."""
26
+ cwd = Path.cwd()
27
+
28
+ if agent_name:
29
+ if Path(agent_name).is_absolute() and Path(agent_name).exists():
30
+ return Path(agent_name)
31
+ if (cwd / agent_name).exists():
32
+ return cwd / agent_name
33
+ agent_store = cwd / "file-store" / "agent-store" / agent_name
34
+ if agent_store.exists():
35
+ return agent_store
36
+ return None
37
+
38
+ if (cwd / "agent.py").exists() and (cwd / "metadata.json").exists():
39
+ return cwd
40
+
41
+ return None
42
+
43
+
44
+ @app.callback(invoke_without_command=True)
45
+ def deploy(
46
+ ctx: typer.Context,
47
+ agent_name: Optional[str] = typer.Option(
48
+ None, "--agent", "-a", help="Agent name/path (defaults to current directory)"
49
+ ),
50
+ skip_validation: bool = typer.Option(
51
+ False, "--skip-validation", help="Skip pre-deployment validation"
52
+ ),
53
+ yes: bool = typer.Option(
54
+ False, "--yes", "-y", help="Skip confirmation prompt"
55
+ ),
56
+ ) -> None:
57
+ """
58
+ Deploy the agent to production.
59
+
60
+ This command will:
61
+ 1. Validate the agent (unless --skip-validation)
62
+ 2. Upload the agent to the A4E Hub
63
+ 3. Return the deployment URL
64
+
65
+ Example:
66
+ a4e deploy
67
+ a4e deploy --agent my-agent --yes
68
+ """
69
+ agent_dir = find_agent_dir(agent_name)
70
+ if not agent_dir:
71
+ console.print("[red]Error: Not in an agent directory. Use --agent to specify the agent.[/red]")
72
+ raise typer.Exit(code=1)
73
+
74
+ console.print(Panel.fit(
75
+ f"[bold]Deploying agent:[/bold] {agent_dir.name}",
76
+ border_style="blue"
77
+ ))
78
+
79
+ # Run validation first (unless skipped)
80
+ if not skip_validation:
81
+ console.print("\n[bold]Step 1: Validation[/bold]")
82
+ try:
83
+ from ..tools.validation.validate import validate as mcp_validate
84
+
85
+ result = mcp_validate(agent_name=str(agent_dir))
86
+
87
+ errors = result.get("errors", [])
88
+ warnings = result.get("warnings", [])
89
+
90
+ if errors:
91
+ console.print(f"[red] ✗ Validation failed with {len(errors)} error(s)[/red]")
92
+ for error in errors:
93
+ console.print(f" [red]•[/red] {error}")
94
+ console.print("\n[yellow]Fix errors before deploying, or use --skip-validation to bypass.[/yellow]")
95
+ raise typer.Exit(code=1)
96
+ elif warnings:
97
+ console.print(f"[green] ✓ Validation passed with {len(warnings)} warning(s)[/green]")
98
+ else:
99
+ console.print("[green] ✓ Validation passed[/green]")
100
+
101
+ except ImportError as e:
102
+ console.print(f"[red]Error importing validation: {e}[/red]")
103
+ raise typer.Exit(code=1)
104
+
105
+ # Confirm deployment
106
+ if not yes:
107
+ console.print("")
108
+ if not Confirm.ask("Proceed with deployment?", default=True):
109
+ console.print("[yellow]Deployment cancelled.[/yellow]")
110
+ raise typer.Exit(code=0)
111
+
112
+ # Deploy
113
+ console.print("\n[bold]Step 2: Deployment[/bold]")
114
+
115
+ try:
116
+ from ..tools.deploy.deploy import deploy as mcp_deploy
117
+
118
+ with Progress(
119
+ SpinnerColumn(),
120
+ TextColumn("[progress.description]{task.description}"),
121
+ console=console,
122
+ ) as progress:
123
+ task = progress.add_task("Deploying to A4E Hub...", total=None)
124
+
125
+ result = mcp_deploy(agent_name=str(agent_dir))
126
+
127
+ progress.update(task, completed=True)
128
+
129
+ if result.get("success"):
130
+ console.print("[green] ✓ Agent ready for deployment![/green]")
131
+
132
+ # Show next steps
133
+ next_steps = result.get("next_steps", [])
134
+ if next_steps:
135
+ console.print("\n[bold]Next Steps:[/bold]")
136
+ for i, step in enumerate(next_steps, 1):
137
+ console.print(f" {i}. {step}")
138
+
139
+ console.print("\n[dim]Run 'a4e dev start' to test locally with the playground.[/dim]")
140
+ else:
141
+ console.print(f"[red] ✗ Deployment failed: {result.get('error')}[/red]")
142
+ raise typer.Exit(code=1)
143
+
144
+ except ImportError as e:
145
+ console.print(f"[red]Error importing deploy: {e}[/red]")
146
+ raise typer.Exit(code=1)
147
+ except Exception as e:
148
+ console.print(f"[red]Error during deployment: {e}[/red]")
149
+ raise typer.Exit(code=1)