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.
@@ -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, discover_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
- print("Available plugins:")
31
- _print_builtin_plugins(builtin_plugins)
32
- _print_external_plugins(available, builtin_plugins)
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("No plugins found in search paths")
35
- print("Search paths:")
36
- print(f" - {os.getcwd()}/plugins")
37
- print(f" - {os.path.expanduser('~')}/.janito/plugins")
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
- manager = PluginManager()
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
- print(f"\n{plugin_name} v{metadata.version if metadata else 'unknown'}:")
67
- _print_resources_by_type(resources)
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("No plugins loaded")
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
- manager = PluginManager()
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
- print("Loaded plugins:")
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
- _print_plugin_details(manager, plugin_name)
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("No plugins loaded")
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(Text(msg, style="on dark_green"))
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
@@ -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 content.
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): Path to the file to create.
21
- content (str): Content to write to the file.
22
- overwrite (bool, optional): Overwrite existing file if True. Default: False. Recommended only after reading the file to be overwritten.
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: Status message indicating the result. Example:
25
- - "✅ Successfully created the file at ..."
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
- Note: Syntax validation is automatically performed after this operation.
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
- Security: This tool includes loop protection to prevent excessive file creation operations.
30
- Maximum 5 calls per 10 seconds for the same file path.
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.30.0
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=K48Ywwj6xz_NikuezzBmYJM1PANmQD-G48sE4NjQhn0,6835
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=lha2XX7AKIGtFattATpJgsEKRSxRULPHXI1vNSQiQcg,3846
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/discovery.py,sha256=cs2wlN-7HxH_w8ZcDpmwYQ2tAKUqxm-zzl4cIRYTO-g,9777
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=8xJw8Qv3T_wwkiGBVVgs9p7pH1ONIAipccEUqY2II8A,2231
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=nZf8iPScO9_nrvmHwXqOcqpLZkLABTh9uLVNddC4PCk,3760
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=bkrFFE41h8pWepqnFpmsJ1gF0oDDd5htBsEwFniSKMQ,16064
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.30.0.dist-info/licenses/LICENSE,sha256=dXV4fOF2ZErugtN8l_Nrj5tsRTYgtjE3cgiya0UfBio,11356
250
- janito-2.30.0.dist-info/METADATA,sha256=j6bJNIIHZWxOkwjq2a_7ChXefB2RRskuGazhxMByAvI,2282
251
- janito-2.30.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
252
- janito-2.30.0.dist-info/entry_points.txt,sha256=wIo5zZxbmu4fC-ZMrsKD0T0vq7IqkOOLYhrqRGypkx4,48
253
- janito-2.30.0.dist-info/top_level.txt,sha256=m0NaVCq0-ivxbazE2-ND0EA9Hmuijj_OGkmCbnBcCig,7
254
- janito-2.30.0.dist-info/RECORD,,
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,,