janito 1.9.0__py3-none-any.whl → 1.10.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 (81) hide show
  1. janito/__init__.py +1 -1
  2. janito/agent/api_exceptions.py +4 -0
  3. janito/agent/config.py +1 -1
  4. janito/agent/config_defaults.py +2 -26
  5. janito/agent/conversation.py +163 -122
  6. janito/agent/conversation_api.py +149 -159
  7. janito/agent/{conversation_history.py → llm_conversation_history.py} +18 -1
  8. janito/agent/openai_client.py +38 -23
  9. janito/agent/openai_schema_generator.py +162 -129
  10. janito/agent/platform_discovery.py +134 -77
  11. janito/agent/profile_manager.py +5 -5
  12. janito/agent/rich_message_handler.py +80 -31
  13. janito/agent/templates/profiles/system_prompt_template_base.txt.j2 +5 -4
  14. janito/agent/test_openai_schema_generator.py +93 -0
  15. janito/agent/tool_base.py +7 -2
  16. janito/agent/tool_executor.py +54 -49
  17. janito/agent/tool_registry.py +5 -2
  18. janito/agent/tool_use_tracker.py +26 -5
  19. janito/agent/tools/__init__.py +6 -3
  20. janito/agent/tools/create_directory.py +3 -1
  21. janito/agent/tools/create_file.py +7 -1
  22. janito/agent/tools/fetch_url.py +40 -3
  23. janito/agent/tools/find_files.py +3 -1
  24. janito/agent/tools/get_file_outline/core.py +6 -7
  25. janito/agent/tools/get_file_outline/search_outline.py +3 -1
  26. janito/agent/tools/get_lines.py +7 -2
  27. janito/agent/tools/move_file.py +3 -1
  28. janito/agent/tools/present_choices.py +3 -1
  29. janito/agent/tools/python_command_runner.py +150 -0
  30. janito/agent/tools/python_file_runner.py +148 -0
  31. janito/agent/tools/python_stdin_runner.py +154 -0
  32. janito/agent/tools/remove_directory.py +3 -1
  33. janito/agent/tools/remove_file.py +5 -1
  34. janito/agent/tools/replace_file.py +12 -2
  35. janito/agent/tools/replace_text_in_file.py +4 -2
  36. janito/agent/tools/run_bash_command.py +30 -69
  37. janito/agent/tools/run_powershell_command.py +134 -105
  38. janito/agent/tools/search_text.py +172 -122
  39. janito/agent/tools/validate_file_syntax/core.py +3 -1
  40. janito/agent/tools_utils/action_type.py +7 -0
  41. janito/agent/tools_utils/dir_walk_utils.py +3 -2
  42. janito/agent/tools_utils/formatting.py +47 -21
  43. janito/agent/tools_utils/gitignore_utils.py +66 -40
  44. janito/agent/tools_utils/test_gitignore_utils.py +46 -0
  45. janito/cli/_print_config.py +63 -61
  46. janito/cli/arg_parser.py +13 -12
  47. janito/cli/cli_main.py +137 -147
  48. janito/cli/main.py +152 -174
  49. janito/cli/one_shot.py +40 -26
  50. janito/i18n/__init__.py +1 -1
  51. janito/rich_utils.py +46 -8
  52. janito/shell/commands/__init__.py +2 -4
  53. janito/shell/commands/conversation_restart.py +3 -1
  54. janito/shell/commands/edit.py +3 -0
  55. janito/shell/commands/history_view.py +3 -3
  56. janito/shell/commands/lang.py +3 -0
  57. janito/shell/commands/livelogs.py +5 -3
  58. janito/shell/commands/prompt.py +6 -0
  59. janito/shell/commands/session.py +3 -0
  60. janito/shell/commands/session_control.py +3 -0
  61. janito/shell/commands/termweb_log.py +8 -0
  62. janito/shell/commands/tools.py +3 -0
  63. janito/shell/commands/track.py +36 -0
  64. janito/shell/commands/utility.py +13 -18
  65. janito/shell/commands/verbose.py +3 -4
  66. janito/shell/input_history.py +62 -0
  67. janito/shell/main.py +117 -181
  68. janito/shell/session/manager.py +0 -21
  69. janito/shell/ui/interactive.py +0 -2
  70. janito/termweb/static/editor.css +0 -4
  71. janito/tests/test_rich_utils.py +44 -0
  72. janito/web/app.py +0 -75
  73. {janito-1.9.0.dist-info → janito-1.10.0.dist-info}/METADATA +61 -42
  74. {janito-1.9.0.dist-info → janito-1.10.0.dist-info}/RECORD +78 -71
  75. {janito-1.9.0.dist-info → janito-1.10.0.dist-info}/WHEEL +1 -1
  76. janito/agent/providers.py +0 -77
  77. janito/agent/tools/run_python_command.py +0 -161
  78. janito/shell/commands/sum.py +0 -49
  79. {janito-1.9.0.dist-info → janito-1.10.0.dist-info}/entry_points.txt +0 -0
  80. {janito-1.9.0.dist-info → janito-1.10.0.dist-info}/licenses/LICENSE +0 -0
  81. {janito-1.9.0.dist-info → janito-1.10.0.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,5 @@
1
1
  from janito.agent.tool_base import ToolBase
2
+ from janito.agent.tools_utils.action_type import ActionType
2
3
  from janito.agent.tool_registry import register_tool
3
4
  from janito.i18n import tr
4
5
 
@@ -32,7 +33,7 @@ class ReplaceTextInFileTool(ToolBase):
32
33
  from janito.agent.tools_utils.utils import display_path
33
34
 
34
35
  disp_path = display_path(file_path)
35
- action = "(all)" if replace_all else "(unique)"
36
+ action = "(all)" if replace_all else ""
36
37
  search_lines = len(search_text.splitlines())
37
38
  replace_lines = len(replacement_text.splitlines())
38
39
  if replace_lines == 0:
@@ -67,7 +68,8 @@ class ReplaceTextInFileTool(ToolBase):
67
68
  action=action,
68
69
  )
69
70
  self.report_info(
70
- info_msg + (" ..." if not info_msg.rstrip().endswith("...") else "")
71
+ ActionType.WRITE,
72
+ info_msg + (" ..." if not info_msg.rstrip().endswith("...") else ""),
71
73
  )
72
74
  try:
73
75
  with open(file_path, "r", encoding="utf-8", errors="replace") as f:
@@ -1,4 +1,5 @@
1
1
  from janito.agent.tool_base import ToolBase
2
+ from janito.agent.tools_utils.action_type import ActionType
2
3
  from janito.agent.tool_registry import register_tool
3
4
  from janito.i18n import tr
4
5
  import subprocess
@@ -16,7 +17,7 @@ class RunBashCommandTool(ToolBase):
16
17
  command (str): The bash command to execute.
17
18
  timeout (int, optional): Timeout in seconds for the command. Defaults to 60.
18
19
  require_confirmation (bool, optional): If True, require user confirmation before running. Defaults to False.
19
- interactive (bool, optional): If True, warns that the command may require user interaction. Defaults to False. Non-interactive commands are preferred for automation and reliability.
20
+ requires_user_input (bool, optional): 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.
20
21
  Returns:
21
22
  str: File paths and line counts for stdout and stderr.
22
23
  """
@@ -26,16 +27,19 @@ class RunBashCommandTool(ToolBase):
26
27
  command: str,
27
28
  timeout: int = 60,
28
29
  require_confirmation: bool = False,
29
- interactive: bool = False,
30
+ requires_user_input: bool = False,
30
31
  ) -> str:
31
32
  if not command.strip():
32
- self.report_warning(tr("ℹ️ Empty command provided."))
33
+ self.report_warning(tr("\u2139\ufe0f Empty command provided."))
33
34
  return tr("Warning: Empty command provided. Operation skipped.")
34
- self.report_info(tr("🖥️ Running bash command: {command} ...\n", command=command))
35
- if interactive:
35
+ self.report_info(
36
+ ActionType.EXECUTE,
37
+ tr("🖥️ Running bash command: {command} ...\n", command=command),
38
+ )
39
+ if requires_user_input:
36
40
  self.report_warning(
37
41
  tr(
38
- "⚠️ Warning: This command might be interactive, require user input, and might hang."
42
+ "\u26a0\ufe0f Warning: This command might be interactive, require user input, and might hang."
39
43
  )
40
44
  )
41
45
  sys.stdout.flush()
@@ -61,88 +65,45 @@ class RunBashCommandTool(ToolBase):
61
65
  bufsize=1,
62
66
  env=env,
63
67
  )
64
- stdout_lines = 0
65
- stderr_lines = 0
66
- stdout_content = []
67
- stderr_content = []
68
- max_lines = 100
69
- import threading
70
-
71
- def stream_reader(
72
- stream, file_handle, report_func, content_list, line_counter
73
- ):
74
- for line in iter(stream.readline, ""):
75
- file_handle.write(line)
76
- file_handle.flush()
77
- report_func(line)
78
- content_list.append(line)
79
- line_counter[0] += 1
80
- stream.close()
81
-
82
- stdout_counter = [0]
83
- stderr_counter = [0]
84
- stdout_thread = threading.Thread(
85
- target=stream_reader,
86
- args=(
87
- process.stdout,
88
- stdout_file,
89
- self.report_stdout,
90
- stdout_content,
91
- stdout_counter,
92
- ),
93
- )
94
- stderr_thread = threading.Thread(
95
- target=stream_reader,
96
- args=(
97
- process.stderr,
98
- stderr_file,
99
- self.report_stderr,
100
- stderr_content,
101
- stderr_counter,
102
- ),
103
- )
104
- stdout_thread.start()
105
- stderr_thread.start()
106
68
  try:
107
- process.wait(timeout=timeout)
69
+ stdout_content, stderr_content = process.communicate(
70
+ timeout=timeout
71
+ )
108
72
  except subprocess.TimeoutExpired:
109
73
  process.kill()
110
74
  self.report_error(
111
- tr(" ❌ Timed out after {timeout} seconds.", timeout=timeout)
75
+ tr(
76
+ " \u274c Timed out after {timeout} seconds.",
77
+ timeout=timeout,
78
+ )
112
79
  )
113
80
  return tr(
114
81
  "Command timed out after {timeout} seconds.", timeout=timeout
115
82
  )
116
- stdout_thread.join()
117
- stderr_thread.join()
118
- stdout_lines = stdout_counter[0]
119
- stderr_lines = stderr_counter[0]
120
83
  self.report_success(
121
- tr(" ✅ return code {return_code}", return_code=process.returncode)
84
+ tr(
85
+ " \u2705 return code {return_code}",
86
+ return_code=process.returncode,
87
+ )
122
88
  )
123
89
  warning_msg = ""
124
- if interactive:
90
+ if requires_user_input:
125
91
  warning_msg = tr(
126
- "⚠️ Warning: This command might be interactive, require user input, and might hang.\n"
92
+ "\u26a0\ufe0f Warning: This command might be interactive, require user input, and might hang.\n"
127
93
  )
94
+ max_lines = 100
95
+ stdout_lines = stdout_content.count("\n")
96
+ stderr_lines = stderr_content.count("\n")
128
97
  if stdout_lines <= max_lines and stderr_lines <= max_lines:
129
- with open(
130
- stdout_file.name, "r", encoding="utf-8", errors="replace"
131
- ) as out_f:
132
- stdout_content_str = out_f.read()
133
- with open(
134
- stderr_file.name, "r", encoding="utf-8", errors="replace"
135
- ) as err_f:
136
- stderr_content_str = err_f.read()
137
98
  result = warning_msg + tr(
138
99
  "Return code: {return_code}\n--- STDOUT ---\n{stdout_content}",
139
100
  return_code=process.returncode,
140
- stdout_content=stdout_content_str,
101
+ stdout_content=stdout_content,
141
102
  )
142
- if stderr_content_str.strip():
103
+ if stderr_content.strip():
143
104
  result += tr(
144
105
  "\n--- STDERR ---\n{stderr_content}",
145
- stderr_content=stderr_content_str,
106
+ stderr_content=stderr_content,
146
107
  )
147
108
  return result
148
109
  else:
@@ -163,5 +124,5 @@ class RunBashCommandTool(ToolBase):
163
124
  )
164
125
  return result
165
126
  except Exception as e:
166
- self.report_error(tr(" Error: {error}", error=e))
127
+ self.report_error(tr(" \u274c Error: {error}", error=e))
167
128
  return tr("Error running command: {error}", error=e)
@@ -1,4 +1,5 @@
1
1
  from janito.agent.tool_base import ToolBase
2
+ from janito.agent.tools_utils.action_type import ActionType
2
3
  from janito.agent.tool_registry import register_tool
3
4
  from janito.i18n import tr
4
5
  import subprocess
@@ -18,30 +19,16 @@ class RunPowerShellCommandTool(ToolBase):
18
19
  command (str): The PowerShell command to execute. This string is passed directly to PowerShell using the --Command argument (not as a script file).
19
20
  timeout (int, optional): Timeout in seconds for the command. Defaults to 60.
20
21
  require_confirmation (bool, optional): If True, require user confirmation before running. Defaults to False.
21
- interactive (bool, optional): If True, warns that the command may require user interaction. Defaults to False. Non-interactive commands are preferred for automation and reliability.
22
+ requires_user_input (bool, optional): 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.
22
23
  Returns:
23
24
  str: Output and status message, or file paths/line counts if output is large.
24
25
  """
25
26
 
26
- def run(
27
- self,
28
- command: str,
29
- timeout: int = 60,
30
- require_confirmation: bool = False,
31
- interactive: bool = False,
32
- ) -> str:
33
- if not command.strip():
34
- self.report_warning(tr("ℹ️ Empty command provided."))
35
- return tr("Warning: Empty command provided. Operation skipped.")
36
- encoding_prefix = "$OutputEncoding = [Console]::OutputEncoding = [System.Text.Encoding]::UTF8; "
37
- command_with_encoding = encoding_prefix + command
38
- self.report_info(
39
- tr("🖥️ Running PowerShell command: {command} ...\n", command=command)
40
- )
41
- if interactive:
27
+ def _confirm_and_warn(self, command, require_confirmation, requires_user_input):
28
+ if requires_user_input:
42
29
  self.report_warning(
43
30
  tr(
44
- "⚠️ Warning: This command might be interactive, require user input, and might hang."
31
+ "\u26a0\ufe0f Warning: This command might be interactive, require user input, and might hang."
45
32
  )
46
33
  )
47
34
  if require_confirmation:
@@ -52,11 +39,109 @@ class RunPowerShellCommandTool(ToolBase):
52
39
  )
53
40
  )
54
41
  if not confirmed:
55
- self.report_warning(tr("⚠️ Execution cancelled by user."))
56
- return tr("❌ Command execution cancelled by user.")
57
- from janito.agent.platform_discovery import is_windows
42
+ self.report_warning(tr("\u26a0\ufe0f Execution cancelled by user."))
43
+ return False
44
+ return True
58
45
 
59
- shell_exe = "powershell.exe" if is_windows() else "pwsh"
46
+ def _launch_process(self, shell_exe, command_with_encoding):
47
+ return subprocess.Popen(
48
+ [
49
+ shell_exe,
50
+ "-NoProfile",
51
+ "-ExecutionPolicy",
52
+ "Bypass",
53
+ "-Command",
54
+ command_with_encoding,
55
+ ],
56
+ stdout=subprocess.PIPE,
57
+ stderr=subprocess.PIPE,
58
+ text=True,
59
+ bufsize=1,
60
+ universal_newlines=True,
61
+ encoding="utf-8",
62
+ )
63
+
64
+ def _stream_output(self, stream, file_obj, report_func, count_func, counter):
65
+ for line in stream:
66
+ file_obj.write(line)
67
+ file_obj.flush()
68
+ report_func(line)
69
+ if count_func == "stdout":
70
+ counter["stdout"] += 1
71
+ else:
72
+ counter["stderr"] += 1
73
+
74
+ def _format_result(
75
+ self, requires_user_input, return_code, stdout_file, stderr_file, max_lines=100
76
+ ):
77
+ warning_msg = ""
78
+ if requires_user_input:
79
+ warning_msg = tr(
80
+ "\u26a0\ufe0f Warning: This command might be interactive, require user input, and might hang.\n"
81
+ )
82
+ with open(stdout_file.name, "r", encoding="utf-8", errors="replace") as out_f:
83
+ stdout_content = out_f.read()
84
+ with open(stderr_file.name, "r", encoding="utf-8", errors="replace") as err_f:
85
+ stderr_content = err_f.read()
86
+ stdout_lines = stdout_content.count("\n")
87
+ stderr_lines = stderr_content.count("\n")
88
+ if stdout_lines <= max_lines and stderr_lines <= max_lines:
89
+ result = warning_msg + tr(
90
+ "Return code: {return_code}\n--- STDOUT ---\n{stdout_content}",
91
+ return_code=return_code,
92
+ stdout_content=stdout_content,
93
+ )
94
+ if stderr_content.strip():
95
+ result += tr(
96
+ "\n--- STDERR ---\n{stderr_content}",
97
+ stderr_content=stderr_content,
98
+ )
99
+ return result
100
+ else:
101
+ result = warning_msg + tr(
102
+ "stdout_file: {stdout_file} (lines: {stdout_lines})\n",
103
+ stdout_file=stdout_file.name,
104
+ stdout_lines=stdout_lines,
105
+ )
106
+ if stderr_lines > 0 and stderr_content.strip():
107
+ result += tr(
108
+ "stderr_file: {stderr_file} (lines: {stderr_lines})\n",
109
+ stderr_file=stderr_file.name,
110
+ stderr_lines=stderr_lines,
111
+ )
112
+ result += tr(
113
+ "returncode: {return_code}\nUse the get_lines tool to inspect the contents of these files when needed.",
114
+ return_code=return_code,
115
+ )
116
+ return result
117
+
118
+ def run(
119
+ self,
120
+ command: str,
121
+ timeout: int = 60,
122
+ require_confirmation: bool = False,
123
+ requires_user_input: bool = False,
124
+ ) -> str:
125
+ if not command.strip():
126
+ self.report_warning(tr("\u2139\ufe0f Empty command provided."))
127
+ return tr("Warning: Empty command provided. Operation skipped.")
128
+ encoding_prefix = "$OutputEncoding = [Console]::OutputEncoding = [System.Text.Encoding]::UTF8; "
129
+ command_with_encoding = encoding_prefix + command
130
+ self.report_info(
131
+ ActionType.EXECUTE,
132
+ tr(
133
+ "\U0001f5a5\ufe0f Running PowerShell command: {command} ...\n",
134
+ command=command,
135
+ ),
136
+ )
137
+ if not self._confirm_and_warn(
138
+ command, require_confirmation, requires_user_input
139
+ ):
140
+ return tr("\u274c Command execution cancelled by user.")
141
+ from janito.agent.platform_discovery import PlatformDiscovery
142
+
143
+ pd = PlatformDiscovery()
144
+ shell_exe = "powershell.exe" if pd.is_windows() else "pwsh"
60
145
  try:
61
146
  with (
62
147
  tempfile.NamedTemporaryFile(
@@ -72,44 +157,27 @@ class RunPowerShellCommandTool(ToolBase):
72
157
  encoding="utf-8",
73
158
  ) as stderr_file,
74
159
  ):
75
- process = subprocess.Popen(
76
- [
77
- shell_exe,
78
- "-NoProfile",
79
- "-ExecutionPolicy",
80
- "Bypass",
81
- "-Command",
82
- command_with_encoding,
83
- ],
84
- stdout=subprocess.PIPE,
85
- stderr=subprocess.PIPE,
86
- text=True,
87
- bufsize=1,
88
- universal_newlines=True,
89
- encoding="utf-8",
90
- )
91
-
92
- stdout_lines = 0
93
- stderr_lines = 0
94
-
95
- def stream_output(stream, file_obj, report_func, count_func):
96
- nonlocal stdout_lines, stderr_lines
97
- for line in stream:
98
- file_obj.write(line)
99
- file_obj.flush()
100
- report_func(line)
101
- if count_func == "stdout":
102
- stdout_lines += 1
103
- else:
104
- stderr_lines += 1
105
-
160
+ process = self._launch_process(shell_exe, command_with_encoding)
161
+ counter = {"stdout": 0, "stderr": 0}
106
162
  stdout_thread = threading.Thread(
107
- target=stream_output,
108
- args=(process.stdout, stdout_file, self.report_stdout, "stdout"),
163
+ target=self._stream_output,
164
+ args=(
165
+ process.stdout,
166
+ stdout_file,
167
+ self.report_stdout,
168
+ "stdout",
169
+ counter,
170
+ ),
109
171
  )
110
172
  stderr_thread = threading.Thread(
111
- target=stream_output,
112
- args=(process.stderr, stderr_file, self.report_stderr, "stderr"),
173
+ target=self._stream_output,
174
+ args=(
175
+ process.stderr,
176
+ stderr_file,
177
+ self.report_stderr,
178
+ "stderr",
179
+ counter,
180
+ ),
113
181
  )
114
182
  stdout_thread.start()
115
183
  stderr_thread.start()
@@ -118,7 +186,10 @@ class RunPowerShellCommandTool(ToolBase):
118
186
  except subprocess.TimeoutExpired:
119
187
  process.kill()
120
188
  self.report_error(
121
- tr(" ❌ Timed out after {timeout} seconds.", timeout=timeout)
189
+ tr(
190
+ " \u274c Timed out after {timeout} seconds.",
191
+ timeout=timeout,
192
+ )
122
193
  )
123
194
  return tr(
124
195
  "Command timed out after {timeout} seconds.", timeout=timeout
@@ -127,54 +198,12 @@ class RunPowerShellCommandTool(ToolBase):
127
198
  stderr_thread.join()
128
199
  stdout_file.flush()
129
200
  stderr_file.flush()
130
-
131
201
  self.report_success(
132
- tr(" return code {return_code}", return_code=return_code)
202
+ tr(" \u2705 return code {return_code}", return_code=return_code)
203
+ )
204
+ return self._format_result(
205
+ requires_user_input, return_code, stdout_file, stderr_file
133
206
  )
134
- warning_msg = ""
135
- if interactive:
136
- warning_msg = tr(
137
- "⚠️ Warning: This command might be interactive, require user input, and might hang.\n"
138
- )
139
- # Read back the content for summary if not too large
140
- with open(
141
- stdout_file.name, "r", encoding="utf-8", errors="replace"
142
- ) as out_f:
143
- stdout_content = out_f.read()
144
- with open(
145
- stderr_file.name, "r", encoding="utf-8", errors="replace"
146
- ) as err_f:
147
- stderr_content = err_f.read()
148
- max_lines = 100
149
- if stdout_lines <= max_lines and stderr_lines <= max_lines:
150
- result = warning_msg + tr(
151
- "Return code: {return_code}\n--- STDOUT ---\n{stdout_content}",
152
- return_code=return_code,
153
- stdout_content=stdout_content,
154
- )
155
- if stderr_content.strip():
156
- result += tr(
157
- "\n--- STDERR ---\n{stderr_content}",
158
- stderr_content=stderr_content,
159
- )
160
- return result
161
- else:
162
- result = warning_msg + tr(
163
- "stdout_file: {stdout_file} (lines: {stdout_lines})\n",
164
- stdout_file=stdout_file.name,
165
- stdout_lines=stdout_lines,
166
- )
167
- if stderr_lines > 0 and stderr_content.strip():
168
- result += tr(
169
- "stderr_file: {stderr_file} (lines: {stderr_lines})\n",
170
- stderr_file=stderr_file.name,
171
- stderr_lines=stderr_lines,
172
- )
173
- result += tr(
174
- "returncode: {return_code}\nUse the get_lines tool to inspect the contents of these files when needed.",
175
- return_code=return_code,
176
- )
177
- return result
178
207
  except Exception as e:
179
- self.report_error(tr(" Error: {error}", error=e))
208
+ self.report_error(tr(" \u274c Error: {error}", error=e))
180
209
  return tr("Error running command: {error}", error=e)