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/cli_commands/dev.py
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# a4e/cli_commands/dev.py
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Optional
|
|
6
|
+
import pyperclip
|
|
7
|
+
import os
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
from ..utils.dev_manager import DevManager # Correct relative import
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# Create a 'Typer' app specifically for the 'dev' command group
|
|
14
|
+
app = typer.Typer(
|
|
15
|
+
no_args_is_help=True,
|
|
16
|
+
help="Commands for running the development server and ngrok tunnels.",
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def get_ngrok_authtoken():
|
|
21
|
+
"""
|
|
22
|
+
Attempts to retrieve the ngrok authtoken from the system.
|
|
23
|
+
"""
|
|
24
|
+
# Try to get the ngrok token from an environment variable
|
|
25
|
+
token = os.environ.get("NGROK_AUTHTOKEN")
|
|
26
|
+
if token:
|
|
27
|
+
return token
|
|
28
|
+
|
|
29
|
+
# If not an environment variable try to look for it in the system
|
|
30
|
+
try:
|
|
31
|
+
home = Path.home()
|
|
32
|
+
config_paths = [
|
|
33
|
+
home / "AppData" / "Local" / "ngrok" / "ngrok.yml", # Windows
|
|
34
|
+
home / ".ngrok2" / "ngrok.yml", # Linux
|
|
35
|
+
home / ".config" / "ngrok" / "ngrok.yml", # Linux
|
|
36
|
+
home / "Library" / "Application Support" / "ngrok" / "ngrok.yml", # macOS
|
|
37
|
+
]
|
|
38
|
+
for config_path in config_paths:
|
|
39
|
+
if config_path.exists():
|
|
40
|
+
with open(config_path, "r") as f:
|
|
41
|
+
for line in f:
|
|
42
|
+
if line.strip().startswith("authtoken:"):
|
|
43
|
+
return line.strip().split(":", 1)[1].strip().strip("'\"")
|
|
44
|
+
except Exception:
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
return None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@app.command()
|
|
51
|
+
def start(
|
|
52
|
+
directory: str = typer.Option(None, help="The directory to run the server on."),
|
|
53
|
+
port: int = typer.Option(5000, help="The local port to run the server on."),
|
|
54
|
+
auth_token: Optional[str] = typer.Option(
|
|
55
|
+
None,
|
|
56
|
+
help="Your ngrok authtoken. If not provided, it will be sourced from your environment or ngrok config.",
|
|
57
|
+
),
|
|
58
|
+
) -> None:
|
|
59
|
+
"""
|
|
60
|
+
Starts the development server with an ngrok tunnel.
|
|
61
|
+
This command must be run from within the 'agent-store' directory.
|
|
62
|
+
"""
|
|
63
|
+
if not auth_token:
|
|
64
|
+
auth_token = get_ngrok_authtoken()
|
|
65
|
+
|
|
66
|
+
if not auth_token:
|
|
67
|
+
print("Error: Could not find ngrok authtoken.")
|
|
68
|
+
print(
|
|
69
|
+
"Please provide it with the --auth-token option, or run 'ngrok config add-authtoken <YOUR_TOKEN>'"
|
|
70
|
+
)
|
|
71
|
+
raise typer.Exit(code=1)
|
|
72
|
+
|
|
73
|
+
current_dir = Path.cwd()
|
|
74
|
+
if directory:
|
|
75
|
+
current_dir = Path(directory)
|
|
76
|
+
project_dir = None
|
|
77
|
+
|
|
78
|
+
# Check if we're inside an agent directory (has agent.py and metadata.json)
|
|
79
|
+
if (current_dir / "agent.py").exists() and (current_dir / "metadata.json").exists():
|
|
80
|
+
# We're inside an agent directory - use it directly
|
|
81
|
+
project_dir = current_dir
|
|
82
|
+
print(f"Detected agent directory: {project_dir.name}")
|
|
83
|
+
elif current_dir.name == "agent-store":
|
|
84
|
+
# We're in agent-store - list available agents and prompt for selection
|
|
85
|
+
agent_store_path = current_dir
|
|
86
|
+
available_agents = []
|
|
87
|
+
if agent_store_path.is_dir():
|
|
88
|
+
available_agents = [d for d in agent_store_path.iterdir() if d.is_dir()]
|
|
89
|
+
|
|
90
|
+
while not project_dir:
|
|
91
|
+
print("\nSelect an agent to start:")
|
|
92
|
+
if available_agents:
|
|
93
|
+
for i, agent in enumerate(available_agents):
|
|
94
|
+
print(f" [{i + 1}] {agent.name}")
|
|
95
|
+
prompt_text = "\nPlease choose an agent"
|
|
96
|
+
else:
|
|
97
|
+
print("No agents found in the current 'agent-store' directory.")
|
|
98
|
+
raise typer.Exit(code=1)
|
|
99
|
+
|
|
100
|
+
try:
|
|
101
|
+
# Check if user entered a number
|
|
102
|
+
response = typer.prompt(prompt_text, type=str)
|
|
103
|
+
choice_index = int(response) - 1
|
|
104
|
+
if 0 <= choice_index < len(available_agents):
|
|
105
|
+
project_dir = available_agents[choice_index]
|
|
106
|
+
else:
|
|
107
|
+
print("Invalid number. Please try again.")
|
|
108
|
+
except ValueError:
|
|
109
|
+
# User entered a path string
|
|
110
|
+
print("Please enter a valid number.")
|
|
111
|
+
else:
|
|
112
|
+
print("Error: Run this command from an agent directory or the 'agent-store' directory.")
|
|
113
|
+
print(f"Current directory: {current_dir}")
|
|
114
|
+
print("\nTip: cd into your agent folder, or use --directory to specify the path.")
|
|
115
|
+
raise typer.Exit(code=1)
|
|
116
|
+
|
|
117
|
+
# Final validation of the selected directory
|
|
118
|
+
if not project_dir or not project_dir.is_dir():
|
|
119
|
+
print("Error: No valid agent directory selected.")
|
|
120
|
+
raise typer.Exit(code=1)
|
|
121
|
+
|
|
122
|
+
print(f"\nUsing agent folder: {project_dir}")
|
|
123
|
+
print("Starting development server...")
|
|
124
|
+
|
|
125
|
+
result = DevManager.start_dev_server(
|
|
126
|
+
project_dir=project_dir, port=port, auth_token=auth_token
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
if result.get("success"):
|
|
130
|
+
print("Dev mode started successfully:")
|
|
131
|
+
print(f" Public URL: {result.get('public_url')}")
|
|
132
|
+
print(f" Hub URL: {result.get('hub_url')}")
|
|
133
|
+
|
|
134
|
+
try:
|
|
135
|
+
pyperclip.copy(str(result.get("hub_url")))
|
|
136
|
+
print(" (Hub URL copied to clipboard!)")
|
|
137
|
+
except pyperclip.PyperclipException:
|
|
138
|
+
print(
|
|
139
|
+
" (Could not copy Hub URL to clipboard. Please install xclip/xsel or enable Wayland clipboard for Linux.)"
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
print("\nInstructions:")
|
|
143
|
+
for instruction in result.get("instructions", []):
|
|
144
|
+
print(f" {instruction}")
|
|
145
|
+
|
|
146
|
+
# Keep the CLI running to maintain the server process
|
|
147
|
+
print("\n[Server running - Press Ctrl+C to stop]")
|
|
148
|
+
try:
|
|
149
|
+
import time
|
|
150
|
+
while True:
|
|
151
|
+
time.sleep(1)
|
|
152
|
+
except KeyboardInterrupt:
|
|
153
|
+
print("\nStopping dev server...")
|
|
154
|
+
DevManager.stop_dev_server(port)
|
|
155
|
+
print("Dev server stopped.")
|
|
156
|
+
else:
|
|
157
|
+
print(f"Error starting server: {result.get('error')}")
|
|
158
|
+
if result.get("details"):
|
|
159
|
+
print(f"Details: {result.get('details')}")
|
|
160
|
+
if result.get("fix"):
|
|
161
|
+
print(f"Fix: {result.get('fix')}")
|
|
162
|
+
raise typer.Exit(code=1)
|
a4e/cli_commands/info.py
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# a4e/cli_commands/info.py
|
|
2
|
+
"""
|
|
3
|
+
Command for displaying agent information.
|
|
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.panel import Panel
|
|
13
|
+
from rich.table import Table
|
|
14
|
+
|
|
15
|
+
console = Console()
|
|
16
|
+
|
|
17
|
+
# Create a 'Typer' app for the 'info' command
|
|
18
|
+
app = typer.Typer(
|
|
19
|
+
no_args_is_help=False,
|
|
20
|
+
help="Display information about your agent.",
|
|
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 info(
|
|
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
|
+
json_output: bool = typer.Option(
|
|
51
|
+
False, "--json", "-j", help="Output as JSON"
|
|
52
|
+
),
|
|
53
|
+
) -> None:
|
|
54
|
+
"""
|
|
55
|
+
Display information about the agent.
|
|
56
|
+
|
|
57
|
+
Shows metadata, structure, and statistics about the agent.
|
|
58
|
+
|
|
59
|
+
Example:
|
|
60
|
+
a4e info
|
|
61
|
+
a4e info --agent my-agent --json
|
|
62
|
+
"""
|
|
63
|
+
# If a subcommand is being invoked, skip the callback logic
|
|
64
|
+
if ctx.invoked_subcommand is not None:
|
|
65
|
+
return
|
|
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
|
+
try:
|
|
73
|
+
from ..tools.project.get_agent_info import get_agent_info as mcp_get_agent_info
|
|
74
|
+
|
|
75
|
+
result = mcp_get_agent_info(agent_name=str(agent_dir))
|
|
76
|
+
|
|
77
|
+
# Handle response (get_agent_info returns {"agent_id", "metadata", "path"} or {"error"})
|
|
78
|
+
if "error" not in result:
|
|
79
|
+
if json_output:
|
|
80
|
+
console.print(json.dumps(result, indent=2))
|
|
81
|
+
return
|
|
82
|
+
|
|
83
|
+
# Display panel with basic info
|
|
84
|
+
metadata = result.get("metadata", {})
|
|
85
|
+
|
|
86
|
+
console.print(Panel.fit(
|
|
87
|
+
f"[bold cyan]{metadata.get('display_name', agent_dir.name)}[/bold cyan]\n"
|
|
88
|
+
f"[dim]{metadata.get('description', 'No description')}[/dim]\n\n"
|
|
89
|
+
f"[bold]ID:[/bold] {metadata.get('id', agent_dir.name)}\n"
|
|
90
|
+
f"[bold]Category:[/bold] {metadata.get('category', 'Unknown')}\n"
|
|
91
|
+
f"[bold]Path:[/bold] {agent_dir}",
|
|
92
|
+
title="Agent Info",
|
|
93
|
+
border_style="blue"
|
|
94
|
+
))
|
|
95
|
+
|
|
96
|
+
# Structure summary
|
|
97
|
+
structure = result.get("structure", {})
|
|
98
|
+
|
|
99
|
+
table = Table(title="Structure Summary")
|
|
100
|
+
table.add_column("Component", style="cyan")
|
|
101
|
+
table.add_column("Count", justify="right")
|
|
102
|
+
|
|
103
|
+
table.add_row("Tools", str(structure.get("tools_count", 0)))
|
|
104
|
+
table.add_row("Views", str(structure.get("views_count", 0)))
|
|
105
|
+
table.add_row("Skills", str(structure.get("skills_count", 0)))
|
|
106
|
+
table.add_row("Prompts", str(structure.get("prompts_count", 0)))
|
|
107
|
+
|
|
108
|
+
console.print("")
|
|
109
|
+
console.print(table)
|
|
110
|
+
|
|
111
|
+
# List items if present
|
|
112
|
+
if structure.get("tools"):
|
|
113
|
+
console.print("\n[bold]Tools:[/bold]")
|
|
114
|
+
for tool in structure.get("tools", []):
|
|
115
|
+
console.print(f" • {tool}")
|
|
116
|
+
|
|
117
|
+
if structure.get("views"):
|
|
118
|
+
console.print("\n[bold]Views:[/bold]")
|
|
119
|
+
for view in structure.get("views", []):
|
|
120
|
+
console.print(f" • {view}")
|
|
121
|
+
|
|
122
|
+
if structure.get("skills"):
|
|
123
|
+
console.print("\n[bold]Skills:[/bold]")
|
|
124
|
+
for skill in structure.get("skills", []):
|
|
125
|
+
console.print(f" • {skill}")
|
|
126
|
+
|
|
127
|
+
else:
|
|
128
|
+
console.print(f"[red]Error: {result.get('error')}[/red]")
|
|
129
|
+
raise typer.Exit(code=1)
|
|
130
|
+
|
|
131
|
+
except ImportError as e:
|
|
132
|
+
console.print(f"[red]Error importing tools: {e}[/red]")
|
|
133
|
+
raise typer.Exit(code=1)
|
|
134
|
+
except Exception as e:
|
|
135
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
136
|
+
raise typer.Exit(code=1)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@app.command("instructions")
|
|
140
|
+
def instructions(
|
|
141
|
+
json_output: bool = typer.Option(
|
|
142
|
+
False, "--json", "-j", help="Output as JSON"
|
|
143
|
+
),
|
|
144
|
+
) -> None:
|
|
145
|
+
"""
|
|
146
|
+
Show instructions for AI agents on how to use A4E tools.
|
|
147
|
+
|
|
148
|
+
This displays the comprehensive documentation that AI assistants
|
|
149
|
+
(like Claude, Cursor, etc.) use to understand how to create A4E agents.
|
|
150
|
+
|
|
151
|
+
Example:
|
|
152
|
+
a4e info instructions
|
|
153
|
+
a4e info instructions --json
|
|
154
|
+
"""
|
|
155
|
+
try:
|
|
156
|
+
from ..tools.project.get_instructions import get_instructions as mcp_get_instructions
|
|
157
|
+
|
|
158
|
+
result = mcp_get_instructions()
|
|
159
|
+
|
|
160
|
+
if result.get("success"):
|
|
161
|
+
if json_output:
|
|
162
|
+
console.print(json.dumps(result, indent=2))
|
|
163
|
+
return
|
|
164
|
+
|
|
165
|
+
# Display the instructions with nice formatting
|
|
166
|
+
console.print(Panel.fit(
|
|
167
|
+
"[bold cyan]A4E Agent Creator - AI Instructions[/bold cyan]\n\n"
|
|
168
|
+
"These instructions are provided to AI assistants when they use\n"
|
|
169
|
+
"the A4E MCP tools to create agents.",
|
|
170
|
+
border_style="blue"
|
|
171
|
+
))
|
|
172
|
+
|
|
173
|
+
# Display quick reference
|
|
174
|
+
quick_ref = result.get("quick_reference", {})
|
|
175
|
+
|
|
176
|
+
table = Table(title="Quick Reference")
|
|
177
|
+
table.add_column("Item", style="cyan")
|
|
178
|
+
table.add_column("Value")
|
|
179
|
+
|
|
180
|
+
table.add_row("Workflow", " → ".join(quick_ref.get("workflow", [])))
|
|
181
|
+
table.add_row("Parameter Types", ", ".join(quick_ref.get("parameter_types", [])))
|
|
182
|
+
table.add_row("Templates", ", ".join(quick_ref.get("templates", [])))
|
|
183
|
+
|
|
184
|
+
naming = quick_ref.get("naming", {})
|
|
185
|
+
table.add_row("agent_name", naming.get("agent_name", ""))
|
|
186
|
+
table.add_row("tool_name", naming.get("tool_name", ""))
|
|
187
|
+
table.add_row("view_id", naming.get("view_id", ""))
|
|
188
|
+
table.add_row("skill_id", naming.get("skill_id", ""))
|
|
189
|
+
|
|
190
|
+
console.print("")
|
|
191
|
+
console.print(table)
|
|
192
|
+
|
|
193
|
+
# Print the full instructions
|
|
194
|
+
console.print("\n[bold]Full Instructions:[/bold]\n")
|
|
195
|
+
console.print(result.get("instructions", ""))
|
|
196
|
+
|
|
197
|
+
else:
|
|
198
|
+
console.print(f"[red]Error: {result.get('error')}[/red]")
|
|
199
|
+
raise typer.Exit(code=1)
|
|
200
|
+
|
|
201
|
+
except ImportError as e:
|
|
202
|
+
console.print(f"[red]Error importing tools: {e}[/red]")
|
|
203
|
+
raise typer.Exit(code=1)
|
|
204
|
+
except Exception as e:
|
|
205
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
206
|
+
raise typer.Exit(code=1)
|
a4e/cli_commands/init.py
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
# a4e/cli_commands/init.py
|
|
2
|
+
"""
|
|
3
|
+
Interactive project initialization command.
|
|
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 Prompt, Confirm
|
|
13
|
+
|
|
14
|
+
console = Console()
|
|
15
|
+
|
|
16
|
+
# Create a 'Typer' app for the 'init' command
|
|
17
|
+
app = typer.Typer(
|
|
18
|
+
no_args_is_help=False,
|
|
19
|
+
help="Initialize a new A4E agent project with an interactive wizard.",
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
CATEGORIES = [
|
|
23
|
+
"Concierge",
|
|
24
|
+
"E-commerce",
|
|
25
|
+
"Fitness & Health",
|
|
26
|
+
"Education",
|
|
27
|
+
"Entertainment",
|
|
28
|
+
"Productivity",
|
|
29
|
+
"Finance",
|
|
30
|
+
"Customer Support",
|
|
31
|
+
"General",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
TEMPLATES = {
|
|
35
|
+
"basic": "Minimal files only (agent.py, metadata.json, welcome view)",
|
|
36
|
+
"with-tools": "Basic + example tool",
|
|
37
|
+
"with-views": "Basic + example view",
|
|
38
|
+
"full": "Basic + example tool + example view",
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def validate_agent_name(name: str) -> bool:
|
|
43
|
+
"""Validate agent name format (lowercase, hyphens/underscores only)."""
|
|
44
|
+
return name.replace("-", "").replace("_", "").isalnum() and name.islower()
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
@app.callback(invoke_without_command=True)
|
|
48
|
+
def init(
|
|
49
|
+
ctx: typer.Context,
|
|
50
|
+
name: Optional[str] = typer.Option(
|
|
51
|
+
None, "--name", "-n", help="Agent ID (lowercase, hyphens, e.g., 'nutrition-coach')"
|
|
52
|
+
),
|
|
53
|
+
display_name: Optional[str] = typer.Option(
|
|
54
|
+
None, "--display-name", "-d", help="Human-readable name"
|
|
55
|
+
),
|
|
56
|
+
description: Optional[str] = typer.Option(
|
|
57
|
+
None, "--description", help="Short description of the agent"
|
|
58
|
+
),
|
|
59
|
+
category: Optional[str] = typer.Option(
|
|
60
|
+
None, "--category", "-c", help="Agent category for marketplace"
|
|
61
|
+
),
|
|
62
|
+
template: Optional[str] = typer.Option(
|
|
63
|
+
None, "--template", "-t", help="Project template (basic, with-tools, with-views, full)"
|
|
64
|
+
),
|
|
65
|
+
directory: Optional[str] = typer.Option(
|
|
66
|
+
None, "--directory", help="Directory to create agent in (defaults to current directory)"
|
|
67
|
+
),
|
|
68
|
+
non_interactive: bool = typer.Option(
|
|
69
|
+
False, "--yes", "-y", help="Skip interactive prompts (requires all options)"
|
|
70
|
+
),
|
|
71
|
+
) -> None:
|
|
72
|
+
"""
|
|
73
|
+
Initialize a new A4E agent project.
|
|
74
|
+
|
|
75
|
+
Run without arguments for an interactive wizard, or pass all options for non-interactive mode.
|
|
76
|
+
"""
|
|
77
|
+
console.print(Panel.fit(
|
|
78
|
+
"[bold blue]A4E Agent Creator[/bold blue]\n"
|
|
79
|
+
"Create a new conversational AI agent",
|
|
80
|
+
border_style="blue"
|
|
81
|
+
))
|
|
82
|
+
|
|
83
|
+
# Interactive mode - prompt for missing values
|
|
84
|
+
if not non_interactive:
|
|
85
|
+
# Agent name
|
|
86
|
+
if not name:
|
|
87
|
+
while True:
|
|
88
|
+
name = Prompt.ask(
|
|
89
|
+
"\n[bold]Agent name[/bold] (lowercase, hyphens allowed)",
|
|
90
|
+
default="my-agent"
|
|
91
|
+
)
|
|
92
|
+
if validate_agent_name(name):
|
|
93
|
+
break
|
|
94
|
+
console.print("[red]Invalid name. Use lowercase letters, numbers, and hyphens only.[/red]")
|
|
95
|
+
|
|
96
|
+
# Display name
|
|
97
|
+
if not display_name:
|
|
98
|
+
default_display = name.replace("-", " ").replace("_", " ").title()
|
|
99
|
+
display_name = Prompt.ask(
|
|
100
|
+
"[bold]Display name[/bold]",
|
|
101
|
+
default=default_display
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
# Description
|
|
105
|
+
if not description:
|
|
106
|
+
description = Prompt.ask(
|
|
107
|
+
"[bold]Description[/bold]",
|
|
108
|
+
default=f"A helpful {display_name} agent"
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
# Category selection
|
|
112
|
+
if not category:
|
|
113
|
+
console.print("\n[bold]Select a category:[/bold]")
|
|
114
|
+
for i, cat in enumerate(CATEGORIES, 1):
|
|
115
|
+
console.print(f" [{i}] {cat}")
|
|
116
|
+
|
|
117
|
+
while True:
|
|
118
|
+
choice = Prompt.ask("Enter number", default="9")
|
|
119
|
+
try:
|
|
120
|
+
idx = int(choice) - 1
|
|
121
|
+
if 0 <= idx < len(CATEGORIES):
|
|
122
|
+
category = CATEGORIES[idx]
|
|
123
|
+
break
|
|
124
|
+
except ValueError:
|
|
125
|
+
pass
|
|
126
|
+
console.print("[red]Invalid selection. Please enter a number.[/red]")
|
|
127
|
+
|
|
128
|
+
# Template selection
|
|
129
|
+
if not template:
|
|
130
|
+
console.print("\n[bold]Select a template:[/bold]")
|
|
131
|
+
for i, (key, desc) in enumerate(TEMPLATES.items(), 1):
|
|
132
|
+
console.print(f" [{i}] {key}: {desc}")
|
|
133
|
+
|
|
134
|
+
while True:
|
|
135
|
+
choice = Prompt.ask("Enter number", default="1")
|
|
136
|
+
try:
|
|
137
|
+
idx = int(choice) - 1
|
|
138
|
+
if 0 <= idx < len(TEMPLATES):
|
|
139
|
+
template = list(TEMPLATES.keys())[idx]
|
|
140
|
+
break
|
|
141
|
+
except ValueError:
|
|
142
|
+
pass
|
|
143
|
+
console.print("[red]Invalid selection. Please enter a number.[/red]")
|
|
144
|
+
|
|
145
|
+
# Validate all required fields
|
|
146
|
+
if not all([name, display_name, description, category, template]):
|
|
147
|
+
console.print("[red]Error: All fields are required. Use --yes for non-interactive mode only with all options.[/red]")
|
|
148
|
+
raise typer.Exit(code=1)
|
|
149
|
+
|
|
150
|
+
if not validate_agent_name(name):
|
|
151
|
+
console.print("[red]Error: Agent name must be lowercase with hyphens/underscores only.[/red]")
|
|
152
|
+
raise typer.Exit(code=1)
|
|
153
|
+
|
|
154
|
+
if category not in CATEGORIES:
|
|
155
|
+
console.print(f"[red]Error: Invalid category. Choose from: {', '.join(CATEGORIES)}[/red]")
|
|
156
|
+
raise typer.Exit(code=1)
|
|
157
|
+
|
|
158
|
+
if template not in TEMPLATES:
|
|
159
|
+
console.print(f"[red]Error: Invalid template. Choose from: {', '.join(TEMPLATES.keys())}[/red]")
|
|
160
|
+
raise typer.Exit(code=1)
|
|
161
|
+
|
|
162
|
+
# Confirmation
|
|
163
|
+
if not non_interactive:
|
|
164
|
+
console.print("\n[bold]Creating agent with:[/bold]")
|
|
165
|
+
console.print(f" Name: {name}")
|
|
166
|
+
console.print(f" Display Name: {display_name}")
|
|
167
|
+
console.print(f" Description: {description}")
|
|
168
|
+
console.print(f" Category: {category}")
|
|
169
|
+
console.print(f" Template: {template}")
|
|
170
|
+
|
|
171
|
+
if not Confirm.ask("\nProceed?", default=True):
|
|
172
|
+
console.print("[yellow]Cancelled.[/yellow]")
|
|
173
|
+
raise typer.Exit(code=0)
|
|
174
|
+
|
|
175
|
+
# Import and call the MCP tool function directly
|
|
176
|
+
try:
|
|
177
|
+
from ..tools.project.initialize_project import initialize_project
|
|
178
|
+
|
|
179
|
+
# Set environment variable for directory if specified
|
|
180
|
+
import os
|
|
181
|
+
if directory:
|
|
182
|
+
os.environ["A4E_WORKSPACE"] = str(Path(directory).resolve())
|
|
183
|
+
|
|
184
|
+
result = initialize_project(
|
|
185
|
+
name=name,
|
|
186
|
+
display_name=display_name,
|
|
187
|
+
description=description,
|
|
188
|
+
category=category,
|
|
189
|
+
template=template,
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
if result.get("success"):
|
|
193
|
+
console.print(f"\n[green]✓ Agent '{name}' created successfully![/green]")
|
|
194
|
+
console.print(f" Path: {result.get('path')}")
|
|
195
|
+
|
|
196
|
+
console.print("\n[bold]Next steps:[/bold]")
|
|
197
|
+
for step in result.get("next_steps", []):
|
|
198
|
+
console.print(f" • {step}")
|
|
199
|
+
|
|
200
|
+
console.print(f"\n cd {name}")
|
|
201
|
+
console.print(" a4e dev start")
|
|
202
|
+
else:
|
|
203
|
+
console.print(f"\n[red]Error: {result.get('error')}[/red]")
|
|
204
|
+
raise typer.Exit(code=1)
|
|
205
|
+
|
|
206
|
+
except ImportError as e:
|
|
207
|
+
console.print(f"[red]Error importing tools: {e}[/red]")
|
|
208
|
+
raise typer.Exit(code=1)
|
|
209
|
+
except Exception as e:
|
|
210
|
+
console.print(f"[red]Error: {e}[/red]")
|
|
211
|
+
raise typer.Exit(code=1)
|