tunacode-cli 0.0.39__py3-none-any.whl → 0.0.40__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 +11 -1
- tunacode/utils/text_utils.py +131 -25
- {tunacode_cli-0.0.39.dist-info → tunacode_cli-0.0.40.dist-info}/METADATA +2 -1
- {tunacode_cli-0.0.39.dist-info → tunacode_cli-0.0.40.dist-info}/RECORD +8 -8
- {tunacode_cli-0.0.39.dist-info → tunacode_cli-0.0.40.dist-info}/WHEEL +0 -0
- {tunacode_cli-0.0.39.dist-info → tunacode_cli-0.0.40.dist-info}/entry_points.txt +0 -0
- {tunacode_cli-0.0.39.dist-info → tunacode_cli-0.0.40.dist-info}/licenses/LICENSE +0 -0
- {tunacode_cli-0.0.39.dist-info → tunacode_cli-0.0.40.dist-info}/top_level.txt +0 -0
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.
|
|
10
|
+
APP_VERSION = "0.0.40"
|
|
11
11
|
|
|
12
12
|
# File patterns
|
|
13
13
|
GUIDE_FILE_PATTERN = "{name}.md"
|
|
@@ -18,6 +18,9 @@ CONFIG_FILE_NAME = "tunacode.json"
|
|
|
18
18
|
# Default limits
|
|
19
19
|
MAX_FILE_SIZE = 100 * 1024 # 100KB
|
|
20
20
|
MAX_COMMAND_OUTPUT = 5000 # 5000 chars
|
|
21
|
+
MAX_FILES_IN_DIR = 50
|
|
22
|
+
MAX_TOTAL_DIR_SIZE = 2 * 1024 * 1024 # 2 MB
|
|
23
|
+
|
|
21
24
|
|
|
22
25
|
# Command output processing
|
|
23
26
|
COMMAND_OUTPUT_THRESHOLD = 3500 # Length threshold for truncation
|
|
@@ -115,6 +118,13 @@ ERROR_COMMAND_NOT_FOUND = "Error: Command not found or failed to execute:"
|
|
|
115
118
|
ERROR_COMMAND_EXECUTION = (
|
|
116
119
|
"Error: Command not found or failed to execute: {command}. Details: {error}"
|
|
117
120
|
)
|
|
121
|
+
# Directory expansion errors
|
|
122
|
+
ERROR_DIR_TOO_LARGE = (
|
|
123
|
+
"Error: Directory '{path}' expansion aborted. Total size exceeds {limit_mb:.1f} MB limit."
|
|
124
|
+
)
|
|
125
|
+
ERROR_DIR_TOO_MANY_FILES = (
|
|
126
|
+
"Error: Directory '{path}' expansion aborted. Exceeds limit of {limit} files."
|
|
127
|
+
)
|
|
118
128
|
|
|
119
129
|
# Command output messages
|
|
120
130
|
CMD_OUTPUT_NO_OUTPUT = "No output."
|
tunacode/utils/text_utils.py
CHANGED
|
@@ -51,50 +51,156 @@ def ext_to_lang(path: str) -> str:
|
|
|
51
51
|
|
|
52
52
|
|
|
53
53
|
def expand_file_refs(text: str) -> Tuple[str, List[str]]:
|
|
54
|
-
"""
|
|
54
|
+
"""
|
|
55
|
+
Expands @-references with file or directory contents wrapped in code fences.
|
|
56
|
+
- @path/to/file.ext: Reads a single file.
|
|
57
|
+
- @path/to/dir/: Reads all files in a directory (non-recursive).
|
|
58
|
+
- @path/to/dir/**: Reads all files in a directory and its subdirectories.
|
|
55
59
|
|
|
56
60
|
Args:
|
|
57
|
-
text: The input text potentially containing
|
|
61
|
+
text: The input text potentially containing @-references.
|
|
58
62
|
|
|
59
63
|
Returns:
|
|
60
|
-
|
|
61
|
-
- Text with
|
|
62
|
-
- List of absolute paths of files that were successfully expanded
|
|
64
|
+
A tuple containing:
|
|
65
|
+
- Text with references replaced by file/directory contents.
|
|
66
|
+
- List of absolute paths of files that were successfully expanded.
|
|
63
67
|
|
|
64
68
|
Raises:
|
|
65
|
-
ValueError: If a referenced
|
|
69
|
+
ValueError: If a referenced path does not exist.
|
|
66
70
|
"""
|
|
67
71
|
import os
|
|
68
72
|
import re
|
|
69
73
|
|
|
70
74
|
from tunacode.constants import (
|
|
75
|
+
ERROR_DIR_TOO_LARGE,
|
|
76
|
+
ERROR_DIR_TOO_MANY_FILES,
|
|
71
77
|
ERROR_FILE_NOT_FOUND,
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
MSG_FILE_SIZE_LIMIT,
|
|
78
|
+
MAX_FILES_IN_DIR,
|
|
79
|
+
MAX_TOTAL_DIR_SIZE,
|
|
75
80
|
)
|
|
76
81
|
|
|
77
|
-
|
|
82
|
+
# Regex now includes trailing / and ** to capture directory intentions
|
|
83
|
+
pattern = re.compile(r"@([\w./\-_*]+)")
|
|
78
84
|
expanded_files = []
|
|
79
85
|
|
|
80
86
|
def replacer(match: re.Match) -> str:
|
|
81
|
-
|
|
82
|
-
if not os.path.exists(path):
|
|
83
|
-
raise ValueError(ERROR_FILE_NOT_FOUND.format(filepath=path))
|
|
87
|
+
path_spec = match.group(1)
|
|
84
88
|
|
|
85
|
-
|
|
86
|
-
|
|
89
|
+
is_recursive = path_spec.endswith("/**")
|
|
90
|
+
is_dir = path_spec.endswith("/")
|
|
87
91
|
|
|
88
|
-
|
|
89
|
-
|
|
92
|
+
# Determine the actual path to check on the filesystem
|
|
93
|
+
if is_recursive:
|
|
94
|
+
base_path = path_spec[:-3]
|
|
95
|
+
elif is_dir:
|
|
96
|
+
base_path = path_spec[:-1]
|
|
97
|
+
else:
|
|
98
|
+
base_path = path_spec
|
|
99
|
+
|
|
100
|
+
if not os.path.exists(base_path):
|
|
101
|
+
raise ValueError(ERROR_FILE_NOT_FOUND.format(filepath=base_path))
|
|
102
|
+
|
|
103
|
+
# For Recursive Directory Expansion ---
|
|
104
|
+
if is_recursive:
|
|
105
|
+
if not os.path.isdir(base_path):
|
|
106
|
+
raise ValueError(
|
|
107
|
+
f"Error: Path '{base_path}' for recursive expansion is not a directory."
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
all_contents = [f"\n=== START RECURSIVE EXPANSION: {path_spec} ===\n"]
|
|
111
|
+
total_size, file_count = 0, 0
|
|
112
|
+
|
|
113
|
+
for root, _, filenames in os.walk(base_path):
|
|
114
|
+
for filename in filenames:
|
|
115
|
+
if file_count >= MAX_FILES_IN_DIR:
|
|
116
|
+
all_contents.append(
|
|
117
|
+
ERROR_DIR_TOO_MANY_FILES.format(path=base_path, limit=MAX_FILES_IN_DIR)
|
|
118
|
+
)
|
|
119
|
+
break
|
|
120
|
+
|
|
121
|
+
file_path = os.path.join(root, filename)
|
|
122
|
+
content, size = _read_and_format_file(file_path, expanded_files)
|
|
123
|
+
|
|
124
|
+
if total_size + size > MAX_TOTAL_DIR_SIZE:
|
|
125
|
+
all_contents.append(
|
|
126
|
+
ERROR_DIR_TOO_LARGE.format(
|
|
127
|
+
path=base_path, limit_mb=MAX_TOTAL_DIR_SIZE / (1024 * 1024)
|
|
128
|
+
)
|
|
129
|
+
)
|
|
130
|
+
break
|
|
131
|
+
|
|
132
|
+
all_contents.append(content)
|
|
133
|
+
total_size += size
|
|
134
|
+
file_count += 1
|
|
135
|
+
if file_count >= MAX_FILES_IN_DIR or total_size > MAX_TOTAL_DIR_SIZE:
|
|
136
|
+
break
|
|
137
|
+
|
|
138
|
+
all_contents.append(f"\n=== END RECURSIVE EXPANSION: {path_spec} ===\n")
|
|
139
|
+
return "".join(all_contents)
|
|
140
|
+
|
|
141
|
+
# For Non-Recursive Directory Expansion
|
|
142
|
+
if is_dir:
|
|
143
|
+
if not os.path.isdir(base_path):
|
|
144
|
+
raise ValueError(
|
|
145
|
+
f"Error: Path '{base_path}' for directory expansion is not a directory."
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
all_contents = [f"\n=== START DIRECTORY EXPANSION: {path_spec} ===\n"]
|
|
149
|
+
total_size, file_count = 0, 0
|
|
150
|
+
|
|
151
|
+
for item_name in sorted(os.listdir(base_path)):
|
|
152
|
+
item_path = os.path.join(base_path, item_name)
|
|
153
|
+
if os.path.isfile(item_path):
|
|
154
|
+
if file_count >= MAX_FILES_IN_DIR:
|
|
155
|
+
all_contents.append(
|
|
156
|
+
ERROR_DIR_TOO_MANY_FILES.format(path=base_path, limit=MAX_FILES_IN_DIR)
|
|
157
|
+
)
|
|
158
|
+
break
|
|
159
|
+
|
|
160
|
+
content, size = _read_and_format_file(item_path, expanded_files)
|
|
161
|
+
if total_size + size > MAX_TOTAL_DIR_SIZE:
|
|
162
|
+
all_contents.append(
|
|
163
|
+
ERROR_DIR_TOO_LARGE.format(
|
|
164
|
+
path=base_path, limit_mb=MAX_TOTAL_DIR_SIZE / (1024 * 1024)
|
|
165
|
+
)
|
|
166
|
+
)
|
|
167
|
+
break
|
|
168
|
+
|
|
169
|
+
all_contents.append(content)
|
|
170
|
+
total_size += size
|
|
171
|
+
file_count += 1
|
|
172
|
+
|
|
173
|
+
all_contents.append(f"\n=== END DIRECTORY EXPANSION: {path_spec} ===\n")
|
|
174
|
+
return "".join(all_contents)
|
|
175
|
+
|
|
176
|
+
# For Single File Expansion
|
|
177
|
+
if os.path.isfile(base_path):
|
|
178
|
+
content, _ = _read_and_format_file(base_path, expanded_files)
|
|
179
|
+
return content
|
|
180
|
+
|
|
181
|
+
raise ValueError(f"Path '{base_path}' is not a valid file or directory specification.")
|
|
90
182
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
expanded_files.append(abs_path)
|
|
183
|
+
expanded_text = pattern.sub(replacer, text)
|
|
184
|
+
return expanded_text, list(set(expanded_files))
|
|
94
185
|
|
|
95
|
-
lang = ext_to_lang(path)
|
|
96
|
-
# Add clear headers to indicate this is a file reference, not code to write
|
|
97
|
-
return f"\n=== FILE REFERENCE: {path} ===\n```{lang}\n{content}\n```\n=== END FILE REFERENCE: {path} ===\n"
|
|
98
186
|
|
|
99
|
-
|
|
100
|
-
|
|
187
|
+
def _read_and_format_file(file_path: str, expanded_files_tracker: List[str]) -> Tuple[str, int]:
|
|
188
|
+
"""Reads a single file, formats it, and checks size limits."""
|
|
189
|
+
from tunacode.constants import MAX_FILE_SIZE
|
|
190
|
+
|
|
191
|
+
if os.path.getsize(file_path) > MAX_FILE_SIZE:
|
|
192
|
+
# Instead of raising an error, we'll just note it and skip or process gets terminated.
|
|
193
|
+
return f"\n--- SKIPPED (too large): {file_path} ---\n", 0
|
|
194
|
+
|
|
195
|
+
with open(file_path, "r", encoding="utf-8", errors="ignore") as f:
|
|
196
|
+
content = f.read()
|
|
197
|
+
|
|
198
|
+
abs_path = os.path.abspath(file_path)
|
|
199
|
+
expanded_files_tracker.append(abs_path)
|
|
200
|
+
|
|
201
|
+
lang = ext_to_lang(file_path)
|
|
202
|
+
header = f"=== FILE REFERENCE: {file_path} ==="
|
|
203
|
+
footer = f"=== END FILE REFERENCE: {file_path} ==="
|
|
204
|
+
|
|
205
|
+
formatted_content = f"\n{header}\n```{lang}\n{content}\n```\n{footer}\n"
|
|
206
|
+
return formatted_content, len(content.encode("utf-8"))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tunacode-cli
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.40
|
|
4
4
|
Summary: Your agentic CLI developer.
|
|
5
5
|
Author-email: larock22 <noreply@github.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -31,6 +31,7 @@ Requires-Dist: pytest; extra == "dev"
|
|
|
31
31
|
Requires-Dist: pytest-cov; extra == "dev"
|
|
32
32
|
Requires-Dist: pytest-asyncio; extra == "dev"
|
|
33
33
|
Requires-Dist: textual-dev; extra == "dev"
|
|
34
|
+
Requires-Dist: pre-commit; extra == "dev"
|
|
34
35
|
Dynamic: license-file
|
|
35
36
|
|
|
36
37
|
# TunaCode
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
tunacode/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
tunacode/constants.py,sha256=
|
|
2
|
+
tunacode/constants.py,sha256=70p2W_hEiyypHaSXzMjnsHlbJsS8kg95-YRDi-iNEs8,4406
|
|
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
|
|
@@ -76,12 +76,12 @@ tunacode/utils/import_cache.py,sha256=q_xjJbtju05YbFopLDSkIo1hOtCx3DOTl3GQE5FFDg
|
|
|
76
76
|
tunacode/utils/ripgrep.py,sha256=AXUs2FFt0A7KBV996deS8wreIlUzKOlAHJmwrcAr4No,583
|
|
77
77
|
tunacode/utils/security.py,sha256=e_zo9VmcOKFjgFMr9GOBIFhAmND4PBlJZgY7zqnsGjI,6548
|
|
78
78
|
tunacode/utils/system.py,sha256=FSoibTIH0eybs4oNzbYyufIiV6gb77QaeY2yGqW39AY,11381
|
|
79
|
-
tunacode/utils/text_utils.py,sha256=
|
|
79
|
+
tunacode/utils/text_utils.py,sha256=6YBD9QfkDO44-6jxnwRWIpmfIifPG-NqMzy_O2NAouc,7277
|
|
80
80
|
tunacode/utils/token_counter.py,sha256=nGCWwrHHFbKywqeDCEuJnADCkfJuzysWiB6cCltJOKI,648
|
|
81
81
|
tunacode/utils/user_configuration.py,sha256=Ilz8dpGVJDBE2iLWHAPT0xR8D51VRKV3kIbsAz8Bboc,3275
|
|
82
|
-
tunacode_cli-0.0.
|
|
83
|
-
tunacode_cli-0.0.
|
|
84
|
-
tunacode_cli-0.0.
|
|
85
|
-
tunacode_cli-0.0.
|
|
86
|
-
tunacode_cli-0.0.
|
|
87
|
-
tunacode_cli-0.0.
|
|
82
|
+
tunacode_cli-0.0.40.dist-info/licenses/LICENSE,sha256=Btzdu2kIoMbdSp6OyCLupB1aRgpTCJ_szMimgEnpkkE,1056
|
|
83
|
+
tunacode_cli-0.0.40.dist-info/METADATA,sha256=77mabiAPHOIxkWTQjhTkzhKxENwIGRO4yA_xhbkHW3c,5106
|
|
84
|
+
tunacode_cli-0.0.40.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
85
|
+
tunacode_cli-0.0.40.dist-info/entry_points.txt,sha256=hbkytikj4dGu6rizPuAd_DGUPBGF191RTnhr9wdhORY,51
|
|
86
|
+
tunacode_cli-0.0.40.dist-info/top_level.txt,sha256=lKy2P6BWNi5XSA4DHFvyjQ14V26lDZctwdmhEJrxQbU,9
|
|
87
|
+
tunacode_cli-0.0.40.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|