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.
- janito/README.md +0 -0
- janito/agent/setup_agent.py +14 -0
- janito/agent/templates/profiles/system_prompt_template_Developer_with_Python_Tools.txt.j2 +59 -11
- janito/agent/templates/profiles/system_prompt_template_developer.txt.j2 +53 -7
- janito/agent/templates/profiles/system_prompt_template_market_analyst.txt.j2 +108 -8
- janito/agent/templates/profiles/system_prompt_template_model_conversation_without_tools_or_context.txt.j2 +53 -1
- janito/cli/chat_mode/session.py +8 -1
- janito/cli/chat_mode/shell/commands/__init__.py +2 -0
- janito/cli/chat_mode/shell/commands/security/__init__.py +1 -0
- janito/cli/chat_mode/shell/commands/security/allowed_sites.py +94 -0
- janito/cli/chat_mode/shell/commands/security_command.py +51 -0
- janito/cli/chat_mode/shell/commands.bak.zip +0 -0
- janito/cli/chat_mode/shell/session.bak.zip +0 -0
- janito/cli/cli_commands/list_plugins.py +45 -0
- janito/cli/cli_commands/show_system_prompt.py +13 -40
- janito/cli/core/getters.py +4 -0
- janito/cli/core/runner.py +7 -2
- janito/cli/core/setters.py +10 -1
- janito/cli/main_cli.py +25 -3
- janito/cli/single_shot_mode/handler.py +3 -1
- janito/config_manager.py +10 -0
- janito/docs/GETTING_STARTED.md +0 -0
- janito/drivers/dashscope.bak.zip +0 -0
- janito/drivers/openai/README.md +0 -0
- janito/drivers/openai_responses.bak.zip +0 -0
- janito/llm/README.md +0 -0
- janito/mkdocs.yml +0 -0
- janito/plugins/__init__.py +17 -0
- janito/plugins/base.py +93 -0
- janito/plugins/discovery.py +160 -0
- janito/plugins/manager.py +185 -0
- janito/providers/dashscope.bak.zip +0 -0
- janito/providers/ibm/README.md +0 -0
- janito/shell.bak.zip +0 -0
- janito/tools/DOCSTRING_STANDARD.txt +0 -0
- janito/tools/README.md +0 -0
- janito/tools/adapters/local/__init__.py +2 -0
- janito/tools/adapters/local/adapter.py +55 -0
- janito/tools/adapters/local/ask_user.py +2 -0
- janito/tools/adapters/local/fetch_url.py +89 -4
- janito/tools/adapters/local/find_files.py +2 -0
- janito/tools/adapters/local/get_file_outline/core.py +2 -0
- janito/tools/adapters/local/get_file_outline/search_outline.py +2 -0
- janito/tools/adapters/local/open_html_in_browser.py +2 -0
- janito/tools/adapters/local/open_url.py +2 -0
- janito/tools/adapters/local/python_code_run.py +15 -10
- janito/tools/adapters/local/python_command_run.py +14 -9
- janito/tools/adapters/local/python_file_run.py +15 -10
- janito/tools/adapters/local/read_chart.py +252 -0
- janito/tools/adapters/local/read_files.py +2 -0
- janito/tools/adapters/local/replace_text_in_file.py +1 -1
- janito/tools/adapters/local/run_bash_command.py +18 -12
- janito/tools/adapters/local/run_powershell_command.py +15 -9
- janito/tools/adapters/local/search_text/core.py +2 -0
- janito/tools/adapters/local/validate_file_syntax/core.py +6 -0
- janito/tools/adapters/local/validate_file_syntax/jinja2_validator.py +47 -0
- janito/tools/adapters/local/view_file.py +2 -0
- janito/tools/loop_protection.py +115 -0
- janito/tools/loop_protection_decorator.py +110 -0
- janito/tools/outline_file.bak.zip +0 -0
- janito/tools/url_whitelist.py +121 -0
- {janito-2.22.0.dist-info → janito-2.24.0.dist-info}/METADATA +411 -411
- {janito-2.22.0.dist-info → janito-2.24.0.dist-info}/RECORD +52 -39
- {janito-2.22.0.dist-info → janito-2.24.0.dist-info}/entry_points.txt +0 -0
- {janito-2.22.0.dist-info → janito-2.24.0.dist-info}/licenses/LICENSE +0 -0
- {janito-2.22.0.dist-info → janito-2.24.0.dist-info}/top_level.txt +0 -0
- {janito-2.22.0.dist-info → janito-2.24.0.dist-info}/WHEEL +0 -0
janito/cli/core/getters.py
CHANGED
@@ -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
|
-
|
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
|
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):
|
janito/cli/core/setters.py
CHANGED
@@ -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
|
19
|
+
["-u", "--unrestricted"],
|
19
20
|
{
|
20
21
|
"action": "store_true",
|
21
|
-
"help": "
|
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
|
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
|
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
|
|
janito/docs/GETTING_STARTED.md
CHANGED
File without changes
|
janito/drivers/dashscope.bak.zip
CHANGED
File without changes
|
janito/drivers/openai/README.md
CHANGED
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
|
janito/providers/ibm/README.md
CHANGED
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
|
|