janito 1.14.3__py3-none-any.whl → 2.0.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 (282) 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/{agent/tools → tools/adapters/local}/open_url.py +7 -5
  131. janito/tools/adapters/local/python_code_run.py +165 -0
  132. janito/tools/adapters/local/python_command_run.py +163 -0
  133. janito/tools/adapters/local/python_file_run.py +162 -0
  134. janito/{agent/tools → tools/adapters/local}/remove_directory.py +15 -9
  135. janito/{agent/tools → tools/adapters/local}/remove_file.py +17 -14
  136. janito/{agent/tools → tools/adapters/local}/replace_text_in_file.py +27 -22
  137. janito/tools/adapters/local/run_bash_command.py +176 -0
  138. janito/tools/adapters/local/run_powershell_command.py +219 -0
  139. janito/{agent/tools → tools/adapters/local}/search_text/core.py +32 -12
  140. janito/{agent/tools → tools/adapters/local}/search_text/match_lines.py +13 -4
  141. janito/{agent/tools → tools/adapters/local}/search_text/pattern_utils.py +12 -4
  142. janito/{agent/tools → tools/adapters/local}/search_text/traverse_directory.py +15 -2
  143. janito/{agent/tools → tools/adapters/local}/validate_file_syntax/core.py +12 -11
  144. janito/{agent/tools → tools/adapters/local}/validate_file_syntax/css_validator.py +1 -1
  145. janito/{agent/tools → tools/adapters/local}/validate_file_syntax/html_validator.py +1 -1
  146. janito/{agent/tools → tools/adapters/local}/validate_file_syntax/js_validator.py +1 -1
  147. janito/{agent/tools → tools/adapters/local}/validate_file_syntax/json_validator.py +1 -1
  148. janito/{agent/tools → tools/adapters/local}/validate_file_syntax/markdown_validator.py +1 -1
  149. janito/{agent/tools → tools/adapters/local}/validate_file_syntax/ps1_validator.py +1 -1
  150. janito/{agent/tools → tools/adapters/local}/validate_file_syntax/python_validator.py +1 -1
  151. janito/{agent/tools → tools/adapters/local}/validate_file_syntax/xml_validator.py +1 -1
  152. janito/{agent/tools → tools/adapters/local}/validate_file_syntax/yaml_validator.py +1 -1
  153. janito/{agent/tools/get_lines.py → tools/adapters/local/view_file.py} +45 -27
  154. janito/tools/inspect_registry.py +17 -0
  155. janito/tools/tool_base.py +105 -0
  156. janito/tools/tool_events.py +58 -0
  157. janito/tools/tool_run_exception.py +12 -0
  158. janito/{agent → tools}/tool_use_tracker.py +2 -4
  159. janito/{agent/tools_utils/utils.py → tools/tool_utils.py} +18 -9
  160. janito/tools/tools_adapter.py +207 -0
  161. janito/tools/tools_schema.py +104 -0
  162. janito/utils.py +11 -0
  163. janito/version.py +4 -0
  164. janito-2.0.0.dist-info/METADATA +232 -0
  165. janito-2.0.0.dist-info/RECORD +180 -0
  166. janito/agent/__init__.py +0 -0
  167. janito/agent/api_exceptions.py +0 -4
  168. janito/agent/config.py +0 -147
  169. janito/agent/config_defaults.py +0 -12
  170. janito/agent/config_utils.py +0 -0
  171. janito/agent/content_handler.py +0 -0
  172. janito/agent/conversation.py +0 -238
  173. janito/agent/conversation_api.py +0 -306
  174. janito/agent/conversation_exceptions.py +0 -18
  175. janito/agent/conversation_tool_calls.py +0 -39
  176. janito/agent/conversation_ui.py +0 -17
  177. janito/agent/event.py +0 -24
  178. janito/agent/event_dispatcher.py +0 -24
  179. janito/agent/event_handler_protocol.py +0 -5
  180. janito/agent/event_system.py +0 -15
  181. janito/agent/llm_conversation_history.py +0 -82
  182. janito/agent/message_handler.py +0 -20
  183. janito/agent/message_handler_protocol.py +0 -5
  184. janito/agent/openai_client.py +0 -149
  185. janito/agent/openai_schema_generator.py +0 -187
  186. janito/agent/profile_manager.py +0 -96
  187. janito/agent/queued_message_handler.py +0 -50
  188. janito/agent/rich_live.py +0 -32
  189. janito/agent/rich_message_handler.py +0 -115
  190. janito/agent/runtime_config.py +0 -36
  191. janito/agent/test_handler_protocols.py +0 -47
  192. janito/agent/test_openai_schema_generator.py +0 -93
  193. janito/agent/tests/__init__.py +0 -1
  194. janito/agent/tool_base.py +0 -63
  195. janito/agent/tool_executor.py +0 -122
  196. janito/agent/tool_registry.py +0 -49
  197. janito/agent/tools/__init__.py +0 -47
  198. janito/agent/tools/create_file.py +0 -59
  199. janito/agent/tools/delete_text_in_file.py +0 -97
  200. janito/agent/tools/find_files.py +0 -106
  201. janito/agent/tools/get_file_outline/core.py +0 -81
  202. janito/agent/tools/present_choices.py +0 -64
  203. janito/agent/tools/python_command_runner.py +0 -201
  204. janito/agent/tools/python_file_runner.py +0 -199
  205. janito/agent/tools/python_stdin_runner.py +0 -208
  206. janito/agent/tools/replace_file.py +0 -72
  207. janito/agent/tools/run_bash_command.py +0 -218
  208. janito/agent/tools/run_powershell_command.py +0 -251
  209. janito/agent/tools_utils/__init__.py +0 -1
  210. janito/agent/tools_utils/action_type.py +0 -7
  211. janito/agent/tools_utils/test_gitignore_utils.py +0 -46
  212. janito/cli/_livereload_log_utils.py +0 -13
  213. janito/cli/_print_config.py +0 -96
  214. janito/cli/_termweb_log_utils.py +0 -17
  215. janito/cli/_utils.py +0 -9
  216. janito/cli/arg_parser.py +0 -272
  217. janito/cli/cli_main.py +0 -281
  218. janito/cli/config_commands.py +0 -211
  219. janito/cli/config_runner.py +0 -35
  220. janito/cli/formatting_runner.py +0 -12
  221. janito/cli/livereload_starter.py +0 -60
  222. janito/cli/logging_setup.py +0 -38
  223. janito/cli/one_shot.py +0 -80
  224. janito/livereload/app.py +0 -25
  225. janito/rich_utils.py +0 -59
  226. janito/shell/__init__.py +0 -0
  227. janito/shell/commands/__init__.py +0 -61
  228. janito/shell/commands/config.py +0 -22
  229. janito/shell/commands/edit.py +0 -24
  230. janito/shell/commands/history_view.py +0 -18
  231. janito/shell/commands/lang.py +0 -19
  232. janito/shell/commands/livelogs.py +0 -42
  233. janito/shell/commands/prompt.py +0 -62
  234. janito/shell/commands/termweb_log.py +0 -94
  235. janito/shell/commands/tools.py +0 -26
  236. janito/shell/commands/track.py +0 -36
  237. janito/shell/main.py +0 -326
  238. janito/shell/prompt/load_prompt.py +0 -57
  239. janito/shell/prompt/session_setup.py +0 -57
  240. janito/shell/session/config.py +0 -109
  241. janito/shell/session/history.py +0 -0
  242. janito/shell/ui/interactive.py +0 -226
  243. janito/termweb/static/editor.css +0 -158
  244. janito/termweb/static/editor.css.bak +0 -145
  245. janito/termweb/static/editor.html +0 -46
  246. janito/termweb/static/editor.html.bak +0 -46
  247. janito/termweb/static/editor.js +0 -265
  248. janito/termweb/static/editor.js.bak +0 -259
  249. janito/termweb/static/explorer.html.bak +0 -59
  250. janito/termweb/static/favicon.ico +0 -0
  251. janito/termweb/static/favicon.ico.bak +0 -0
  252. janito/termweb/static/index.html +0 -53
  253. janito/termweb/static/index.html.bak +0 -54
  254. janito/termweb/static/index.html.bak.bak +0 -175
  255. janito/termweb/static/landing.html.bak +0 -36
  256. janito/termweb/static/termicon.svg +0 -1
  257. janito/termweb/static/termweb.css +0 -214
  258. janito/termweb/static/termweb.css.bak +0 -237
  259. janito/termweb/static/termweb.js +0 -162
  260. janito/termweb/static/termweb.js.bak +0 -168
  261. janito/termweb/static/termweb.js.bak.bak +0 -157
  262. janito/termweb/static/termweb_quickopen.js +0 -135
  263. janito/termweb/static/termweb_quickopen.js.bak +0 -125
  264. janito/tests/test_rich_utils.py +0 -44
  265. janito/web/__init__.py +0 -0
  266. janito/web/__main__.py +0 -25
  267. janito/web/app.py +0 -145
  268. janito-1.14.3.dist-info/METADATA +0 -313
  269. janito-1.14.3.dist-info/RECORD +0 -162
  270. janito-1.14.3.dist-info/licenses/LICENSE +0 -21
  271. /janito/{shell → cli/chat_mode/shell}/input_history.py +0 -0
  272. /janito/{shell/commands/session.py → cli/chat_mode/shell/session/history.py} +0 -0
  273. /janito/{agent/tools_utils/formatting.py → formatting.py} +0 -0
  274. /janito/{agent/tools_utils/gitignore_utils.py → gitignore_utils.py} +0 -0
  275. /janito/{agent/platform_discovery.py → platform_discovery.py} +0 -0
  276. /janito/{agent/tools → tools/adapters/local}/get_file_outline/__init__.py +0 -0
  277. /janito/{agent/tools → tools/adapters/local}/get_file_outline/markdown_outline.py +0 -0
  278. /janito/{agent/tools → tools/adapters/local}/search_text/__init__.py +0 -0
  279. /janito/{agent/tools → tools/adapters/local}/validate_file_syntax/__init__.py +0 -0
  280. {janito-1.14.3.dist-info → janito-2.0.0.dist-info}/WHEEL +0 -0
  281. {janito-1.14.3.dist-info → janito-2.0.0.dist-info}/entry_points.txt +0 -0
  282. {janito-1.14.3.dist-info → janito-2.0.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,38 @@
1
+ import attr
2
+ from typing import Any, ClassVar, Optional, Dict
3
+ from enum import Enum
4
+ from janito.event_bus.event import Event
5
+
6
+
7
+ class ReportSubtype(Enum):
8
+ ACTION_INFO = "action_info"
9
+ ERROR = "error"
10
+ SUCCESS = "success"
11
+ WARNING = "warning"
12
+ STDOUT = "stdout"
13
+ STDERR = "stderr"
14
+ PROGRESS = "progress"
15
+
16
+
17
+ class ReportAction(Enum):
18
+ READ = "READ"
19
+ CREATE = "CREATE"
20
+ DELETE = "DELETE"
21
+ UPDATE = "UPDATE"
22
+ EXECUTE = "EXECUTE"
23
+ # Add more as needed
24
+
25
+
26
+ @attr.s(auto_attribs=True, kw_only=True)
27
+ class ReportEvent(Event):
28
+ """
29
+ Event for reporting status, errors, warnings, and output.
30
+ Uses enums for subtype and action for type safety and clarity.
31
+ """
32
+
33
+ category: ClassVar[str] = "report"
34
+ subtype: ReportSubtype
35
+ message: str
36
+ action: Optional[ReportAction] = None
37
+ tool: Optional[str] = None
38
+ context: Optional[Dict[str, Any]] = None
janito/termweb/app.py CHANGED
@@ -1,6 +1,5 @@
1
1
  from quart import Quart, send_from_directory, request, jsonify, websocket
2
2
  import os
3
- from janito.agent.tools_utils.dir_walk_utils import walk_dir_with_gitignore
4
3
 
5
4
  app = Quart(__name__)
6
5
 
@@ -84,6 +83,7 @@ if __name__ == "__main__":
84
83
  import sys
85
84
 
86
85
  port = 8088
86
+ # Allow override from CLI for direct execution/testing
87
87
  if "--port" in sys.argv:
88
88
  idx = sys.argv.index("--port")
89
89
  if idx + 1 < len(sys.argv):
@@ -0,0 +1,16 @@
1
+ from janito.tools.adapters.local import (
2
+ local_tools_adapter as _internal_local_tools_adapter,
3
+ LocalToolsAdapter,
4
+ )
5
+
6
+
7
+ def get_local_tools_adapter():
8
+
9
+ # Use set_verbose_tools on the returned adapter to set verbosity as needed
10
+ return _internal_local_tools_adapter
11
+
12
+
13
+ __all__ = [
14
+ "LocalToolsAdapter",
15
+ "get_local_tools_adapter",
16
+ ]
@@ -0,0 +1 @@
1
+ # Tools providers package: for plug-and-play tool collections, integrations, and adapters.
@@ -0,0 +1,54 @@
1
+ from .adapter import LocalToolsAdapter
2
+
3
+ from .ask_user import AskUserTool
4
+ from .copy_file import CopyFileTool
5
+ from .create_directory import CreateDirectoryTool
6
+ from .create_file import CreateFileTool
7
+ from .fetch_url import FetchUrlTool
8
+ from .find_files import FindFilesTool
9
+ from .view_file import ViewFileTool
10
+ from .move_file import MoveFileTool
11
+ from .open_url import OpenUrlTool
12
+ from .python_code_run import PythonCodeRunTool
13
+ from .python_command_run import PythonCommandRunTool
14
+ from .python_file_run import PythonFileRunTool
15
+ from .remove_directory import RemoveDirectoryTool
16
+ from .remove_file import RemoveFileTool
17
+ from .replace_text_in_file import ReplaceTextInFileTool
18
+ from .run_bash_command import RunBashCommandTool
19
+ from .run_powershell_command import RunPowershellCommandTool
20
+ from .get_file_outline.core import GetFileOutlineTool
21
+ from .get_file_outline.search_outline import SearchOutlineTool
22
+ from .search_text.core import SearchTextTool
23
+ from .validate_file_syntax.core import ValidateFileSyntaxTool
24
+
25
+ # Singleton tools adapter with all standard tools registered
26
+ local_tools_adapter = LocalToolsAdapter()
27
+
28
+ # Register tools
29
+ for tool_class in [
30
+ AskUserTool,
31
+ CopyFileTool,
32
+ CreateDirectoryTool,
33
+ CreateFileTool,
34
+ FetchUrlTool,
35
+ FindFilesTool,
36
+ ViewFileTool,
37
+ MoveFileTool,
38
+ OpenUrlTool,
39
+ PythonCodeRunTool,
40
+ PythonCommandRunTool,
41
+ PythonFileRunTool,
42
+ RemoveDirectoryTool,
43
+ RemoveFileTool,
44
+ ReplaceTextInFileTool,
45
+ RunBashCommandTool,
46
+ RunPowershellCommandTool,
47
+ GetFileOutlineTool,
48
+ SearchOutlineTool,
49
+ SearchTextTool,
50
+ ValidateFileSyntaxTool,
51
+ ]:
52
+ local_tools_adapter.register_tool(tool_class)
53
+
54
+ # DEBUG: Print registered tools at startup
@@ -0,0 +1,92 @@
1
+ from typing import Type, Dict, Any
2
+ from janito.tools.tools_adapter import ToolsAdapterBase as ToolsAdapter
3
+
4
+
5
+ class LocalToolsAdapter(ToolsAdapter):
6
+ def disable_execution_tools(self):
7
+ """Unregister all tools with provides_execution = True."""
8
+ to_remove = [name for name, entry in self._tools.items()
9
+ if getattr(entry["instance"], "provides_execution", False)]
10
+ for name in to_remove:
11
+ self.unregister_tool(name)
12
+
13
+ """
14
+ Adapter for local, statically registered tools in the agent/tools system.
15
+ Handles registration, lookup, enabling/disabling, listing, and now, tool execution (merged from executor).
16
+ """
17
+
18
+ def __init__(self, tools=None, event_bus=None, allowed_tools=None):
19
+ super().__init__(tools=tools, event_bus=event_bus, allowed_tools=allowed_tools)
20
+ self._tools: Dict[str, Dict[str, Any]] = {}
21
+ if tools:
22
+ for tool in tools:
23
+ self.register_tool(tool)
24
+
25
+ def register_tool(self, tool_class: Type):
26
+ instance = tool_class()
27
+ if not hasattr(instance, "run") or not callable(instance.run):
28
+ raise TypeError(
29
+ f"Tool '{tool_class.__name__}' must implement a callable 'run' method."
30
+ )
31
+ tool_name = getattr(instance, "tool_name", None)
32
+ if not tool_name or not isinstance(tool_name, str):
33
+ raise ValueError(
34
+ f"Tool '{tool_class.__name__}' must provide a class attribute 'tool_name' (str) for its registration name."
35
+ )
36
+ if tool_name in self._tools:
37
+ raise ValueError(f"Tool '{tool_name}' is already registered.")
38
+ self._tools[tool_name] = {
39
+ "function": instance.run,
40
+ "class": tool_class,
41
+ "instance": instance,
42
+ }
43
+
44
+ def unregister_tool(self, name: str):
45
+ if name in self._tools:
46
+ del self._tools[name]
47
+
48
+ def disable_tool(self, name: str):
49
+ self.unregister_tool(name)
50
+
51
+ def get_tool(self, name: str):
52
+ return self._tools[name]["instance"] if name in self._tools else None
53
+
54
+ def list_tools(self):
55
+ return list(self._tools.keys())
56
+
57
+ def get_tool_classes(self):
58
+ return [entry["class"] for entry in self._tools.values()]
59
+
60
+ def get_tools(self):
61
+ return [entry["instance"] for entry in self._tools.values()]
62
+
63
+
64
+ def add_tool(self, tool):
65
+ # Register by instance (useful for hand-built objects)
66
+ if not hasattr(tool, "run") or not callable(tool.run):
67
+ raise TypeError(f"Tool '{tool}' must implement a callable 'run' method.")
68
+ tool_name = getattr(tool, "tool_name", None)
69
+ if not tool_name or not isinstance(tool_name, str):
70
+ raise ValueError(
71
+ f"Tool '{tool}' must provide a 'tool_name' (str) attribute."
72
+ )
73
+ if tool_name in self._tools:
74
+ raise ValueError(f"Tool '{tool_name}' is already registered.")
75
+ self._tools[tool_name] = {
76
+ "function": tool.run,
77
+ "class": tool.__class__,
78
+ "instance": tool,
79
+ }
80
+
81
+
82
+ # Optional: a local-tool decorator
83
+
84
+
85
+ def register_local_tool(tool=None):
86
+ def decorator(cls):
87
+ LocalToolsAdapter().register_tool(cls)
88
+ return cls
89
+
90
+ if tool is None:
91
+ return decorator
92
+ return decorator(tool)
@@ -1,5 +1,5 @@
1
- from janito.agent.tool_base import ToolBase
2
- from janito.agent.tool_registry import register_tool
1
+ from janito.tools.tool_base import ToolBase
2
+ from janito.tools.adapters.local.adapter import register_local_tool
3
3
 
4
4
  from rich import print as rich_print
5
5
  from janito.i18n import tr
@@ -8,16 +8,20 @@ from prompt_toolkit import PromptSession
8
8
  from prompt_toolkit.key_binding import KeyBindings
9
9
  from prompt_toolkit.enums import EditingMode
10
10
  from prompt_toolkit.formatted_text import HTML
11
+ from janito.cli.chat_mode.prompt_style import chat_shell_style
11
12
  from prompt_toolkit.styles import Style
12
13
 
14
+ toolbar_style = Style.from_dict({"bottom-toolbar": "fg:yellow bg:darkred"})
13
15
 
14
- @register_tool(name="ask_user")
16
+
17
+ @register_local_tool
15
18
  class AskUserTool(ToolBase):
16
19
  """
17
- Request clarification or input from the user whenever there is uncertainty, ambiguity, missing information, or multiple valid options. Returns the user's response as a string.
20
+ Prompts the user for clarification or input with a question.
18
21
 
19
22
  Args:
20
23
  question (str): The question to ask the user.
24
+
21
25
  Returns:
22
26
  str: The user's response as a string. Example:
23
27
  - "Yes"
@@ -25,8 +29,11 @@ class AskUserTool(ToolBase):
25
29
  - "Some detailed answer..."
26
30
  """
27
31
 
32
+ tool_name = "ask_user"
33
+
28
34
  def run(self, question: str) -> str:
29
35
 
36
+ print() # Print an empty line before the question panel
30
37
  rich_print(Panel.fit(question, title=tr("Question"), style="cyan"))
31
38
 
32
39
  bindings = KeyBindings()
@@ -55,13 +62,13 @@ class AskUserTool(ToolBase):
55
62
  buf.validate_and_handle()
56
63
  _f12_index["value"] = (idx + 1) % len(_f12_instructions)
57
64
 
58
- style = Style.from_dict(
59
- {
60
- "bottom-toolbar": "bg:#333333 #ffffff",
61
- "b": "bold",
62
- "prompt": "bold bg:#000080 #ffffff",
63
- }
64
- )
65
+ # Use shared CLI styles
66
+
67
+ # prompt_style contains the prompt area and input background
68
+ # toolbar_style contains the bottom-toolbar styling
69
+
70
+ # Use the shared chat_shell_style for input styling only
71
+ style = chat_shell_style
65
72
 
66
73
  def get_toolbar():
67
74
  f12_hint = ""
@@ -82,7 +89,7 @@ class AskUserTool(ToolBase):
82
89
  style=style,
83
90
  )
84
91
 
85
- prompt_icon = HTML("<prompt>💬 </prompt>")
92
+ prompt_icon = HTML("<inputline>💬 </inputline>")
86
93
 
87
94
  while True:
88
95
  response = session.prompt(prompt_icon)
@@ -95,4 +102,14 @@ class AskUserTool(ToolBase):
95
102
  session.multiline = False
96
103
  continue
97
104
  else:
98
- return response
105
+ sanitized = response.strip()
106
+ try:
107
+ sanitized.encode("utf-8")
108
+ except UnicodeEncodeError:
109
+ sanitized = sanitized.encode("utf-8", errors="replace").decode(
110
+ "utf-8"
111
+ )
112
+ rich_print(
113
+ "[yellow]Warning: Some characters in your input were not valid UTF-8 and have been replaced.[/yellow]"
114
+ )
115
+ return sanitized
@@ -0,0 +1,84 @@
1
+ import os
2
+ import shutil
3
+ from typing import List, Union
4
+ from janito.tools.adapters.local.adapter import register_local_tool
5
+ from janito.tools.tool_base import ToolBase
6
+ from janito.tools.tool_utils import display_path
7
+ from janito.report_events import ReportAction
8
+ from janito.i18n import tr
9
+
10
+
11
+ @register_local_tool
12
+ class CopyFileTool(ToolBase):
13
+ """
14
+ Copy one or more files to a target directory, or copy a single file to a new file.
15
+ Args:
16
+ sources (str): Space-separated path(s) to the file(s) to copy.
17
+ For multiple sources, provide a single string with paths separated by spaces.
18
+ target (str): Destination path. If copying multiple sources, this must be an existing directory.
19
+ overwrite (bool, optional): Overwrite existing files. Default: False.
20
+ Recommended only after reading the file to be overwritten.
21
+ Returns:
22
+ str: Status string for each copy operation.
23
+ """
24
+
25
+ tool_name = "copy_file"
26
+
27
+ def run(self, sources: str, target: str, overwrite: bool = False) -> str:
28
+ source_list = [src for src in sources.split() if src]
29
+ messages = []
30
+ if len(source_list) > 1:
31
+ if not os.path.isdir(target):
32
+ return tr(
33
+ "❗ Target must be an existing directory when copying multiple files: '{target}'",
34
+ target=display_path(target),
35
+ )
36
+ for src in source_list:
37
+ if not os.path.isfile(src):
38
+ messages.append(
39
+ tr(
40
+ "❗ Source file does not exist: '{src}'",
41
+ src=display_path(src),
42
+ )
43
+ )
44
+ continue
45
+ dst = os.path.join(target, os.path.basename(src))
46
+ messages.append(self._copy_one(src, dst, overwrite=overwrite))
47
+ else:
48
+ src = source_list[0]
49
+ if os.path.isdir(target):
50
+ dst = os.path.join(target, os.path.basename(src))
51
+ else:
52
+ dst = target
53
+ messages.append(self._copy_one(src, dst, overwrite=overwrite))
54
+ return "\n".join(messages)
55
+
56
+ def _copy_one(self, src, dst, overwrite=False) -> str:
57
+ disp_src = display_path(src)
58
+ disp_dst = display_path(dst)
59
+ if not os.path.isfile(src):
60
+ return tr("❗ Source file does not exist: '{src}'", src=disp_src)
61
+ if os.path.exists(dst) and not overwrite:
62
+ return tr(
63
+ "❗ Target already exists: '{dst}'. Set overwrite=True to replace.",
64
+ dst=disp_dst,
65
+ )
66
+ try:
67
+ os.makedirs(os.path.dirname(dst), exist_ok=True)
68
+ shutil.copy2(src, dst)
69
+ note = (
70
+ "\n⚠️ Overwrote existing file. (recommended only after reading the file to be overwritten)"
71
+ if (os.path.exists(dst) and overwrite)
72
+ else ""
73
+ )
74
+ self.report_success(
75
+ tr("✅ Copied '{src}' to '{dst}'", src=disp_src, dst=disp_dst)
76
+ )
77
+ return tr("✅ Copied '{src}' to '{dst}'", src=disp_src, dst=disp_dst) + note
78
+ except Exception as e:
79
+ return tr(
80
+ "❗ Copy failed from '{src}' to '{dst}': {err}",
81
+ src=disp_src,
82
+ dst=disp_dst,
83
+ err=str(e),
84
+ )
@@ -1,14 +1,13 @@
1
- from janito.agent.tool_registry import register_tool
1
+ from janito.tools.adapters.local.adapter import register_local_tool
2
2
 
3
- # from janito.agent.tools_utils.expand_path import expand_path
4
- from janito.agent.tools_utils.utils import display_path
5
- from janito.agent.tool_base import ToolBase
6
- from janito.agent.tools_utils.action_type import ActionType
3
+ from janito.tools.tool_utils import display_path
4
+ from janito.tools.tool_base import ToolBase
5
+ from janito.report_events import ReportAction
7
6
  from janito.i18n import tr
8
7
  import os
9
8
 
10
9
 
11
- @register_tool(name="create_directory")
10
+ @register_local_tool
12
11
  class CreateDirectoryTool(ToolBase):
13
12
  """
14
13
  Create a new directory at the specified file_path.
@@ -16,17 +15,19 @@ class CreateDirectoryTool(ToolBase):
16
15
  file_path (str): Path for the new directory.
17
16
  Returns:
18
17
  str: Status message indicating the result. Example:
19
- - " Successfully created the directory at ..."
20
- - " Cannot create directory: ..."
18
+ - "5c5 Successfully created the directory at ..."
19
+ - "5d7 Cannot create directory: ..."
21
20
  """
22
21
 
22
+ tool_name = "create_directory"
23
+
23
24
  def run(self, file_path: str) -> str:
24
25
  # file_path = expand_path(file_path)
25
26
  # Using file_path as is
26
27
  disp_path = display_path(file_path)
27
- self.report_info(
28
- ActionType.WRITE,
28
+ self.report_action(
29
29
  tr("📁 Create directory '{disp_path}' ...", disp_path=disp_path),
30
+ ReportAction.CREATE,
30
31
  )
31
32
  try:
32
33
  if os.path.exists(file_path):
@@ -0,0 +1,82 @@
1
+ import os
2
+ from janito.tools.adapters.local.adapter import register_local_tool
3
+
4
+ from janito.tools.tool_utils import display_path
5
+ from janito.tools.tool_base import ToolBase
6
+ from janito.report_events import ReportAction
7
+ from janito.i18n import tr
8
+
9
+
10
+ from janito.tools.adapters.local.validate_file_syntax.core import validate_file_syntax
11
+
12
+
13
+ @register_local_tool
14
+ class CreateFileTool(ToolBase):
15
+ """
16
+ Create a new file with the given content.
17
+
18
+ Args:
19
+ file_path (str): Path to the file to create.
20
+ content (str): Content to write to the file.
21
+ overwrite (bool, optional): Overwrite existing file if True. Default: False. Recommended only after reading the file to be overwritten.
22
+ Returns:
23
+ str: Status message indicating the result. Example:
24
+ - "✅ Successfully created the file at ..."
25
+
26
+ Note: Syntax validation is automatically performed after this operation.
27
+ """
28
+
29
+ tool_name = "create_file"
30
+
31
+ def run(self, file_path: str, content: str, overwrite: bool = False) -> str:
32
+ expanded_file_path = file_path # Using file_path as is
33
+ disp_path = display_path(expanded_file_path)
34
+ file_path = expanded_file_path
35
+ if os.path.exists(file_path) and not overwrite:
36
+ try:
37
+ with open(file_path, "r", encoding="utf-8", errors="replace") as f:
38
+ existing_content = f.read()
39
+ except Exception as e:
40
+ existing_content = f"[Error reading file: {e}]"
41
+ return tr(
42
+ "❗ Cannot create file: file already exists at '{disp_path}'.\n--- Current file content ---\n{existing_content}",
43
+ disp_path=disp_path,
44
+ existing_content=existing_content,
45
+ )
46
+ # Determine if we are overwriting an existing file
47
+ is_overwrite = os.path.exists(file_path) and overwrite
48
+ if is_overwrite:
49
+ # Overwrite branch: log only overwrite warning (no create message)
50
+ self.report_action(
51
+ tr("⚠️ Overwriting file '{disp_path}'", disp_path=disp_path),
52
+ ReportAction.CREATE,
53
+ )
54
+ dir_name = os.path.dirname(file_path)
55
+ if dir_name:
56
+ os.makedirs(dir_name, exist_ok=True)
57
+ if not is_overwrite:
58
+ # Create branch: log file creation message
59
+ self.report_action(
60
+ tr("📝 Create file '{disp_path}' ...", disp_path=disp_path),
61
+ ReportAction.CREATE,
62
+ )
63
+ with open(file_path, "w", encoding="utf-8", errors="replace") as f:
64
+ f.write(content)
65
+ new_lines = content.count("\n") + 1 if content else 0
66
+ self.report_success(
67
+ tr("✅ {new_lines} lines", new_lines=new_lines), ReportAction.CREATE
68
+ )
69
+ # Perform syntax validation and append result
70
+ validation_result = validate_file_syntax(file_path)
71
+ if is_overwrite:
72
+ # Overwrite branch: return minimal overwrite info to user
73
+ return (
74
+ tr("✅ {new_lines} lines", new_lines=new_lines)
75
+ + f"\n{validation_result}"
76
+ )
77
+ else:
78
+ # Create branch: return detailed create success to user
79
+ return (
80
+ tr("✅ Created file {new_lines} lines.", new_lines=new_lines)
81
+ + f"\n{validation_result}"
82
+ )
@@ -0,0 +1,136 @@
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 shutil
6
+ from janito.tools.adapters.local.validate_file_syntax.core import validate_file_syntax
7
+
8
+
9
+ @register_local_tool
10
+ class DeleteTextInFileTool(ToolBase):
11
+ """
12
+ Delete all occurrences of text between start_marker and end_marker (inclusive) in a file, using exact string markers.
13
+
14
+ Args:
15
+ file_path (str): Path to the file to modify.
16
+ start_marker (str): The starting delimiter string.
17
+ end_marker (str): The ending delimiter string.
18
+ backup (bool, optional): If True, create a backup (.bak) before deleting. Defaults to False.
19
+ Returns:
20
+ str: Status message indicating the result.
21
+ """
22
+
23
+ tool_name = "delete_text_in_file"
24
+
25
+ def run(
26
+ self,
27
+ file_path: str,
28
+ start_marker: str,
29
+ end_marker: str,
30
+ backup: bool = False,
31
+ ) -> str:
32
+ from janito.tools.tool_utils import display_path
33
+
34
+ disp_path = display_path(file_path)
35
+ info_msg = tr(
36
+ "📝 Delete text in {disp_path} between markers: '{start_marker}' ... '{end_marker}'",
37
+ disp_path=disp_path,
38
+ start_marker=start_marker,
39
+ end_marker=end_marker,
40
+ )
41
+ self.report_action(info_msg, ReportAction.CREATE)
42
+ try:
43
+ content = self._read_file_content(file_path)
44
+ occurrences, match_lines = self._find_marker_blocks(
45
+ content, start_marker, end_marker
46
+ )
47
+ if occurrences == 0:
48
+ self.report_warning(
49
+ tr(" ℹ️ No blocks found between markers."), ReportAction.CREATE
50
+ )
51
+ return tr(
52
+ "No blocks found between markers in {file_path}.",
53
+ file_path=file_path,
54
+ )
55
+ backup_path = file_path + ".bak"
56
+ if backup:
57
+ self._backup_file(file_path, backup_path)
58
+ new_content, deleted_blocks = self._delete_blocks(
59
+ content, start_marker, end_marker
60
+ )
61
+ self._write_file_content(file_path, new_content)
62
+ validation_result = validate_file_syntax(file_path)
63
+ self._report_success(match_lines)
64
+ return tr(
65
+ "Deleted {count} block(s) between markers in {file_path}. (backup at {backup_path})",
66
+ count=deleted_blocks,
67
+ file_path=file_path,
68
+ backup_path=backup_path if backup else "N/A",
69
+ ) + (f"\n{validation_result}" if validation_result else "")
70
+ except Exception as e:
71
+ self.report_error(tr(" ❌ Error: {error}", error=e), ReportAction.REPLACE)
72
+ return tr("Error deleting text: {error}", error=e)
73
+
74
+ def _read_file_content(self, file_path):
75
+ with open(file_path, "r", encoding="utf-8", errors="replace") as f:
76
+ return f.read()
77
+
78
+ def _find_marker_blocks(self, content, start_marker, end_marker):
79
+ """Find all blocks between start_marker and end_marker, return count and starting line numbers."""
80
+ lines = content.splitlines(keepends=True)
81
+ joined = "".join(lines)
82
+ match_lines = []
83
+ idx = 0
84
+ occurrences = 0
85
+ while True:
86
+ start_idx = joined.find(start_marker, idx)
87
+ if start_idx == -1:
88
+ break
89
+ end_idx = joined.find(end_marker, start_idx + len(start_marker))
90
+ if end_idx == -1:
91
+ break
92
+ upto = joined[:start_idx]
93
+ line_no = upto.count("\n") + 1
94
+ match_lines.append(line_no)
95
+ idx = end_idx + len(end_marker)
96
+ occurrences += 1
97
+ return occurrences, match_lines
98
+
99
+ def _delete_blocks(self, content, start_marker, end_marker):
100
+ """Delete all blocks between start_marker and end_marker (inclusive)."""
101
+ count = 0
102
+ new_content = content
103
+ while True:
104
+ start_idx = new_content.find(start_marker)
105
+ if start_idx == -1:
106
+ break
107
+ end_idx = new_content.find(end_marker, start_idx + len(start_marker))
108
+ if end_idx == -1:
109
+ break
110
+ new_content = (
111
+ new_content[:start_idx] + new_content[end_idx + len(end_marker) :]
112
+ )
113
+ count += 1
114
+ return new_content, count
115
+
116
+ def _backup_file(self, file_path, backup_path):
117
+ shutil.copy2(file_path, backup_path)
118
+
119
+ def _write_file_content(self, file_path, content):
120
+ with open(file_path, "w", encoding="utf-8", errors="replace") as f:
121
+ f.write(content)
122
+
123
+ def _report_success(self, match_lines):
124
+ if match_lines:
125
+ lines_str = ", ".join(str(line_no) for line_no in match_lines)
126
+ self.report_success(
127
+ tr(
128
+ " ✅ deleted block(s) starting at line(s): {lines_str}",
129
+ lines_str=lines_str,
130
+ ),
131
+ ReportAction.CREATE,
132
+ )
133
+ else:
134
+ self.report_success(
135
+ tr(" ✅ deleted block(s) (lines unknown)"), ReportAction.CREATE
136
+ )