janito 2.30.0__py3-none-any.whl → 2.31.1__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/cli/cli_commands/list_plugins.py +131 -20
- janito/cli/rich_terminal_reporter.py +1 -1
- janito/plugins/auto_loader.py +91 -0
- janito/plugins/auto_loader_fixed.py +90 -0
- janito/plugins/core_adapter.py +53 -0
- janito/plugins/core_loader.py +120 -0
- janito/plugins/core_loader_fixed.py +125 -0
- janito/plugins/discovery.py +8 -0
- janito/plugins/discovery_core.py +49 -0
- janito/tools/adapters/local/__init__.py +4 -0
- janito/tools/adapters/local/create_file.py +60 -9
- janito/tools/adapters/local/fetch_url.py +35 -6
- janito/tools/adapters/local/show_image.py +70 -0
- janito/tools/adapters/local/show_image_grid.py +75 -0
- janito/tools/function_adapter.py +65 -0
- {janito-2.30.0.dist-info → janito-2.31.1.dist-info}/METADATA +2 -1
- {janito-2.30.0.dist-info → janito-2.31.1.dist-info}/RECORD +21 -12
- {janito-2.30.0.dist-info → janito-2.31.1.dist-info}/WHEEL +0 -0
- {janito-2.30.0.dist-info → janito-2.31.1.dist-info}/entry_points.txt +0 -0
- {janito-2.30.0.dist-info → janito-2.31.1.dist-info}/licenses/LICENSE +0 -0
- {janito-2.30.0.dist-info → janito-2.31.1.dist-info}/top_level.txt +0 -0
@@ -4,10 +4,15 @@ CLI command to list available and loaded plugins.
|
|
4
4
|
|
5
5
|
import argparse
|
6
6
|
from typing import List, Dict, Any
|
7
|
-
from janito.plugins.discovery import list_available_plugins
|
7
|
+
from janito.plugins.discovery import list_available_plugins
|
8
8
|
import os
|
9
9
|
from janito.plugins.manager import PluginManager
|
10
10
|
from janito.plugins.builtin import BuiltinPluginRegistry
|
11
|
+
from janito.plugins.auto_loader_fixed import load_core_plugins, get_loaded_core_plugins, is_core_plugin
|
12
|
+
from rich.console import Console
|
13
|
+
from rich.table import Table
|
14
|
+
from rich.panel import Panel
|
15
|
+
from rich.text import Text
|
11
16
|
|
12
17
|
|
13
18
|
def handle_list_plugins(args: argparse.Namespace) -> None:
|
@@ -22,19 +27,49 @@ def handle_list_plugins(args: argparse.Namespace) -> None:
|
|
22
27
|
|
23
28
|
|
24
29
|
def _list_available_plugins():
|
25
|
-
"""List available plugins."""
|
30
|
+
"""List available plugins using rich formatting."""
|
31
|
+
console = Console()
|
26
32
|
available = list_available_plugins()
|
27
33
|
builtin_plugins = BuiltinPluginRegistry.list_builtin_plugins()
|
28
34
|
|
29
35
|
if available or builtin_plugins:
|
30
|
-
|
31
|
-
|
32
|
-
|
36
|
+
# Create main table
|
37
|
+
table = Table(title="Available Plugins")
|
38
|
+
table.add_column("Plugin Name", style="cyan", no_wrap=True)
|
39
|
+
table.add_column("Type", style="magenta")
|
40
|
+
table.add_column("Status", style="green")
|
41
|
+
|
42
|
+
# Add builtin plugins
|
43
|
+
for plugin in builtin_plugins:
|
44
|
+
table.add_row(plugin, "Builtin", "📦")
|
45
|
+
|
46
|
+
# Add external plugins
|
47
|
+
other_plugins = [p for p in available if p not in builtin_plugins]
|
48
|
+
for plugin in other_plugins:
|
49
|
+
table.add_row(plugin, "External", "🔌")
|
50
|
+
|
51
|
+
console.print(table)
|
52
|
+
|
53
|
+
# Show core plugins
|
54
|
+
from janito.plugins.core_loader_fixed import get_core_plugins
|
55
|
+
core_plugins = get_core_plugins()
|
56
|
+
core_table = Table(title="Core Plugins (Enabled by Default)")
|
57
|
+
core_table.add_column("Plugin Name", style="cyan", no_wrap=True)
|
58
|
+
core_table.add_column("Status", style="yellow")
|
59
|
+
|
60
|
+
for plugin in core_plugins:
|
61
|
+
core_table.add_row(plugin, "✅ Available")
|
62
|
+
|
63
|
+
console.print(core_table)
|
33
64
|
else:
|
34
|
-
print(
|
35
|
-
|
36
|
-
|
37
|
-
|
65
|
+
console.print(Panel(
|
66
|
+
"No plugins found in search paths\n"
|
67
|
+
f"[dim]Search paths:[/dim]\n"
|
68
|
+
f" • {os.getcwd()}/plugins\n"
|
69
|
+
f" • {os.path.expanduser('~')}/.janito/plugins",
|
70
|
+
title="No Plugins Found",
|
71
|
+
style="yellow"
|
72
|
+
))
|
38
73
|
|
39
74
|
|
40
75
|
def _print_builtin_plugins(builtin_plugins):
|
@@ -55,18 +90,51 @@ def _print_external_plugins(available, builtin_plugins):
|
|
55
90
|
|
56
91
|
|
57
92
|
def _list_plugin_resources():
|
58
|
-
"""List all resources from loaded plugins."""
|
59
|
-
|
93
|
+
"""List all resources from loaded plugins using rich formatting."""
|
94
|
+
from janito.plugins.auto_loader_fixed import get_plugin_manager
|
95
|
+
|
96
|
+
console = Console()
|
97
|
+
manager = get_plugin_manager()
|
60
98
|
all_resources = manager.list_all_resources()
|
61
99
|
|
62
100
|
if all_resources:
|
63
|
-
print("Plugin Resources:")
|
64
101
|
for plugin_name, resources in all_resources.items():
|
65
102
|
metadata = manager.get_plugin_metadata(plugin_name)
|
66
|
-
|
67
|
-
|
103
|
+
version = metadata.version if metadata else 'unknown'
|
104
|
+
|
105
|
+
# Create panel for each plugin
|
106
|
+
panel_content = []
|
107
|
+
|
108
|
+
tools = [r for r in resources if r["type"] == "tool"]
|
109
|
+
commands = [r for r in resources if r["type"] == "command"]
|
110
|
+
configs = [r for r in resources if r["type"] == "config"]
|
111
|
+
|
112
|
+
if tools:
|
113
|
+
panel_content.append("[bold blue]Tools:[/bold blue]")
|
114
|
+
for tool in tools:
|
115
|
+
panel_content.append(f" • {tool['name']}: {tool['description']}")
|
116
|
+
|
117
|
+
if commands:
|
118
|
+
panel_content.append("[bold green]Commands:[/bold green]")
|
119
|
+
for cmd in commands:
|
120
|
+
panel_content.append(f" • {cmd['name']}: {cmd['description']}")
|
121
|
+
|
122
|
+
if configs:
|
123
|
+
panel_content.append("[bold yellow]Configuration:[/bold yellow]")
|
124
|
+
for config in configs:
|
125
|
+
panel_content.append(f" • {config['name']}: {config['description']}")
|
126
|
+
|
127
|
+
console.print(Panel(
|
128
|
+
"\n".join(panel_content),
|
129
|
+
title=f"{plugin_name} v{version}",
|
130
|
+
style="cyan"
|
131
|
+
))
|
68
132
|
else:
|
69
|
-
print(
|
133
|
+
console.print(Panel(
|
134
|
+
"No plugins are currently loaded.",
|
135
|
+
title="No Plugin Resources",
|
136
|
+
style="yellow"
|
137
|
+
))
|
70
138
|
|
71
139
|
|
72
140
|
def _print_resources_by_type(resources):
|
@@ -92,16 +160,59 @@ def _print_resources_by_type(resources):
|
|
92
160
|
|
93
161
|
|
94
162
|
def _list_loaded_plugins():
|
95
|
-
"""List loaded plugins."""
|
96
|
-
|
163
|
+
"""List loaded plugins using rich formatting."""
|
164
|
+
from janito.plugins.auto_loader_fixed import get_plugin_manager
|
165
|
+
|
166
|
+
console = Console()
|
167
|
+
manager = get_plugin_manager()
|
97
168
|
loaded = manager.list_plugins()
|
98
169
|
|
99
170
|
if loaded:
|
100
|
-
|
171
|
+
# Create main table
|
172
|
+
table = Table(title="Loaded Plugins")
|
173
|
+
table.add_column("Plugin Name", style="cyan", no_wrap=True)
|
174
|
+
table.add_column("Version", style="magenta")
|
175
|
+
table.add_column("Description", style="green", max_width=50)
|
176
|
+
table.add_column("Type", style="yellow")
|
177
|
+
|
178
|
+
core_plugins = []
|
179
|
+
other_plugins = []
|
180
|
+
|
101
181
|
for plugin_name in loaded:
|
102
|
-
|
182
|
+
if is_core_plugin(plugin_name):
|
183
|
+
core_plugins.append(plugin_name)
|
184
|
+
else:
|
185
|
+
other_plugins.append(plugin_name)
|
186
|
+
|
187
|
+
# Add core plugins
|
188
|
+
for plugin_name in core_plugins:
|
189
|
+
metadata = manager.get_plugin_metadata(plugin_name)
|
190
|
+
if metadata:
|
191
|
+
table.add_row(
|
192
|
+
metadata.name,
|
193
|
+
metadata.version,
|
194
|
+
metadata.description,
|
195
|
+
"🔵 Core"
|
196
|
+
)
|
197
|
+
|
198
|
+
# Add other plugins
|
199
|
+
for plugin_name in other_plugins:
|
200
|
+
metadata = manager.get_plugin_metadata(plugin_name)
|
201
|
+
if metadata:
|
202
|
+
table.add_row(
|
203
|
+
metadata.name,
|
204
|
+
metadata.version,
|
205
|
+
metadata.description,
|
206
|
+
"🔶 External"
|
207
|
+
)
|
208
|
+
|
209
|
+
console.print(table)
|
103
210
|
else:
|
104
|
-
print(
|
211
|
+
console.print(Panel(
|
212
|
+
"No plugins are currently loaded.",
|
213
|
+
title="No Plugins Loaded",
|
214
|
+
style="yellow"
|
215
|
+
))
|
105
216
|
|
106
217
|
|
107
218
|
def _print_plugin_details(manager, plugin_name):
|
@@ -161,7 +161,7 @@ class RichTerminalReporter(EventHandlerBase):
|
|
161
161
|
self.console.print(msg)
|
162
162
|
self.console.file.flush()
|
163
163
|
elif subtype == ReportSubtype.STDOUT:
|
164
|
-
self.console.print(
|
164
|
+
self.console.print(msg)
|
165
165
|
self.console.file.flush()
|
166
166
|
elif subtype == ReportSubtype.STDERR:
|
167
167
|
self.console.print(Text(msg, style="on red"))
|
@@ -0,0 +1,91 @@
|
|
1
|
+
"""
|
2
|
+
Auto-loader for core plugins.
|
3
|
+
|
4
|
+
This module automatically loads core plugins when the plugin system is initialized.
|
5
|
+
"""
|
6
|
+
|
7
|
+
import os
|
8
|
+
from pathlib import Path
|
9
|
+
from typing import List
|
10
|
+
from janito.plugins.manager import PluginManager
|
11
|
+
from janito.plugins.discovery import list_available_plugins
|
12
|
+
|
13
|
+
# List of core plugins that should be enabled by default
|
14
|
+
CORE_PLUGINS = [
|
15
|
+
"core.filemanager",
|
16
|
+
"core.codeanalyzer",
|
17
|
+
"core.system",
|
18
|
+
"core.imagedisplay",
|
19
|
+
"dev.pythondev",
|
20
|
+
"dev.visualization",
|
21
|
+
"ui.userinterface",
|
22
|
+
"web.webtools",
|
23
|
+
]
|
24
|
+
|
25
|
+
|
26
|
+
def load_core_plugins(pm: PluginManager = None) -> List[str]:
|
27
|
+
"""
|
28
|
+
Load all core plugins.
|
29
|
+
|
30
|
+
Args:
|
31
|
+
pm: PluginManager instance. If None, creates a new one.
|
32
|
+
|
33
|
+
Returns:
|
34
|
+
List of successfully loaded plugin names
|
35
|
+
"""
|
36
|
+
if pm is None:
|
37
|
+
pm = PluginManager()
|
38
|
+
|
39
|
+
# Ensure plugins directory is in search path
|
40
|
+
plugins_dir = Path.cwd() / "plugins"
|
41
|
+
if plugins_dir.exists():
|
42
|
+
pm.add_plugin_path(str(plugins_dir))
|
43
|
+
|
44
|
+
loaded = []
|
45
|
+
|
46
|
+
# Load core plugins
|
47
|
+
for plugin_name in CORE_PLUGINS:
|
48
|
+
try:
|
49
|
+
if pm.load_plugin(plugin_name):
|
50
|
+
loaded.append(plugin_name)
|
51
|
+
except Exception as e:
|
52
|
+
print(f"Warning: Failed to load core plugin {plugin_name}: {e}")
|
53
|
+
|
54
|
+
return loaded
|
55
|
+
|
56
|
+
|
57
|
+
def get_loaded_core_plugins() -> List[str]:
|
58
|
+
"""
|
59
|
+
Get list of currently loaded core plugins.
|
60
|
+
|
61
|
+
Returns:
|
62
|
+
List of loaded core plugin names
|
63
|
+
"""
|
64
|
+
pm = PluginManager()
|
65
|
+
loaded = pm.list_plugins()
|
66
|
+
return [p for p in loaded if p in CORE_PLUGINS]
|
67
|
+
|
68
|
+
|
69
|
+
def is_core_plugin(plugin_name: str) -> bool:
|
70
|
+
"""
|
71
|
+
Check if a plugin is a core plugin.
|
72
|
+
|
73
|
+
Args:
|
74
|
+
plugin_name: Name of the plugin to check
|
75
|
+
|
76
|
+
Returns:
|
77
|
+
True if it's a core plugin
|
78
|
+
"""
|
79
|
+
return plugin_name in CORE_PLUGINS
|
80
|
+
|
81
|
+
|
82
|
+
# Auto-load core plugins when module is imported
|
83
|
+
_plugin_manager = None
|
84
|
+
|
85
|
+
def get_plugin_manager() -> PluginManager:
|
86
|
+
"""Get the global plugin manager with core plugins loaded."""
|
87
|
+
global _plugin_manager
|
88
|
+
if _plugin_manager is None:
|
89
|
+
_plugin_manager = PluginManager()
|
90
|
+
load_core_plugins(_plugin_manager)
|
91
|
+
return _plugin_manager
|
@@ -0,0 +1,90 @@
|
|
1
|
+
"""
|
2
|
+
Fixed auto-loader for core plugins.
|
3
|
+
|
4
|
+
This module provides a working implementation to load core plugins
|
5
|
+
without the complex discovery mechanism.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import os
|
9
|
+
from pathlib import Path
|
10
|
+
from typing import List
|
11
|
+
from janito.plugins.manager import PluginManager
|
12
|
+
from janito.plugins.core_loader_fixed import load_core_plugin, get_core_plugins
|
13
|
+
|
14
|
+
# List of core plugins that should be enabled by default
|
15
|
+
CORE_PLUGINS = [
|
16
|
+
"core.filemanager",
|
17
|
+
"core.codeanalyzer",
|
18
|
+
"core.system",
|
19
|
+
"core.imagedisplay",
|
20
|
+
"dev.pythondev",
|
21
|
+
"dev.visualization",
|
22
|
+
"ui.userinterface",
|
23
|
+
"web.webtools",
|
24
|
+
]
|
25
|
+
|
26
|
+
|
27
|
+
def load_core_plugins(pm: PluginManager = None) -> List[str]:
|
28
|
+
"""
|
29
|
+
Load all core plugins.
|
30
|
+
|
31
|
+
Args:
|
32
|
+
pm: PluginManager instance. If None, creates a new one.
|
33
|
+
|
34
|
+
Returns:
|
35
|
+
List of successfully loaded plugin names
|
36
|
+
"""
|
37
|
+
if pm is None:
|
38
|
+
pm = PluginManager()
|
39
|
+
|
40
|
+
loaded = []
|
41
|
+
|
42
|
+
# Load core plugins
|
43
|
+
for plugin_name in CORE_PLUGINS:
|
44
|
+
try:
|
45
|
+
plugin = load_core_plugin(plugin_name)
|
46
|
+
if plugin:
|
47
|
+
# Manually register the plugin
|
48
|
+
pm.plugins[plugin_name] = plugin
|
49
|
+
loaded.append(plugin_name)
|
50
|
+
except Exception as e:
|
51
|
+
print(f"Warning: Failed to load core plugin {plugin_name}: {e}")
|
52
|
+
|
53
|
+
return loaded
|
54
|
+
|
55
|
+
|
56
|
+
def get_loaded_core_plugins() -> List[str]:
|
57
|
+
"""
|
58
|
+
Get list of currently loaded core plugins.
|
59
|
+
|
60
|
+
Returns:
|
61
|
+
List of loaded core plugin names
|
62
|
+
"""
|
63
|
+
pm = PluginManager()
|
64
|
+
loaded = pm.list_plugins()
|
65
|
+
return [p for p in loaded if p in CORE_PLUGINS]
|
66
|
+
|
67
|
+
|
68
|
+
def is_core_plugin(plugin_name: str) -> bool:
|
69
|
+
"""
|
70
|
+
Check if a plugin is a core plugin.
|
71
|
+
|
72
|
+
Args:
|
73
|
+
plugin_name: Name of the plugin to check
|
74
|
+
|
75
|
+
Returns:
|
76
|
+
True if it's a core plugin
|
77
|
+
"""
|
78
|
+
return plugin_name in CORE_PLUGINS
|
79
|
+
|
80
|
+
|
81
|
+
# Auto-load core plugins when module is imported
|
82
|
+
_plugin_manager = None
|
83
|
+
|
84
|
+
def get_plugin_manager() -> PluginManager:
|
85
|
+
"""Get the global plugin manager with core plugins loaded."""
|
86
|
+
global _plugin_manager
|
87
|
+
if _plugin_manager is None:
|
88
|
+
_plugin_manager = PluginManager()
|
89
|
+
load_core_plugins(_plugin_manager)
|
90
|
+
return _plugin_manager
|
@@ -0,0 +1,53 @@
|
|
1
|
+
"""
|
2
|
+
Core plugin adapter for legacy plugin system.
|
3
|
+
|
4
|
+
This module provides proper Plugin class implementations for core plugins
|
5
|
+
that use the function-based approach instead of class-based.
|
6
|
+
"""
|
7
|
+
|
8
|
+
from janito.plugins.base import Plugin, PluginMetadata
|
9
|
+
from typing import List, Type
|
10
|
+
from janito.tools.tool_base import ToolBase
|
11
|
+
from janito.tools.function_adapter import create_function_tool
|
12
|
+
|
13
|
+
|
14
|
+
class CorePluginAdapter(Plugin):
|
15
|
+
"""Adapter for core plugins using function-based tools."""
|
16
|
+
|
17
|
+
def __init__(self, plugin_name: str, description: str, tools_module):
|
18
|
+
super().__init__()
|
19
|
+
self._plugin_name = plugin_name
|
20
|
+
self._description = description
|
21
|
+
self._tools_module = tools_module
|
22
|
+
self._tool_classes = []
|
23
|
+
|
24
|
+
# Set the metadata attribute that Plugin expects
|
25
|
+
self.metadata = self.get_metadata()
|
26
|
+
|
27
|
+
def get_metadata(self) -> PluginMetadata:
|
28
|
+
return PluginMetadata(
|
29
|
+
name=self._plugin_name,
|
30
|
+
version="1.0.0",
|
31
|
+
description=self._description,
|
32
|
+
author="Janito",
|
33
|
+
license="MIT",
|
34
|
+
)
|
35
|
+
|
36
|
+
def get_tools(self) -> List[Type[ToolBase]]:
|
37
|
+
return self._tool_classes
|
38
|
+
|
39
|
+
def initialize(self):
|
40
|
+
"""Initialize the plugin by creating tool classes."""
|
41
|
+
# Get tools from the module
|
42
|
+
tools = getattr(self._tools_module, "__plugin_tools__", [])
|
43
|
+
|
44
|
+
self._tool_classes = []
|
45
|
+
for tool_func in tools:
|
46
|
+
if callable(tool_func):
|
47
|
+
tool_class = create_function_tool(tool_func)
|
48
|
+
self._tool_classes.append(tool_class)
|
49
|
+
|
50
|
+
|
51
|
+
def create_core_plugin(plugin_name: str, description: str, tools_module) -> CorePluginAdapter:
|
52
|
+
"""Create a core plugin adapter."""
|
53
|
+
return CorePluginAdapter(plugin_name, description, tools_module)
|
@@ -0,0 +1,120 @@
|
|
1
|
+
"""
|
2
|
+
Core plugin loader that properly handles function-based plugins.
|
3
|
+
|
4
|
+
This module provides a simplified approach to load core plugins
|
5
|
+
without the complex discovery mechanism.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import importlib.util
|
9
|
+
import sys
|
10
|
+
from pathlib import Path
|
11
|
+
from typing import Optional
|
12
|
+
|
13
|
+
from janito.plugins.base import Plugin, PluginMetadata
|
14
|
+
from janito.tools.function_adapter import create_function_tool
|
15
|
+
from janito.tools.tool_base import ToolBase
|
16
|
+
|
17
|
+
|
18
|
+
class CorePlugin(Plugin):
|
19
|
+
"""Simple core plugin implementation."""
|
20
|
+
|
21
|
+
def __init__(self, name: str, description: str, tools: list):
|
22
|
+
super().__init__()
|
23
|
+
self._plugin_name = name
|
24
|
+
self._description = description
|
25
|
+
self._tools = tools
|
26
|
+
self._tool_classes = []
|
27
|
+
|
28
|
+
def get_metadata(self) -> PluginMetadata:
|
29
|
+
return PluginMetadata(
|
30
|
+
name=self._plugin_name,
|
31
|
+
version="1.0.0",
|
32
|
+
description=self._description,
|
33
|
+
author="Janito",
|
34
|
+
license="MIT",
|
35
|
+
)
|
36
|
+
|
37
|
+
def get_tools(self) -> list:
|
38
|
+
return self._tool_classes
|
39
|
+
|
40
|
+
def initialize(self):
|
41
|
+
"""Initialize by creating tool classes."""
|
42
|
+
self._tool_classes = []
|
43
|
+
for tool_func in self._tools:
|
44
|
+
if callable(tool_func):
|
45
|
+
tool_class = create_function_tool(tool_func)
|
46
|
+
self._tool_classes.append(tool_class)
|
47
|
+
|
48
|
+
|
49
|
+
def load_core_plugin(plugin_name: str) -> Optional[Plugin]:
|
50
|
+
"""
|
51
|
+
Load a core plugin by name.
|
52
|
+
|
53
|
+
Args:
|
54
|
+
plugin_name: Name of the plugin (e.g., 'core.filemanager')
|
55
|
+
|
56
|
+
Returns:
|
57
|
+
Plugin instance if loaded successfully
|
58
|
+
"""
|
59
|
+
try:
|
60
|
+
# Parse plugin name
|
61
|
+
if "." not in plugin_name:
|
62
|
+
return None
|
63
|
+
|
64
|
+
parts = plugin_name.split(".")
|
65
|
+
if len(parts) != 2:
|
66
|
+
return None
|
67
|
+
|
68
|
+
package_name, submodule_name = parts
|
69
|
+
|
70
|
+
# Build path to plugin
|
71
|
+
plugin_path = Path("plugins") / package_name / submodule_name / "__init__.py"
|
72
|
+
if not plugin_path.exists():
|
73
|
+
return None
|
74
|
+
|
75
|
+
# Load the module
|
76
|
+
spec = importlib.util.spec_from_file_location(plugin_name, plugin_path)
|
77
|
+
if spec is None or spec.loader is None:
|
78
|
+
return None
|
79
|
+
|
80
|
+
module = importlib.util.module_from_spec(spec)
|
81
|
+
spec.loader.exec_module(module)
|
82
|
+
|
83
|
+
# Get plugin info
|
84
|
+
name = getattr(module, "__plugin_name__", plugin_name)
|
85
|
+
description = getattr(module, "__plugin_description__", f"Core plugin: {plugin_name}")
|
86
|
+
tools = getattr(module, "__plugin_tools__", [])
|
87
|
+
|
88
|
+
if not tools:
|
89
|
+
return None
|
90
|
+
|
91
|
+
# Create plugin
|
92
|
+
plugin = CorePlugin(name, description, tools)
|
93
|
+
plugin.initialize()
|
94
|
+
return plugin
|
95
|
+
|
96
|
+
except Exception as e:
|
97
|
+
print(f"Error loading core plugin {plugin_name}: {e}")
|
98
|
+
return None
|
99
|
+
|
100
|
+
|
101
|
+
def get_core_plugins() -> list:
|
102
|
+
"""Get list of all available core plugins."""
|
103
|
+
core_plugins = [
|
104
|
+
"core.filemanager",
|
105
|
+
"core.codeanalyzer",
|
106
|
+
"core.system",
|
107
|
+
"core.imagedisplay",
|
108
|
+
"dev.pythondev",
|
109
|
+
"dev.visualization",
|
110
|
+
"ui.userinterface",
|
111
|
+
"web.webtools",
|
112
|
+
]
|
113
|
+
|
114
|
+
available = []
|
115
|
+
for plugin_name in core_plugins:
|
116
|
+
plugin = load_core_plugin(plugin_name)
|
117
|
+
if plugin:
|
118
|
+
available.append(plugin_name)
|
119
|
+
|
120
|
+
return available
|
@@ -0,0 +1,125 @@
|
|
1
|
+
"""
|
2
|
+
Fixed core plugin loader.
|
3
|
+
|
4
|
+
This module provides a working implementation to load core plugins
|
5
|
+
by directly using the Plugin base class properly.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import importlib.util
|
9
|
+
import sys
|
10
|
+
from pathlib import Path
|
11
|
+
from typing import Optional, List, Type
|
12
|
+
|
13
|
+
from janito.plugins.base import Plugin, PluginMetadata
|
14
|
+
from janito.tools.function_adapter import create_function_tool
|
15
|
+
from janito.tools.tool_base import ToolBase
|
16
|
+
|
17
|
+
|
18
|
+
class CorePlugin(Plugin):
|
19
|
+
"""Working core plugin implementation."""
|
20
|
+
|
21
|
+
def __init__(self, name: str, description: str, tools: list):
|
22
|
+
self._plugin_name = name
|
23
|
+
self._description = description
|
24
|
+
self._tools = tools
|
25
|
+
self._tool_classes = []
|
26
|
+
super().__init__() # Call super after setting attributes
|
27
|
+
|
28
|
+
def get_metadata(self) -> PluginMetadata:
|
29
|
+
return PluginMetadata(
|
30
|
+
name=self._plugin_name,
|
31
|
+
version="1.0.0",
|
32
|
+
description=self._description,
|
33
|
+
author="Janito",
|
34
|
+
license="MIT",
|
35
|
+
)
|
36
|
+
|
37
|
+
def get_tools(self) -> List[Type[ToolBase]]:
|
38
|
+
return self._tool_classes
|
39
|
+
|
40
|
+
def initialize(self):
|
41
|
+
"""Initialize by creating tool classes."""
|
42
|
+
self._tool_classes = []
|
43
|
+
for tool_func in self._tools:
|
44
|
+
if callable(tool_func):
|
45
|
+
tool_class = create_function_tool(tool_func)
|
46
|
+
self._tool_classes.append(tool_class)
|
47
|
+
|
48
|
+
|
49
|
+
def load_core_plugin(plugin_name: str) -> Optional[Plugin]:
|
50
|
+
"""
|
51
|
+
Load a core plugin by name.
|
52
|
+
|
53
|
+
Args:
|
54
|
+
plugin_name: Name of the plugin (e.g., 'core.filemanager')
|
55
|
+
|
56
|
+
Returns:
|
57
|
+
Plugin instance if loaded successfully
|
58
|
+
"""
|
59
|
+
try:
|
60
|
+
# Parse plugin name
|
61
|
+
if "." not in plugin_name:
|
62
|
+
return None
|
63
|
+
|
64
|
+
parts = plugin_name.split(".")
|
65
|
+
if len(parts) != 2:
|
66
|
+
return None
|
67
|
+
|
68
|
+
package_name, submodule_name = parts
|
69
|
+
|
70
|
+
# Handle imagedisplay specially
|
71
|
+
if plugin_name == "core.imagedisplay":
|
72
|
+
# Import the actual plugin class
|
73
|
+
try:
|
74
|
+
from plugins.core.imagedisplay.plugin import ImageDisplayPlugin
|
75
|
+
return ImageDisplayPlugin()
|
76
|
+
except ImportError:
|
77
|
+
# If import fails, return None - don't return True
|
78
|
+
return None
|
79
|
+
|
80
|
+
# Build path to plugin
|
81
|
+
plugin_path = Path("plugins") / package_name / submodule_name / "__init__.py"
|
82
|
+
if not plugin_path.exists():
|
83
|
+
return None
|
84
|
+
|
85
|
+
# Load the module
|
86
|
+
spec = importlib.util.spec_from_file_location(plugin_name, plugin_path)
|
87
|
+
if spec is None or spec.loader is None:
|
88
|
+
return None
|
89
|
+
|
90
|
+
module = importlib.util.module_from_spec(spec)
|
91
|
+
spec.loader.exec_module(module)
|
92
|
+
|
93
|
+
# Get plugin info
|
94
|
+
name = getattr(module, "__plugin_name__", plugin_name)
|
95
|
+
description = getattr(module, "__plugin_description__", f"Core plugin: {plugin_name}")
|
96
|
+
tools = getattr(module, "__plugin_tools__", [])
|
97
|
+
|
98
|
+
if not tools:
|
99
|
+
return None
|
100
|
+
|
101
|
+
# Create plugin
|
102
|
+
plugin = CorePlugin(name, description, tools)
|
103
|
+
plugin.initialize()
|
104
|
+
return plugin
|
105
|
+
|
106
|
+
except Exception as e:
|
107
|
+
print(f"Error loading core plugin {plugin_name}: {e}")
|
108
|
+
return None
|
109
|
+
|
110
|
+
|
111
|
+
def get_core_plugins() -> list:
|
112
|
+
"""Get list of all available core plugins."""
|
113
|
+
core_plugins = [
|
114
|
+
"core.filemanager",
|
115
|
+
"core.codeanalyzer",
|
116
|
+
"core.system",
|
117
|
+
"core.imagedisplay",
|
118
|
+
"dev.pythondev",
|
119
|
+
"dev.visualization",
|
120
|
+
"ui.userinterface",
|
121
|
+
"web.webtools",
|
122
|
+
]
|
123
|
+
|
124
|
+
# All core plugins are always available
|
125
|
+
return core_plugins
|
janito/plugins/discovery.py
CHANGED
@@ -33,6 +33,7 @@ import logging
|
|
33
33
|
|
34
34
|
from .base import Plugin
|
35
35
|
from .builtin import load_builtin_plugin, BuiltinPluginRegistry
|
36
|
+
from .core_loader import load_core_plugin
|
36
37
|
|
37
38
|
logger = logging.getLogger(__name__)
|
38
39
|
|
@@ -74,6 +75,13 @@ def discover_plugins(
|
|
74
75
|
parts = plugin_name.split(".")
|
75
76
|
if len(parts) == 2:
|
76
77
|
package_name, submodule_name = parts
|
78
|
+
|
79
|
+
# Handle core plugins with dedicated loader
|
80
|
+
if plugin_name.startswith(("core.", "dev.", "ui.", "web.")):
|
81
|
+
plugin = load_core_plugin(plugin_name)
|
82
|
+
if plugin:
|
83
|
+
return plugin
|
84
|
+
|
77
85
|
for base_path in all_paths:
|
78
86
|
package_path = base_path / package_name / submodule_name / "__init__.py"
|
79
87
|
if package_path.exists():
|
@@ -0,0 +1,49 @@
|
|
1
|
+
"""
|
2
|
+
Core plugin discovery utilities.
|
3
|
+
|
4
|
+
This module provides specialized handling for core plugins that use
|
5
|
+
the function-based approach instead of class-based plugins.
|
6
|
+
"""
|
7
|
+
|
8
|
+
import importlib.util
|
9
|
+
from pathlib import Path
|
10
|
+
from typing import Optional
|
11
|
+
import sys
|
12
|
+
|
13
|
+
from .base import Plugin
|
14
|
+
from .core_adapter import CorePluginAdapter
|
15
|
+
|
16
|
+
|
17
|
+
def _load_core_plugin(package_path: Path, plugin_name: str) -> Optional[Plugin]:
|
18
|
+
"""
|
19
|
+
Load a core plugin from a package directory.
|
20
|
+
|
21
|
+
Args:
|
22
|
+
package_path: Path to the __init__.py file
|
23
|
+
plugin_name: Full plugin name (e.g., core.filemanager)
|
24
|
+
|
25
|
+
Returns:
|
26
|
+
Plugin instance if loaded successfully
|
27
|
+
"""
|
28
|
+
try:
|
29
|
+
# Import the module
|
30
|
+
spec = importlib.util.spec_from_file_location(plugin_name, package_path)
|
31
|
+
if spec is None or spec.loader is None:
|
32
|
+
return None
|
33
|
+
|
34
|
+
module = importlib.util.module_from_spec(spec)
|
35
|
+
spec.loader.exec_module(module)
|
36
|
+
|
37
|
+
# Get plugin metadata
|
38
|
+
plugin_name_attr = getattr(module, "__plugin_name__", plugin_name)
|
39
|
+
description = getattr(module, "__plugin_description__", f"Core plugin: {plugin_name}")
|
40
|
+
|
41
|
+
# Create and return the core plugin adapter
|
42
|
+
plugin = CorePluginAdapter(plugin_name_attr, description, module)
|
43
|
+
plugin.initialize() # Initialize to set up tools
|
44
|
+
return plugin
|
45
|
+
|
46
|
+
except Exception as e:
|
47
|
+
import logging
|
48
|
+
logging.getLogger(__name__).error(f"Failed to load core plugin {plugin_name}: {e}")
|
49
|
+
return None
|
@@ -24,6 +24,8 @@ 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
26
|
from .read_chart import ReadChartTool
|
27
|
+
from .show_image import ShowImageTool
|
28
|
+
from .show_image_grid import ShowImageGridTool
|
27
29
|
|
28
30
|
from janito.tools.tool_base import ToolPermissions
|
29
31
|
import os
|
@@ -63,6 +65,8 @@ for tool_class in [
|
|
63
65
|
SearchTextTool,
|
64
66
|
ValidateFileSyntaxTool,
|
65
67
|
ReadChartTool,
|
68
|
+
ShowImageTool,
|
69
|
+
ShowImageGridTool,
|
66
70
|
]:
|
67
71
|
local_tools_adapter.register_tool(tool_class)
|
68
72
|
|
@@ -14,20 +14,71 @@ from janito.tools.adapters.local.validate_file_syntax.core import validate_file_
|
|
14
14
|
@register_local_tool
|
15
15
|
class CreateFileTool(ToolBase):
|
16
16
|
"""
|
17
|
-
Create a new file with the given
|
17
|
+
Create a new file with specified content at the given path.
|
18
|
+
|
19
|
+
This tool provides comprehensive file creation capabilities with built-in safety features,
|
20
|
+
automatic syntax validation, and detailed feedback. It handles path expansion, directory
|
21
|
+
creation, encoding issues, and provides clear status messages for both success and failure cases.
|
22
|
+
|
23
|
+
Key Features:
|
24
|
+
- Automatic directory creation for nested paths
|
25
|
+
- UTF-8 encoding with error handling for special characters
|
26
|
+
- Built-in syntax validation for common file types (Python, JavaScript, JSON, YAML, etc.)
|
27
|
+
- Loop protection to prevent excessive file creation
|
28
|
+
- Detailed error messages with context
|
29
|
+
- Safe overwrite protection with preview of existing content
|
30
|
+
- Cross-platform path handling (Windows, macOS, Linux)
|
18
31
|
|
19
32
|
Args:
|
20
|
-
path (str):
|
21
|
-
|
22
|
-
|
33
|
+
path (str): Target file path. Supports relative and absolute paths, with automatic
|
34
|
+
expansion of user home directory (~) and environment variables.
|
35
|
+
Examples: "src/main.py", "~/Documents/config.json", "$HOME/.env"
|
36
|
+
content (str): File content to write. Empty string creates empty file.
|
37
|
+
Supports any text content including Unicode characters, newlines,
|
38
|
+
and binary-safe text representation.
|
39
|
+
overwrite (bool, optional): If True, allows overwriting existing files. Default: False.
|
40
|
+
When False, prevents accidental overwrites by checking
|
41
|
+
file existence and showing current content. Always review
|
42
|
+
existing content before enabling overwrite.
|
43
|
+
|
23
44
|
Returns:
|
24
|
-
str:
|
25
|
-
-
|
45
|
+
str: Detailed status message including:
|
46
|
+
- Success confirmation with line count
|
47
|
+
- File path (display-friendly format)
|
48
|
+
- Syntax validation results
|
49
|
+
- Existing content preview (when overwrite blocked)
|
50
|
+
- Error details (when creation fails)
|
51
|
+
|
52
|
+
Raises:
|
53
|
+
No direct exceptions - all errors are caught and returned as user-friendly messages.
|
54
|
+
Common error cases include: permission denied, invalid path format, disk full,
|
55
|
+
or file exists (when overwrite=False).
|
56
|
+
|
57
|
+
Security Features:
|
58
|
+
- Loop protection: Maximum 5 calls per 10 seconds for the same file path
|
59
|
+
- Path traversal prevention: Validates and sanitizes file paths
|
60
|
+
- Permission checking: Respects file system permissions
|
61
|
+
- Atomic writes: Prevents partial file creation on errors
|
62
|
+
|
63
|
+
Examples:
|
64
|
+
Basic file creation:
|
65
|
+
>>> create_file("hello.py", "print('Hello, World!')")
|
66
|
+
✅ Created file 1 lines.
|
67
|
+
✅ Syntax OK
|
68
|
+
|
69
|
+
Creating nested directories:
|
70
|
+
>>> create_file("src/utils/helpers.py", "def helper(): pass")
|
71
|
+
✅ Created file 2 lines.
|
72
|
+
✅ Syntax OK
|
26
73
|
|
27
|
-
|
74
|
+
Overwrite protection:
|
75
|
+
>>> create_file("existing.txt", "new content")
|
76
|
+
❗ Cannot create file: file already exists at 'existing.txt'.
|
77
|
+
--- Current file content ---
|
78
|
+
old content
|
28
79
|
|
29
|
-
|
30
|
-
|
80
|
+
Note: After successful creation, automatic syntax validation is performed based on
|
81
|
+
file extension. Results are appended to the return message for immediate feedback.
|
31
82
|
"""
|
32
83
|
|
33
84
|
permissions = ToolPermissions(write=True)
|
@@ -246,10 +246,35 @@ class FetchUrlTool(ToolBase):
|
|
246
246
|
return content
|
247
247
|
except requests.exceptions.HTTPError as http_err:
|
248
248
|
status_code = http_err.response.status_code if http_err.response else None
|
249
|
+
|
250
|
+
# Map status codes to descriptions
|
251
|
+
status_descriptions = {
|
252
|
+
400: "Bad Request",
|
253
|
+
401: "Unauthorized",
|
254
|
+
403: "Forbidden",
|
255
|
+
404: "Not Found",
|
256
|
+
405: "Method Not Allowed",
|
257
|
+
408: "Request Timeout",
|
258
|
+
409: "Conflict",
|
259
|
+
410: "Gone",
|
260
|
+
413: "Payload Too Large",
|
261
|
+
414: "URI Too Long",
|
262
|
+
415: "Unsupported Media Type",
|
263
|
+
429: "Too Many Requests",
|
264
|
+
500: "Internal Server Error",
|
265
|
+
501: "Not Implemented",
|
266
|
+
502: "Bad Gateway",
|
267
|
+
503: "Service Unavailable",
|
268
|
+
504: "Gateway Timeout",
|
269
|
+
505: "HTTP Version Not Supported"
|
270
|
+
}
|
271
|
+
|
249
272
|
if status_code and 400 <= status_code < 500:
|
273
|
+
description = status_descriptions.get(status_code, "Client Error")
|
250
274
|
error_message = tr(
|
251
|
-
"HTTP {status_code}",
|
275
|
+
"HTTP Error {status_code} {description}",
|
252
276
|
status_code=status_code,
|
277
|
+
description=description,
|
253
278
|
)
|
254
279
|
# Cache 403 and 404 errors
|
255
280
|
if status_code in [403, 404]:
|
@@ -257,23 +282,27 @@ class FetchUrlTool(ToolBase):
|
|
257
282
|
|
258
283
|
self.report_error(
|
259
284
|
tr(
|
260
|
-
"❗ HTTP {status_code}",
|
285
|
+
"❗ HTTP Error {status_code} {description}",
|
261
286
|
status_code=status_code,
|
287
|
+
description=description,
|
262
288
|
),
|
263
289
|
ReportAction.READ,
|
264
290
|
)
|
265
291
|
return error_message
|
266
292
|
else:
|
293
|
+
description = status_descriptions.get(status_code, "Server Error") if status_code else "Error"
|
267
294
|
self.report_error(
|
268
295
|
tr(
|
269
|
-
"❗ HTTP {status_code}",
|
296
|
+
"❗ HTTP Error {status_code} {description}",
|
270
297
|
status_code=status_code or "Error",
|
298
|
+
description=description,
|
271
299
|
),
|
272
300
|
ReportAction.READ,
|
273
301
|
)
|
274
302
|
return tr(
|
275
|
-
"HTTP {status_code}",
|
303
|
+
"HTTP Error {status_code} {description}",
|
276
304
|
status_code=status_code or "Error",
|
305
|
+
description=description,
|
277
306
|
)
|
278
307
|
except Exception as err:
|
279
308
|
self.report_error(
|
@@ -355,7 +384,7 @@ class FetchUrlTool(ToolBase):
|
|
355
384
|
follow_redirects=follow_redirects,
|
356
385
|
)
|
357
386
|
if (
|
358
|
-
html_content.startswith("HTTP ")
|
387
|
+
html_content.startswith("HTTP Error ")
|
359
388
|
or html_content == "Error"
|
360
389
|
or html_content == "Blocked"
|
361
390
|
):
|
@@ -388,7 +417,7 @@ class FetchUrlTool(ToolBase):
|
|
388
417
|
follow_redirects=follow_redirects,
|
389
418
|
)
|
390
419
|
if (
|
391
|
-
html_content.startswith("HTTP ")
|
420
|
+
html_content.startswith("HTTP Error ")
|
392
421
|
or html_content == "Error"
|
393
422
|
or html_content == "Blocked"
|
394
423
|
):
|
@@ -0,0 +1,70 @@
|
|
1
|
+
from janito.tools.tool_base import ToolBase, ToolPermissions
|
2
|
+
from janito.report_events import ReportAction
|
3
|
+
from janito.tools.adapters.local.adapter import register_local_tool
|
4
|
+
from janito.i18n import tr
|
5
|
+
from janito.tools.loop_protection_decorator import protect_against_loops
|
6
|
+
|
7
|
+
|
8
|
+
@register_local_tool
|
9
|
+
class ShowImageTool(ToolBase):
|
10
|
+
"""Display an image inline in the terminal using the rich library.
|
11
|
+
|
12
|
+
Args:
|
13
|
+
path (str): Path to the image file.
|
14
|
+
width (int, optional): Target width in terminal cells. If unset, auto-fit.
|
15
|
+
height (int, optional): Target height in terminal rows. If unset, auto-fit.
|
16
|
+
preserve_aspect (bool, optional): Preserve aspect ratio. Default: True.
|
17
|
+
|
18
|
+
Returns:
|
19
|
+
str: Status message indicating display result or error details.
|
20
|
+
"""
|
21
|
+
|
22
|
+
permissions = ToolPermissions(read=True)
|
23
|
+
tool_name = "show_image"
|
24
|
+
|
25
|
+
@protect_against_loops(max_calls=5, time_window=10.0, key_field="path")
|
26
|
+
def run(
|
27
|
+
self,
|
28
|
+
path: str,
|
29
|
+
width: int | None = None,
|
30
|
+
height: int | None = None,
|
31
|
+
preserve_aspect: bool = True,
|
32
|
+
) -> str:
|
33
|
+
from janito.tools.tool_utils import display_path
|
34
|
+
from janito.tools.path_utils import expand_path
|
35
|
+
import os
|
36
|
+
|
37
|
+
try:
|
38
|
+
from rich.console import Console
|
39
|
+
from rich.image import Image as RichImage
|
40
|
+
except Exception as e:
|
41
|
+
msg = tr("⚠️ Missing dependency: rich ({error})", error=e)
|
42
|
+
self.report_error(msg)
|
43
|
+
return msg
|
44
|
+
|
45
|
+
path = expand_path(path)
|
46
|
+
disp_path = display_path(path)
|
47
|
+
self.report_action(tr("🖼️ Show image '{disp_path}'", disp_path=disp_path), ReportAction.READ)
|
48
|
+
|
49
|
+
if not os.path.exists(path):
|
50
|
+
msg = tr("❗ not found")
|
51
|
+
self.report_warning(msg)
|
52
|
+
return tr("Error: file not found: {path}", path=disp_path)
|
53
|
+
|
54
|
+
try:
|
55
|
+
console = Console()
|
56
|
+
img = RichImage.from_path(path, width=width, height=height, preserve_aspect_ratio=preserve_aspect)
|
57
|
+
console.print(img)
|
58
|
+
self.report_success(tr("✅ Displayed"))
|
59
|
+
details = []
|
60
|
+
if width:
|
61
|
+
details.append(f"width={width}")
|
62
|
+
if height:
|
63
|
+
details.append(f"height={height}")
|
64
|
+
if not preserve_aspect:
|
65
|
+
details.append("preserve_aspect=False")
|
66
|
+
info = ("; ".join(details)) if details else "auto-fit"
|
67
|
+
return tr("Image displayed: {disp_path} ({info})", disp_path=disp_path, info=info)
|
68
|
+
except Exception as e:
|
69
|
+
self.report_error(tr(" ❌ Error: {error}", error=e))
|
70
|
+
return tr("Error displaying image: {error}", error=e)
|
@@ -0,0 +1,75 @@
|
|
1
|
+
from janito.tools.tool_base import ToolBase, ToolPermissions
|
2
|
+
from janito.report_events import ReportAction
|
3
|
+
from janito.tools.adapters.local.adapter import register_local_tool
|
4
|
+
from janito.i18n import tr
|
5
|
+
from janito.tools.loop_protection_decorator import protect_against_loops
|
6
|
+
from typing import Sequence
|
7
|
+
|
8
|
+
|
9
|
+
@register_local_tool
|
10
|
+
class ShowImageGridTool(ToolBase):
|
11
|
+
"""Display multiple images in a grid inline in the terminal using rich.
|
12
|
+
|
13
|
+
Args:
|
14
|
+
paths (list[str]): List of image file paths.
|
15
|
+
columns (int, optional): Number of columns in the grid. Default: 2.
|
16
|
+
width (int, optional): Max width for each image cell. Default: None (auto).
|
17
|
+
height (int, optional): Max height for each image cell. Default: None (auto).
|
18
|
+
preserve_aspect (bool, optional): Preserve aspect ratio. Default: True.
|
19
|
+
|
20
|
+
Returns:
|
21
|
+
str: Status string summarizing the grid display.
|
22
|
+
"""
|
23
|
+
|
24
|
+
permissions = ToolPermissions(read=True)
|
25
|
+
tool_name = "show_image_grid"
|
26
|
+
|
27
|
+
@protect_against_loops(max_calls=5, time_window=10.0, key_field="paths")
|
28
|
+
def run(
|
29
|
+
self,
|
30
|
+
paths: Sequence[str],
|
31
|
+
columns: int = 2,
|
32
|
+
width: int | None = None,
|
33
|
+
height: int | None = None,
|
34
|
+
preserve_aspect: bool = True,
|
35
|
+
) -> str:
|
36
|
+
from janito.tools.path_utils import expand_path
|
37
|
+
from janito.tools.tool_utils import display_path
|
38
|
+
import os
|
39
|
+
|
40
|
+
try:
|
41
|
+
from rich.console import Console
|
42
|
+
from rich.columns import Columns
|
43
|
+
from rich.image import Image as RichImage
|
44
|
+
from rich.panel import Panel
|
45
|
+
except Exception as e:
|
46
|
+
msg = tr("⚠️ Missing dependency: rich ({error})", error=e)
|
47
|
+
self.report_error(msg)
|
48
|
+
return msg
|
49
|
+
|
50
|
+
if not paths:
|
51
|
+
return tr("No images provided")
|
52
|
+
|
53
|
+
self.report_action(tr("🖼️ Show image grid ({n} images)", n=len(paths)), ReportAction.READ)
|
54
|
+
|
55
|
+
console = Console()
|
56
|
+
images = []
|
57
|
+
shown = 0
|
58
|
+
for p in paths:
|
59
|
+
fp = expand_path(p)
|
60
|
+
if not os.path.exists(fp):
|
61
|
+
self.report_warning(tr("❗ not found: {p}", p=display_path(fp)))
|
62
|
+
continue
|
63
|
+
try:
|
64
|
+
img = RichImage.from_path(fp, width=width, height=height, preserve_aspect_ratio=preserve_aspect)
|
65
|
+
images.append(Panel.fit(img, title=display_path(fp), border_style="dim"))
|
66
|
+
shown += 1
|
67
|
+
except Exception as e:
|
68
|
+
self.report_warning(tr("⚠️ Skipped {p}: {e}", p=display_path(fp), e=e))
|
69
|
+
|
70
|
+
if not images:
|
71
|
+
return tr("No images could be displayed")
|
72
|
+
|
73
|
+
console.print(Columns(images, equal=True, expand=True, columns=columns))
|
74
|
+
self.report_success(tr("✅ Displayed {n} images", n=shown))
|
75
|
+
return tr("Displayed {shown}/{total} images in a {cols}x? grid", shown=shown, total=len(paths), cols=columns)
|
@@ -0,0 +1,65 @@
|
|
1
|
+
"""
|
2
|
+
Function-to-Tool adapter for core plugins.
|
3
|
+
|
4
|
+
This module provides a way to wrap function-based tools into proper ToolBase classes.
|
5
|
+
"""
|
6
|
+
|
7
|
+
import inspect
|
8
|
+
from typing import Any, Dict, List, Optional, get_type_hints
|
9
|
+
from janito.tools.tool_base import ToolBase, ToolPermissions
|
10
|
+
|
11
|
+
|
12
|
+
class FunctionToolAdapter(ToolBase):
|
13
|
+
"""Adapter that wraps a function into a ToolBase class."""
|
14
|
+
|
15
|
+
def __init__(self, func, tool_name: str = None, description: str = None):
|
16
|
+
super().__init__()
|
17
|
+
self._func = func
|
18
|
+
self.tool_name = tool_name or func.__name__
|
19
|
+
self._description = description or func.__doc__ or f"Tool: {self.tool_name}"
|
20
|
+
self.permissions = ToolPermissions(read=True, write=True, execute=True)
|
21
|
+
|
22
|
+
def run(self, **kwargs) -> Any:
|
23
|
+
"""Execute the wrapped function."""
|
24
|
+
return self._func(**kwargs)
|
25
|
+
|
26
|
+
def get_signature(self) -> Dict[str, Any]:
|
27
|
+
"""Get function signature for documentation."""
|
28
|
+
sig = inspect.signature(self._func)
|
29
|
+
type_hints = get_type_hints(self._func)
|
30
|
+
|
31
|
+
params = {}
|
32
|
+
for name, param in sig.parameters.items():
|
33
|
+
param_info = {
|
34
|
+
"type": str(type_hints.get(name, Any)),
|
35
|
+
"default": param.default if param.default != inspect.Parameter.empty else None,
|
36
|
+
"required": param.default == inspect.Parameter.empty,
|
37
|
+
}
|
38
|
+
params[name] = param_info
|
39
|
+
|
40
|
+
return {
|
41
|
+
"name": self.tool_name,
|
42
|
+
"description": self._description,
|
43
|
+
"parameters": params,
|
44
|
+
"return_type": str(type_hints.get("return", Any))
|
45
|
+
}
|
46
|
+
|
47
|
+
|
48
|
+
def create_function_tool(func, tool_name: str = None, description: str = None) -> type:
|
49
|
+
"""
|
50
|
+
Create a ToolBase class from a function.
|
51
|
+
|
52
|
+
Args:
|
53
|
+
func: The function to wrap
|
54
|
+
tool_name: Optional custom tool name
|
55
|
+
description: Optional custom description
|
56
|
+
|
57
|
+
Returns:
|
58
|
+
A ToolBase subclass that wraps the function
|
59
|
+
"""
|
60
|
+
|
61
|
+
class DynamicFunctionTool(FunctionToolAdapter):
|
62
|
+
def __init__(self):
|
63
|
+
super().__init__(func, tool_name, description)
|
64
|
+
|
65
|
+
return DynamicFunctionTool
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: janito
|
3
|
-
Version: 2.
|
3
|
+
Version: 2.31.1
|
4
4
|
Summary: A new Python package called janito.
|
5
5
|
Author-email: João Pinto <janito@ikignosis.org>
|
6
6
|
Project-URL: Homepage, https://github.com/ikignosis/janito
|
@@ -19,6 +19,7 @@ Requires-Dist: requests>=2.32.4
|
|
19
19
|
Requires-Dist: bs4>=0.0.2
|
20
20
|
Requires-Dist: questionary>=2.0.1
|
21
21
|
Requires-Dist: openai>=1.68.0
|
22
|
+
Requires-Dist: Pillow>=10.0.0
|
22
23
|
Provides-Extra: dev
|
23
24
|
Requires-Dist: pytest; extra == "dev"
|
24
25
|
Requires-Dist: pre-commit; extra == "dev"
|
@@ -33,7 +33,7 @@ janito/cli/main_cli.py,sha256=FXpoybaEbDRlK5-oMOMlcdM7TpVfnOdr6-mOkgJHeTo,16648
|
|
33
33
|
janito/cli/prompt_core.py,sha256=F68J4Xl6jZMYFN4oBBYZFj15Jp-HTYoLub4bw2XpNRU,11648
|
34
34
|
janito/cli/prompt_handler.py,sha256=SnPTlL64noeAMGlI08VBDD5IDD8jlVMIYA4-fS8zVLg,215
|
35
35
|
janito/cli/prompt_setup.py,sha256=s48gvNfZhKjsEhf4EzL1tKIGm4wDidPMDvlM6TAPYes,2116
|
36
|
-
janito/cli/rich_terminal_reporter.py,sha256=
|
36
|
+
janito/cli/rich_terminal_reporter.py,sha256=Hitf5U13gncad4GPVAcDMfdSwlfzQzOn9KdeX4TjTWU,6806
|
37
37
|
janito/cli/utils.py,sha256=plCQiDKIf3V8mFhhX5H9-MF2W86i-xRdWf8Xi117Z0w,677
|
38
38
|
janito/cli/verbose_output.py,sha256=wY_B4of5e8Vv7w1fRwOZzNGU2JqbMdcFnGjtEr4hLus,7686
|
39
39
|
janito/cli/chat_mode/bindings.py,sha256=odjc5_-YW1t2FRhBUNRNoBMoQIg5sMz3ktV7xG0ADFU,975
|
@@ -81,7 +81,7 @@ janito/cli/cli_commands/enable_disable_plugin.py,sha256=IIEg5Gz2aAW_7BKrMQTXSGF0
|
|
81
81
|
janito/cli/cli_commands/list_config.py,sha256=oiQEGaGPjwjG-PrOcakpNMbbqISTsBEs7rkGH3ceQsI,1179
|
82
82
|
janito/cli/cli_commands/list_drivers.py,sha256=r2ENykUcvf_9XYp6LHd3RvLXGXyVUA6oe_Pr0dyv92I,5124
|
83
83
|
janito/cli/cli_commands/list_models.py,sha256=QF3Wa7OhNcJFKeBxaw0C_rDfsvJFNb-siz5uorajBvo,1595
|
84
|
-
janito/cli/cli_commands/list_plugins.py,sha256=
|
84
|
+
janito/cli/cli_commands/list_plugins.py,sha256=Lv-M6EAFiqnj0tl9Bq23s162t--81AIs2EqZxhi81lQ,7996
|
85
85
|
janito/cli/cli_commands/list_profiles.py,sha256=O4k6U9iCEeNH3lM-NP_XX_E9W0h__hheLSn23241dkA,3538
|
86
86
|
janito/cli/cli_commands/list_providers.py,sha256=oilrBjNL5mot1nz45XQQY6oeiSxoNvphhQYspNcEJpw,391
|
87
87
|
janito/cli/cli_commands/list_providers_region.py,sha256=qrMj_gtgEMty8UH0P_O5SgWCVJ9ZKxGUp_GdsE4_EH4,2548
|
@@ -132,10 +132,16 @@ janito/llm/message_parts.py,sha256=QY_0kDjaxdoErDgKPRPv1dNkkYJuXIBmHWNLiOEKAH4,1
|
|
132
132
|
janito/llm/model.py,sha256=EioBkdgn8hJ0iQaKN-0KbXlsrk3YKmwR9IbvoEbdVTE,1159
|
133
133
|
janito/llm/provider.py,sha256=3FbhQPrWBSEoIdIi-5DWIh0DD_CM570EFf1NcuGyGko,7961
|
134
134
|
janito/plugins/__init__.py,sha256=6tTpUDNJJCRqlGK6RBd40wP_xEowuZZh0FRGeQ5lQQA,400
|
135
|
+
janito/plugins/auto_loader.py,sha256=8DRpPZtAUtP-et4Lzaam93MH4s_sqQXXKI8Ly5-GQf8,2329
|
136
|
+
janito/plugins/auto_loader_fixed.py,sha256=11pL7J0bX3XOOevQa10_oUtNbH-CXkpKhxOl69Bmzro,2335
|
135
137
|
janito/plugins/base.py,sha256=4sQdTOogtDgmLWgA3q1Z8h8s-JySRkk9nSAmg0mHoNM,4285
|
136
138
|
janito/plugins/builtin.py,sha256=rAdOz5cekFfsTfXJd5-2AXW4Nh81JoBMONqj1skiTR8,2985
|
137
139
|
janito/plugins/config.py,sha256=ex5f1XsKPEgIO4DgJCohPzMKAQoUdx1OdSVU0FlSr5o,2331
|
138
|
-
janito/plugins/
|
140
|
+
janito/plugins/core_adapter.py,sha256=zOhRMLIsqPb5r-8-pJGucEiWEB1_M5btW6TmsAm-mdE,1877
|
141
|
+
janito/plugins/core_loader.py,sha256=VcIwuKUaR2bvRZIFEiUHaS8YulTOKrYtvO_VhKHRpGI,3576
|
142
|
+
janito/plugins/core_loader_fixed.py,sha256=ZjBOU-aQCMnRXxQfFSI5caUR_IqaI0HdqOJSt-G-Lkc,3909
|
143
|
+
janito/plugins/discovery.py,sha256=JKaoROD4Ci-k8HN1hns130mXq28ZZhQsWfTBSJoOqYc,10106
|
144
|
+
janito/plugins/discovery_core.py,sha256=O5OccNWNyrSXGeeTk5xSG0qbsjNsajclNNCiHljFRpI,1613
|
139
145
|
janito/plugins/manager.py,sha256=OyqGiZubcPgogs2u_rIV2cs_r4HNPwFZH9U25Z9g5cI,8135
|
140
146
|
janito/providers/__init__.py,sha256=EvOFeiqucAY9tgCosJ81p0QA6wQwMT1cR3EeIGrhSQQ,528
|
141
147
|
janito/providers/dashscope.bak.zip,sha256=BwXxRmZreEivvRtmqbr5BR62IFVlNjAf4y6DrF2BVJo,5998
|
@@ -183,6 +189,7 @@ janito/tools/README.md,sha256=5HkLpF5k4PENJER7SlDPRXj0yo9mpHvAHW4uuzhq4ak,115
|
|
183
189
|
janito/tools/__init__.py,sha256=W1B39PztC2UF7PS2WyLH6el32MFOETMlN1-LurOROCg,1171
|
184
190
|
janito/tools/base.py,sha256=R38A9xWYh3JRYZMDSom2d1taNDy9J7HpLbZo9X2wH_o,316
|
185
191
|
janito/tools/disabled_tools.py,sha256=Tx__16wtMWZ9z34cYLdH1gukwot5MCL-9kLjd5MPX6Y,2110
|
192
|
+
janito/tools/function_adapter.py,sha256=A_-5pA5Y3v0TAYMA-vq3A-Cawg75kH5XWcUEkNHSOoI,2267
|
186
193
|
janito/tools/inspect_registry.py,sha256=Jo7PrMPRKLuR-j_mBAk9PBcTzeJf1eQrS1ChGofgQk0,538
|
187
194
|
janito/tools/loop_protection.py,sha256=WQ2Cqt459vXvrO0T1EqkEHynHlRkPzfaC83RSmXzjkM,4718
|
188
195
|
janito/tools/loop_protection_decorator.py,sha256=R1j2ouscKbVcDm2wlxRZ6zQuKExgj633ijeDq4j0oO0,6457
|
@@ -200,14 +207,14 @@ janito/tools/tools_adapter.py,sha256=F1Wkji222dY53HMaZWf3vqVas1Bimm3UXERKvxF54Ew
|
|
200
207
|
janito/tools/tools_schema.py,sha256=rGrKrmpPNR07VXHAJ_haGBRRO-YGLOF51BlYRep9AAQ,4415
|
201
208
|
janito/tools/url_whitelist.py,sha256=0CPLkHTp5HgnwgjxwgXnJmwPeZQ30q4j3YjW59hiUUE,4295
|
202
209
|
janito/tools/adapters/__init__.py,sha256=XKixOKtUJs1R-rGwGDXSLVLg5-Kp090gvWbsseWT4LI,92
|
203
|
-
janito/tools/adapters/local/__init__.py,sha256=
|
210
|
+
janito/tools/adapters/local/__init__.py,sha256=I6LtLnDTyApZrSlKcihSQqZv3gADVX781Mcj46LH7ac,2362
|
204
211
|
janito/tools/adapters/local/adapter.py,sha256=u4nLHTaYdwZXMi1J8lsKvlG6rOmdq9xjey_3zeyCG4k,8707
|
205
212
|
janito/tools/adapters/local/ask_user.py,sha256=-shjMRKrRe7HNHM2w_6YAl7eEgl8QXaIV6LKrUDEBxU,4060
|
206
213
|
janito/tools/adapters/local/copy_file.py,sha256=SBJm19Ipe5dqRE1Mxl6JSrn4bNmfObVnDr5b1mcEu6c,3682
|
207
214
|
janito/tools/adapters/local/create_directory.py,sha256=LxwqQEsnOrEphCIoaMRRx9P9bu0MzidP3Fc5q6letxc,2584
|
208
|
-
janito/tools/adapters/local/create_file.py,sha256=
|
215
|
+
janito/tools/adapters/local/create_file.py,sha256=fLTVnMpDnHzzIVK3nS0DtawBT-I18Is-qa0Hg8y6TXY,6385
|
209
216
|
janito/tools/adapters/local/delete_text_in_file.py,sha256=uEeedRxXAR7_CqUc_qhbEdM0OzRi_pgnP-iDjs2Zvjk,5087
|
210
|
-
janito/tools/adapters/local/fetch_url.py,sha256=
|
217
|
+
janito/tools/adapters/local/fetch_url.py,sha256=y_KL85y01VxOSPBBr9kQqJiPH3QNFnd248kVYfVKL0g,17401
|
211
218
|
janito/tools/adapters/local/find_files.py,sha256=Zbag3aP34vc7ffJh8bOqAwXj3KiZhV--uzTVHtNb-fI,6250
|
212
219
|
janito/tools/adapters/local/move_file.py,sha256=LMGm8bn3NNyIPJG4vrlO09smXQcgzA09EwoooZxkIA8,4695
|
213
220
|
janito/tools/adapters/local/open_html_in_browser.py,sha256=XqICIwVx5vEE77gHkaNAC-bAeEEy0DBmDksATiL-sRY,2101
|
@@ -222,6 +229,8 @@ janito/tools/adapters/local/remove_file.py,sha256=Imra4jGkBfAd6pnUAmbUsjN0exj2vz
|
|
222
229
|
janito/tools/adapters/local/replace_text_in_file.py,sha256=zJIDecviF2YRpWxbvhtka4Iaje-QYhcaqQX1PxWolzE,10966
|
223
230
|
janito/tools/adapters/local/run_bash_command.py,sha256=7fABqAeAu7WJwzzwHmT54_m5OSwPMcgpQ74lQhPG7TA,7955
|
224
231
|
janito/tools/adapters/local/run_powershell_command.py,sha256=uQSJVQe40wSGbesyvZxDmIKJthAbDJFaxXm1dEN3gBs,9313
|
232
|
+
janito/tools/adapters/local/show_image.py,sha256=CsWUyLuIrKnTmzG7hw79If40U3ka_VP_jalC2Nu5VPw,2785
|
233
|
+
janito/tools/adapters/local/show_image_grid.py,sha256=uPtPaVnyJDjbouGmMsv9AUAsD0NQ9SL4MVnDuyrUOwo,2999
|
225
234
|
janito/tools/adapters/local/view_file.py,sha256=cBKcbwbfH-UMyvQ7PmYTgsshcFmorjWtyH1kaYi7oNY,7379
|
226
235
|
janito/tools/adapters/local/get_file_outline/__init__.py,sha256=OKV_BHnoD9h-vkcVoW6AHmsuDjjauHPCKNK0nVFl4sU,37
|
227
236
|
janito/tools/adapters/local/get_file_outline/core.py,sha256=25k6a8PaDYxAfxnEAvZvOWg2BgUcgKU_BkzJmZKUMEA,4611
|
@@ -246,9 +255,9 @@ janito/tools/adapters/local/validate_file_syntax/ps1_validator.py,sha256=TeIkPt0
|
|
246
255
|
janito/tools/adapters/local/validate_file_syntax/python_validator.py,sha256=BfCO_K18qy92m-2ZVvHsbEU5e11OPo1pO9Vz4G4616E,130
|
247
256
|
janito/tools/adapters/local/validate_file_syntax/xml_validator.py,sha256=AijlsP_PgNuC8ZbGsC5vOTt3Jur76otQzkd_7qR0QFY,284
|
248
257
|
janito/tools/adapters/local/validate_file_syntax/yaml_validator.py,sha256=TgyI0HRL6ug_gBcWEm5TGJJuA4E34ZXcIzMpAbv3oJs,155
|
249
|
-
janito-2.
|
250
|
-
janito-2.
|
251
|
-
janito-2.
|
252
|
-
janito-2.
|
253
|
-
janito-2.
|
254
|
-
janito-2.
|
258
|
+
janito-2.31.1.dist-info/licenses/LICENSE,sha256=dXV4fOF2ZErugtN8l_Nrj5tsRTYgtjE3cgiya0UfBio,11356
|
259
|
+
janito-2.31.1.dist-info/METADATA,sha256=BycR-X5ENC7IszTvyQZnUOdwDPGeaqKx1-znJ6F__Hk,2313
|
260
|
+
janito-2.31.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
261
|
+
janito-2.31.1.dist-info/entry_points.txt,sha256=wIo5zZxbmu4fC-ZMrsKD0T0vq7IqkOOLYhrqRGypkx4,48
|
262
|
+
janito-2.31.1.dist-info/top_level.txt,sha256=m0NaVCq0-ivxbazE2-ND0EA9Hmuijj_OGkmCbnBcCig,7
|
263
|
+
janito-2.31.1.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|