tunacode-cli 0.0.16__py3-none-any.whl → 0.0.18__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 (38) hide show
  1. tunacode/cli/commands.py +39 -41
  2. tunacode/cli/main.py +29 -26
  3. tunacode/cli/repl.py +35 -10
  4. tunacode/cli/textual_app.py +69 -66
  5. tunacode/cli/textual_bridge.py +33 -32
  6. tunacode/configuration/settings.py +2 -9
  7. tunacode/constants.py +2 -4
  8. tunacode/context.py +1 -1
  9. tunacode/core/agents/main.py +88 -62
  10. tunacode/core/setup/config_setup.py +79 -44
  11. tunacode/core/setup/coordinator.py +20 -13
  12. tunacode/core/setup/git_safety_setup.py +35 -49
  13. tunacode/core/state.py +2 -9
  14. tunacode/exceptions.py +0 -2
  15. tunacode/tools/__init__.py +10 -1
  16. tunacode/tools/base.py +1 -1
  17. tunacode/tools/bash.py +5 -5
  18. tunacode/tools/grep.py +210 -250
  19. tunacode/tools/read_file.py +2 -8
  20. tunacode/tools/run_command.py +4 -11
  21. tunacode/tools/update_file.py +2 -6
  22. tunacode/ui/completers.py +32 -31
  23. tunacode/ui/console.py +1 -0
  24. tunacode/ui/input.py +8 -5
  25. tunacode/ui/keybindings.py +1 -3
  26. tunacode/ui/lexers.py +16 -16
  27. tunacode/ui/output.py +7 -2
  28. tunacode/ui/panels.py +8 -8
  29. tunacode/ui/prompt_manager.py +19 -7
  30. tunacode/utils/import_cache.py +11 -0
  31. tunacode/utils/user_configuration.py +24 -2
  32. {tunacode_cli-0.0.16.dist-info → tunacode_cli-0.0.18.dist-info}/METADATA +56 -2
  33. tunacode_cli-0.0.18.dist-info/RECORD +68 -0
  34. tunacode_cli-0.0.16.dist-info/RECORD +0 -67
  35. {tunacode_cli-0.0.16.dist-info → tunacode_cli-0.0.18.dist-info}/WHEEL +0 -0
  36. {tunacode_cli-0.0.16.dist-info → tunacode_cli-0.0.18.dist-info}/entry_points.txt +0 -0
  37. {tunacode_cli-0.0.16.dist-info → tunacode_cli-0.0.18.dist-info}/licenses/LICENSE +0 -0
  38. {tunacode_cli-0.0.16.dist-info → tunacode_cli-0.0.18.dist-info}/top_level.txt +0 -0
@@ -7,14 +7,8 @@ Provides safe file reading with size limits and proper error handling.
7
7
 
8
8
  import os
9
9
 
10
- from tunacode.constants import (
11
- ERROR_FILE_DECODE,
12
- ERROR_FILE_DECODE_DETAILS,
13
- ERROR_FILE_NOT_FOUND,
14
- ERROR_FILE_TOO_LARGE,
15
- MAX_FILE_SIZE,
16
- MSG_FILE_SIZE_LIMIT,
17
- )
10
+ from tunacode.constants import (ERROR_FILE_DECODE, ERROR_FILE_DECODE_DETAILS, ERROR_FILE_NOT_FOUND,
11
+ ERROR_FILE_TOO_LARGE, MAX_FILE_SIZE, MSG_FILE_SIZE_LIMIT)
18
12
  from tunacode.exceptions import ToolExecutionError
19
13
  from tunacode.tools.base import FileBasedTool
20
14
  from tunacode.types import ToolResult
@@ -7,17 +7,10 @@ Provides controlled shell command execution with output capture and truncation.
7
7
 
8
8
  import subprocess
9
9
 
10
- from tunacode.constants import (
11
- CMD_OUTPUT_FORMAT,
12
- CMD_OUTPUT_NO_ERRORS,
13
- CMD_OUTPUT_NO_OUTPUT,
14
- CMD_OUTPUT_TRUNCATED,
15
- COMMAND_OUTPUT_END_SIZE,
16
- COMMAND_OUTPUT_START_INDEX,
17
- COMMAND_OUTPUT_THRESHOLD,
18
- ERROR_COMMAND_EXECUTION,
19
- MAX_COMMAND_OUTPUT,
20
- )
10
+ from tunacode.constants import (CMD_OUTPUT_FORMAT, CMD_OUTPUT_NO_ERRORS, CMD_OUTPUT_NO_OUTPUT,
11
+ CMD_OUTPUT_TRUNCATED, COMMAND_OUTPUT_END_SIZE,
12
+ COMMAND_OUTPUT_START_INDEX, COMMAND_OUTPUT_THRESHOLD,
13
+ ERROR_COMMAND_EXECUTION, MAX_COMMAND_OUTPUT)
21
14
  from tunacode.exceptions import ToolExecutionError
22
15
  from tunacode.tools.base import BaseTool
23
16
  from tunacode.types import ToolResult
@@ -21,9 +21,7 @@ class UpdateFileTool(FileBasedTool):
21
21
  def tool_name(self) -> str:
22
22
  return "Update"
23
23
 
24
- async def _execute(
25
- self, filepath: str, target: str, patch: str
26
- ) -> ToolResult:
24
+ async def _execute(self, filepath: str, target: str, patch: str) -> ToolResult:
27
25
  """Update an existing file by replacing a target text block with a patch.
28
26
 
29
27
  Args:
@@ -73,9 +71,7 @@ class UpdateFileTool(FileBasedTool):
73
71
 
74
72
  return f"File '{filepath}' updated successfully."
75
73
 
76
- def _format_args(
77
- self, filepath: str, target: str = None, patch: str = None
78
- ) -> str:
74
+ def _format_args(self, filepath: str, target: str = None, patch: str = None) -> str:
79
75
  """Format arguments, truncating target and patch for display."""
80
76
  args = [repr(filepath)]
81
77
 
tunacode/ui/completers.py CHANGED
@@ -11,53 +11,52 @@ from ..cli.commands import CommandRegistry
11
11
 
12
12
  class CommandCompleter(Completer):
13
13
  """Completer for slash commands."""
14
-
14
+
15
15
  def __init__(self, command_registry: Optional[CommandRegistry] = None):
16
16
  self.command_registry = command_registry
17
-
17
+
18
18
  def get_completions(
19
19
  self, document: Document, complete_event: CompleteEvent
20
20
  ) -> Iterable[Completion]:
21
21
  """Get completions for slash commands."""
22
22
  # Get the text before cursor
23
23
  text = document.text_before_cursor
24
-
24
+
25
25
  # Check if we're at the start of a line or after whitespace
26
- if text and not text.isspace() and text[-1] != '\n':
26
+ if text and not text.isspace() and text[-1] != "\n":
27
27
  # Only complete commands at the start of input or after a newline
28
- last_newline = text.rfind('\n')
29
- line_start = text[last_newline + 1:] if last_newline >= 0 else text
30
-
28
+ last_newline = text.rfind("\n")
29
+ line_start = text[last_newline + 1 :] if last_newline >= 0 else text
30
+
31
31
  # Skip if not at the beginning of a line
32
- if line_start and not line_start.startswith('/'):
32
+ if line_start and not line_start.startswith("/"):
33
33
  return
34
-
34
+
35
35
  # Get the word before cursor
36
36
  word_before_cursor = document.get_word_before_cursor(WORD=True)
37
-
37
+
38
38
  # Only complete if word starts with /
39
- if not word_before_cursor.startswith('/'):
39
+ if not word_before_cursor.startswith("/"):
40
40
  return
41
-
41
+
42
42
  # Get command names from registry
43
43
  if self.command_registry:
44
44
  command_names = self.command_registry.get_command_names()
45
45
  else:
46
46
  # Fallback list of commands
47
- command_names = ['/help', '/clear', '/dump', '/yolo',
48
- '/branch', '/compact', '/model']
49
-
47
+ command_names = ["/help", "/clear", "/dump", "/yolo", "/branch", "/compact", "/model"]
48
+
50
49
  # Get the partial command (without /)
51
50
  partial = word_before_cursor[1:].lower()
52
-
51
+
53
52
  # Yield completions for matching commands
54
53
  for cmd in command_names:
55
- if cmd.startswith('/') and cmd[1:].lower().startswith(partial):
54
+ if cmd.startswith("/") and cmd[1:].lower().startswith(partial):
56
55
  yield Completion(
57
56
  text=cmd,
58
57
  start_position=-len(word_before_cursor),
59
58
  display=cmd,
60
- display_meta='command'
59
+ display_meta="command",
61
60
  )
62
61
 
63
62
 
@@ -70,14 +69,14 @@ class FileReferenceCompleter(Completer):
70
69
  """Get completions for @file references."""
71
70
  # Get the word before cursor
72
71
  word_before_cursor = document.get_word_before_cursor(WORD=True)
73
-
72
+
74
73
  # Check if we're in an @file reference
75
74
  if not word_before_cursor.startswith("@"):
76
75
  return
77
-
76
+
78
77
  # Get the path part after @
79
78
  path_part = word_before_cursor[1:] # Remove @
80
-
79
+
81
80
  # Determine directory and prefix
82
81
  if "/" in path_part:
83
82
  # Path includes directory
@@ -87,18 +86,18 @@ class FileReferenceCompleter(Completer):
87
86
  # Just filename, search in current directory
88
87
  dir_path = "."
89
88
  prefix = path_part
90
-
89
+
91
90
  # Get matching files
92
91
  try:
93
92
  if os.path.exists(dir_path) and os.path.isdir(dir_path):
94
93
  for item in sorted(os.listdir(dir_path)):
95
94
  if item.startswith(prefix):
96
95
  full_path = os.path.join(dir_path, item) if dir_path != "." else item
97
-
96
+
98
97
  # Skip hidden files unless explicitly requested
99
98
  if item.startswith(".") and not prefix.startswith("."):
100
99
  continue
101
-
100
+
102
101
  # Add / for directories
103
102
  if os.path.isdir(full_path):
104
103
  display = item + "/"
@@ -106,15 +105,15 @@ class FileReferenceCompleter(Completer):
106
105
  else:
107
106
  display = item
108
107
  completion = full_path
109
-
108
+
110
109
  # Calculate how much to replace
111
110
  start_position = -len(path_part)
112
-
111
+
113
112
  yield Completion(
114
113
  text=completion,
115
114
  start_position=start_position,
116
115
  display=display,
117
- display_meta="dir" if os.path.isdir(full_path) else "file"
116
+ display_meta="dir" if os.path.isdir(full_path) else "file",
118
117
  )
119
118
  except (OSError, PermissionError):
120
119
  # Silently ignore inaccessible directories
@@ -123,7 +122,9 @@ class FileReferenceCompleter(Completer):
123
122
 
124
123
  def create_completer(command_registry: Optional[CommandRegistry] = None) -> Completer:
125
124
  """Create a merged completer for both commands and file references."""
126
- return merge_completers([
127
- CommandCompleter(command_registry),
128
- FileReferenceCompleter(),
129
- ])
125
+ return merge_completers(
126
+ [
127
+ CommandCompleter(command_registry),
128
+ FileReferenceCompleter(),
129
+ ]
130
+ )
tunacode/ui/console.py CHANGED
@@ -11,6 +11,7 @@ from .input import formatted_text, input, multiline_input
11
11
  from .keybindings import create_key_bindings
12
12
  from .output import (banner, clear, info, line, muted, print, spinner, success, sync_print,
13
13
  update_available, usage, version, warning)
14
+ # Patch banner to use sync fast version
14
15
  from .panels import (agent, dump_messages, error, help, models, panel, sync_panel,
15
16
  sync_tool_confirm, tool_confirm)
16
17
  from .prompt_manager import PromptConfig, PromptManager
tunacode/ui/input.py CHANGED
@@ -72,7 +72,9 @@ async def input(
72
72
  return await manager.get_input(session_key, pretext, config)
73
73
 
74
74
 
75
- async def multiline_input(state_manager: Optional[StateManager] = None, command_registry=None) -> str:
75
+ async def multiline_input(
76
+ state_manager: Optional[StateManager] = None, command_registry=None
77
+ ) -> str:
76
78
  """Get multiline input from the user with @file completion and highlighting."""
77
79
  kb = create_key_bindings()
78
80
  placeholder = formatted_text(
@@ -85,11 +87,12 @@ async def multiline_input(state_manager: Optional[StateManager] = None, command_
85
87
  )
86
88
  )
87
89
  return await input(
88
- "multiline",
89
- key_bindings=kb,
90
- multiline=True,
90
+ "multiline",
91
+ pretext="❯ ", # Default prompt
92
+ key_bindings=kb,
93
+ multiline=True,
91
94
  placeholder=placeholder,
92
95
  completer=create_completer(command_registry),
93
96
  lexer=FileReferenceLexer(),
94
- state_manager=state_manager
97
+ state_manager=state_manager,
95
98
  )
@@ -7,8 +7,6 @@ def create_key_bindings() -> KeyBindings:
7
7
  """Create and configure key bindings for the UI."""
8
8
  kb = KeyBindings()
9
9
 
10
-
11
-
12
10
  @kb.add("enter")
13
11
  def _submit(event):
14
12
  """Submit the current buffer."""
@@ -18,7 +16,7 @@ def create_key_bindings() -> KeyBindings:
18
16
  def _newline(event):
19
17
  """Insert a newline character."""
20
18
  event.current_buffer.insert_text("\n")
21
-
19
+
22
20
  @kb.add("escape", "enter")
23
21
  def _escape_enter(event):
24
22
  """Insert a newline when escape then enter is pressed."""
tunacode/ui/lexers.py CHANGED
@@ -8,39 +8,39 @@ from prompt_toolkit.lexers import Lexer
8
8
 
9
9
  class FileReferenceLexer(Lexer):
10
10
  """Lexer that highlights @file references in light blue."""
11
-
11
+
12
12
  # Pattern to match @file references
13
- FILE_REF_PATTERN = re.compile(r'@([\w./_-]+)')
14
-
13
+ FILE_REF_PATTERN = re.compile(r"@([\w./_-]+)")
14
+
15
15
  def lex_document(self, document):
16
16
  """Return a formatted text list for the given document."""
17
- lines = document.text.split('\n')
18
-
17
+ lines = document.text.split("\n")
18
+
19
19
  def get_line_tokens(line_number):
20
20
  """Get tokens for a specific line."""
21
21
  if line_number >= len(lines):
22
22
  return []
23
-
23
+
24
24
  line = lines[line_number]
25
25
  tokens = []
26
26
  last_end = 0
27
-
27
+
28
28
  # Find all @file references in the line
29
29
  for match in self.FILE_REF_PATTERN.finditer(line):
30
30
  start, end = match.span()
31
-
31
+
32
32
  # Add text before the match
33
33
  if start > last_end:
34
- tokens.append(('', line[last_end:start]))
35
-
34
+ tokens.append(("", line[last_end:start]))
35
+
36
36
  # Add the @file reference with styling
37
- tokens.append(('class:file-reference', match.group(0)))
37
+ tokens.append(("class:file-reference", match.group(0)))
38
38
  last_end = end
39
-
39
+
40
40
  # Add remaining text
41
41
  if last_end < len(line):
42
- tokens.append(('', line[last_end:]))
43
-
42
+ tokens.append(("", line[last_end:]))
43
+
44
44
  return tokens
45
-
46
- return get_line_tokens
45
+
46
+ return get_line_tokens
tunacode/ui/output.py CHANGED
@@ -72,9 +72,9 @@ async def version() -> None:
72
72
 
73
73
  async def banner() -> None:
74
74
  """Display the application banner."""
75
- console.clear()
75
+ await run_in_terminal(lambda: console.clear())
76
76
  banner_padding = Padding(BANNER, (2, 0, 1, 0))
77
- await print(banner_padding)
77
+ await run_in_terminal(lambda: console.print(banner_padding))
78
78
 
79
79
 
80
80
  async def clear() -> None:
@@ -89,6 +89,11 @@ async def update_available(latest_version: str) -> None:
89
89
  await muted(MSG_UPDATE_INSTRUCTION)
90
90
 
91
91
 
92
+ async def show_update_message(latest_version: str) -> None:
93
+ """Display update available message (alias for update_available)."""
94
+ await update_available(latest_version)
95
+
96
+
92
97
  async def spinner(show: bool = True, spinner_obj=None, state_manager: StateManager = None):
93
98
  """Manage a spinner display."""
94
99
  icon = SPINNER_TYPE
tunacode/ui/panels.py CHANGED
@@ -11,10 +11,10 @@ from rich.table import Table
11
11
 
12
12
  from tunacode.configuration.models import ModelRegistry
13
13
  from tunacode.constants import (APP_NAME, CMD_CLEAR, CMD_COMPACT, CMD_DUMP, CMD_EXIT, CMD_HELP,
14
- CMD_MODEL, CMD_YOLO, DESC_CLEAR, DESC_COMPACT, DESC_DUMP,
15
- DESC_EXIT, DESC_HELP, DESC_MODEL, DESC_MODEL_DEFAULT,
16
- DESC_MODEL_SWITCH, DESC_YOLO, PANEL_AVAILABLE_COMMANDS,
17
- PANEL_ERROR, PANEL_MESSAGE_HISTORY, PANEL_MODELS, UI_COLORS)
14
+ CMD_MODEL, CMD_YOLO, DESC_CLEAR, DESC_COMPACT, DESC_DUMP, DESC_EXIT,
15
+ DESC_HELP, DESC_MODEL, DESC_MODEL_DEFAULT, DESC_MODEL_SWITCH,
16
+ DESC_YOLO, PANEL_AVAILABLE_COMMANDS, PANEL_ERROR,
17
+ PANEL_MESSAGE_HISTORY, PANEL_MODELS, UI_COLORS)
18
18
  from tunacode.core.state import StateManager
19
19
  from tunacode.utils.file_utils import DotDict
20
20
 
@@ -39,12 +39,12 @@ async def panel(
39
39
  """Display a rich panel with modern styling."""
40
40
  border_style = border_style or kwargs.get("style") or colors.border
41
41
  panel_obj = Panel(
42
- Padding(text, (0, 1, 0, 1)),
43
- title=f"[bold]{title}[/bold]",
44
- title_align="left",
42
+ Padding(text, (0, 1, 0, 1)),
43
+ title=f"[bold]{title}[/bold]",
44
+ title_align="left",
45
45
  border_style=border_style,
46
46
  padding=(0, 1),
47
- box=ROUNDED # Use ROUNDED box style
47
+ box=ROUNDED, # Use ROUNDED box style
48
48
  )
49
49
  await print(Padding(panel_obj, (top, right, bottom, left)), **kwargs)
50
50
 
@@ -4,7 +4,7 @@ from dataclasses import dataclass
4
4
  from typing import Optional
5
5
 
6
6
  from prompt_toolkit.completion import Completer
7
- from prompt_toolkit.formatted_text import FormattedText
7
+ from prompt_toolkit.formatted_text import HTML, FormattedText
8
8
  from prompt_toolkit.key_binding import KeyBindings
9
9
  from prompt_toolkit.lexers import Lexer
10
10
  from prompt_toolkit.shortcuts import PromptSession
@@ -42,12 +42,14 @@ class PromptManager:
42
42
  self.state_manager = state_manager
43
43
  self._temp_sessions = {} # For when no state manager is available
44
44
  self._style = self._create_style()
45
-
45
+
46
46
  def _create_style(self) -> Style:
47
47
  """Create the style for the prompt with file reference highlighting."""
48
- return Style.from_dict({
49
- 'file-reference': UI_COLORS.get('file_ref', 'light_blue'),
50
- })
48
+ return Style.from_dict(
49
+ {
50
+ "file-reference": UI_COLORS.get("file_ref", "light_blue"),
51
+ }
52
+ )
51
53
 
52
54
  def get_session(self, session_key: str, config: PromptConfig) -> PromptSession:
53
55
  """Get or create a prompt session.
@@ -98,10 +100,20 @@ class PromptManager:
98
100
  """
99
101
  session = self.get_session(session_key, config)
100
102
 
103
+ # Create a custom prompt that changes based on input
104
+ def get_prompt():
105
+ # Check if current buffer starts with "!"
106
+ if hasattr(session.app, "current_buffer") and session.app.current_buffer:
107
+ text = session.app.current_buffer.text
108
+ if text.startswith("!"):
109
+ # Use bright yellow background with black text for high visibility
110
+ return HTML('<style bg="#ffcc00" fg="black"><b> ◆ BASH MODE ◆ </b></style> ')
111
+ return HTML(prompt) if isinstance(prompt, str) else prompt
112
+
101
113
  try:
102
- # Get user input
114
+ # Get user input with dynamic prompt
103
115
  response = await session.prompt_async(
104
- prompt,
116
+ get_prompt,
105
117
  is_password=config.is_password,
106
118
  validator=config.validator,
107
119
  multiline=config.multiline,
@@ -0,0 +1,11 @@
1
+ """Utility for import caching and lazy importing heavy modules."""
2
+
3
+ import importlib
4
+
5
+ _import_cache = {}
6
+
7
+
8
+ def lazy_import(module_name: str):
9
+ if module_name not in _import_cache:
10
+ _import_cache[module_name] = importlib.import_module(module_name)
11
+ return _import_cache[module_name]
@@ -18,12 +18,34 @@ if TYPE_CHECKING:
18
18
  from tunacode.core.state import StateManager
19
19
 
20
20
 
21
+ import hashlib
22
+
23
+ _config_fingerprint = None
24
+ _config_cache = None
25
+
26
+
27
+ def compute_config_fingerprint(config_obj) -> str:
28
+ """Returns a short hash/fingerprint for a config object/searchable for fastpath usage."""
29
+ b = json.dumps(config_obj, sort_keys=True).encode()
30
+ return hashlib.sha1(b).hexdigest()[:12]
31
+
32
+
21
33
  def load_config() -> Optional[UserConfig]:
22
- """Load user config from file"""
34
+ """Load user config from file, using fingerprint fast path if available."""
35
+ global _config_fingerprint, _config_cache
23
36
  app_settings = ApplicationSettings()
24
37
  try:
25
38
  with open(app_settings.paths.config_file, "r") as f:
26
- return json.load(f)
39
+ raw = f.read()
40
+ loaded = json.loads(raw)
41
+ new_fp = hashlib.sha1(raw.encode()).hexdigest()[:12]
42
+ # If hash matches, return in-memory cached config object
43
+ if new_fp == _config_fingerprint and _config_cache is not None:
44
+ return _config_cache
45
+ # else, update fast path
46
+ _config_fingerprint = new_fp
47
+ _config_cache = loaded
48
+ return loaded
27
49
  except FileNotFoundError:
28
50
  return None
29
51
  except JSONDecodeError:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tunacode-cli
3
- Version: 0.0.16
3
+ Version: 0.0.18
4
4
  Summary: Your agentic CLI developer.
5
5
  Author-email: larock22 <noreply@github.com>
6
6
  License-Expression: MIT
@@ -58,8 +58,10 @@ Dynamic: license-file
58
58
 
59
59
  ---
60
60
 
61
- ### Recent Updates (v0.0.14)
61
+ ### Recent Updates (v0.0.15)
62
62
 
63
+ - **🐚 Shell Command Support**: Execute shell commands directly with `!command` or open interactive shell with `!`
64
+ - **🔧 Enhanced Bash Tool**: Advanced bash execution with timeouts, working directory, and environment variables
63
65
  - **🔧 JSON Tool Parsing Fallback**: Automatic recovery when API providers fail with structured tool calling
64
66
  - **⚡ Enhanced Reliability**: Fixed parameter naming issues that caused tool schema errors
65
67
  - **🔄 Configuration Management**: New `/refresh` command to reload config without restart
@@ -84,6 +86,7 @@ Dynamic: license-file
84
86
  ### **Developer Tools**
85
87
 
86
88
  - 6 core tools: bash, grep, read_file, write_file, update_file, run_command
89
+ - Direct shell command execution with `!` prefix
87
90
  - MCP (Model Context Protocol) support
88
91
  - File operation confirmations with diffs
89
92
  - Per-project context guides (TUNACODE.md)
@@ -284,6 +287,8 @@ Learn more at [modelcontextprotocol.io](https://modelcontextprotocol.io/)
284
287
  | `/model <provider:name> default` | Set default model |
285
288
  | `/branch <name>` | Create and switch Git branch |
286
289
  | `/dump` | Show message history (debug) |
290
+ | `!<command>` | Run shell command |
291
+ | `!` | Open interactive shell |
287
292
  | `exit` | Exit application |
288
293
 
289
294
  ### Debug & Recovery Commands
@@ -298,6 +303,42 @@ Learn more at [modelcontextprotocol.io](https://modelcontextprotocol.io/)
298
303
 
299
304
  ---
300
305
 
306
+ ## Available Tools
307
+
308
+ ### Bash Tool
309
+ The enhanced bash tool provides advanced shell command execution with safety features:
310
+
311
+ - **Working Directory Support**: Execute commands in specific directories
312
+ - **Environment Variables**: Set custom environment variables for commands
313
+ - **Timeout Control**: Configurable timeouts (1-300 seconds) to prevent hanging
314
+ - **Output Capture**: Full stdout/stderr capture with truncation for large outputs
315
+ - **Safety Checks**: Warns about potentially destructive commands
316
+ - **Error Guidance**: Helpful error messages for common issues (command not found, permission denied, etc.)
317
+
318
+ **Example usage by the AI:**
319
+ ```python
320
+ # Simple command
321
+ await bash("ls -la")
322
+
323
+ # With working directory
324
+ await bash("npm install", cwd="/path/to/project")
325
+
326
+ # With timeout for long operations
327
+ await bash("npm run build", timeout=120)
328
+
329
+ # With environment variables
330
+ await bash("python script.py", env={"API_KEY": "secret"})
331
+ ```
332
+
333
+ ### Other Core Tools
334
+ - **grep**: Fast parallel content search across files
335
+ - **read_file**: Read file contents with line numbers
336
+ - **write_file**: Create new files (fails if file exists)
337
+ - **update_file**: Modify existing files with precise replacements
338
+ - **run_command**: Basic command execution (simpler than bash)
339
+
340
+ ---
341
+
301
342
  ## Reliability Features
302
343
 
303
344
  ### JSON Tool Parsing Fallback
@@ -475,3 +516,16 @@ MIT License - see [LICENSE](LICENSE) file for details.
475
516
  ## Acknowledgments
476
517
 
477
518
  TunaCode is a fork of [sidekick-cli](https://github.com/geekforbrains/sidekick-cli). Special thanks to the sidekick-cli team for creating the foundation that made TunaCode possible.
519
+
520
+ ### Key Differences from sidekick-cli
521
+
522
+ While TunaCode builds on the foundation of sidekick-cli, we've made several architectural changes for our workflow:
523
+
524
+ - **JSON Tool Parsing Fallback**: Added fallback parsing for when API providers fail with structured tool calling
525
+ - **Parallel Search Tools**: New `bash` and `grep` tools with parallel execution for codebase navigation
526
+ - **ReAct Reasoning**: Implemented ReAct (Reasoning + Acting) patterns with configurable iteration limits
527
+ - **Dynamic Configuration**: Added `/refresh` command and modified configuration management
528
+ - **Safety Changes**: Removed automatic git commits and `/undo` command - requires explicit git usage
529
+ - **Error Recovery**: Multiple fallback mechanisms and orphaned tool call recovery
530
+ - **Tool System Rewrite**: Complete overhaul of internal tools with atomic operations and different confirmation UIs
531
+ - **Debug Commands**: Added `/parsetools`, `/thoughts`, `/iterations` for debugging
@@ -0,0 +1,68 @@
1
+ tunacode/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ tunacode/constants.py,sha256=yUpXwyNffwbcdiQMNrX58wieycOu8UjDx1AzBVXYVRE,3799
3
+ tunacode/context.py,sha256=9FQ2vf9qY4bcRufKtR0g0eOzB8hZ4aesOfFGy_JOMzQ,2634
4
+ tunacode/exceptions.py,sha256=_Dyj6cC0868dMABekdQHXCg5XhucJumbGUMXaSDzgB4,2645
5
+ tunacode/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ tunacode/setup.py,sha256=dYn0NeAxtNIDSogWEmGSyjb9wsr8AonZ8vAo5sw9NIw,1909
7
+ tunacode/types.py,sha256=5mMJDgFqVcKzhtHh9unPISBFqkeNje6KISGUpRkqRjY,7146
8
+ tunacode/cli/__init__.py,sha256=zgs0UbAck8hfvhYsWhWOfBe5oK09ug2De1r4RuQZREA,55
9
+ tunacode/cli/commands.py,sha256=tTsEKXZqFx-xdTn3DqKsrz_gNC6_5Z4Bq_biXrfIBc0,29513
10
+ tunacode/cli/main.py,sha256=C3hBF9RGADz8wtdhKngwM-IjMeLA8MhttYnXiGb-L9g,1870
11
+ tunacode/cli/repl.py,sha256=RqI8AnFuKsdhvgI_zKaRBiIi50xEt1hoHoe6Bd3OSlI,11941
12
+ tunacode/cli/textual_app.py,sha256=1GNyYacMqCgIgYl1G2B5sIoksIAzKiGuPJ-wxHhzu5I,12951
13
+ tunacode/cli/textual_bridge.py,sha256=DcIbIbYBW4rDKMTWDqAKgWv7PtxZYBKGUcYo7uLSzuM,6175
14
+ tunacode/configuration/__init__.py,sha256=MbVXy8bGu0yKehzgdgZ_mfWlYGvIdb1dY2Ly75nfuPE,17
15
+ tunacode/configuration/defaults.py,sha256=7UdHP50NmnZWlx0f1PGV4JZIpmQem191vWisGky6AVA,688
16
+ tunacode/configuration/models.py,sha256=XPobkLM_TzKTuMIWhK-svJfGRGFT9r2LhKEM6rv6QHk,3756
17
+ tunacode/configuration/settings.py,sha256=lm2ov8rG1t4C5JIXMOhIKik5FAsjpuLVYtFmnE1ZQ3k,995
18
+ tunacode/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
+ tunacode/core/state.py,sha256=n1claG-gVVDMBCCS8cDmgas4XbKLJJwKRc-8CtXeTS8,1376
20
+ tunacode/core/tool_handler.py,sha256=OKx7jM8pml6pSEnoARu33_uBY8awJBqvhbVeBn6T4ow,1804
21
+ tunacode/core/agents/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
+ tunacode/core/agents/main.py,sha256=m9HYOQn-vo2WMdZcltuHHQevLnEEf8f-w5j2jDm8skw,14224
23
+ tunacode/core/setup/__init__.py,sha256=lzdpY6rIGf9DDlDBDGFvQZaSOQeFsNglHbkpq1-GtU8,376
24
+ tunacode/core/setup/agent_setup.py,sha256=trELO8cPnWo36BBnYmXDEnDPdhBg0p-VLnx9A8hSSSQ,1401
25
+ tunacode/core/setup/base.py,sha256=cbyT2-xK2mWgH4EO17VfM_OM2bj0kT895NW2jSXbe3c,968
26
+ tunacode/core/setup/config_setup.py,sha256=BZPQjJSVsIF3bIP2TteRf8NsQCnVBnx1JdxzriNjWB4,13266
27
+ tunacode/core/setup/coordinator.py,sha256=oVTN2xIeJERXitVJpkIk9tDGLs1D1bxIRmaogJwZJFI,2049
28
+ tunacode/core/setup/environment_setup.py,sha256=n3IrObKEynHZSwtUJ1FddMg2C4sHz7ca42awemImV8s,2225
29
+ tunacode/core/setup/git_safety_setup.py,sha256=T7hwIf3u3Tq3QtIdUAfuHI6vclMfm2Sqcml5l6x02oA,6799
30
+ tunacode/prompts/system.txt,sha256=wZkP3vTv4h8ukBUkCZ9yR-oZt-ZcdvIRPxl1uN3AHIk,4030
31
+ tunacode/services/__init__.py,sha256=w_E8QK6RnvKSvU866eDe8BCRV26rAm4d3R-Yg06OWCU,19
32
+ tunacode/services/mcp.py,sha256=R48X73KQjQ9vwhBrtbWHSBl-4K99QXmbIhh5J_1Gezo,3046
33
+ tunacode/tools/__init__.py,sha256=oDcxl7uoi1s9TPz2WNsjHH7xTKL6jM78WlrGbh-9SYQ,318
34
+ tunacode/tools/base.py,sha256=TF71ZE66-W-7GLY8QcPpPJ5CVjod6FHL1caBOTCssvU,7044
35
+ tunacode/tools/bash.py,sha256=mgZqugfDFevZ4BETuUv90pzXvtq7qKGUGFiuDxzmytk,8766
36
+ tunacode/tools/grep.py,sha256=USJayOfFP8l2WYFSqFgeMlpeWrJREJIWpv64fL_fQHI,26502
37
+ tunacode/tools/read_file.py,sha256=Cz1-7sdQwOdaqkVvkVpORiBdNtncCVlP9e9cu37ya80,2988
38
+ tunacode/tools/run_command.py,sha256=kYg_Re397OmZdKtUSjpNfYYNDBjd0vsS1xMK0yP181I,3776
39
+ tunacode/tools/update_file.py,sha256=bW1MhTzRjBDjJzqQ6A1yCVEbkr1oIqtEC8uqcg_rfY4,3957
40
+ tunacode/tools/write_file.py,sha256=prL6u8XOi9ZyPU-YNlG9YMLbSLrDJXDRuDX73ncXh-k,2699
41
+ tunacode/ui/__init__.py,sha256=aRNE2pS50nFAX6y--rSGMNYwhz905g14gRd6g4BolYU,13
42
+ tunacode/ui/completers.py,sha256=ORkMqX_L1ChdLqCDUlUr3drBnnxec3Jj7LyE54Mnod0,4837
43
+ tunacode/ui/console.py,sha256=LTEWQLdRP0JkhbEPIHIDrSX3ZZld9iJI2aZ3-eGliOc,1930
44
+ tunacode/ui/constants.py,sha256=A76B_KpM8jCuBYRg4cPmhi8_j6LLyWttO7_jjv47r3w,421
45
+ tunacode/ui/decorators.py,sha256=e2KM-_pI5EKHa2M045IjUe4rPkTboxaKHXJT0K3461g,1914
46
+ tunacode/ui/input.py,sha256=RXoqN8ZsGcsTJc04-3orDHP3BiOTKZ0dEx9tf-xwhUA,3008
47
+ tunacode/ui/keybindings.py,sha256=h0MlD73CW_3i2dQzb9EFSPkqy0raZ_isgjxUiA9u6ts,691
48
+ tunacode/ui/lexers.py,sha256=xb-KkgIqu2Xh1zqiuH4zFfPVHV8pPeKuuRBrhb3zjf4,1396
49
+ tunacode/ui/output.py,sha256=7fIo_WjRtkXrhm9VJd6OqaWEZ_XVqI0hxU8J1XaeLw4,4501
50
+ tunacode/ui/panels.py,sha256=_8B1rGOhPnSLekGM4ZzDJw-dCuaPeacsaCmmCggqxwE,5950
51
+ tunacode/ui/prompt_manager.py,sha256=U2cntB34vm-YwOj3gzFRUK362zccrz8pigQfpxr5sv8,4650
52
+ tunacode/ui/tool_ui.py,sha256=S5-k1HwRlSqiQ8shGQ_QYGXQbuzb6Pg7u3CTqZwffdQ,6533
53
+ tunacode/ui/validators.py,sha256=MMIMT1I2v0l2jIy-gxX_4GSApvUTi8XWIOACr_dmoBA,758
54
+ tunacode/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
55
+ tunacode/utils/bm25.py,sha256=yq7KFWP3g_zIsjUV7l2hFPXYCzXyNQUInLU7u4qsc_4,1909
56
+ tunacode/utils/diff_utils.py,sha256=V9QqQ0q4MfabVTnWptF3IXDp3estnfOKcJtDe_Sj14I,2372
57
+ tunacode/utils/file_utils.py,sha256=AXiAJ_idtlmXEi9pMvwtfPy9Ys3yK-F4K7qb_NpwonU,923
58
+ tunacode/utils/import_cache.py,sha256=q_xjJbtju05YbFopLDSkIo1hOtCx3DOTl3GQE5FFDgs,295
59
+ tunacode/utils/ripgrep.py,sha256=AXUs2FFt0A7KBV996deS8wreIlUzKOlAHJmwrcAr4No,583
60
+ tunacode/utils/system.py,sha256=FSoibTIH0eybs4oNzbYyufIiV6gb77QaeY2yGqW39AY,11381
61
+ tunacode/utils/text_utils.py,sha256=B9M1cuLTm_SSsr15WNHF6j7WdLWPvWzKZV0Lvfgdbjg,2458
62
+ tunacode/utils/user_configuration.py,sha256=IGvUH37wWtZ4M5xpukZEWYhtuKKyKcl6DaeObGXdleU,2610
63
+ tunacode_cli-0.0.18.dist-info/licenses/LICENSE,sha256=Btzdu2kIoMbdSp6OyCLupB1aRgpTCJ_szMimgEnpkkE,1056
64
+ tunacode_cli-0.0.18.dist-info/METADATA,sha256=gSvYPIonpAUDFNbHkUEEgD7QwwadVFgBEhNTuF2kE9o,17273
65
+ tunacode_cli-0.0.18.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
66
+ tunacode_cli-0.0.18.dist-info/entry_points.txt,sha256=hbkytikj4dGu6rizPuAd_DGUPBGF191RTnhr9wdhORY,51
67
+ tunacode_cli-0.0.18.dist-info/top_level.txt,sha256=lKy2P6BWNi5XSA4DHFvyjQ14V26lDZctwdmhEJrxQbU,9
68
+ tunacode_cli-0.0.18.dist-info/RECORD,,