tunacode-cli 0.0.12__py3-none-any.whl → 0.0.13__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.

Potentially problematic release.


This version of tunacode-cli might be problematic. Click here for more details.

tunacode/constants.py CHANGED
@@ -7,7 +7,7 @@ Centralizes all magic strings, UI text, error messages, and application constant
7
7
 
8
8
  # Application info
9
9
  APP_NAME = "TunaCode"
10
- APP_VERSION = "0.0.12"
10
+ APP_VERSION = "0.0.13"
11
11
 
12
12
  # File patterns
13
13
  GUIDE_FILE_PATTERN = "{name}.md"
@@ -12,6 +12,7 @@ from pydantic_ai.messages import ModelRequest, ToolReturnPart
12
12
 
13
13
  from tunacode.core.state import StateManager
14
14
  from tunacode.services.mcp import get_mcp_servers
15
+ from tunacode.tools.bash import bash
15
16
  from tunacode.tools.read_file import read_file
16
17
  from tunacode.tools.run_command import run_command
17
18
  from tunacode.tools.update_file import update_file
@@ -37,6 +38,7 @@ def get_or_create_agent(model: ModelName, state_manager: StateManager) -> Pydant
37
38
  state_manager.session.agents[model] = Agent(
38
39
  model=model,
39
40
  tools=[
41
+ Tool(bash, max_retries=max_retries),
40
42
  Tool(read_file, max_retries=max_retries),
41
43
  Tool(run_command, max_retries=max_retries),
42
44
  Tool(update_file, max_retries=max_retries),
@@ -0,0 +1 @@
1
+ """TunaCode tools package."""
tunacode/tools/bash.py ADDED
@@ -0,0 +1,252 @@
1
+ """
2
+ Module: tunacode.tools.bash
3
+
4
+ Enhanced bash execution tool for agent operations in the TunaCode application.
5
+ Provides advanced shell command execution with working directory support,
6
+ environment variables, timeouts, and improved output handling.
7
+ """
8
+
9
+ import asyncio
10
+ import os
11
+ import subprocess
12
+ from typing import Dict, Optional
13
+
14
+ from pydantic_ai.exceptions import ModelRetry
15
+
16
+ from tunacode.constants import MAX_COMMAND_OUTPUT
17
+ from tunacode.exceptions import ToolExecutionError
18
+ from tunacode.tools.base import BaseTool
19
+ from tunacode.types import ToolResult
20
+
21
+
22
+ class BashTool(BaseTool):
23
+ """Enhanced shell command execution tool with advanced features."""
24
+
25
+ @property
26
+ def tool_name(self) -> str:
27
+ return "Bash"
28
+
29
+ async def _execute(
30
+ self,
31
+ command: str,
32
+ cwd: Optional[str] = None,
33
+ env: Optional[Dict[str, str]] = None,
34
+ timeout: Optional[int] = 30,
35
+ capture_output: bool = True,
36
+ ) -> ToolResult:
37
+ """Execute a bash command with enhanced features.
38
+
39
+ Args:
40
+ command: The bash command to execute
41
+ cwd: Working directory for the command (defaults to current)
42
+ env: Additional environment variables to set
43
+ timeout: Command timeout in seconds (default 30, max 300)
44
+ capture_output: Whether to capture stdout/stderr (default True)
45
+
46
+ Returns:
47
+ ToolResult: Formatted output with exit code, stdout, and stderr
48
+
49
+ Raises:
50
+ ModelRetry: For guidance on command failures
51
+ Exception: Any command execution errors
52
+ """
53
+ # Validate and sanitize inputs
54
+ if timeout and (timeout < 1 or timeout > 300):
55
+ raise ModelRetry(
56
+ "Timeout must be between 1 and 300 seconds. "
57
+ "Use shorter timeouts for quick commands, longer for builds/tests."
58
+ )
59
+
60
+ # Validate working directory if specified
61
+ if cwd and not os.path.isdir(cwd):
62
+ raise ModelRetry(
63
+ f"Working directory '{cwd}' does not exist. "
64
+ "Please verify the path or create the directory first."
65
+ )
66
+
67
+ # Check for potentially destructive commands
68
+ destructive_patterns = ["rm -rf", "rm -r", "rm /", "dd if=", "mkfs", "fdisk"]
69
+ if any(pattern in command for pattern in destructive_patterns):
70
+ raise ModelRetry(
71
+ f"Command contains potentially destructive operations: {command}\n"
72
+ "Please confirm this is intentional and safe for your system."
73
+ )
74
+
75
+ # Prepare environment
76
+ exec_env = os.environ.copy()
77
+ if env:
78
+ # Sanitize environment variables
79
+ for key, value in env.items():
80
+ if isinstance(key, str) and isinstance(value, str):
81
+ exec_env[key] = value
82
+
83
+ # Set working directory
84
+ exec_cwd = cwd or os.getcwd()
85
+
86
+ try:
87
+ # Execute command with timeout
88
+ process = await asyncio.create_subprocess_shell(
89
+ command,
90
+ stdout=subprocess.PIPE if capture_output else None,
91
+ stderr=subprocess.PIPE if capture_output else None,
92
+ cwd=exec_cwd,
93
+ env=exec_env,
94
+ )
95
+
96
+ try:
97
+ stdout, stderr = await asyncio.wait_for(
98
+ process.communicate(), timeout=timeout
99
+ )
100
+ except asyncio.TimeoutError:
101
+ # Kill the process if it times out
102
+ process.kill()
103
+ await process.wait()
104
+ raise ModelRetry(
105
+ f"Command timed out after {timeout} seconds: {command}\n"
106
+ "Consider using a longer timeout or breaking the command into smaller parts."
107
+ )
108
+
109
+ # Decode output
110
+ stdout_text = stdout.decode("utf-8", errors="replace").strip() if stdout else ""
111
+ stderr_text = stderr.decode("utf-8", errors="replace").strip() if stderr else ""
112
+
113
+ # Format output
114
+ result = self._format_output(
115
+ command=command,
116
+ exit_code=process.returncode,
117
+ stdout=stdout_text,
118
+ stderr=stderr_text,
119
+ cwd=exec_cwd,
120
+ )
121
+
122
+ # Handle non-zero exit codes as guidance, not failures
123
+ if process.returncode != 0 and stderr_text:
124
+ # Provide guidance for common error patterns
125
+ if "command not found" in stderr_text.lower():
126
+ raise ModelRetry(
127
+ f"Command '{command}' not found. "
128
+ "Check if the command is installed or use the full path."
129
+ )
130
+ elif "permission denied" in stderr_text.lower():
131
+ raise ModelRetry(
132
+ f"Permission denied for command '{command}'. "
133
+ "You may need elevated privileges or different file permissions."
134
+ )
135
+ elif "no such file or directory" in stderr_text.lower():
136
+ raise ModelRetry(
137
+ f"File or directory not found when running '{command}'. "
138
+ "Verify the path exists or create it first."
139
+ )
140
+
141
+ return result
142
+
143
+ except FileNotFoundError:
144
+ raise ModelRetry(
145
+ f"Shell not found. Cannot execute command: {command}\n"
146
+ "This typically indicates a system configuration issue."
147
+ )
148
+
149
+ def _format_output(
150
+ self,
151
+ command: str,
152
+ exit_code: int,
153
+ stdout: str,
154
+ stderr: str,
155
+ cwd: str,
156
+ ) -> str:
157
+ """Format command output in a consistent way.
158
+
159
+ Args:
160
+ command: The executed command
161
+ exit_code: The process exit code
162
+ stdout: Standard output content
163
+ stderr: Standard error content
164
+ cwd: Working directory where command was executed
165
+
166
+ Returns:
167
+ str: Formatted output string
168
+ """
169
+ # Build the result
170
+ lines = [
171
+ f"Command: {command}",
172
+ f"Exit Code: {exit_code}",
173
+ f"Working Directory: {cwd}",
174
+ "",
175
+ ]
176
+
177
+ # Add stdout if present
178
+ if stdout:
179
+ lines.extend(["STDOUT:", stdout, ""])
180
+ else:
181
+ lines.extend(["STDOUT:", "(no output)", ""])
182
+
183
+ # Add stderr if present
184
+ if stderr:
185
+ lines.extend(["STDERR:", stderr])
186
+ else:
187
+ lines.extend(["STDERR:", "(no errors)"])
188
+
189
+ result = "\n".join(lines)
190
+
191
+ # Truncate if too long
192
+ if len(result) > MAX_COMMAND_OUTPUT:
193
+ truncate_point = MAX_COMMAND_OUTPUT - 100 # Leave room for truncation message
194
+ result = result[:truncate_point] + "\n\n[... output truncated ...]"
195
+
196
+ return result
197
+
198
+ def _format_args(
199
+ self,
200
+ command: str,
201
+ cwd: Optional[str] = None,
202
+ env: Optional[Dict[str, str]] = None,
203
+ timeout: Optional[int] = None,
204
+ **kwargs,
205
+ ) -> str:
206
+ """Format arguments for display in UI logging."""
207
+ args = [repr(command)]
208
+
209
+ if cwd:
210
+ args.append(f"cwd={repr(cwd)}")
211
+ if timeout:
212
+ args.append(f"timeout={timeout}")
213
+ if env:
214
+ env_summary = f"{len(env)} vars" if len(env) > 3 else str(env)
215
+ args.append(f"env={env_summary}")
216
+
217
+ return ", ".join(args)
218
+
219
+ def _get_error_context(self, command: str = None, **kwargs) -> str:
220
+ """Get error context for bash execution."""
221
+ if command:
222
+ return f"executing bash command '{command}'"
223
+ return super()._get_error_context()
224
+
225
+
226
+ # Create the function that maintains the existing interface
227
+ async def bash(
228
+ command: str,
229
+ cwd: Optional[str] = None,
230
+ env: Optional[Dict[str, str]] = None,
231
+ timeout: Optional[int] = 30,
232
+ capture_output: bool = True,
233
+ ) -> ToolResult:
234
+ """
235
+ Execute a bash command with enhanced features.
236
+
237
+ Args:
238
+ command (str): The bash command to execute
239
+ cwd (Optional[str]): Working directory for the command
240
+ env (Optional[Dict[str, str]]): Additional environment variables
241
+ timeout (Optional[int]): Command timeout in seconds (default 30, max 300)
242
+ capture_output (bool): Whether to capture stdout/stderr
243
+
244
+ Returns:
245
+ ToolResult: Formatted output with exit code, stdout, and stderr
246
+ """
247
+ tool = BashTool()
248
+ try:
249
+ return await tool.execute(command, cwd=cwd, env=env, timeout=timeout, capture_output=capture_output)
250
+ except ToolExecutionError as e:
251
+ # Return error message for pydantic-ai compatibility
252
+ return str(e)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tunacode-cli
3
- Version: 0.0.12
3
+ Version: 0.0.13
4
4
  Summary: Your agentic CLI developer.
5
5
  Author-email: larock22 <noreply@github.com>
6
6
  License-Expression: MIT
@@ -1,5 +1,5 @@
1
1
  tunacode/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- tunacode/constants.py,sha256=RPAd2a-jP0RifT8Rfj1EVfoG8i7U5viyk99HKLwPy5U,3807
2
+ tunacode/constants.py,sha256=gBGd50EIBnIUeflCZXGpyWYXjEOgymDTmVwETS1fDFE,3807
3
3
  tunacode/context.py,sha256=0ttsxxLAyD4pSoxw7S-pyzor0JUkhOFZh96aAf4Kqsg,2634
4
4
  tunacode/exceptions.py,sha256=RFUH8wOsWEvSPGIYM2exr4t47YkEyZt4Fr-DfTo6_JY,2647
5
5
  tunacode/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -19,7 +19,7 @@ tunacode/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
19
  tunacode/core/state.py,sha256=sf3xvc9NBnz98tkHiSi-mi1GgxuN-r5kERwjmuIKjq8,1344
20
20
  tunacode/core/tool_handler.py,sha256=OKx7jM8pml6pSEnoARu33_uBY8awJBqvhbVeBn6T4ow,1804
21
21
  tunacode/core/agents/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
- tunacode/core/agents/main.py,sha256=o6EJUWBbOMqg9Y9UVgn_3l2ZK8iJveK5UpAmWQQYz0E,4661
22
+ tunacode/core/agents/main.py,sha256=DVwm0T3NbmY1NmA41EWHiLry_OE_f-CUoV6tyn3Y9bo,4751
23
23
  tunacode/core/setup/__init__.py,sha256=lzdpY6rIGf9DDlDBDGFvQZaSOQeFsNglHbkpq1-GtU8,376
24
24
  tunacode/core/setup/agent_setup.py,sha256=trELO8cPnWo36BBnYmXDEnDPdhBg0p-VLnx9A8hSSSQ,1401
25
25
  tunacode/core/setup/base.py,sha256=cbyT2-xK2mWgH4EO17VfM_OM2bj0kT895NW2jSXbe3c,968
@@ -30,8 +30,9 @@ tunacode/core/setup/git_safety_setup.py,sha256=6sAQ0L74rRpayR7hWpnPyGKxCzW1Mk-2g
30
30
  tunacode/prompts/system.txt,sha256=6ecousLK6KvRj6wzIVyzRE7OPQVC5n7P6ceSbtmmaZQ,3207
31
31
  tunacode/services/__init__.py,sha256=w_E8QK6RnvKSvU866eDe8BCRV26rAm4d3R-Yg06OWCU,19
32
32
  tunacode/services/mcp.py,sha256=R48X73KQjQ9vwhBrtbWHSBl-4K99QXmbIhh5J_1Gezo,3046
33
- tunacode/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
33
+ tunacode/tools/__init__.py,sha256=l5O018T-bUjCSy7JSupJoKojZp07uAz3913qDFVdCzM,29
34
34
  tunacode/tools/base.py,sha256=UioygDbXy0JY67EIcyEoYt2TBfX9-n9Usf_1huQ4zOE,7046
35
+ tunacode/tools/bash.py,sha256=h8pUzfp7n2aVtAJcUICJOJV0FBTphifZdYrzHLvrZjA,8781
35
36
  tunacode/tools/read_file.py,sha256=ZNX9PvaYcI2Hw_GeSpQ3_wEy26DQ3LqLwYlaCVYDXO0,3051
36
37
  tunacode/tools/run_command.py,sha256=jI5TWi4SKukfUkCdcFpSWDULsAM4RQN2Du0VTttIWvs,3796
37
38
  tunacode/tools/update_file.py,sha256=QQwdTHUbtfwjCa2sbbrf-d2uIfZY1SQH1wkvjKU9OlQ,4137
@@ -57,9 +58,9 @@ tunacode/utils/ripgrep.py,sha256=AXUs2FFt0A7KBV996deS8wreIlUzKOlAHJmwrcAr4No,583
57
58
  tunacode/utils/system.py,sha256=FSoibTIH0eybs4oNzbYyufIiV6gb77QaeY2yGqW39AY,11381
58
59
  tunacode/utils/text_utils.py,sha256=B9M1cuLTm_SSsr15WNHF6j7WdLWPvWzKZV0Lvfgdbjg,2458
59
60
  tunacode/utils/user_configuration.py,sha256=uFrpSRTUf0CijZjw1rOp1sovqy1uyr0UNcn85S6jvdk,1790
60
- tunacode_cli-0.0.12.dist-info/licenses/LICENSE,sha256=Btzdu2kIoMbdSp6OyCLupB1aRgpTCJ_szMimgEnpkkE,1056
61
- tunacode_cli-0.0.12.dist-info/METADATA,sha256=cgYzSgQ7fjai-wCWUiETKaqpU-itmLUhIkM1ZUIK7bg,12837
62
- tunacode_cli-0.0.12.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
63
- tunacode_cli-0.0.12.dist-info/entry_points.txt,sha256=hbkytikj4dGu6rizPuAd_DGUPBGF191RTnhr9wdhORY,51
64
- tunacode_cli-0.0.12.dist-info/top_level.txt,sha256=lKy2P6BWNi5XSA4DHFvyjQ14V26lDZctwdmhEJrxQbU,9
65
- tunacode_cli-0.0.12.dist-info/RECORD,,
61
+ tunacode_cli-0.0.13.dist-info/licenses/LICENSE,sha256=Btzdu2kIoMbdSp6OyCLupB1aRgpTCJ_szMimgEnpkkE,1056
62
+ tunacode_cli-0.0.13.dist-info/METADATA,sha256=sJNahxSaLVB_QK6WoXW3SkDD0Wmh67MPSTmI0g_gub8,12837
63
+ tunacode_cli-0.0.13.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
64
+ tunacode_cli-0.0.13.dist-info/entry_points.txt,sha256=hbkytikj4dGu6rizPuAd_DGUPBGF191RTnhr9wdhORY,51
65
+ tunacode_cli-0.0.13.dist-info/top_level.txt,sha256=lKy2P6BWNi5XSA4DHFvyjQ14V26lDZctwdmhEJrxQbU,9
66
+ tunacode_cli-0.0.13.dist-info/RECORD,,