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,115 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Add tool tool.
|
|
3
|
+
|
|
4
|
+
Creates tools with the params: Dict[str, Any] pattern required by the A4E main application.
|
|
5
|
+
Also includes exec() compatibility helpers for proper module loading.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
from ...core import mcp, jinja_env, get_project_dir
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@mcp.tool()
|
|
14
|
+
def add_tool(
|
|
15
|
+
tool_name: str, description: str, parameters: dict, agent_name: Optional[str] = None
|
|
16
|
+
) -> dict:
|
|
17
|
+
"""
|
|
18
|
+
Add a new tool with params: Dict[str, Any] signature (A4E compatible)
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
tool_name: Name of the tool (snake_case)
|
|
22
|
+
description: What the tool does
|
|
23
|
+
parameters: Dictionary of parameters with types and descriptions
|
|
24
|
+
agent_name: Optional agent ID if not in agent directory
|
|
25
|
+
|
|
26
|
+
The generated tool will use the params dict pattern:
|
|
27
|
+
def tool_name(params: Dict[str, Any]) -> Dict[str, Any]
|
|
28
|
+
|
|
29
|
+
This is required for compatibility with the A4E main application's
|
|
30
|
+
exec() context and tool wrapping system.
|
|
31
|
+
"""
|
|
32
|
+
# Validate tool name
|
|
33
|
+
if not tool_name.replace("_", "").isalnum():
|
|
34
|
+
suggested = tool_name.replace("-", "_").replace(" ", "_").lower()
|
|
35
|
+
return {
|
|
36
|
+
"success": False,
|
|
37
|
+
"error": "Tool name must be alphanumeric with underscores",
|
|
38
|
+
"fix": f"Try: {suggested}",
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
project_dir = get_project_dir(agent_name)
|
|
42
|
+
tools_dir = project_dir / "tools"
|
|
43
|
+
|
|
44
|
+
if not tools_dir.exists():
|
|
45
|
+
return {
|
|
46
|
+
"success": False,
|
|
47
|
+
"error": f"tools/ directory not found at {tools_dir}",
|
|
48
|
+
"fix": "Initialize an agent first with initialize_project() or specify agent_name",
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
tool_file = tools_dir / f"{tool_name}.py"
|
|
52
|
+
if tool_file.exists():
|
|
53
|
+
return {
|
|
54
|
+
"success": False,
|
|
55
|
+
"error": f"Tool '{tool_name}' already exists",
|
|
56
|
+
"fix": "Use update_tool() to modify or remove_tool() first",
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
try:
|
|
60
|
+
# Prepare parameters with Python types
|
|
61
|
+
mapped_params = {}
|
|
62
|
+
type_mapping = {
|
|
63
|
+
"string": "str",
|
|
64
|
+
"number": "float",
|
|
65
|
+
"integer": "int",
|
|
66
|
+
"boolean": "bool",
|
|
67
|
+
"array": "List",
|
|
68
|
+
"object": "dict",
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
for name, info in parameters.items():
|
|
72
|
+
# Handle both simple format {'a': 'number'} and detailed format {'a': {'type': 'number', 'description': '...'}}
|
|
73
|
+
if isinstance(info, str):
|
|
74
|
+
param_info = {"type": info}
|
|
75
|
+
else:
|
|
76
|
+
param_info = info.copy()
|
|
77
|
+
raw_type = param_info.get("type", "Any")
|
|
78
|
+
|
|
79
|
+
# Map JSON type to Python type, or use as-is if not in map (allows direct Python types)
|
|
80
|
+
py_type = type_mapping.get(raw_type, raw_type)
|
|
81
|
+
|
|
82
|
+
# Handle optional parameters
|
|
83
|
+
# Check for "required" key (boolean)
|
|
84
|
+
is_required = param_info.get("required", False)
|
|
85
|
+
|
|
86
|
+
if not is_required:
|
|
87
|
+
py_type = f"Optional[{py_type}] = None"
|
|
88
|
+
|
|
89
|
+
param_info["type"] = py_type
|
|
90
|
+
param_info["is_required"] = is_required
|
|
91
|
+
mapped_params[name] = param_info
|
|
92
|
+
|
|
93
|
+
# Sort parameters: required first, then optional (to avoid SyntaxError)
|
|
94
|
+
sorted_params = dict(
|
|
95
|
+
sorted(mapped_params.items(), key=lambda x: (not x[1]["is_required"], x[0]))
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
template = jinja_env.get_template("tool.py.j2")
|
|
99
|
+
code = template.render(
|
|
100
|
+
tool_name=tool_name, description=description, parameters=sorted_params
|
|
101
|
+
)
|
|
102
|
+
tool_file.write_text(code)
|
|
103
|
+
|
|
104
|
+
# Auto-generate schemas after adding tool
|
|
105
|
+
from ..schemas import generate_schemas
|
|
106
|
+
generate_schemas(force=False, agent_name=agent_name)
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
"success": True,
|
|
110
|
+
"message": f"Created tool '{tool_name}'",
|
|
111
|
+
"path": str(tool_file),
|
|
112
|
+
}
|
|
113
|
+
except Exception as e:
|
|
114
|
+
return {"success": False, "error": str(e)}
|
|
115
|
+
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""
|
|
2
|
+
List tools tool.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
from ...core import mcp, get_project_dir
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@mcp.tool()
|
|
11
|
+
def list_tools(agent_name: Optional[str] = None) -> dict:
|
|
12
|
+
"""
|
|
13
|
+
List all tools available in the current agent project
|
|
14
|
+
"""
|
|
15
|
+
project_dir = get_project_dir(agent_name)
|
|
16
|
+
tools_dir = project_dir / "tools"
|
|
17
|
+
|
|
18
|
+
if not tools_dir.exists():
|
|
19
|
+
return {"tools": [], "count": 0}
|
|
20
|
+
|
|
21
|
+
tools = []
|
|
22
|
+
for tool_file in tools_dir.glob("*.py"):
|
|
23
|
+
if tool_file.name == "__init__.py":
|
|
24
|
+
continue
|
|
25
|
+
tools.append(tool_file.stem)
|
|
26
|
+
|
|
27
|
+
return {"tools": sorted(tools), "count": len(tools)}
|
|
28
|
+
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Remove tool tool.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Optional
|
|
7
|
+
import json
|
|
8
|
+
|
|
9
|
+
from ...core import mcp, get_project_dir
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@mcp.tool()
|
|
13
|
+
def remove_tool(
|
|
14
|
+
tool_name: str,
|
|
15
|
+
agent_name: Optional[str] = None,
|
|
16
|
+
) -> dict:
|
|
17
|
+
"""
|
|
18
|
+
Remove a tool from the agent
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
tool_name: Name of the tool to remove (without .py extension)
|
|
22
|
+
agent_name: Optional agent ID if not in agent directory
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
Result with success status and removed file path
|
|
26
|
+
"""
|
|
27
|
+
project_dir = get_project_dir(agent_name)
|
|
28
|
+
tools_dir = project_dir / "tools"
|
|
29
|
+
|
|
30
|
+
if not tools_dir.exists():
|
|
31
|
+
return {
|
|
32
|
+
"success": False,
|
|
33
|
+
"error": f"tools/ directory not found at {tools_dir}",
|
|
34
|
+
"fix": "Initialize an agent first with initialize_project() or specify agent_name",
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
tool_file = tools_dir / f"{tool_name}.py"
|
|
38
|
+
|
|
39
|
+
if not tool_file.exists():
|
|
40
|
+
# List available tools for helpful error
|
|
41
|
+
available = [f.stem for f in tools_dir.glob("*.py") if f.stem != "__init__"]
|
|
42
|
+
return {
|
|
43
|
+
"success": False,
|
|
44
|
+
"error": f"Tool '{tool_name}' not found",
|
|
45
|
+
"fix": f"Available tools: {', '.join(available) if available else 'none'}",
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
try:
|
|
49
|
+
# Remove the tool file
|
|
50
|
+
tool_file.unlink()
|
|
51
|
+
|
|
52
|
+
# Update schemas.json if it exists
|
|
53
|
+
schemas_file = tools_dir / "schemas.json"
|
|
54
|
+
if schemas_file.exists():
|
|
55
|
+
try:
|
|
56
|
+
schemas = json.loads(schemas_file.read_text())
|
|
57
|
+
if tool_name in schemas:
|
|
58
|
+
del schemas[tool_name]
|
|
59
|
+
schemas_file.write_text(json.dumps(schemas, indent=2))
|
|
60
|
+
except (json.JSONDecodeError, KeyError):
|
|
61
|
+
pass # Ignore schema update errors
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
"success": True,
|
|
65
|
+
"message": f"Removed tool '{tool_name}'",
|
|
66
|
+
"removed_file": str(tool_file),
|
|
67
|
+
}
|
|
68
|
+
except Exception as e:
|
|
69
|
+
return {"success": False, "error": str(e)}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Update tool - modify existing tool's description or parameters.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
from ...core import mcp, jinja_env, get_project_dir
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@mcp.tool()
|
|
11
|
+
def update_tool(
|
|
12
|
+
tool_name: str,
|
|
13
|
+
description: Optional[str] = None,
|
|
14
|
+
parameters: Optional[dict] = None,
|
|
15
|
+
agent_name: Optional[str] = None,
|
|
16
|
+
) -> dict:
|
|
17
|
+
"""
|
|
18
|
+
Update an existing tool's description or parameters.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
tool_name: Name of the tool to update
|
|
22
|
+
description: New description (optional)
|
|
23
|
+
parameters: New parameters dict (optional, replaces all parameters)
|
|
24
|
+
agent_name: Optional agent ID if not in agent directory
|
|
25
|
+
"""
|
|
26
|
+
project_dir = get_project_dir(agent_name)
|
|
27
|
+
tools_dir = project_dir / "tools"
|
|
28
|
+
|
|
29
|
+
if not tools_dir.exists():
|
|
30
|
+
return {
|
|
31
|
+
"success": False,
|
|
32
|
+
"error": f"tools/ directory not found at {tools_dir}",
|
|
33
|
+
"fix": "Make sure you're in an agent project directory or specify agent_name",
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
tool_file = tools_dir / f"{tool_name}.py"
|
|
37
|
+
if not tool_file.exists():
|
|
38
|
+
# List available tools for helpful error
|
|
39
|
+
available = [f.stem for f in tools_dir.glob("*.py") if f.stem != "__init__"]
|
|
40
|
+
return {
|
|
41
|
+
"success": False,
|
|
42
|
+
"error": f"Tool '{tool_name}' not found",
|
|
43
|
+
"fix": f"Available tools: {', '.join(available) if available else 'none'}",
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if description is None and parameters is None:
|
|
47
|
+
return {
|
|
48
|
+
"success": False,
|
|
49
|
+
"error": "Nothing to update",
|
|
50
|
+
"fix": "Provide at least one of: description, parameters",
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
try:
|
|
54
|
+
# Read current tool to extract existing values if needed
|
|
55
|
+
current_content = tool_file.read_text()
|
|
56
|
+
|
|
57
|
+
# Extract current description from docstring if not provided
|
|
58
|
+
if description is None:
|
|
59
|
+
import re
|
|
60
|
+
match = re.search(r'"""([^"]+)"""', current_content)
|
|
61
|
+
if match:
|
|
62
|
+
description = match.group(1).strip()
|
|
63
|
+
else:
|
|
64
|
+
description = f"Tool: {tool_name}"
|
|
65
|
+
|
|
66
|
+
# If parameters not provided, we need to extract from current file
|
|
67
|
+
if parameters is None:
|
|
68
|
+
# For now, require parameters to be specified for update
|
|
69
|
+
return {
|
|
70
|
+
"success": False,
|
|
71
|
+
"error": "Parameters must be specified when updating",
|
|
72
|
+
"fix": "Provide the parameters dict with all parameters for the tool",
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
# Prepare parameters with Python types (same logic as add_tool)
|
|
76
|
+
mapped_params = {}
|
|
77
|
+
type_mapping = {
|
|
78
|
+
"string": "str",
|
|
79
|
+
"number": "float",
|
|
80
|
+
"integer": "int",
|
|
81
|
+
"boolean": "bool",
|
|
82
|
+
"array": "List",
|
|
83
|
+
"object": "dict",
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
for name, info in parameters.items():
|
|
87
|
+
if isinstance(info, str):
|
|
88
|
+
param_info = {"type": info}
|
|
89
|
+
else:
|
|
90
|
+
param_info = info.copy()
|
|
91
|
+
raw_type = param_info.get("type", "Any")
|
|
92
|
+
py_type = type_mapping.get(raw_type, raw_type)
|
|
93
|
+
is_required = param_info.get("required", False)
|
|
94
|
+
|
|
95
|
+
if not is_required:
|
|
96
|
+
py_type = f"Optional[{py_type}] = None"
|
|
97
|
+
|
|
98
|
+
param_info["type"] = py_type
|
|
99
|
+
param_info["is_required"] = is_required
|
|
100
|
+
mapped_params[name] = param_info
|
|
101
|
+
|
|
102
|
+
sorted_params = dict(
|
|
103
|
+
sorted(mapped_params.items(), key=lambda x: (not x[1]["is_required"], x[0]))
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
# Regenerate tool file
|
|
107
|
+
template = jinja_env.get_template("tool.py.j2")
|
|
108
|
+
code = template.render(
|
|
109
|
+
tool_name=tool_name, description=description, parameters=sorted_params
|
|
110
|
+
)
|
|
111
|
+
tool_file.write_text(code)
|
|
112
|
+
|
|
113
|
+
# Regenerate schemas
|
|
114
|
+
from ..schemas import generate_schemas
|
|
115
|
+
generate_schemas(force=True, agent_name=agent_name)
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
"success": True,
|
|
119
|
+
"message": f"Updated tool '{tool_name}'",
|
|
120
|
+
"path": str(tool_file),
|
|
121
|
+
}
|
|
122
|
+
except Exception as e:
|
|
123
|
+
return {"success": False, "error": str(e)}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Deploy agent tool.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Optional
|
|
6
|
+
from urllib.parse import urlencode
|
|
7
|
+
|
|
8
|
+
from ...core import mcp, get_project_dir
|
|
9
|
+
from ..validation import validate
|
|
10
|
+
from ..schemas import generate_schemas
|
|
11
|
+
|
|
12
|
+
HUB_URL = "https://dev-a4e.global.simetrik.com"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@mcp.tool()
|
|
16
|
+
def deploy(
|
|
17
|
+
environment: str = "production",
|
|
18
|
+
auto_publish: bool = False,
|
|
19
|
+
agent_name: Optional[str] = None,
|
|
20
|
+
) -> dict:
|
|
21
|
+
"""
|
|
22
|
+
Deploy agent to A4E Hub.
|
|
23
|
+
|
|
24
|
+
This validates the agent, regenerates schemas, and prepares it for deployment.
|
|
25
|
+
To test locally with the playground, use `dev_start` instead.
|
|
26
|
+
"""
|
|
27
|
+
val_result = validate(strict=True, agent_name=agent_name)
|
|
28
|
+
if not val_result["success"]:
|
|
29
|
+
return val_result
|
|
30
|
+
|
|
31
|
+
gen_result = generate_schemas(force=True, agent_name=agent_name)
|
|
32
|
+
|
|
33
|
+
if (
|
|
34
|
+
gen_result.get("tools", {}).get("status") == "error"
|
|
35
|
+
or gen_result.get("views", {}).get("status") == "error"
|
|
36
|
+
):
|
|
37
|
+
return {
|
|
38
|
+
"success": False,
|
|
39
|
+
"error": "Schema generation failed",
|
|
40
|
+
"details": gen_result,
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
project_dir = get_project_dir(agent_name)
|
|
44
|
+
agent_id = project_dir.name
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
"success": True,
|
|
48
|
+
"message": f"Agent '{agent_id}' validated and schemas generated for {environment}",
|
|
49
|
+
"agent_id": agent_id,
|
|
50
|
+
"next_steps": [
|
|
51
|
+
f"Run 'a4e dev start' to test locally with the playground",
|
|
52
|
+
f"The playground URL will be: {HUB_URL}/builder/playground?url=<ngrok_url>&agent={agent_id}",
|
|
53
|
+
],
|
|
54
|
+
"details": {
|
|
55
|
+
"validation": val_result,
|
|
56
|
+
"schema_generation": gen_result,
|
|
57
|
+
},
|
|
58
|
+
}
|
|
59
|
+
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Check environment tool.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
import shutil
|
|
7
|
+
import os
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from ...core import mcp, get_project_dir, get_configured_project_dir
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@mcp.tool()
|
|
14
|
+
def check_environment() -> dict:
|
|
15
|
+
"""
|
|
16
|
+
Diagnose the current environment for agent development
|
|
17
|
+
"""
|
|
18
|
+
_PROJECT_DIR = get_configured_project_dir()
|
|
19
|
+
|
|
20
|
+
results = {
|
|
21
|
+
"python": {
|
|
22
|
+
"version": sys.version.split()[0],
|
|
23
|
+
"ok": sys.version_info >= (3, 10),
|
|
24
|
+
},
|
|
25
|
+
"project_root": {
|
|
26
|
+
"configured_dir": str(_PROJECT_DIR) if _PROJECT_DIR else None,
|
|
27
|
+
"effective_dir": str(get_project_dir()),
|
|
28
|
+
"using_fallback": _PROJECT_DIR is None,
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {"pyngrok": False, "message": "Not installed"},
|
|
31
|
+
"ngrok_binary": {"found": False, "path": None},
|
|
32
|
+
"ngrok_auth": {"configured": False},
|
|
33
|
+
"recommendations": [],
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
# Check pyngrok
|
|
37
|
+
try:
|
|
38
|
+
import pyngrok
|
|
39
|
+
|
|
40
|
+
results["dependencies"]["pyngrok"] = True
|
|
41
|
+
results["dependencies"]["message"] = f"Installed ({pyngrok.__version__})"
|
|
42
|
+
except ImportError:
|
|
43
|
+
results["recommendations"].append("Install pyngrok: 'uv add pyngrok'")
|
|
44
|
+
|
|
45
|
+
# Check ngrok binary
|
|
46
|
+
ngrok_path = shutil.which("ngrok")
|
|
47
|
+
if ngrok_path:
|
|
48
|
+
results["ngrok_binary"]["found"] = True
|
|
49
|
+
results["ngrok_binary"]["path"] = ngrok_path
|
|
50
|
+
else:
|
|
51
|
+
results["recommendations"].append("Install ngrok CLI and add to PATH")
|
|
52
|
+
|
|
53
|
+
# Check auth token
|
|
54
|
+
if os.environ.get("NGROK_AUTHTOKEN"):
|
|
55
|
+
results["ngrok_auth"]["configured"] = True
|
|
56
|
+
results["ngrok_auth"]["source"] = "env_var"
|
|
57
|
+
else:
|
|
58
|
+
try:
|
|
59
|
+
from pyngrok import conf
|
|
60
|
+
|
|
61
|
+
if conf.get_default().auth_token:
|
|
62
|
+
results["ngrok_auth"]["configured"] = True
|
|
63
|
+
results["ngrok_auth"]["source"] = "config_file"
|
|
64
|
+
except ImportError:
|
|
65
|
+
config_path = Path.home() / ".ngrok2" / "ngrok.yml"
|
|
66
|
+
config_path_new = (
|
|
67
|
+
Path.home() / "Library/Application Support/ngrok/ngrok.yml"
|
|
68
|
+
)
|
|
69
|
+
if config_path.exists() or config_path_new.exists():
|
|
70
|
+
results["ngrok_auth"]["configured"] = True
|
|
71
|
+
results["ngrok_auth"]["source"] = "config_file_detected"
|
|
72
|
+
|
|
73
|
+
if not results["ngrok_auth"]["configured"]:
|
|
74
|
+
results["recommendations"].append(
|
|
75
|
+
"Configure ngrok auth: 'ngrok config add-authtoken <TOKEN>'"
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
return results
|
|
79
|
+
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Start development server tool.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Optional
|
|
7
|
+
import importlib.util
|
|
8
|
+
|
|
9
|
+
from ...core import mcp, get_project_dir
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@mcp.tool()
|
|
13
|
+
def dev_start(
|
|
14
|
+
port: int = 5000, auth_token: Optional[str] = None, agent_name: Optional[str] = None
|
|
15
|
+
) -> dict:
|
|
16
|
+
"""
|
|
17
|
+
Start development mode with ngrok tunnel
|
|
18
|
+
"""
|
|
19
|
+
# Import DevManager dynamically
|
|
20
|
+
dev_manager_path = Path(__file__).parent.parent.parent / "utils" / "dev_manager.py"
|
|
21
|
+
spec = importlib.util.spec_from_file_location("dev_manager", dev_manager_path)
|
|
22
|
+
if not spec or not spec.loader:
|
|
23
|
+
return {"success": False, "error": "Failed to load dev_manager module"}
|
|
24
|
+
dev_manager_module = importlib.util.module_from_spec(spec)
|
|
25
|
+
spec.loader.exec_module(dev_manager_module)
|
|
26
|
+
DevManager = dev_manager_module.DevManager
|
|
27
|
+
|
|
28
|
+
project_dir = get_project_dir(agent_name)
|
|
29
|
+
return DevManager.start_dev_server(project_dir, port, auth_token)
|
|
30
|
+
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Stop development server tool.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import importlib.util
|
|
7
|
+
|
|
8
|
+
from ...core import mcp
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@mcp.tool()
|
|
12
|
+
def dev_stop(port: int = 5000) -> dict:
|
|
13
|
+
"""
|
|
14
|
+
Stop development server and cleanup tunnels
|
|
15
|
+
"""
|
|
16
|
+
# Import DevManager dynamically
|
|
17
|
+
dev_manager_path = Path(__file__).parent.parent.parent / "utils" / "dev_manager.py"
|
|
18
|
+
spec = importlib.util.spec_from_file_location("dev_manager", dev_manager_path)
|
|
19
|
+
if not spec or not spec.loader:
|
|
20
|
+
return {"success": False, "error": "Failed to load dev_manager module"}
|
|
21
|
+
dev_manager_module = importlib.util.module_from_spec(spec)
|
|
22
|
+
spec.loader.exec_module(dev_manager_module)
|
|
23
|
+
DevManager = dev_manager_module.DevManager
|
|
24
|
+
|
|
25
|
+
return DevManager.stop_dev_server(port)
|
|
26
|
+
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Project initialization tools.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from .initialize_project import initialize_project
|
|
6
|
+
from .get_agent_info import get_agent_info
|
|
7
|
+
from .get_instructions import get_instructions
|
|
8
|
+
|
|
9
|
+
__all__ = ["initialize_project", "get_agent_info", "get_instructions"]
|
|
10
|
+
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Get agent info tool.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Optional
|
|
6
|
+
import json
|
|
7
|
+
|
|
8
|
+
from ...core import mcp, get_project_dir
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@mcp.tool()
|
|
12
|
+
def get_agent_info(agent_name: Optional[str] = None) -> dict:
|
|
13
|
+
"""
|
|
14
|
+
Get general metadata and info about the current agent
|
|
15
|
+
"""
|
|
16
|
+
project_dir = get_project_dir(agent_name)
|
|
17
|
+
metadata_file = project_dir / "metadata.json"
|
|
18
|
+
|
|
19
|
+
if not metadata_file.exists():
|
|
20
|
+
return {
|
|
21
|
+
"error": f"metadata.json not found in {project_dir}. Are you in an agent project?"
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
metadata = json.loads(metadata_file.read_text())
|
|
26
|
+
|
|
27
|
+
# Count structure
|
|
28
|
+
tools_dir = project_dir / "tools"
|
|
29
|
+
views_dir = project_dir / "views"
|
|
30
|
+
skills_dir = project_dir / "skills"
|
|
31
|
+
prompts_dir = project_dir / "prompts"
|
|
32
|
+
|
|
33
|
+
tools = []
|
|
34
|
+
if tools_dir.exists():
|
|
35
|
+
tools = [f.stem for f in tools_dir.glob("*.py") if f.stem != "__init__"]
|
|
36
|
+
|
|
37
|
+
views = []
|
|
38
|
+
if views_dir.exists():
|
|
39
|
+
views = [d.name for d in views_dir.iterdir() if d.is_dir() and (d / "view.tsx").exists()]
|
|
40
|
+
|
|
41
|
+
skills = []
|
|
42
|
+
if skills_dir.exists():
|
|
43
|
+
skills = [d.name for d in skills_dir.iterdir() if d.is_dir()]
|
|
44
|
+
|
|
45
|
+
prompts = []
|
|
46
|
+
if prompts_dir.exists():
|
|
47
|
+
prompts = [f.stem for f in prompts_dir.glob("*.md")]
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
"agent_id": project_dir.name,
|
|
51
|
+
"metadata": metadata,
|
|
52
|
+
"path": str(project_dir),
|
|
53
|
+
"structure": {
|
|
54
|
+
"tools": tools,
|
|
55
|
+
"tools_count": len(tools),
|
|
56
|
+
"views": views,
|
|
57
|
+
"views_count": len(views),
|
|
58
|
+
"skills": skills,
|
|
59
|
+
"skills_count": len(skills),
|
|
60
|
+
"prompts": prompts,
|
|
61
|
+
"prompts_count": len(prompts),
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
except Exception as e:
|
|
65
|
+
return {"error": f"Failed to read metadata: {str(e)}"}
|
|
66
|
+
|