Skip to content

Commit 6767bc2

Browse files
author
André Lange
committed
fix: multi-framework remove, owner-aware capability_exists, qwen adapter, force-purge config-backed frameworks
1 parent 0b7c65c commit 6767bc2

12 files changed

Lines changed: 142 additions & 44 deletions

File tree

src/capacium/adapters/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from .aider import AiderAdapter
2525
from .openclaw import OpenClawAdapter
2626
from .hermes import HermesAdapter
27+
from .qwen import QwenAdapter
2728
from .copilot import CopilotAdapter
2829
from .langchain_bridge import LangChainToolAdapter, FlowiseAdapter
2930

@@ -83,6 +84,7 @@ def list_registered_adapters() -> List[str]:
8384
register_adapter("antigravity", AntigravityAdapter)
8485
register_adapter("continue-dev", ContinueDevAdapter)
8586
register_adapter("codex", CodexAdapter)
87+
register_adapter("qwen", QwenAdapter)
8688
register_adapter("gemini-cli", GeminiCLIAdapter)
8789

8890
# ── Tier 2: Specialized / Workflow ─────────────────────────────────────

src/capacium/adapters/antigravity.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,13 @@ def install_mcp_server(self, cap_name: str, version: str, source_dir: Path, owne
6262

6363
def remove_mcp_server(self, cap_name: str, owner: str = "global") -> bool:
6464
return McpConfigPatcher.remove_json_mcp_server(
65-
self.config_path, cap_name, "mcpServers",
65+
self.config_path, McpConfigPatcher.build_server_key(cap_name, owner), "mcpServers",
6666
)
6767

68-
def capability_exists(self, cap_name: str) -> bool:
69-
link_path = self.skills_dir / cap_name
68+
def capability_exists(self, cap_name: str, owner: str = "global") -> bool:
69+
link_path = self.skills_dir / _cap_id(cap_name, owner)
7070
if link_path.exists() and link_path.is_symlink():
7171
return True
7272
return McpConfigPatcher.mcp_server_exists_json(
73-
self.config_path, cap_name, "mcpServers",
73+
self.config_path, McpConfigPatcher.build_server_key(cap_name, owner), "mcpServers",
7474
)

src/capacium/adapters/claude_code.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ def remove_skill(self, cap_name: str, owner: str = "global") -> bool:
4040
link_path.unlink()
4141
return True
4242

43-
def capability_exists(self, cap_name: str) -> bool:
44-
link_path = self.skills_dir / cap_name
43+
def capability_exists(self, cap_name: str, owner: str = "global") -> bool:
44+
link_path = self.skills_dir / _cap_id(cap_name, owner)
4545
return link_path.exists() and link_path.is_symlink()
4646

4747
def install_mcp_server(self, cap_name: str, version: str, source_dir: Path, owner: str = "global") -> bool:

src/capacium/adapters/codex.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,10 @@ def remove_mcp_server(self, cap_name: str, owner: str = "global") -> bool:
7272
McpConfigPatcher.write_toml(self.config_path, config)
7373
return True
7474

75-
def capability_exists(self, cap_name: str) -> bool:
76-
link_path = self.skills_dir / cap_name
75+
def capability_exists(self, cap_name: str, owner: str = "global") -> bool:
76+
link_path = self.skills_dir / _cap_id(cap_name, owner)
7777
if link_path.exists() and link_path.is_symlink():
7878
return True
7979
config = McpConfigPatcher.read_toml(self.config_path)
80-
return cap_name in config.get("mcp_servers", {})
80+
server_key = McpConfigPatcher.build_server_key(cap_name, owner)
81+
return server_key in config.get("mcp_servers", {})

src/capacium/adapters/cursor.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,12 +64,12 @@ def remove_skill(self, cap_name: str, owner: str = "global") -> bool:
6464
pass
6565
return True
6666

67-
def capability_exists(self, cap_name: str) -> bool:
68-
link_path = self.skills_dir / cap_name
67+
def capability_exists(self, cap_name: str, owner: str = "global") -> bool:
68+
link_path = self.skills_dir / _cap_id(cap_name, owner)
6969
if link_path.exists() and link_path.is_symlink():
7070
return True
7171
return McpConfigPatcher.mcp_server_exists_json(
72-
self._get_mcp_path(), cap_name, self.MCP_SECTION_KEY,
72+
self._get_mcp_path(), McpConfigPatcher.build_server_key(cap_name, owner), self.MCP_SECTION_KEY,
7373
)
7474

7575
def install_mcp_server(self, cap_name: str, version: str, source_dir: Path, owner: str = "global") -> bool:

src/capacium/adapters/gemini_cli.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ def remove_skill(self, cap_name: str, owner: str = "global") -> bool:
4242
link_path.unlink()
4343
return True
4444

45-
def capability_exists(self, cap_name: str) -> bool:
46-
link_path = self.skills_dir / cap_name
45+
def capability_exists(self, cap_name: str, owner: str = "global") -> bool:
46+
link_path = self.skills_dir / _cap_id(cap_name, owner)
4747
return link_path.exists() and link_path.is_symlink()
4848

4949
def list_capabilities(self) -> List[str]:
@@ -104,5 +104,5 @@ def remove_mcp_server(self, cap_name: str, owner: str = "global") -> bool:
104104
from .mcp_config_patcher import McpConfigPatcher
105105
config_path = Path.home() / ".gemini" / "settings" / "mcp_config.json"
106106
return McpConfigPatcher.remove_json_mcp_server(
107-
config_path, cap_name, "mcpServers",
107+
config_path, McpConfigPatcher.build_server_key(cap_name, owner), "mcpServers",
108108
)

src/capacium/adapters/hermes.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,10 @@ def remove_mcp_server(self, cap_name: str, owner: str = "global") -> bool:
6868
self.config_path, cap_name, "mcpServers",
6969
)
7070

71-
def capability_exists(self, cap_name: str) -> bool:
72-
link_path = self.skills_dir / cap_name
71+
def capability_exists(self, cap_name: str, owner: str = "global") -> bool:
72+
link_path = self.skills_dir / _cap_id(cap_name, owner)
7373
if link_path.exists() and link_path.is_symlink():
7474
return True
7575
return McpConfigPatcher.mcp_server_exists_json(
76-
self.config_path, cap_name, "mcpServers",
76+
self.config_path, McpConfigPatcher.build_server_key(cap_name, owner), "mcpServers",
7777
)

src/capacium/adapters/junie.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ def remove_skill(self, cap_name: str, owner: str = "global") -> bool:
4040
link_path.unlink()
4141
return True
4242

43-
def capability_exists(self, cap_name: str) -> bool:
44-
link_path = self.skills_dir / cap_name
43+
def capability_exists(self, cap_name: str, owner: str = "global") -> bool:
44+
link_path = self.skills_dir / _cap_id(cap_name, owner)
4545
return link_path.exists() and link_path.is_symlink()
4646

4747
def install_mcp_server(self, cap_name: str, version: str, source_dir: Path, owner: str = "global") -> bool:

src/capacium/adapters/openclaw.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,10 @@ def remove_mcp_server(self, cap_name: str, owner: str = "global") -> bool:
6868
self.config_path, cap_name, "mcpServers",
6969
)
7070

71-
def capability_exists(self, cap_name: str) -> bool:
72-
link_path = self.skills_dir / cap_name
71+
def capability_exists(self, cap_name: str, owner: str = "global") -> bool:
72+
link_path = self.skills_dir / _cap_id(cap_name, owner)
7373
if link_path.exists() and link_path.is_symlink():
7474
return True
7575
return McpConfigPatcher.mcp_server_exists_json(
76-
self.config_path, cap_name, "mcpServers",
76+
self.config_path, McpConfigPatcher.build_server_key(cap_name, owner), "mcpServers",
7777
)

src/capacium/adapters/opencode.py

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from ..storage import StorageManager
77
from ..symlink_manager import SymlinkManager
88
from .base import FrameworkAdapter, _cap_id, ensure_package_dir
9+
from .mcp_config_patcher import McpConfigPatcher
910

1011

1112
class OpenCodeAdapter(FrameworkAdapter):
@@ -40,20 +41,19 @@ def remove_skill(self, cap_name: str, owner: str = "global") -> bool:
4041
link_path.unlink()
4142
return True
4243

43-
def capability_exists(self, cap_name: str) -> bool:
44-
link_path = self.opencode_skills_dir / cap_name
44+
def capability_exists(self, cap_name: str, owner: str = "global") -> bool:
45+
link_path = self.opencode_skills_dir / _cap_id(cap_name, owner)
4546
if link_path.exists() and link_path.is_symlink():
4647
return True
4748

48-
from .mcp_config_patcher import McpConfigPatcher
4949
config_path = Path.home() / ".config" / "opencode" / "opencode.json"
50+
server_key = McpConfigPatcher.build_server_key(cap_name, owner)
5051
return (
51-
McpConfigPatcher.mcp_server_exists_json(config_path, cap_name, "mcp")
52-
or McpConfigPatcher.mcp_server_exists_json(config_path, cap_name, "mcpServers")
52+
McpConfigPatcher.mcp_server_exists_json(config_path, server_key, "mcp")
53+
or McpConfigPatcher.mcp_server_exists_json(config_path, server_key, "mcpServers")
5354
)
5455

5556
def install_mcp_server(self, cap_name: str, version: str, source_dir: Path, owner: str = "global") -> bool:
56-
from .mcp_config_patcher import McpConfigPatcher
5757
package_dir = ensure_package_dir(self.storage, cap_name, version, source_dir, owner=owner)
5858
if package_dir.exists() and package_dir.resolve() != source_dir.resolve():
5959
shutil.rmtree(package_dir)
@@ -73,8 +73,6 @@ def install_mcp_server(self, cap_name: str, version: str, source_dir: Path, owne
7373
cap_name, package_dir, mcp_meta,
7474
)
7575

76-
# v0.7.2 and older wrote OpenCode MCP servers to the Claude-style
77-
# mcpServers block. Remove the stale entry when upgrading/reconciling.
7876
legacy_servers = config.get("mcpServers")
7977
if isinstance(legacy_servers, dict):
8078
legacy_servers.pop(cap_name, None)
@@ -132,7 +130,6 @@ def install_skill(self, cap_name: str, version: str, source_dir: Path, owner: st
132130
package_dir = ensure_package_dir(self.storage, cap_name, version, source_dir, owner)
133131

134132
md_files = sorted(package_dir.glob("*.md"))
135-
# Skip SKILL.md files — those are loaded by the skills adapter, not as commands
136133
command_files = [f for f in md_files if f.name != "SKILL.md"]
137134
cmd_file = None
138135
for f in command_files:

0 commit comments

Comments
 (0)