janito 1.10.0__py3-none-any.whl → 1.11.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.
Files changed (57) hide show
  1. janito/__init__.py +1 -1
  2. janito/agent/conversation_api.py +178 -90
  3. janito/agent/conversation_ui.py +1 -1
  4. janito/agent/llm_conversation_history.py +12 -0
  5. janito/agent/templates/profiles/system_prompt_template_base.txt.j2 +19 -4
  6. janito/agent/tools/__init__.py +2 -0
  7. janito/agent/tools/create_directory.py +1 -1
  8. janito/agent/tools/create_file.py +1 -1
  9. janito/agent/tools/fetch_url.py +1 -1
  10. janito/agent/tools/find_files.py +26 -13
  11. janito/agent/tools/get_file_outline/core.py +1 -1
  12. janito/agent/tools/get_file_outline/python_outline.py +139 -95
  13. janito/agent/tools/get_lines.py +92 -63
  14. janito/agent/tools/move_file.py +58 -32
  15. janito/agent/tools/open_url.py +31 -0
  16. janito/agent/tools/python_command_runner.py +85 -86
  17. janito/agent/tools/python_file_runner.py +85 -86
  18. janito/agent/tools/python_stdin_runner.py +87 -88
  19. janito/agent/tools/remove_directory.py +1 -1
  20. janito/agent/tools/remove_file.py +1 -1
  21. janito/agent/tools/replace_file.py +2 -2
  22. janito/agent/tools/replace_text_in_file.py +193 -149
  23. janito/agent/tools/run_bash_command.py +1 -1
  24. janito/agent/tools/run_powershell_command.py +4 -0
  25. janito/agent/tools/search_text/__init__.py +1 -0
  26. janito/agent/tools/search_text/core.py +176 -0
  27. janito/agent/tools/search_text/match_lines.py +58 -0
  28. janito/agent/tools/search_text/pattern_utils.py +65 -0
  29. janito/agent/tools/search_text/traverse_directory.py +132 -0
  30. janito/agent/tools/validate_file_syntax/core.py +41 -30
  31. janito/agent/tools/validate_file_syntax/html_validator.py +21 -5
  32. janito/agent/tools/validate_file_syntax/markdown_validator.py +77 -34
  33. janito/agent/tools_utils/gitignore_utils.py +25 -2
  34. janito/agent/tools_utils/utils.py +7 -1
  35. janito/cli/config_commands.py +112 -109
  36. janito/shell/main.py +51 -8
  37. janito/shell/session/config.py +83 -75
  38. janito/shell/ui/interactive.py +97 -73
  39. janito/termweb/static/editor.css +32 -29
  40. janito/termweb/static/editor.css.bak +140 -22
  41. janito/termweb/static/editor.html +12 -7
  42. janito/termweb/static/editor.html.bak +16 -11
  43. janito/termweb/static/editor.js +94 -40
  44. janito/termweb/static/editor.js.bak +97 -65
  45. janito/termweb/static/index.html +1 -2
  46. janito/termweb/static/index.html.bak +1 -1
  47. janito/termweb/static/termweb.css +1 -22
  48. janito/termweb/static/termweb.css.bak +6 -4
  49. janito/termweb/static/termweb.js +0 -6
  50. janito/termweb/static/termweb.js.bak +1 -2
  51. {janito-1.10.0.dist-info → janito-1.11.1.dist-info}/METADATA +1 -1
  52. {janito-1.10.0.dist-info → janito-1.11.1.dist-info}/RECORD +56 -51
  53. {janito-1.10.0.dist-info → janito-1.11.1.dist-info}/WHEEL +1 -1
  54. janito/agent/tools/search_text.py +0 -254
  55. {janito-1.10.0.dist-info → janito-1.11.1.dist-info}/entry_points.txt +0 -0
  56. {janito-1.10.0.dist-info → janito-1.11.1.dist-info}/licenses/LICENSE +0 -0
  57. {janito-1.10.0.dist-info → janito-1.11.1.dist-info}/top_level.txt +0 -0
janito/__init__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "1.10.0-dev"
1
+ __version__ = "1.11.0"
@@ -9,19 +9,19 @@ from janito.agent.runtime_config import runtime_config
9
9
  from janito.agent.tool_registry import get_tool_schemas
10
10
  from janito.agent.conversation_exceptions import NoToolSupportError, EmptyResponseError
11
11
  from janito.agent.api_exceptions import ApiError
12
+ from rich.console import Console
13
+ from rich.status import Status
14
+
15
+ console = Console()
12
16
 
13
17
 
14
18
  def _sanitize_utf8_surrogates(obj):
15
- """
16
- Recursively sanitize a dict/list/string by replacing surrogate codepoints with the unicode replacement character.
17
- """
18
- if isinstance(obj, str):
19
- # Encode with surrogatepass, then decode with 'utf-8', replacing errors
20
- return obj.encode("utf-8", "replace").decode("utf-8", "replace")
21
- elif isinstance(obj, dict):
19
+ if isinstance(obj, dict):
22
20
  return {k: _sanitize_utf8_surrogates(v) for k, v in obj.items()}
23
21
  elif isinstance(obj, list):
24
- return [_sanitize_utf8_surrogates(x) for x in obj]
22
+ return [_sanitize_utf8_surrogates(i) for i in obj]
23
+ elif isinstance(obj, str):
24
+ return obj.encode("utf-8", "surrogatepass").decode("utf-8", "ignore")
25
25
  else:
26
26
  return obj
27
27
 
@@ -75,110 +75,176 @@ def _extract_status_and_retry_after(e, error_message):
75
75
  status_code = getattr(e, "status_code")
76
76
  elif hasattr(e, "response") and hasattr(e.response, "status_code"):
77
77
  status_code = getattr(e.response, "status_code")
78
- if hasattr(e.response, "headers") and e.response.headers:
79
- retry_after = e.response.headers.get("Retry-After")
80
- else:
81
- import re
82
-
83
- match = re.search(r"[Ee]rror code: (\d{3})", error_message)
84
- if match:
85
- status_code = int(match.group(1))
86
- retry_after_match = re.search(r"Retry-After\['\"]?:?\s*(\d+)", error_message)
87
- if retry_after_match:
88
- retry_after = retry_after_match.group(1)
78
+ elif "429" in error_message:
79
+ status_code = 429
80
+ import re
81
+
82
+ match = re.search(r"status[ _]?code[=: ]+([0-9]+)", error_message)
83
+ if match:
84
+ status_code = int(match.group(1))
85
+ match_retry = re.search(r"retry[-_ ]?after[=: ]+([0-9]+)", error_message)
86
+ if match_retry:
87
+ retry_after = int(match_retry.group(1))
89
88
  return status_code, retry_after
90
89
 
91
90
 
92
91
  def _calculate_wait_time(status_code, retry_after, attempt):
93
- if status_code == 429 and retry_after is not None:
94
- try:
95
- return int(float(retry_after))
96
- except Exception:
97
- return 2**attempt
92
+ if status_code == 429 and retry_after:
93
+ return max(retry_after, 2**attempt)
98
94
  return 2**attempt
99
95
 
100
96
 
101
- def _log_and_sleep(message, attempt, max_retries, e=None, wait_time=None):
102
- print(
103
- tr(
104
- message,
105
- attempt=attempt,
106
- max_retries=max_retries,
107
- e=e,
108
- wait_time=wait_time,
109
- )
97
+ def _log_and_sleep(
98
+ message,
99
+ attempt,
100
+ max_retries,
101
+ e=None,
102
+ wait_time=None,
103
+ status=None,
104
+ waiting_message=None,
105
+ restore_message=None,
106
+ ):
107
+ status_message = tr(
108
+ message,
109
+ attempt=attempt,
110
+ max_retries=max_retries,
111
+ e=e,
112
+ wait_time=wait_time,
110
113
  )
111
- time.sleep(wait_time)
114
+ if (
115
+ status is not None
116
+ and waiting_message is not None
117
+ and restore_message is not None
118
+ ):
119
+ original_message = status.status
120
+ status.update(waiting_message)
121
+ time.sleep(wait_time)
122
+ status.update(restore_message)
123
+ else:
124
+ with Status(status_message, console=console, spinner="dots"):
125
+ time.sleep(wait_time)
112
126
 
113
127
 
114
- def _handle_json_decode_error(e, attempt, max_retries):
128
+ def _handle_json_decode_error(e, attempt, max_retries, status=None):
115
129
  if attempt < max_retries:
116
130
  wait_time = 2**attempt
117
- _log_and_sleep(
118
- "Invalid/malformed response from OpenAI (attempt {attempt}/{max_retries}). Retrying in {wait_time} seconds...",
119
- attempt,
120
- max_retries,
121
- wait_time=wait_time,
122
- )
131
+ if status is not None:
132
+ _log_and_sleep(
133
+ "Invalid/malformed response from OpenAI (attempt {attempt}/{max_retries}). Retrying in {wait_time} seconds...",
134
+ attempt,
135
+ max_retries,
136
+ wait_time=wait_time,
137
+ status=status,
138
+ waiting_message="Waiting after error...",
139
+ restore_message="Waiting for AI response...",
140
+ )
141
+ else:
142
+ _log_and_sleep(
143
+ "Invalid/malformed response from OpenAI (attempt {attempt}/{max_retries}). Retrying in {wait_time} seconds...",
144
+ attempt,
145
+ max_retries,
146
+ wait_time=wait_time,
147
+ )
123
148
  return None
124
149
  else:
125
150
  print(tr("Max retries for invalid response reached. Raising error."))
126
151
  raise e
127
152
 
128
153
 
129
- def _handle_general_exception(e, attempt, max_retries):
130
- error_message = str(e)
154
+ def _handle_no_tool_support(error_message):
131
155
  if "No endpoints found that support tool use" in error_message:
132
156
  print(tr("API does not support tool use."))
133
157
  raise NoToolSupportError(error_message)
134
- status_code, retry_after = _extract_status_and_retry_after(e, error_message)
135
- if status_code is not None:
136
- if status_code == 429:
137
- wait_time = _calculate_wait_time(status_code, retry_after, attempt)
138
- if attempt < max_retries:
139
- _log_and_sleep(
140
- "OpenAI API rate limit (429) (attempt {attempt}/{max_retries}): {e}. Retrying in {wait_time} seconds...",
141
- attempt,
142
- max_retries,
143
- e=e,
144
- wait_time=wait_time,
145
- )
146
- return None
147
- else:
148
- print("Max retries for OpenAI API rate limit reached. Raising error.")
149
- raise e
150
- elif 500 <= status_code < 600:
151
- wait_time = 2**attempt
152
- if attempt < max_retries:
153
- _log_and_sleep(
154
- "OpenAI API server error (attempt {attempt}/{max_retries}): {e}. Retrying in {wait_time} seconds...",
155
- attempt,
156
- max_retries,
157
- e=e,
158
- wait_time=wait_time,
159
- )
160
- return None
161
- else:
162
- print("Max retries for OpenAI API server error reached. Raising error.")
163
- raise e
164
- elif 400 <= status_code < 500:
165
- print(
166
- tr(
167
- "OpenAI API client error {status_code}: {e}. Not retrying.",
168
- status_code=status_code,
169
- e=e,
170
- )
158
+
159
+
160
+ def _handle_rate_limit(e, attempt, max_retries, status, status_code, retry_after):
161
+ wait_time = _calculate_wait_time(status_code, retry_after, attempt)
162
+ if attempt < max_retries:
163
+ if status is not None:
164
+ _log_and_sleep(
165
+ "OpenAI API rate limit (429) (attempt {attempt}/{max_retries}): {e}. Retrying in {wait_time} seconds...",
166
+ attempt,
167
+ max_retries,
168
+ e=e,
169
+ wait_time=wait_time,
170
+ status=status,
171
+ waiting_message="Waiting after rate limit reached...",
172
+ restore_message="Waiting for AI response...",
173
+ )
174
+ else:
175
+ _log_and_sleep(
176
+ "OpenAI API rate limit (429) (attempt {attempt}/{max_retries}): {e}. Retrying in {wait_time} seconds...",
177
+ attempt,
178
+ max_retries,
179
+ e=e,
180
+ wait_time=wait_time,
171
181
  )
172
- raise e
182
+ return None
183
+ else:
184
+ raise e
185
+
186
+
187
+ def _handle_server_error(e, attempt, max_retries, status, status_code):
188
+ wait_time = 2**attempt
173
189
  if attempt < max_retries:
174
- wait_time = 2**attempt
175
- _log_and_sleep(
176
- "OpenAI API error (attempt {attempt}/{max_retries}): {e}. Retrying in {wait_time} seconds...",
177
- attempt,
178
- max_retries,
190
+ if status is not None:
191
+ _log_and_sleep(
192
+ "OpenAI API server error (attempt {attempt}/{max_retries}): {e}. Retrying in {wait_time} seconds...",
193
+ attempt,
194
+ max_retries,
195
+ e=e,
196
+ wait_time=wait_time,
197
+ status=status,
198
+ waiting_message="Waiting after server error...",
199
+ restore_message="Waiting for AI response...",
200
+ )
201
+ else:
202
+ _log_and_sleep(
203
+ "OpenAI API server error (attempt {attempt}/{max_retries}): {e}. Retrying in {wait_time} seconds...",
204
+ attempt,
205
+ max_retries,
206
+ e=e,
207
+ wait_time=wait_time,
208
+ )
209
+ return None
210
+ else:
211
+ print("Max retries for OpenAI API server error reached. Raising error.")
212
+ raise e
213
+
214
+
215
+ def _handle_client_error(e, status_code):
216
+ print(
217
+ tr(
218
+ "OpenAI API client error {status_code}: {e}. Not retrying.",
219
+ status_code=status_code,
179
220
  e=e,
180
- wait_time=wait_time,
181
221
  )
222
+ )
223
+ raise e
224
+
225
+
226
+ def _handle_generic_error(e, attempt, max_retries, status):
227
+ wait_time = 2**attempt
228
+ if attempt < max_retries:
229
+ if status is not None:
230
+ _log_and_sleep(
231
+ "OpenAI API error (attempt {attempt}/{max_retries}): {e}. Retrying in {wait_time} seconds...",
232
+ attempt,
233
+ max_retries,
234
+ e=e,
235
+ wait_time=wait_time,
236
+ status=status,
237
+ waiting_message="Waiting after error...",
238
+ restore_message="Waiting for AI response...",
239
+ )
240
+ else:
241
+ _log_and_sleep(
242
+ "OpenAI API error (attempt {attempt}/{max_retries}): {e}. Retrying in {wait_time} seconds...",
243
+ attempt,
244
+ max_retries,
245
+ e=e,
246
+ wait_time=wait_time,
247
+ )
182
248
  print(f"[DEBUG] Exception repr: {repr(e)}")
183
249
  return None
184
250
  else:
@@ -186,8 +252,30 @@ def _handle_general_exception(e, attempt, max_retries):
186
252
  raise e
187
253
 
188
254
 
255
+ def _handle_general_exception(e, attempt, max_retries, status=None):
256
+ error_message = str(e)
257
+ _handle_no_tool_support(error_message)
258
+ status_code, retry_after = _extract_status_and_retry_after(e, error_message)
259
+ if status_code is not None:
260
+ if status_code == 429:
261
+ return _handle_rate_limit(
262
+ e, attempt, max_retries, status, status_code, retry_after
263
+ )
264
+ elif 500 <= status_code < 600:
265
+ return _handle_server_error(e, attempt, max_retries, status, status_code)
266
+ elif 400 <= status_code < 500:
267
+ _handle_client_error(e, status_code)
268
+ return _handle_generic_error(e, attempt, max_retries, status)
269
+
270
+
189
271
  def retry_api_call(
190
- api_func, max_retries=5, *args, history=None, user_message_on_empty=None, **kwargs
272
+ api_func,
273
+ max_retries=5,
274
+ *args,
275
+ history=None,
276
+ user_message_on_empty=None,
277
+ status=None,
278
+ **kwargs,
191
279
  ):
192
280
  for attempt in range(1, max_retries + 1):
193
281
  try:
@@ -209,10 +297,10 @@ def retry_api_call(
209
297
  else:
210
298
  raise
211
299
  except json.JSONDecodeError as e:
212
- result = _handle_json_decode_error(e, attempt, max_retries)
300
+ result = _handle_json_decode_error(e, attempt, max_retries, status=status)
213
301
  if result is not None:
214
302
  return result
215
303
  except Exception as e:
216
- result = _handle_general_exception(e, attempt, max_retries)
304
+ result = _handle_general_exception(e, attempt, max_retries, status=status)
217
305
  if result is not None:
218
306
  return result
@@ -8,7 +8,7 @@ from rich.console import Console
8
8
  def show_spinner(message, func, *args, **kwargs):
9
9
  console = Console()
10
10
  with console.status(message, spinner="dots") as status:
11
- result = func(*args, **kwargs)
11
+ result = func(*args, status=status, **kwargs)
12
12
  status.stop()
13
13
  return result
14
14
 
@@ -68,3 +68,15 @@ class LLMConversationHistory:
68
68
 
69
69
  def __getitem__(self, idx):
70
70
  return self._messages[idx]
71
+
72
+ def remove_last_message(self):
73
+ """Remove and return the last message in the history, or None if empty."""
74
+ if self._messages:
75
+ return self._messages.pop()
76
+ return None
77
+
78
+ def last_message(self):
79
+ """Return the last message in the history, or None if empty."""
80
+ if self._messages:
81
+ return self._messages[-1]
82
+ return None
@@ -1,15 +1,30 @@
1
+ {# General role setup
2
+ ex. "Search in code" -> Python Developer -> find(*.py) | Java Developer -> find(*.java)
3
+ #}
1
4
  You are: {{ role }}
2
5
 
6
+ {# Improves tool selection and platform specific constrains, eg, path format, C:\ vs /path #}
3
7
  You will be developing and testing in the following environment:
4
8
  Platform: {{ platform }}
5
9
  Python version: {{ python_version }}
6
10
  Shell/Environment: {{ shell_info }}
7
11
 
8
12
  Respond according to the following guidelines:
9
- - Before answering check for files that might be related to the question
13
+ {# Exploratory hint #}
14
+ - Before answering to the user, explore the content related to the question
15
+ {# Define exploration order, prefers search/outline, reduces chunking roundtip #}
16
+ - When exploring full files content, provide empty range to read the entire files instead of chunked reads
17
+ {# Prefix tools with purpose for user awarnesses #}
10
18
  - Before using your namespace functions, provide a concise explanation.
11
- - When planning to get the entire file content, set the range line numbers to None
12
- - Do not provide the code in the messages, use the namespace functions to provide the code changes.
19
+ {# Reduce unrequest code verbosity overhead #}
20
+ - Use the namespace functions to deliver the code changes instead of showing the code.
21
+ {# Drive edit mode, place holders critical as shown to be crucial to avoid corruption with code placeholders #}
13
22
  - Prefer making localized edits using string replacements. If the required change is extensive, replace the entire file instead, provide full content without placeholders.
23
+ {# Trying to prevent surrogates generation, found this frequently in gpt4.1/windows #}
24
+ - While writing code, if you need an emoji or special Unicode character in a string, then insert the actual character (e.g., 📖) directly instead of using surrogate pairs or escape sequences.
25
+ {# Without this, the LLM choses to create files from a literal interpretation of the purpose and intention #}
14
26
  - Before creating files search the code for the location related to the file purpose
15
- - Once development or updates are finished, ensure that all new or updated packages, modules, functions, and methods
27
+ {# This will trigger a search for the old names/locations to be updates #}
28
+ - After moving, removing or renaming functions or classes to different modules, update all imports, references, tests, and documentation to reflect the new locations, then verify functionality.
29
+ {# Keeping docstrings update is key to have semanatic match between prompts and code #}
30
+ - Once development or updates are finished, ensure that new or updated packages, modules, functions are properly documented.
@@ -3,6 +3,7 @@ from . import create_directory
3
3
  from . import create_file
4
4
  from . import replace_file
5
5
  from . import fetch_url
6
+ from . import open_url
6
7
  from . import find_files
7
8
  from . import get_lines
8
9
  from .get_file_outline import core # noqa: F401,F811
@@ -25,6 +26,7 @@ __all__ = [
25
26
  "create_directory",
26
27
  "create_file",
27
28
  "fetch_url",
29
+ "open_url",
28
30
  "find_files",
29
31
  "GetFileOutlineTool",
30
32
  "get_lines",
@@ -26,7 +26,7 @@ class CreateDirectoryTool(ToolBase):
26
26
  disp_path = display_path(file_path)
27
27
  self.report_info(
28
28
  ActionType.WRITE,
29
- tr("📁 Creating directory '{disp_path}' ...", disp_path=disp_path),
29
+ tr("📁 Create directory '{disp_path}' ...", disp_path=disp_path),
30
30
  )
31
31
  try:
32
32
  if os.path.exists(file_path):
@@ -45,7 +45,7 @@ class CreateFileTool(ToolBase):
45
45
  os.makedirs(dir_name, exist_ok=True)
46
46
  self.report_info(
47
47
  ActionType.WRITE,
48
- tr("📝 Creating file '{disp_path}' ...", disp_path=disp_path),
48
+ tr("📝 Create file '{disp_path}' ...", disp_path=disp_path),
49
49
  )
50
50
  with open(file_path, "w", encoding="utf-8", errors="replace") as f:
51
51
  f.write(content)
@@ -25,7 +25,7 @@ class FetchUrlTool(ToolBase):
25
25
  if not url.strip():
26
26
  self.report_warning(tr("ℹ️ Empty URL provided."))
27
27
  return tr("Warning: Empty URL provided. Operation skipped.")
28
- self.report_info(ActionType.READ, tr("🌐 Fetching URL '{url}' ...", url=url))
28
+ self.report_info(ActionType.READ, tr("🌐 Fetch URL '{url}' ...", url=url))
29
29
  try:
30
30
  response = requests.get(url, timeout=10)
31
31
  response.raise_for_status()
@@ -25,6 +25,26 @@ class FindFilesTool(ToolBase):
25
25
  If max_results is reached, appends a note to the output.
26
26
  """
27
27
 
28
+ def _match_directories(self, root, dirs, pat):
29
+ dir_output = set()
30
+ dir_pat = pat.rstrip("/\\")
31
+ for d in dirs:
32
+ if fnmatch.fnmatch(d, dir_pat):
33
+ dir_output.add(os.path.join(root, d) + os.sep)
34
+ return dir_output
35
+
36
+ def _match_files(self, root, files, pat):
37
+ file_output = set()
38
+ for filename in fnmatch.filter(files, pat):
39
+ file_output.add(os.path.join(root, filename))
40
+ return file_output
41
+
42
+ def _match_dirs_without_slash(self, root, dirs, pat):
43
+ dir_output = set()
44
+ for d in fnmatch.filter(dirs, pat):
45
+ dir_output.add(os.path.join(root, d))
46
+ return dir_output
47
+
28
48
  def run(self, paths: str, pattern: str, max_depth: int = None) -> str:
29
49
  if not pattern:
30
50
  self.report_warning(tr("ℹ️ Empty file pattern provided."))
@@ -41,7 +61,7 @@ class FindFilesTool(ToolBase):
41
61
  self.report_info(
42
62
  ActionType.READ,
43
63
  tr(
44
- "🔍 Searching for files '{pattern}' in '{disp_path}'{depth_msg} ...",
64
+ "🔍 Search for files '{pattern}' in '{disp_path}'{depth_msg} ...",
45
65
  pattern=pattern,
46
66
  disp_path=disp_path,
47
67
  depth_msg=depth_msg,
@@ -52,19 +72,13 @@ class FindFilesTool(ToolBase):
52
72
  directory, max_depth=max_depth
53
73
  ):
54
74
  for pat in patterns:
55
- # Directory matching: pattern ends with '/' or '\'
56
75
  if pat.endswith("/") or pat.endswith("\\"):
57
- dir_pat = pat.rstrip("/\\")
58
- for d in dirs:
59
- if fnmatch.fnmatch(d, dir_pat):
60
- dir_output.add(os.path.join(root, d) + os.sep)
76
+ dir_output.update(self._match_directories(root, dirs, pat))
61
77
  else:
62
- # Match files
63
- for filename in fnmatch.filter(files, pat):
64
- dir_output.add(os.path.join(root, filename))
65
- # Also match directories (without trailing slash)
66
- for d in fnmatch.filter(dirs, pat):
67
- dir_output.add(os.path.join(root, d))
78
+ dir_output.update(self._match_files(root, files, pat))
79
+ dir_output.update(
80
+ self._match_dirs_without_slash(root, dirs, pat)
81
+ )
68
82
  self.report_success(
69
83
  tr(
70
84
  " ✅ {count} {file_word}",
@@ -72,7 +86,6 @@ class FindFilesTool(ToolBase):
72
86
  file_word=pluralize("file", len(dir_output)),
73
87
  )
74
88
  )
75
- # If searching in '.', strip leading './' from results
76
89
  if directory.strip() == ".":
77
90
  dir_output = {
78
91
  p[2:] if (p.startswith("./") or p.startswith(".\\")) else p
@@ -23,7 +23,7 @@ class GetFileOutlineTool(ToolBase):
23
23
  self.report_info(
24
24
  ActionType.READ,
25
25
  tr(
26
- "📄 Outlining file '{disp_path}' ...",
26
+ "📄 Outline file '{disp_path}' ...",
27
27
  disp_path=display_path(file_path),
28
28
  ),
29
29
  )