tunacode-cli 0.0.34__py3-none-any.whl → 0.0.35__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/cli/commands.py +37 -21
- tunacode/cli/repl.py +17 -5
- tunacode/constants.py +1 -1
- tunacode/tools/run_command.py +18 -8
- tunacode/utils/security.py +208 -0
- {tunacode_cli-0.0.34.dist-info → tunacode_cli-0.0.35.dist-info}/METADATA +1 -1
- {tunacode_cli-0.0.34.dist-info → tunacode_cli-0.0.35.dist-info}/RECORD +11 -10
- {tunacode_cli-0.0.34.dist-info → tunacode_cli-0.0.35.dist-info}/WHEEL +0 -0
- {tunacode_cli-0.0.34.dist-info → tunacode_cli-0.0.35.dist-info}/entry_points.txt +0 -0
- {tunacode_cli-0.0.34.dist-info → tunacode_cli-0.0.35.dist-info}/licenses/LICENSE +0 -0
- {tunacode_cli-0.0.34.dist-info → tunacode_cli-0.0.35.dist-info}/top_level.txt +0 -0
tunacode/cli/commands.py
CHANGED
|
@@ -181,26 +181,29 @@ class IterationsCommand(SimpleCommand):
|
|
|
181
181
|
|
|
182
182
|
async def execute(self, args: List[str], context: CommandContext) -> None:
|
|
183
183
|
state = context.state_manager.session
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
if new_limit < 1 or new_limit > 100:
|
|
188
|
-
await ui.error("Iterations must be between 1 and 100")
|
|
189
|
-
return
|
|
190
|
-
|
|
191
|
-
# Update the user config
|
|
192
|
-
if "settings" not in state.user_config:
|
|
193
|
-
state.user_config["settings"] = {}
|
|
194
|
-
state.user_config["settings"]["max_iterations"] = new_limit
|
|
195
|
-
|
|
196
|
-
await ui.success(f"Maximum iterations set to {new_limit}")
|
|
197
|
-
await ui.muted("Higher values allow more complex reasoning but may be slower")
|
|
198
|
-
except ValueError:
|
|
199
|
-
await ui.error("Please provide a valid number")
|
|
200
|
-
else:
|
|
184
|
+
|
|
185
|
+
# Guard clause - handle "no args" case first and return early
|
|
186
|
+
if not args:
|
|
201
187
|
current = state.user_config.get("settings", {}).get("max_iterations", 40)
|
|
202
188
|
await ui.info(f"Current maximum iterations: {current}")
|
|
203
189
|
await ui.muted("Usage: /iterations <number> (1-100)")
|
|
190
|
+
return
|
|
191
|
+
|
|
192
|
+
# update the logic to not be as nested messely, the above guars needing to get as messy
|
|
193
|
+
try:
|
|
194
|
+
new_limit = int(args[0])
|
|
195
|
+
if new_limit < 1 or new_limit > 100:
|
|
196
|
+
await ui.error("Iterations must be between 1 and 100")
|
|
197
|
+
return
|
|
198
|
+
|
|
199
|
+
# Update the user config
|
|
200
|
+
if "settings" not in state.user_config:
|
|
201
|
+
state.user_config["settings"] = {}
|
|
202
|
+
state.user_config["settings"]["max_iterations"] = new_limit
|
|
203
|
+
|
|
204
|
+
await ui.success(f"Maximum iterations set to {new_limit}")
|
|
205
|
+
except ValueError:
|
|
206
|
+
await ui.error("Please provide a valid number")
|
|
204
207
|
|
|
205
208
|
|
|
206
209
|
class ClearCommand(SimpleCommand):
|
|
@@ -288,7 +291,9 @@ class ParseToolsCommand(SimpleCommand):
|
|
|
288
291
|
|
|
289
292
|
try:
|
|
290
293
|
await extract_and_execute_tool_calls(
|
|
291
|
-
part.content,
|
|
294
|
+
part.content,
|
|
295
|
+
tool_callback_with_state,
|
|
296
|
+
context.state_manager,
|
|
292
297
|
)
|
|
293
298
|
await ui.success("JSON tool parsing completed")
|
|
294
299
|
found_content = True
|
|
@@ -524,7 +529,8 @@ class UpdateCommand(SimpleCommand):
|
|
|
524
529
|
result = subprocess.run(
|
|
525
530
|
["pipx", "list"], capture_output=True, text=True, timeout=10
|
|
526
531
|
)
|
|
527
|
-
|
|
532
|
+
pipx_installed = "tunacode" in result.stdout.lower()
|
|
533
|
+
if pipx_installed:
|
|
528
534
|
installation_method = "pipx"
|
|
529
535
|
except (subprocess.TimeoutExpired, subprocess.CalledProcessError):
|
|
530
536
|
pass
|
|
@@ -555,12 +561,22 @@ class UpdateCommand(SimpleCommand):
|
|
|
555
561
|
if installation_method == "pipx":
|
|
556
562
|
await ui.info("Updating via pipx...")
|
|
557
563
|
result = subprocess.run(
|
|
558
|
-
["pipx", "upgrade", "tunacode"],
|
|
564
|
+
["pipx", "upgrade", "tunacode"],
|
|
565
|
+
capture_output=True,
|
|
566
|
+
text=True,
|
|
567
|
+
timeout=60,
|
|
559
568
|
)
|
|
560
569
|
else: # pip
|
|
561
570
|
await ui.info("Updating via pip...")
|
|
562
571
|
result = subprocess.run(
|
|
563
|
-
[
|
|
572
|
+
[
|
|
573
|
+
sys.executable,
|
|
574
|
+
"-m",
|
|
575
|
+
"pip",
|
|
576
|
+
"install",
|
|
577
|
+
"--upgrade",
|
|
578
|
+
"tunacode-cli",
|
|
579
|
+
],
|
|
564
580
|
capture_output=True,
|
|
565
581
|
text=True,
|
|
566
582
|
timeout=60,
|
tunacode/cli/repl.py
CHANGED
|
@@ -22,6 +22,7 @@ from tunacode.core.tool_handler import ToolHandler
|
|
|
22
22
|
from tunacode.exceptions import AgentError, UserAbortError, ValidationError
|
|
23
23
|
from tunacode.ui import console as ui
|
|
24
24
|
from tunacode.ui.tool_ui import ToolUI
|
|
25
|
+
from tunacode.utils.security import CommandSecurityError, safe_subprocess_run
|
|
25
26
|
|
|
26
27
|
from ..types import CommandContext, CommandResult, StateManager, ToolArgs
|
|
27
28
|
from .commands import CommandRegistry
|
|
@@ -320,13 +321,24 @@ async def repl(state_manager: StateManager):
|
|
|
320
321
|
def run_shell():
|
|
321
322
|
try:
|
|
322
323
|
if command:
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
324
|
+
# Use secure subprocess execution for shell commands
|
|
325
|
+
# Note: User shell commands are inherently risky but this is by design
|
|
326
|
+
# We validate but allow shell features since it's explicit user intent
|
|
327
|
+
try:
|
|
328
|
+
result = safe_subprocess_run(
|
|
329
|
+
command,
|
|
330
|
+
shell=True,
|
|
331
|
+
validate=True, # Still validate for basic safety
|
|
332
|
+
capture_output=False,
|
|
333
|
+
)
|
|
334
|
+
if result.returncode != 0:
|
|
335
|
+
print(f"\nCommand exited with code {result.returncode}")
|
|
336
|
+
except CommandSecurityError as e:
|
|
337
|
+
print(f"\nSecurity validation failed: {str(e)}")
|
|
338
|
+
print("If you need to run this command, please ensure it's safe.")
|
|
327
339
|
else:
|
|
328
340
|
shell = os.environ.get("SHELL", "bash")
|
|
329
|
-
subprocess.run(shell)
|
|
341
|
+
subprocess.run(shell) # Interactive shell is safe
|
|
330
342
|
except Exception as e:
|
|
331
343
|
print(f"\nShell command failed: {str(e)}")
|
|
332
344
|
|
tunacode/constants.py
CHANGED
tunacode/tools/run_command.py
CHANGED
|
@@ -14,6 +14,7 @@ from tunacode.constants import (CMD_OUTPUT_FORMAT, CMD_OUTPUT_NO_ERRORS, CMD_OUT
|
|
|
14
14
|
from tunacode.exceptions import ToolExecutionError
|
|
15
15
|
from tunacode.tools.base import BaseTool
|
|
16
16
|
from tunacode.types import ToolResult
|
|
17
|
+
from tunacode.utils.security import CommandSecurityError, safe_subprocess_popen
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
class RunCommandTool(BaseTool):
|
|
@@ -34,16 +35,23 @@ class RunCommandTool(BaseTool):
|
|
|
34
35
|
|
|
35
36
|
Raises:
|
|
36
37
|
FileNotFoundError: If command not found
|
|
38
|
+
CommandSecurityError: If command fails security validation
|
|
37
39
|
Exception: Any command execution errors
|
|
38
40
|
"""
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
41
|
+
try:
|
|
42
|
+
# Use secure subprocess execution with validation
|
|
43
|
+
process = safe_subprocess_popen(
|
|
44
|
+
command,
|
|
45
|
+
shell=True, # CLI tool requires shell features
|
|
46
|
+
validate=True, # Enable security validation
|
|
47
|
+
stdout=subprocess.PIPE,
|
|
48
|
+
stderr=subprocess.PIPE,
|
|
49
|
+
text=True,
|
|
50
|
+
)
|
|
51
|
+
stdout, stderr = process.communicate()
|
|
52
|
+
except CommandSecurityError as e:
|
|
53
|
+
# Security validation failed - return error without execution
|
|
54
|
+
return f"Security validation failed: {str(e)}"
|
|
47
55
|
output = stdout.strip() or CMD_OUTPUT_NO_OUTPUT
|
|
48
56
|
error = stderr.strip() or CMD_OUTPUT_NO_ERRORS
|
|
49
57
|
resp = CMD_OUTPUT_FORMAT.format(output=output, error=error).strip()
|
|
@@ -70,6 +78,8 @@ class RunCommandTool(BaseTool):
|
|
|
70
78
|
"""
|
|
71
79
|
if isinstance(error, FileNotFoundError):
|
|
72
80
|
err_msg = ERROR_COMMAND_EXECUTION.format(command=command, error=error)
|
|
81
|
+
elif isinstance(error, CommandSecurityError):
|
|
82
|
+
err_msg = f"Command blocked for security: {str(error)}"
|
|
73
83
|
else:
|
|
74
84
|
# Use parent class handling for other errors
|
|
75
85
|
await super()._handle_error(error, command)
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Security utilities for safe command execution and input validation.
|
|
3
|
+
Provides defensive measures against command injection attacks.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import logging
|
|
7
|
+
import re
|
|
8
|
+
import shlex
|
|
9
|
+
import subprocess
|
|
10
|
+
from typing import List, Optional
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
# Dangerous shell metacharacters that indicate potential injection
|
|
15
|
+
DANGEROUS_CHARS = [
|
|
16
|
+
";",
|
|
17
|
+
"&",
|
|
18
|
+
"|",
|
|
19
|
+
"`",
|
|
20
|
+
"$",
|
|
21
|
+
"(",
|
|
22
|
+
")",
|
|
23
|
+
"{",
|
|
24
|
+
"}",
|
|
25
|
+
"<",
|
|
26
|
+
">",
|
|
27
|
+
"\n",
|
|
28
|
+
"\r",
|
|
29
|
+
"\\",
|
|
30
|
+
'"',
|
|
31
|
+
"'",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
# Common injection patterns
|
|
35
|
+
INJECTION_PATTERNS = [
|
|
36
|
+
r";\s*\w+", # Command chaining with semicolon
|
|
37
|
+
r"&&\s*\w+", # Command chaining with &&
|
|
38
|
+
r"\|\s*\w+", # Piping to another command
|
|
39
|
+
r"`[^`]+`", # Command substitution with backticks
|
|
40
|
+
r"\$\([^)]+\)", # Command substitution with $()
|
|
41
|
+
r">\s*[/\w]", # Output redirection
|
|
42
|
+
r"<\s*[/\w]", # Input redirection
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class CommandSecurityError(Exception):
|
|
47
|
+
"""Raised when a command fails security validation."""
|
|
48
|
+
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def validate_command_safety(command: str, allow_shell_features: bool = False) -> None:
|
|
53
|
+
"""
|
|
54
|
+
Validate that a command is safe to execute.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
command: The command string to validate
|
|
58
|
+
allow_shell_features: If True, allows some shell features like pipes
|
|
59
|
+
|
|
60
|
+
Raises:
|
|
61
|
+
CommandSecurityError: If the command contains potentially dangerous patterns
|
|
62
|
+
"""
|
|
63
|
+
if not command or not command.strip():
|
|
64
|
+
raise CommandSecurityError("Empty command not allowed")
|
|
65
|
+
|
|
66
|
+
# Log the command being validated
|
|
67
|
+
logger.info(f"Validating command: {command[:100]}...")
|
|
68
|
+
|
|
69
|
+
# Always check for the most dangerous patterns regardless of shell features
|
|
70
|
+
dangerous_patterns = [
|
|
71
|
+
r"rm\s+-rf\s+/", # Dangerous rm commands
|
|
72
|
+
r"sudo\s+rm", # Sudo rm commands
|
|
73
|
+
r">\s*/dev/sd[a-z]", # Writing to disk devices
|
|
74
|
+
r"dd\s+.*of=/dev/", # DD to devices
|
|
75
|
+
r"mkfs\.", # Format filesystem
|
|
76
|
+
r"fdisk", # Partition manipulation
|
|
77
|
+
r":\(\)\{.*\}\;", # Fork bomb pattern
|
|
78
|
+
]
|
|
79
|
+
|
|
80
|
+
for pattern in dangerous_patterns:
|
|
81
|
+
if re.search(pattern, command, re.IGNORECASE):
|
|
82
|
+
logger.error(f"Highly dangerous pattern '{pattern}' detected in command")
|
|
83
|
+
raise CommandSecurityError("Command contains dangerous pattern and is blocked")
|
|
84
|
+
|
|
85
|
+
if not allow_shell_features:
|
|
86
|
+
# Check for dangerous characters (but allow some for CLI tools)
|
|
87
|
+
restricted_chars = [";", "&", "`", "$", "{", "}"] # More permissive for CLI
|
|
88
|
+
for char in restricted_chars:
|
|
89
|
+
if char in command:
|
|
90
|
+
logger.warning(f"Potentially dangerous character '{char}' detected in command")
|
|
91
|
+
raise CommandSecurityError(f"Potentially unsafe character '{char}' in command")
|
|
92
|
+
|
|
93
|
+
# Check for injection patterns (more selective)
|
|
94
|
+
strict_patterns = [
|
|
95
|
+
r";\s*rm\s+", # Command chaining to rm
|
|
96
|
+
r"&&\s*rm\s+", # Command chaining to rm
|
|
97
|
+
r"`[^`]*rm[^`]*`", # Command substitution with rm
|
|
98
|
+
r"\$\([^)]*rm[^)]*\)", # Command substitution with rm
|
|
99
|
+
]
|
|
100
|
+
|
|
101
|
+
for pattern in strict_patterns:
|
|
102
|
+
if re.search(pattern, command):
|
|
103
|
+
logger.warning(f"Dangerous injection pattern '{pattern}' detected in command")
|
|
104
|
+
raise CommandSecurityError("Potentially unsafe pattern detected in command")
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def sanitize_command_args(args: List[str]) -> List[str]:
|
|
108
|
+
"""
|
|
109
|
+
Sanitize command arguments by shell-quoting them.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
args: List of command arguments
|
|
113
|
+
|
|
114
|
+
Returns:
|
|
115
|
+
List of sanitized arguments
|
|
116
|
+
"""
|
|
117
|
+
return [shlex.quote(arg) for arg in args]
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def safe_subprocess_run(
|
|
121
|
+
command: str,
|
|
122
|
+
shell: bool = False,
|
|
123
|
+
validate: bool = True,
|
|
124
|
+
timeout: Optional[int] = None,
|
|
125
|
+
**kwargs,
|
|
126
|
+
) -> subprocess.CompletedProcess:
|
|
127
|
+
"""
|
|
128
|
+
Safely execute a subprocess with security validation.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
command: Command to execute (string if shell=True, list if shell=False)
|
|
132
|
+
shell: Whether to use shell execution (discouraged)
|
|
133
|
+
validate: Whether to validate command safety
|
|
134
|
+
timeout: Timeout in seconds
|
|
135
|
+
**kwargs: Additional subprocess arguments
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
CompletedProcess result
|
|
139
|
+
|
|
140
|
+
Raises:
|
|
141
|
+
CommandSecurityError: If command fails security validation
|
|
142
|
+
"""
|
|
143
|
+
if validate and shell and isinstance(command, str):
|
|
144
|
+
validate_command_safety(command, allow_shell_features=shell)
|
|
145
|
+
|
|
146
|
+
# Log the command execution
|
|
147
|
+
logger.info(f"Executing command: {str(command)[:100]}...")
|
|
148
|
+
|
|
149
|
+
try:
|
|
150
|
+
if shell:
|
|
151
|
+
# When using shell=True, command should be a string
|
|
152
|
+
result = subprocess.run(command, shell=True, timeout=timeout, **kwargs)
|
|
153
|
+
else:
|
|
154
|
+
# When shell=False, command should be a list
|
|
155
|
+
if isinstance(command, str):
|
|
156
|
+
# Parse the string into a list
|
|
157
|
+
command_list = shlex.split(command)
|
|
158
|
+
else:
|
|
159
|
+
command_list = command
|
|
160
|
+
|
|
161
|
+
result = subprocess.run(command_list, shell=False, timeout=timeout, **kwargs)
|
|
162
|
+
|
|
163
|
+
logger.info(f"Command completed with return code: {result.returncode}")
|
|
164
|
+
return result
|
|
165
|
+
|
|
166
|
+
except subprocess.TimeoutExpired:
|
|
167
|
+
logger.error(f"Command timed out after {timeout} seconds")
|
|
168
|
+
raise
|
|
169
|
+
except Exception as e:
|
|
170
|
+
logger.error(f"Command execution failed: {str(e)}")
|
|
171
|
+
raise
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def safe_subprocess_popen(
|
|
175
|
+
command: str, shell: bool = False, validate: bool = True, **kwargs
|
|
176
|
+
) -> subprocess.Popen:
|
|
177
|
+
"""
|
|
178
|
+
Safely create a subprocess.Popen with security validation.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
command: Command to execute
|
|
182
|
+
shell: Whether to use shell execution (discouraged)
|
|
183
|
+
validate: Whether to validate command safety
|
|
184
|
+
**kwargs: Additional Popen arguments
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
Popen process object
|
|
188
|
+
|
|
189
|
+
Raises:
|
|
190
|
+
CommandSecurityError: If command fails security validation
|
|
191
|
+
"""
|
|
192
|
+
if validate and shell and isinstance(command, str):
|
|
193
|
+
validate_command_safety(command, allow_shell_features=shell)
|
|
194
|
+
|
|
195
|
+
# Log the command execution
|
|
196
|
+
logger.info(f"Creating Popen for command: {str(command)[:100]}...")
|
|
197
|
+
|
|
198
|
+
if shell:
|
|
199
|
+
# When using shell=True, command should be a string
|
|
200
|
+
return subprocess.Popen(command, shell=True, **kwargs)
|
|
201
|
+
else:
|
|
202
|
+
# When shell=False, command should be a list
|
|
203
|
+
if isinstance(command, str):
|
|
204
|
+
command_list = shlex.split(command)
|
|
205
|
+
else:
|
|
206
|
+
command_list = command
|
|
207
|
+
|
|
208
|
+
return subprocess.Popen(command_list, shell=False, **kwargs)
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
tunacode/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
tunacode/constants.py,sha256=
|
|
2
|
+
tunacode/constants.py,sha256=OnQYL4TeNFuMCo_7x9FGWmjQCSDOB544wPPs9oOKk-8,4074
|
|
3
3
|
tunacode/context.py,sha256=6sterdRvPOyG3LU0nEAXpBsEPZbO3qtPyTlJBi-_VXE,2612
|
|
4
4
|
tunacode/exceptions.py,sha256=mTWXuWyr1k16CGLWN2tsthDGi7lbx1JK0ekIqogYDP8,3105
|
|
5
5
|
tunacode/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
6
|
tunacode/setup.py,sha256=dYn0NeAxtNIDSogWEmGSyjb9wsr8AonZ8vAo5sw9NIw,1909
|
|
7
7
|
tunacode/types.py,sha256=BciT-uxnQ44iC-4QiDY72OD23LOtqSyMOuK_N0ttlaA,7676
|
|
8
8
|
tunacode/cli/__init__.py,sha256=zgs0UbAck8hfvhYsWhWOfBe5oK09ug2De1r4RuQZREA,55
|
|
9
|
-
tunacode/cli/commands.py,sha256=
|
|
9
|
+
tunacode/cli/commands.py,sha256=r-WYp5ajzkZfFjiLXuK9pfB5ugq3HWQyhRB8Usr567k,31668
|
|
10
10
|
tunacode/cli/main.py,sha256=PIcFnfmIoI_pmK2y-zB_ouJbzR5fbSI7zsKQNPB_J8o,2406
|
|
11
|
-
tunacode/cli/repl.py,sha256=
|
|
11
|
+
tunacode/cli/repl.py,sha256=ELnJBk3Vn2almXmmCIjGfgi7J5kNNVnO0o4KNYGXF9Q,14556
|
|
12
12
|
tunacode/cli/textual_app.py,sha256=14-Nt0IIETmyHBrNn9uwSF3EwCcutwTp6gdoKgNm0sY,12593
|
|
13
13
|
tunacode/cli/textual_bridge.py,sha256=LvqiTtF0hu3gNujzpKaW9h-m6xzEP3OH2M8KL2pCwRc,6333
|
|
14
14
|
tunacode/configuration/__init__.py,sha256=MbVXy8bGu0yKehzgdgZ_mfWlYGvIdb1dY2Ly75nfuPE,17
|
|
@@ -42,7 +42,7 @@ tunacode/tools/grep.py,sha256=jboEVA2ATv0YI8zg9dF89emZ_HWy2vVtsQ_-hDhlr7g,26337
|
|
|
42
42
|
tunacode/tools/list_dir.py,sha256=1kNqzYCNlcA5rqXIEVqcjQy6QxlLZLj5AG6YIECfwio,7217
|
|
43
43
|
tunacode/tools/read_file.py,sha256=BqHxPspZBYotz5wtjuP-zve61obsx98z5TU-aw5BJHg,3273
|
|
44
44
|
tunacode/tools/read_file_async_poc.py,sha256=0rSfYCmoNcvWk8hB1z86l32-tomSc9yOM4tR4nrty_o,6267
|
|
45
|
-
tunacode/tools/run_command.py,sha256=
|
|
45
|
+
tunacode/tools/run_command.py,sha256=2TtndMIeOWHQRC2XwkxUDVb06Ob-RHKSheBdluH3QgQ,4433
|
|
46
46
|
tunacode/tools/update_file.py,sha256=bW1MhTzRjBDjJzqQ6A1yCVEbkr1oIqtEC8uqcg_rfY4,3957
|
|
47
47
|
tunacode/tools/write_file.py,sha256=prL6u8XOi9ZyPU-YNlG9YMLbSLrDJXDRuDX73ncXh-k,2699
|
|
48
48
|
tunacode/ui/__init__.py,sha256=aRNE2pS50nFAX6y--rSGMNYwhz905g14gRd6g4BolYU,13
|
|
@@ -64,13 +64,14 @@ tunacode/utils/diff_utils.py,sha256=V9QqQ0q4MfabVTnWptF3IXDp3estnfOKcJtDe_Sj14I,
|
|
|
64
64
|
tunacode/utils/file_utils.py,sha256=AXiAJ_idtlmXEi9pMvwtfPy9Ys3yK-F4K7qb_NpwonU,923
|
|
65
65
|
tunacode/utils/import_cache.py,sha256=q_xjJbtju05YbFopLDSkIo1hOtCx3DOTl3GQE5FFDgs,295
|
|
66
66
|
tunacode/utils/ripgrep.py,sha256=AXUs2FFt0A7KBV996deS8wreIlUzKOlAHJmwrcAr4No,583
|
|
67
|
+
tunacode/utils/security.py,sha256=e_zo9VmcOKFjgFMr9GOBIFhAmND4PBlJZgY7zqnsGjI,6548
|
|
67
68
|
tunacode/utils/system.py,sha256=FSoibTIH0eybs4oNzbYyufIiV6gb77QaeY2yGqW39AY,11381
|
|
68
69
|
tunacode/utils/text_utils.py,sha256=zRBaorvtyd7HBEWtIfCH1Wce1L6rhsQwpORUEGBFMjA,2981
|
|
69
70
|
tunacode/utils/token_counter.py,sha256=nGCWwrHHFbKywqeDCEuJnADCkfJuzysWiB6cCltJOKI,648
|
|
70
71
|
tunacode/utils/user_configuration.py,sha256=Ilz8dpGVJDBE2iLWHAPT0xR8D51VRKV3kIbsAz8Bboc,3275
|
|
71
|
-
tunacode_cli-0.0.
|
|
72
|
-
tunacode_cli-0.0.
|
|
73
|
-
tunacode_cli-0.0.
|
|
74
|
-
tunacode_cli-0.0.
|
|
75
|
-
tunacode_cli-0.0.
|
|
76
|
-
tunacode_cli-0.0.
|
|
72
|
+
tunacode_cli-0.0.35.dist-info/licenses/LICENSE,sha256=Btzdu2kIoMbdSp6OyCLupB1aRgpTCJ_szMimgEnpkkE,1056
|
|
73
|
+
tunacode_cli-0.0.35.dist-info/METADATA,sha256=4ck_-g8eF10l6md95_EaEK1Sd4UmqmRAUEJacINtDA8,4943
|
|
74
|
+
tunacode_cli-0.0.35.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
75
|
+
tunacode_cli-0.0.35.dist-info/entry_points.txt,sha256=hbkytikj4dGu6rizPuAd_DGUPBGF191RTnhr9wdhORY,51
|
|
76
|
+
tunacode_cli-0.0.35.dist-info/top_level.txt,sha256=lKy2P6BWNi5XSA4DHFvyjQ14V26lDZctwdmhEJrxQbU,9
|
|
77
|
+
tunacode_cli-0.0.35.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|