janito 0.15.0__py3-none-any.whl → 1.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 (109) hide show
  1. janito/__init__.py +1 -5
  2. janito/__main__.py +3 -5
  3. janito/agent/__init__.py +1 -0
  4. janito/agent/agent.py +96 -0
  5. janito/agent/config.py +113 -0
  6. janito/agent/config_defaults.py +10 -0
  7. janito/agent/conversation.py +107 -0
  8. janito/agent/queued_tool_handler.py +16 -0
  9. janito/agent/runtime_config.py +30 -0
  10. janito/agent/tool_handler.py +124 -0
  11. janito/agent/tools/__init__.py +11 -0
  12. janito/agent/tools/ask_user.py +63 -0
  13. janito/agent/tools/bash_exec.py +58 -0
  14. janito/agent/tools/create_directory.py +19 -0
  15. janito/agent/tools/create_file.py +43 -0
  16. janito/agent/tools/fetch_url.py +48 -0
  17. janito/agent/tools/file_str_replace.py +48 -0
  18. janito/agent/tools/find_files.py +37 -0
  19. janito/agent/tools/gitignore_utils.py +40 -0
  20. janito/agent/tools/move_file.py +37 -0
  21. janito/agent/tools/remove_file.py +19 -0
  22. janito/agent/tools/rich_live.py +37 -0
  23. janito/agent/tools/rich_utils.py +31 -0
  24. janito/agent/tools/search_text.py +41 -0
  25. janito/agent/tools/view_file.py +34 -0
  26. janito/cli/__init__.py +0 -6
  27. janito/cli/_print_config.py +68 -0
  28. janito/cli/_utils.py +8 -0
  29. janito/cli/arg_parser.py +26 -0
  30. janito/cli/config_commands.py +131 -0
  31. janito/cli/logging_setup.py +27 -0
  32. janito/cli/main.py +39 -0
  33. janito/cli/runner.py +135 -0
  34. janito/cli_chat_shell/__init__.py +1 -0
  35. janito/cli_chat_shell/chat_loop.py +147 -0
  36. janito/cli_chat_shell/commands.py +202 -0
  37. janito/cli_chat_shell/config_shell.py +75 -0
  38. janito/cli_chat_shell/load_prompt.py +15 -0
  39. janito/cli_chat_shell/session_manager.py +60 -0
  40. janito/cli_chat_shell/ui.py +136 -0
  41. janito/render_prompt.py +12 -0
  42. janito/templates/system_instructions.j2 +36 -0
  43. janito/web/__init__.py +0 -0
  44. janito/web/__main__.py +17 -0
  45. janito/web/app.py +132 -0
  46. janito-1.0.0.dist-info/METADATA +144 -0
  47. janito-1.0.0.dist-info/RECORD +51 -0
  48. {janito-0.15.0.dist-info → janito-1.0.0.dist-info}/WHEEL +2 -1
  49. janito-1.0.0.dist-info/entry_points.txt +2 -0
  50. {janito-0.15.0.dist-info → janito-1.0.0.dist-info}/licenses/LICENSE +2 -2
  51. janito-1.0.0.dist-info/top_level.txt +1 -0
  52. janito/callbacks.py +0 -34
  53. janito/cli/agent/__init__.py +0 -7
  54. janito/cli/agent/conversation.py +0 -149
  55. janito/cli/agent/initialization.py +0 -168
  56. janito/cli/agent/query.py +0 -112
  57. janito/cli/agent.py +0 -12
  58. janito/cli/app.py +0 -178
  59. janito/cli/commands/__init__.py +0 -12
  60. janito/cli/commands/config.py +0 -30
  61. janito/cli/commands/history.py +0 -119
  62. janito/cli/commands/profile.py +0 -93
  63. janito/cli/commands/validation.py +0 -24
  64. janito/cli/commands/workspace.py +0 -31
  65. janito/cli/commands.py +0 -12
  66. janito/cli/output.py +0 -29
  67. janito/cli/utils.py +0 -22
  68. janito/config/README.md +0 -104
  69. janito/config/__init__.py +0 -16
  70. janito/config/cli/__init__.py +0 -28
  71. janito/config/cli/commands.py +0 -397
  72. janito/config/cli/validators.py +0 -77
  73. janito/config/core/__init__.py +0 -23
  74. janito/config/core/file_operations.py +0 -90
  75. janito/config/core/properties.py +0 -316
  76. janito/config/core/singleton.py +0 -282
  77. janito/config/profiles/__init__.py +0 -8
  78. janito/config/profiles/definitions.py +0 -38
  79. janito/config/profiles/manager.py +0 -80
  80. janito/data/instructions_template.txt +0 -34
  81. janito/token_report.py +0 -154
  82. janito/tools/__init__.py +0 -44
  83. janito/tools/bash/bash.py +0 -157
  84. janito/tools/bash/unix_persistent_bash.py +0 -215
  85. janito/tools/bash/win_persistent_bash.py +0 -341
  86. janito/tools/decorators.py +0 -90
  87. janito/tools/delete_file.py +0 -65
  88. janito/tools/fetch_webpage/__init__.py +0 -23
  89. janito/tools/fetch_webpage/core.py +0 -182
  90. janito/tools/find_files.py +0 -220
  91. janito/tools/move_file.py +0 -72
  92. janito/tools/prompt_user.py +0 -57
  93. janito/tools/replace_file.py +0 -63
  94. janito/tools/rich_console.py +0 -176
  95. janito/tools/search_text.py +0 -226
  96. janito/tools/str_replace_editor/__init__.py +0 -6
  97. janito/tools/str_replace_editor/editor.py +0 -55
  98. janito/tools/str_replace_editor/handlers/__init__.py +0 -16
  99. janito/tools/str_replace_editor/handlers/create.py +0 -60
  100. janito/tools/str_replace_editor/handlers/insert.py +0 -100
  101. janito/tools/str_replace_editor/handlers/str_replace.py +0 -94
  102. janito/tools/str_replace_editor/handlers/undo.py +0 -64
  103. janito/tools/str_replace_editor/handlers/view.py +0 -165
  104. janito/tools/str_replace_editor/utils.py +0 -33
  105. janito/tools/think.py +0 -37
  106. janito/tools/usage_tracker.py +0 -137
  107. janito-0.15.0.dist-info/METADATA +0 -481
  108. janito-0.15.0.dist-info/RECORD +0 -64
  109. janito-0.15.0.dist-info/entry_points.txt +0 -2
@@ -1,215 +0,0 @@
1
- import subprocess
2
- import time
3
- import uuid
4
-
5
- class PersistentBash:
6
- """
7
- A wrapper class that maintains a persistent Bash session.
8
- Allows sending commands and collecting output without restarting Bash.
9
- """
10
-
11
- def __init__(self, bash_path=None):
12
- """
13
- Initialize a persistent Bash session.
14
-
15
- Args:
16
- bash_path (str, optional): Path to the Bash executable. If None, tries to detect automatically.
17
- """
18
- self.process = None
19
- self.bash_path = bash_path
20
-
21
- # If bash_path is not provided, try to detect it
22
- if self.bash_path is None:
23
- # On Unix-like systems, bash is usually in the PATH
24
- self.bash_path = "bash"
25
-
26
- # Check if bash exists
27
- try:
28
- subprocess.run(["which", "bash"], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
29
- except subprocess.CalledProcessError as err:
30
- raise FileNotFoundError("Could not find bash executable. Please specify the path manually.") from err
31
-
32
- # Start the bash process
33
- self.start_process()
34
-
35
- def start_process(self):
36
- """Start the Bash process."""
37
- # Create a subprocess with pipe for stdin, stdout, and stderr
38
- bash_args = [self.bash_path]
39
-
40
- self.process = subprocess.Popen(
41
- bash_args,
42
- stdin=subprocess.PIPE,
43
- stdout=subprocess.PIPE,
44
- stderr=subprocess.STDOUT, # Redirect stderr to stdout
45
- text=True, # Use text mode for input/output
46
- bufsize=0, # Unbuffered
47
- universal_newlines=True, # Universal newlines mode
48
- )
49
-
50
- # Set up a more reliable environment
51
- setup_commands = [
52
- "export PS1='$ '", # Simple prompt to avoid parsing issues
53
- "export TERM=dumb", # Disable color codes and other terminal features
54
- "set +o history", # Disable history
55
- "shopt -s expand_aliases", # Enable alias expansion
56
- ]
57
-
58
- # Send setup commands
59
- for cmd in setup_commands:
60
- self._send_command(cmd)
61
-
62
- # Clear initial output with a marker
63
- marker = f"INIT_COMPLETE_{uuid.uuid4().hex}"
64
- self._send_command(f"echo {marker}")
65
-
66
- while True:
67
- line = self.process.stdout.readline().strip()
68
- if marker in line:
69
- break
70
-
71
- def _send_command(self, command):
72
- """Send a command to the Bash process without reading the output."""
73
- if self.process is None or self.process.poll() is not None:
74
- self.start_process()
75
-
76
- self.process.stdin.write(command + "\n")
77
- self.process.stdin.flush()
78
-
79
- def execute(self, command, timeout=None):
80
- """
81
- Execute a command in the Bash session and return the output.
82
-
83
- Args:
84
- command (str): The command to execute.
85
- timeout (int, optional): Timeout in seconds. If None, no timeout is applied.
86
-
87
- Returns:
88
- str: The command output.
89
- """
90
- from janito.tools.rich_console import console
91
-
92
- if self.process is None or self.process.poll() is not None:
93
- # Process has terminated, restart it
94
- self.start_process()
95
-
96
- # Create a unique marker to identify the end of output
97
- end_marker = f"END_OF_COMMAND_{uuid.uuid4().hex}"
98
-
99
- # Construct the wrapped command with echo markers
100
- # Only use timeout when explicitly requested
101
- if timeout is not None and timeout > 0:
102
- # Check if timeout command is available
103
- is_timeout_available = False
104
- try:
105
- check_cmd = "command -v timeout > /dev/null 2>&1 && echo available || echo unavailable"
106
- self._send_command(check_cmd)
107
- for _ in range(10): # Read up to 10 lines to find the result
108
- line = self.process.stdout.readline().strip()
109
- if "available" in line:
110
- is_timeout_available = True
111
- break
112
- elif "unavailable" in line:
113
- is_timeout_available = False
114
- break
115
- except:
116
- is_timeout_available = False
117
-
118
- if is_timeout_available:
119
- # For timeout to work with shell syntax, we need to use bash -c
120
- escaped_command = command.replace('"', '\\"')
121
- wrapped_command = f"timeout {timeout}s bash -c \"{escaped_command}\" 2>&1; echo '{end_marker}'"
122
- else:
123
- wrapped_command = f"{command} 2>&1; echo '{end_marker}'"
124
- else:
125
- wrapped_command = f"{command} 2>&1; echo '{end_marker}'"
126
-
127
- # Send the command
128
- self._send_command(wrapped_command)
129
-
130
- # Collect output until the end marker is found
131
- output_lines = []
132
- start_time = time.time()
133
- max_wait = timeout if timeout is not None else 3600 # Default to 1 hour if no timeout
134
-
135
- # Check if we're being run from the main bash_tool function
136
- # which will handle interruption
137
- try:
138
- from janito.tools.bash.bash import _command_interrupted
139
- except ImportError:
140
- _command_interrupted = False
141
-
142
- while time.time() - start_time < max_wait + 5: # Add buffer time
143
- # Check if we've been interrupted
144
- if '_command_interrupted' in globals() and _command_interrupted:
145
- # Send Ctrl+C to the running process
146
- if self.process and self.process.poll() is None:
147
- try:
148
- # Send interrupt signal to the process group
149
- import os
150
- import signal
151
- pgid = os.getpgid(self.process.pid)
152
- os.killpg(pgid, signal.SIGINT)
153
- except:
154
- pass
155
-
156
- # Add message to output
157
- interrupt_msg = "Command interrupted by user (Ctrl+C)"
158
- console.print(f"[bold red]{interrupt_msg}[/bold red]")
159
- output_lines.append(interrupt_msg)
160
-
161
- # Reset the bash session
162
- self.close()
163
- self.start_process()
164
-
165
- break
166
-
167
- try:
168
- line = self.process.stdout.readline().rstrip('\r\n')
169
- if end_marker in line:
170
- break
171
-
172
- # Print the output to the console in real-time if not in trust mode
173
- if line:
174
- from janito.config import get_config
175
- if not get_config().trust_mode:
176
- console.print(line)
177
-
178
- output_lines.append(line)
179
- except Exception as e:
180
- error_msg = f"[Error reading output: {str(e)}]"
181
- console.print(error_msg, style="red")
182
- output_lines.append(error_msg)
183
- continue
184
-
185
- # Check for timeout
186
- if time.time() - start_time >= max_wait + 5 and not _command_interrupted:
187
- timeout_msg = f"Error: Command timed out after {max_wait} seconds"
188
- console.print(timeout_msg, style="red bold")
189
- output_lines.append(timeout_msg)
190
-
191
- # Try to reset the bash session after a timeout
192
- self.close()
193
- self.start_process()
194
-
195
- return "\n".join(output_lines)
196
-
197
- def close(self):
198
- """Close the Bash session."""
199
- if self.process and self.process.poll() is None:
200
- try:
201
- self._send_command("exit")
202
- self.process.wait(timeout=2)
203
- except:
204
- pass
205
- finally:
206
- try:
207
- self.process.terminate()
208
- except:
209
- pass
210
-
211
- self.process = None
212
-
213
- def __del__(self):
214
- """Destructor to ensure the process is closed."""
215
- self.close()
@@ -1,341 +0,0 @@
1
- import subprocess
2
- import os
3
- import platform
4
- import time
5
- import uuid
6
- import sys
7
- import io
8
- import codecs
9
-
10
- class PersistentBash:
11
- """
12
- A wrapper class that maintains a persistent Bash session.
13
- Allows sending commands and collecting output without restarting Bash.
14
- """
15
-
16
- def __init__(self, bash_path=None):
17
- """
18
- Initialize a persistent Bash session.
19
-
20
- Args:
21
- bash_path (str, optional): Path to the Bash executable. If None, tries to detect automatically.
22
- This can be configured in Janito's config using the gitbash_path setting.
23
- """
24
- self.process = None
25
- self.bash_path = bash_path
26
-
27
- # Configure UTF-8 support for Windows
28
- if platform.system() == "Windows":
29
- # Force UTF-8 mode in Python 3.7+
30
- os.environ["PYTHONUTF8"] = "1"
31
-
32
- # Set Python's standard IO encoding to UTF-8
33
- if hasattr(sys.stdout, 'reconfigure'):
34
- sys.stdout.reconfigure(encoding='utf-8')
35
- if hasattr(sys.stderr, 'reconfigure'):
36
- sys.stderr.reconfigure(encoding='utf-8')
37
- if hasattr(sys.stdin, 'reconfigure'):
38
- sys.stdin.reconfigure(encoding='utf-8')
39
-
40
- # Ensure Windows console is in UTF-8 mode
41
- try:
42
- # Try to set console mode to UTF-8
43
- os.system("chcp 65001 > nul")
44
-
45
- # Redirect stdout through a UTF-8 writer
46
- sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace', line_buffering=True)
47
- sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace', line_buffering=True)
48
- except Exception as e:
49
- print(f"Warning: Failed to set up UTF-8 encoding: {str(e)}")
50
-
51
- # If bash_path is not provided, try to detect it
52
- if self.bash_path is None:
53
- if platform.system() == "Windows":
54
- # Common paths for Git Bash on Windows
55
- possible_paths = [
56
- r"C:\Program Files\Git\bin\bash.exe",
57
- r"C:\Program Files (x86)\Git\bin\bash.exe",
58
- ]
59
- for path in possible_paths:
60
- if os.path.exists(path):
61
- self.bash_path = path
62
- break
63
- if self.bash_path is None:
64
- raise FileNotFoundError("Could not find Git Bash executable. Please specify the path manually.")
65
- else:
66
- # On Unix-like systems, bash is usually in the PATH
67
- self.bash_path = "bash"
68
-
69
- # Start the bash process
70
- self.start_process()
71
-
72
- def start_process(self):
73
- """Start the Bash process."""
74
- # Create a subprocess with pipe for stdin, stdout, and stderr
75
- bash_args = [self.bash_path]
76
-
77
- # Set UTF-8 codepage for Windows
78
- env = os.environ.copy()
79
- if platform.system() == "Windows":
80
- # Set codepage to UTF-8 (65001) - run this before starting the process
81
- os.system("chcp 65001 > nul")
82
- # Set environment variables for proper UTF-8 handling
83
- env["PYTHONIOENCODING"] = "utf-8"
84
- env["PYTHONUTF8"] = "1"
85
- # Add additional environment variables for Windows CMD
86
- env["LANG"] = "en_US.UTF-8"
87
- env["LC_ALL"] = "en_US.UTF-8"
88
-
89
- # Create the process with binary pipes for better control over encoding
90
- if platform.system() == "Windows":
91
- # On Windows, we need special handling for UTF-8
92
- self.process = subprocess.Popen(
93
- bash_args,
94
- stdin=subprocess.PIPE,
95
- stdout=subprocess.PIPE,
96
- stderr=subprocess.STDOUT, # Redirect stderr to stdout
97
- bufsize=0, # Unbuffered
98
- universal_newlines=False, # Use binary mode
99
- env=env # Pass the modified environment
100
- )
101
-
102
- # Create UTF-8 wrappers for stdin/stdout
103
- self.stdin = io.TextIOWrapper(self.process.stdin, encoding='utf-8', errors='replace', line_buffering=True)
104
- self.stdout = io.TextIOWrapper(self.process.stdout, encoding='utf-8', errors='replace', line_buffering=True)
105
- else:
106
- # On Unix systems, the standard approach works fine
107
- self.process = subprocess.Popen(
108
- bash_args,
109
- stdin=subprocess.PIPE,
110
- stdout=subprocess.PIPE,
111
- stderr=subprocess.STDOUT, # Redirect stderr to stdout
112
- text=True, # Use text mode for input/output
113
- bufsize=0, # Unbuffered
114
- universal_newlines=True, # Universal newlines mode
115
- env=env, # Pass the modified environment
116
- encoding='utf-8', # Explicitly set encoding to UTF-8
117
- errors='replace' # Replace invalid characters instead of failing
118
- )
119
- self.stdin = self.process.stdin
120
- self.stdout = self.process.stdout
121
-
122
- # Set up a more reliable environment
123
- setup_commands = [
124
- "export PS1='$ '", # Simple prompt to avoid parsing issues
125
- "export TERM=dumb", # Disable color codes and other terminal features
126
- "set +o history", # Disable history
127
- "shopt -s expand_aliases", # Enable alias expansion
128
- "export LANG=en_US.UTF-8", # Set UTF-8 locale
129
- "export LC_ALL=en_US.UTF-8", # Set all locale categories to UTF-8
130
- ]
131
-
132
- # Additional setup for Windows to handle UTF-8
133
- if platform.system() == "Windows":
134
- setup_commands.extend([
135
- # Force Git Bash to use UTF-8
136
- "export LESSCHARSET=utf-8",
137
- # Ensure proper display of Unicode characters
138
- "export PYTHONIOENCODING=utf-8"
139
- ])
140
-
141
- # Send setup commands
142
- for cmd in setup_commands:
143
- self._send_command(cmd)
144
-
145
- # Clear initial output with a marker
146
- marker = f"INIT_COMPLETE_{uuid.uuid4().hex}"
147
- self._send_command(f"echo {marker}")
148
-
149
- while True:
150
- line = self.stdout.readline().strip()
151
- if marker in line:
152
- break
153
-
154
- def _send_command(self, command):
155
- """Send a command to the Bash process without reading the output."""
156
- if self.process is None or self.process.poll() is not None:
157
- self.start_process()
158
-
159
- # Use our stdin wrapper instead of process.stdin directly
160
- self.stdin.write(command + "\n")
161
- self.stdin.flush()
162
-
163
- def execute(self, command, timeout=None):
164
- """
165
- Execute a command in the Bash session and return the output.
166
-
167
- Args:
168
- command (str): The command to execute.
169
- timeout (int, optional): Timeout in seconds. If None, no timeout is applied.
170
-
171
- Returns:
172
- str: The command output.
173
- """
174
- if self.process is None or self.process.poll() is not None:
175
- # Process has terminated, restart it
176
- self.start_process()
177
-
178
- # Create a unique marker to identify the end of output
179
- end_marker = f"END_OF_COMMAND_{uuid.uuid4().hex}"
180
-
181
- # Construct the wrapped command with echo markers
182
- # Only use timeout when explicitly requested
183
- if timeout is not None and timeout > 0:
184
- # Check if timeout command is available
185
- is_timeout_available = False
186
- try:
187
- check_cmd = "command -v timeout > /dev/null 2>&1 && echo available || echo unavailable"
188
- self._send_command(check_cmd)
189
- for _ in range(10): # Read up to 10 lines to find the result
190
- line = self.stdout.readline().strip()
191
- if "available" in line:
192
- is_timeout_available = True
193
- break
194
- elif "unavailable" in line:
195
- is_timeout_available = False
196
- break
197
- except:
198
- is_timeout_available = False
199
-
200
- if is_timeout_available:
201
- # For timeout to work with shell syntax, we need to use bash -c
202
- wrapped_command = f"timeout {timeout}s bash -c \"{command.replace('"', '\\"')}\" 2>&1; echo '{end_marker}'"
203
- else:
204
- wrapped_command = f"{command} 2>&1; echo '{end_marker}'"
205
- else:
206
- wrapped_command = f"{command} 2>&1; echo '{end_marker}'"
207
-
208
- # Send the command
209
- self._send_command(wrapped_command)
210
-
211
- # Import the console here to avoid circular imports
212
- from janito.tools.rich_console import console
213
-
214
- # Collect output until the end marker is found
215
- output_lines = []
216
- start_time = time.time()
217
- max_wait = timeout if timeout is not None else 3600 # Default to 1 hour if no timeout
218
-
219
- # Check if we're being run from the main bash_tool function
220
- # which will handle interruption
221
- try:
222
- from janito.tools.bash.bash import _command_interrupted
223
- except ImportError:
224
- _command_interrupted = False
225
-
226
- while time.time() - start_time < max_wait + 5: # Add buffer time
227
- # Check if we've been interrupted
228
- if '_command_interrupted' in globals() and _command_interrupted:
229
- # Send Ctrl+C to the running process
230
- if self.process and self.process.poll() is None:
231
- try:
232
- # On Windows, we need to use CTRL_C_EVENT
233
- import signal
234
- self.process.send_signal(signal.CTRL_C_EVENT)
235
- except:
236
- # If that fails, try to terminate the process
237
- try:
238
- self.process.terminate()
239
- except:
240
- pass
241
-
242
- # Add message to output
243
- interrupt_msg = "Command interrupted by user (Ctrl+C)"
244
- console.print(f"[bold red]{interrupt_msg}[/bold red]")
245
- output_lines.append(interrupt_msg)
246
-
247
- # Reset the bash session
248
- self.close()
249
- self.start_process()
250
-
251
- break
252
-
253
- try:
254
- line = self.stdout.readline().rstrip('\r\n')
255
- if end_marker in line:
256
- break
257
-
258
- # Print the output to the console in real-time if not in trust mode
259
- if line:
260
- from janito.config import get_config
261
- if not get_config().trust_mode:
262
- console.print(line)
263
-
264
- output_lines.append(line)
265
- except UnicodeDecodeError as e:
266
- # Handle potential UTF-8 decoding errors
267
- error_msg = f"[Warning: Unicode decode error occurred: {str(e)}]"
268
- console.print(error_msg, style="yellow")
269
- output_lines.append(error_msg)
270
- # Just continue with replacement character
271
- continue
272
- except Exception as e:
273
- error_msg = f"[Error reading output: {str(e)}]"
274
- console.print(error_msg, style="red")
275
- output_lines.append(error_msg)
276
- continue
277
-
278
- # Check for timeout
279
- if time.time() - start_time >= max_wait + 5 and not _command_interrupted:
280
- timeout_msg = f"Error: Command timed out after {max_wait} seconds"
281
- console.print(timeout_msg, style="red bold")
282
- output_lines.append(timeout_msg)
283
-
284
- # Try to reset the bash session after a timeout
285
- self.close()
286
- self.start_process()
287
-
288
- return "\n".join(output_lines)
289
-
290
- def windows_to_bash_path(self, windows_path):
291
- """
292
- Convert a Windows path to a Git Bash compatible path.
293
-
294
- Args:
295
- windows_path (str): A Windows path like 'C:\\folder\\file.txt'
296
-
297
- Returns:
298
- str: Git Bash compatible path like '/c/folder/file.txt'
299
- """
300
- if not windows_path or not platform.system() == "Windows":
301
- return windows_path
302
-
303
- # Handle drive letter (e.g., C: -> /c)
304
- if ":" in windows_path:
305
- drive, path = windows_path.split(":", 1)
306
- unix_path = f"/{drive.lower()}{path}"
307
- else:
308
- unix_path = windows_path
309
-
310
- # Convert backslashes to forward slashes
311
- unix_path = unix_path.replace("\\", "/")
312
-
313
- # Remove any double slashes
314
- while "//" in unix_path:
315
- unix_path = unix_path.replace("//", "/")
316
-
317
- # If the path contains spaces, we need to escape them or quote the entire path
318
- if " " in unix_path:
319
- unix_path = f'"{unix_path}"'
320
-
321
- return unix_path
322
-
323
- def close(self):
324
- """Close the Bash session."""
325
- if self.process and self.process.poll() is None:
326
- try:
327
- self._send_command("exit")
328
- self.process.wait(timeout=2)
329
- except:
330
- pass
331
- finally:
332
- try:
333
- self.process.terminate()
334
- except:
335
- pass
336
-
337
- self.process = None
338
-
339
- def __del__(self):
340
- """Destructor to ensure the process is closed."""
341
- self.close()
@@ -1,90 +0,0 @@
1
- """
2
- Decorators for janito tools.
3
- """
4
- import functools
5
- import string
6
- from typing import Any, Callable, Dict, Optional
7
-
8
-
9
- class ToolMetaFormatter(string.Formatter):
10
- """Custom string formatter that handles conditional expressions in format strings."""
11
-
12
- def get_value(self, key, args, kwargs):
13
- """Override to handle conditional expressions."""
14
- if key in kwargs:
15
- return kwargs[key]
16
-
17
- # Try to evaluate the key as a Python expression
18
- try:
19
- # Create a safe local namespace with only the parameters
20
- return eval(key, {"__builtins__": {}}, kwargs)
21
- except Exception:
22
- return f"[{key}]"
23
-
24
-
25
- def tool_meta(label: str):
26
- """
27
- Decorator to add metadata to a tool function.
28
-
29
- Args:
30
- label: A format string that can reference function parameters.
31
- Example: "Finding files {pattern}, on {root_dir}"
32
-
33
- Returns:
34
- Decorated function with metadata attached
35
- """
36
- def decorator(func: Callable):
37
- @functools.wraps(func)
38
- def wrapper(*args, **kwargs):
39
- return func(*args, **kwargs)
40
-
41
- # Attach metadata to the function
42
- wrapper._tool_meta = {
43
- 'label': label
44
- }
45
-
46
- return wrapper
47
-
48
- return decorator
49
-
50
-
51
- def tool(func: Callable):
52
- """
53
- Basic decorator for tool functions.
54
-
55
- This decorator marks a function as a tool and can be used for
56
- simpler tools that don't need additional metadata.
57
-
58
- Returns:
59
- Decorated function
60
- """
61
- @functools.wraps(func)
62
- def wrapper(*args, **kwargs):
63
- return func(*args, **kwargs)
64
-
65
- return wrapper
66
-
67
-
68
- def format_tool_label(func: Callable, tool_input: Dict[str, Any]) -> Optional[str]:
69
- """
70
- Format the tool label using the function's parameters.
71
-
72
- Args:
73
- func: The tool function
74
- tool_input: Input parameters for the tool
75
-
76
- Returns:
77
- Formatted label string or None if no label is defined
78
- """
79
- if not hasattr(func, '_tool_meta') or 'label' not in func._tool_meta:
80
- return None
81
-
82
- # Get the label template
83
- label_template = func._tool_meta['label']
84
-
85
- # Format the label with the parameters
86
- try:
87
- formatter = ToolMetaFormatter()
88
- return formatter.format(label_template, **tool_input)
89
- except Exception:
90
- return f"{func.__name__}"