janito 1.14.3__py3-none-any.whl → 2.0.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 (283) hide show
  1. janito/__init__.py +6 -1
  2. janito/__main__.py +1 -1
  3. janito/agent/setup_agent.py +139 -0
  4. janito/agent/templates/profiles/{system_prompt_template_base.txt.j2 → system_prompt_template_main.txt.j2} +1 -1
  5. janito/cli/__init__.py +9 -0
  6. janito/cli/chat_mode/bindings.py +37 -0
  7. janito/cli/chat_mode/chat_entry.py +23 -0
  8. janito/cli/chat_mode/prompt_style.py +19 -0
  9. janito/cli/chat_mode/session.py +272 -0
  10. janito/{shell/prompt/completer.py → cli/chat_mode/shell/autocomplete.py} +7 -6
  11. janito/cli/chat_mode/shell/commands/__init__.py +55 -0
  12. janito/cli/chat_mode/shell/commands/base.py +9 -0
  13. janito/cli/chat_mode/shell/commands/clear.py +12 -0
  14. janito/{shell → cli/chat_mode/shell}/commands/conversation_restart.py +34 -30
  15. janito/cli/chat_mode/shell/commands/edit.py +25 -0
  16. janito/cli/chat_mode/shell/commands/help.py +16 -0
  17. janito/cli/chat_mode/shell/commands/history_view.py +93 -0
  18. janito/cli/chat_mode/shell/commands/lang.py +25 -0
  19. janito/cli/chat_mode/shell/commands/last.py +137 -0
  20. janito/cli/chat_mode/shell/commands/livelogs.py +49 -0
  21. janito/cli/chat_mode/shell/commands/multi.py +51 -0
  22. janito/cli/chat_mode/shell/commands/prompt.py +64 -0
  23. janito/cli/chat_mode/shell/commands/role.py +36 -0
  24. janito/cli/chat_mode/shell/commands/session.py +40 -0
  25. janito/{shell → cli/chat_mode/shell}/commands/session_control.py +2 -2
  26. janito/cli/chat_mode/shell/commands/termweb_log.py +92 -0
  27. janito/cli/chat_mode/shell/commands/tools.py +32 -0
  28. janito/{shell → cli/chat_mode/shell}/commands/utility.py +4 -7
  29. janito/{shell → cli/chat_mode/shell}/commands/verbose.py +5 -5
  30. janito/cli/chat_mode/shell/session/__init__.py +1 -0
  31. janito/{shell → cli/chat_mode/shell}/session/manager.py +9 -1
  32. janito/cli/chat_mode/toolbar.py +90 -0
  33. janito/cli/cli_commands/list_models.py +35 -0
  34. janito/cli/cli_commands/list_providers.py +9 -0
  35. janito/cli/cli_commands/list_tools.py +53 -0
  36. janito/cli/cli_commands/model_selection.py +50 -0
  37. janito/cli/cli_commands/model_utils.py +84 -0
  38. janito/cli/cli_commands/set_api_key.py +19 -0
  39. janito/cli/cli_commands/show_config.py +51 -0
  40. janito/cli/cli_commands/show_system_prompt.py +62 -0
  41. janito/cli/config.py +28 -0
  42. janito/cli/console.py +3 -0
  43. janito/cli/core/__init__.py +4 -0
  44. janito/cli/core/event_logger.py +59 -0
  45. janito/cli/core/getters.py +31 -0
  46. janito/cli/core/runner.py +141 -0
  47. janito/cli/core/setters.py +174 -0
  48. janito/cli/core/unsetters.py +54 -0
  49. janito/cli/main.py +8 -196
  50. janito/cli/main_cli.py +312 -0
  51. janito/cli/prompt_core.py +230 -0
  52. janito/cli/prompt_handler.py +6 -0
  53. janito/cli/rich_terminal_reporter.py +101 -0
  54. janito/cli/single_shot_mode/__init__.py +6 -0
  55. janito/cli/single_shot_mode/handler.py +137 -0
  56. janito/cli/termweb_starter.py +73 -24
  57. janito/cli/utils.py +25 -0
  58. janito/cli/verbose_output.py +196 -0
  59. janito/config.py +5 -0
  60. janito/config_manager.py +110 -0
  61. janito/conversation_history.py +30 -0
  62. janito/{agent/tools_utils/dir_walk_utils.py → dir_walk_utils.py} +3 -2
  63. janito/driver_events.py +98 -0
  64. janito/drivers/anthropic/driver.py +113 -0
  65. janito/drivers/azure_openai/driver.py +36 -0
  66. janito/drivers/driver_registry.py +33 -0
  67. janito/drivers/google_genai/driver.py +54 -0
  68. janito/drivers/google_genai/schema_generator.py +67 -0
  69. janito/drivers/mistralai/driver.py +41 -0
  70. janito/drivers/openai/driver.py +334 -0
  71. janito/event_bus/__init__.py +2 -0
  72. janito/event_bus/bus.py +68 -0
  73. janito/event_bus/event.py +15 -0
  74. janito/event_bus/handler.py +31 -0
  75. janito/event_bus/queue_bus.py +57 -0
  76. janito/exceptions.py +23 -0
  77. janito/formatting_token.py +54 -0
  78. janito/i18n/pt.py +1 -0
  79. janito/llm/__init__.py +5 -0
  80. janito/llm/agent.py +443 -0
  81. janito/llm/auth.py +62 -0
  82. janito/llm/driver.py +239 -0
  83. janito/llm/driver_config.py +34 -0
  84. janito/llm/driver_config_builder.py +34 -0
  85. janito/llm/driver_input.py +12 -0
  86. janito/llm/message_parts.py +60 -0
  87. janito/llm/model.py +38 -0
  88. janito/llm/provider.py +187 -0
  89. janito/perf_singleton.py +3 -0
  90. janito/performance_collector.py +167 -0
  91. janito/provider_config.py +98 -0
  92. janito/provider_registry.py +152 -0
  93. janito/providers/__init__.py +7 -0
  94. janito/providers/anthropic/model_info.py +22 -0
  95. janito/providers/anthropic/provider.py +65 -0
  96. janito/providers/azure_openai/model_info.py +15 -0
  97. janito/providers/azure_openai/provider.py +72 -0
  98. janito/providers/deepseek/__init__.py +1 -0
  99. janito/providers/deepseek/model_info.py +16 -0
  100. janito/providers/deepseek/provider.py +91 -0
  101. janito/providers/google/__init__.py +1 -0
  102. janito/providers/google/model_info.py +40 -0
  103. janito/providers/google/provider.py +69 -0
  104. janito/providers/mistralai/model_info.py +37 -0
  105. janito/providers/mistralai/provider.py +69 -0
  106. janito/providers/openai/__init__.py +1 -0
  107. janito/providers/openai/model_info.py +137 -0
  108. janito/providers/openai/provider.py +107 -0
  109. janito/providers/openai/schema_generator.py +63 -0
  110. janito/providers/provider_static_info.py +21 -0
  111. janito/providers/registry.py +26 -0
  112. janito/report_events.py +38 -0
  113. janito/termweb/app.py +1 -1
  114. janito/tools/__init__.py +16 -0
  115. janito/tools/adapters/__init__.py +1 -0
  116. janito/tools/adapters/local/__init__.py +54 -0
  117. janito/tools/adapters/local/adapter.py +92 -0
  118. janito/{agent/tools → tools/adapters/local}/ask_user.py +30 -13
  119. janito/tools/adapters/local/copy_file.py +84 -0
  120. janito/{agent/tools → tools/adapters/local}/create_directory.py +11 -10
  121. janito/tools/adapters/local/create_file.py +82 -0
  122. janito/tools/adapters/local/delete_text_in_file.py +136 -0
  123. janito/{agent/tools → tools/adapters/local}/fetch_url.py +18 -19
  124. janito/tools/adapters/local/find_files.py +140 -0
  125. janito/tools/adapters/local/get_file_outline/core.py +151 -0
  126. janito/{agent/tools → tools/adapters/local}/get_file_outline/python_outline.py +125 -0
  127. janito/tools/adapters/local/get_file_outline/python_outline_v2.py +156 -0
  128. janito/{agent/tools → tools/adapters/local}/get_file_outline/search_outline.py +12 -7
  129. janito/{agent/tools → tools/adapters/local}/move_file.py +13 -9
  130. janito/tools/adapters/local/open_html_in_browser.py +34 -0
  131. janito/{agent/tools → tools/adapters/local}/open_url.py +7 -5
  132. janito/tools/adapters/local/python_code_run.py +165 -0
  133. janito/tools/adapters/local/python_command_run.py +163 -0
  134. janito/tools/adapters/local/python_file_run.py +162 -0
  135. janito/{agent/tools → tools/adapters/local}/remove_directory.py +15 -9
  136. janito/{agent/tools → tools/adapters/local}/remove_file.py +17 -14
  137. janito/{agent/tools → tools/adapters/local}/replace_text_in_file.py +27 -22
  138. janito/tools/adapters/local/run_bash_command.py +176 -0
  139. janito/tools/adapters/local/run_powershell_command.py +219 -0
  140. janito/{agent/tools → tools/adapters/local}/search_text/core.py +32 -12
  141. janito/{agent/tools → tools/adapters/local}/search_text/match_lines.py +13 -4
  142. janito/{agent/tools → tools/adapters/local}/search_text/pattern_utils.py +12 -4
  143. janito/{agent/tools → tools/adapters/local}/search_text/traverse_directory.py +15 -2
  144. janito/{agent/tools → tools/adapters/local}/validate_file_syntax/core.py +12 -11
  145. janito/{agent/tools → tools/adapters/local}/validate_file_syntax/css_validator.py +1 -1
  146. janito/{agent/tools → tools/adapters/local}/validate_file_syntax/html_validator.py +1 -1
  147. janito/{agent/tools → tools/adapters/local}/validate_file_syntax/js_validator.py +1 -1
  148. janito/{agent/tools → tools/adapters/local}/validate_file_syntax/json_validator.py +1 -1
  149. janito/{agent/tools → tools/adapters/local}/validate_file_syntax/markdown_validator.py +1 -1
  150. janito/{agent/tools → tools/adapters/local}/validate_file_syntax/ps1_validator.py +1 -1
  151. janito/{agent/tools → tools/adapters/local}/validate_file_syntax/python_validator.py +1 -1
  152. janito/{agent/tools → tools/adapters/local}/validate_file_syntax/xml_validator.py +1 -1
  153. janito/{agent/tools → tools/adapters/local}/validate_file_syntax/yaml_validator.py +1 -1
  154. janito/{agent/tools/get_lines.py → tools/adapters/local/view_file.py} +45 -27
  155. janito/tools/inspect_registry.py +17 -0
  156. janito/tools/tool_base.py +105 -0
  157. janito/tools/tool_events.py +58 -0
  158. janito/tools/tool_run_exception.py +12 -0
  159. janito/{agent → tools}/tool_use_tracker.py +2 -4
  160. janito/{agent/tools_utils/utils.py → tools/tool_utils.py} +18 -9
  161. janito/tools/tools_adapter.py +207 -0
  162. janito/tools/tools_schema.py +104 -0
  163. janito/utils.py +11 -0
  164. janito/version.py +4 -0
  165. janito-2.0.1.dist-info/METADATA +232 -0
  166. janito-2.0.1.dist-info/RECORD +181 -0
  167. janito/agent/__init__.py +0 -0
  168. janito/agent/api_exceptions.py +0 -4
  169. janito/agent/config.py +0 -147
  170. janito/agent/config_defaults.py +0 -12
  171. janito/agent/config_utils.py +0 -0
  172. janito/agent/content_handler.py +0 -0
  173. janito/agent/conversation.py +0 -238
  174. janito/agent/conversation_api.py +0 -306
  175. janito/agent/conversation_exceptions.py +0 -18
  176. janito/agent/conversation_tool_calls.py +0 -39
  177. janito/agent/conversation_ui.py +0 -17
  178. janito/agent/event.py +0 -24
  179. janito/agent/event_dispatcher.py +0 -24
  180. janito/agent/event_handler_protocol.py +0 -5
  181. janito/agent/event_system.py +0 -15
  182. janito/agent/llm_conversation_history.py +0 -82
  183. janito/agent/message_handler.py +0 -20
  184. janito/agent/message_handler_protocol.py +0 -5
  185. janito/agent/openai_client.py +0 -149
  186. janito/agent/openai_schema_generator.py +0 -187
  187. janito/agent/profile_manager.py +0 -96
  188. janito/agent/queued_message_handler.py +0 -50
  189. janito/agent/rich_live.py +0 -32
  190. janito/agent/rich_message_handler.py +0 -115
  191. janito/agent/runtime_config.py +0 -36
  192. janito/agent/test_handler_protocols.py +0 -47
  193. janito/agent/test_openai_schema_generator.py +0 -93
  194. janito/agent/tests/__init__.py +0 -1
  195. janito/agent/tool_base.py +0 -63
  196. janito/agent/tool_executor.py +0 -122
  197. janito/agent/tool_registry.py +0 -49
  198. janito/agent/tools/__init__.py +0 -47
  199. janito/agent/tools/create_file.py +0 -59
  200. janito/agent/tools/delete_text_in_file.py +0 -97
  201. janito/agent/tools/find_files.py +0 -106
  202. janito/agent/tools/get_file_outline/core.py +0 -81
  203. janito/agent/tools/present_choices.py +0 -64
  204. janito/agent/tools/python_command_runner.py +0 -201
  205. janito/agent/tools/python_file_runner.py +0 -199
  206. janito/agent/tools/python_stdin_runner.py +0 -208
  207. janito/agent/tools/replace_file.py +0 -72
  208. janito/agent/tools/run_bash_command.py +0 -218
  209. janito/agent/tools/run_powershell_command.py +0 -251
  210. janito/agent/tools_utils/__init__.py +0 -1
  211. janito/agent/tools_utils/action_type.py +0 -7
  212. janito/agent/tools_utils/test_gitignore_utils.py +0 -46
  213. janito/cli/_livereload_log_utils.py +0 -13
  214. janito/cli/_print_config.py +0 -96
  215. janito/cli/_termweb_log_utils.py +0 -17
  216. janito/cli/_utils.py +0 -9
  217. janito/cli/arg_parser.py +0 -272
  218. janito/cli/cli_main.py +0 -281
  219. janito/cli/config_commands.py +0 -211
  220. janito/cli/config_runner.py +0 -35
  221. janito/cli/formatting_runner.py +0 -12
  222. janito/cli/livereload_starter.py +0 -60
  223. janito/cli/logging_setup.py +0 -38
  224. janito/cli/one_shot.py +0 -80
  225. janito/livereload/app.py +0 -25
  226. janito/rich_utils.py +0 -59
  227. janito/shell/__init__.py +0 -0
  228. janito/shell/commands/__init__.py +0 -61
  229. janito/shell/commands/config.py +0 -22
  230. janito/shell/commands/edit.py +0 -24
  231. janito/shell/commands/history_view.py +0 -18
  232. janito/shell/commands/lang.py +0 -19
  233. janito/shell/commands/livelogs.py +0 -42
  234. janito/shell/commands/prompt.py +0 -62
  235. janito/shell/commands/termweb_log.py +0 -94
  236. janito/shell/commands/tools.py +0 -26
  237. janito/shell/commands/track.py +0 -36
  238. janito/shell/main.py +0 -326
  239. janito/shell/prompt/load_prompt.py +0 -57
  240. janito/shell/prompt/session_setup.py +0 -57
  241. janito/shell/session/config.py +0 -109
  242. janito/shell/session/history.py +0 -0
  243. janito/shell/ui/interactive.py +0 -226
  244. janito/termweb/static/editor.css +0 -158
  245. janito/termweb/static/editor.css.bak +0 -145
  246. janito/termweb/static/editor.html +0 -46
  247. janito/termweb/static/editor.html.bak +0 -46
  248. janito/termweb/static/editor.js +0 -265
  249. janito/termweb/static/editor.js.bak +0 -259
  250. janito/termweb/static/explorer.html.bak +0 -59
  251. janito/termweb/static/favicon.ico +0 -0
  252. janito/termweb/static/favicon.ico.bak +0 -0
  253. janito/termweb/static/index.html +0 -53
  254. janito/termweb/static/index.html.bak +0 -54
  255. janito/termweb/static/index.html.bak.bak +0 -175
  256. janito/termweb/static/landing.html.bak +0 -36
  257. janito/termweb/static/termicon.svg +0 -1
  258. janito/termweb/static/termweb.css +0 -214
  259. janito/termweb/static/termweb.css.bak +0 -237
  260. janito/termweb/static/termweb.js +0 -162
  261. janito/termweb/static/termweb.js.bak +0 -168
  262. janito/termweb/static/termweb.js.bak.bak +0 -157
  263. janito/termweb/static/termweb_quickopen.js +0 -135
  264. janito/termweb/static/termweb_quickopen.js.bak +0 -125
  265. janito/tests/test_rich_utils.py +0 -44
  266. janito/web/__init__.py +0 -0
  267. janito/web/__main__.py +0 -25
  268. janito/web/app.py +0 -145
  269. janito-1.14.3.dist-info/METADATA +0 -313
  270. janito-1.14.3.dist-info/RECORD +0 -162
  271. janito-1.14.3.dist-info/licenses/LICENSE +0 -21
  272. /janito/{shell → cli/chat_mode/shell}/input_history.py +0 -0
  273. /janito/{shell/commands/session.py → cli/chat_mode/shell/session/history.py} +0 -0
  274. /janito/{agent/tools_utils/formatting.py → formatting.py} +0 -0
  275. /janito/{agent/tools_utils/gitignore_utils.py → gitignore_utils.py} +0 -0
  276. /janito/{agent/platform_discovery.py → platform_discovery.py} +0 -0
  277. /janito/{agent/tools → tools/adapters/local}/get_file_outline/__init__.py +0 -0
  278. /janito/{agent/tools → tools/adapters/local}/get_file_outline/markdown_outline.py +0 -0
  279. /janito/{agent/tools → tools/adapters/local}/search_text/__init__.py +0 -0
  280. /janito/{agent/tools → tools/adapters/local}/validate_file_syntax/__init__.py +0 -0
  281. {janito-1.14.3.dist-info → janito-2.0.1.dist-info}/WHEEL +0 -0
  282. {janito-1.14.3.dist-info → janito-2.0.1.dist-info}/entry_points.txt +0 -0
  283. {janito-1.14.3.dist-info → janito-2.0.1.dist-info}/top_level.txt +0 -0
@@ -1,14 +1,14 @@
1
- from janito.agent.tool_base import ToolBase
2
- from janito.agent.tools_utils.action_type import ActionType
3
- from janito.agent.tool_registry import register_tool
4
- from janito.agent.tools_utils.utils import pluralize, display_path
1
+ from janito.tools.tool_base import ToolBase
2
+ from janito.report_events import ReportAction
3
+ from janito.tools.adapters.local.adapter import register_local_tool
4
+ from janito.tools.tool_utils import pluralize, display_path
5
5
  from janito.i18n import tr
6
6
  import shutil
7
7
  import os
8
8
  import zipfile
9
9
 
10
10
 
11
- @register_tool(name="remove_directory")
11
+ @register_local_tool
12
12
  class RemoveDirectoryTool(ToolBase):
13
13
  """
14
14
  Remove a directory.
@@ -22,11 +22,13 @@ class RemoveDirectoryTool(ToolBase):
22
22
  - "Error removing directory: <error message>"
23
23
  """
24
24
 
25
+ tool_name = "remove_directory"
26
+
25
27
  def run(self, file_path: str, recursive: bool = False) -> str:
26
28
  disp_path = display_path(file_path)
27
- self.report_info(
28
- ActionType.WRITE,
29
+ self.report_action(
29
30
  tr("🗃️ Remove directory '{disp_path}' ...", disp_path=disp_path),
31
+ ReportAction.CREATE,
30
32
  )
31
33
  backup_zip = None
32
34
  try:
@@ -46,12 +48,16 @@ class RemoveDirectoryTool(ToolBase):
46
48
  else:
47
49
  os.rmdir(file_path)
48
50
  self.report_success(
49
- tr("✅ 1 {dir_word}", dir_word=pluralize("directory", 1))
51
+ tr("✅ 1 {dir_word}", dir_word=pluralize("directory", 1)),
52
+ ReportAction.CREATE,
50
53
  )
51
54
  msg = tr("Directory removed: {disp_path}", disp_path=disp_path)
52
55
  if backup_zip:
53
56
  msg += tr(" (backup at {backup_zip})", backup_zip=backup_zip)
54
57
  return msg
55
58
  except Exception as e:
56
- self.report_error(tr(" ❌ Error removing directory: {error}", error=e))
59
+ self.report_error(
60
+ tr(" ❌ Error removing directory: {error}", error=e),
61
+ ReportAction.REMOVE,
62
+ )
57
63
  return tr("Error removing directory: {error}", error=e)
@@ -1,15 +1,14 @@
1
1
  import os
2
2
  import shutil
3
- from janito.agent.tool_registry import register_tool
3
+ from janito.tools.adapters.local.adapter import register_local_tool
4
4
 
5
- # from janito.agent.tools_utils.expand_path import expand_path
6
- from janito.agent.tools_utils.utils import display_path
7
- from janito.agent.tool_base import ToolBase
8
- from janito.agent.tools_utils.action_type import ActionType
5
+ from janito.tools.tool_utils import display_path
6
+ from janito.tools.tool_base import ToolBase
7
+ from janito.report_events import ReportAction
9
8
  from janito.i18n import tr
10
9
 
11
10
 
12
- @register_tool(name="remove_file")
11
+ @register_local_tool
13
12
  class RemoveFileTool(ToolBase):
14
13
  """
15
14
  Remove a file at the specified path.
@@ -19,32 +18,34 @@ class RemoveFileTool(ToolBase):
19
18
  backup (bool, optional): If True, create a backup (.bak) before removing. Recommend using backup=True only in the first call to avoid redundant backups. Defaults to False.
20
19
  Returns:
21
20
  str: Status message indicating the result. Example:
22
- - "Successfully removed the file at ..."
23
- - "Cannot remove file: ..."
21
+ - " Successfully removed the file at ..."
22
+ - " Cannot remove file: ..."
24
23
  """
25
24
 
25
+ tool_name = "remove_file"
26
+
26
27
  def run(self, file_path: str, backup: bool = False) -> str:
27
28
  original_path = file_path
28
29
  path = file_path # Using file_path as is
29
30
  disp_path = display_path(original_path)
30
31
  backup_path = None
31
32
  # Report initial info about what is going to be removed
32
- self.report_info(
33
- ActionType.WRITE,
33
+ self.report_action(
34
34
  tr("🗑️ Remove file '{disp_path}' ...", disp_path=disp_path),
35
+ ReportAction.CREATE,
35
36
  )
36
37
  if not os.path.exists(path):
37
- self.report_error(tr("❌ File does not exist."))
38
+ self.report_error(tr("❌ File does not exist."), ReportAction.REMOVE)
38
39
  return tr("❌ File does not exist.")
39
40
  if not os.path.isfile(path):
40
- self.report_error(tr("❌ Path is not a file."))
41
+ self.report_error(tr("❌ Path is not a file."), ReportAction.REMOVE)
41
42
  return tr("❌ Path is not a file.")
42
43
  try:
43
44
  if backup:
44
45
  backup_path = path + ".bak"
45
46
  shutil.copy2(path, backup_path)
46
47
  os.remove(path)
47
- self.report_success(tr("✅ File removed"))
48
+ self.report_success(tr("✅ File removed"), ReportAction.CREATE)
48
49
  msg = tr(
49
50
  "✅ Successfully removed the file at '{disp_path}'.",
50
51
  disp_path=disp_path,
@@ -56,5 +57,7 @@ class RemoveFileTool(ToolBase):
56
57
  )
57
58
  return msg
58
59
  except Exception as e:
59
- self.report_error(tr("❌ Error removing file: {error}", error=e))
60
+ self.report_error(
61
+ tr("❌ Error removing file: {error}", error=e), ReportAction.REMOVE
62
+ )
60
63
  return tr("❌ Error removing file: {error}", error=e)
@@ -1,14 +1,13 @@
1
- from janito.agent.tool_base import ToolBase
2
- from janito.agent.tools_utils.action_type import ActionType
3
- from janito.agent.tool_registry import register_tool
1
+ from janito.tools.tool_base import ToolBase
2
+ from janito.report_events import ReportAction
3
+ from janito.tools.adapters.local.adapter import register_local_tool
4
4
  from janito.i18n import tr
5
5
  import shutil
6
6
  import re
7
+ from janito.tools.adapters.local.validate_file_syntax.core import validate_file_syntax
7
8
 
8
- from janito.agent.tools.validate_file_syntax.core import validate_file_syntax
9
9
 
10
-
11
- @register_tool(name="replace_text_in_file")
10
+ @register_local_tool
12
11
  class ReplaceTextInFileTool(ToolBase):
13
12
  """
14
13
  Replace exact occurrences of a given text in a file.
@@ -30,6 +29,8 @@ class ReplaceTextInFileTool(ToolBase):
30
29
  - "Error replacing text: <error message>"
31
30
  """
32
31
 
32
+ tool_name = "replace_text_in_file"
33
+
33
34
  def run(
34
35
  self,
35
36
  file_path: str,
@@ -38,7 +39,7 @@ class ReplaceTextInFileTool(ToolBase):
38
39
  replace_all: bool = False,
39
40
  backup: bool = False,
40
41
  ) -> str:
41
- from janito.agent.tools_utils.utils import display_path
42
+ from janito.tools.tool_utils import display_path
42
43
 
43
44
  disp_path = display_path(file_path)
44
45
  action = "(all)" if replace_all else ""
@@ -53,7 +54,7 @@ class ReplaceTextInFileTool(ToolBase):
53
54
  replacement_text,
54
55
  file_path,
55
56
  )
56
- self.report_info(ActionType.WRITE, info_msg)
57
+ self.report_action(info_msg, ReportAction.CREATE)
57
58
  try:
58
59
  content = self._read_file_content(file_path)
59
60
  match_lines = self._find_match_lines(content, search_text)
@@ -65,13 +66,15 @@ class ReplaceTextInFileTool(ToolBase):
65
66
  backup_path = file_path + ".bak"
66
67
  if backup and file_changed:
67
68
  self._backup_file(file_path, backup_path)
69
+ validation_result = ""
68
70
  if file_changed:
69
71
  self._write_file_content(file_path, new_content)
72
+ # Perform syntax validation and append result
73
+ validation_result = validate_file_syntax(file_path)
70
74
  warning, concise_warning = self._handle_warnings(
71
75
  replaced_count, file_changed, occurrences
72
76
  )
73
- if warning:
74
- self.report_warning(warning)
77
+
75
78
  if concise_warning:
76
79
  return concise_warning
77
80
  self._report_success(match_lines)
@@ -84,16 +87,11 @@ class ReplaceTextInFileTool(ToolBase):
84
87
  line_delta_str,
85
88
  replace_all,
86
89
  )
87
- final_msg = self._format_final_msg(
90
+ return self._format_final_msg(
88
91
  file_path, warning, backup_path, match_info, details
89
- )
90
- # Perform syntax validation and append result if file was changed
91
- if file_changed:
92
- validation_result = validate_file_syntax(file_path)
93
- final_msg += f"\n{validation_result}"
94
- return final_msg
92
+ ) + (f"\n{validation_result}" if validation_result else "")
95
93
  except Exception as e:
96
- self.report_error(tr(" ❌ Error"))
94
+ self.report_error(tr(" ❌ Error"), ReportAction.REPLACE)
97
95
  return tr("Error replacing text: {error}", error=e)
98
96
 
99
97
  def _read_file_content(self, file_path):
@@ -147,12 +145,16 @@ class ReplaceTextInFileTool(ToolBase):
147
145
  if replaced_count == 0:
148
146
  warning = tr(" [Warning: Search text not found in file]")
149
147
  if not file_changed:
150
- self.report_warning(tr(" ℹ️ No changes made. [not found]"))
148
+ self.report_warning(
149
+ tr(" ℹ️ No changes made. (not found)"), ReportAction.CREATE
150
+ )
151
151
  concise_warning = tr(
152
152
  "No changes made. The search text was not found. Expand your search context with surrounding lines if needed."
153
153
  )
154
154
  if occurrences > 1 and replaced_count == 0:
155
- self.report_warning(tr(" ℹ️ No changes made. [not unique]"))
155
+ self.report_warning(
156
+ tr(" ℹ️ No changes made. (not unique)"), ReportAction.CREATE
157
+ )
156
158
  concise_warning = tr(
157
159
  "No changes made. The search text is not unique. Expand your search context with surrounding lines to ensure uniqueness."
158
160
  )
@@ -162,9 +164,12 @@ class ReplaceTextInFileTool(ToolBase):
162
164
  """Report success with line numbers where replacements occurred."""
163
165
  if match_lines:
164
166
  lines_str = ", ".join(str(line_no) for line_no in match_lines)
165
- self.report_success(tr(" ✅ replaced at {lines_str}", lines_str=lines_str))
167
+ self.report_success(
168
+ tr(" ✅ replaced at {lines_str}", lines_str=lines_str),
169
+ ReportAction.CREATE,
170
+ )
166
171
  else:
167
- self.report_success(tr(" ✅ replaced (lines unknown)"))
172
+ self.report_success(tr(" ✅ replaced (lines unknown)"), ReportAction.CREATE)
168
173
 
169
174
  def _get_line_delta_str(self, content, new_content):
170
175
  """Return a string describing the net line change after replacement."""
@@ -0,0 +1,176 @@
1
+ from janito.tools.tool_base import ToolBase
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
+ import subprocess
6
+ import tempfile
7
+ import sys
8
+ import os
9
+ import threading
10
+
11
+
12
+ @register_local_tool
13
+ class RunBashCommandTool(ToolBase):
14
+ """
15
+ Execute a non-interactive command using the bash shell and capture live output.
16
+ This tool explicitly invokes the 'bash' shell (not just the system default shell), so it requires bash to be installed and available in the system PATH. On Windows, this will only work if bash is available (e.g., via WSL, Git Bash, or similar).
17
+
18
+ Parameters:
19
+ command (str): The bash command to execute.
20
+ timeout (int): Timeout in seconds for the command. Defaults to 60.
21
+ require_confirmation (bool): If True, require user confirmation before running. Defaults to False.
22
+ requires_user_input (bool): If True, warns that the command may require user input and might hang. Defaults to False. Non-interactive commands are preferred for automation and reliability.
23
+
24
+ Returns:
25
+ str: File paths and line counts for stdout and stderr.
26
+ """
27
+ provides_execution = True
28
+ tool_name = "run_bash_command"
29
+
30
+ def _stream_output(self, stream, file_obj, report_func, count_func, counter):
31
+ for line in stream:
32
+ file_obj.write(line)
33
+ file_obj.flush()
34
+ report_func(line.rstrip("\r\n"), ReportAction.EXECUTE)
35
+ if count_func == "stdout":
36
+ counter["stdout"] += 1
37
+ else:
38
+ counter["stderr"] += 1
39
+
40
+ def run(
41
+ self,
42
+ command: str,
43
+ timeout: int = 60,
44
+ require_confirmation: bool = False,
45
+ requires_user_input: bool = False,
46
+ ) -> str:
47
+ if not command.strip():
48
+ self.report_warning(tr("ℹ️ Empty command provided."), ReportAction.EXECUTE)
49
+ return tr("Warning: Empty command provided. Operation skipped.")
50
+ self.report_action(
51
+ tr("🖥️ Run bash command: {command} ...\n", command=command),
52
+ ReportAction.EXECUTE,
53
+ )
54
+ if requires_user_input:
55
+ self.report_warning(
56
+ tr(
57
+ "⚠️ Warning: This command might be interactive, require user input, and might hang."
58
+ ),
59
+ ReportAction.EXECUTE,
60
+ )
61
+ sys.stdout.flush()
62
+ try:
63
+ with (
64
+ tempfile.NamedTemporaryFile(
65
+ mode="w+", prefix="run_bash_stdout_", delete=False, encoding="utf-8"
66
+ ) as stdout_file,
67
+ tempfile.NamedTemporaryFile(
68
+ mode="w+", prefix="run_bash_stderr_", delete=False, encoding="utf-8"
69
+ ) as stderr_file,
70
+ ):
71
+ env = os.environ.copy()
72
+ env["PYTHONIOENCODING"] = "utf-8"
73
+ env["LC_ALL"] = "C.UTF-8"
74
+ env["LANG"] = "C.UTF-8"
75
+ process = subprocess.Popen(
76
+ ["bash", "-c", command],
77
+ stdout=subprocess.PIPE,
78
+ stderr=subprocess.PIPE,
79
+ text=True,
80
+ encoding="utf-8",
81
+ bufsize=1,
82
+ env=env,
83
+ )
84
+ counter = {"stdout": 0, "stderr": 0}
85
+ stdout_thread = threading.Thread(
86
+ target=self._stream_output,
87
+ args=(
88
+ process.stdout,
89
+ stdout_file,
90
+ self.report_stdout,
91
+ "stdout",
92
+ counter,
93
+ ),
94
+ )
95
+ stderr_thread = threading.Thread(
96
+ target=self._stream_output,
97
+ args=(
98
+ process.stderr,
99
+ stderr_file,
100
+ self.report_stderr,
101
+ "stderr",
102
+ counter,
103
+ ),
104
+ )
105
+ stdout_thread.start()
106
+ stderr_thread.start()
107
+ try:
108
+ return_code = process.wait(timeout=timeout)
109
+ except subprocess.TimeoutExpired:
110
+ process.kill()
111
+ self.report_error(
112
+ tr(
113
+ " ❌ Timed out after {timeout} seconds.",
114
+ timeout=timeout,
115
+ ),
116
+ ReportAction.EXECUTE,
117
+ )
118
+ return tr(
119
+ "Command timed out after {timeout} seconds.", timeout=timeout
120
+ )
121
+ stdout_thread.join()
122
+ stderr_thread.join()
123
+ stdout_file.flush()
124
+ stderr_file.flush()
125
+ self.report_success(
126
+ tr(
127
+ " ✅ return code {return_code}",
128
+ return_code=return_code,
129
+ ),
130
+ ReportAction.EXECUTE,
131
+ )
132
+ max_lines = 100
133
+ # Read back the output for summary
134
+ stdout_file.seek(0)
135
+ stderr_file.seek(0)
136
+ stdout_content = stdout_file.read()
137
+ stderr_content = stderr_file.read()
138
+ stdout_lines = counter["stdout"]
139
+ stderr_lines = counter["stderr"]
140
+ warning_msg = ""
141
+ if requires_user_input:
142
+ warning_msg = tr(
143
+ "⚠️ Warning: This command might be interactive, require user input, and might hang.\n"
144
+ )
145
+ if stdout_lines <= max_lines and stderr_lines <= max_lines:
146
+ result = warning_msg + tr(
147
+ "Return code: {return_code}\n--- STDOUT ---\n{stdout_content}",
148
+ return_code=return_code,
149
+ stdout_content=stdout_content,
150
+ )
151
+ if stderr_content.strip():
152
+ result += tr(
153
+ "\n--- STDERR ---\n{stderr_content}",
154
+ stderr_content=stderr_content,
155
+ )
156
+ return result
157
+ else:
158
+ result = warning_msg + tr(
159
+ "[LARGE OUTPUT]\nstdout_file: {stdout_file} (lines: {stdout_lines})\n",
160
+ stdout_file=stdout_file.name,
161
+ stdout_lines=stdout_lines,
162
+ )
163
+ if stderr_lines > 0:
164
+ result += tr(
165
+ "stderr_file: {stderr_file} (lines: {stderr_lines})\n",
166
+ stderr_file=stderr_file.name,
167
+ stderr_lines=stderr_lines,
168
+ )
169
+ result += tr(
170
+ "returncode: {return_code}\nUse the view_file tool to inspect the contents of these files when needed.",
171
+ return_code=return_code,
172
+ )
173
+ return result
174
+ except Exception as e:
175
+ self.report_error(tr(" ❌ Error: {error}", error=e), ReportAction.EXECUTE)
176
+ return tr("Error running command: {error}", error=e)
@@ -0,0 +1,219 @@
1
+ from janito.tools.tool_base import ToolBase
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
+ import subprocess
6
+ import os
7
+ import tempfile
8
+ import threading
9
+
10
+
11
+ @register_local_tool
12
+ class RunPowershellCommandTool(ToolBase):
13
+ """
14
+ Execute a non-interactive command using the PowerShell shell and capture live output.
15
+ This tool explicitly invokes 'powershell.exe' (on Windows) or 'pwsh' (on other platforms if available).
16
+ All commands are automatically prepended with UTF-8 output encoding:
17
+ $OutputEncoding = [Console]::OutputEncoding = [System.Text.Encoding]::UTF8;
18
+ For file output, it is recommended to use -Encoding utf8 in your PowerShell commands (e.g., Out-File -Encoding utf8) to ensure correct file encoding.
19
+
20
+ Parameters:
21
+ command (str): The PowerShell command to execute. This string is passed directly to PowerShell using the --Command argument (not as a script file).
22
+ timeout (int): Timeout in seconds for the command. Defaults to 60.
23
+ require_confirmation (bool): If True, require user confirmation before running. Defaults to False.
24
+ requires_user_input (bool): If True, warns that the command may require user input and might hang. Defaults to False. Non-interactive commands are preferred for automation and reliability.
25
+
26
+ Returns:
27
+ str: Output and status message, or file paths/line counts if output is large.
28
+ """
29
+ provides_execution = True
30
+ tool_name = "run_powershell_command"
31
+
32
+ def _confirm_and_warn(self, command, require_confirmation, requires_user_input):
33
+ if requires_user_input:
34
+ self.report_warning(
35
+ tr(
36
+ "⚠️ Warning: This command might be interactive, require user input, and might hang."
37
+ ),
38
+ ReportAction.EXECUTE,
39
+ )
40
+ if require_confirmation:
41
+ confirmed = self.ask_user_confirmation(
42
+ tr(
43
+ "About to run PowerShell command: {command}\nContinue?",
44
+ command=command,
45
+ )
46
+ )
47
+ if not confirmed:
48
+ self.report_warning(
49
+ tr("⚠️ Execution cancelled by user."), ReportAction.EXECUTE
50
+ )
51
+ return False
52
+ return True
53
+
54
+ def _launch_process(self, shell_exe, command_with_encoding):
55
+ env = os.environ.copy()
56
+ env["PYTHONIOENCODING"] = "utf-8"
57
+ return subprocess.Popen(
58
+ [
59
+ shell_exe,
60
+ "-NoProfile",
61
+ "-ExecutionPolicy",
62
+ "Bypass",
63
+ "-Command",
64
+ command_with_encoding,
65
+ ],
66
+ stdout=subprocess.PIPE,
67
+ stderr=subprocess.PIPE,
68
+ text=True,
69
+ bufsize=1,
70
+ universal_newlines=True,
71
+ encoding="utf-8",
72
+ env=env,
73
+ )
74
+
75
+ def _stream_output(self, stream, file_obj, report_func, count_func, counter):
76
+ for line in stream:
77
+ file_obj.write(line)
78
+ file_obj.flush()
79
+ report_func(line.rstrip("\r\n"), ReportAction.EXECUTE)
80
+ if count_func == "stdout":
81
+ counter["stdout"] += 1
82
+ else:
83
+ counter["stderr"] += 1
84
+
85
+ def _format_result(
86
+ self, requires_user_input, return_code, stdout_file, stderr_file, max_lines=100
87
+ ):
88
+ warning_msg = ""
89
+ if requires_user_input:
90
+ warning_msg = tr(
91
+ "⚠️ Warning: This command might be interactive, require user input, and might hang.\n"
92
+ )
93
+ with open(stdout_file.name, "r", encoding="utf-8", errors="replace") as out_f:
94
+ stdout_content = out_f.read()
95
+ with open(stderr_file.name, "r", encoding="utf-8", errors="replace") as err_f:
96
+ stderr_content = err_f.read()
97
+ stdout_lines = stdout_content.count("\n")
98
+ stderr_lines = stderr_content.count("\n")
99
+ if stdout_lines <= max_lines and stderr_lines <= max_lines:
100
+ result = warning_msg + tr(
101
+ "Return code: {return_code}\n--- STDOUT ---\n{stdout_content}",
102
+ return_code=return_code,
103
+ stdout_content=stdout_content,
104
+ )
105
+ if stderr_content.strip():
106
+ result += tr(
107
+ "\n--- STDERR ---\n{stderr_content}",
108
+ stderr_content=stderr_content,
109
+ )
110
+ return result
111
+ else:
112
+ result = warning_msg + tr(
113
+ "stdout_file: {stdout_file} (lines: {stdout_lines})\n",
114
+ stdout_file=stdout_file.name,
115
+ stdout_lines=stdout_lines,
116
+ )
117
+ if stderr_lines > 0 and stderr_content.strip():
118
+ result += tr(
119
+ "stderr_file: {stderr_file} (lines: {stderr_lines})\n",
120
+ stderr_file=stderr_file.name,
121
+ stderr_lines=stderr_lines,
122
+ )
123
+ result += tr(
124
+ "returncode: {return_code}\nUse the view_file tool to inspect the contents of these files when needed.",
125
+ return_code=return_code,
126
+ )
127
+ return result
128
+
129
+ def run(
130
+ self,
131
+ command: str,
132
+ timeout: int = 60,
133
+ require_confirmation: bool = False,
134
+ requires_user_input: bool = False,
135
+ ) -> str:
136
+ if not command.strip():
137
+ self.report_warning(tr("ℹ️ Empty command provided."), ReportAction.EXECUTE)
138
+ return tr("Warning: Empty command provided. Operation skipped.")
139
+ encoding_prefix = "$OutputEncoding = [Console]::OutputEncoding = [System.Text.Encoding]::UTF8; "
140
+ command_with_encoding = encoding_prefix + command
141
+ self.report_action(
142
+ tr("🖥️ Running PowerShell command: {command} ...\n", command=command),
143
+ ReportAction.EXECUTE,
144
+ )
145
+ if not self._confirm_and_warn(
146
+ command, require_confirmation, requires_user_input
147
+ ):
148
+ return tr("❌ Command execution cancelled by user.")
149
+ from janito.platform_discovery import PlatformDiscovery
150
+
151
+ pd = PlatformDiscovery()
152
+ shell_exe = "powershell.exe" if pd.is_windows() else "pwsh"
153
+ try:
154
+ with (
155
+ tempfile.NamedTemporaryFile(
156
+ mode="w+",
157
+ prefix="run_powershell_stdout_",
158
+ delete=False,
159
+ encoding="utf-8",
160
+ ) as stdout_file,
161
+ tempfile.NamedTemporaryFile(
162
+ mode="w+",
163
+ prefix="run_powershell_stderr_",
164
+ delete=False,
165
+ encoding="utf-8",
166
+ ) as stderr_file,
167
+ ):
168
+ process = self._launch_process(shell_exe, command_with_encoding)
169
+ counter = {"stdout": 0, "stderr": 0}
170
+ stdout_thread = threading.Thread(
171
+ target=self._stream_output,
172
+ args=(
173
+ process.stdout,
174
+ stdout_file,
175
+ self.report_stdout,
176
+ "stdout",
177
+ counter,
178
+ ),
179
+ )
180
+ stderr_thread = threading.Thread(
181
+ target=self._stream_output,
182
+ args=(
183
+ process.stderr,
184
+ stderr_file,
185
+ self.report_stderr,
186
+ "stderr",
187
+ counter,
188
+ ),
189
+ )
190
+ stdout_thread.start()
191
+ stderr_thread.start()
192
+ try:
193
+ return_code = process.wait(timeout=timeout)
194
+ except subprocess.TimeoutExpired:
195
+ process.kill()
196
+ self.report_error(
197
+ tr(
198
+ " ❌ Timed out after {timeout} seconds.",
199
+ timeout=timeout,
200
+ ),
201
+ ReportAction.EXECUTE,
202
+ )
203
+ return tr(
204
+ "Command timed out after {timeout} seconds.", timeout=timeout
205
+ )
206
+ stdout_thread.join()
207
+ stderr_thread.join()
208
+ stdout_file.flush()
209
+ stderr_file.flush()
210
+ self.report_success(
211
+ tr(" ✅ return code {return_code}", return_code=return_code),
212
+ ReportAction.EXECUTE,
213
+ )
214
+ return self._format_result(
215
+ requires_user_input, return_code, stdout_file, stderr_file
216
+ )
217
+ except Exception as e:
218
+ self.report_error(tr(" ❌ Error: {error}", error=e), ReportAction.EXECUTE)
219
+ return tr("Error running command: {error}", error=e)