janito 0.14.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 (98) 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.14.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.14.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 -172
  56. janito/cli/agent/query.py +0 -108
  57. janito/cli/agent.py +0 -12
  58. janito/cli/app.py +0 -182
  59. janito/cli/commands/__init__.py +0 -12
  60. janito/cli/commands/config.py +0 -242
  61. janito/cli/commands/history.py +0 -119
  62. janito/cli/commands/profile.py +0 -72
  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.py +0 -375
  69. janito/data/instructions_template.txt +0 -31
  70. janito/token_report.py +0 -154
  71. janito/tools/__init__.py +0 -44
  72. janito/tools/bash/bash.py +0 -84
  73. janito/tools/bash/unix_persistent_bash.py +0 -184
  74. janito/tools/bash/win_persistent_bash.py +0 -308
  75. janito/tools/decorators.py +0 -90
  76. janito/tools/delete_file.py +0 -65
  77. janito/tools/fetch_webpage/__init__.py +0 -23
  78. janito/tools/fetch_webpage/core.py +0 -182
  79. janito/tools/find_files.py +0 -220
  80. janito/tools/move_file.py +0 -72
  81. janito/tools/prompt_user.py +0 -57
  82. janito/tools/replace_file.py +0 -63
  83. janito/tools/rich_console.py +0 -176
  84. janito/tools/search_text.py +0 -226
  85. janito/tools/str_replace_editor/__init__.py +0 -6
  86. janito/tools/str_replace_editor/editor.py +0 -55
  87. janito/tools/str_replace_editor/handlers/__init__.py +0 -16
  88. janito/tools/str_replace_editor/handlers/create.py +0 -60
  89. janito/tools/str_replace_editor/handlers/insert.py +0 -100
  90. janito/tools/str_replace_editor/handlers/str_replace.py +0 -94
  91. janito/tools/str_replace_editor/handlers/undo.py +0 -64
  92. janito/tools/str_replace_editor/handlers/view.py +0 -159
  93. janito/tools/str_replace_editor/utils.py +0 -33
  94. janito/tools/think.py +0 -37
  95. janito/tools/usage_tracker.py +0 -137
  96. janito-0.14.0.dist-info/METADATA +0 -396
  97. janito-0.14.0.dist-info/RECORD +0 -53
  98. janito-0.14.0.dist-info/entry_points.txt +0 -2
janito/tools/bash/bash.py DELETED
@@ -1,84 +0,0 @@
1
- from typing import Optional
2
- from typing import Tuple
3
- import threading
4
- import platform
5
- import re
6
- from janito.config import get_config
7
- from janito.tools.usage_tracker import get_tracker
8
- from janito.tools.rich_console import console, print_info
9
-
10
- # Import the appropriate implementation based on the platform
11
- if platform.system() == "Windows":
12
- from janito.tools.bash.win_persistent_bash import PersistentBash
13
- else:
14
- from janito.tools.bash.unix_persistent_bash import PersistentBash
15
-
16
- # Global instance of PersistentBash to maintain state between calls
17
- _bash_session = None
18
- _session_lock = threading.RLock() # Use RLock to allow reentrant locking
19
-
20
- def bash_tool(command: str, restart: Optional[bool] = False) -> Tuple[str, bool]:
21
- """
22
- Execute a bash command using a persistent Bash session.
23
- The appropriate implementation (Windows or Unix) is selected based on the detected platform.
24
- When in ask mode, only read-only commands are allowed.
25
- Output is printed to the console in real-time as it's received.
26
-
27
- Args:
28
- command: The bash command to execute
29
- restart: Whether to restart the bash session
30
-
31
- Returns:
32
- A tuple containing (output message, is_error flag)
33
- """
34
- # Import console for printing output in real-time
35
- from janito.tools.rich_console import console, print_info
36
-
37
- # Only print command if not in trust mode
38
- if not get_config().trust_mode:
39
- print_info(f"{command}", "Bash Run")
40
- global _bash_session
41
-
42
- # Check if in ask mode and if the command might modify files
43
- if get_config().ask_mode:
44
- # List of potentially modifying commands
45
- modifying_patterns = [
46
- r'\brm\b', r'\bmkdir\b', r'\btouch\b', r'\becho\b.*[>\|]', r'\bmv\b', r'\bcp\b',
47
- r'\bchmod\b', r'\bchown\b', r'\bsed\b.*-i', r'\bawk\b.*[>\|]', r'\bcat\b.*[>\|]',
48
- r'\bwrite\b', r'\binstall\b', r'\bapt\b', r'\byum\b', r'\bpip\b.*install',
49
- r'\bnpm\b.*install', r'\bdocker\b', r'\bkubectl\b.*apply', r'\bgit\b.*commit',
50
- r'\bgit\b.*push', r'\bgit\b.*merge', r'\bdd\b'
51
- ]
52
-
53
- # Check if command matches any modifying pattern
54
- for pattern in modifying_patterns:
55
- if re.search(pattern, command, re.IGNORECASE):
56
- return ("Cannot execute potentially modifying commands in ask mode. Use --ask option to disable modifications.", True)
57
-
58
- with _session_lock:
59
- # Initialize or restart the session if needed
60
- if _bash_session is None or restart:
61
- if _bash_session is not None:
62
- _bash_session.close()
63
- # Get GitBash path from config (None means auto-detect)
64
- gitbash_path = get_config().gitbash_path
65
- _bash_session = PersistentBash(bash_path=gitbash_path)
66
-
67
- try:
68
- # Execute the command - output will be printed to console in real-time
69
- output = _bash_session.execute(command)
70
-
71
- # Track bash command execution
72
- get_tracker().increment('bash_commands')
73
-
74
- # Always assume execution was successful
75
- is_error = False
76
-
77
- # Return the output as a string (even though it was already printed in real-time)
78
- return output, is_error
79
-
80
- except Exception as e:
81
- # Handle any exceptions that might occur
82
- error_message = f"Error executing bash command: {str(e)}"
83
- console.print(error_message, style="red bold")
84
- return error_message, True
@@ -1,184 +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
- while time.time() - start_time < max_wait + 5: # Add buffer time
136
- try:
137
- line = self.process.stdout.readline().rstrip('\r\n')
138
- if end_marker in line:
139
- break
140
-
141
- # Print the output to the console in real-time if not in trust mode
142
- if line:
143
- from janito.config import get_config
144
- if not get_config().trust_mode:
145
- console.print(line)
146
-
147
- output_lines.append(line)
148
- except Exception as e:
149
- error_msg = f"[Error reading output: {str(e)}]"
150
- console.print(error_msg, style="red")
151
- output_lines.append(error_msg)
152
- continue
153
-
154
- # Check for timeout
155
- if time.time() - start_time >= max_wait + 5:
156
- timeout_msg = f"Error: Command timed out after {max_wait} seconds"
157
- console.print(timeout_msg, style="red bold")
158
- output_lines.append(timeout_msg)
159
-
160
- # Try to reset the bash session after a timeout
161
- self.close()
162
- self.start_process()
163
-
164
- return "\n".join(output_lines)
165
-
166
- def close(self):
167
- """Close the Bash session."""
168
- if self.process and self.process.poll() is None:
169
- try:
170
- self._send_command("exit")
171
- self.process.wait(timeout=2)
172
- except:
173
- pass
174
- finally:
175
- try:
176
- self.process.terminate()
177
- except:
178
- pass
179
-
180
- self.process = None
181
-
182
- def __del__(self):
183
- """Destructor to ensure the process is closed."""
184
- self.close()
@@ -1,308 +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
- while time.time() - start_time < max_wait + 5: # Add buffer time
220
- try:
221
- line = self.stdout.readline().rstrip('\r\n')
222
- if end_marker in line:
223
- break
224
-
225
- # Print the output to the console in real-time if not in trust mode
226
- if line:
227
- from janito.config import get_config
228
- if not get_config().trust_mode:
229
- console.print(line)
230
-
231
- output_lines.append(line)
232
- except UnicodeDecodeError as e:
233
- # Handle potential UTF-8 decoding errors
234
- error_msg = f"[Warning: Unicode decode error occurred: {str(e)}]"
235
- console.print(error_msg, style="yellow")
236
- output_lines.append(error_msg)
237
- # Just continue with replacement character
238
- continue
239
- except Exception as e:
240
- error_msg = f"[Error reading output: {str(e)}]"
241
- console.print(error_msg, style="red")
242
- output_lines.append(error_msg)
243
- continue
244
-
245
- # Check for timeout
246
- if time.time() - start_time >= max_wait + 5:
247
- timeout_msg = f"Error: Command timed out after {max_wait} seconds"
248
- console.print(timeout_msg, style="red bold")
249
- output_lines.append(timeout_msg)
250
-
251
- # Try to reset the bash session after a timeout
252
- self.close()
253
- self.start_process()
254
-
255
- return "\n".join(output_lines)
256
-
257
- def windows_to_bash_path(self, windows_path):
258
- """
259
- Convert a Windows path to a Git Bash compatible path.
260
-
261
- Args:
262
- windows_path (str): A Windows path like 'C:\\folder\\file.txt'
263
-
264
- Returns:
265
- str: Git Bash compatible path like '/c/folder/file.txt'
266
- """
267
- if not windows_path or not platform.system() == "Windows":
268
- return windows_path
269
-
270
- # Handle drive letter (e.g., C: -> /c)
271
- if ":" in windows_path:
272
- drive, path = windows_path.split(":", 1)
273
- unix_path = f"/{drive.lower()}{path}"
274
- else:
275
- unix_path = windows_path
276
-
277
- # Convert backslashes to forward slashes
278
- unix_path = unix_path.replace("\\", "/")
279
-
280
- # Remove any double slashes
281
- while "//" in unix_path:
282
- unix_path = unix_path.replace("//", "/")
283
-
284
- # If the path contains spaces, we need to escape them or quote the entire path
285
- if " " in unix_path:
286
- unix_path = f'"{unix_path}"'
287
-
288
- return unix_path
289
-
290
- def close(self):
291
- """Close the Bash session."""
292
- if self.process and self.process.poll() is None:
293
- try:
294
- self._send_command("exit")
295
- self.process.wait(timeout=2)
296
- except:
297
- pass
298
- finally:
299
- try:
300
- self.process.terminate()
301
- except:
302
- pass
303
-
304
- self.process = None
305
-
306
- def __del__(self):
307
- """Destructor to ensure the process is closed."""
308
- 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__}"