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,80 +0,0 @@
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,34 +0,0 @@
1
- You are a {{ role }}, using the name Janito .
2
- You will be assisting an user using a computer system on a {{ platform }} platform.
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.
5
-
6
- If creating or editing files with a large number of lines, organize them into smaller files.
7
- If creating or editing files in an existing directory check surrounding files for the used patterns.
8
-
9
- # Structure Discovery (docs/STRUCTURE.md from current directory)
10
- Always start exploring the project by viewing for the file docs/STRUCTURE.md.
11
- Do not track files or directories wich are in .gitignore in the structure.
12
- At the end of responding to the user, update the structure file based on the files and directories you have interacted with,
13
- be precise focusing on the most important files and directories, avoid adding extra information like architecture or design patterns.
14
-
15
- # Tools
16
- The bash tool does not support commands which will require user input.
17
- Use the bash tool to get the current date or time when needed.
18
- Prefer the str_replace_editor tool to view directories and file contents.
19
-
20
- <IMPORTANT>
21
- Call the user_prompt tool when:
22
- - There are multiple options to apply a certain change
23
- - The next operation risk is moderated or high
24
- - The implementation plan is complex, requiring a review
25
- Proceed according to the user answer.
26
- </IMPORTANT>
27
-
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
- 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
- When displaying commands in instructions to the user, consider their platform.
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/token_report.py DELETED
@@ -1,154 +0,0 @@
1
- """
2
- Module for generating token usage reports.
3
- """
4
-
5
- from rich.console import Console
6
-
7
- def generate_token_report(agent, verbose=False, interrupted=False):
8
- """
9
- Generate a token usage report.
10
-
11
- Args:
12
- agent: The Claude agent instance
13
- verbose: Whether to show detailed token usage information
14
- interrupted: Whether the request was interrupted
15
-
16
- Returns:
17
- None - prints the report to the console
18
- """
19
- console = Console()
20
- usage = agent.get_tokens()
21
- cost = agent.get_token_cost()
22
-
23
- text_usage = usage.text_usage
24
- tools_usage = usage.tools_usage
25
-
26
- if verbose:
27
- total_usage = usage.total_usage
28
-
29
- # Get costs from the cost object
30
- text_input_cost = cost.input_cost
31
- text_output_cost = cost.output_cost
32
- text_cache_creation_cost = cost.cache_creation_cost
33
- text_cache_read_cost = cost.cache_read_cost
34
-
35
- tools_input_cost = cost.input_cost
36
- tools_output_cost = cost.output_cost
37
- tools_cache_creation_cost = cost.cache_creation_cost
38
- tools_cache_read_cost = cost.cache_read_cost
39
-
40
- # Format costs
41
- def format_cost(cost):
42
- return f"{cost * 100:.2f}¢ USD" if cost < 1.0 else f"${cost:.6f} USD"
43
-
44
- console.print("\n[bold blue]📊 Detailed Token Usage:[/bold blue]")
45
- console.print(f"📝 Text Input tokens: {text_usage.input_tokens}")
46
- console.print(f"📤 Text Output tokens: {text_usage.output_tokens}")
47
- console.print(f"💾 Text Cache Creation tokens: {text_usage.cache_creation_input_tokens}")
48
- console.print(f"📖 Text Cache Read tokens: {text_usage.cache_read_input_tokens}")
49
- console.print(f"📋 Text Total tokens: {text_usage.input_tokens + text_usage.output_tokens + text_usage.cache_creation_input_tokens + text_usage.cache_read_input_tokens}")
50
-
51
- console.print(f"🔧 Tool Input tokens: {tools_usage.input_tokens}")
52
- console.print(f"🔨 Tool Output tokens: {tools_usage.output_tokens}")
53
- console.print(f"💾 Tool Cache Creation tokens: {tools_usage.cache_creation_input_tokens}")
54
- console.print(f"📖 Tool Cache Read tokens: {tools_usage.cache_read_input_tokens}")
55
- console.print(f"🧰 Tool Total tokens: {tools_usage.input_tokens + tools_usage.output_tokens + tools_usage.cache_creation_input_tokens + tools_usage.cache_read_input_tokens}")
56
-
57
- console.print(f"🔢 Total tokens: {total_usage.input_tokens + total_usage.output_tokens + total_usage.cache_creation_input_tokens + total_usage.cache_read_input_tokens}")
58
-
59
- console.print("\n[bold blue]💰 Pricing Information:[/bold blue]")
60
- console.print(f"📝 Text Input cost: {format_cost(text_input_cost)}")
61
- console.print(f"📤 Text Output cost: {format_cost(text_output_cost)}")
62
- console.print(f"💾 Text Cache Creation cost: {format_cost(text_cache_creation_cost)}")
63
- console.print(f"📖 Text Cache Read cost: {format_cost(text_cache_read_cost)}")
64
- console.print(f"📋 Text Total cost: {format_cost(text_input_cost + text_output_cost + text_cache_creation_cost + text_cache_read_cost)}")
65
-
66
- console.print(f"🔧 Tool Input cost: {format_cost(tools_input_cost)}")
67
- console.print(f"🔨 Tool Output cost: {format_cost(tools_output_cost)}")
68
- console.print(f"💾 Tool Cache Creation cost: {format_cost(tools_cache_creation_cost)}")
69
- console.print(f"📖 Tool Cache Read cost: {format_cost(tools_cache_read_cost)}")
70
- console.print(f"🧰 Tool Total cost: {format_cost(tools_input_cost + tools_output_cost + tools_cache_creation_cost + tools_cache_read_cost)}")
71
-
72
- total_cost_text = f"💵 Total cost: {format_cost(text_input_cost + text_output_cost + text_cache_creation_cost + text_cache_read_cost + tools_input_cost + tools_output_cost + tools_cache_creation_cost + tools_cache_read_cost)}"
73
- if interrupted:
74
- total_cost_text += " (interrupted request not accounted)"
75
- console.print(total_cost_text)
76
-
77
- # Show cache delta if available
78
- if hasattr(cost, 'cache_delta') and cost.cache_delta:
79
- cache_delta = cost.cache_delta
80
- console.print(f"\n[bold green]💰 Cache Savings:[/bold green] {format_cost(cache_delta)}")
81
-
82
- # Calculate percentage savings
83
- total_cost_without_cache = cost.total_cost + cache_delta
84
- if total_cost_without_cache > 0:
85
- savings_percentage = (cache_delta / total_cost_without_cache) * 100
86
- console.print(f"[bold green]📊 Cache Savings Percentage:[/bold green] {savings_percentage:.2f}%")
87
- console.print(f"[bold green]💸 Cost without cache:[/bold green] {format_cost(total_cost_without_cache)}")
88
- console.print(f"[bold green]💲 Cost with cache:[/bold green] {format_cost(cost.total_cost)}")
89
-
90
- # Per-tool breakdown
91
- if usage.by_tool:
92
- console.print("\n[bold blue]🔧 Per-Tool Breakdown:[/bold blue]")
93
- try:
94
- if hasattr(cost, 'by_tool') and cost.by_tool:
95
- for tool_name, tool_usage in usage.by_tool.items():
96
- tool_input_cost = cost.by_tool[tool_name].input_cost
97
- tool_output_cost = cost.by_tool[tool_name].output_cost
98
- tool_cache_creation_cost = cost.by_tool[tool_name].cache_creation_cost
99
- tool_cache_read_cost = cost.by_tool[tool_name].cache_read_cost
100
- tool_total_cost = tool_input_cost + tool_output_cost + tool_cache_creation_cost + tool_cache_read_cost
101
-
102
- console.print(f" 🔧 Tool: {tool_name}")
103
- console.print(f" 📥 Input tokens: {tool_usage.input_tokens}")
104
- console.print(f" 📤 Output tokens: {tool_usage.output_tokens}")
105
- console.print(f" 💾 Cache Creation tokens: {tool_usage.cache_creation_input_tokens}")
106
- console.print(f" 📖 Cache Read tokens: {tool_usage.cache_read_input_tokens}")
107
- console.print(f" 🔢 Total tokens: {tool_usage.input_tokens + tool_usage.output_tokens + tool_usage.cache_creation_input_tokens + tool_usage.cache_read_input_tokens}")
108
- console.print(f" 💵 Total cost: {format_cost(tool_total_cost)}")
109
- else:
110
- # Calculate costs manually for each tool if cost.by_tool is not available
111
- for tool_name, tool_usage in usage.by_tool.items():
112
- # Estimate costs based on overall pricing
113
- total_tokens = tool_usage.input_tokens + tool_usage.output_tokens + tool_usage.cache_creation_input_tokens + tool_usage.cache_read_input_tokens
114
- estimated_cost = (total_tokens / (usage.total_usage.total_tokens + usage.total_usage.total_cache_tokens)) * cost.total_cost if usage.total_usage.total_tokens > 0 else 0
115
-
116
- console.print(f" 🔧 Tool: {tool_name}")
117
- console.print(f" 📥 Input tokens: {tool_usage.input_tokens}")
118
- console.print(f" 📤 Output tokens: {tool_usage.output_tokens}")
119
- console.print(f" 💾 Cache Creation tokens: {tool_usage.cache_creation_input_tokens}")
120
- console.print(f" 📖 Cache Read tokens: {tool_usage.cache_read_input_tokens}")
121
- console.print(f" 🔢 Total tokens: {tool_usage.input_tokens + tool_usage.output_tokens + tool_usage.cache_creation_input_tokens + tool_usage.cache_read_input_tokens}")
122
- console.print(f" 💵 Total cost: {format_cost(estimated_cost)}")
123
- except Exception as e:
124
- console.print(f"❌ Error: {str(e)}")
125
- else:
126
- total_tokens = (text_usage.input_tokens + text_usage.output_tokens +
127
- text_usage.cache_creation_input_tokens + text_usage.cache_read_input_tokens +
128
- tools_usage.input_tokens + tools_usage.output_tokens +
129
- tools_usage.cache_creation_input_tokens + tools_usage.cache_read_input_tokens)
130
-
131
- # Format costs
132
- def format_cost(cost):
133
- return f"{cost * 100:.2f}¢ USD" if cost < 1.0 else f"${cost:.6f} USD"
134
-
135
- # Prepare summary message
136
- cost_text = f"Cost: {format_cost(cost.total_cost)}"
137
- if interrupted:
138
- cost_text += " (interrupted request not accounted)"
139
-
140
- summary = f"Total tokens: {total_tokens} | {cost_text}"
141
-
142
- # Add cache savings if available
143
- if hasattr(cost, 'cache_delta') and cost.cache_delta != 0:
144
- cache_delta = cost.cache_delta
145
- total_cost_without_cache = cost.total_cost + cache_delta
146
- savings_percentage = 0
147
- if total_cost_without_cache > 0:
148
- savings_percentage = (cache_delta / total_cost_without_cache) * 100
149
-
150
- summary += f" | Cache savings: {format_cost(cache_delta)} ({savings_percentage:.1f}%)"
151
-
152
- # Display with a rule
153
- console.rule("[blue]Token Usage[/blue]")
154
- console.print(f"[blue]{summary}[/blue]", justify="center")
janito/tools/__init__.py DELETED
@@ -1,44 +0,0 @@
1
- """
2
- Janito tools package.
3
- """
4
-
5
- from .str_replace_editor import str_replace_editor
6
- from .find_files import find_files
7
- from .delete_file import delete_file
8
- from .search_text import search_text
9
- from .replace_file import replace_file
10
- from .prompt_user import prompt_user
11
- from .move_file import move_file
12
- from janito.tools.fetch_webpage import fetch_webpage
13
- from .think import think
14
- from .usage_tracker import get_tracker, reset_tracker, print_usage_stats
15
- from janito.config import get_config
16
-
17
- __all__ = ["str_replace_editor", "find_files", "delete_file", "search_text", "replace_file",
18
- "prompt_user", "move_file", "fetch_webpage", "think", "get_tools",
19
- "get_tracker", "reset_tracker", "print_usage_stats"]
20
-
21
- def get_tools():
22
- """
23
- Get a list of all available tools.
24
-
25
- Returns:
26
- List of tool functions (excluding str_replace_editor which is passed separately)
27
- If no_tools mode is enabled, returns an empty list
28
- If ask_mode is enabled, only returns tools that don't perform changes
29
- """
30
- # If no_tools mode is enabled, return an empty list
31
- if get_config().no_tools:
32
- return []
33
-
34
- # Tools that only read or view but don't modify anything
35
- read_only_tools = [find_files, search_text, prompt_user, fetch_webpage, think]
36
-
37
- # Tools that modify the filesystem
38
- write_tools = [delete_file, replace_file, move_file]
39
-
40
- # If ask_mode is enabled, only return read-only tools
41
- if get_config().ask_mode:
42
- return read_only_tools
43
- else:
44
- return read_only_tools + write_tools
janito/tools/bash/bash.py DELETED
@@ -1,157 +0,0 @@
1
- from typing import Optional
2
- from typing import Tuple
3
- import threading
4
- import platform
5
- import re
6
- import queue
7
- import signal
8
- import time
9
- from janito.config import get_config
10
- from janito.tools.usage_tracker import get_tracker
11
- from janito.tools.rich_console import console, print_info
12
-
13
- # Import the appropriate implementation based on the platform
14
- if platform.system() == "Windows":
15
- from janito.tools.bash.win_persistent_bash import PersistentBash
16
- else:
17
- from janito.tools.bash.unix_persistent_bash import PersistentBash
18
-
19
- # Global instance of PersistentBash to maintain state between calls
20
- _bash_session = None
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)
58
-
59
- def bash_tool(command: str, restart: Optional[bool] = False) -> Tuple[str, bool]:
60
- """
61
- Execute a bash command using a persistent Bash session.
62
- The appropriate implementation (Windows or Unix) is selected based on the detected platform.
63
- When in ask mode, only read-only commands are allowed.
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.
66
-
67
- Args:
68
- command: The bash command to execute
69
- restart: Whether to restart the bash session
70
-
71
- Returns:
72
- A tuple containing (output message, is_error flag)
73
- """
74
- # Import console for printing output in real-time
75
- from janito.tools.rich_console import console, print_info
76
-
77
- # Only print command if not in trust mode
78
- if not get_config().trust_mode:
79
- print_info(f"{command}", "Bash Run")
80
-
81
- global _bash_session, _current_bash_thread, _command_interrupted, original_sigint_handler
82
- _command_interrupted = False
83
-
84
- # Check if in ask mode and if the command might modify files
85
- if get_config().ask_mode:
86
- # List of potentially modifying commands
87
- modifying_patterns = [
88
- r'\brm\b', r'\bmkdir\b', r'\btouch\b', r'\becho\b.*[>\|]', r'\bmv\b', r'\bcp\b',
89
- r'\bchmod\b', r'\bchown\b', r'\bsed\b.*-i', r'\bawk\b.*[>\|]', r'\bcat\b.*[>\|]',
90
- r'\bwrite\b', r'\binstall\b', r'\bapt\b', r'\byum\b', r'\bpip\b.*install',
91
- r'\bnpm\b.*install', r'\bdocker\b', r'\bkubectl\b.*apply', r'\bgit\b.*commit',
92
- r'\bgit\b.*push', r'\bgit\b.*merge', r'\bdd\b'
93
- ]
94
-
95
- # Check if command matches any modifying pattern
96
- for pattern in modifying_patterns:
97
- if re.search(pattern, command, re.IGNORECASE):
98
- return ("Cannot execute potentially modifying commands in ask mode. Use --ask option to disable modifications.", True)
99
-
100
- with _session_lock:
101
- # Initialize or restart the session if needed
102
- if _bash_session is None or restart:
103
- if _bash_session is not None:
104
- _bash_session.close()
105
- # Get GitBash path from config (None means auto-detect)
106
- gitbash_path = get_config().gitbash_path
107
- _bash_session = PersistentBash(bash_path=gitbash_path)
108
-
109
- try:
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)
142
-
143
- # Track bash command execution
144
- get_tracker().increment('bash_commands')
145
-
146
- # Return the output
147
- return output, is_error
148
-
149
- except Exception as e:
150
- # Handle any exceptions that might occur
151
- error_message = f"Error executing bash command: {str(e)}"
152
- console.print(error_message, style="red bold")
153
-
154
- # Restore the original signal handler
155
- signal.signal(signal.SIGINT, original_sigint_handler)
156
-
157
- return error_message, True