Skip to content

Commit ad57a77

Browse files
author
James Zhu
committed
feat: add multi-source repository configuration support
Implement flexible multi-source repository configuration system inspired by awesome-claude-skills: Features: - Load repos from multiple sources in priority order: 1. Local files (~/.config/code-assistant-manager/*.json) - highest priority 2. Remote URLs (with HTTP caching) - community repos 3. Bundled defaults - package fallback - config.yaml configuration: * Define local and remote sources for skills/agents/plugins * Configure cache TTL and directory * Add custom remote sources easily - RepoConfigLoader utility: * HTTP fetcher for remote sources * Local caching with configurable TTL (default: 1 hour) * Smart merging with priority handling * Offline support via cache and bundled fallbacks - Updated all managers: * skills/manager.py - multi-source skill repos * agents/manager.py - multi-source agent repos * plugins/manager.py - multi-source plugin repos Benefits: - ✓ Automatic community updates from remote sources - ✓ Custom local overrides without conflicts - ✓ Offline development with local/bundled fallbacks - ✓ Cached remote sources reduce network requests - ✓ Fully backward compatible Testing: - Successfully loads 25 skill repos (remote + bundled) - Successfully loads 9 agent repos (remote + bundled) - Successfully loads 26 plugin repos (remote + bundled) - All existing tests pass - No breaking changes Files: - Created: config.yaml, repo_loader.py, docs/multi-source-repos.md - Modified: skills/manager.py, agents/manager.py, plugins/manager.py
1 parent d9a3fe8 commit ad57a77

6 files changed

Lines changed: 672 additions & 33 deletions

File tree

code_assistant_manager/agents/manager.py

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from pathlib import Path
1111
from typing import Dict, List, Optional, Type
1212

13+
from ..repo_loader import RepoConfigLoader
1314
from .base import BaseAgentHandler
1415
from .claude import ClaudeAgentHandler
1516
from .codebuddy import CodebuddyAgentHandler
@@ -24,41 +25,63 @@
2425
logger = logging.getLogger(__name__)
2526

2627

27-
def _load_builtin_agent_repos() -> List[Dict]:
28-
"""Load built-in agent repos from the bundled agent_repos.json file."""
28+
def _load_builtin_agent_repos() -> Dict:
29+
"""Load built-in agent repos from the bundled agent_repos.json file.
30+
31+
Returns bundled repos as a dictionary for fallback.
32+
"""
2933
package_dir = Path(__file__).parent.parent
3034
repos_file = package_dir / "agent_repos.json"
3135

3236
if repos_file.exists():
3337
try:
3438
with open(repos_file, "r", encoding="utf-8") as f:
3539
repos_data = json.load(f)
36-
return [
37-
{
38-
"owner": repo.get("owner"),
39-
"name": repo.get("name"),
40-
"branch": repo.get("branch", "main"),
41-
"enabled": repo.get("enabled", True),
42-
"agentsPath": repo.get("agentsPath"),
43-
}
44-
for repo in repos_data.values()
45-
]
40+
return repos_data
4641
except Exception as e:
4742
logger.warning(f"Failed to load builtin agent repos: {e}")
4843

4944
# Fallback defaults
50-
return [
51-
{
45+
return {
46+
"iannuttall/claude-agents": {
5247
"owner": "iannuttall",
5348
"name": "claude-agents",
5449
"branch": "main",
5550
"enabled": True,
5651
"agentsPath": "agents",
5752
},
53+
}
54+
55+
56+
def _load_agent_repos_from_config(config_dir: Optional[Path] = None) -> List[Dict]:
57+
"""Load agent repos from config.yaml sources.
58+
59+
Args:
60+
config_dir: Configuration directory
61+
62+
Returns:
63+
List of repository configurations
64+
"""
65+
loader = RepoConfigLoader(config_dir)
66+
bundled_fallback = _load_builtin_agent_repos()
67+
68+
# Get repos from all configured sources
69+
repos_dict = loader.get_repos("agents", bundled_fallback)
70+
71+
# Convert to list format for backward compatibility
72+
return [
73+
{
74+
"owner": repo.get("owner"),
75+
"name": repo.get("name"),
76+
"branch": repo.get("branch", "main"),
77+
"enabled": repo.get("enabled", True),
78+
"agentsPath": repo.get("agentsPath"),
79+
}
80+
for repo in repos_dict.values()
5881
]
5982

6083

61-
DEFAULT_AGENT_REPOS = _load_builtin_agent_repos()
84+
DEFAULT_AGENT_REPOS = _load_agent_repos_from_config()
6285

6386
# Registry of available handlers
6487
AGENT_HANDLERS: Dict[str, Type[BaseAgentHandler]] = {

code_assistant_manager/config.yaml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Code Assistant Manager - Repository Configuration
2+
# This file configures sources for skills, agents, and plugins
3+
4+
repositories:
5+
# Skill repositories
6+
skills:
7+
sources:
8+
# Local user file (highest priority)
9+
- type: local
10+
path: ~/.config/code-assistant-manager/skill_repos.json
11+
12+
# Remote awesome repository configs
13+
- type: remote
14+
url: https://raw.githubusercontent.com/Chat2AnyLLM/awesome-repo-configs/main/skill_repos.json
15+
16+
# Agent repositories
17+
agents:
18+
sources:
19+
# Local user file (highest priority)
20+
- type: local
21+
path: ~/.config/code-assistant-manager/agent_repos.json
22+
23+
# Remote awesome repository configs
24+
- type: remote
25+
url: https://raw.githubusercontent.com/Chat2AnyLLM/awesome-repo-configs/main/agent_repos.json
26+
27+
# Plugin repositories
28+
plugins:
29+
sources:
30+
# Local user file (highest priority)
31+
- type: local
32+
path: ~/.config/code-assistant-manager/plugin_repos.json
33+
34+
# Remote awesome repository configs
35+
- type: remote
36+
url: https://raw.githubusercontent.com/Chat2AnyLLM/awesome-repo-configs/main/plugin_repos.json
37+
38+
# Cache settings for remote sources
39+
cache:
40+
enabled: true
41+
directory: ~/.cache/code-assistant-manager/repos
42+
ttl_seconds: 3600 # Cache remote sources for 1 hour

code_assistant_manager/plugins/manager.py

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from pathlib import Path
66
from typing import Any, Dict, List, Optional, Type
77

8+
from ..repo_loader import RepoConfigLoader
89
from .base import BasePluginHandler
910
from .claude import ClaudePluginHandler
1011
from .codebuddy import CodebuddyPluginHandler
@@ -16,7 +17,10 @@
1617

1718

1819
def _load_builtin_plugin_repos() -> Dict[str, PluginRepo]:
19-
"""Load built-in plugin repos from the bundled plugin_repos.json file."""
20+
"""Load built-in plugin repos from the bundled plugin_repos.json file.
21+
22+
Returns bundled repos as PluginRepo objects for fallback.
23+
"""
2024
package_dir = Path(__file__).parent.parent
2125
repos_file = package_dir / "plugin_repos.json"
2226

@@ -44,6 +48,54 @@ def _load_builtin_plugin_repos() -> Dict[str, PluginRepo]:
4448
return repos
4549

4650

51+
def _load_plugin_repos_from_config(config_dir: Optional[Path] = None) -> Dict[str, PluginRepo]:
52+
"""Load plugin repos from config.yaml sources.
53+
54+
Args:
55+
config_dir: Configuration directory
56+
57+
Returns:
58+
Dictionary of PluginRepo objects
59+
"""
60+
loader = RepoConfigLoader(config_dir)
61+
bundled_fallback_dict = _load_builtin_plugin_repos()
62+
63+
# Convert PluginRepo objects to dict for loader
64+
bundled_data = {}
65+
for key, repo in bundled_fallback_dict.items():
66+
bundled_data[key] = {
67+
"name": repo.name,
68+
"description": repo.description,
69+
"repoOwner": repo.repo_owner,
70+
"repoName": repo.repo_name,
71+
"repoBranch": repo.repo_branch,
72+
"pluginPath": repo.plugin_path,
73+
"enabled": repo.enabled,
74+
"type": repo.type,
75+
"aliases": repo.aliases,
76+
}
77+
78+
# Get repos from all configured sources
79+
repos_dict = loader.get_repos("plugins", bundled_data)
80+
81+
# Convert back to PluginRepo objects
82+
repos: Dict[str, PluginRepo] = {}
83+
for key, repo_data in repos_dict.items():
84+
repos[key] = PluginRepo(
85+
name=repo_data.get("name", key),
86+
description=repo_data.get("description", ""),
87+
repo_owner=repo_data.get("repoOwner"),
88+
repo_name=repo_data.get("repoName"),
89+
repo_branch=repo_data.get("repoBranch", "main"),
90+
plugin_path=repo_data.get("pluginPath"),
91+
enabled=repo_data.get("enabled", True),
92+
type=repo_data.get("type", "plugin"),
93+
aliases=repo_data.get("aliases", []),
94+
)
95+
96+
return repos
97+
98+
4799
# Registry of all available plugin handlers
48100
PLUGIN_HANDLERS: Dict[str, Type[BasePluginHandler]] = {
49101
"claude": ClaudePluginHandler,
@@ -56,7 +108,7 @@ def _load_builtin_plugin_repos() -> Dict[str, PluginRepo]:
56108
VALID_APP_TYPES = list(PLUGIN_HANDLERS.keys())
57109

58110
# Built-in plugin repositories
59-
BUILTIN_PLUGIN_REPOS = _load_builtin_plugin_repos()
111+
BUILTIN_PLUGIN_REPOS = _load_plugin_repos_from_config()
60112

61113

62114
def get_handler(app_type: str) -> BasePluginHandler:

0 commit comments

Comments
 (0)