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
@@ -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,8 @@
1
+ """
2
+ Deployment tools.
3
+ """
4
+
5
+ from .deploy import deploy
6
+
7
+ __all__ = ["deploy"]
8
+
@@ -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,10 @@
1
+ """
2
+ Development tools.
3
+ """
4
+
5
+ from .dev_start import dev_start
6
+ from .dev_stop import dev_stop
7
+ from .check_environment import check_environment
8
+
9
+ __all__ = ["dev_start", "dev_stop", "check_environment"]
10
+
@@ -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
+