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.
Files changed (47) hide show
  1. janito/cli/chat_mode/session.py +2 -2
  2. janito/cli/cli_commands/list_plugins.py +32 -0
  3. janito/cli/core/getters.py +2 -0
  4. janito/cli/main_cli.py +6 -2
  5. janito/cli/single_shot_mode/handler.py +2 -2
  6. janito/config_manager.py +8 -0
  7. janito/exceptions.py +19 -1
  8. janito/plugins/base.py +53 -2
  9. janito/plugins/config.py +87 -0
  10. janito/plugins/discovery.py +32 -0
  11. janito/plugins/manager.py +56 -2
  12. janito/tools/adapters/local/adapter.py +8 -26
  13. janito/tools/adapters/local/ask_user.py +1 -1
  14. janito/tools/adapters/local/copy_file.py +3 -1
  15. janito/tools/adapters/local/create_directory.py +2 -2
  16. janito/tools/adapters/local/create_file.py +8 -4
  17. janito/tools/adapters/local/fetch_url.py +25 -22
  18. janito/tools/adapters/local/find_files.py +3 -2
  19. janito/tools/adapters/local/get_file_outline/core.py +3 -1
  20. janito/tools/adapters/local/get_file_outline/search_outline.py +1 -1
  21. janito/tools/adapters/local/move_file.py +3 -2
  22. janito/tools/adapters/local/open_html_in_browser.py +1 -1
  23. janito/tools/adapters/local/open_url.py +1 -1
  24. janito/tools/adapters/local/python_file_run.py +2 -0
  25. janito/tools/adapters/local/read_chart.py +61 -54
  26. janito/tools/adapters/local/read_files.py +4 -3
  27. janito/tools/adapters/local/remove_directory.py +2 -0
  28. janito/tools/adapters/local/remove_file.py +3 -3
  29. janito/tools/adapters/local/run_powershell_command.py +1 -0
  30. janito/tools/adapters/local/search_text/core.py +3 -2
  31. janito/tools/adapters/local/validate_file_syntax/core.py +3 -1
  32. janito/tools/adapters/local/view_file.py +3 -1
  33. janito/tools/loop_protection_decorator.py +64 -25
  34. janito/tools/path_utils.py +39 -0
  35. janito/tools/tools_adapter.py +68 -22
  36. {janito-2.24.1.dist-info → janito-2.25.0.dist-info}/METADATA +1 -1
  37. {janito-2.24.1.dist-info → janito-2.25.0.dist-info}/RECORD +46 -39
  38. janito-2.25.0.dist-info/top_level.txt +2 -0
  39. janito-coder/janito_coder/__init__.py +9 -0
  40. janito-coder/janito_coder/plugins/__init__.py +27 -0
  41. janito-coder/janito_coder/plugins/code_navigator.py +618 -0
  42. janito-coder/janito_coder/plugins/git_analyzer.py +273 -0
  43. janito-coder/pyproject.toml +347 -0
  44. janito-2.24.1.dist-info/top_level.txt +0 -1
  45. {janito-2.24.1.dist-info → janito-2.25.0.dist-info}/WHEEL +0 -0
  46. {janito-2.24.1.dist-info → janito-2.25.0.dist-info}/entry_points.txt +0 -0
  47. {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 = {} # In-memory session cache - lifetime matches tool instance
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, 'r', encoding='utf-8') as f:
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, 'w', encoding='utf-8') as f:
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['status_code'] == 403:
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['status_code'] == 404:
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['timestamp'] > expiration_time:
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['message'], True
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
- 'status_code': status_code,
119
- 'message': message,
120
- 'timestamp': time.time()
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, 'w', encoding='utf-8') as f:
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(self, data: dict, title: str = "Chart", width: int = 80, height: int = 20) -> str:
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('type', 'table').lower()
46
- chart_data = data.get('data', [])
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("📊 Displaying {chart_type} chart: {title}",
53
- chart_type=chart_type, title=title),
54
- ReportAction.READ
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 == 'table':
61
+
62
+ if chart_type == "table":
58
63
  return self._display_table(console, chart_data, title, width)
59
- elif chart_type == 'bar':
64
+ elif chart_type == "bar":
60
65
  return self._display_bar(console, chart_data, title, width, height)
61
- elif chart_type == 'line':
66
+ elif chart_type == "line":
62
67
  return self._display_line(console, chart_data, title, width, height)
63
- elif chart_type == 'pie':
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(f"{segment} {label:<15} {bar} {percentage:5.1f}% ({value})")
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
- original_path = path
30
- path = path # Using path as is
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.i18n import tr
5
5
  import subprocess
6
6
  import os
7
+ from janito.tools.path_utils import expand_path
7
8
  import tempfile
8
9
  import threading
9
10
 
@@ -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),