janito 2.27.0__py3-none-any.whl → 2.28.0__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.
- janito/README.md +9 -9
- janito/agent/setup_agent.py +29 -16
- janito/cli/chat_mode/script_runner.py +1 -1
- janito/cli/chat_mode/session.py +50 -24
- janito/cli/chat_mode/session_profile_select.py +8 -2
- janito/cli/chat_mode/shell/commands/__init__.py +2 -0
- janito/cli/chat_mode/shell/commands/execute.py +4 -2
- janito/cli/chat_mode/shell/commands/help.py +8 -1
- janito/cli/chat_mode/shell/commands/privileges.py +6 -2
- janito/cli/chat_mode/shell/commands/provider.py +28 -0
- janito/cli/chat_mode/shell/commands/read.py +4 -2
- janito/cli/chat_mode/shell/commands/security/__init__.py +1 -1
- janito/cli/chat_mode/shell/commands/security/allowed_sites.py +16 -13
- janito/cli/chat_mode/shell/commands/security_command.py +14 -10
- janito/cli/chat_mode/shell/commands/tools.py +4 -2
- janito/cli/chat_mode/shell/commands/unrestricted.py +17 -12
- janito/cli/chat_mode/shell/commands/write.py +4 -2
- janito/cli/chat_mode/toolbar.py +15 -1
- janito/cli/cli_commands/enable_disable_plugin.py +87 -0
- janito/cli/cli_commands/list_models.py +2 -2
- janito/cli/cli_commands/list_plugins.py +35 -19
- janito/cli/cli_commands/list_profiles.py +6 -6
- janito/cli/cli_commands/list_providers.py +1 -1
- janito/cli/cli_commands/model_utils.py +45 -20
- janito/cli/cli_commands/ping_providers.py +10 -10
- janito/cli/cli_commands/set_api_key.py +5 -3
- janito/cli/cli_commands/show_config.py +13 -7
- janito/cli/cli_commands/show_system_prompt.py +13 -6
- janito/cli/core/getters.py +7 -0
- janito/cli/core/model_guesser.py +18 -15
- janito/cli/core/runner.py +28 -6
- janito/cli/core/setters.py +21 -6
- janito/cli/main_cli.py +14 -12
- janito/cli/prompt_core.py +2 -0
- janito/cli/prompt_setup.py +4 -4
- janito/cli/single_shot_mode/handler.py +2 -0
- janito/config_manager.py +2 -0
- janito/docs/GETTING_STARTED.md +9 -9
- janito/drivers/cerebras/__init__.py +1 -1
- janito/exceptions.py +6 -4
- janito/plugins/__init__.py +2 -2
- janito/plugins/base.py +48 -40
- janito/plugins/builtin.py +88 -0
- janito/plugins/config.py +16 -19
- janito/plugins/discovery.py +129 -40
- janito/plugins/manager.py +63 -59
- janito/provider_registry.py +10 -10
- janito/providers/__init__.py +1 -1
- janito/providers/alibaba/model_info.py +3 -5
- janito/providers/alibaba/provider.py +3 -1
- janito/providers/cerebras/__init__.py +1 -1
- janito/providers/cerebras/model_info.py +12 -27
- janito/providers/cerebras/provider.py +11 -9
- janito/providers/mistral/__init__.py +1 -1
- janito/providers/mistral/model_info.py +1 -1
- janito/providers/mistral/provider.py +1 -1
- janito/providers/moonshot/__init__.py +1 -0
- janito/providers/{moonshotai → moonshot}/model_info.py +3 -3
- janito/providers/{moonshotai → moonshot}/provider.py +8 -8
- janito/providers/openai/provider.py +3 -1
- janito/report_events.py +0 -1
- janito/tools/adapters/local/ask_user.py +7 -1
- janito/tools/adapters/local/create_file.py +1 -1
- janito/tools/adapters/local/fetch_url.py +45 -29
- janito/tools/adapters/local/python_command_run.py +2 -1
- janito/tools/adapters/local/python_file_run.py +1 -0
- janito/tools/adapters/local/run_powershell_command.py +1 -1
- janito/tools/adapters/local/search_text/core.py +1 -1
- janito/tools/adapters/local/validate_file_syntax/jinja2_validator.py +14 -11
- janito/tools/base.py +12 -0
- janito/tools/loop_protection.py +24 -22
- janito/tools/path_utils.py +7 -7
- janito/tools/tool_base.py +0 -2
- janito/tools/tools_adapter.py +15 -5
- janito/tools/url_whitelist.py +27 -26
- {janito-2.27.0.dist-info → janito-2.28.0.dist-info}/METADATA +3 -1
- {janito-2.27.0.dist-info → janito-2.28.0.dist-info}/RECORD +81 -82
- janito-2.28.0.dist-info/top_level.txt +1 -0
- janito/providers/moonshotai/__init__.py +0 -1
- janito-2.27.0.dist-info/top_level.txt +0 -2
- janito-coder/janito_coder/__init__.py +0 -9
- janito-coder/janito_coder/plugins/__init__.py +0 -27
- janito-coder/janito_coder/plugins/code_navigator.py +0 -618
- janito-coder/janito_coder/plugins/git_analyzer.py +0 -273
- janito-coder/pyproject.toml +0 -347
- {janito-2.27.0.dist-info → janito-2.28.0.dist-info}/WHEEL +0 -0
- {janito-2.27.0.dist-info → janito-2.28.0.dist-info}/entry_points.txt +0 -0
- {janito-2.27.0.dist-info → janito-2.28.0.dist-info}/licenses/LICENSE +0 -0
janito/plugins/config.py
CHANGED
@@ -21,35 +21,32 @@ def get_plugins_config_path() -> Path:
|
|
21
21
|
def load_plugins_config() -> Dict[str, Any]:
|
22
22
|
"""
|
23
23
|
Load plugins configuration from user directory.
|
24
|
-
|
24
|
+
|
25
25
|
Returns:
|
26
26
|
Dict containing plugins configuration
|
27
27
|
"""
|
28
28
|
config_path = get_plugins_config_path()
|
29
|
-
|
29
|
+
|
30
30
|
if not config_path.exists():
|
31
31
|
# Create default config if it doesn't exist
|
32
32
|
default_config = {
|
33
33
|
"plugins": {
|
34
|
-
"paths": [
|
35
|
-
|
36
|
-
"./plugins"
|
37
|
-
],
|
38
|
-
"load": {}
|
34
|
+
"paths": [str(Path.home() / ".janito" / "plugins"), "./plugins"],
|
35
|
+
"load": {},
|
39
36
|
}
|
40
37
|
}
|
41
|
-
|
38
|
+
|
42
39
|
# Ensure directory exists
|
43
40
|
config_path.parent.mkdir(parents=True, exist_ok=True)
|
44
|
-
|
41
|
+
|
45
42
|
# Save default config
|
46
|
-
with open(config_path,
|
43
|
+
with open(config_path, "w") as f:
|
47
44
|
json.dump(default_config, f, indent=2)
|
48
|
-
|
45
|
+
|
49
46
|
return default_config
|
50
|
-
|
47
|
+
|
51
48
|
try:
|
52
|
-
with open(config_path,
|
49
|
+
with open(config_path, "r") as f:
|
53
50
|
return json.load(f)
|
54
51
|
except (json.JSONDecodeError, IOError) as e:
|
55
52
|
print(f"Warning: Failed to load plugins config from {config_path}: {e}")
|
@@ -59,20 +56,20 @@ def load_plugins_config() -> Dict[str, Any]:
|
|
59
56
|
def save_plugins_config(config: Dict[str, Any]) -> bool:
|
60
57
|
"""
|
61
58
|
Save plugins configuration to user directory.
|
62
|
-
|
59
|
+
|
63
60
|
Args:
|
64
61
|
config: Configuration dict to save
|
65
|
-
|
62
|
+
|
66
63
|
Returns:
|
67
64
|
True if saved successfully
|
68
65
|
"""
|
69
66
|
config_path = get_plugins_config_path()
|
70
|
-
|
67
|
+
|
71
68
|
try:
|
72
69
|
# Ensure directory exists
|
73
70
|
config_path.parent.mkdir(parents=True, exist_ok=True)
|
74
|
-
|
75
|
-
with open(config_path,
|
71
|
+
|
72
|
+
with open(config_path, "w") as f:
|
76
73
|
json.dump(config, f, indent=2)
|
77
74
|
return True
|
78
75
|
except IOError as e:
|
@@ -84,4 +81,4 @@ def get_user_plugins_dir() -> Path:
|
|
84
81
|
"""Get the user plugins directory."""
|
85
82
|
plugins_dir = get_user_config_dir() / "plugins"
|
86
83
|
plugins_dir.mkdir(parents=True, exist_ok=True)
|
87
|
-
return plugins_dir
|
84
|
+
return plugins_dir
|
janito/plugins/discovery.py
CHANGED
@@ -32,56 +32,79 @@ from typing import Optional, List
|
|
32
32
|
import logging
|
33
33
|
|
34
34
|
from .base import Plugin
|
35
|
+
from .builtin import load_builtin_plugin, BuiltinPluginRegistry
|
35
36
|
|
36
37
|
logger = logging.getLogger(__name__)
|
37
38
|
|
38
39
|
|
39
|
-
def discover_plugins(
|
40
|
+
def discover_plugins(
|
41
|
+
plugin_name: str, search_paths: List[Path] = None
|
42
|
+
) -> Optional[Plugin]:
|
40
43
|
"""
|
41
44
|
Discover and load a plugin by name.
|
42
|
-
|
45
|
+
|
43
46
|
Supports multiple plugin formats:
|
44
47
|
- Single .py files
|
45
48
|
- Python package directories
|
49
|
+
- Package-based plugins (e.g., core.filemanager)
|
46
50
|
- Installed Python packages
|
47
51
|
- ZIP files containing packages
|
48
|
-
|
52
|
+
|
49
53
|
Args:
|
50
54
|
plugin_name: Name of the plugin to discover
|
51
55
|
search_paths: List of directories to search for plugins
|
52
|
-
|
56
|
+
|
53
57
|
Returns:
|
54
58
|
Plugin instance if found, None otherwise
|
55
59
|
"""
|
56
60
|
if search_paths is None:
|
57
61
|
search_paths = []
|
58
|
-
|
62
|
+
|
59
63
|
# Add default search paths
|
60
64
|
default_paths = [
|
61
65
|
Path.cwd() / "plugins",
|
62
66
|
Path.home() / ".janito" / "plugins",
|
63
67
|
Path(sys.prefix) / "share" / "janito" / "plugins",
|
64
68
|
]
|
65
|
-
|
69
|
+
|
66
70
|
all_paths = search_paths + default_paths
|
67
|
-
|
71
|
+
|
72
|
+
# Handle package-based plugins (e.g., core.filemanager)
|
73
|
+
if "." in plugin_name:
|
74
|
+
parts = plugin_name.split(".")
|
75
|
+
if len(parts) == 2:
|
76
|
+
package_name, submodule_name = parts
|
77
|
+
for base_path in all_paths:
|
78
|
+
package_path = base_path / package_name / submodule_name / "__init__.py"
|
79
|
+
if package_path.exists():
|
80
|
+
return _load_plugin_from_file(package_path, plugin_name=plugin_name)
|
81
|
+
|
82
|
+
plugin_path = base_path / package_name / submodule_name / "plugin.py"
|
83
|
+
if plugin_path.exists():
|
84
|
+
return _load_plugin_from_file(plugin_path, plugin_name=plugin_name)
|
85
|
+
|
68
86
|
# Try to find plugin in search paths
|
69
87
|
for base_path in all_paths:
|
70
88
|
plugin_path = base_path / plugin_name
|
71
89
|
if plugin_path.exists():
|
72
90
|
return _load_plugin_from_directory(plugin_path)
|
73
|
-
|
91
|
+
|
74
92
|
# Try as Python module
|
75
93
|
module_path = base_path / f"{plugin_name}.py"
|
76
94
|
if module_path.exists():
|
77
95
|
return _load_plugin_from_file(module_path)
|
78
|
-
|
96
|
+
|
97
|
+
# Check for builtin plugins
|
98
|
+
builtin_plugin = load_builtin_plugin(plugin_name)
|
99
|
+
if builtin_plugin:
|
100
|
+
return builtin_plugin
|
101
|
+
|
79
102
|
# Try importing as installed package
|
80
103
|
try:
|
81
104
|
return _load_plugin_from_package(plugin_name)
|
82
105
|
except ImportError:
|
83
106
|
pass
|
84
|
-
|
107
|
+
|
85
108
|
return None
|
86
109
|
|
87
110
|
|
@@ -91,42 +114,78 @@ def _load_plugin_from_directory(plugin_path: Path) -> Optional[Plugin]:
|
|
91
114
|
# Look for __init__.py or plugin.py
|
92
115
|
init_file = plugin_path / "__init__.py"
|
93
116
|
plugin_file = plugin_path / "plugin.py"
|
94
|
-
|
117
|
+
|
95
118
|
if init_file.exists():
|
96
119
|
return _load_plugin_from_file(init_file, plugin_name=plugin_path.name)
|
97
120
|
elif plugin_file.exists():
|
98
121
|
return _load_plugin_from_file(plugin_file, plugin_name=plugin_path.name)
|
99
|
-
|
122
|
+
|
100
123
|
except Exception as e:
|
101
124
|
logger.error(f"Failed to load plugin from directory {plugin_path}: {e}")
|
102
|
-
|
125
|
+
|
103
126
|
return None
|
104
127
|
|
105
128
|
|
106
|
-
def _load_plugin_from_file(
|
129
|
+
def _load_plugin_from_file(
|
130
|
+
file_path: Path, plugin_name: str = None
|
131
|
+
) -> Optional[Plugin]:
|
107
132
|
"""Load a plugin from a Python file."""
|
108
133
|
try:
|
109
134
|
if plugin_name is None:
|
110
135
|
plugin_name = file_path.stem
|
111
|
-
|
136
|
+
|
112
137
|
spec = importlib.util.spec_from_file_location(plugin_name, file_path)
|
113
138
|
if spec is None or spec.loader is None:
|
114
139
|
return None
|
115
|
-
|
140
|
+
|
116
141
|
module = importlib.util.module_from_spec(spec)
|
117
142
|
spec.loader.exec_module(module)
|
118
|
-
|
143
|
+
|
119
144
|
# Look for Plugin class
|
120
145
|
for attr_name in dir(module):
|
121
146
|
attr = getattr(module, attr_name)
|
122
|
-
if
|
123
|
-
issubclass(attr, Plugin) and
|
124
|
-
attr != Plugin):
|
147
|
+
if isinstance(attr, type) and issubclass(attr, Plugin) and attr != Plugin:
|
125
148
|
return attr()
|
126
|
-
|
149
|
+
|
150
|
+
# Check for package-based plugin with __plugin_name__ metadata
|
151
|
+
if hasattr(module, "__plugin_name__"):
|
152
|
+
from janito.plugins.base import PluginMetadata
|
153
|
+
|
154
|
+
# Create a dynamic plugin class
|
155
|
+
class PackagePlugin(Plugin):
|
156
|
+
def __init__(self):
|
157
|
+
super().__init__()
|
158
|
+
self._module = module
|
159
|
+
|
160
|
+
def get_metadata(self) -> PluginMetadata:
|
161
|
+
return PluginMetadata(
|
162
|
+
name=getattr(module, "__plugin_name__", plugin_name),
|
163
|
+
version=getattr(module, "__plugin_version__", "1.0.0"),
|
164
|
+
description=getattr(
|
165
|
+
module,
|
166
|
+
"__plugin_description__",
|
167
|
+
f"Package plugin: {plugin_name}",
|
168
|
+
),
|
169
|
+
author=getattr(module, "__plugin_author__", "Unknown"),
|
170
|
+
license=getattr(module, "__plugin_license__", "MIT"),
|
171
|
+
)
|
172
|
+
|
173
|
+
def get_tools(self):
|
174
|
+
return getattr(module, "__plugin_tools__", [])
|
175
|
+
|
176
|
+
def initialize(self):
|
177
|
+
if hasattr(module, "initialize"):
|
178
|
+
module.initialize()
|
179
|
+
|
180
|
+
def cleanup(self):
|
181
|
+
if hasattr(module, "cleanup"):
|
182
|
+
module.cleanup()
|
183
|
+
|
184
|
+
return PackagePlugin()
|
185
|
+
|
127
186
|
except Exception as e:
|
128
187
|
logger.error(f"Failed to load plugin from file {file_path}: {e}")
|
129
|
-
|
188
|
+
|
130
189
|
return None
|
131
190
|
|
132
191
|
|
@@ -134,59 +193,89 @@ def _load_plugin_from_package(package_name: str) -> Optional[Plugin]:
|
|
134
193
|
"""Load a plugin from an installed package."""
|
135
194
|
try:
|
136
195
|
module = importlib.import_module(package_name)
|
137
|
-
|
196
|
+
|
138
197
|
# Look for Plugin class
|
139
198
|
for attr_name in dir(module):
|
140
199
|
attr = getattr(module, attr_name)
|
141
|
-
if
|
142
|
-
issubclass(attr, Plugin) and
|
143
|
-
attr != Plugin):
|
200
|
+
if isinstance(attr, type) and issubclass(attr, Plugin) and attr != Plugin:
|
144
201
|
return attr()
|
145
|
-
|
202
|
+
|
146
203
|
except ImportError as e:
|
147
204
|
logger.debug(f"Could not import package {package_name}: {e}")
|
148
|
-
|
205
|
+
|
149
206
|
return None
|
150
207
|
|
151
208
|
|
152
209
|
def list_available_plugins(search_paths: List[Path] = None) -> List[str]:
|
153
210
|
"""
|
154
211
|
List all available plugins in search paths.
|
155
|
-
|
212
|
+
|
156
213
|
Scans for plugins in multiple formats:
|
157
214
|
- .py files (excluding __init__.py)
|
158
215
|
- Directories with __init__.py or plugin.py
|
216
|
+
- Package directories with plugin metadata (__plugin_name__)
|
159
217
|
- Any valid plugin structure in search paths
|
160
|
-
|
218
|
+
|
161
219
|
Args:
|
162
220
|
search_paths: List of directories to search for plugins
|
163
|
-
|
221
|
+
|
164
222
|
Returns:
|
165
223
|
List of plugin names found
|
166
224
|
"""
|
167
225
|
if search_paths is None:
|
168
226
|
search_paths = []
|
169
|
-
|
227
|
+
|
170
228
|
# Add default search paths
|
171
229
|
default_paths = [
|
172
230
|
Path.cwd() / "plugins",
|
173
|
-
Path.home() / ".janito" / "plugins",
|
231
|
+
Path.home() / ".janito" / "plugins",
|
174
232
|
Path(sys.prefix) / "share" / "janito" / "plugins",
|
175
233
|
]
|
176
|
-
|
234
|
+
|
177
235
|
all_paths = search_paths + default_paths
|
178
236
|
plugins = []
|
179
|
-
|
237
|
+
|
180
238
|
for base_path in all_paths:
|
181
239
|
if not base_path.exists():
|
182
240
|
continue
|
183
|
-
|
241
|
+
|
184
242
|
# Look for directories with __init__.py or plugin.py
|
185
243
|
for item in base_path.iterdir():
|
186
244
|
if item.is_dir():
|
187
|
-
|
245
|
+
# Check for package-based plugins (subdirectories with __init__.py)
|
246
|
+
if (item / "__init__.py").exists():
|
247
|
+
# Check subdirectories for plugin metadata
|
248
|
+
for subitem in item.iterdir():
|
249
|
+
if subitem.is_dir() and (subitem / "__init__.py").exists():
|
250
|
+
try:
|
251
|
+
import importlib.util
|
252
|
+
|
253
|
+
spec = importlib.util.spec_from_file_location(
|
254
|
+
f"{item.name}.{subitem.name}",
|
255
|
+
subitem / "__init__.py",
|
256
|
+
)
|
257
|
+
if spec and spec.loader:
|
258
|
+
module = importlib.util.module_from_spec(spec)
|
259
|
+
spec.loader.exec_module(module)
|
260
|
+
|
261
|
+
# Check for plugin metadata
|
262
|
+
if hasattr(module, "__plugin_name__"):
|
263
|
+
plugins.append(
|
264
|
+
getattr(module, "__plugin_name__")
|
265
|
+
)
|
266
|
+
except Exception:
|
267
|
+
pass
|
268
|
+
|
269
|
+
# Also check for plugin.py files
|
270
|
+
plugin_file = item / "plugin.py"
|
271
|
+
if plugin_file.exists():
|
188
272
|
plugins.append(item.name)
|
189
|
-
|
273
|
+
|
274
|
+
elif item.suffix == ".py" and item.stem != "__init__":
|
190
275
|
plugins.append(item.stem)
|
191
|
-
|
192
|
-
|
276
|
+
|
277
|
+
# Add builtin plugins
|
278
|
+
builtin_plugins = BuiltinPluginRegistry.list_builtin_plugins()
|
279
|
+
plugins.extend(builtin_plugins)
|
280
|
+
|
281
|
+
return sorted(set(plugins))
|
janito/plugins/manager.py
CHANGED
@@ -13,6 +13,7 @@ import logging
|
|
13
13
|
from .base import Plugin, PluginMetadata
|
14
14
|
from .discovery import discover_plugins
|
15
15
|
from .config import load_plugins_config, get_user_plugins_dir
|
16
|
+
from .builtin import BuiltinPluginRegistry, load_builtin_plugin
|
16
17
|
from janito.tools.adapters.local import LocalToolsAdapter
|
17
18
|
|
18
19
|
logger = logging.getLogger(__name__)
|
@@ -22,13 +23,13 @@ class PluginManager:
|
|
22
23
|
"""
|
23
24
|
Manages plugin loading, registration, and lifecycle.
|
24
25
|
"""
|
25
|
-
|
26
|
+
|
26
27
|
def __init__(self, tools_adapter: Optional[LocalToolsAdapter] = None):
|
27
28
|
self.tools_adapter = tools_adapter or LocalToolsAdapter()
|
28
29
|
self.plugins: Dict[str, Plugin] = {}
|
29
30
|
self.plugin_configs: Dict[str, Dict[str, Any]] = {}
|
30
31
|
self.plugin_paths: List[Path] = []
|
31
|
-
|
32
|
+
|
32
33
|
def add_plugin_path(self, path: str) -> None:
|
33
34
|
"""Add a directory to search for plugins."""
|
34
35
|
plugin_path = Path(path)
|
@@ -36,15 +37,17 @@ class PluginManager:
|
|
36
37
|
self.plugin_paths.append(plugin_path)
|
37
38
|
if str(plugin_path) not in sys.path:
|
38
39
|
sys.path.insert(0, str(plugin_path))
|
39
|
-
|
40
|
-
def load_plugin(
|
40
|
+
|
41
|
+
def load_plugin(
|
42
|
+
self, plugin_name: str, config: Optional[Dict[str, Any]] = None
|
43
|
+
) -> bool:
|
41
44
|
"""
|
42
45
|
Load a plugin by name.
|
43
|
-
|
46
|
+
|
44
47
|
Args:
|
45
48
|
plugin_name: Name of the plugin to load
|
46
49
|
config: Optional configuration for the plugin
|
47
|
-
|
50
|
+
|
48
51
|
Returns:
|
49
52
|
True if plugin loaded successfully
|
50
53
|
"""
|
@@ -52,47 +55,47 @@ class PluginManager:
|
|
52
55
|
if plugin_name in self.plugins:
|
53
56
|
logger.warning(f"Plugin {plugin_name} already loaded")
|
54
57
|
return True
|
55
|
-
|
58
|
+
|
56
59
|
plugin = discover_plugins(plugin_name, self.plugin_paths)
|
57
60
|
if not plugin:
|
58
61
|
logger.error(f"Plugin {plugin_name} not found")
|
59
62
|
return False
|
60
|
-
|
63
|
+
|
61
64
|
# Store config
|
62
65
|
if config:
|
63
66
|
self.plugin_configs[plugin_name] = config
|
64
|
-
|
67
|
+
|
65
68
|
# Validate config if provided
|
66
|
-
if config and hasattr(plugin,
|
69
|
+
if config and hasattr(plugin, "validate_config"):
|
67
70
|
if not plugin.validate_config(config):
|
68
71
|
logger.error(f"Invalid configuration for plugin {plugin_name}")
|
69
72
|
return False
|
70
|
-
|
73
|
+
|
71
74
|
# Initialize plugin
|
72
75
|
plugin.initialize()
|
73
|
-
|
76
|
+
|
74
77
|
# Register tools
|
75
78
|
tools = plugin.get_tools()
|
76
79
|
for tool_class in tools:
|
77
80
|
self.tools_adapter.register_tool(tool_class)
|
78
|
-
|
81
|
+
|
79
82
|
# Store plugin
|
80
83
|
self.plugins[plugin_name] = plugin
|
81
|
-
|
84
|
+
|
82
85
|
logger.info(f"Successfully loaded plugin: {plugin_name}")
|
83
86
|
return True
|
84
|
-
|
87
|
+
|
85
88
|
except Exception as e:
|
86
89
|
logger.error(f"Failed to load plugin {plugin_name}: {e}")
|
87
90
|
return False
|
88
|
-
|
91
|
+
|
89
92
|
def unload_plugin(self, plugin_name: str) -> bool:
|
90
93
|
"""
|
91
94
|
Unload a plugin.
|
92
|
-
|
95
|
+
|
93
96
|
Args:
|
94
97
|
plugin_name: Name of the plugin to unload
|
95
|
-
|
98
|
+
|
96
99
|
Returns:
|
97
100
|
True if plugin unloaded successfully
|
98
101
|
"""
|
@@ -100,65 +103,65 @@ class PluginManager:
|
|
100
103
|
if plugin_name not in self.plugins:
|
101
104
|
logger.warning(f"Plugin {plugin_name} not loaded")
|
102
105
|
return False
|
103
|
-
|
106
|
+
|
104
107
|
plugin = self.plugins[plugin_name]
|
105
|
-
|
108
|
+
|
106
109
|
# Unregister tools
|
107
110
|
tools = plugin.get_tools()
|
108
111
|
for tool_class in tools:
|
109
|
-
tool_name = getattr(tool_class(),
|
112
|
+
tool_name = getattr(tool_class(), "tool_name", None)
|
110
113
|
if tool_name:
|
111
114
|
self.tools_adapter.unregister_tool(tool_name)
|
112
|
-
|
115
|
+
|
113
116
|
# Cleanup plugin
|
114
117
|
plugin.cleanup()
|
115
|
-
|
118
|
+
|
116
119
|
# Remove from registry
|
117
120
|
del self.plugins[plugin_name]
|
118
121
|
if plugin_name in self.plugin_configs:
|
119
122
|
del self.plugin_configs[plugin_name]
|
120
|
-
|
123
|
+
|
121
124
|
logger.info(f"Successfully unloaded plugin: {plugin_name}")
|
122
125
|
return True
|
123
|
-
|
126
|
+
|
124
127
|
except Exception as e:
|
125
128
|
logger.error(f"Failed to unload plugin {plugin_name}: {e}")
|
126
129
|
return False
|
127
|
-
|
130
|
+
|
128
131
|
def list_plugins(self) -> List[str]:
|
129
132
|
"""Return list of loaded plugin names."""
|
130
133
|
return list(self.plugins.keys())
|
131
|
-
|
134
|
+
|
132
135
|
def get_plugin(self, plugin_name: str) -> Optional[Plugin]:
|
133
136
|
"""Get a loaded plugin by name."""
|
134
137
|
return self.plugins.get(plugin_name)
|
135
|
-
|
138
|
+
|
136
139
|
def get_plugin_metadata(self, plugin_name: str) -> Optional[PluginMetadata]:
|
137
140
|
"""Get metadata for a loaded plugin."""
|
138
141
|
plugin = self.plugins.get(plugin_name)
|
139
142
|
return plugin.metadata if plugin else None
|
140
|
-
|
143
|
+
|
141
144
|
def load_plugins_from_config(self, config: Dict[str, Any]) -> None:
|
142
145
|
"""
|
143
146
|
Load plugins from configuration.
|
144
|
-
|
147
|
+
|
145
148
|
Args:
|
146
149
|
config: Configuration dict with plugin settings
|
147
150
|
"""
|
148
|
-
plugins_config = config.get(
|
149
|
-
|
151
|
+
plugins_config = config.get("plugins", {})
|
152
|
+
|
150
153
|
# Add plugin paths
|
151
|
-
for path in plugins_config.get(
|
154
|
+
for path in plugins_config.get("paths", []):
|
152
155
|
self.add_plugin_path(path)
|
153
|
-
|
156
|
+
|
154
157
|
# Load plugins
|
155
|
-
for plugin_name, plugin_config in plugins_config.get(
|
158
|
+
for plugin_name, plugin_config in plugins_config.get("load", {}).items():
|
156
159
|
if isinstance(plugin_config, bool):
|
157
160
|
if plugin_config:
|
158
161
|
self.load_plugin(plugin_name)
|
159
162
|
else:
|
160
163
|
self.load_plugin(plugin_name, plugin_config)
|
161
|
-
|
164
|
+
|
162
165
|
def load_plugins_from_user_config(self) -> None:
|
163
166
|
"""
|
164
167
|
Load plugins from user configuration directory.
|
@@ -166,62 +169,63 @@ class PluginManager:
|
|
166
169
|
"""
|
167
170
|
config = load_plugins_config()
|
168
171
|
self.load_plugins_from_config(config)
|
169
|
-
|
172
|
+
|
170
173
|
def reload_plugin(self, plugin_name: str) -> bool:
|
171
174
|
"""
|
172
175
|
Reload a plugin.
|
173
|
-
|
176
|
+
|
174
177
|
Args:
|
175
178
|
plugin_name: Name of the plugin to reload
|
176
|
-
|
179
|
+
|
177
180
|
Returns:
|
178
181
|
True if plugin reloaded successfully
|
179
182
|
"""
|
180
183
|
config = self.plugin_configs.get(plugin_name)
|
181
184
|
self.unload_plugin(plugin_name)
|
182
185
|
return self.load_plugin(plugin_name, config)
|
183
|
-
|
186
|
+
|
184
187
|
def get_loaded_plugins_info(self) -> Dict[str, Dict[str, Any]]:
|
185
188
|
"""Get information about all loaded plugins."""
|
186
189
|
info = {}
|
187
190
|
for name, plugin in self.plugins.items():
|
188
191
|
info[name] = {
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
192
|
+
"metadata": plugin.metadata,
|
193
|
+
"tools": [tool.__name__ for tool in plugin.get_tools()],
|
194
|
+
"commands": list(plugin.get_commands().keys()),
|
195
|
+
"config": self.plugin_configs.get(name, {}),
|
196
|
+
"builtin": BuiltinPluginRegistry.is_builtin(name),
|
197
|
+
"resources": [
|
194
198
|
{
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
+
"name": resource.name,
|
200
|
+
"type": resource.type,
|
201
|
+
"description": resource.description,
|
202
|
+
"schema": resource.schema,
|
199
203
|
}
|
200
204
|
for resource in plugin.get_resources()
|
201
|
-
]
|
205
|
+
],
|
202
206
|
}
|
203
207
|
return info
|
204
208
|
|
205
209
|
def get_plugin_resources(self, plugin_name: str) -> List[Dict[str, Any]]:
|
206
210
|
"""
|
207
211
|
Get resources provided by a specific plugin.
|
208
|
-
|
212
|
+
|
209
213
|
Args:
|
210
214
|
plugin_name: Name of the plugin
|
211
|
-
|
215
|
+
|
212
216
|
Returns:
|
213
217
|
List of resource dictionaries
|
214
218
|
"""
|
215
219
|
plugin = self.plugins.get(plugin_name)
|
216
220
|
if not plugin:
|
217
221
|
return []
|
218
|
-
|
222
|
+
|
219
223
|
return [
|
220
224
|
{
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
+
"name": resource.name,
|
226
|
+
"type": resource.type,
|
227
|
+
"description": resource.description,
|
228
|
+
"schema": resource.schema,
|
225
229
|
}
|
226
230
|
for resource in plugin.get_resources()
|
227
231
|
]
|
@@ -229,11 +233,11 @@ class PluginManager:
|
|
229
233
|
def list_all_resources(self) -> Dict[str, List[Dict[str, Any]]]:
|
230
234
|
"""
|
231
235
|
List all resources from all loaded plugins.
|
232
|
-
|
236
|
+
|
233
237
|
Returns:
|
234
238
|
Dict mapping plugin names to their resources
|
235
239
|
"""
|
236
240
|
all_resources = {}
|
237
241
|
for plugin_name in self.plugins:
|
238
242
|
all_resources[plugin_name] = self.get_plugin_resources(plugin_name)
|
239
|
-
return all_resources
|
243
|
+
return all_resources
|