janito 2.24.1__py3-none-any.whl → 2.25.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- janito/cli/chat_mode/session.py +2 -2
- janito/cli/cli_commands/list_plugins.py +32 -0
- janito/cli/core/getters.py +2 -0
- janito/cli/main_cli.py +6 -2
- janito/cli/single_shot_mode/handler.py +2 -2
- janito/config_manager.py +8 -0
- janito/exceptions.py +19 -1
- janito/plugins/base.py +53 -2
- janito/plugins/config.py +87 -0
- janito/plugins/discovery.py +32 -0
- janito/plugins/manager.py +56 -2
- janito/tools/adapters/local/adapter.py +8 -26
- janito/tools/adapters/local/ask_user.py +1 -1
- janito/tools/adapters/local/copy_file.py +3 -1
- janito/tools/adapters/local/create_directory.py +2 -2
- janito/tools/adapters/local/create_file.py +8 -4
- janito/tools/adapters/local/fetch_url.py +25 -22
- janito/tools/adapters/local/find_files.py +3 -2
- janito/tools/adapters/local/get_file_outline/core.py +3 -1
- janito/tools/adapters/local/get_file_outline/search_outline.py +1 -1
- janito/tools/adapters/local/move_file.py +3 -2
- janito/tools/adapters/local/open_html_in_browser.py +1 -1
- janito/tools/adapters/local/open_url.py +1 -1
- janito/tools/adapters/local/python_file_run.py +2 -0
- janito/tools/adapters/local/read_chart.py +61 -54
- janito/tools/adapters/local/read_files.py +4 -3
- janito/tools/adapters/local/remove_directory.py +2 -0
- janito/tools/adapters/local/remove_file.py +3 -3
- janito/tools/adapters/local/run_powershell_command.py +1 -0
- janito/tools/adapters/local/search_text/core.py +3 -2
- janito/tools/adapters/local/validate_file_syntax/core.py +3 -1
- janito/tools/adapters/local/view_file.py +3 -1
- janito/tools/loop_protection_decorator.py +64 -25
- janito/tools/path_utils.py +39 -0
- janito/tools/tools_adapter.py +68 -22
- {janito-2.24.1.dist-info → janito-2.25.0.dist-info}/METADATA +1 -1
- {janito-2.24.1.dist-info → janito-2.25.0.dist-info}/RECORD +46 -39
- janito-2.25.0.dist-info/top_level.txt +2 -0
- janito-coder/janito_coder/__init__.py +9 -0
- janito-coder/janito_coder/plugins/__init__.py +27 -0
- janito-coder/janito_coder/plugins/code_navigator.py +618 -0
- janito-coder/janito_coder/plugins/git_analyzer.py +273 -0
- janito-coder/pyproject.toml +347 -0
- janito-2.24.1.dist-info/top_level.txt +0 -1
- {janito-2.24.1.dist-info → janito-2.25.0.dist-info}/WHEEL +0 -0
- {janito-2.24.1.dist-info → janito-2.25.0.dist-info}/entry_points.txt +0 -0
- {janito-2.24.1.dist-info → janito-2.25.0.dist-info}/licenses/LICENSE +0 -0
@@ -43,7 +43,7 @@ class FetchUrlTool(ToolBase):
|
|
43
43
|
max_lines (int, optional): Maximum number of lines to return. Defaults to 200.
|
44
44
|
context_chars (int, optional): Characters of context around search matches. Defaults to 400.
|
45
45
|
timeout (int, optional): Timeout in seconds for the HTTP request. Defaults to 10.
|
46
|
-
save_to_file (str, optional): File path to save the full resource content. If provided,
|
46
|
+
save_to_file (str, optional): File path to save the full resource content. If provided,
|
47
47
|
the complete response will be saved to this file instead of being processed.
|
48
48
|
Returns:
|
49
49
|
str: Extracted text content from the web page, or a warning message. Example:
|
@@ -60,14 +60,16 @@ class FetchUrlTool(ToolBase):
|
|
60
60
|
self.cache_dir = Path.home() / ".janito" / "cache" / "fetch_url"
|
61
61
|
self.cache_dir.mkdir(parents=True, exist_ok=True)
|
62
62
|
self.cache_file = self.cache_dir / "error_cache.json"
|
63
|
-
self.session_cache =
|
63
|
+
self.session_cache = (
|
64
|
+
{}
|
65
|
+
) # In-memory session cache - lifetime matches tool instance
|
64
66
|
self._load_cache()
|
65
67
|
|
66
68
|
def _load_cache(self):
|
67
69
|
"""Load error cache from disk."""
|
68
70
|
if self.cache_file.exists():
|
69
71
|
try:
|
70
|
-
with open(self.cache_file,
|
72
|
+
with open(self.cache_file, "r", encoding="utf-8") as f:
|
71
73
|
self.error_cache = json.load(f)
|
72
74
|
except (json.JSONDecodeError, IOError):
|
73
75
|
self.error_cache = {}
|
@@ -77,7 +79,7 @@ class FetchUrlTool(ToolBase):
|
|
77
79
|
def _save_cache(self):
|
78
80
|
"""Save error cache to disk."""
|
79
81
|
try:
|
80
|
-
with open(self.cache_file,
|
82
|
+
with open(self.cache_file, "w", encoding="utf-8") as f:
|
81
83
|
json.dump(self.error_cache, f, indent=2)
|
82
84
|
except IOError:
|
83
85
|
pass # Silently fail if we can't write cache
|
@@ -89,51 +91,52 @@ class FetchUrlTool(ToolBase):
|
|
89
91
|
"""
|
90
92
|
if url not in self.error_cache:
|
91
93
|
return None, False
|
92
|
-
|
94
|
+
|
93
95
|
entry = self.error_cache[url]
|
94
96
|
current_time = time.time()
|
95
|
-
|
97
|
+
|
96
98
|
# Different expiration times for different status codes
|
97
|
-
if entry[
|
99
|
+
if entry["status_code"] == 403:
|
98
100
|
# Cache 403 errors for 24 hours (more permanent)
|
99
101
|
expiration_time = 24 * 3600
|
100
|
-
elif entry[
|
102
|
+
elif entry["status_code"] == 404:
|
101
103
|
# Cache 404 errors for 1 hour (more temporary)
|
102
104
|
expiration_time = 3600
|
103
105
|
else:
|
104
106
|
# Cache other 4xx errors for 30 minutes
|
105
107
|
expiration_time = 1800
|
106
|
-
|
107
|
-
if current_time - entry[
|
108
|
+
|
109
|
+
if current_time - entry["timestamp"] > expiration_time:
|
108
110
|
# Cache expired, remove it
|
109
111
|
del self.error_cache[url]
|
110
112
|
self._save_cache()
|
111
113
|
return None, False
|
112
|
-
|
113
|
-
return entry[
|
114
|
+
|
115
|
+
return entry["message"], True
|
114
116
|
|
115
117
|
def _cache_error(self, url: str, status_code: int, message: str):
|
116
118
|
"""Cache an HTTP error response."""
|
117
119
|
self.error_cache[url] = {
|
118
|
-
|
119
|
-
|
120
|
-
|
120
|
+
"status_code": status_code,
|
121
|
+
"message": message,
|
122
|
+
"timestamp": time.time(),
|
121
123
|
}
|
122
124
|
self._save_cache()
|
123
125
|
|
124
126
|
def _fetch_url_content(self, url: str, timeout: int = 10) -> str:
|
125
127
|
"""Fetch URL content and handle HTTP errors.
|
126
|
-
|
128
|
+
|
127
129
|
Implements two-tier caching:
|
128
130
|
1. Session cache: In-memory cache for successful responses (lifetime = tool instance)
|
129
131
|
2. Error cache: Persistent disk cache for HTTP errors with different expiration times
|
130
|
-
|
132
|
+
|
131
133
|
Also implements URL whitelist checking.
|
132
134
|
"""
|
133
135
|
# Check URL whitelist
|
134
136
|
from janito.tools.url_whitelist import get_url_whitelist_manager
|
137
|
+
|
135
138
|
whitelist_manager = get_url_whitelist_manager()
|
136
|
-
|
139
|
+
|
137
140
|
if not whitelist_manager.is_url_allowed(url):
|
138
141
|
error_message = tr(
|
139
142
|
"Warning: URL blocked by whitelist: {url}",
|
@@ -186,7 +189,7 @@ class FetchUrlTool(ToolBase):
|
|
186
189
|
# Cache 403 and 404 errors
|
187
190
|
if status_code in [403, 404]:
|
188
191
|
self._cache_error(url, status_code, error_message)
|
189
|
-
|
192
|
+
|
190
193
|
self.report_error(
|
191
194
|
tr(
|
192
195
|
"❗ HTTP {status_code} error for URL: {url}",
|
@@ -262,7 +265,7 @@ class FetchUrlTool(ToolBase):
|
|
262
265
|
|
263
266
|
return text
|
264
267
|
|
265
|
-
@protect_against_loops(max_calls=5, time_window=10.0)
|
268
|
+
@protect_against_loops(max_calls=5, time_window=10.0, key_field="url")
|
266
269
|
def run(
|
267
270
|
self,
|
268
271
|
url: str,
|
@@ -284,9 +287,9 @@ class FetchUrlTool(ToolBase):
|
|
284
287
|
html_content = self._fetch_url_content(url, timeout=timeout)
|
285
288
|
if html_content.startswith("Warning:"):
|
286
289
|
return html_content
|
287
|
-
|
290
|
+
|
288
291
|
try:
|
289
|
-
with open(save_to_file,
|
292
|
+
with open(save_to_file, "w", encoding="utf-8") as f:
|
290
293
|
f.write(html_content)
|
291
294
|
file_size = len(html_content)
|
292
295
|
self.report_success(
|
@@ -6,6 +6,7 @@ from janito.dir_walk_utils import walk_dir_with_gitignore
|
|
6
6
|
from janito.i18n import tr
|
7
7
|
import fnmatch
|
8
8
|
import os
|
9
|
+
from janito.tools.path_utils import expand_path
|
9
10
|
from janito.tools.loop_protection_decorator import protect_against_loops
|
10
11
|
|
11
12
|
|
@@ -108,7 +109,7 @@ class FindFilesTool(ToolBase):
|
|
108
109
|
}
|
109
110
|
return sorted(dir_output)
|
110
111
|
|
111
|
-
@protect_against_loops(max_calls=5, time_window=10.0)
|
112
|
+
@protect_against_loops(max_calls=5, time_window=10.0, key_field="paths")
|
112
113
|
def run(
|
113
114
|
self,
|
114
115
|
paths: str,
|
@@ -121,7 +122,7 @@ class FindFilesTool(ToolBase):
|
|
121
122
|
return tr("Warning: Empty file pattern provided. Operation skipped.")
|
122
123
|
patterns = pattern.split()
|
123
124
|
results = []
|
124
|
-
for directory in paths.split():
|
125
|
+
for directory in [expand_path(p) for p in paths.split()]:
|
125
126
|
disp_path = display_path(directory)
|
126
127
|
depth_msg = (
|
127
128
|
tr(" (max depth: {max_depth})", max_depth=max_depth)
|
@@ -4,6 +4,7 @@ from .markdown_outline import parse_markdown_outline
|
|
4
4
|
from janito.formatting import OutlineFormatter
|
5
5
|
from .java_outline import parse_java_outline
|
6
6
|
import os
|
7
|
+
from janito.tools.path_utils import expand_path
|
7
8
|
from janito.tools.tool_base import ToolBase, ToolPermissions
|
8
9
|
from janito.report_events import ReportAction
|
9
10
|
from janito.tools.tool_utils import display_path, pluralize
|
@@ -25,9 +26,10 @@ class GetFileOutlineTool(ToolBase):
|
|
25
26
|
permissions = ToolPermissions(read=True)
|
26
27
|
tool_name = "get_file_outline"
|
27
28
|
|
28
|
-
@protect_against_loops(max_calls=5, time_window=10.0)
|
29
|
+
@protect_against_loops(max_calls=5, time_window=10.0, key_field="path")
|
29
30
|
def run(self, path: str) -> str:
|
30
31
|
try:
|
32
|
+
path = expand_path(path)
|
31
33
|
self.report_action(
|
32
34
|
tr(
|
33
35
|
"📄 Outline file '{disp_path}' ...",
|
@@ -16,7 +16,7 @@ class SearchOutlineTool(ToolBase):
|
|
16
16
|
permissions = ToolPermissions(read=True)
|
17
17
|
tool_name = "search_outline"
|
18
18
|
|
19
|
-
@protect_against_loops(max_calls=5, time_window=10.0)
|
19
|
+
@protect_against_loops(max_calls=5, time_window=10.0, key_field="path")
|
20
20
|
def run(self, path: str) -> str:
|
21
21
|
from janito.tools.tool_utils import display_path
|
22
22
|
from janito.i18n import tr
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import os
|
2
|
+
from janito.tools.path_utils import expand_path
|
2
3
|
import shutil
|
3
4
|
from janito.tools.adapters.local.adapter import register_local_tool
|
4
5
|
from janito.tools.tool_utils import display_path
|
@@ -31,10 +32,10 @@ class MoveFileTool(ToolBase):
|
|
31
32
|
overwrite: bool = False,
|
32
33
|
backup: bool = False,
|
33
34
|
) -> str:
|
35
|
+
src = expand_path(src_path)
|
36
|
+
dest = expand_path(dest_path)
|
34
37
|
original_src = src_path
|
35
38
|
original_dest = dest_path
|
36
|
-
src = src_path
|
37
|
-
dest = dest_path
|
38
39
|
disp_src = display_path(original_src)
|
39
40
|
disp_dest = display_path(original_dest)
|
40
41
|
backup_path = None
|
@@ -21,7 +21,7 @@ class OpenHtmlInBrowserTool(ToolBase):
|
|
21
21
|
permissions = ToolPermissions(read=True)
|
22
22
|
tool_name = "open_html_in_browser"
|
23
23
|
|
24
|
-
@protect_against_loops(max_calls=5, time_window=10.0)
|
24
|
+
@protect_against_loops(max_calls=5, time_window=10.0, key_field="path")
|
25
25
|
def run(self, path: str) -> str:
|
26
26
|
if not path.strip():
|
27
27
|
self.report_warning(tr("ℹ️ Empty file path provided."))
|
@@ -20,7 +20,7 @@ class OpenUrlTool(ToolBase):
|
|
20
20
|
permissions = ToolPermissions(read=True)
|
21
21
|
tool_name = "open_url"
|
22
22
|
|
23
|
-
@protect_against_loops(max_calls=5, time_window=10.0)
|
23
|
+
@protect_against_loops(max_calls=5, time_window=10.0, key_field="url")
|
24
24
|
def run(self, url: str) -> str:
|
25
25
|
if not url.strip():
|
26
26
|
self.report_warning(tr("ℹ️ Empty URL provided."))
|
@@ -27,6 +27,8 @@ class PythonFileRunTool(ToolBase):
|
|
27
27
|
tool_name = "python_file_run"
|
28
28
|
|
29
29
|
def run(self, path: str, timeout: int = 60, silent: bool = False) -> str:
|
30
|
+
from janito.tools.path_utils import expand_path
|
31
|
+
path = expand_path(path)
|
30
32
|
if not silent:
|
31
33
|
self.report_action(
|
32
34
|
tr("🚀 Running: python {path}", path=path),
|
@@ -26,8 +26,10 @@ class ReadChartTool(ToolBase):
|
|
26
26
|
permissions = ToolPermissions(read=True)
|
27
27
|
tool_name = "read_chart"
|
28
28
|
|
29
|
-
@protect_against_loops(max_calls=5, time_window=10.0)
|
30
|
-
def run(
|
29
|
+
@protect_against_loops(max_calls=5, time_window=10.0, key_field="data")
|
30
|
+
def run(
|
31
|
+
self, data: dict, title: str = "Chart", width: int = 80, height: int = 20
|
32
|
+
) -> str:
|
31
33
|
try:
|
32
34
|
from rich.console import Console
|
33
35
|
from rich.table import Table
|
@@ -36,49 +38,52 @@ class ReadChartTool(ToolBase):
|
|
36
38
|
from rich.panel import Panel
|
37
39
|
from rich.columns import Columns
|
38
40
|
from rich import box
|
39
|
-
|
41
|
+
|
40
42
|
console = Console(width=width)
|
41
|
-
|
43
|
+
|
42
44
|
if not isinstance(data, dict):
|
43
45
|
return "❌ Error: Data must be a dictionary"
|
44
|
-
|
45
|
-
chart_type = data.get(
|
46
|
-
chart_data = data.get(
|
47
|
-
|
46
|
+
|
47
|
+
chart_type = data.get("type", "table").lower()
|
48
|
+
chart_data = data.get("data", [])
|
49
|
+
|
48
50
|
if not chart_data:
|
49
51
|
return "⚠️ Warning: No data provided for chart"
|
50
|
-
|
52
|
+
|
51
53
|
self.report_action(
|
52
|
-
tr(
|
53
|
-
|
54
|
-
|
54
|
+
tr(
|
55
|
+
"📊 Displaying {chart_type} chart: {title}",
|
56
|
+
chart_type=chart_type,
|
57
|
+
title=title,
|
58
|
+
),
|
59
|
+
ReportAction.READ,
|
55
60
|
)
|
56
|
-
|
57
|
-
if chart_type ==
|
61
|
+
|
62
|
+
if chart_type == "table":
|
58
63
|
return self._display_table(console, chart_data, title, width)
|
59
|
-
elif chart_type ==
|
64
|
+
elif chart_type == "bar":
|
60
65
|
return self._display_bar(console, chart_data, title, width, height)
|
61
|
-
elif chart_type ==
|
66
|
+
elif chart_type == "line":
|
62
67
|
return self._display_line(console, chart_data, title, width, height)
|
63
|
-
elif chart_type ==
|
68
|
+
elif chart_type == "pie":
|
64
69
|
return self._display_pie(console, chart_data, title, width)
|
65
70
|
else:
|
66
71
|
return f"❌ Error: Unsupported chart type '{chart_type}'. Use: table, bar, line, pie"
|
67
|
-
|
72
|
+
|
68
73
|
except ImportError:
|
69
74
|
return "❌ Error: rich library not available for chart display"
|
70
75
|
except Exception as e:
|
71
76
|
return f"❌ Error displaying chart: {e}"
|
72
|
-
|
77
|
+
|
73
78
|
def _display_table(self, console, data, title, width):
|
74
79
|
"""Display data as a rich table."""
|
75
80
|
from rich.table import Table
|
76
|
-
|
81
|
+
|
77
82
|
if not data:
|
78
83
|
return "No data to display"
|
79
|
-
|
84
|
+
|
80
85
|
table = Table(title=title, show_header=True, header_style="bold magenta")
|
81
|
-
|
86
|
+
|
82
87
|
# Handle different data formats
|
83
88
|
if isinstance(data, dict):
|
84
89
|
# Dictionary format: key-value pairs
|
@@ -99,10 +104,10 @@ class ReadChartTool(ToolBase):
|
|
99
104
|
table.add_column("Items", style="cyan")
|
100
105
|
for item in data:
|
101
106
|
table.add_row(str(item))
|
102
|
-
|
107
|
+
|
103
108
|
console.print(table)
|
104
109
|
return f"✅ Table chart displayed: {title}"
|
105
|
-
|
110
|
+
|
106
111
|
def _display_bar(self, console, data, title, width, height):
|
107
112
|
"""Display data as a simple bar chart using unicode blocks."""
|
108
113
|
try:
|
@@ -118,10 +123,10 @@ class ReadChartTool(ToolBase):
|
|
118
123
|
items = [(str(i), v) for i, v in enumerate(data)]
|
119
124
|
else:
|
120
125
|
items = [(str(i), v) for i, v in enumerate(data)]
|
121
|
-
|
126
|
+
|
122
127
|
if not items:
|
123
128
|
return "No data to display"
|
124
|
-
|
129
|
+
|
125
130
|
# Convert values to numbers
|
126
131
|
numeric_items = []
|
127
132
|
for label, value in items:
|
@@ -129,25 +134,25 @@ class ReadChartTool(ToolBase):
|
|
129
134
|
numeric_items.append((str(label), float(value)))
|
130
135
|
except (ValueError, TypeError):
|
131
136
|
numeric_items.append((str(label), 0.0))
|
132
|
-
|
137
|
+
|
133
138
|
if not numeric_items:
|
134
139
|
return "No valid numeric data to display"
|
135
|
-
|
140
|
+
|
136
141
|
max_val = max(val for _, val in numeric_items) if numeric_items else 1
|
137
|
-
|
142
|
+
|
138
143
|
console.print(f"\n[bold]{title}[/bold]")
|
139
144
|
console.print("=" * min(len(title), width))
|
140
|
-
|
145
|
+
|
141
146
|
for label, value in numeric_items:
|
142
147
|
bar_length = int((value / max_val) * (width - 20)) if max_val > 0 else 0
|
143
148
|
bar = "█" * bar_length
|
144
149
|
console.print(f"{label:<15} {bar} {value:.1f}")
|
145
|
-
|
150
|
+
|
146
151
|
return f"✅ Bar chart displayed: {title}"
|
147
|
-
|
152
|
+
|
148
153
|
except Exception as e:
|
149
154
|
return f"❌ Error displaying bar chart: {e}"
|
150
|
-
|
155
|
+
|
151
156
|
def _display_line(self, console, data, title, width, height):
|
152
157
|
"""Display data as a simple line chart using unicode characters."""
|
153
158
|
try:
|
@@ -165,7 +170,7 @@ class ReadChartTool(ToolBase):
|
|
165
170
|
items = [(str(i), v) for i, v in enumerate(data)]
|
166
171
|
else:
|
167
172
|
return "Unsupported data format"
|
168
|
-
|
173
|
+
|
169
174
|
# Convert to numeric values
|
170
175
|
points = []
|
171
176
|
for x, y in items:
|
@@ -173,34 +178,34 @@ class ReadChartTool(ToolBase):
|
|
173
178
|
points.append((float(x), float(y)))
|
174
179
|
except (ValueError, TypeError):
|
175
180
|
continue
|
176
|
-
|
181
|
+
|
177
182
|
if len(points) < 2:
|
178
183
|
return "Need at least 2 data points for line chart"
|
179
|
-
|
184
|
+
|
180
185
|
points.sort(key=lambda p: p[0])
|
181
|
-
|
186
|
+
|
182
187
|
# Simple ASCII line chart
|
183
188
|
min_x, max_x = min(p[0] for p in points), max(p[0] for p in points)
|
184
189
|
min_y, max_y = min(p[1] for p in points), max(p[1] for p in points)
|
185
|
-
|
190
|
+
|
186
191
|
if max_x == min_x or max_y == min_y:
|
187
192
|
return "Cannot display line chart: all values are the same"
|
188
|
-
|
193
|
+
|
189
194
|
console.print(f"\n[bold]{title}[/bold]")
|
190
195
|
console.print("=" * min(len(title), width))
|
191
|
-
|
196
|
+
|
192
197
|
# Simple representation
|
193
198
|
for x, y in points:
|
194
199
|
x_norm = int(((x - min_x) / (max_x - min_x)) * (width - 20))
|
195
200
|
y_norm = int(((y - min_y) / (max_y - min_y)) * 10)
|
196
201
|
line = " " * x_norm + "●" + " " * (width - 20 - x_norm)
|
197
202
|
console.print(f"{x:>8.1f}: {line} {y:.1f}")
|
198
|
-
|
203
|
+
|
199
204
|
return f"✅ Line chart displayed: {title}"
|
200
|
-
|
205
|
+
|
201
206
|
except Exception as e:
|
202
207
|
return f"❌ Error displaying line chart: {e}"
|
203
|
-
|
208
|
+
|
204
209
|
def _display_pie(self, console, data, title, width):
|
205
210
|
"""Display data as a simple pie chart representation."""
|
206
211
|
try:
|
@@ -215,7 +220,7 @@ class ReadChartTool(ToolBase):
|
|
215
220
|
items = [(str(i), v) for i, v in enumerate(data)]
|
216
221
|
else:
|
217
222
|
items = [(str(i), v) for i, v in enumerate(data)]
|
218
|
-
|
223
|
+
|
219
224
|
# Convert to numeric values
|
220
225
|
values = []
|
221
226
|
for label, value in items:
|
@@ -223,30 +228,32 @@ class ReadChartTool(ToolBase):
|
|
223
228
|
values.append((str(label), float(value)))
|
224
229
|
except (ValueError, TypeError):
|
225
230
|
continue
|
226
|
-
|
231
|
+
|
227
232
|
if not values:
|
228
233
|
return "No valid numeric data to display"
|
229
|
-
|
234
|
+
|
230
235
|
total = sum(val for _, val in values)
|
231
236
|
if total == 0:
|
232
237
|
return "Cannot display pie chart: total is zero"
|
233
|
-
|
238
|
+
|
234
239
|
console.print(f"\n[bold]{title}[/bold]")
|
235
240
|
console.print("=" * min(len(title), width))
|
236
|
-
|
241
|
+
|
237
242
|
# Unicode pie chart segments
|
238
243
|
segments = ["🟦", "🟥", "🟩", "🟨", "🟪", "🟧", "⬛", "⬜"]
|
239
|
-
|
244
|
+
|
240
245
|
for i, (label, value) in enumerate(values):
|
241
246
|
percentage = (value / total) * 100
|
242
247
|
segment = segments[i % len(segments)]
|
243
248
|
bar_length = int((value / total) * (width - 30))
|
244
249
|
bar = "█" * bar_length
|
245
|
-
console.print(
|
246
|
-
|
250
|
+
console.print(
|
251
|
+
f"{segment} {label:<15} {bar} {percentage:5.1f}% ({value})"
|
252
|
+
)
|
253
|
+
|
247
254
|
console.print(f"\n[dim]Total: {total}[/dim]")
|
248
|
-
|
255
|
+
|
249
256
|
return f"✅ Pie chart displayed: {title}"
|
250
|
-
|
257
|
+
|
251
258
|
except Exception as e:
|
252
|
-
return f"❌ Error displaying pie chart: {e}"
|
259
|
+
return f"❌ Error displaying pie chart: {e}"
|
@@ -21,13 +21,14 @@ class ReadFilesTool(ToolBase):
|
|
21
21
|
permissions = ToolPermissions(read=True)
|
22
22
|
tool_name = "read_files"
|
23
23
|
|
24
|
-
@protect_against_loops(max_calls=5, time_window=10.0)
|
24
|
+
@protect_against_loops(max_calls=5, time_window=10.0, key_field="paths")
|
25
25
|
def run(self, paths: list[str]) -> str:
|
26
26
|
from janito.tools.tool_utils import display_path
|
27
27
|
import os
|
28
|
+
from janito.tools.path_utils import expand_path
|
28
29
|
|
29
30
|
results = []
|
30
|
-
for path in paths:
|
31
|
+
for path in [expand_path(p) for p in paths]:
|
31
32
|
disp_path = display_path(path)
|
32
33
|
self.report_action(
|
33
34
|
tr("📖 Read '{disp_path}'", disp_path=disp_path), ReportAction.READ
|
@@ -54,4 +55,4 @@ class ReadFilesTool(ToolBase):
|
|
54
55
|
results.append(
|
55
56
|
f"--- File: {disp_path} (error) ---\nError reading file: {e}\n"
|
56
57
|
)
|
57
|
-
return "\n".join(results)
|
58
|
+
return "\n".join(results)
|
@@ -6,6 +6,7 @@ from janito.i18n import tr
|
|
6
6
|
import shutil
|
7
7
|
import os
|
8
8
|
import zipfile
|
9
|
+
from janito.tools.path_utils import expand_path
|
9
10
|
|
10
11
|
|
11
12
|
@register_local_tool
|
@@ -26,6 +27,7 @@ class RemoveDirectoryTool(ToolBase):
|
|
26
27
|
tool_name = "remove_directory"
|
27
28
|
|
28
29
|
def run(self, path: str, recursive: bool = False) -> str:
|
30
|
+
path = expand_path(path)
|
29
31
|
disp_path = display_path(path)
|
30
32
|
self.report_action(
|
31
33
|
tr("🗃️ Remove directory '{disp_path}' ...", disp_path=disp_path),
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import os
|
2
|
+
from janito.tools.path_utils import expand_path
|
2
3
|
import shutil
|
3
4
|
from janito.tools.adapters.local.adapter import register_local_tool
|
4
5
|
|
@@ -26,9 +27,8 @@ class RemoveFileTool(ToolBase):
|
|
26
27
|
tool_name = "remove_file"
|
27
28
|
|
28
29
|
def run(self, path: str, backup: bool = False) -> str:
|
29
|
-
|
30
|
-
|
31
|
-
disp_path = display_path(original_path)
|
30
|
+
path = expand_path(path)
|
31
|
+
disp_path = display_path(path)
|
32
32
|
|
33
33
|
# Report initial info about what is going to be removed
|
34
34
|
self.report_action(
|
@@ -4,6 +4,7 @@ from janito.tools.adapters.local.adapter import register_local_tool
|
|
4
4
|
from janito.tools.tool_utils import pluralize, display_path
|
5
5
|
from janito.i18n import tr
|
6
6
|
import os
|
7
|
+
from janito.tools.path_utils import expand_path
|
7
8
|
from .pattern_utils import prepare_pattern, format_result, summarize_total
|
8
9
|
from .match_lines import read_file_lines
|
9
10
|
from .traverse_directory import traverse_directory
|
@@ -153,7 +154,7 @@ class SearchTextTool(ToolBase):
|
|
153
154
|
)
|
154
155
|
return info_str, dir_output, dir_limit_reached, per_file_counts
|
155
156
|
|
156
|
-
@protect_against_loops(max_calls=5, time_window=10.0)
|
157
|
+
@protect_against_loops(max_calls=5, time_window=10.0, key_field="paths")
|
157
158
|
def run(
|
158
159
|
self,
|
159
160
|
paths: str,
|
@@ -169,7 +170,7 @@ class SearchTextTool(ToolBase):
|
|
169
170
|
)
|
170
171
|
if error_msg:
|
171
172
|
return error_msg
|
172
|
-
paths_list = paths.split()
|
173
|
+
paths_list = [expand_path(p) for p in paths.split()]
|
173
174
|
results = []
|
174
175
|
all_per_file_counts = []
|
175
176
|
for search_path in paths_list:
|
@@ -1,4 +1,5 @@
|
|
1
1
|
import os
|
2
|
+
from janito.tools.path_utils import expand_path
|
2
3
|
from janito.i18n import tr
|
3
4
|
from janito.tools.tool_base import ToolBase, ToolPermissions
|
4
5
|
from janito.report_events import ReportAction
|
@@ -91,8 +92,9 @@ class ValidateFileSyntaxTool(ToolBase):
|
|
91
92
|
permissions = ToolPermissions(read=True)
|
92
93
|
tool_name = "validate_file_syntax"
|
93
94
|
|
94
|
-
@protect_against_loops(max_calls=5, time_window=10.0)
|
95
|
+
@protect_against_loops(max_calls=5, time_window=10.0, key_field="path")
|
95
96
|
def run(self, path: str) -> str:
|
97
|
+
path = expand_path(path)
|
96
98
|
disp_path = display_path(path)
|
97
99
|
self.report_action(
|
98
100
|
tr(
|
@@ -29,11 +29,13 @@ class ViewFileTool(ToolBase):
|
|
29
29
|
permissions = ToolPermissions(read=True)
|
30
30
|
tool_name = "view_file"
|
31
31
|
|
32
|
-
@protect_against_loops(max_calls=5, time_window=10.0)
|
32
|
+
@protect_against_loops(max_calls=5, time_window=10.0, key_field="path")
|
33
33
|
def run(self, path: str, from_line: int = None, to_line: int = None) -> str:
|
34
34
|
import os
|
35
35
|
from janito.tools.tool_utils import display_path
|
36
|
+
from janito.tools.path_utils import expand_path
|
36
37
|
|
38
|
+
path = expand_path(path)
|
37
39
|
disp_path = display_path(path)
|
38
40
|
self.report_action(
|
39
41
|
tr("📖 View '{disp_path}'", disp_path=disp_path),
|