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/mcp.py
ADDED
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MCP configuration commands for A4E.
|
|
3
|
+
|
|
4
|
+
This module provides commands to configure the A4E MCP server
|
|
5
|
+
for different IDEs like Cursor, Claude Code, and Antigravity.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
a4e mcp setup cursor
|
|
9
|
+
a4e mcp setup claude-code
|
|
10
|
+
a4e mcp setup antigravity
|
|
11
|
+
a4e mcp show cursor
|
|
12
|
+
a4e mcp remove cursor
|
|
13
|
+
a4e mcp test
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import json
|
|
17
|
+
import sys
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
import typer
|
|
21
|
+
|
|
22
|
+
app = typer.Typer(
|
|
23
|
+
help="Configure MCP server for IDEs",
|
|
24
|
+
no_args_is_help=True,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# ============================================================================
|
|
29
|
+
# Configuration Paths
|
|
30
|
+
# ============================================================================
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _get_platform() -> str:
|
|
34
|
+
"""Get the current platform: darwin, win32, or linux."""
|
|
35
|
+
return sys.platform
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _get_ide_configs() -> dict:
|
|
39
|
+
"""
|
|
40
|
+
Get IDE configuration paths and formats.
|
|
41
|
+
|
|
42
|
+
Returns dict with IDE name as key and:
|
|
43
|
+
- path: Path to config file
|
|
44
|
+
- servers_key: Key name for MCP servers in config
|
|
45
|
+
- name: Display name for the IDE
|
|
46
|
+
"""
|
|
47
|
+
platform = _get_platform()
|
|
48
|
+
home = Path.home()
|
|
49
|
+
|
|
50
|
+
# Antigravity config path varies by OS
|
|
51
|
+
if platform == "win32":
|
|
52
|
+
antigravity_path = home / ".gemini" / "antigravity" / "mcp_config.json"
|
|
53
|
+
else: # macOS and Linux
|
|
54
|
+
antigravity_path = home / ".gemini" / "antigravity" / "mcp_config.json"
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
"cursor": {
|
|
58
|
+
"path": home / ".cursor" / "mcp.json",
|
|
59
|
+
"servers_key": "mcpServers",
|
|
60
|
+
"name": "Cursor",
|
|
61
|
+
},
|
|
62
|
+
"claude-code": {
|
|
63
|
+
"path": home / ".claude.json",
|
|
64
|
+
"servers_key": "mcpServers",
|
|
65
|
+
"name": "Claude Code",
|
|
66
|
+
},
|
|
67
|
+
"antigravity": {
|
|
68
|
+
"path": antigravity_path,
|
|
69
|
+
"servers_key": "mcpServers",
|
|
70
|
+
"name": "Antigravity",
|
|
71
|
+
},
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _get_a4e_server_config(ide: str) -> dict:
|
|
76
|
+
"""
|
|
77
|
+
Generate the MCP server configuration for A4E.
|
|
78
|
+
|
|
79
|
+
Uses sys.executable to get the exact Python that has a4e installed.
|
|
80
|
+
This ensures the config works regardless of how Python was installed
|
|
81
|
+
(pyenv, system, venv, homebrew, etc.)
|
|
82
|
+
|
|
83
|
+
The env configuration varies by IDE:
|
|
84
|
+
- Cursor: Uses ${workspaceFolder} which it expands
|
|
85
|
+
- Claude Code / Antigravity: No workspace variable, uses cwd
|
|
86
|
+
|
|
87
|
+
Args:
|
|
88
|
+
ide: The IDE identifier (cursor, claude-code, antigravity)
|
|
89
|
+
"""
|
|
90
|
+
base_config = {
|
|
91
|
+
"command": sys.executable,
|
|
92
|
+
"args": ["-m", "a4e.server"],
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
# IDEs that support ${workspaceFolder}
|
|
96
|
+
if ide == "cursor":
|
|
97
|
+
base_config["env"] = {
|
|
98
|
+
"A4E_WORKSPACE": "${workspaceFolder}"
|
|
99
|
+
}
|
|
100
|
+
# Claude Code and Antigravity: no workspace variable
|
|
101
|
+
# The server will use cwd or require explicit project_path in tool calls
|
|
102
|
+
|
|
103
|
+
return base_config
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _get_supported_ides() -> list[str]:
|
|
107
|
+
"""Get list of supported IDE names."""
|
|
108
|
+
return list(_get_ide_configs().keys())
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
# ============================================================================
|
|
112
|
+
# Helper Functions
|
|
113
|
+
# ============================================================================
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _load_config(config_path: Path) -> tuple[dict, bool]:
|
|
117
|
+
"""
|
|
118
|
+
Load existing config from path.
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
Tuple of (config_dict, had_error)
|
|
122
|
+
If file doesn't exist, returns empty dict with no error.
|
|
123
|
+
If file has invalid JSON, returns empty dict with error=True.
|
|
124
|
+
"""
|
|
125
|
+
if not config_path.exists():
|
|
126
|
+
return {}, False
|
|
127
|
+
|
|
128
|
+
try:
|
|
129
|
+
content = config_path.read_text().strip()
|
|
130
|
+
if not content:
|
|
131
|
+
return {}, False
|
|
132
|
+
return json.loads(content), False
|
|
133
|
+
except json.JSONDecodeError:
|
|
134
|
+
return {}, True
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _save_config(config_path: Path, config: dict) -> None:
|
|
138
|
+
"""Save config to path, creating parent directories if needed."""
|
|
139
|
+
config_path.parent.mkdir(parents=True, exist_ok=True)
|
|
140
|
+
config_path.write_text(json.dumps(config, indent=2) + "\n")
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
# ============================================================================
|
|
144
|
+
# Commands
|
|
145
|
+
# ============================================================================
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@app.command()
|
|
149
|
+
def setup(
|
|
150
|
+
ide: str = typer.Argument(
|
|
151
|
+
...,
|
|
152
|
+
help="IDE to configure: cursor, claude-code, antigravity"
|
|
153
|
+
),
|
|
154
|
+
dry_run: bool = typer.Option(
|
|
155
|
+
False, "--dry-run", "-n",
|
|
156
|
+
help="Show what would be done without making changes"
|
|
157
|
+
),
|
|
158
|
+
force: bool = typer.Option(
|
|
159
|
+
False, "--force", "-f",
|
|
160
|
+
help="Overwrite existing A4E configuration"
|
|
161
|
+
),
|
|
162
|
+
):
|
|
163
|
+
"""
|
|
164
|
+
Configure A4E MCP server for your IDE.
|
|
165
|
+
|
|
166
|
+
This writes the necessary configuration so your IDE can use
|
|
167
|
+
A4E tools through the Model Context Protocol (MCP).
|
|
168
|
+
|
|
169
|
+
Examples:
|
|
170
|
+
|
|
171
|
+
a4e mcp setup cursor
|
|
172
|
+
|
|
173
|
+
a4e mcp setup claude-code
|
|
174
|
+
|
|
175
|
+
a4e mcp setup antigravity
|
|
176
|
+
|
|
177
|
+
a4e mcp setup cursor --dry-run
|
|
178
|
+
|
|
179
|
+
a4e mcp setup cursor --force
|
|
180
|
+
"""
|
|
181
|
+
ide_configs = _get_ide_configs()
|
|
182
|
+
ide_lower = ide.lower()
|
|
183
|
+
|
|
184
|
+
if ide_lower not in ide_configs:
|
|
185
|
+
supported = ", ".join(_get_supported_ides())
|
|
186
|
+
typer.echo(f"Error: Unknown IDE '{ide}'", err=True)
|
|
187
|
+
typer.echo(f"Supported IDEs: {supported}", err=True)
|
|
188
|
+
raise typer.Exit(1)
|
|
189
|
+
|
|
190
|
+
config_info = ide_configs[ide_lower]
|
|
191
|
+
config_path: Path = config_info["path"]
|
|
192
|
+
servers_key: str = config_info["servers_key"]
|
|
193
|
+
ide_name: str = config_info["name"]
|
|
194
|
+
a4e_config = _get_a4e_server_config(ide_lower)
|
|
195
|
+
|
|
196
|
+
# Dry run mode - show what would happen
|
|
197
|
+
if dry_run:
|
|
198
|
+
typer.echo(f"Would configure A4E for {ide_name}:")
|
|
199
|
+
typer.echo()
|
|
200
|
+
typer.echo(f" Config file: {config_path}")
|
|
201
|
+
typer.echo(f" Python path: {sys.executable}")
|
|
202
|
+
typer.echo()
|
|
203
|
+
typer.echo(" Configuration to add:")
|
|
204
|
+
preview = {servers_key: {"a4e": a4e_config}}
|
|
205
|
+
typer.echo(json.dumps(preview, indent=2))
|
|
206
|
+
return
|
|
207
|
+
|
|
208
|
+
# Load existing config
|
|
209
|
+
existing, had_json_error = _load_config(config_path)
|
|
210
|
+
|
|
211
|
+
# Always create backup if file exists (before any modification)
|
|
212
|
+
if config_path.exists() and not had_json_error:
|
|
213
|
+
backup_path = config_path.with_suffix(".json.backup")
|
|
214
|
+
backup_path.write_text(config_path.read_text())
|
|
215
|
+
typer.echo(f"Backup created: {backup_path}")
|
|
216
|
+
|
|
217
|
+
if had_json_error:
|
|
218
|
+
# Backup corrupted config
|
|
219
|
+
backup_path = config_path.with_suffix(".json.backup")
|
|
220
|
+
typer.echo(f"Warning: Invalid JSON in {config_path}", err=True)
|
|
221
|
+
typer.echo(f"Creating backup at {backup_path}", err=True)
|
|
222
|
+
|
|
223
|
+
if config_path.exists():
|
|
224
|
+
backup_path.write_text(config_path.read_text())
|
|
225
|
+
|
|
226
|
+
existing = {}
|
|
227
|
+
|
|
228
|
+
# Ensure servers key exists
|
|
229
|
+
existing.setdefault(servers_key, {})
|
|
230
|
+
|
|
231
|
+
# Check if a4e already configured
|
|
232
|
+
if "a4e" in existing[servers_key]:
|
|
233
|
+
if not force:
|
|
234
|
+
typer.echo(f"A4E is already configured for {ide_name}")
|
|
235
|
+
typer.echo()
|
|
236
|
+
typer.echo("Current configuration:")
|
|
237
|
+
typer.echo(json.dumps(existing[servers_key]["a4e"], indent=2))
|
|
238
|
+
typer.echo()
|
|
239
|
+
typer.echo("Use --force to overwrite, or --dry-run to preview")
|
|
240
|
+
raise typer.Exit(1)
|
|
241
|
+
else:
|
|
242
|
+
typer.echo("Overwriting existing A4E configuration...")
|
|
243
|
+
|
|
244
|
+
# Add/update a4e config
|
|
245
|
+
existing[servers_key]["a4e"] = a4e_config
|
|
246
|
+
|
|
247
|
+
# Save config
|
|
248
|
+
_save_config(config_path, existing)
|
|
249
|
+
|
|
250
|
+
typer.echo(f"A4E MCP configured for {ide_name}")
|
|
251
|
+
typer.echo()
|
|
252
|
+
typer.echo(f" Config file: {config_path}")
|
|
253
|
+
typer.echo(f" Python: {sys.executable}")
|
|
254
|
+
typer.echo()
|
|
255
|
+
|
|
256
|
+
# IDE-specific notes
|
|
257
|
+
if ide_lower == "cursor":
|
|
258
|
+
typer.echo(" Workspace: Uses ${workspaceFolder} (auto-detected)")
|
|
259
|
+
else:
|
|
260
|
+
typer.echo(" Workspace: Uses current directory or specify project_path in tool calls")
|
|
261
|
+
|
|
262
|
+
typer.echo()
|
|
263
|
+
typer.echo(f"Restart {ide_name} to activate the MCP server.")
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
@app.command()
|
|
267
|
+
def show(
|
|
268
|
+
ide: str = typer.Argument(
|
|
269
|
+
...,
|
|
270
|
+
help="IDE to show config for: cursor, claude-code, antigravity"
|
|
271
|
+
)
|
|
272
|
+
):
|
|
273
|
+
"""
|
|
274
|
+
Show current MCP configuration for an IDE.
|
|
275
|
+
|
|
276
|
+
Example:
|
|
277
|
+
|
|
278
|
+
a4e mcp show cursor
|
|
279
|
+
"""
|
|
280
|
+
ide_configs = _get_ide_configs()
|
|
281
|
+
ide_lower = ide.lower()
|
|
282
|
+
|
|
283
|
+
if ide_lower not in ide_configs:
|
|
284
|
+
supported = ", ".join(_get_supported_ides())
|
|
285
|
+
typer.echo(f"Error: Unknown IDE '{ide}'", err=True)
|
|
286
|
+
typer.echo(f"Supported IDEs: {supported}", err=True)
|
|
287
|
+
raise typer.Exit(1)
|
|
288
|
+
|
|
289
|
+
config_info = ide_configs[ide_lower]
|
|
290
|
+
config_path: Path = config_info["path"]
|
|
291
|
+
servers_key: str = config_info["servers_key"]
|
|
292
|
+
ide_name: str = config_info["name"]
|
|
293
|
+
|
|
294
|
+
if not config_path.exists():
|
|
295
|
+
typer.echo(f"No MCP config found for {ide_name}")
|
|
296
|
+
typer.echo(f"Expected path: {config_path}")
|
|
297
|
+
typer.echo()
|
|
298
|
+
typer.echo(f"Run: a4e mcp setup {ide}")
|
|
299
|
+
return
|
|
300
|
+
|
|
301
|
+
config, had_error = _load_config(config_path)
|
|
302
|
+
|
|
303
|
+
if had_error:
|
|
304
|
+
typer.echo(f"Error: Invalid JSON in {config_path}", err=True)
|
|
305
|
+
raise typer.Exit(1)
|
|
306
|
+
|
|
307
|
+
typer.echo(f"MCP configuration for {ide_name}")
|
|
308
|
+
typer.echo(f"File: {config_path}")
|
|
309
|
+
typer.echo()
|
|
310
|
+
typer.echo(json.dumps(config, indent=2))
|
|
311
|
+
|
|
312
|
+
# Check A4E status
|
|
313
|
+
if servers_key in config and "a4e" in config[servers_key]:
|
|
314
|
+
typer.echo()
|
|
315
|
+
typer.echo("A4E status: Configured")
|
|
316
|
+
else:
|
|
317
|
+
typer.echo()
|
|
318
|
+
typer.echo("A4E status: Not configured")
|
|
319
|
+
typer.echo(f"Run: a4e mcp setup {ide}")
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
@app.command()
|
|
323
|
+
def remove(
|
|
324
|
+
ide: str = typer.Argument(
|
|
325
|
+
...,
|
|
326
|
+
help="IDE to remove A4E config from"
|
|
327
|
+
),
|
|
328
|
+
):
|
|
329
|
+
"""
|
|
330
|
+
Remove A4E MCP configuration from an IDE.
|
|
331
|
+
|
|
332
|
+
This removes only the A4E configuration, preserving other MCP servers.
|
|
333
|
+
|
|
334
|
+
Example:
|
|
335
|
+
|
|
336
|
+
a4e mcp remove cursor
|
|
337
|
+
"""
|
|
338
|
+
ide_configs = _get_ide_configs()
|
|
339
|
+
ide_lower = ide.lower()
|
|
340
|
+
|
|
341
|
+
if ide_lower not in ide_configs:
|
|
342
|
+
supported = ", ".join(_get_supported_ides())
|
|
343
|
+
typer.echo(f"Error: Unknown IDE '{ide}'", err=True)
|
|
344
|
+
typer.echo(f"Supported IDEs: {supported}", err=True)
|
|
345
|
+
raise typer.Exit(1)
|
|
346
|
+
|
|
347
|
+
config_info = ide_configs[ide_lower]
|
|
348
|
+
config_path: Path = config_info["path"]
|
|
349
|
+
servers_key: str = config_info["servers_key"]
|
|
350
|
+
ide_name: str = config_info["name"]
|
|
351
|
+
|
|
352
|
+
if not config_path.exists():
|
|
353
|
+
typer.echo(f"No MCP config found for {ide_name}")
|
|
354
|
+
return
|
|
355
|
+
|
|
356
|
+
config, had_error = _load_config(config_path)
|
|
357
|
+
|
|
358
|
+
if had_error:
|
|
359
|
+
typer.echo(f"Error: Invalid JSON in {config_path}", err=True)
|
|
360
|
+
raise typer.Exit(1)
|
|
361
|
+
|
|
362
|
+
# Check if a4e is configured
|
|
363
|
+
if servers_key not in config or "a4e" not in config[servers_key]:
|
|
364
|
+
typer.echo(f"A4E is not configured for {ide_name}")
|
|
365
|
+
return
|
|
366
|
+
|
|
367
|
+
# Remove a4e
|
|
368
|
+
del config[servers_key]["a4e"]
|
|
369
|
+
|
|
370
|
+
# Clean up empty servers dict
|
|
371
|
+
if not config[servers_key]:
|
|
372
|
+
del config[servers_key]
|
|
373
|
+
|
|
374
|
+
# Save config
|
|
375
|
+
_save_config(config_path, config)
|
|
376
|
+
|
|
377
|
+
typer.echo(f"A4E removed from {ide_name} configuration")
|
|
378
|
+
typer.echo()
|
|
379
|
+
typer.echo(f"Restart {ide_name} to apply changes.")
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
@app.command()
|
|
383
|
+
def test():
|
|
384
|
+
"""
|
|
385
|
+
Test that the A4E MCP server can start correctly.
|
|
386
|
+
|
|
387
|
+
This verifies that:
|
|
388
|
+
- The server module can be imported
|
|
389
|
+
- The Python path is correct
|
|
390
|
+
- Dependencies are installed
|
|
391
|
+
|
|
392
|
+
Useful for debugging configuration issues.
|
|
393
|
+
"""
|
|
394
|
+
import subprocess
|
|
395
|
+
|
|
396
|
+
typer.echo("Testing A4E MCP server...")
|
|
397
|
+
typer.echo()
|
|
398
|
+
typer.echo(f" Python: {sys.executable}")
|
|
399
|
+
typer.echo(f" Command: {sys.executable} -m a4e.server")
|
|
400
|
+
typer.echo()
|
|
401
|
+
|
|
402
|
+
# Test 1: Module import
|
|
403
|
+
typer.echo("1. Testing module import...")
|
|
404
|
+
try:
|
|
405
|
+
# Test that the module can be imported
|
|
406
|
+
import importlib
|
|
407
|
+
importlib.import_module("a4e.server")
|
|
408
|
+
typer.echo(" Module imports correctly")
|
|
409
|
+
except ImportError as e:
|
|
410
|
+
typer.echo(f" Error: Failed to import a4e.server: {e}", err=True)
|
|
411
|
+
raise typer.Exit(1)
|
|
412
|
+
|
|
413
|
+
# Test 2: FastMCP instance
|
|
414
|
+
typer.echo("2. Testing MCP server instance...")
|
|
415
|
+
try:
|
|
416
|
+
from a4e.core import mcp
|
|
417
|
+
tools = list(mcp._tool_manager._tools.keys()) if hasattr(mcp, '_tool_manager') else []
|
|
418
|
+
typer.echo(" MCP server initialized")
|
|
419
|
+
if tools:
|
|
420
|
+
typer.echo(f" Tools registered: {len(tools)}")
|
|
421
|
+
except Exception as e:
|
|
422
|
+
typer.echo(f" Warning: Could not inspect MCP instance: {e}", err=True)
|
|
423
|
+
|
|
424
|
+
# Test 3: Server process start
|
|
425
|
+
typer.echo("3. Testing server process...")
|
|
426
|
+
try:
|
|
427
|
+
# Run with --help to quickly test if it starts
|
|
428
|
+
result = subprocess.run(
|
|
429
|
+
[sys.executable, "-c", "from a4e.server import main; print('OK')"],
|
|
430
|
+
capture_output=True,
|
|
431
|
+
text=True,
|
|
432
|
+
timeout=10,
|
|
433
|
+
)
|
|
434
|
+
|
|
435
|
+
if result.returncode == 0 and "OK" in result.stdout:
|
|
436
|
+
typer.echo(" Server process starts correctly")
|
|
437
|
+
else:
|
|
438
|
+
typer.echo(" Warning: Unexpected output", err=True)
|
|
439
|
+
if result.stderr:
|
|
440
|
+
typer.echo(f" stderr: {result.stderr}", err=True)
|
|
441
|
+
except subprocess.TimeoutExpired:
|
|
442
|
+
typer.echo(" Server process timed out (may be normal for stdio server)")
|
|
443
|
+
except Exception as e:
|
|
444
|
+
typer.echo(f" Error testing server process: {e}", err=True)
|
|
445
|
+
raise typer.Exit(1)
|
|
446
|
+
|
|
447
|
+
typer.echo()
|
|
448
|
+
typer.echo("All tests passed. MCP server is ready.")
|
|
449
|
+
typer.echo()
|
|
450
|
+
typer.echo("Next steps:")
|
|
451
|
+
typer.echo(" 1. Run: a4e mcp setup <ide>")
|
|
452
|
+
typer.echo(" 2. Restart your IDE")
|
|
453
|
+
typer.echo(" 3. Start using A4E tools!")
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
@app.command()
|
|
457
|
+
def path(
|
|
458
|
+
ide: str = typer.Argument(
|
|
459
|
+
...,
|
|
460
|
+
help="IDE to show config path for"
|
|
461
|
+
)
|
|
462
|
+
):
|
|
463
|
+
"""
|
|
464
|
+
Show the config file path for an IDE.
|
|
465
|
+
|
|
466
|
+
Useful for manual configuration or debugging.
|
|
467
|
+
|
|
468
|
+
Example:
|
|
469
|
+
|
|
470
|
+
a4e mcp path cursor
|
|
471
|
+
"""
|
|
472
|
+
ide_configs = _get_ide_configs()
|
|
473
|
+
ide_lower = ide.lower()
|
|
474
|
+
|
|
475
|
+
if ide_lower not in ide_configs:
|
|
476
|
+
supported = ", ".join(_get_supported_ides())
|
|
477
|
+
typer.echo(f"Error: Unknown IDE '{ide}'", err=True)
|
|
478
|
+
typer.echo(f"Supported IDEs: {supported}", err=True)
|
|
479
|
+
raise typer.Exit(1)
|
|
480
|
+
|
|
481
|
+
config_path = ide_configs[ide_lower]["path"]
|
|
482
|
+
typer.echo(str(config_path))
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
@app.command(name="list")
|
|
486
|
+
def list_ides():
|
|
487
|
+
"""
|
|
488
|
+
List all supported IDEs.
|
|
489
|
+
|
|
490
|
+
Example:
|
|
491
|
+
|
|
492
|
+
a4e mcp list
|
|
493
|
+
"""
|
|
494
|
+
ide_configs = _get_ide_configs()
|
|
495
|
+
|
|
496
|
+
typer.echo("Supported IDEs:")
|
|
497
|
+
typer.echo()
|
|
498
|
+
|
|
499
|
+
for ide_key, config in ide_configs.items():
|
|
500
|
+
path_exists = "exists" if config["path"].exists() else "not found"
|
|
501
|
+
typer.echo(f" {ide_key}")
|
|
502
|
+
typer.echo(f" Name: {config['name']}")
|
|
503
|
+
typer.echo(f" Config: {config['path']} ({path_exists})")
|
|
504
|
+
typer.echo()
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# a4e/cli_commands/remove.py
|
|
2
|
+
"""
|
|
3
|
+
Commands for removing tools, views, and skills from an agent.
|
|
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.prompt import Confirm
|
|
12
|
+
|
|
13
|
+
console = Console()
|
|
14
|
+
|
|
15
|
+
# Create a 'Typer' app for the 'remove' command group
|
|
16
|
+
app = typer.Typer(
|
|
17
|
+
no_args_is_help=True,
|
|
18
|
+
help="Remove tools, views, or skills from your agent.",
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def find_agent_dir(agent_name: Optional[str] = None) -> Optional[Path]:
|
|
23
|
+
"""Find the agent directory from name or current working directory."""
|
|
24
|
+
cwd = Path.cwd()
|
|
25
|
+
|
|
26
|
+
if agent_name:
|
|
27
|
+
if Path(agent_name).is_absolute() and Path(agent_name).exists():
|
|
28
|
+
return Path(agent_name)
|
|
29
|
+
if (cwd / agent_name).exists():
|
|
30
|
+
return cwd / agent_name
|
|
31
|
+
agent_store = cwd / "file-store" / "agent-store" / agent_name
|
|
32
|
+
if agent_store.exists():
|
|
33
|
+
return agent_store
|
|
34
|
+
return None
|
|
35
|
+
|
|
36
|
+
if (cwd / "agent.py").exists() and (cwd / "metadata.json").exists():
|
|
37
|
+
return cwd
|
|
38
|
+
|
|
39
|
+
return None
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@app.command("tool")
|
|
43
|
+
def remove_tool(
|
|
44
|
+
tool_name: str = typer.Argument(..., help="Name of the tool to remove"),
|
|
45
|
+
agent_name: Optional[str] = typer.Option(
|
|
46
|
+
None, "--agent", "-a", help="Agent name/path (defaults to current directory)"
|
|
47
|
+
),
|
|
48
|
+
yes: bool = typer.Option(
|
|
49
|
+
False, "--yes", "-y", help="Skip confirmation prompt"
|
|
50
|
+
),
|
|
51
|
+
) -> None:
|
|
52
|
+
"""
|
|
53
|
+
Remove a tool from the agent.
|
|
54
|
+
|
|
55
|
+
Example:
|
|
56
|
+
a4e remove tool calculate_bmi
|
|
57
|
+
a4e remove tool calculate_bmi --yes
|
|
58
|
+
"""
|
|
59
|
+
agent_dir = find_agent_dir(agent_name)
|
|
60
|
+
if not agent_dir:
|
|
61
|
+
console.print("[red]Error: Not in an agent directory. Use --agent to specify the agent.[/red]")
|
|
62
|
+
raise typer.Exit(code=1)
|
|
63
|
+
|
|
64
|
+
# Confirm removal
|
|
65
|
+
if not yes:
|
|
66
|
+
if not Confirm.ask(f"Remove tool '{tool_name}'?", default=False):
|
|
67
|
+
console.print("[yellow]Cancelled.[/yellow]")
|
|
68
|
+
raise typer.Exit(code=0)
|
|
69
|
+
|
|
70
|
+
try:
|
|
71
|
+
from ..tools.agent_tools.remove_tool import remove_tool as mcp_remove_tool
|
|
72
|
+
|
|
73
|
+
result = mcp_remove_tool(
|
|
74
|
+
tool_name=tool_name,
|
|
75
|
+
agent_name=str(agent_dir),
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
if result.get("success"):
|
|
79
|
+
console.print(f"[green]✓ Tool '{tool_name}' removed![/green]")
|
|
80
|
+
console.print(f" Removed: {result.get('removed_file')}")
|
|
81
|
+
else:
|
|
82
|
+
console.print(f"[red]Error: {result.get('error')}[/red]")
|
|
83
|
+
raise typer.Exit(code=1)
|
|
84
|
+
|
|
85
|
+
except ImportError as e:
|
|
86
|
+
console.print(f"[red]Error importing tools: {e}[/red]")
|
|
87
|
+
raise typer.Exit(code=1)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@app.command("view")
|
|
91
|
+
def remove_view(
|
|
92
|
+
view_id: str = typer.Argument(..., help="ID of the view to remove"),
|
|
93
|
+
agent_name: Optional[str] = typer.Option(
|
|
94
|
+
None, "--agent", "-a", help="Agent name/path (defaults to current directory)"
|
|
95
|
+
),
|
|
96
|
+
yes: bool = typer.Option(
|
|
97
|
+
False, "--yes", "-y", help="Skip confirmation prompt"
|
|
98
|
+
),
|
|
99
|
+
) -> None:
|
|
100
|
+
"""
|
|
101
|
+
Remove a view from the agent.
|
|
102
|
+
|
|
103
|
+
Note: The 'welcome' view cannot be removed as it's required.
|
|
104
|
+
|
|
105
|
+
Example:
|
|
106
|
+
a4e remove view results-display
|
|
107
|
+
a4e remove view results-display --yes
|
|
108
|
+
"""
|
|
109
|
+
agent_dir = find_agent_dir(agent_name)
|
|
110
|
+
if not agent_dir:
|
|
111
|
+
console.print("[red]Error: Not in an agent directory. Use --agent to specify the agent.[/red]")
|
|
112
|
+
raise typer.Exit(code=1)
|
|
113
|
+
|
|
114
|
+
# Warn about welcome view
|
|
115
|
+
if view_id == "welcome":
|
|
116
|
+
console.print("[red]Error: Cannot remove the 'welcome' view - it is required for all agents.[/red]")
|
|
117
|
+
raise typer.Exit(code=1)
|
|
118
|
+
|
|
119
|
+
# Confirm removal
|
|
120
|
+
if not yes:
|
|
121
|
+
if not Confirm.ask(f"Remove view '{view_id}'?", default=False):
|
|
122
|
+
console.print("[yellow]Cancelled.[/yellow]")
|
|
123
|
+
raise typer.Exit(code=0)
|
|
124
|
+
|
|
125
|
+
try:
|
|
126
|
+
from ..tools.views.remove_view import remove_view as mcp_remove_view
|
|
127
|
+
|
|
128
|
+
result = mcp_remove_view(
|
|
129
|
+
view_id=view_id,
|
|
130
|
+
agent_name=str(agent_dir),
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
if result.get("success"):
|
|
134
|
+
console.print(f"[green]✓ View '{view_id}' removed![/green]")
|
|
135
|
+
console.print(f" Removed: {result.get('removed_folder')}")
|
|
136
|
+
else:
|
|
137
|
+
console.print(f"[red]Error: {result.get('error')}[/red]")
|
|
138
|
+
raise typer.Exit(code=1)
|
|
139
|
+
|
|
140
|
+
except ImportError as e:
|
|
141
|
+
console.print(f"[red]Error importing tools: {e}[/red]")
|
|
142
|
+
raise typer.Exit(code=1)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
@app.command("skill")
|
|
146
|
+
def remove_skill(
|
|
147
|
+
skill_id: str = typer.Argument(..., help="ID of the skill to remove"),
|
|
148
|
+
agent_name: Optional[str] = typer.Option(
|
|
149
|
+
None, "--agent", "-a", help="Agent name/path (defaults to current directory)"
|
|
150
|
+
),
|
|
151
|
+
yes: bool = typer.Option(
|
|
152
|
+
False, "--yes", "-y", help="Skip confirmation prompt"
|
|
153
|
+
),
|
|
154
|
+
) -> None:
|
|
155
|
+
"""
|
|
156
|
+
Remove a skill from the agent.
|
|
157
|
+
|
|
158
|
+
Note: The 'show_welcome' skill cannot be removed as it's required.
|
|
159
|
+
|
|
160
|
+
Example:
|
|
161
|
+
a4e remove skill show_results
|
|
162
|
+
a4e remove skill show_results --yes
|
|
163
|
+
"""
|
|
164
|
+
agent_dir = find_agent_dir(agent_name)
|
|
165
|
+
if not agent_dir:
|
|
166
|
+
console.print("[red]Error: Not in an agent directory. Use --agent to specify the agent.[/red]")
|
|
167
|
+
raise typer.Exit(code=1)
|
|
168
|
+
|
|
169
|
+
# Warn about show_welcome skill
|
|
170
|
+
if skill_id == "show_welcome":
|
|
171
|
+
console.print("[red]Error: Cannot remove the 'show_welcome' skill - it is required for all agents.[/red]")
|
|
172
|
+
raise typer.Exit(code=1)
|
|
173
|
+
|
|
174
|
+
# Confirm removal
|
|
175
|
+
if not yes:
|
|
176
|
+
if not Confirm.ask(f"Remove skill '{skill_id}'?", default=False):
|
|
177
|
+
console.print("[yellow]Cancelled.[/yellow]")
|
|
178
|
+
raise typer.Exit(code=0)
|
|
179
|
+
|
|
180
|
+
try:
|
|
181
|
+
from ..tools.skills.remove_skill import remove_skill as mcp_remove_skill
|
|
182
|
+
|
|
183
|
+
result = mcp_remove_skill(
|
|
184
|
+
skill_id=skill_id,
|
|
185
|
+
agent_name=str(agent_dir),
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
if result.get("success"):
|
|
189
|
+
console.print(f"[green]✓ Skill '{skill_id}' removed![/green]")
|
|
190
|
+
console.print(f" Removed: {result.get('removed_folder')}")
|
|
191
|
+
else:
|
|
192
|
+
console.print(f"[red]Error: {result.get('error')}[/red]")
|
|
193
|
+
raise typer.Exit(code=1)
|
|
194
|
+
|
|
195
|
+
except ImportError as e:
|
|
196
|
+
console.print(f"[red]Error importing tools: {e}[/red]")
|
|
197
|
+
raise typer.Exit(code=1)
|