tunacode-cli 0.0.51__py3-none-any.whl → 0.0.53__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/base.py +2 -2
- tunacode/cli/commands/implementations/__init__.py +7 -1
- tunacode/cli/commands/implementations/conversation.py +1 -1
- tunacode/cli/commands/implementations/debug.py +1 -1
- tunacode/cli/commands/implementations/development.py +4 -1
- tunacode/cli/commands/implementations/template.py +132 -0
- tunacode/cli/commands/registry.py +28 -1
- tunacode/cli/commands/template_shortcut.py +93 -0
- tunacode/cli/main.py +6 -0
- tunacode/cli/repl.py +29 -174
- tunacode/cli/repl_components/__init__.py +10 -0
- tunacode/cli/repl_components/command_parser.py +34 -0
- tunacode/cli/repl_components/error_recovery.py +88 -0
- tunacode/cli/repl_components/output_display.py +33 -0
- tunacode/cli/repl_components/tool_executor.py +84 -0
- tunacode/configuration/defaults.py +2 -2
- tunacode/configuration/settings.py +11 -14
- tunacode/constants.py +57 -23
- tunacode/context.py +0 -14
- tunacode/core/agents/agent_components/__init__.py +27 -0
- tunacode/core/agents/agent_components/agent_config.py +109 -0
- tunacode/core/agents/agent_components/json_tool_parser.py +109 -0
- tunacode/core/agents/agent_components/message_handler.py +100 -0
- tunacode/core/agents/agent_components/node_processor.py +480 -0
- tunacode/core/agents/agent_components/response_state.py +13 -0
- tunacode/core/agents/agent_components/result_wrapper.py +50 -0
- tunacode/core/agents/agent_components/task_completion.py +28 -0
- tunacode/core/agents/agent_components/tool_buffer.py +24 -0
- tunacode/core/agents/agent_components/tool_executor.py +49 -0
- tunacode/core/agents/main.py +421 -778
- tunacode/core/agents/utils.py +42 -2
- tunacode/core/background/manager.py +3 -3
- tunacode/core/logging/__init__.py +4 -3
- tunacode/core/logging/config.py +1 -1
- tunacode/core/logging/formatters.py +1 -1
- tunacode/core/logging/handlers.py +41 -7
- tunacode/core/setup/__init__.py +2 -0
- tunacode/core/setup/agent_setup.py +2 -2
- tunacode/core/setup/base.py +2 -2
- tunacode/core/setup/config_setup.py +10 -6
- tunacode/core/setup/git_safety_setup.py +13 -2
- tunacode/core/setup/template_setup.py +75 -0
- tunacode/core/state.py +13 -2
- tunacode/core/token_usage/api_response_parser.py +6 -2
- tunacode/core/token_usage/usage_tracker.py +37 -7
- tunacode/core/tool_handler.py +24 -1
- tunacode/prompts/system.md +289 -4
- tunacode/setup.py +2 -0
- tunacode/templates/__init__.py +9 -0
- tunacode/templates/loader.py +210 -0
- tunacode/tools/glob.py +3 -3
- tunacode/tools/grep.py +26 -276
- tunacode/tools/grep_components/__init__.py +9 -0
- tunacode/tools/grep_components/file_filter.py +93 -0
- tunacode/tools/grep_components/pattern_matcher.py +152 -0
- tunacode/tools/grep_components/result_formatter.py +45 -0
- tunacode/tools/grep_components/search_result.py +35 -0
- tunacode/tools/todo.py +27 -21
- tunacode/types.py +19 -4
- tunacode/ui/completers.py +6 -1
- tunacode/ui/decorators.py +2 -2
- tunacode/ui/keybindings.py +1 -1
- tunacode/ui/panels.py +13 -5
- tunacode/ui/prompt_manager.py +1 -1
- tunacode/ui/tool_ui.py +8 -2
- tunacode/utils/bm25.py +4 -4
- tunacode/utils/file_utils.py +2 -2
- tunacode/utils/message_utils.py +3 -1
- tunacode/utils/system.py +0 -4
- tunacode/utils/text_utils.py +1 -1
- tunacode/utils/token_counter.py +2 -2
- {tunacode_cli-0.0.51.dist-info → tunacode_cli-0.0.53.dist-info}/METADATA +146 -1
- tunacode_cli-0.0.53.dist-info/RECORD +123 -0
- {tunacode_cli-0.0.51.dist-info → tunacode_cli-0.0.53.dist-info}/top_level.txt +0 -1
- api/auth.py +0 -13
- api/users.py +0 -8
- tunacode/core/recursive/__init__.py +0 -18
- tunacode/core/recursive/aggregator.py +0 -467
- tunacode/core/recursive/budget.py +0 -414
- tunacode/core/recursive/decomposer.py +0 -398
- tunacode/core/recursive/executor.py +0 -470
- tunacode/core/recursive/hierarchy.py +0 -488
- tunacode/ui/recursive_progress.py +0 -380
- tunacode_cli-0.0.51.dist-info/RECORD +0 -107
- {tunacode_cli-0.0.51.dist-info → tunacode_cli-0.0.53.dist-info}/WHEEL +0 -0
- {tunacode_cli-0.0.51.dist-info → tunacode_cli-0.0.53.dist-info}/entry_points.txt +0 -0
- {tunacode_cli-0.0.51.dist-info → tunacode_cli-0.0.53.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Search result and configuration data structures for the grep tool.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import List, Optional
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class SearchResult:
|
|
11
|
+
"""Represents a single search match with context."""
|
|
12
|
+
|
|
13
|
+
file_path: str
|
|
14
|
+
line_number: int
|
|
15
|
+
line_content: str
|
|
16
|
+
match_start: int
|
|
17
|
+
match_end: int
|
|
18
|
+
context_before: List[str]
|
|
19
|
+
context_after: List[str]
|
|
20
|
+
relevance_score: float = 0.0
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@dataclass
|
|
24
|
+
class SearchConfig:
|
|
25
|
+
"""Configuration for search operations."""
|
|
26
|
+
|
|
27
|
+
case_sensitive: bool = False
|
|
28
|
+
use_regex: bool = False
|
|
29
|
+
max_results: int = 50
|
|
30
|
+
context_lines: int = 2
|
|
31
|
+
include_patterns: Optional[List[str]] = None
|
|
32
|
+
exclude_patterns: Optional[List[str]] = None
|
|
33
|
+
max_file_size: int = 1024 * 1024 # 1MB
|
|
34
|
+
timeout_seconds: int = 30
|
|
35
|
+
first_match_deadline: float = 3.0 # Timeout for finding first match
|
tunacode/tools/todo.py
CHANGED
|
@@ -14,8 +14,8 @@ from tunacode.constants import (
|
|
|
14
14
|
MAX_TODO_CONTENT_LENGTH,
|
|
15
15
|
MAX_TODOS_PER_SESSION,
|
|
16
16
|
TODO_PRIORITIES,
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
TodoPriority,
|
|
18
|
+
TodoStatus,
|
|
19
19
|
)
|
|
20
20
|
from tunacode.types import TodoItem, ToolResult, UILogger
|
|
21
21
|
|
|
@@ -65,10 +65,14 @@ class TodoTool(BaseTool):
|
|
|
65
65
|
ModelRetry: When invalid parameters are provided
|
|
66
66
|
"""
|
|
67
67
|
if action == "add":
|
|
68
|
+
if isinstance(content, list):
|
|
69
|
+
raise ModelRetry("Use 'add_multiple' action for adding multiple todos")
|
|
68
70
|
return await self._add_todo(content, priority)
|
|
69
71
|
elif action == "add_multiple":
|
|
70
72
|
return await self._add_multiple_todos(content, todos, priority)
|
|
71
73
|
elif action == "update":
|
|
74
|
+
if isinstance(content, list):
|
|
75
|
+
raise ModelRetry("Cannot update with list content")
|
|
72
76
|
return await self._update_todo(todo_id, status, priority, content)
|
|
73
77
|
elif action == "complete":
|
|
74
78
|
return await self._complete_todo(todo_id)
|
|
@@ -102,16 +106,16 @@ class TodoTool(BaseTool):
|
|
|
102
106
|
new_id = f"todo_{uuid.uuid4().hex[:8]}"
|
|
103
107
|
|
|
104
108
|
# Default priority if not specified
|
|
105
|
-
todo_priority = priority or
|
|
106
|
-
if todo_priority not in
|
|
109
|
+
todo_priority = priority or TodoPriority.MEDIUM
|
|
110
|
+
if todo_priority not in [p.value for p in TodoPriority]:
|
|
107
111
|
raise ModelRetry(
|
|
108
|
-
f"Invalid priority '{todo_priority}'. Must be one of: {', '.join(
|
|
112
|
+
f"Invalid priority '{todo_priority}'. Must be one of: {', '.join([p.value for p in TodoPriority])}"
|
|
109
113
|
)
|
|
110
114
|
|
|
111
115
|
new_todo = TodoItem(
|
|
112
116
|
id=new_id,
|
|
113
117
|
content=content,
|
|
114
|
-
status=
|
|
118
|
+
status=TodoStatus.PENDING,
|
|
115
119
|
priority=todo_priority,
|
|
116
120
|
created_at=datetime.now(),
|
|
117
121
|
)
|
|
@@ -136,7 +140,7 @@ class TodoTool(BaseTool):
|
|
|
136
140
|
if not isinstance(todo_data, dict) or "content" not in todo_data:
|
|
137
141
|
raise ModelRetry("Each todo must be a dict with 'content' field")
|
|
138
142
|
todo_content = todo_data["content"]
|
|
139
|
-
todo_priority = todo_data.get("priority", priority or
|
|
143
|
+
todo_priority = todo_data.get("priority", priority or TodoPriority.MEDIUM)
|
|
140
144
|
if todo_priority not in TODO_PRIORITIES:
|
|
141
145
|
raise ModelRetry(
|
|
142
146
|
f"Invalid priority '{todo_priority}'. Must be one of: {', '.join(TODO_PRIORITIES)}"
|
|
@@ -144,7 +148,7 @@ class TodoTool(BaseTool):
|
|
|
144
148
|
todos_to_add.append((todo_content, todo_priority))
|
|
145
149
|
elif isinstance(content, list):
|
|
146
150
|
# List of strings format: ["task1", "task2", ...]
|
|
147
|
-
default_priority = priority or
|
|
151
|
+
default_priority = priority or TodoPriority.MEDIUM
|
|
148
152
|
if default_priority not in TODO_PRIORITIES:
|
|
149
153
|
raise ModelRetry(
|
|
150
154
|
f"Invalid priority '{default_priority}'. Must be one of: {', '.join(TODO_PRIORITIES)}"
|
|
@@ -184,7 +188,7 @@ class TodoTool(BaseTool):
|
|
|
184
188
|
new_todo = TodoItem(
|
|
185
189
|
id=new_id,
|
|
186
190
|
content=task_content,
|
|
187
|
-
status=
|
|
191
|
+
status=TodoStatus.PENDING,
|
|
188
192
|
priority=task_priority,
|
|
189
193
|
created_at=datetime.now(),
|
|
190
194
|
)
|
|
@@ -220,19 +224,21 @@ class TodoTool(BaseTool):
|
|
|
220
224
|
|
|
221
225
|
# Update status if provided
|
|
222
226
|
if status:
|
|
223
|
-
if status not in [
|
|
227
|
+
if status not in [s.value for s in TodoStatus]:
|
|
224
228
|
raise ModelRetry(
|
|
225
|
-
f"Invalid status '{status}'. Must be
|
|
229
|
+
f"Invalid status '{status}'. Must be one of: {', '.join([s.value for s in TodoStatus])}"
|
|
226
230
|
)
|
|
227
231
|
todo.status = status
|
|
228
|
-
if status ==
|
|
232
|
+
if status == TodoStatus.COMPLETED.value and not todo.completed_at:
|
|
229
233
|
todo.completed_at = datetime.now()
|
|
230
234
|
changes.append(f"status to {status}")
|
|
231
235
|
|
|
232
236
|
# Update priority if provided
|
|
233
237
|
if priority:
|
|
234
|
-
if priority not in [
|
|
235
|
-
raise ModelRetry(
|
|
238
|
+
if priority not in [p.value for p in TodoPriority]:
|
|
239
|
+
raise ModelRetry(
|
|
240
|
+
f"Invalid priority '{priority}'. Must be one of: {', '.join([p.value for p in TodoPriority])}"
|
|
241
|
+
)
|
|
236
242
|
todo.priority = priority
|
|
237
243
|
changes.append(f"priority to {priority}")
|
|
238
244
|
|
|
@@ -257,7 +263,7 @@ class TodoTool(BaseTool):
|
|
|
257
263
|
# Find and update the todo
|
|
258
264
|
for todo in self.state_manager.session.todos:
|
|
259
265
|
if todo.id == todo_id:
|
|
260
|
-
todo.status =
|
|
266
|
+
todo.status = TodoStatus.COMPLETED.value
|
|
261
267
|
todo.completed_at = datetime.now()
|
|
262
268
|
return f"Marked todo {todo_id} as completed: {todo.content}"
|
|
263
269
|
|
|
@@ -270,9 +276,9 @@ class TodoTool(BaseTool):
|
|
|
270
276
|
return "No todos found"
|
|
271
277
|
|
|
272
278
|
# Group by status for better organization
|
|
273
|
-
pending = [t for t in todos if t.status ==
|
|
274
|
-
in_progress = [t for t in todos if t.status ==
|
|
275
|
-
completed = [t for t in todos if t.status ==
|
|
279
|
+
pending = [t for t in todos if t.status == TodoStatus.PENDING.value]
|
|
280
|
+
in_progress = [t for t in todos if t.status == TodoStatus.IN_PROGRESS.value]
|
|
281
|
+
completed = [t for t in todos if t.status == TodoStatus.COMPLETED.value]
|
|
276
282
|
|
|
277
283
|
lines = []
|
|
278
284
|
|
|
@@ -319,9 +325,9 @@ class TodoTool(BaseTool):
|
|
|
319
325
|
return "No todos found"
|
|
320
326
|
|
|
321
327
|
# Group by status for better organization
|
|
322
|
-
pending = [t for t in todos if t.status ==
|
|
323
|
-
in_progress = [t for t in todos if t.status ==
|
|
324
|
-
completed = [t for t in todos if t.status ==
|
|
328
|
+
pending = [t for t in todos if t.status == TodoStatus.PENDING.value]
|
|
329
|
+
in_progress = [t for t in todos if t.status == TodoStatus.IN_PROGRESS.value]
|
|
330
|
+
completed = [t for t in todos if t.status == TodoStatus.COMPLETED.value]
|
|
325
331
|
|
|
326
332
|
lines = []
|
|
327
333
|
|
tunacode/types.py
CHANGED
|
@@ -8,7 +8,18 @@ used throughout the TunaCode codebase.
|
|
|
8
8
|
from dataclasses import dataclass, field
|
|
9
9
|
from datetime import datetime
|
|
10
10
|
from pathlib import Path
|
|
11
|
-
from typing import
|
|
11
|
+
from typing import (
|
|
12
|
+
Any,
|
|
13
|
+
Awaitable,
|
|
14
|
+
Callable,
|
|
15
|
+
Dict,
|
|
16
|
+
List,
|
|
17
|
+
Literal,
|
|
18
|
+
Optional,
|
|
19
|
+
Protocol,
|
|
20
|
+
Tuple,
|
|
21
|
+
Union,
|
|
22
|
+
)
|
|
12
23
|
|
|
13
24
|
# Try to import pydantic-ai types if available
|
|
14
25
|
try:
|
|
@@ -17,12 +28,14 @@ try:
|
|
|
17
28
|
|
|
18
29
|
PydanticAgent = Agent
|
|
19
30
|
MessagePart = Union[ToolReturnPart, Any]
|
|
31
|
+
ModelRequest = ModelRequest # type: ignore[misc]
|
|
32
|
+
ModelResponse = Any
|
|
20
33
|
except ImportError:
|
|
21
34
|
# Fallback if pydantic-ai is not available
|
|
22
35
|
PydanticAgent = Any
|
|
23
|
-
MessagePart = Any
|
|
36
|
+
MessagePart = Any # type: ignore[misc]
|
|
24
37
|
ModelRequest = Any
|
|
25
|
-
ModelResponse = Any
|
|
38
|
+
ModelResponse = Any # type: ignore[misc]
|
|
26
39
|
|
|
27
40
|
|
|
28
41
|
@dataclass
|
|
@@ -94,7 +107,7 @@ ToolCallId = str
|
|
|
94
107
|
class ToolFunction(Protocol):
|
|
95
108
|
"""Protocol for tool functions."""
|
|
96
109
|
|
|
97
|
-
async def __call__(self, *
|
|
110
|
+
async def __call__(self, *_args, **kwargs) -> str: ...
|
|
98
111
|
|
|
99
112
|
|
|
100
113
|
@dataclass
|
|
@@ -158,6 +171,8 @@ class ResponseState:
|
|
|
158
171
|
|
|
159
172
|
has_user_response: bool = False
|
|
160
173
|
has_final_synthesis: bool = False
|
|
174
|
+
task_completed: bool = False
|
|
175
|
+
awaiting_user_guidance: bool = False
|
|
161
176
|
|
|
162
177
|
|
|
163
178
|
@dataclass
|
tunacode/ui/completers.py
CHANGED
|
@@ -3,7 +3,12 @@
|
|
|
3
3
|
import os
|
|
4
4
|
from typing import TYPE_CHECKING, Iterable, Optional
|
|
5
5
|
|
|
6
|
-
from prompt_toolkit.completion import
|
|
6
|
+
from prompt_toolkit.completion import (
|
|
7
|
+
CompleteEvent,
|
|
8
|
+
Completer,
|
|
9
|
+
Completion,
|
|
10
|
+
merge_completers,
|
|
11
|
+
)
|
|
7
12
|
from prompt_toolkit.document import Document
|
|
8
13
|
|
|
9
14
|
if TYPE_CHECKING:
|
tunacode/ui/decorators.py
CHANGED
|
@@ -53,7 +53,7 @@ def create_sync_wrapper(async_func: F) -> F:
|
|
|
53
53
|
)
|
|
54
54
|
|
|
55
55
|
# Attach the sync version as an attribute
|
|
56
|
-
async_func
|
|
56
|
+
setattr(async_func, "sync", sync_wrapper)
|
|
57
57
|
|
|
58
58
|
# Return the original async function
|
|
59
|
-
return async_func
|
|
59
|
+
return async_func # type: ignore[return-value]
|
tunacode/ui/keybindings.py
CHANGED
|
@@ -44,7 +44,7 @@ def create_key_bindings(state_manager: StateManager = None) -> KeyBindings:
|
|
|
44
44
|
if session.last_esc_time and (current_time - session.last_esc_time) > 3.0:
|
|
45
45
|
session.esc_press_count = 0
|
|
46
46
|
|
|
47
|
-
session.esc_press_count
|
|
47
|
+
session.esc_press_count = (session.esc_press_count or 0) + 1
|
|
48
48
|
session.last_esc_time = current_time
|
|
49
49
|
|
|
50
50
|
logger.debug(f"ESC key pressed: count={session.esc_press_count}, time={current_time}")
|
tunacode/ui/panels.py
CHANGED
|
@@ -47,7 +47,7 @@ colors = DotDict(UI_COLORS)
|
|
|
47
47
|
@create_sync_wrapper
|
|
48
48
|
async def panel(
|
|
49
49
|
title: str,
|
|
50
|
-
text: Union[str, Markdown, Pretty],
|
|
50
|
+
text: Union[str, Markdown, Pretty, Table],
|
|
51
51
|
top: int = DEFAULT_PANEL_PADDING["top"],
|
|
52
52
|
right: int = DEFAULT_PANEL_PADDING["right"],
|
|
53
53
|
bottom: int = DEFAULT_PANEL_PADDING["bottom"],
|
|
@@ -57,6 +57,7 @@ async def panel(
|
|
|
57
57
|
) -> None:
|
|
58
58
|
"""Display a rich panel with modern styling."""
|
|
59
59
|
border_style = border_style or kwargs.get("style") or colors.border
|
|
60
|
+
|
|
60
61
|
panel_obj = Panel(
|
|
61
62
|
Padding(text, (0, 1, 0, 1)),
|
|
62
63
|
title=f"[bold]{title}[/bold]",
|
|
@@ -65,7 +66,10 @@ async def panel(
|
|
|
65
66
|
padding=(0, 1),
|
|
66
67
|
box=ROUNDED, # Use ROUNDED box style
|
|
67
68
|
)
|
|
68
|
-
|
|
69
|
+
|
|
70
|
+
final_padding = Padding(panel_obj, (top, right, bottom, left))
|
|
71
|
+
|
|
72
|
+
await print(final_padding, **kwargs)
|
|
69
73
|
|
|
70
74
|
|
|
71
75
|
async def agent(text: str, bottom: int = 1) -> None:
|
|
@@ -83,7 +87,7 @@ class StreamingAgentPanel:
|
|
|
83
87
|
self.content = ""
|
|
84
88
|
self.live = None
|
|
85
89
|
|
|
86
|
-
def _create_panel(self) ->
|
|
90
|
+
def _create_panel(self) -> Padding:
|
|
87
91
|
"""Create a Rich panel with current content."""
|
|
88
92
|
# Use the UI_THINKING_MESSAGE constant instead of hardcoded text
|
|
89
93
|
from rich.text import Text
|
|
@@ -92,7 +96,7 @@ class StreamingAgentPanel:
|
|
|
92
96
|
|
|
93
97
|
# Handle the default thinking message with Rich markup
|
|
94
98
|
if not self.content:
|
|
95
|
-
content_renderable = Text.from_markup(UI_THINKING_MESSAGE)
|
|
99
|
+
content_renderable: Union[Text, Markdown] = Text.from_markup(UI_THINKING_MESSAGE)
|
|
96
100
|
else:
|
|
97
101
|
content_renderable = Markdown(self.content)
|
|
98
102
|
panel_obj = Panel(
|
|
@@ -122,7 +126,11 @@ class StreamingAgentPanel:
|
|
|
122
126
|
|
|
123
127
|
async def update(self, content_chunk: str):
|
|
124
128
|
"""Update the streaming display with new content."""
|
|
125
|
-
|
|
129
|
+
# Defensive: some providers may yield None chunks intermittently
|
|
130
|
+
if content_chunk is None:
|
|
131
|
+
content_chunk = ""
|
|
132
|
+
# Ensure type safety for concatenation
|
|
133
|
+
self.content = (self.content or "") + str(content_chunk)
|
|
126
134
|
if self.live:
|
|
127
135
|
self.live.update(self._create_panel())
|
|
128
136
|
|
tunacode/ui/prompt_manager.py
CHANGED
|
@@ -40,7 +40,7 @@ class PromptManager:
|
|
|
40
40
|
state_manager: Optional state manager for session persistence
|
|
41
41
|
"""
|
|
42
42
|
self.state_manager = state_manager
|
|
43
|
-
self._temp_sessions = {} # For when no state manager is available
|
|
43
|
+
self._temp_sessions: dict[str, PromptSession] = {} # For when no state manager is available
|
|
44
44
|
self._style = self._create_style()
|
|
45
45
|
|
|
46
46
|
def _create_style(self) -> Style:
|
tunacode/ui/tool_ui.py
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
Tool confirmation UI components, separated from business logic.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
from typing import TYPE_CHECKING, Optional
|
|
6
|
+
|
|
5
7
|
from rich.box import ROUNDED
|
|
6
8
|
from rich.markdown import Markdown
|
|
7
9
|
from rich.padding import Padding
|
|
@@ -16,6 +18,9 @@ from tunacode.utils.diff_utils import render_file_diff
|
|
|
16
18
|
from tunacode.utils.file_utils import DotDict
|
|
17
19
|
from tunacode.utils.text_utils import ext_to_lang, key_to_title
|
|
18
20
|
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
from tunacode.core.state import StateManager
|
|
23
|
+
|
|
19
24
|
|
|
20
25
|
class ToolUI:
|
|
21
26
|
"""Handles tool confirmation UI presentation."""
|
|
@@ -71,7 +76,8 @@ class ToolUI:
|
|
|
71
76
|
|
|
72
77
|
# Show file content on write_file
|
|
73
78
|
elif tool_name == TOOL_WRITE_FILE:
|
|
74
|
-
|
|
79
|
+
markdown_obj = self._create_code_block(args["filepath"], args["content"])
|
|
80
|
+
return str(markdown_obj)
|
|
75
81
|
|
|
76
82
|
# Default to showing key and value on new line
|
|
77
83
|
content = ""
|
|
@@ -92,7 +98,7 @@ class ToolUI:
|
|
|
92
98
|
return content.strip()
|
|
93
99
|
|
|
94
100
|
async def show_confirmation(
|
|
95
|
-
self, request: ToolConfirmationRequest, state_manager=None
|
|
101
|
+
self, request: ToolConfirmationRequest, state_manager: Optional["StateManager"] = None
|
|
96
102
|
) -> ToolConfirmationResponse:
|
|
97
103
|
"""
|
|
98
104
|
Show tool confirmation UI and get user response.
|
tunacode/utils/bm25.py
CHANGED
|
@@ -16,14 +16,14 @@ class BM25:
|
|
|
16
16
|
self.k1 = k1
|
|
17
17
|
self.b = b
|
|
18
18
|
self.documents = [tokenize(doc) for doc in corpus]
|
|
19
|
-
self.doc_freqs = []
|
|
20
|
-
self.doc_lens = []
|
|
21
|
-
self.idf = {}
|
|
19
|
+
self.doc_freqs: list[dict] = []
|
|
20
|
+
self.doc_lens: list[int] = []
|
|
21
|
+
self.idf: dict[str, float] = {}
|
|
22
22
|
self.avgdl = 0.0
|
|
23
23
|
self._initialize()
|
|
24
24
|
|
|
25
25
|
def _initialize(self) -> None:
|
|
26
|
-
df = Counter()
|
|
26
|
+
df: dict[str, int] = Counter()
|
|
27
27
|
for doc in self.documents:
|
|
28
28
|
freqs = Counter(doc)
|
|
29
29
|
self.doc_freqs.append(freqs)
|
tunacode/utils/file_utils.py
CHANGED
|
@@ -14,8 +14,8 @@ class DotDict(dict):
|
|
|
14
14
|
"""dot.notation access to dictionary attributes"""
|
|
15
15
|
|
|
16
16
|
__getattr__ = dict.get
|
|
17
|
-
__setattr__ = dict.__setitem__
|
|
18
|
-
__delattr__ = dict.__delitem__
|
|
17
|
+
__setattr__ = dict.__setitem__ # type: ignore[assignment]
|
|
18
|
+
__delattr__ = dict.__delitem__ # type: ignore[assignment]
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
@contextmanager
|
tunacode/utils/message_utils.py
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
"""Utilities for processing message history."""
|
|
2
2
|
|
|
3
|
+
from typing import Any
|
|
3
4
|
|
|
4
|
-
|
|
5
|
+
|
|
6
|
+
def get_message_content(message: Any) -> str:
|
|
5
7
|
"""Extracts the content from a message object of any type."""
|
|
6
8
|
if isinstance(message, str):
|
|
7
9
|
return message
|
tunacode/utils/system.py
CHANGED
|
@@ -85,9 +85,7 @@ def _load_gitignore_patterns(filepath=".gitignore"):
|
|
|
85
85
|
line = line.strip()
|
|
86
86
|
if line and not line.startswith("#"):
|
|
87
87
|
patterns.add(line)
|
|
88
|
-
# print(f"Loaded {len(patterns)} patterns from {filepath}") # Debug print (optional)
|
|
89
88
|
except FileNotFoundError:
|
|
90
|
-
# print(f"{filepath} not found.") # Debug print (optional)
|
|
91
89
|
return None
|
|
92
90
|
except Exception as e:
|
|
93
91
|
print(f"Error reading {filepath}: {e}")
|
|
@@ -322,8 +320,6 @@ def list_cwd(max_depth=3):
|
|
|
322
320
|
dir_rel_path = os.path.join(rel_root, d) if rel_root else d
|
|
323
321
|
if not _is_ignored(dir_rel_path, d, ignore_patterns):
|
|
324
322
|
dirs.append(d)
|
|
325
|
-
# else: # Optional debug print
|
|
326
|
-
# print(f"Ignoring dir: {dir_rel_path}")
|
|
327
323
|
|
|
328
324
|
# --- File Processing ---
|
|
329
325
|
if current_depth <= max_depth:
|
tunacode/utils/text_utils.py
CHANGED
|
@@ -81,7 +81,7 @@ def expand_file_refs(text: str) -> Tuple[str, List[str]]:
|
|
|
81
81
|
|
|
82
82
|
# Regex now includes trailing / and ** to capture directory intentions
|
|
83
83
|
pattern = re.compile(r"@([\w./\-_*]+)")
|
|
84
|
-
expanded_files = []
|
|
84
|
+
expanded_files: list[str] = []
|
|
85
85
|
|
|
86
86
|
def replacer(match: re.Match) -> str:
|
|
87
87
|
path_spec = match.group(1)
|
tunacode/utils/token_counter.py
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
4
|
from functools import lru_cache
|
|
5
|
-
from typing import Optional
|
|
5
|
+
from typing import Any, Optional
|
|
6
6
|
|
|
7
7
|
# Get logger for this module
|
|
8
8
|
logger = logging.getLogger(__name__)
|
|
9
9
|
|
|
10
10
|
# Cache for tokenizer encodings
|
|
11
|
-
_encoding_cache = {}
|
|
11
|
+
_encoding_cache: dict[str, Any] = {}
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
@lru_cache(maxsize=8)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tunacode-cli
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.53
|
|
4
4
|
Summary: Your agentic CLI developer.
|
|
5
5
|
Author-email: larock22 <noreply@github.com>
|
|
6
6
|
License: MIT
|
|
@@ -35,6 +35,10 @@ Requires-Dist: pytest-cov; extra == "dev"
|
|
|
35
35
|
Requires-Dist: pytest-asyncio; extra == "dev"
|
|
36
36
|
Requires-Dist: textual-dev; extra == "dev"
|
|
37
37
|
Requires-Dist: pre-commit; extra == "dev"
|
|
38
|
+
Requires-Dist: vulture>=2.7; extra == "dev"
|
|
39
|
+
Requires-Dist: unimport>=1.0.0; extra == "dev"
|
|
40
|
+
Requires-Dist: autoflake>=2.0.0; extra == "dev"
|
|
41
|
+
Requires-Dist: dead>=1.5.0; extra == "dev"
|
|
38
42
|
Dynamic: license-file
|
|
39
43
|
|
|
40
44
|
# TunaCode CLI
|
|
@@ -175,3 +179,144 @@ _Note: While the tool is fully functional, we're focusing on stability and core
|
|
|
175
179
|
---
|
|
176
180
|
|
|
177
181
|
MIT License - see [LICENSE](LICENSE) file
|
|
182
|
+
|
|
183
|
+
hello from tuna world
|
|
184
|
+
|
|
185
|
+
hello world
|
|
186
|
+
|
|
187
|
+
## Getting Started
|
|
188
|
+
|
|
189
|
+
### Prerequisites
|
|
190
|
+
|
|
191
|
+
Before you begin, ensure you have the following installed:
|
|
192
|
+
|
|
193
|
+
- Python 3.10 or higher
|
|
194
|
+
- Git
|
|
195
|
+
|
|
196
|
+
### Installation
|
|
197
|
+
|
|
198
|
+
To install TunaCode, you can use one of the following methods:
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
# Option 1: One-line install (Linux/macOS)
|
|
202
|
+
wget -qO- https://raw.githubusercontent.com/alchemiststudiosDOTai/tunacode/master/scripts/install_linux.sh | bash
|
|
203
|
+
|
|
204
|
+
# Option 2: pip install
|
|
205
|
+
pip install tunacode-cli
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Development Installation
|
|
209
|
+
|
|
210
|
+
For developers who want to contribute to TunaCode:
|
|
211
|
+
|
|
212
|
+
```bash
|
|
213
|
+
# Clone the repository
|
|
214
|
+
git clone https://github.com/alchemiststudiosDOTai/tunacode.git
|
|
215
|
+
cd tunacode
|
|
216
|
+
|
|
217
|
+
# Quick setup (recommended)
|
|
218
|
+
./scripts/setup_dev_env.sh
|
|
219
|
+
|
|
220
|
+
# Or manual setup
|
|
221
|
+
python3 -m venv venv
|
|
222
|
+
source venv/bin/activate # On Windows: venv\Scripts\activate
|
|
223
|
+
pip install -e ".[dev]"
|
|
224
|
+
|
|
225
|
+
# Verify installation
|
|
226
|
+
python -m tunacode --version
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
See [Development Guide](docs/DEVELOPMENT.md) for detailed instructions.
|
|
230
|
+
|
|
231
|
+
### Configuration
|
|
232
|
+
|
|
233
|
+
Choose your AI provider and set your API key:
|
|
234
|
+
|
|
235
|
+
```bash
|
|
236
|
+
# OpenAI
|
|
237
|
+
tunacode --model "openai:gpt-4o" --key "sk-your-openai-key"
|
|
238
|
+
|
|
239
|
+
# Anthropic Claude
|
|
240
|
+
tunacode --model "anthropic:claude-3.5-sonnet" --key "sk-ant-your-anthropic-key"
|
|
241
|
+
|
|
242
|
+
# OpenRouter (100+ models)
|
|
243
|
+
tunacode --model "openrouter:openai/gpt-4o" --key "sk-or-your-openrouter-key"
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Your config is saved to `~/.config/tunacode.json` (edit directly with `nvim ~/.config/tunacode.json`)
|
|
247
|
+
|
|
248
|
+
### Recommended Models
|
|
249
|
+
|
|
250
|
+
Based on extensive testing, these models provide the best performance:
|
|
251
|
+
|
|
252
|
+
- `google/gemini-2.5-pro` - Excellent for complex reasoning
|
|
253
|
+
- `openai/gpt-4.1` - Strong general-purpose model
|
|
254
|
+
- `deepseek/deepseek-r1-0528` - Great for code generation
|
|
255
|
+
- `openai/gpt-4.1-mini` - Fast and cost-effective
|
|
256
|
+
- `anthropic/claude-4-sonnet-20250522` - Superior context handling
|
|
257
|
+
|
|
258
|
+
_Note: Formal evaluations coming soon. Any model can work, but these have shown the best results in practice._
|
|
259
|
+
|
|
260
|
+
## Usage
|
|
261
|
+
|
|
262
|
+
### Starting TunaCode
|
|
263
|
+
|
|
264
|
+
```bash
|
|
265
|
+
tunacode
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### Basic Commands
|
|
269
|
+
|
|
270
|
+
| Command | Description |
|
|
271
|
+
| ------------------------ | ---------------------- |
|
|
272
|
+
| `/help` | Show all commands |
|
|
273
|
+
| `/model <provider:name>` | Switch model |
|
|
274
|
+
| `/clear` | Clear message history |
|
|
275
|
+
| `/compact` | Summarize conversation |
|
|
276
|
+
| `/branch <name>` | Create Git branch |
|
|
277
|
+
| `/yolo` | Skip confirmations |
|
|
278
|
+
| `!<command>` | Run shell command |
|
|
279
|
+
| `exit` | Exit TunaCode |
|
|
280
|
+
|
|
281
|
+
## Performance
|
|
282
|
+
|
|
283
|
+
TunaCode leverages parallel execution for read-only operations, achieving **3x faster** file operations:
|
|
284
|
+
|
|
285
|
+

|
|
286
|
+
|
|
287
|
+
Multiple file reads, directory listings, and searches execute concurrently using async I/O, making code exploration significantly faster.
|
|
288
|
+
|
|
289
|
+
## Features in Development
|
|
290
|
+
|
|
291
|
+
- **Streaming UI**: Currently working on implementing streaming responses for better user experience
|
|
292
|
+
- **Bug Fixes**: Actively addressing issues - please report any bugs you encounter!
|
|
293
|
+
|
|
294
|
+
_Note: While the tool is fully functional, we're focusing on stability and core features before optimizing for speed._
|
|
295
|
+
|
|
296
|
+
## Safety First
|
|
297
|
+
|
|
298
|
+
⚠️ **Important**: TunaCode can modify your codebase. Always:
|
|
299
|
+
|
|
300
|
+
- Use Git branches before making changes
|
|
301
|
+
- Review file modifications before confirming
|
|
302
|
+
- Keep backups of important work
|
|
303
|
+
|
|
304
|
+
## Documentation
|
|
305
|
+
|
|
306
|
+
- [**Features**](docs/FEATURES.md) - All features, tools, and commands
|
|
307
|
+
- [**Advanced Configuration**](docs/ADVANCED-CONFIG.md) - Provider setup, MCP, customization
|
|
308
|
+
- [**Architecture**](docs/ARCHITECTURE.md) - Source code organization and design
|
|
309
|
+
- [**Development**](docs/DEVELOPMENT.md) - Contributing and development setup
|
|
310
|
+
- [**Troubleshooting**](docs/TROUBLESHOOTING.md) - Common issues and solutions
|
|
311
|
+
|
|
312
|
+
## Links
|
|
313
|
+
|
|
314
|
+
- [PyPI Package](https://pypi.org/project/tunacode-cli/)
|
|
315
|
+
- [GitHub Repository](https://github.com/alchemiststudiosDOTai/tunacode)
|
|
316
|
+
- [Report Issues](https://github.com/alchemiststudiosDOTai/tunacode/issues)
|
|
317
|
+
|
|
318
|
+
---
|
|
319
|
+
|
|
320
|
+
MIT License - see [LICENSE](LICENSE) file
|
|
321
|
+
# Test
|
|
322
|
+
# Test
|