Skip to content

Commit 8bc257d

Browse files
authored
Merge pull request #25 from zhujian0805/main
add goose support and multi-model selection on menu
2 parents d77d0e6 + 9519adc commit 8bc257d

File tree

13 files changed

+1050
-82
lines changed

13 files changed

+1050
-82
lines changed

README.md

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
**One CLI to Rule Them All.**
99
<br>
10-
Tired of juggling multiple AI coding assistants? **CAM** is a unified Python CLI to manage configurations, prompts, skills, and plugins for **13 AI assistants** including Claude, Codex, Gemini, Qwen, Copilot, and more from a single, polished terminal interface.
10+
Tired of juggling multiple AI coding assistants? **CAM** is a unified Python CLI to manage configurations, prompts, skills, and plugins for **16 AI assistants** including Claude, Codex, Gemini, Qwen, Copilot, Goose, Continue, and more from a single, polished terminal interface.
1111

1212
</div>
1313

@@ -36,14 +36,14 @@ CAM solves this by providing a single, consistent interface to manage everything
3636
- **Configuration:** Advanced configuration management with set/unset/show commands and TOML support.
3737
- **MCP Support:** First-class support for the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/), allowing assistants to connect to external data sources and tools.
3838
- **Parallel Upgrades:** Concurrent tool upgrades with npm version checking and progress visualization.
39-
- **Comprehensive Testing:** Enterprise-grade test suite with 1,076+ tests, coverage reporting, and quality assurance.
39+
- **Comprehensive Testing:** Enterprise-grade test suite with 1,423+ tests, 28.6% coverage (71/248 lines), and quality assurance.
4040
- **Diagnostics:** A comprehensive `doctor` command to validate your environment, API keys, tool installations, and cache status.
4141
- **Enterprise Security:** Config-first approach eliminates shell injection vulnerabilities with secure MCP client implementations.
4242
- **Automated Quality Assurance:** Built-in complexity monitoring, file size limits, and comprehensive CI/CD quality gates.
4343

4444
## Supported AI Assistants
4545

46-
CAM supports **13 AI coding assistants**:
46+
CAM supports **16 AI coding assistants**:
4747

4848
| Assistant | Command | Description | Install Method |
4949
| :--- | :--- | :--- | :--- |
@@ -60,6 +60,9 @@ CAM supports **13 AI coding assistants**:
6060
| **Neovate** | `neovate` | Neovate Code CLI | npm |
6161
| **Qoder** | `qodercli` | Qoder CLI | npm |
6262
| **Zed** | `zed` | Zed Editor | Shell script |
63+
| **Goose** | `goose` | Block Goose CLI | Shell script |
64+
| **Continue** | `continue` | Continue.dev CLI | npm |
65+
| **OpenCode** | `opencode` | OpenCode CLI | npm |
6366

6467
## Feature Support Matrix
6568

@@ -71,7 +74,7 @@ CAM supports **13 AI coding assistants**:
7174
| **Plugin** Support ||||||||
7275
| **MCP** Integration ||||||||
7376

74-
**MCP Integration** is supported across all 13 assistants including: Claude, Codex, Gemini, Qwen, Copilot, CodeBuddy, Droid, iFlow, Zed, Qoder, Neovate, Crush, and Cursor.
77+
**MCP Integration** is supported across all 16 assistants including: Claude, Codex, Gemini, Qwen, Copilot, CodeBuddy, Droid, iFlow, Zed, Qoder, Neovate, Crush, Cursor, Goose, Continue, and OpenCode.
7578

7679
> **Note:** Some tools (Zed, Qoder, Neovate) are disabled by default in the menu as they are still under development. You can enable them in `tools.yaml` by setting `enabled: true`.
7780
@@ -426,7 +429,14 @@ This project is licensed under the MIT License.
426429

427430
## 🏆 Recent Improvements
428431

429-
**Version 1.x.x** introduces significant enhancements to code quality, security, and maintainability:
432+
**Version 1.0.3** introduces significant enhancements to code quality, security, and new tool support:
433+
434+
### 🆕 New Features
435+
- **Goose CLI Support:** Added Block Goose CLI tool with dynamic engine type determination and custom provider configuration
436+
- **Multi-Model Selection:** Enhanced agent installation with support for Goose, Codex, Droid, and Continue multi-model selection
437+
- **Agent Metadata System:** Implemented agent metadata pulling using awesome-claude-agents approach for better discovery
438+
- **Multi-App Marketplace:** Support for multiple app targets during marketplace installation
439+
- **Enhanced Configuration:** Environment loader with flexible config path management
430440

431441
### 🔧 Technical Debt Resolution
432442
- **Function Complexity:** Reduced from D-level (21-30 branches) to B-C level (<18 branches)
@@ -445,15 +455,16 @@ This project is licensed under the MIT License.
445455
### ⚡ Quality Assurance
446456
- **Automated Complexity Monitoring:** CI/CD checks using radon cc/mi analysis
447457
- **File Size Limits:** Enforced 500-line maximum per file
448-
- **Comprehensive Testing:** 1,076+ tests covering all functionality including integration tests
458+
- **Comprehensive Testing:** 1,423+ tests covering all functionality including integration tests
449459
- **Coverage Reporting:** Multiple coverage report formats (HTML, terminal, XML) with detailed analysis
450460
- **Quality Gates:** Automated checks prevent code quality regression
451461

452462
### 📊 Current Health Metrics
453463
- **Code Quality:** A+ grade with enterprise-grade standards
464+
- **Codebase Size:** 32,087 lines of Python code across 148 files
454465
- **Security:** Zero known vulnerabilities
455-
- **Test Coverage:** 48% across 14,200+ statements with comprehensive testing infrastructure
456-
- **Test Suite:** 1,076+ tests including unit, integration, and interactive tests
466+
- **Test Coverage:** 28.6% (71/248 lines covered) with comprehensive testing infrastructure
467+
- **Test Suite:** 1,423+ tests including unit, integration, and interactive tests
457468
- **Maintainability:** Clean, modular architecture with clear separation of concerns
458469

459470
### 🎯 Development Standards

README_zh.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
**一个 CLI 统一管理所有 AI 编码助手**
1010
<br>
11-
厌倦了在多个 AI 编码助手之间切换?**CAM** 是一个统一的 Python CLI 工具,可以从单一、优雅的终端界面管理 **13 个 AI 助手**(包括 Claude、Codex、Gemini、Qwen、Copilot 等)的配置、提示词、技能和插件。
11+
厌倦了在多个 AI 编码助手之间切换?**CAM** 是一个统一的 Python CLI 工具,可以从单一、优雅的终端界面管理 **16 个 AI 助手**(包括 Claude、Codex、Gemini、Qwen、Copilot、Goose、Continue 等)的配置、提示词、技能和插件。
1212

1313
</div>
1414

@@ -40,7 +40,7 @@ CAM 通过提供单一、一致的界面来管理所有内容,将混乱的工
4040

4141
## 支持的 AI 助手
4242

43-
CAM 支持 **13 个 AI 编码助手**
43+
CAM 支持 **16 个 AI 编码助手**
4444

4545
| 助手 | 命令 | 描述 | 安装方式 |
4646
| :--- | :--- | :--- | :--- |
@@ -57,6 +57,9 @@ CAM 支持 **13 个 AI 编码助手**:
5757
| **Neovate** | `neovate` | Neovate Code CLI | npm |
5858
| **Qoder** | `qodercli` | Qoder CLI | npm |
5959
| **Zed** | `zed` | Zed 编辑器 | Shell 脚本 |
60+
| **Goose** | `goose` | Block Goose CLI | Shell 脚本 |
61+
| **Continue** | `continue` | Continue.dev CLI | npm |
62+
| **OpenCode** | `opencode` | OpenCode CLI | npm |
6063

6164
## 功能支持矩阵
6265

@@ -68,7 +71,7 @@ CAM 支持 **13 个 AI 编码助手**:
6871
| **插件**支持 ||||||||
6972
| **MCP** 集成 ||||||||
7073

71-
**MCP 集成**支持所有 13 个助手,包括:Claude、Codex、Gemini、Qwen、Copilot、CodeBuddy、Droid、iFlow、Zed、Qoder、Neovate、Crush 和 Cursor
74+
**MCP 集成**支持所有 16 个助手,包括:Claude、Codex、Gemini、Qwen、Copilot、CodeBuddy、Droid、iFlow、Zed、Qoder、Neovate、Crush、Cursor、Goose、ContinueOpenCode
7275

7376
> **注意:** 部分工具(Zed、Qoder、Neovate)默认在菜单中隐藏,因为它们仍在开发中。您可以在 `tools.yaml` 中设置 `enabled: true` 来启用它们。
7477

code_assistant_manager/agents/base.py

Lines changed: 67 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -77,14 +77,55 @@ def install(self, agent: Agent) -> Path:
7777
)
7878

7979
try:
80-
# Determine source path
80+
# Try to find the agent file using multiple strategies
81+
source_path = None
82+
83+
# Strategy 1: Try exact path (agents_path + filename)
8184
if agent.agents_path:
82-
source_path = temp_dir / agent.agents_path.strip("/") / agent.filename
83-
else:
84-
source_path = temp_dir / agent.filename
85-
86-
if not source_path.exists():
87-
raise ValueError(f"Agent file not found in repository: {source_path}")
85+
exact_path = temp_dir / agent.agents_path.strip("/") / agent.filename
86+
if exact_path.exists():
87+
source_path = exact_path
88+
logger.debug(f"Found agent at exact path: {exact_path}")
89+
90+
# Strategy 2: Try root directory
91+
if not source_path:
92+
root_path = temp_dir / agent.filename
93+
if root_path.exists():
94+
source_path = root_path
95+
logger.debug(f"Found agent at root: {root_path}")
96+
97+
# Strategy 3: Recursive search in agents_path directory
98+
if not source_path and agent.agents_path:
99+
search_dir = temp_dir / agent.agents_path.strip("/")
100+
if search_dir.exists():
101+
source_path = self._find_file_recursive(search_dir, agent.filename)
102+
if source_path:
103+
logger.debug(f"Found agent via recursive search in {agent.agents_path}: {source_path}")
104+
105+
# Strategy 4: Recursive search in plugins directory
106+
if not source_path:
107+
plugins_dir = temp_dir / "plugins"
108+
if plugins_dir.exists():
109+
source_path = self._find_file_recursive(plugins_dir, agent.filename)
110+
if source_path:
111+
logger.debug(f"Found agent via recursive search in plugins: {source_path}")
112+
113+
# Strategy 5: Recursive search in agents directory
114+
if not source_path:
115+
agents_dir = temp_dir / "agents"
116+
if agents_dir.exists():
117+
source_path = self._find_file_recursive(agents_dir, agent.filename)
118+
if source_path:
119+
logger.debug(f"Found agent via recursive search in agents: {source_path}")
120+
121+
# Strategy 6: Search entire repository
122+
if not source_path:
123+
source_path = self._find_file_recursive(temp_dir, agent.filename)
124+
if source_path:
125+
logger.debug(f"Found agent via full repository search: {source_path}")
126+
127+
if not source_path:
128+
raise ValueError(f"Agent file not found in repository: {agent.filename}")
88129

89130
# Copy to install directory
90131
dest_path = self.agents_dir / agent.filename
@@ -95,6 +136,25 @@ def install(self, agent: Agent) -> Path:
95136
if temp_dir.exists():
96137
shutil.rmtree(temp_dir)
97138

139+
def _find_file_recursive(self, search_dir: Path, filename: str) -> Optional[Path]:
140+
"""Recursively search for a file in a directory.
141+
142+
Args:
143+
search_dir: Directory to search in
144+
filename: Filename to search for
145+
146+
Returns:
147+
Path to the file if found, None otherwise
148+
"""
149+
try:
150+
for item in search_dir.rglob(filename):
151+
if item.is_file():
152+
return item
153+
except Exception as e:
154+
logger.debug(f"Error searching {search_dir}: {e}")
155+
return None
156+
157+
98158
def uninstall(self, agent: Agent) -> bool:
99159
"""Uninstall an agent by removing its file.
100160

code_assistant_manager/cli/agents_commands.py

Lines changed: 121 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -255,22 +255,133 @@ def install_agent(
255255
agent_key: str = AGENT_KEY_ARGUMENT,
256256
app_type: str = APP_TYPE_OPTION,
257257
):
258-
"""Install an agent to one or more app's agents directories."""
258+
"""Install an agent to one or more app's agents directories.
259+
260+
Can accept either a registered agent key or a GitHub specification:
261+
- Registered key: 'security-auditor'
262+
- GitHub spec: 'owner/repo:agent-name' or 'owner/repo:agent-name@branch'
263+
"""
259264
target_apps = resolve_app_targets(app_type, VALID_APP_TYPES, default="claude")
260-
265+
261266
manager = _get_agent_manager()
262-
263-
for app in target_apps:
267+
268+
# First check if agent_key matches a registered agent (most common case)
269+
all_agents = manager.get_all()
270+
if agent_key in all_agents:
271+
# Use registered agent configuration
272+
for app in target_apps:
273+
try:
274+
handler = manager.get_handler(app)
275+
dest_path = manager.install(agent_key, app)
276+
typer.echo(
277+
f"{Colors.GREEN}✓ Agent installed to {app}: {agent_key}{Colors.RESET}"
278+
)
279+
typer.echo(f" {Colors.CYAN}Location:{Colors.RESET} {handler.agents_dir}")
280+
except ValueError as e:
281+
typer.echo(f"{Colors.RED}✗ Error installing to {app}: {e}{Colors.RESET}")
282+
raise typer.Exit(1)
283+
return
284+
285+
# Check if agent_key is a GitHub specification (contains / or :)
286+
if "/" in agent_key and ":" in agent_key:
287+
# Parse GitHub specification: owner/repo:agent-name[@branch]
264288
try:
265-
handler = manager.get_handler(app)
266-
dest_path = manager.install(agent_key, app)
289+
parts = agent_key.split(":")
290+
branch = "main"
291+
292+
if len(parts) == 2:
293+
repo_part, agent_name = parts
294+
owner, repo = repo_part.split("/")
295+
elif len(parts) == 3:
296+
repo_part, agent_name, branch = parts
297+
owner, repo = repo_part.split("/")
298+
else:
299+
typer.echo(
300+
f"{Colors.RED}✗ Invalid agent specification: {agent_key}{Colors.RESET}"
301+
)
302+
typer.echo(
303+
f" {Colors.CYAN}Use format: owner/repo:agent-name or owner/repo:agent-name@branch{Colors.RESET}"
304+
)
305+
raise typer.Exit(1)
306+
307+
# Check if this exact GitHub spec is registered in agents.json
308+
if agent_key in all_agents:
309+
agent = all_agents[agent_key]
310+
else:
311+
# Create a temporary agent object for installation
312+
from code_assistant_manager.agents.models import Agent
313+
314+
# Determine filename from agent name
315+
filename = f"{agent_name}.md"
316+
agent = Agent(
317+
key=agent_key,
318+
name=agent_name,
319+
description=f"Installed from {owner}/{repo}",
320+
filename=filename,
321+
repo_owner=owner,
322+
repo_name=repo,
323+
repo_branch=branch,
324+
agents_path=None, # Try common paths
325+
)
326+
327+
for app in target_apps:
328+
try:
329+
handler = manager.get_handler(app)
330+
dest_path = handler.install(agent)
331+
typer.echo(
332+
f"{Colors.GREEN}✓ Agent installed to {app}: {agent_name}{Colors.RESET}"
333+
)
334+
typer.echo(f" {Colors.CYAN}Location:{Colors.RESET} {dest_path}")
335+
except ValueError as e:
336+
# If agent not found in configured path, try common paths
337+
error_msg = str(e)
338+
if "not found" in error_msg and agent.agents_path is None:
339+
# Try plugins directory
340+
agent.agents_path = "plugins"
341+
try:
342+
dest_path = handler.install(agent)
343+
typer.echo(
344+
f"{Colors.GREEN}✓ Agent installed to {app}: {agent_name}{Colors.RESET}"
345+
)
346+
typer.echo(f" {Colors.CYAN}Location:{Colors.RESET} {dest_path}")
347+
except ValueError:
348+
# Try agents directory
349+
agent.agents_path = "agents"
350+
try:
351+
dest_path = handler.install(agent)
352+
typer.echo(
353+
f"{Colors.GREEN}✓ Agent installed to {app}: {agent_name}{Colors.RESET}"
354+
)
355+
typer.echo(f" {Colors.CYAN}Location:{Colors.RESET} {dest_path}")
356+
except ValueError as e2:
357+
typer.echo(f"{Colors.RED}✗ Error installing to {app}: {e2}{Colors.RESET}")
358+
raise typer.Exit(1)
359+
else:
360+
typer.echo(f"{Colors.RED}✗ Error installing to {app}: {e}{Colors.RESET}")
361+
raise typer.Exit(1)
362+
except (ValueError, IndexError) as e:
267363
typer.echo(
268-
f"{Colors.GREEN}✓ Agent installed to {app}: {agent_key}{Colors.RESET}"
364+
f"{Colors.RED}✗ Invalid agent specification format: {agent_key}{Colors.RESET}"
365+
)
366+
typer.echo(
367+
f" {Colors.CYAN}Use format: owner/repo:agent-name or owner/repo:agent-name@branch{Colors.RESET}"
269368
)
270-
typer.echo(f" {Colors.CYAN}Location:{Colors.RESET} {handler.agents_dir}")
271-
except ValueError as e:
272-
typer.echo(f"{Colors.RED}✗ Error installing to {app}: {e}{Colors.RESET}")
273369
raise typer.Exit(1)
370+
else:
371+
# Not found as registered key and not in GitHub format
372+
typer.echo(
373+
f"{Colors.RED}✗ Agent '{agent_key}' not found{Colors.RESET}"
374+
)
375+
typer.echo(
376+
f" {Colors.CYAN}Try one of:{Colors.RESET}"
377+
)
378+
typer.echo(
379+
f" • cam agent list (to see available agents)"
380+
)
381+
typer.echo(
382+
f" • cam agent fetch (to discover agents from repositories)"
383+
)
384+
raise typer.Exit(1)
274385

275386

276387
@agent_app.command("uninstall")

0 commit comments

Comments
 (0)