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.
- a4e/__init__.py +0 -0
- a4e/cli.py +47 -0
- a4e/cli_commands/__init__.py +5 -0
- a4e/cli_commands/add.py +376 -0
- a4e/cli_commands/deploy.py +149 -0
- a4e/cli_commands/dev.py +162 -0
- a4e/cli_commands/info.py +206 -0
- a4e/cli_commands/init.py +211 -0
- a4e/cli_commands/list.py +227 -0
- a4e/cli_commands/mcp.py +504 -0
- a4e/cli_commands/remove.py +197 -0
- a4e/cli_commands/update.py +285 -0
- a4e/cli_commands/validate.py +117 -0
- a4e/core.py +109 -0
- a4e/dev_runner.py +425 -0
- a4e/server.py +86 -0
- a4e/templates/agent.md.j2 +168 -0
- a4e/templates/agent.py.j2 +15 -0
- a4e/templates/agents.md.j2 +99 -0
- a4e/templates/metadata.json.j2 +20 -0
- a4e/templates/prompt.md.j2 +20 -0
- a4e/templates/prompts/agent.md.j2 +206 -0
- a4e/templates/skills/agents.md.j2 +110 -0
- a4e/templates/skills/skill.md.j2 +120 -0
- a4e/templates/support_module.py.j2 +84 -0
- a4e/templates/tool.py.j2 +60 -0
- a4e/templates/tools/agent.md.j2 +192 -0
- a4e/templates/view.tsx.j2 +21 -0
- a4e/templates/views/agent.md.j2 +219 -0
- a4e/tools/__init__.py +70 -0
- a4e/tools/agent_tools/__init__.py +12 -0
- a4e/tools/agent_tools/add_support_module.py +95 -0
- a4e/tools/agent_tools/add_tool.py +115 -0
- a4e/tools/agent_tools/list_tools.py +28 -0
- a4e/tools/agent_tools/remove_tool.py +69 -0
- a4e/tools/agent_tools/update_tool.py +123 -0
- a4e/tools/deploy/__init__.py +8 -0
- a4e/tools/deploy/deploy.py +59 -0
- a4e/tools/dev/__init__.py +10 -0
- a4e/tools/dev/check_environment.py +79 -0
- a4e/tools/dev/dev_start.py +30 -0
- a4e/tools/dev/dev_stop.py +26 -0
- a4e/tools/project/__init__.py +10 -0
- a4e/tools/project/get_agent_info.py +66 -0
- a4e/tools/project/get_instructions.py +216 -0
- a4e/tools/project/initialize_project.py +231 -0
- a4e/tools/schemas/__init__.py +8 -0
- a4e/tools/schemas/generate_schemas.py +278 -0
- a4e/tools/skills/__init__.py +12 -0
- a4e/tools/skills/add_skill.py +105 -0
- a4e/tools/skills/helpers.py +137 -0
- a4e/tools/skills/list_skills.py +54 -0
- a4e/tools/skills/remove_skill.py +74 -0
- a4e/tools/skills/update_skill.py +150 -0
- a4e/tools/validation/__init__.py +8 -0
- a4e/tools/validation/validate.py +389 -0
- a4e/tools/views/__init__.py +12 -0
- a4e/tools/views/add_view.py +40 -0
- a4e/tools/views/helpers.py +91 -0
- a4e/tools/views/list_views.py +27 -0
- a4e/tools/views/remove_view.py +73 -0
- a4e/tools/views/update_view.py +124 -0
- a4e/utils/dev_manager.py +253 -0
- a4e/utils/schema_generator.py +255 -0
- a4e-0.1.5.dist-info/METADATA +427 -0
- a4e-0.1.5.dist-info/RECORD +70 -0
- a4e-0.1.5.dist-info/WHEEL +5 -0
- a4e-0.1.5.dist-info/entry_points.txt +2 -0
- a4e-0.1.5.dist-info/licenses/LICENSE +21 -0
- 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()
|
a4e/cli_commands/add.py
ADDED
|
@@ -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)
|