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
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
# a4e/cli_commands/update.py
|
|
2
|
+
"""
|
|
3
|
+
Commands for updating tools, views, and skills in 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
|
+
|
|
14
|
+
console = Console()
|
|
15
|
+
|
|
16
|
+
# Create a 'Typer' app for the 'update' command group
|
|
17
|
+
app = typer.Typer(
|
|
18
|
+
no_args_is_help=True,
|
|
19
|
+
help="Update existing tools, views, or skills.",
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
TYPE_OPTIONS = ["string", "number", "integer", "boolean", "array", "object"]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def find_agent_dir(agent_name: Optional[str] = None) -> Optional[Path]:
|
|
26
|
+
"""Find the agent directory from name or current working directory."""
|
|
27
|
+
cwd = Path.cwd()
|
|
28
|
+
|
|
29
|
+
if agent_name:
|
|
30
|
+
if Path(agent_name).is_absolute() and Path(agent_name).exists():
|
|
31
|
+
return Path(agent_name)
|
|
32
|
+
if (cwd / agent_name).exists():
|
|
33
|
+
return cwd / agent_name
|
|
34
|
+
agent_store = cwd / "file-store" / "agent-store" / agent_name
|
|
35
|
+
if agent_store.exists():
|
|
36
|
+
return agent_store
|
|
37
|
+
return None
|
|
38
|
+
|
|
39
|
+
if (cwd / "agent.py").exists() and (cwd / "metadata.json").exists():
|
|
40
|
+
return cwd
|
|
41
|
+
|
|
42
|
+
return None
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@app.command("tool")
|
|
46
|
+
def update_tool(
|
|
47
|
+
tool_name: Optional[str] = typer.Argument(None, help="Name of the tool to update"),
|
|
48
|
+
description: Optional[str] = typer.Option(
|
|
49
|
+
None, "--description", "-d", help="New description"
|
|
50
|
+
),
|
|
51
|
+
parameters_json: Optional[str] = typer.Option(
|
|
52
|
+
None, "--parameters", "-p", help="New parameters as JSON string"
|
|
53
|
+
),
|
|
54
|
+
agent_name: Optional[str] = typer.Option(
|
|
55
|
+
None, "--agent", "-a", help="Agent name/path"
|
|
56
|
+
),
|
|
57
|
+
) -> None:
|
|
58
|
+
"""
|
|
59
|
+
Update an existing tool's description or parameters.
|
|
60
|
+
|
|
61
|
+
Example:
|
|
62
|
+
a4e update tool calculate_bmi -d "New description"
|
|
63
|
+
a4e update tool calculate_bmi -p '{"weight": "number", "height": "number"}'
|
|
64
|
+
"""
|
|
65
|
+
agent_dir = find_agent_dir(agent_name)
|
|
66
|
+
if not agent_dir:
|
|
67
|
+
console.print("[red]Error: Not in an agent directory. Use --agent to specify the agent.[/red]")
|
|
68
|
+
raise typer.Exit(code=1)
|
|
69
|
+
|
|
70
|
+
# List available tools if none specified
|
|
71
|
+
if not tool_name:
|
|
72
|
+
tools_dir = agent_dir / "tools"
|
|
73
|
+
if tools_dir.exists():
|
|
74
|
+
available = [f.stem for f in tools_dir.glob("*.py") if f.stem != "__init__"]
|
|
75
|
+
if available:
|
|
76
|
+
console.print("[bold]Available tools:[/bold]")
|
|
77
|
+
for t in available:
|
|
78
|
+
console.print(f" • {t}")
|
|
79
|
+
tool_name = Prompt.ask("[bold]Tool to update[/bold]")
|
|
80
|
+
else:
|
|
81
|
+
console.print("[red]No tools directory found[/red]")
|
|
82
|
+
raise typer.Exit(code=1)
|
|
83
|
+
|
|
84
|
+
if not description and not parameters_json:
|
|
85
|
+
console.print("[yellow]What would you like to update?[/yellow]")
|
|
86
|
+
update_desc = Confirm.ask("Update description?", default=False)
|
|
87
|
+
if update_desc:
|
|
88
|
+
description = Prompt.ask("New description")
|
|
89
|
+
|
|
90
|
+
update_params = Confirm.ask("Update parameters?", default=False)
|
|
91
|
+
if update_params:
|
|
92
|
+
console.print("[dim]Enter parameters as JSON or use interactive mode[/dim]")
|
|
93
|
+
parameters_json = Prompt.ask("Parameters JSON", default="{}")
|
|
94
|
+
|
|
95
|
+
parameters = None
|
|
96
|
+
if parameters_json:
|
|
97
|
+
try:
|
|
98
|
+
parameters = json.loads(parameters_json)
|
|
99
|
+
except json.JSONDecodeError:
|
|
100
|
+
console.print("[red]Invalid JSON for parameters[/red]")
|
|
101
|
+
raise typer.Exit(code=1)
|
|
102
|
+
|
|
103
|
+
try:
|
|
104
|
+
from ..tools.agent_tools.update_tool import update_tool as mcp_update_tool
|
|
105
|
+
|
|
106
|
+
result = mcp_update_tool(
|
|
107
|
+
tool_name=tool_name,
|
|
108
|
+
description=description,
|
|
109
|
+
parameters=parameters,
|
|
110
|
+
agent_name=str(agent_dir),
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
if result.get("success"):
|
|
114
|
+
console.print(f"\n[green]✓ Tool '{tool_name}' updated![/green]")
|
|
115
|
+
console.print(f" Path: {result.get('path')}")
|
|
116
|
+
else:
|
|
117
|
+
console.print(f"\n[red]Error: {result.get('error')}[/red]")
|
|
118
|
+
if result.get("fix"):
|
|
119
|
+
console.print(f" [yellow]Fix: {result.get('fix')}[/yellow]")
|
|
120
|
+
raise typer.Exit(code=1)
|
|
121
|
+
|
|
122
|
+
except ImportError as e:
|
|
123
|
+
console.print(f"[red]Error importing tools: {e}[/red]")
|
|
124
|
+
raise typer.Exit(code=1)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@app.command("view")
|
|
128
|
+
def update_view(
|
|
129
|
+
view_id: Optional[str] = typer.Argument(None, help="ID of the view to update"),
|
|
130
|
+
description: Optional[str] = typer.Option(
|
|
131
|
+
None, "--description", "-d", help="New description"
|
|
132
|
+
),
|
|
133
|
+
props_json: Optional[str] = typer.Option(
|
|
134
|
+
None, "--props", "-p", help="New props as JSON string"
|
|
135
|
+
),
|
|
136
|
+
agent_name: Optional[str] = typer.Option(
|
|
137
|
+
None, "--agent", "-a", help="Agent name/path"
|
|
138
|
+
),
|
|
139
|
+
) -> None:
|
|
140
|
+
"""
|
|
141
|
+
Update an existing view's description or props.
|
|
142
|
+
|
|
143
|
+
Example:
|
|
144
|
+
a4e update view dashboard -d "New description"
|
|
145
|
+
a4e update view dashboard -p '{"title": "string", "items": "array"}'
|
|
146
|
+
"""
|
|
147
|
+
agent_dir = find_agent_dir(agent_name)
|
|
148
|
+
if not agent_dir:
|
|
149
|
+
console.print("[red]Error: Not in an agent directory. Use --agent to specify the agent.[/red]")
|
|
150
|
+
raise typer.Exit(code=1)
|
|
151
|
+
|
|
152
|
+
# List available views if none specified
|
|
153
|
+
if not view_id:
|
|
154
|
+
views_dir = agent_dir / "views"
|
|
155
|
+
if views_dir.exists():
|
|
156
|
+
available = [d.name for d in views_dir.iterdir() if d.is_dir() and not d.name.startswith("_")]
|
|
157
|
+
if available:
|
|
158
|
+
console.print("[bold]Available views:[/bold]")
|
|
159
|
+
for v in available:
|
|
160
|
+
console.print(f" • {v}")
|
|
161
|
+
view_id = Prompt.ask("[bold]View to update[/bold]")
|
|
162
|
+
else:
|
|
163
|
+
console.print("[red]No views directory found[/red]")
|
|
164
|
+
raise typer.Exit(code=1)
|
|
165
|
+
|
|
166
|
+
if not description and not props_json:
|
|
167
|
+
console.print("[yellow]What would you like to update?[/yellow]")
|
|
168
|
+
update_desc = Confirm.ask("Update description?", default=False)
|
|
169
|
+
if update_desc:
|
|
170
|
+
description = Prompt.ask("New description")
|
|
171
|
+
|
|
172
|
+
update_props = Confirm.ask("Update props?", default=False)
|
|
173
|
+
if update_props:
|
|
174
|
+
props_json = Prompt.ask("Props JSON", default="{}")
|
|
175
|
+
|
|
176
|
+
props = None
|
|
177
|
+
if props_json:
|
|
178
|
+
try:
|
|
179
|
+
props = json.loads(props_json)
|
|
180
|
+
except json.JSONDecodeError:
|
|
181
|
+
console.print("[red]Invalid JSON for props[/red]")
|
|
182
|
+
raise typer.Exit(code=1)
|
|
183
|
+
|
|
184
|
+
try:
|
|
185
|
+
from ..tools.views.update_view import update_view as mcp_update_view
|
|
186
|
+
|
|
187
|
+
result = mcp_update_view(
|
|
188
|
+
view_id=view_id,
|
|
189
|
+
description=description,
|
|
190
|
+
props=props,
|
|
191
|
+
agent_name=str(agent_dir),
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
if result.get("success"):
|
|
195
|
+
console.print(f"\n[green]✓ View '{view_id}' updated![/green]")
|
|
196
|
+
console.print(f" Path: {result.get('path')}")
|
|
197
|
+
else:
|
|
198
|
+
console.print(f"\n[red]Error: {result.get('error')}[/red]")
|
|
199
|
+
if result.get("fix"):
|
|
200
|
+
console.print(f" [yellow]Fix: {result.get('fix')}[/yellow]")
|
|
201
|
+
raise typer.Exit(code=1)
|
|
202
|
+
|
|
203
|
+
except ImportError as e:
|
|
204
|
+
console.print(f"[red]Error importing tools: {e}[/red]")
|
|
205
|
+
raise typer.Exit(code=1)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
@app.command("skill")
|
|
209
|
+
def update_skill(
|
|
210
|
+
skill_id: Optional[str] = typer.Argument(None, help="ID of the skill to update"),
|
|
211
|
+
name: Optional[str] = typer.Option(None, "--name", "-n", help="New display name"),
|
|
212
|
+
description: Optional[str] = typer.Option(None, "--description", "-d", help="New description"),
|
|
213
|
+
intent_triggers: Optional[str] = typer.Option(
|
|
214
|
+
None, "--triggers", "-t", help="New comma-separated triggers"
|
|
215
|
+
),
|
|
216
|
+
output_view: Optional[str] = typer.Option(None, "--view", "-v", help="New output view"),
|
|
217
|
+
internal_tools: Optional[str] = typer.Option(
|
|
218
|
+
None, "--tools", help="New comma-separated internal tools"
|
|
219
|
+
),
|
|
220
|
+
requires_auth: Optional[bool] = typer.Option(None, "--auth/--no-auth", help="Requires auth"),
|
|
221
|
+
agent_name: Optional[str] = typer.Option(None, "--agent", "-a", help="Agent name/path"),
|
|
222
|
+
) -> None:
|
|
223
|
+
"""
|
|
224
|
+
Update an existing skill's properties.
|
|
225
|
+
|
|
226
|
+
Example:
|
|
227
|
+
a4e update skill show_results --name "Display Results" --view new-results
|
|
228
|
+
"""
|
|
229
|
+
agent_dir = find_agent_dir(agent_name)
|
|
230
|
+
if not agent_dir:
|
|
231
|
+
console.print("[red]Error: Not in an agent directory. Use --agent to specify the agent.[/red]")
|
|
232
|
+
raise typer.Exit(code=1)
|
|
233
|
+
|
|
234
|
+
# List available skills if none specified
|
|
235
|
+
if not skill_id:
|
|
236
|
+
skills_file = agent_dir / "skills" / "schemas.json"
|
|
237
|
+
if skills_file.exists():
|
|
238
|
+
try:
|
|
239
|
+
schemas = json.loads(skills_file.read_text())
|
|
240
|
+
available = list(schemas.keys())
|
|
241
|
+
if available:
|
|
242
|
+
console.print("[bold]Available skills:[/bold]")
|
|
243
|
+
for s in available:
|
|
244
|
+
console.print(f" • {s}")
|
|
245
|
+
skill_id = Prompt.ask("[bold]Skill to update[/bold]")
|
|
246
|
+
except json.JSONDecodeError:
|
|
247
|
+
console.print("[red]Could not parse skills/schemas.json[/red]")
|
|
248
|
+
raise typer.Exit(code=1)
|
|
249
|
+
else:
|
|
250
|
+
console.print("[red]No skills/schemas.json found[/red]")
|
|
251
|
+
raise typer.Exit(code=1)
|
|
252
|
+
|
|
253
|
+
# Parse list options
|
|
254
|
+
triggers_list = [t.strip() for t in intent_triggers.split(",")] if intent_triggers else None
|
|
255
|
+
tools_list = [t.strip() for t in internal_tools.split(",") if t.strip()] if internal_tools else None
|
|
256
|
+
|
|
257
|
+
try:
|
|
258
|
+
from ..tools.skills.update_skill import update_skill as mcp_update_skill
|
|
259
|
+
|
|
260
|
+
result = mcp_update_skill(
|
|
261
|
+
skill_id=skill_id,
|
|
262
|
+
name=name,
|
|
263
|
+
description=description,
|
|
264
|
+
intent_triggers=triggers_list,
|
|
265
|
+
output_view=output_view,
|
|
266
|
+
internal_tools=tools_list,
|
|
267
|
+
requires_auth=requires_auth,
|
|
268
|
+
agent_name=str(agent_dir),
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
if result.get("success"):
|
|
272
|
+
console.print(f"\n[green]✓ Skill '{skill_id}' updated![/green]")
|
|
273
|
+
console.print(f" Path: {result.get('path')}")
|
|
274
|
+
if result.get("warnings"):
|
|
275
|
+
for w in result["warnings"]:
|
|
276
|
+
console.print(f" [yellow]Warning: {w}[/yellow]")
|
|
277
|
+
else:
|
|
278
|
+
console.print(f"\n[red]Error: {result.get('error')}[/red]")
|
|
279
|
+
if result.get("fix"):
|
|
280
|
+
console.print(f" [yellow]Fix: {result.get('fix')}[/yellow]")
|
|
281
|
+
raise typer.Exit(code=1)
|
|
282
|
+
|
|
283
|
+
except ImportError as e:
|
|
284
|
+
console.print(f"[red]Error importing tools: {e}[/red]")
|
|
285
|
+
raise typer.Exit(code=1)
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# a4e/cli_commands/validate.py
|
|
2
|
+
"""
|
|
3
|
+
Command for validating an agent project.
|
|
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.table import Table
|
|
12
|
+
from rich.panel import Panel
|
|
13
|
+
|
|
14
|
+
console = Console()
|
|
15
|
+
|
|
16
|
+
# Create a 'Typer' app for the 'validate' command
|
|
17
|
+
app = typer.Typer(
|
|
18
|
+
no_args_is_help=False,
|
|
19
|
+
help="Validate your agent project for errors and warnings.",
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def find_agent_dir(agent_name: Optional[str] = None) -> Optional[Path]:
|
|
24
|
+
"""Find the agent directory from name or current working directory."""
|
|
25
|
+
cwd = Path.cwd()
|
|
26
|
+
|
|
27
|
+
if agent_name:
|
|
28
|
+
if Path(agent_name).is_absolute() and Path(agent_name).exists():
|
|
29
|
+
return Path(agent_name)
|
|
30
|
+
if (cwd / agent_name).exists():
|
|
31
|
+
return cwd / agent_name
|
|
32
|
+
agent_store = cwd / "file-store" / "agent-store" / agent_name
|
|
33
|
+
if agent_store.exists():
|
|
34
|
+
return agent_store
|
|
35
|
+
return None
|
|
36
|
+
|
|
37
|
+
if (cwd / "agent.py").exists() and (cwd / "metadata.json").exists():
|
|
38
|
+
return cwd
|
|
39
|
+
|
|
40
|
+
return None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@app.callback(invoke_without_command=True)
|
|
44
|
+
def validate(
|
|
45
|
+
ctx: typer.Context,
|
|
46
|
+
agent_name: Optional[str] = typer.Option(
|
|
47
|
+
None, "--agent", "-a", help="Agent name/path (defaults to current directory)"
|
|
48
|
+
),
|
|
49
|
+
strict: bool = typer.Option(
|
|
50
|
+
False, "--strict", "-s", help="Treat warnings as errors"
|
|
51
|
+
),
|
|
52
|
+
) -> None:
|
|
53
|
+
"""
|
|
54
|
+
Validate the agent project structure and code.
|
|
55
|
+
|
|
56
|
+
Checks for:
|
|
57
|
+
- Required files (agent.py, metadata.json, prompts/agent.md, etc.)
|
|
58
|
+
- Python syntax errors
|
|
59
|
+
- Type hints on public functions
|
|
60
|
+
- Schema existence
|
|
61
|
+
- Skill dependencies (views, tools)
|
|
62
|
+
|
|
63
|
+
Example:
|
|
64
|
+
a4e validate
|
|
65
|
+
a4e validate --agent my-agent --strict
|
|
66
|
+
"""
|
|
67
|
+
agent_dir = find_agent_dir(agent_name)
|
|
68
|
+
if not agent_dir:
|
|
69
|
+
console.print("[red]Error: Not in an agent directory. Use --agent to specify the agent.[/red]")
|
|
70
|
+
raise typer.Exit(code=1)
|
|
71
|
+
|
|
72
|
+
console.print(Panel.fit(
|
|
73
|
+
f"[bold]Validating agent:[/bold] {agent_dir.name}",
|
|
74
|
+
border_style="blue"
|
|
75
|
+
))
|
|
76
|
+
|
|
77
|
+
try:
|
|
78
|
+
from ..tools.validation.validate import validate as mcp_validate
|
|
79
|
+
|
|
80
|
+
result = mcp_validate(agent_name=str(agent_dir))
|
|
81
|
+
|
|
82
|
+
if result.get("success"):
|
|
83
|
+
errors = result.get("errors", [])
|
|
84
|
+
warnings = result.get("warnings", [])
|
|
85
|
+
|
|
86
|
+
# Show errors
|
|
87
|
+
if errors:
|
|
88
|
+
console.print(f"\n[red bold]Errors ({len(errors)}):[/red bold]")
|
|
89
|
+
for error in errors:
|
|
90
|
+
console.print(f" [red]✗[/red] {error}")
|
|
91
|
+
|
|
92
|
+
# Show warnings
|
|
93
|
+
if warnings:
|
|
94
|
+
console.print(f"\n[yellow bold]Warnings ({len(warnings)}):[/yellow bold]")
|
|
95
|
+
for warning in warnings:
|
|
96
|
+
console.print(f" [yellow]⚠[/yellow] {warning}")
|
|
97
|
+
|
|
98
|
+
# Summary
|
|
99
|
+
console.print("")
|
|
100
|
+
if errors:
|
|
101
|
+
console.print(f"[red bold]Validation failed with {len(errors)} error(s)[/red bold]")
|
|
102
|
+
raise typer.Exit(code=1)
|
|
103
|
+
elif warnings and strict:
|
|
104
|
+
console.print(f"[yellow bold]Validation failed with {len(warnings)} warning(s) (strict mode)[/yellow bold]")
|
|
105
|
+
raise typer.Exit(code=1)
|
|
106
|
+
elif warnings:
|
|
107
|
+
console.print(f"[green]✓ Validation passed with {len(warnings)} warning(s)[/green]")
|
|
108
|
+
else:
|
|
109
|
+
console.print("[green bold]✓ Validation passed - no issues found![/green bold]")
|
|
110
|
+
|
|
111
|
+
else:
|
|
112
|
+
console.print(f"[red]Error: {result.get('error')}[/red]")
|
|
113
|
+
raise typer.Exit(code=1)
|
|
114
|
+
|
|
115
|
+
except ImportError as e:
|
|
116
|
+
console.print(f"[red]Error importing tools: {e}[/red]")
|
|
117
|
+
raise typer.Exit(code=1)
|
a4e/core.py
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Core module with shared utilities and MCP instance.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from mcp.server.fastmcp import FastMCP
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Optional
|
|
8
|
+
import os
|
|
9
|
+
import re
|
|
10
|
+
from jinja2 import Environment, FileSystemLoader
|
|
11
|
+
|
|
12
|
+
# Global project directory (set by CLI args)
|
|
13
|
+
_PROJECT_DIR: Optional[Path] = None
|
|
14
|
+
|
|
15
|
+
# Initialize MCP server
|
|
16
|
+
mcp = FastMCP(name="a4e-agent-creator")
|
|
17
|
+
|
|
18
|
+
# Load templates
|
|
19
|
+
template_dir = Path(__file__).parent / "templates"
|
|
20
|
+
jinja_env = Environment(
|
|
21
|
+
loader=FileSystemLoader(template_dir),
|
|
22
|
+
autoescape=False, # Explicit for code generation
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def set_project_dir(path: Path) -> None:
|
|
27
|
+
"""Set the global project directory."""
|
|
28
|
+
global _PROJECT_DIR
|
|
29
|
+
_PROJECT_DIR = path
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def get_configured_project_dir() -> Optional[Path]:
|
|
33
|
+
"""Get the configured project directory."""
|
|
34
|
+
return _PROJECT_DIR
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def sanitize_input(value: str, allowed_chars: str = r"a-zA-Z0-9_-") -> str:
|
|
38
|
+
"""
|
|
39
|
+
Sanitize user input to prevent template injection.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
value: Input string to sanitize
|
|
43
|
+
allowed_chars: Regex character class of allowed characters
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
Sanitized string with only allowed characters
|
|
47
|
+
"""
|
|
48
|
+
pattern = f"[^{allowed_chars}]"
|
|
49
|
+
return re.sub(pattern, "", value)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def get_project_dir(agent_name: Optional[str] = None) -> Path:
|
|
53
|
+
"""
|
|
54
|
+
Resolve the agent project directory.
|
|
55
|
+
|
|
56
|
+
Priority (highest to lowest):
|
|
57
|
+
1. --project-dir CLI arg (explicit override)
|
|
58
|
+
2. A4E_WORKSPACE env var (set by editor via ${workspaceFolder})
|
|
59
|
+
3. Path.cwd() (fallback)
|
|
60
|
+
|
|
61
|
+
Note: Tools should prefer using explicit project_path parameter when available,
|
|
62
|
+
as this function relies on environment context that may not be available
|
|
63
|
+
in all MCP clients.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
agent_name: Optional agent ID to resolve path for
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
Path to agent directory or project root
|
|
70
|
+
|
|
71
|
+
Raises:
|
|
72
|
+
ValueError: If agent creation attempted in invalid location
|
|
73
|
+
"""
|
|
74
|
+
global _PROJECT_DIR
|
|
75
|
+
|
|
76
|
+
root = None
|
|
77
|
+
workspace_env = os.environ.get("A4E_WORKSPACE", "")
|
|
78
|
+
|
|
79
|
+
# Priority 1: Explicit CLI override
|
|
80
|
+
if _PROJECT_DIR:
|
|
81
|
+
root = _PROJECT_DIR
|
|
82
|
+
# Priority 2: Workspace from editor (if properly expanded)
|
|
83
|
+
elif workspace_env and "${" not in workspace_env:
|
|
84
|
+
# Only use if the variable was expanded and path exists
|
|
85
|
+
workspace_path = Path(workspace_env)
|
|
86
|
+
if workspace_path.exists() and workspace_path.is_dir():
|
|
87
|
+
root = workspace_path.resolve()
|
|
88
|
+
|
|
89
|
+
# Priority 3: Fallback to cwd
|
|
90
|
+
if root is None:
|
|
91
|
+
root = Path.cwd()
|
|
92
|
+
|
|
93
|
+
if not agent_name:
|
|
94
|
+
return root
|
|
95
|
+
|
|
96
|
+
# Safety: Warn if creating in HOME directory without explicit path
|
|
97
|
+
if root == Path.home():
|
|
98
|
+
raise ValueError(
|
|
99
|
+
f"Cannot create agent in HOME directory.\n"
|
|
100
|
+
f"\n"
|
|
101
|
+
f"Please specify project_path in your tool call:\n"
|
|
102
|
+
f' initialize_project(name="{agent_name}", project_path="/path/to/your/project", ...)\n'
|
|
103
|
+
f"\n"
|
|
104
|
+
f"The agent will be created at: {{project_path}}/{agent_name}/"
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
# Create agent directly in the workspace root
|
|
108
|
+
return root / agent_name
|
|
109
|
+
|