janito 0.14.0__py3-none-any.whl → 0.15.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.
@@ -0,0 +1,282 @@
1
+ """
2
+ Singleton implementation of the Config class for Janito.
3
+ """
4
+ import os
5
+ from typing import Dict, Any, Optional, Union
6
+
7
+ from .properties import ConfigProperties
8
+ from .file_operations import (
9
+ get_global_config_path,
10
+ get_local_config_path,
11
+ load_config_file,
12
+ save_config_file,
13
+ merge_configs
14
+ )
15
+ from ..profiles.manager import get_profile
16
+ from ..profiles.definitions import PROFILES
17
+
18
+ class Config(ConfigProperties):
19
+ """Singleton configuration class for Janito."""
20
+ _instance = None
21
+
22
+ def __new__(cls):
23
+ if cls._instance is None:
24
+ cls._instance = super(Config, cls).__new__(cls)
25
+ cls._instance._workspace_dir = os.getcwd()
26
+ cls._instance._verbose = False
27
+ cls._instance._ask_mode = False
28
+ cls._instance._trust_mode = False
29
+ cls._instance._no_tools = False
30
+ cls._instance._show_usage_report = True # Enabled by default
31
+
32
+ # Set technical profile as default
33
+ profile_data = PROFILES["technical"]
34
+ cls._instance._temperature = profile_data["temperature"]
35
+ cls._instance._profile = "technical"
36
+ cls._instance._role = "software engineer"
37
+ cls._instance._gitbash_path = None # Default to None for auto-detection
38
+ # Default max_view_lines will be retrieved from merged_config
39
+
40
+ # Initialize configuration storage
41
+ cls._instance._global_config = {}
42
+ cls._instance._local_config = {}
43
+ cls._instance._merged_config = {}
44
+
45
+ # Load configurations
46
+ cls._instance._load_config()
47
+ return cls._instance
48
+
49
+ def _load_config(self) -> None:
50
+ """Load both global and local configurations and merge them."""
51
+ # Load global config
52
+ global_config_path = get_global_config_path()
53
+ self._global_config = load_config_file(global_config_path)
54
+
55
+ # Load local config
56
+ local_config_path = get_local_config_path(self._workspace_dir)
57
+ self._local_config = load_config_file(local_config_path)
58
+
59
+ # Remove runtime-only settings from config files if they exist
60
+ self._clean_runtime_settings()
61
+
62
+ # Merge configurations (local overrides global)
63
+ self._merge_configs()
64
+
65
+ # Apply merged configuration to instance variables
66
+ self._apply_config()
67
+
68
+ def _clean_runtime_settings(self) -> None:
69
+ """Remove runtime-only settings from configuration files if they exist."""
70
+ runtime_settings = ["ask_mode"]
71
+ config_changed = False
72
+
73
+ # Remove from local config
74
+ for setting in runtime_settings:
75
+ if setting in self._local_config:
76
+ del self._local_config[setting]
77
+ config_changed = True
78
+
79
+ # Remove from global config
80
+ for setting in runtime_settings:
81
+ if setting in self._global_config:
82
+ del self._global_config[setting]
83
+ config_changed = True
84
+
85
+ # Save changes if needed
86
+ if config_changed:
87
+ self._save_local_config()
88
+ self._save_global_config()
89
+
90
+ def _merge_configs(self) -> None:
91
+ """Merge global and local configurations with local taking precedence."""
92
+ self._merged_config = merge_configs(self._global_config, self._local_config)
93
+
94
+ def _apply_config(self) -> None:
95
+ """Apply the merged configuration to instance variables."""
96
+ config_data = self._merged_config
97
+
98
+ # Apply configuration values to instance variables
99
+ if "debug_mode" in config_data:
100
+ self._verbose = config_data["debug_mode"]
101
+ if "verbose" in config_data:
102
+ self._verbose = config_data["verbose"]
103
+ # ask_mode is a runtime-only setting, not loaded from config
104
+ if "trust_mode" in config_data:
105
+ self._trust_mode = config_data["trust_mode"]
106
+ if "show_usage_report" in config_data:
107
+ self._show_usage_report = config_data["show_usage_report"]
108
+ if "temperature" in config_data:
109
+ self._temperature = config_data["temperature"]
110
+ if "profile" in config_data:
111
+ self._profile = config_data["profile"]
112
+ if "role" in config_data:
113
+ self._role = config_data["role"]
114
+ if "gitbash_path" in config_data:
115
+ self._gitbash_path = config_data["gitbash_path"]
116
+ # max_view_lines is accessed directly from merged_config
117
+
118
+ def _save_local_config(self) -> None:
119
+ """Save local configuration to file."""
120
+ config_path = get_local_config_path(self._workspace_dir)
121
+ save_config_file(config_path, self._local_config)
122
+
123
+ def _save_global_config(self) -> None:
124
+ """Save global configuration to file."""
125
+ config_path = get_global_config_path()
126
+ save_config_file(config_path, self._global_config)
127
+
128
+ def _save_config(self) -> None:
129
+ """Save local configuration to file (for backward compatibility)."""
130
+ self._save_local_config()
131
+
132
+ def set_profile(self, profile_name: str, config_type: str = "local") -> None:
133
+ """
134
+ Set parameter values based on a predefined profile.
135
+
136
+ Args:
137
+ profile_name: Name of the profile to use (precise, balanced, conversational, creative, technical)
138
+ config_type: Type of configuration to update ("local" or "global")
139
+
140
+ Raises:
141
+ ValueError: If the profile name is not recognized or config_type is invalid
142
+ """
143
+ if config_type not in ["local", "global"]:
144
+ raise ValueError(f"Invalid config_type: {config_type}. Must be 'local' or 'global'")
145
+
146
+ profile = get_profile(profile_name)
147
+
148
+ # Update the appropriate configuration
149
+ if config_type == "local":
150
+ self.set_local_config("temperature", profile["temperature"])
151
+ self.set_local_config("profile", profile_name)
152
+ else:
153
+ self.set_global_config("temperature", profile["temperature"])
154
+ self.set_global_config("profile", profile_name)
155
+
156
+ @staticmethod
157
+ def get_available_profiles() -> Dict[str, Dict[str, Any]]:
158
+ """Get all available predefined profiles."""
159
+ from ..profiles.manager import get_available_profiles
160
+ return get_available_profiles()
161
+
162
+ def set_local_config(self, key: str, value: Any) -> None:
163
+ """
164
+ Set a configuration value in the local configuration.
165
+
166
+ Args:
167
+ key: Configuration key
168
+ value: Configuration value
169
+ """
170
+ self._local_config[key] = value
171
+ self._save_local_config()
172
+
173
+ # Re-merge and apply configurations
174
+ self._merge_configs()
175
+ self._apply_config()
176
+
177
+ def set_global_config(self, key: str, value: Any) -> None:
178
+ """
179
+ Set a configuration value in the global configuration.
180
+
181
+ Args:
182
+ key: Configuration key
183
+ value: Configuration value
184
+ """
185
+ self._global_config[key] = value
186
+ self._save_global_config()
187
+
188
+ # Re-merge and apply configurations
189
+ self._merge_configs()
190
+ self._apply_config()
191
+
192
+ def get_local_config(self) -> Dict[str, Any]:
193
+ """
194
+ Get the local configuration.
195
+
196
+ Returns:
197
+ Dict containing the local configuration
198
+ """
199
+ return self._local_config.copy()
200
+
201
+ def get_global_config(self) -> Dict[str, Any]:
202
+ """
203
+ Get the global configuration.
204
+
205
+ Returns:
206
+ Dict containing the global configuration
207
+ """
208
+ return self._global_config.copy()
209
+
210
+ def get_merged_config(self) -> Dict[str, Any]:
211
+ """
212
+ Get the merged configuration.
213
+
214
+ Returns:
215
+ Dict containing the merged configuration
216
+ """
217
+ return self._merged_config.copy()
218
+
219
+ @staticmethod
220
+ def set_api_key(api_key: str) -> None:
221
+ """
222
+ Set the API key in the global configuration file.
223
+
224
+ Args:
225
+ api_key: The Anthropic API key to store
226
+ """
227
+ # Get the singleton instance
228
+ config = Config()
229
+
230
+ # Set the API key in the global configuration
231
+ config.set_global_config("api_key", api_key)
232
+ print(f"API key saved to {get_global_config_path()}")
233
+
234
+ @staticmethod
235
+ def get_api_key() -> Optional[str]:
236
+ """
237
+ Get the API key from the global configuration file.
238
+
239
+ Returns:
240
+ The API key if found, None otherwise
241
+ """
242
+ # Get the singleton instance
243
+ config = Config()
244
+
245
+ # Get the API key from the merged configuration
246
+ return config.get_merged_config().get("api_key")
247
+
248
+ def reset_local_config(self) -> bool:
249
+ """
250
+ Reset local configuration by removing the local config file.
251
+
252
+ Returns:
253
+ bool: True if the config file was removed, False if it didn't exist
254
+ """
255
+ config_path = get_local_config_path(self._workspace_dir)
256
+ if config_path.exists():
257
+ config_path.unlink()
258
+ # Clear local configuration
259
+ self._local_config = {}
260
+ # Re-merge and apply configurations
261
+ self._merge_configs()
262
+ self._apply_config()
263
+ return True
264
+ return False
265
+
266
+ def reset_global_config(self) -> bool:
267
+ """
268
+ Reset global configuration by removing the global config file.
269
+
270
+ Returns:
271
+ bool: True if the config file was removed, False if it didn't exist
272
+ """
273
+ config_path = get_global_config_path()
274
+ if config_path.exists():
275
+ config_path.unlink()
276
+ # Clear global configuration
277
+ self._global_config = {}
278
+ # Re-merge and apply configurations
279
+ self._merge_configs()
280
+ self._apply_config()
281
+ return True
282
+ return False
@@ -0,0 +1,8 @@
1
+ """
2
+ Profile management for Janito configuration.
3
+ Provides predefined parameter profiles and related functionality.
4
+ """
5
+ from .definitions import PROFILES
6
+ from .manager import get_profile, get_available_profiles
7
+
8
+ __all__ = ["PROFILES", "get_profile", "get_available_profiles"]
@@ -0,0 +1,38 @@
1
+ """
2
+ Predefined parameter profiles for Janito.
3
+ """
4
+ from typing import Dict, Any
5
+
6
+ # Predefined parameter profiles
7
+ PROFILES = {
8
+ "precise": {
9
+ "temperature": 0.2,
10
+ "top_p": 0.85,
11
+ "top_k": 20,
12
+ "description": "Factual answers, documentation, structured data, avoiding hallucinations"
13
+ },
14
+ "balanced": {
15
+ "temperature": 0.5,
16
+ "top_p": 0.9,
17
+ "top_k": 40,
18
+ "description": "Professional writing, summarization, everyday tasks with moderate creativity"
19
+ },
20
+ "conversational": {
21
+ "temperature": 0.7,
22
+ "top_p": 0.9,
23
+ "top_k": 45,
24
+ "description": "Natural dialogue, educational content, support conversations"
25
+ },
26
+ "creative": {
27
+ "temperature": 0.9,
28
+ "top_p": 0.95,
29
+ "top_k": 70,
30
+ "description": "Storytelling, brainstorming, marketing copy, poetry"
31
+ },
32
+ "technical": {
33
+ "temperature": 0.3,
34
+ "top_p": 0.95,
35
+ "top_k": 15,
36
+ "description": "Code generation, debugging, decision analysis, technical problem-solving"
37
+ }
38
+ }
@@ -0,0 +1,80 @@
1
+ """
2
+ Profile management functions for Janito configuration.
3
+ """
4
+ from typing import Dict, Any
5
+
6
+ from .definitions import PROFILES
7
+
8
+ def get_available_profiles() -> Dict[str, Dict[str, Any]]:
9
+ """
10
+ Get all available predefined profiles.
11
+
12
+ Returns:
13
+ Dictionary of profile names to profile settings
14
+ """
15
+ return PROFILES
16
+
17
+ def get_profile(profile_name: str) -> Dict[str, Any]:
18
+ """
19
+ Get a specific profile by name.
20
+
21
+ Args:
22
+ profile_name: Name of the profile to retrieve
23
+
24
+ Returns:
25
+ Dict containing the profile settings
26
+
27
+ Raises:
28
+ ValueError: If the profile name is not recognized
29
+ """
30
+ profile_name = profile_name.lower()
31
+ if profile_name not in PROFILES:
32
+ valid_profiles = ", ".join(PROFILES.keys())
33
+ raise ValueError(f"Unknown profile: {profile_name}. Valid profiles are: {valid_profiles}")
34
+
35
+ return PROFILES[profile_name]
36
+
37
+ def create_custom_profile(name: str, temperature: float, description: str = None) -> Dict[str, Any]:
38
+ """
39
+ Create a custom profile with the given parameters.
40
+
41
+ Args:
42
+ name: Name for the custom profile
43
+ temperature: Temperature value (0.0 to 1.0)
44
+ description: Optional description for the profile
45
+
46
+ Returns:
47
+ Dict containing the profile settings
48
+
49
+ Raises:
50
+ ValueError: If temperature is not between 0.0 and 1.0
51
+ """
52
+ if temperature < 0.0 or temperature > 1.0:
53
+ raise ValueError("Temperature must be between 0.0 and 1.0")
54
+
55
+ # Determine top_p and top_k based on temperature
56
+ if temperature <= 0.3:
57
+ top_p = 0.85
58
+ top_k = 15
59
+ elif temperature <= 0.6:
60
+ top_p = 0.9
61
+ top_k = 40
62
+ else:
63
+ top_p = 0.95
64
+ top_k = 60
65
+
66
+ # Use provided description or generate a default one
67
+ if description is None:
68
+ if temperature <= 0.3:
69
+ description = "Custom precise profile"
70
+ elif temperature <= 0.6:
71
+ description = "Custom balanced profile"
72
+ else:
73
+ description = "Custom creative profile"
74
+
75
+ return {
76
+ "temperature": temperature,
77
+ "top_p": top_p,
78
+ "top_k": top_k,
79
+ "description": description
80
+ }
@@ -1,7 +1,7 @@
1
1
  You are a {{ role }}, using the name Janito .
2
2
  You will be assisting an user using a computer system on a {{ platform }} platform.
3
3
  You can find more about the current project using the tools in the workspace directory.
4
- If the question is related to the project, use the tools using the relative path, filename instead of /filename.
4
+ If the question is related to the project, use the tools using the relative path, ./filename instead of /filename.
5
5
 
6
6
  If creating or editing files with a large number of lines, organize them into smaller files.
7
7
  If creating or editing files in an existing directory check surrounding files for the used patterns.
@@ -17,15 +17,18 @@ The bash tool does not support commands which will require user input.
17
17
  Use the bash tool to get the current date or time when needed.
18
18
  Prefer the str_replace_editor tool to view directories and file contents.
19
19
 
20
- </IMPORTANT>
20
+ <IMPORTANT>
21
21
  Call the user_prompt tool when:
22
22
  - There are multiple options to apply a certain change
23
23
  - The next operation risk is moderated or high
24
24
  - The implementation plan is complex, requiring a review
25
25
  Proceed according to the user answer.
26
- <IMPORTANT/>
26
+ </IMPORTANT>
27
27
 
28
28
  When changing code in Python files, be mindful about the need to review the imports specially when new type hints are used (eg. Optional, Tuple, List, Dict, etc).
29
29
  After performing changes to a project in interfaces which are exposed to the user, respond to the user with a short summary on how to verify the changes. eg. "run cmd xpto", prefer to provide a command to run instead of a description.
30
30
  When displaying commands in instructions to the user, consider their platform.
31
31
  When creating html pages which refer to images that should be manually placed by the user, instead of broken links provide a frame with a placeholder image.
32
+
33
+ If STRUCTURE.md was updated add it to the list of files to be committed.
34
+ After significant changes, run git commit with a message describing the changes made.
janito/tools/bash/bash.py CHANGED
@@ -3,6 +3,9 @@ from typing import Tuple
3
3
  import threading
4
4
  import platform
5
5
  import re
6
+ import queue
7
+ import signal
8
+ import time
6
9
  from janito.config import get_config
7
10
  from janito.tools.usage_tracker import get_tracker
8
11
  from janito.tools.rich_console import console, print_info
@@ -16,6 +19,42 @@ else:
16
19
  # Global instance of PersistentBash to maintain state between calls
17
20
  _bash_session = None
18
21
  _session_lock = threading.RLock() # Use RLock to allow reentrant locking
22
+ _current_bash_thread = None
23
+ _command_interrupted = False
24
+
25
+ def _execute_bash_command(command, result_queue):
26
+ """
27
+ Execute a bash command in a separate thread.
28
+
29
+ Args:
30
+ command: The bash command to execute
31
+ result_queue: Queue to store the result
32
+ """
33
+ global _bash_session, _command_interrupted
34
+
35
+ try:
36
+ # Execute the command - output will be printed to console in real-time
37
+ output = _bash_session.execute(command)
38
+
39
+ # Put the result in the queue if the command wasn't interrupted
40
+ if not _command_interrupted:
41
+ result_queue.put((output, False))
42
+ except Exception as e:
43
+ # Handle any exceptions that might occur
44
+ error_message = f"Error executing bash command: {str(e)}"
45
+ console.print(error_message, style="red bold")
46
+ result_queue.put((error_message, True))
47
+
48
+ def _keyboard_interrupt_handler(signum, frame):
49
+ """
50
+ Handle keyboard interrupt (Ctrl+C) by setting the interrupt flag.
51
+ """
52
+ global _command_interrupted
53
+ _command_interrupted = True
54
+ console.print("\n[bold red]Command interrupted by user (Ctrl+C)[/bold red]")
55
+
56
+ # Restore the default signal handler
57
+ signal.signal(signal.SIGINT, original_sigint_handler)
19
58
 
20
59
  def bash_tool(command: str, restart: Optional[bool] = False) -> Tuple[str, bool]:
21
60
  """
@@ -23,6 +62,7 @@ def bash_tool(command: str, restart: Optional[bool] = False) -> Tuple[str, bool]
23
62
  The appropriate implementation (Windows or Unix) is selected based on the detected platform.
24
63
  When in ask mode, only read-only commands are allowed.
25
64
  Output is printed to the console in real-time as it's received.
65
+ Command runs in a background thread, allowing Ctrl+C to interrupt just the command.
26
66
 
27
67
  Args:
28
68
  command: The bash command to execute
@@ -37,7 +77,9 @@ def bash_tool(command: str, restart: Optional[bool] = False) -> Tuple[str, bool]
37
77
  # Only print command if not in trust mode
38
78
  if not get_config().trust_mode:
39
79
  print_info(f"{command}", "Bash Run")
40
- global _bash_session
80
+
81
+ global _bash_session, _current_bash_thread, _command_interrupted, original_sigint_handler
82
+ _command_interrupted = False
41
83
 
42
84
  # Check if in ask mode and if the command might modify files
43
85
  if get_config().ask_mode:
@@ -65,20 +107,51 @@ def bash_tool(command: str, restart: Optional[bool] = False) -> Tuple[str, bool]
65
107
  _bash_session = PersistentBash(bash_path=gitbash_path)
66
108
 
67
109
  try:
68
- # Execute the command - output will be printed to console in real-time
69
- output = _bash_session.execute(command)
110
+ # Create a queue to get the result from the thread
111
+ result_queue = queue.Queue()
112
+
113
+ # Save the original SIGINT handler
114
+ original_sigint_handler = signal.getsignal(signal.SIGINT)
115
+
116
+ # Set our custom SIGINT handler
117
+ signal.signal(signal.SIGINT, _keyboard_interrupt_handler)
118
+
119
+ # Create and start the thread
120
+ _current_bash_thread = threading.Thread(
121
+ target=_execute_bash_command,
122
+ args=(command, result_queue)
123
+ )
124
+ _current_bash_thread.daemon = True
125
+ _current_bash_thread.start()
126
+
127
+ # Wait for the thread to complete or for an interrupt
128
+ while _current_bash_thread.is_alive() and not _command_interrupted:
129
+ _current_bash_thread.join(0.1) # Check every 100ms
130
+
131
+ # If the command was interrupted, return a message
132
+ if _command_interrupted:
133
+ # Restore the original signal handler
134
+ signal.signal(signal.SIGINT, original_sigint_handler)
135
+ return ("Command was interrupted by Ctrl+C", True)
136
+
137
+ # Get the result from the queue
138
+ output, is_error = result_queue.get(timeout=1)
139
+
140
+ # Restore the original signal handler
141
+ signal.signal(signal.SIGINT, original_sigint_handler)
70
142
 
71
143
  # Track bash command execution
72
144
  get_tracker().increment('bash_commands')
73
145
 
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)
146
+ # Return the output
78
147
  return output, is_error
79
148
 
80
149
  except Exception as e:
81
150
  # Handle any exceptions that might occur
82
151
  error_message = f"Error executing bash command: {str(e)}"
83
152
  console.print(error_message, style="red bold")
153
+
154
+ # Restore the original signal handler
155
+ signal.signal(signal.SIGINT, original_sigint_handler)
156
+
84
157
  return error_message, True
@@ -132,7 +132,38 @@ class PersistentBash:
132
132
  start_time = time.time()
133
133
  max_wait = timeout if timeout is not None else 3600 # Default to 1 hour if no timeout
134
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
+
135
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
+
136
167
  try:
137
168
  line = self.process.stdout.readline().rstrip('\r\n')
138
169
  if end_marker in line:
@@ -152,7 +183,7 @@ class PersistentBash:
152
183
  continue
153
184
 
154
185
  # Check for timeout
155
- if time.time() - start_time >= max_wait + 5:
186
+ if time.time() - start_time >= max_wait + 5 and not _command_interrupted:
156
187
  timeout_msg = f"Error: Command timed out after {max_wait} seconds"
157
188
  console.print(timeout_msg, style="red bold")
158
189
  output_lines.append(timeout_msg)
@@ -216,7 +216,40 @@ class PersistentBash:
216
216
  start_time = time.time()
217
217
  max_wait = timeout if timeout is not None else 3600 # Default to 1 hour if no timeout
218
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
+
219
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
+
220
253
  try:
221
254
  line = self.stdout.readline().rstrip('\r\n')
222
255
  if end_marker in line:
@@ -243,7 +276,7 @@ class PersistentBash:
243
276
  continue
244
277
 
245
278
  # Check for timeout
246
- if time.time() - start_time >= max_wait + 5:
279
+ if time.time() - start_time >= max_wait + 5 and not _command_interrupted:
247
280
  timeout_msg = f"Error: Command timed out after {max_wait} seconds"
248
281
  console.print(timeout_msg, style="red bold")
249
282
  output_lines.append(timeout_msg)
janito/tools/move_file.py CHANGED
@@ -64,7 +64,7 @@ def move_file(
64
64
  try:
65
65
  shutil.move(str(source_obj), str(destination_obj))
66
66
  success_msg = f"Successfully moved file from {original_source} to {original_destination}"
67
- print_success(success_msg, "Success")
67
+ print_success("", "Success")
68
68
  return (success_msg, False)
69
69
  except Exception as e:
70
70
  error_msg = f"Error moving file from {original_source} to {original_destination}: {str(e)}"