tunacode-cli 0.0.50__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.

Files changed (87) hide show
  1. tunacode/cli/commands/base.py +2 -2
  2. tunacode/cli/commands/implementations/__init__.py +7 -1
  3. tunacode/cli/commands/implementations/conversation.py +1 -1
  4. tunacode/cli/commands/implementations/debug.py +1 -1
  5. tunacode/cli/commands/implementations/development.py +4 -1
  6. tunacode/cli/commands/implementations/template.py +132 -0
  7. tunacode/cli/commands/registry.py +28 -1
  8. tunacode/cli/commands/template_shortcut.py +93 -0
  9. tunacode/cli/main.py +6 -0
  10. tunacode/cli/repl.py +29 -174
  11. tunacode/cli/repl_components/__init__.py +10 -0
  12. tunacode/cli/repl_components/command_parser.py +34 -0
  13. tunacode/cli/repl_components/error_recovery.py +88 -0
  14. tunacode/cli/repl_components/output_display.py +33 -0
  15. tunacode/cli/repl_components/tool_executor.py +84 -0
  16. tunacode/configuration/defaults.py +2 -2
  17. tunacode/configuration/settings.py +11 -14
  18. tunacode/constants.py +57 -23
  19. tunacode/context.py +0 -14
  20. tunacode/core/agents/agent_components/__init__.py +27 -0
  21. tunacode/core/agents/agent_components/agent_config.py +109 -0
  22. tunacode/core/agents/agent_components/json_tool_parser.py +109 -0
  23. tunacode/core/agents/agent_components/message_handler.py +100 -0
  24. tunacode/core/agents/agent_components/node_processor.py +480 -0
  25. tunacode/core/agents/agent_components/response_state.py +13 -0
  26. tunacode/core/agents/agent_components/result_wrapper.py +50 -0
  27. tunacode/core/agents/agent_components/task_completion.py +28 -0
  28. tunacode/core/agents/agent_components/tool_buffer.py +24 -0
  29. tunacode/core/agents/agent_components/tool_executor.py +49 -0
  30. tunacode/core/agents/main.py +421 -778
  31. tunacode/core/agents/utils.py +42 -2
  32. tunacode/core/background/manager.py +3 -3
  33. tunacode/core/logging/__init__.py +4 -3
  34. tunacode/core/logging/config.py +29 -16
  35. tunacode/core/logging/formatters.py +1 -1
  36. tunacode/core/logging/handlers.py +41 -7
  37. tunacode/core/setup/__init__.py +2 -0
  38. tunacode/core/setup/agent_setup.py +2 -2
  39. tunacode/core/setup/base.py +2 -2
  40. tunacode/core/setup/config_setup.py +10 -6
  41. tunacode/core/setup/git_safety_setup.py +13 -2
  42. tunacode/core/setup/template_setup.py +75 -0
  43. tunacode/core/state.py +13 -2
  44. tunacode/core/token_usage/api_response_parser.py +6 -2
  45. tunacode/core/token_usage/usage_tracker.py +37 -7
  46. tunacode/core/tool_handler.py +24 -1
  47. tunacode/prompts/system.md +289 -4
  48. tunacode/setup.py +2 -0
  49. tunacode/templates/__init__.py +9 -0
  50. tunacode/templates/loader.py +210 -0
  51. tunacode/tools/glob.py +3 -3
  52. tunacode/tools/grep.py +26 -276
  53. tunacode/tools/grep_components/__init__.py +9 -0
  54. tunacode/tools/grep_components/file_filter.py +93 -0
  55. tunacode/tools/grep_components/pattern_matcher.py +152 -0
  56. tunacode/tools/grep_components/result_formatter.py +45 -0
  57. tunacode/tools/grep_components/search_result.py +35 -0
  58. tunacode/tools/todo.py +27 -21
  59. tunacode/types.py +19 -4
  60. tunacode/ui/completers.py +6 -1
  61. tunacode/ui/decorators.py +2 -2
  62. tunacode/ui/keybindings.py +1 -1
  63. tunacode/ui/panels.py +13 -5
  64. tunacode/ui/prompt_manager.py +1 -1
  65. tunacode/ui/tool_ui.py +8 -2
  66. tunacode/utils/bm25.py +4 -4
  67. tunacode/utils/file_utils.py +2 -2
  68. tunacode/utils/message_utils.py +3 -1
  69. tunacode/utils/system.py +0 -4
  70. tunacode/utils/text_utils.py +1 -1
  71. tunacode/utils/token_counter.py +2 -2
  72. {tunacode_cli-0.0.50.dist-info → tunacode_cli-0.0.53.dist-info}/METADATA +146 -1
  73. tunacode_cli-0.0.53.dist-info/RECORD +123 -0
  74. {tunacode_cli-0.0.50.dist-info → tunacode_cli-0.0.53.dist-info}/top_level.txt +0 -1
  75. api/auth.py +0 -13
  76. api/users.py +0 -8
  77. tunacode/core/recursive/__init__.py +0 -18
  78. tunacode/core/recursive/aggregator.py +0 -467
  79. tunacode/core/recursive/budget.py +0 -414
  80. tunacode/core/recursive/decomposer.py +0 -398
  81. tunacode/core/recursive/executor.py +0 -470
  82. tunacode/core/recursive/hierarchy.py +0 -488
  83. tunacode/ui/recursive_progress.py +0 -380
  84. tunacode_cli-0.0.50.dist-info/RECORD +0 -107
  85. {tunacode_cli-0.0.50.dist-info → tunacode_cli-0.0.53.dist-info}/WHEEL +0 -0
  86. {tunacode_cli-0.0.50.dist-info → tunacode_cli-0.0.53.dist-info}/entry_points.txt +0 -0
  87. {tunacode_cli-0.0.50.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
- TODO_PRIORITY_MEDIUM,
18
- TODO_STATUS_PENDING,
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 TODO_PRIORITY_MEDIUM
106
- if todo_priority not in TODO_PRIORITIES:
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(TODO_PRIORITIES)}"
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=TODO_STATUS_PENDING,
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 TODO_PRIORITY_MEDIUM)
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 TODO_PRIORITY_MEDIUM
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=TODO_STATUS_PENDING,
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 ["pending", "in_progress", "completed"]:
227
+ if status not in [s.value for s in TodoStatus]:
224
228
  raise ModelRetry(
225
- f"Invalid status '{status}'. Must be pending, in_progress, or completed"
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 == "completed" and not todo.completed_at:
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 ["high", "medium", "low"]:
235
- raise ModelRetry(f"Invalid priority '{priority}'. Must be high, medium, or low")
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 = "completed"
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 == "pending"]
274
- in_progress = [t for t in todos if t.status == "in_progress"]
275
- completed = [t for t in todos if t.status == "completed"]
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 == "pending"]
323
- in_progress = [t for t in todos if t.status == "in_progress"]
324
- completed = [t for t in todos if t.status == "completed"]
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 Any, Awaitable, Callable, Dict, List, Literal, Optional, Protocol, Tuple, Union
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, *args, **kwargs) -> str: ...
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 CompleteEvent, Completer, Completion, merge_completers
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.sync = sync_wrapper
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]
@@ -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 += 1
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
- await print(Padding(panel_obj, (top, right, bottom, left)), **kwargs)
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) -> Panel:
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
- self.content += content_chunk
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
 
@@ -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
- return self._create_code_block(args["filepath"], args["content"])
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)
@@ -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
@@ -1,7 +1,9 @@
1
1
  """Utilities for processing message history."""
2
2
 
3
+ from typing import Any
3
4
 
4
- def get_message_content(message: any) -> str:
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:
@@ -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)
@@ -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.50
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
+ ![Parallel Execution Performance](docs/assets/parrelel_work_3x.png)
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