janito 2.22.0__py3-none-any.whl → 2.24.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 (67) hide show
  1. janito/README.md +0 -0
  2. janito/agent/setup_agent.py +14 -0
  3. janito/agent/templates/profiles/system_prompt_template_Developer_with_Python_Tools.txt.j2 +59 -11
  4. janito/agent/templates/profiles/system_prompt_template_developer.txt.j2 +53 -7
  5. janito/agent/templates/profiles/system_prompt_template_market_analyst.txt.j2 +108 -8
  6. janito/agent/templates/profiles/system_prompt_template_model_conversation_without_tools_or_context.txt.j2 +53 -1
  7. janito/cli/chat_mode/session.py +8 -1
  8. janito/cli/chat_mode/shell/commands/__init__.py +2 -0
  9. janito/cli/chat_mode/shell/commands/security/__init__.py +1 -0
  10. janito/cli/chat_mode/shell/commands/security/allowed_sites.py +94 -0
  11. janito/cli/chat_mode/shell/commands/security_command.py +51 -0
  12. janito/cli/chat_mode/shell/commands.bak.zip +0 -0
  13. janito/cli/chat_mode/shell/session.bak.zip +0 -0
  14. janito/cli/cli_commands/list_plugins.py +45 -0
  15. janito/cli/cli_commands/show_system_prompt.py +13 -40
  16. janito/cli/core/getters.py +4 -0
  17. janito/cli/core/runner.py +7 -2
  18. janito/cli/core/setters.py +10 -1
  19. janito/cli/main_cli.py +25 -3
  20. janito/cli/single_shot_mode/handler.py +3 -1
  21. janito/config_manager.py +10 -0
  22. janito/docs/GETTING_STARTED.md +0 -0
  23. janito/drivers/dashscope.bak.zip +0 -0
  24. janito/drivers/openai/README.md +0 -0
  25. janito/drivers/openai_responses.bak.zip +0 -0
  26. janito/llm/README.md +0 -0
  27. janito/mkdocs.yml +0 -0
  28. janito/plugins/__init__.py +17 -0
  29. janito/plugins/base.py +93 -0
  30. janito/plugins/discovery.py +160 -0
  31. janito/plugins/manager.py +185 -0
  32. janito/providers/dashscope.bak.zip +0 -0
  33. janito/providers/ibm/README.md +0 -0
  34. janito/shell.bak.zip +0 -0
  35. janito/tools/DOCSTRING_STANDARD.txt +0 -0
  36. janito/tools/README.md +0 -0
  37. janito/tools/adapters/local/__init__.py +2 -0
  38. janito/tools/adapters/local/adapter.py +55 -0
  39. janito/tools/adapters/local/ask_user.py +2 -0
  40. janito/tools/adapters/local/fetch_url.py +89 -4
  41. janito/tools/adapters/local/find_files.py +2 -0
  42. janito/tools/adapters/local/get_file_outline/core.py +2 -0
  43. janito/tools/adapters/local/get_file_outline/search_outline.py +2 -0
  44. janito/tools/adapters/local/open_html_in_browser.py +2 -0
  45. janito/tools/adapters/local/open_url.py +2 -0
  46. janito/tools/adapters/local/python_code_run.py +15 -10
  47. janito/tools/adapters/local/python_command_run.py +14 -9
  48. janito/tools/adapters/local/python_file_run.py +15 -10
  49. janito/tools/adapters/local/read_chart.py +252 -0
  50. janito/tools/adapters/local/read_files.py +2 -0
  51. janito/tools/adapters/local/replace_text_in_file.py +1 -1
  52. janito/tools/adapters/local/run_bash_command.py +18 -12
  53. janito/tools/adapters/local/run_powershell_command.py +15 -9
  54. janito/tools/adapters/local/search_text/core.py +2 -0
  55. janito/tools/adapters/local/validate_file_syntax/core.py +6 -0
  56. janito/tools/adapters/local/validate_file_syntax/jinja2_validator.py +47 -0
  57. janito/tools/adapters/local/view_file.py +2 -0
  58. janito/tools/loop_protection.py +115 -0
  59. janito/tools/loop_protection_decorator.py +110 -0
  60. janito/tools/outline_file.bak.zip +0 -0
  61. janito/tools/url_whitelist.py +121 -0
  62. {janito-2.22.0.dist-info → janito-2.24.0.dist-info}/METADATA +411 -411
  63. {janito-2.22.0.dist-info → janito-2.24.0.dist-info}/RECORD +52 -39
  64. {janito-2.22.0.dist-info → janito-2.24.0.dist-info}/entry_points.txt +0 -0
  65. {janito-2.22.0.dist-info → janito-2.24.0.dist-info}/licenses/LICENSE +0 -0
  66. {janito-2.22.0.dist-info → janito-2.24.0.dist-info}/top_level.txt +0 -0
  67. {janito-2.22.0.dist-info → janito-2.24.0.dist-info}/WHEEL +0 -0
@@ -10,6 +10,7 @@ from janito.cli.cli_commands.list_config import handle_list_config
10
10
  from janito.cli.cli_commands.list_drivers import handle_list_drivers
11
11
  from janito.regions.cli import handle_region_info
12
12
  from janito.cli.cli_commands.list_providers_region import handle_list_providers_region
13
+ from janito.cli.cli_commands.list_plugins import handle_list_plugins
13
14
  from functools import partial
14
15
  from janito.provider_registry import ProviderRegistry
15
16
 
@@ -23,6 +24,8 @@ GETTER_KEYS = [
23
24
  "list_drivers",
24
25
  "region_info",
25
26
  "list_providers_region",
27
+ "list_plugins",
28
+ "list_plugins_available",
26
29
  ]
27
30
 
28
31
 
@@ -51,6 +54,7 @@ def handle_getter(args, config_mgr=None):
51
54
  "list_drivers": partial(handle_list_drivers, args),
52
55
  "region_info": partial(handle_region_info, args),
53
56
  "list_providers_region": partial(handle_list_providers_region, args),
57
+ "list_plugins": partial(handle_list_plugins, args),
54
58
  }
55
59
  for arg in GETTER_KEYS:
56
60
  if getattr(args, arg, False) and arg in GETTER_DISPATCH:
janito/cli/core/runner.py CHANGED
@@ -144,13 +144,18 @@ def handle_runner(
144
144
 
145
145
  load_disabled_tools_from_config()
146
146
 
147
- unrestricted_paths = getattr(args, "unrestricted_paths", False)
147
+ unrestricted = getattr(args, "unrestricted", False)
148
148
  adapter = janito.tools.get_local_tools_adapter(
149
149
  workdir=getattr(args, "workdir", None)
150
150
  )
151
- if unrestricted_paths:
151
+ if unrestricted:
152
152
  # Patch: disable path security enforcement for this adapter instance
153
153
  setattr(adapter, "unrestricted_paths", True)
154
+
155
+ # Also disable URL whitelist restrictions in unrestricted mode
156
+ from janito.tools.url_whitelist import get_url_whitelist_manager
157
+ whitelist_manager = get_url_whitelist_manager()
158
+ whitelist_manager.set_unrestricted_mode(True)
154
159
 
155
160
  # Print allowed permissions in verbose mode
156
161
  if getattr(args, "verbose", False):
@@ -69,8 +69,17 @@ def _dispatch_set_key(key, value):
69
69
  global_config.file_set("disabled_tools", value)
70
70
  print(f"Disabled tools set to '{value}'")
71
71
  return True
72
+ if key == "allowed_sites":
73
+ from janito.tools.url_whitelist import get_url_whitelist_manager
74
+
75
+ sites = [site.strip() for site in value.split(',') if site.strip()]
76
+ whitelist_manager = get_url_whitelist_manager()
77
+ whitelist_manager.set_allowed_sites(sites)
78
+ global_config.file_set("allowed_sites", value)
79
+ print(f"Allowed sites set to: {', '.join(sites)}")
80
+ return True
72
81
  print(
73
- f"Error: Unknown config key '{key}'. Supported: provider, model, max_tokens, base_url, azure_deployment_name, tool_permissions, disabled_tools"
82
+ f"Error: Unknown config key '{key}'. Supported: provider, model, max_tokens, base_url, azure_deployment_name, tool_permissions, disabled_tools, allowed_sites"
74
83
  )
75
84
  return True
76
85
 
janito/cli/main_cli.py CHANGED
@@ -13,12 +13,13 @@ from janito.cli.core.event_logger import (
13
13
  inject_debug_event_bus_if_needed,
14
14
  )
15
15
 
16
+
16
17
  definition = [
17
18
  (
18
- ["-u", "--unrestricted-paths"],
19
+ ["-u", "--unrestricted"],
19
20
  {
20
21
  "action": "store_true",
21
- "help": "Disable path security: allow tool arguments to use any file/directory path (DANGEROUS)",
22
+ "help": "Unrestricted mode: disable path security and URL whitelist restrictions (DANGEROUS)",
22
23
  },
23
24
  ),
24
25
  (
@@ -43,6 +44,13 @@ definition = [
43
44
  "help": "Start with the Python developer profile (equivalent to --profile 'Developer with Python Tools')",
44
45
  },
45
46
  ),
47
+ (
48
+ ["--market"],
49
+ {
50
+ "action": "store_true",
51
+ "help": "Start with the Market Analyst profile (equivalent to --profile 'Market Analyst')",
52
+ },
53
+ ),
46
54
  (
47
55
  ["--role"],
48
56
  {
@@ -149,6 +157,7 @@ definition = [
149
157
  "help": "List all providers with their regional API information",
150
158
  },
151
159
  ),
160
+
152
161
  (
153
162
  ["-l", "--list-models"],
154
163
  {"action": "store_true", "help": "List all supported models"},
@@ -213,6 +222,14 @@ definition = [
213
222
  "help": "Use custom configuration file ~/.janito/configs/NAME.json instead of default config.json",
214
223
  },
215
224
  ),
225
+ (
226
+ ["--list-plugins"],
227
+ {"action": "store_true", "help": "List all loaded plugins"},
228
+ ),
229
+ (
230
+ ["--list-plugins-available"],
231
+ {"action": "store_true", "help": "List all available plugins"},
232
+ ),
216
233
  ]
217
234
 
218
235
  MODIFIER_KEYS = [
@@ -221,6 +238,7 @@ MODIFIER_KEYS = [
221
238
  "role",
222
239
  "profile",
223
240
  "python",
241
+ "market",
224
242
  "system",
225
243
  "temperature",
226
244
  "verbose",
@@ -323,6 +341,8 @@ class JanitoCLI:
323
341
 
324
342
  argkwargs["version"] = f"Janito {janito_version}"
325
343
  self.parser.add_argument(*argnames, **argkwargs)
344
+
345
+
326
346
 
327
347
  def _set_all_arg_defaults(self):
328
348
  # Gather all possible keys from definition, MODIFIER_KEYS, SETTER_KEYS, GETTER_KEYS
@@ -370,7 +390,7 @@ class JanitoCLI:
370
390
  if run_mode == RunMode.SET:
371
391
  if self._run_set_mode():
372
392
  return
373
- # Special handling: provider is not required for list_providers, list_tools, show_config, list_drivers
393
+ # Special handling: provider is not required for list commands
374
394
  if run_mode == RunMode.GET and (
375
395
  self.args.list_providers
376
396
  or self.args.list_tools
@@ -378,6 +398,8 @@ class JanitoCLI:
378
398
  or self.args.show_config
379
399
  or self.args.list_config
380
400
  or self.args.list_drivers
401
+ or self.args.list_plugins
402
+ or self.args.list_plugins_available
381
403
  or self.args.ping
382
404
  ):
383
405
  self._maybe_print_verbose_provider_model()
@@ -24,10 +24,12 @@ class PromptHandler:
24
24
  self.llm_driver_config = llm_driver_config
25
25
  self.role = role
26
26
  # Instantiate agent together with prompt handler using the shared helper
27
- # Handle --python flag for single shot mode
27
+ # Handle --python and --market flags for single shot mode
28
28
  profile = getattr(args, "profile", None)
29
29
  if profile is None and getattr(args, "python", False):
30
30
  profile = "Developer with Python Tools"
31
+ if profile is None and getattr(args, "market", False):
32
+ profile = "Market Analyst"
31
33
 
32
34
  self.agent, self.generic_handler = setup_agent_and_prompt_handler(
33
35
  args=args,
janito/config_manager.py CHANGED
@@ -55,6 +55,16 @@ class ConfigManager:
55
55
  except Exception as e:
56
56
  print(f"Warning: Failed to apply tool_permissions from config: {e}")
57
57
 
58
+ # Load plugins from config
59
+ plugins_config = self.file_config.get("plugins", {})
60
+ if plugins_config:
61
+ try:
62
+ from janito.plugins.manager import PluginManager
63
+ plugin_manager = PluginManager()
64
+ plugin_manager.load_plugins_from_config({"plugins": plugins_config})
65
+ except Exception as e:
66
+ print(f"Warning: Failed to load plugins from config: {e}")
67
+
58
68
  # Load disabled tools from config - skip during startup to avoid circular imports
59
69
  # This will be handled by the CLI when needed
60
70
 
File without changes
File without changes
File without changes
File without changes
janito/llm/README.md CHANGED
File without changes
janito/mkdocs.yml CHANGED
File without changes
@@ -0,0 +1,17 @@
1
+ """
2
+ Plugin system for janito.
3
+
4
+ This package provides a flexible plugin system that allows extending
5
+ janito's functionality with custom tools, commands, and features.
6
+ """
7
+
8
+ from .manager import PluginManager
9
+ from .base import Plugin, PluginMetadata
10
+ from .discovery import discover_plugins
11
+
12
+ __all__ = [
13
+ "PluginManager",
14
+ "Plugin",
15
+ "PluginMetadata",
16
+ "discover_plugins",
17
+ ]
janito/plugins/base.py ADDED
@@ -0,0 +1,93 @@
1
+ """
2
+ Base classes for janito plugins.
3
+ """
4
+
5
+ from abc import ABC, abstractmethod
6
+ from dataclasses import dataclass
7
+ from typing import Dict, Any, List, Optional, Type
8
+ from janito.tools.tool_base import ToolBase
9
+
10
+
11
+ @dataclass
12
+ class PluginMetadata:
13
+ """Metadata describing a plugin."""
14
+ name: str
15
+ version: str
16
+ description: str
17
+ author: str
18
+ license: str = "MIT"
19
+ homepage: Optional[str] = None
20
+ dependencies: List[str] = None
21
+
22
+ def __post_init__(self):
23
+ if self.dependencies is None:
24
+ self.dependencies = []
25
+
26
+
27
+ class Plugin(ABC):
28
+ """
29
+ Base class for all janito plugins.
30
+
31
+ Plugins can provide tools, commands, or other functionality.
32
+ """
33
+
34
+ def __init__(self):
35
+ self.metadata: PluginMetadata = self.get_metadata()
36
+
37
+ @abstractmethod
38
+ def get_metadata(self) -> PluginMetadata:
39
+ """Return metadata describing this plugin."""
40
+ pass
41
+
42
+ def get_tools(self) -> List[Type[ToolBase]]:
43
+ """
44
+ Return a list of tool classes provided by this plugin.
45
+
46
+ Returns:
47
+ List of ToolBase subclasses that should be registered
48
+ """
49
+ return []
50
+
51
+ def get_commands(self) -> Dict[str, Any]:
52
+ """
53
+ Return a dictionary of CLI commands provided by this plugin.
54
+
55
+ Returns:
56
+ Dict mapping command names to command handlers
57
+ """
58
+ return {}
59
+
60
+ def initialize(self) -> None:
61
+ """
62
+ Called when the plugin is loaded.
63
+ Override to perform any initialization needed.
64
+ """
65
+ pass
66
+
67
+ def cleanup(self) -> None:
68
+ """
69
+ Called when the plugin is unloaded.
70
+ Override to perform any cleanup needed.
71
+ """
72
+ pass
73
+
74
+ def get_config_schema(self) -> Dict[str, Any]:
75
+ """
76
+ Return JSON schema for plugin configuration.
77
+
78
+ Returns:
79
+ JSON schema dict describing configuration options
80
+ """
81
+ return {}
82
+
83
+ def validate_config(self, config: Dict[str, Any]) -> bool:
84
+ """
85
+ Validate plugin configuration.
86
+
87
+ Args:
88
+ config: Configuration dict to validate
89
+
90
+ Returns:
91
+ True if configuration is valid
92
+ """
93
+ return True
@@ -0,0 +1,160 @@
1
+ """
2
+ Plugin discovery utilities.
3
+ """
4
+
5
+ import os
6
+ import sys
7
+ import importlib
8
+ import importlib.util
9
+ from pathlib import Path
10
+ from typing import Optional, List
11
+ import logging
12
+
13
+ from .base import Plugin
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ def discover_plugins(plugin_name: str, search_paths: List[Path] = None) -> Optional[Plugin]:
19
+ """
20
+ Discover and load a plugin by name.
21
+
22
+ Args:
23
+ plugin_name: Name of the plugin to discover
24
+ search_paths: List of directories to search for plugins
25
+
26
+ Returns:
27
+ Plugin instance if found, None otherwise
28
+ """
29
+ if search_paths is None:
30
+ search_paths = []
31
+
32
+ # Add default search paths
33
+ default_paths = [
34
+ Path.cwd() / "plugins",
35
+ Path.home() / ".janito" / "plugins",
36
+ Path(sys.prefix) / "share" / "janito" / "plugins",
37
+ ]
38
+
39
+ all_paths = search_paths + default_paths
40
+
41
+ # Try to find plugin in search paths
42
+ for base_path in all_paths:
43
+ plugin_path = base_path / plugin_name
44
+ if plugin_path.exists():
45
+ return _load_plugin_from_directory(plugin_path)
46
+
47
+ # Try as Python module
48
+ module_path = base_path / f"{plugin_name}.py"
49
+ if module_path.exists():
50
+ return _load_plugin_from_file(module_path)
51
+
52
+ # Try importing as installed package
53
+ try:
54
+ return _load_plugin_from_package(plugin_name)
55
+ except ImportError:
56
+ pass
57
+
58
+ return None
59
+
60
+
61
+ def _load_plugin_from_directory(plugin_path: Path) -> Optional[Plugin]:
62
+ """Load a plugin from a directory."""
63
+ try:
64
+ # Look for __init__.py or plugin.py
65
+ init_file = plugin_path / "__init__.py"
66
+ plugin_file = plugin_path / "plugin.py"
67
+
68
+ if init_file.exists():
69
+ return _load_plugin_from_file(init_file, plugin_name=plugin_path.name)
70
+ elif plugin_file.exists():
71
+ return _load_plugin_from_file(plugin_file, plugin_name=plugin_path.name)
72
+
73
+ except Exception as e:
74
+ logger.error(f"Failed to load plugin from directory {plugin_path}: {e}")
75
+
76
+ return None
77
+
78
+
79
+ def _load_plugin_from_file(file_path: Path, plugin_name: str = None) -> Optional[Plugin]:
80
+ """Load a plugin from a Python file."""
81
+ try:
82
+ if plugin_name is None:
83
+ plugin_name = file_path.stem
84
+
85
+ spec = importlib.util.spec_from_file_location(plugin_name, file_path)
86
+ if spec is None or spec.loader is None:
87
+ return None
88
+
89
+ module = importlib.util.module_from_spec(spec)
90
+ spec.loader.exec_module(module)
91
+
92
+ # Look for Plugin class
93
+ for attr_name in dir(module):
94
+ attr = getattr(module, attr_name)
95
+ if (isinstance(attr, type) and
96
+ issubclass(attr, Plugin) and
97
+ attr != Plugin):
98
+ return attr()
99
+
100
+ except Exception as e:
101
+ logger.error(f"Failed to load plugin from file {file_path}: {e}")
102
+
103
+ return None
104
+
105
+
106
+ def _load_plugin_from_package(package_name: str) -> Optional[Plugin]:
107
+ """Load a plugin from an installed package."""
108
+ try:
109
+ module = importlib.import_module(package_name)
110
+
111
+ # Look for Plugin class
112
+ for attr_name in dir(module):
113
+ attr = getattr(module, attr_name)
114
+ if (isinstance(attr, type) and
115
+ issubclass(attr, Plugin) and
116
+ attr != Plugin):
117
+ return attr()
118
+
119
+ except ImportError as e:
120
+ logger.debug(f"Could not import package {package_name}: {e}")
121
+
122
+ return None
123
+
124
+
125
+ def list_available_plugins(search_paths: List[Path] = None) -> List[str]:
126
+ """
127
+ List all available plugins in search paths.
128
+
129
+ Args:
130
+ search_paths: List of directories to search for plugins
131
+
132
+ Returns:
133
+ List of plugin names found
134
+ """
135
+ if search_paths is None:
136
+ search_paths = []
137
+
138
+ # Add default search paths
139
+ default_paths = [
140
+ Path.cwd() / "plugins",
141
+ Path.home() / ".janito" / "plugins",
142
+ Path(sys.prefix) / "share" / "janito" / "plugins",
143
+ ]
144
+
145
+ all_paths = search_paths + default_paths
146
+ plugins = []
147
+
148
+ for base_path in all_paths:
149
+ if not base_path.exists():
150
+ continue
151
+
152
+ # Look for directories with __init__.py or plugin.py
153
+ for item in base_path.iterdir():
154
+ if item.is_dir():
155
+ if (item / "__init__.py").exists() or (item / "plugin.py").exists():
156
+ plugins.append(item.name)
157
+ elif item.suffix == '.py' and item.stem != '__init__':
158
+ plugins.append(item.stem)
159
+
160
+ return sorted(set(plugins))
@@ -0,0 +1,185 @@
1
+ """
2
+ Plugin manager for loading and managing plugins.
3
+ """
4
+
5
+ import os
6
+ import sys
7
+ import importlib
8
+ import importlib.util
9
+ from pathlib import Path
10
+ from typing import Dict, List, Optional, Any
11
+ import logging
12
+
13
+ from .base import Plugin, PluginMetadata
14
+ from .discovery import discover_plugins
15
+ from janito.tools.adapters.local import LocalToolsAdapter
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ class PluginManager:
21
+ """
22
+ Manages plugin loading, registration, and lifecycle.
23
+ """
24
+
25
+ def __init__(self, tools_adapter: Optional[LocalToolsAdapter] = None):
26
+ self.tools_adapter = tools_adapter or LocalToolsAdapter()
27
+ self.plugins: Dict[str, Plugin] = {}
28
+ self.plugin_configs: Dict[str, Dict[str, Any]] = {}
29
+ self.plugin_paths: List[Path] = []
30
+
31
+ def add_plugin_path(self, path: str) -> None:
32
+ """Add a directory to search for plugins."""
33
+ plugin_path = Path(path)
34
+ if plugin_path.exists() and plugin_path.is_dir():
35
+ self.plugin_paths.append(plugin_path)
36
+ if str(plugin_path) not in sys.path:
37
+ sys.path.insert(0, str(plugin_path))
38
+
39
+ def load_plugin(self, plugin_name: str, config: Optional[Dict[str, Any]] = None) -> bool:
40
+ """
41
+ Load a plugin by name.
42
+
43
+ Args:
44
+ plugin_name: Name of the plugin to load
45
+ config: Optional configuration for the plugin
46
+
47
+ Returns:
48
+ True if plugin loaded successfully
49
+ """
50
+ try:
51
+ if plugin_name in self.plugins:
52
+ logger.warning(f"Plugin {plugin_name} already loaded")
53
+ return True
54
+
55
+ plugin = discover_plugins(plugin_name, self.plugin_paths)
56
+ if not plugin:
57
+ logger.error(f"Plugin {plugin_name} not found")
58
+ return False
59
+
60
+ # Store config
61
+ if config:
62
+ self.plugin_configs[plugin_name] = config
63
+
64
+ # Validate config if provided
65
+ if config and hasattr(plugin, 'validate_config'):
66
+ if not plugin.validate_config(config):
67
+ logger.error(f"Invalid configuration for plugin {plugin_name}")
68
+ return False
69
+
70
+ # Initialize plugin
71
+ plugin.initialize()
72
+
73
+ # Register tools
74
+ tools = plugin.get_tools()
75
+ for tool_class in tools:
76
+ self.tools_adapter.register_tool(tool_class)
77
+
78
+ # Store plugin
79
+ self.plugins[plugin_name] = plugin
80
+
81
+ logger.info(f"Successfully loaded plugin: {plugin_name}")
82
+ return True
83
+
84
+ except Exception as e:
85
+ logger.error(f"Failed to load plugin {plugin_name}: {e}")
86
+ return False
87
+
88
+ def unload_plugin(self, plugin_name: str) -> bool:
89
+ """
90
+ Unload a plugin.
91
+
92
+ Args:
93
+ plugin_name: Name of the plugin to unload
94
+
95
+ Returns:
96
+ True if plugin unloaded successfully
97
+ """
98
+ try:
99
+ if plugin_name not in self.plugins:
100
+ logger.warning(f"Plugin {plugin_name} not loaded")
101
+ return False
102
+
103
+ plugin = self.plugins[plugin_name]
104
+
105
+ # Unregister tools
106
+ tools = plugin.get_tools()
107
+ for tool_class in tools:
108
+ tool_name = getattr(tool_class(), 'tool_name', None)
109
+ if tool_name:
110
+ self.tools_adapter.unregister_tool(tool_name)
111
+
112
+ # Cleanup plugin
113
+ plugin.cleanup()
114
+
115
+ # Remove from registry
116
+ del self.plugins[plugin_name]
117
+ if plugin_name in self.plugin_configs:
118
+ del self.plugin_configs[plugin_name]
119
+
120
+ logger.info(f"Successfully unloaded plugin: {plugin_name}")
121
+ return True
122
+
123
+ except Exception as e:
124
+ logger.error(f"Failed to unload plugin {plugin_name}: {e}")
125
+ return False
126
+
127
+ def list_plugins(self) -> List[str]:
128
+ """Return list of loaded plugin names."""
129
+ return list(self.plugins.keys())
130
+
131
+ def get_plugin(self, plugin_name: str) -> Optional[Plugin]:
132
+ """Get a loaded plugin by name."""
133
+ return self.plugins.get(plugin_name)
134
+
135
+ def get_plugin_metadata(self, plugin_name: str) -> Optional[PluginMetadata]:
136
+ """Get metadata for a loaded plugin."""
137
+ plugin = self.plugins.get(plugin_name)
138
+ return plugin.metadata if plugin else None
139
+
140
+ def load_plugins_from_config(self, config: Dict[str, Any]) -> None:
141
+ """
142
+ Load plugins from configuration.
143
+
144
+ Args:
145
+ config: Configuration dict with plugin settings
146
+ """
147
+ plugins_config = config.get('plugins', {})
148
+
149
+ # Add plugin paths
150
+ for path in plugins_config.get('paths', []):
151
+ self.add_plugin_path(path)
152
+
153
+ # Load plugins
154
+ for plugin_name, plugin_config in plugins_config.get('load', {}).items():
155
+ if isinstance(plugin_config, bool):
156
+ if plugin_config:
157
+ self.load_plugin(plugin_name)
158
+ else:
159
+ self.load_plugin(plugin_name, plugin_config)
160
+
161
+ def reload_plugin(self, plugin_name: str) -> bool:
162
+ """
163
+ Reload a plugin.
164
+
165
+ Args:
166
+ plugin_name: Name of the plugin to reload
167
+
168
+ Returns:
169
+ True if plugin reloaded successfully
170
+ """
171
+ config = self.plugin_configs.get(plugin_name)
172
+ self.unload_plugin(plugin_name)
173
+ return self.load_plugin(plugin_name, config)
174
+
175
+ def get_loaded_plugins_info(self) -> Dict[str, Dict[str, Any]]:
176
+ """Get information about all loaded plugins."""
177
+ info = {}
178
+ for name, plugin in self.plugins.items():
179
+ info[name] = {
180
+ 'metadata': plugin.metadata,
181
+ 'tools': [tool.__name__ for tool in plugin.get_tools()],
182
+ 'commands': list(plugin.get_commands().keys()),
183
+ 'config': self.plugin_configs.get(name, {})
184
+ }
185
+ return info
File without changes
File without changes
janito/shell.bak.zip CHANGED
File without changes
File without changes
janito/tools/README.md CHANGED
File without changes
@@ -23,6 +23,7 @@ from .get_file_outline.core import GetFileOutlineTool
23
23
  from .get_file_outline.search_outline import SearchOutlineTool
24
24
  from .search_text.core import SearchTextTool
25
25
  from .validate_file_syntax.core import ValidateFileSyntaxTool
26
+ from .read_chart import ReadChartTool
26
27
 
27
28
  from janito.tools.tool_base import ToolPermissions
28
29
  import os
@@ -61,6 +62,7 @@ for tool_class in [
61
62
  SearchOutlineTool,
62
63
  SearchTextTool,
63
64
  ValidateFileSyntaxTool,
65
+ ReadChartTool,
64
66
  ]:
65
67
  local_tools_adapter.register_tool(tool_class)
66
68