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.
Files changed (88) hide show
  1. janito/README.md +9 -9
  2. janito/agent/setup_agent.py +29 -16
  3. janito/cli/chat_mode/script_runner.py +1 -1
  4. janito/cli/chat_mode/session.py +50 -24
  5. janito/cli/chat_mode/session_profile_select.py +8 -2
  6. janito/cli/chat_mode/shell/commands/__init__.py +2 -0
  7. janito/cli/chat_mode/shell/commands/execute.py +4 -2
  8. janito/cli/chat_mode/shell/commands/help.py +8 -1
  9. janito/cli/chat_mode/shell/commands/privileges.py +6 -2
  10. janito/cli/chat_mode/shell/commands/provider.py +28 -0
  11. janito/cli/chat_mode/shell/commands/read.py +4 -2
  12. janito/cli/chat_mode/shell/commands/security/__init__.py +1 -1
  13. janito/cli/chat_mode/shell/commands/security/allowed_sites.py +16 -13
  14. janito/cli/chat_mode/shell/commands/security_command.py +14 -10
  15. janito/cli/chat_mode/shell/commands/tools.py +4 -2
  16. janito/cli/chat_mode/shell/commands/unrestricted.py +17 -12
  17. janito/cli/chat_mode/shell/commands/write.py +4 -2
  18. janito/cli/chat_mode/toolbar.py +15 -1
  19. janito/cli/cli_commands/enable_disable_plugin.py +87 -0
  20. janito/cli/cli_commands/list_models.py +2 -2
  21. janito/cli/cli_commands/list_plugins.py +35 -19
  22. janito/cli/cli_commands/list_profiles.py +6 -6
  23. janito/cli/cli_commands/list_providers.py +1 -1
  24. janito/cli/cli_commands/model_utils.py +45 -20
  25. janito/cli/cli_commands/ping_providers.py +10 -10
  26. janito/cli/cli_commands/set_api_key.py +5 -3
  27. janito/cli/cli_commands/show_config.py +13 -7
  28. janito/cli/cli_commands/show_system_prompt.py +13 -6
  29. janito/cli/core/getters.py +7 -0
  30. janito/cli/core/model_guesser.py +18 -15
  31. janito/cli/core/runner.py +28 -6
  32. janito/cli/core/setters.py +21 -6
  33. janito/cli/main_cli.py +14 -12
  34. janito/cli/prompt_core.py +2 -0
  35. janito/cli/prompt_setup.py +4 -4
  36. janito/cli/single_shot_mode/handler.py +2 -0
  37. janito/config_manager.py +2 -0
  38. janito/docs/GETTING_STARTED.md +9 -9
  39. janito/drivers/cerebras/__init__.py +1 -1
  40. janito/exceptions.py +6 -4
  41. janito/plugins/__init__.py +2 -2
  42. janito/plugins/base.py +48 -40
  43. janito/plugins/builtin.py +88 -0
  44. janito/plugins/config.py +16 -19
  45. janito/plugins/discovery.py +129 -40
  46. janito/plugins/manager.py +63 -59
  47. janito/provider_registry.py +10 -10
  48. janito/providers/__init__.py +1 -1
  49. janito/providers/alibaba/model_info.py +3 -5
  50. janito/providers/alibaba/provider.py +3 -1
  51. janito/providers/cerebras/__init__.py +1 -1
  52. janito/providers/cerebras/model_info.py +12 -27
  53. janito/providers/cerebras/provider.py +11 -9
  54. janito/providers/mistral/__init__.py +1 -1
  55. janito/providers/mistral/model_info.py +1 -1
  56. janito/providers/mistral/provider.py +1 -1
  57. janito/providers/moonshot/__init__.py +1 -0
  58. janito/providers/{moonshotai → moonshot}/model_info.py +3 -3
  59. janito/providers/{moonshotai → moonshot}/provider.py +8 -8
  60. janito/providers/openai/provider.py +3 -1
  61. janito/report_events.py +0 -1
  62. janito/tools/adapters/local/ask_user.py +7 -1
  63. janito/tools/adapters/local/create_file.py +1 -1
  64. janito/tools/adapters/local/fetch_url.py +45 -29
  65. janito/tools/adapters/local/python_command_run.py +2 -1
  66. janito/tools/adapters/local/python_file_run.py +1 -0
  67. janito/tools/adapters/local/run_powershell_command.py +1 -1
  68. janito/tools/adapters/local/search_text/core.py +1 -1
  69. janito/tools/adapters/local/validate_file_syntax/jinja2_validator.py +14 -11
  70. janito/tools/base.py +12 -0
  71. janito/tools/loop_protection.py +24 -22
  72. janito/tools/path_utils.py +7 -7
  73. janito/tools/tool_base.py +0 -2
  74. janito/tools/tools_adapter.py +15 -5
  75. janito/tools/url_whitelist.py +27 -26
  76. {janito-2.27.0.dist-info → janito-2.28.0.dist-info}/METADATA +3 -1
  77. {janito-2.27.0.dist-info → janito-2.28.0.dist-info}/RECORD +81 -82
  78. janito-2.28.0.dist-info/top_level.txt +1 -0
  79. janito/providers/moonshotai/__init__.py +0 -1
  80. janito-2.27.0.dist-info/top_level.txt +0 -2
  81. janito-coder/janito_coder/__init__.py +0 -9
  82. janito-coder/janito_coder/plugins/__init__.py +0 -27
  83. janito-coder/janito_coder/plugins/code_navigator.py +0 -618
  84. janito-coder/janito_coder/plugins/git_analyzer.py +0 -273
  85. janito-coder/pyproject.toml +0 -347
  86. {janito-2.27.0.dist-info → janito-2.28.0.dist-info}/WHEEL +0 -0
  87. {janito-2.27.0.dist-info → janito-2.28.0.dist-info}/entry_points.txt +0 -0
  88. {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
- str(Path.home() / ".janito" / "plugins"),
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, 'w') as f:
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, 'r') as f:
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, 'w') as f:
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
@@ -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(plugin_name: str, search_paths: List[Path] = None) -> Optional[Plugin]:
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(file_path: Path, plugin_name: str = None) -> Optional[Plugin]:
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 (isinstance(attr, type) and
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 (isinstance(attr, type) and
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
- if (item / "__init__.py").exists() or (item / "plugin.py").exists():
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
- elif item.suffix == '.py' and item.stem != '__init__':
273
+
274
+ elif item.suffix == ".py" and item.stem != "__init__":
190
275
  plugins.append(item.stem)
191
-
192
- return sorted(set(plugins))
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(self, plugin_name: str, config: Optional[Dict[str, Any]] = None) -> bool:
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, 'validate_config'):
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(), 'tool_name', None)
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('plugins', {})
149
-
151
+ plugins_config = config.get("plugins", {})
152
+
150
153
  # Add plugin paths
151
- for path in plugins_config.get('paths', []):
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('load', {}).items():
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
- 'metadata': plugin.metadata,
190
- 'tools': [tool.__name__ for tool in plugin.get_tools()],
191
- 'commands': list(plugin.get_commands().keys()),
192
- 'config': self.plugin_configs.get(name, {}),
193
- 'resources': [
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
- 'name': resource.name,
196
- 'type': resource.type,
197
- 'description': resource.description,
198
- 'schema': resource.schema
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
- 'name': resource.name,
222
- 'type': resource.type,
223
- 'description': resource.description,
224
- 'schema': resource.schema
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