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
@@ -13,15 +13,12 @@ from tunacode.ui.panels import panel
13
13
  async def yes_no_prompt(question: str, default: bool = True) -> bool:
14
14
  """Simple yes/no prompt."""
15
15
  default_text = "[Y/n]" if default else "[y/N]"
16
- response = await prompt_input(
17
- session_key="yes_no",
18
- pretext=f"{question} {default_text}: "
19
- )
20
-
16
+ response = await prompt_input(session_key="yes_no", pretext=f"{question} {default_text}: ")
17
+
21
18
  if not response.strip():
22
19
  return default
23
-
24
- return response.lower().strip() in ['y', 'yes']
20
+
21
+ return response.lower().strip() in ["y", "yes"]
25
22
 
26
23
 
27
24
  class GitSafetySetup(BaseSetup):
@@ -29,7 +26,7 @@ class GitSafetySetup(BaseSetup):
29
26
 
30
27
  def __init__(self, state_manager: StateManager):
31
28
  super().__init__(state_manager)
32
-
29
+
33
30
  @property
34
31
  def name(self) -> str:
35
32
  """Return the name of this setup step."""
@@ -45,18 +42,15 @@ class GitSafetySetup(BaseSetup):
45
42
  try:
46
43
  # Check if git is installed
47
44
  result = subprocess.run(
48
- ["git", "--version"],
49
- capture_output=True,
50
- text=True,
51
- check=False
45
+ ["git", "--version"], capture_output=True, text=True, check=False
52
46
  )
53
-
47
+
54
48
  if result.returncode != 0:
55
49
  await panel(
56
50
  "⚠️ Git Not Found",
57
51
  "Git is not installed or not in PATH. TunaCode will modify files directly.\n"
58
52
  "It's strongly recommended to install Git for safety.",
59
- border_style="yellow"
53
+ border_style="yellow",
60
54
  )
61
55
  return
62
56
 
@@ -66,33 +60,30 @@ class GitSafetySetup(BaseSetup):
66
60
  capture_output=True,
67
61
  text=True,
68
62
  check=False,
69
- cwd=Path.cwd()
63
+ cwd=Path.cwd(),
70
64
  )
71
-
65
+
72
66
  if result.returncode != 0:
73
67
  await panel(
74
68
  "⚠️ Not a Git Repository",
75
69
  "This directory is not a Git repository. TunaCode will modify files directly.\n"
76
70
  "Consider initializing a Git repository for safety: git init",
77
- border_style="yellow"
71
+ border_style="yellow",
78
72
  )
79
73
  return
80
74
 
81
75
  # Get current branch name
82
76
  result = subprocess.run(
83
- ["git", "branch", "--show-current"],
84
- capture_output=True,
85
- text=True,
86
- check=True
77
+ ["git", "branch", "--show-current"], capture_output=True, text=True, check=True
87
78
  )
88
79
  current_branch = result.stdout.strip()
89
-
80
+
90
81
  if not current_branch:
91
82
  # Detached HEAD state
92
83
  await panel(
93
84
  "⚠️ Detached HEAD State",
94
85
  "You're in a detached HEAD state. TunaCode will continue without creating a branch.",
95
- border_style="yellow"
86
+ border_style="yellow",
96
87
  )
97
88
  return
98
89
 
@@ -103,57 +94,52 @@ class GitSafetySetup(BaseSetup):
103
94
 
104
95
  # Propose new branch name
105
96
  new_branch = f"{current_branch}-tunacode"
106
-
97
+
107
98
  # Check if there are uncommitted changes
108
99
  result = subprocess.run(
109
- ["git", "status", "--porcelain"],
110
- capture_output=True,
111
- text=True,
112
- check=True
100
+ ["git", "status", "--porcelain"], capture_output=True, text=True, check=True
113
101
  )
114
-
102
+
115
103
  has_changes = bool(result.stdout.strip())
116
-
104
+
117
105
  # Ask user if they want to create a safety branch
118
106
  message = (
119
107
  f"For safety, TunaCode can create a new branch '{new_branch}' based on '{current_branch}'.\n"
120
108
  f"This helps protect your work from unintended changes.\n"
121
109
  )
122
-
110
+
123
111
  if has_changes:
124
- message += "\n⚠️ You have uncommitted changes that will be brought to the new branch."
125
-
126
- create_branch = await yes_no_prompt(
127
- f"{message}\n\nCreate safety branch?",
128
- default=True
129
- )
130
-
112
+ message += (
113
+ "\n⚠️ You have uncommitted changes that will be brought to the new branch."
114
+ )
115
+
116
+ create_branch = await yes_no_prompt(f"{message}\n\nCreate safety branch?", default=True)
117
+
131
118
  if not create_branch:
132
119
  # User declined - show warning
133
120
  await panel(
134
121
  "⚠️ Working Without Safety Branch",
135
122
  "You've chosen to work directly on your current branch.\n"
136
123
  "TunaCode will modify files in place. Make sure you have backups!",
137
- border_style="red"
124
+ border_style="red",
138
125
  )
139
126
  # Save preference
140
127
  self.state_manager.session.user_config["skip_git_safety"] = True
141
128
  return
142
-
129
+
143
130
  # Create and checkout the new branch
144
131
  try:
145
132
  # Check if branch already exists
146
133
  result = subprocess.run(
147
134
  ["git", "show-ref", "--verify", f"refs/heads/{new_branch}"],
148
135
  capture_output=True,
149
- check=False
136
+ check=False,
150
137
  )
151
-
138
+
152
139
  if result.returncode == 0:
153
140
  # Branch exists, ask to use it
154
141
  use_existing = await yes_no_prompt(
155
- f"Branch '{new_branch}' already exists. Switch to it?",
156
- default=True
142
+ f"Branch '{new_branch}' already exists. Switch to it?", default=True
157
143
  )
158
144
  if use_existing:
159
145
  subprocess.run(["git", "checkout", new_branch], check=True)
@@ -164,24 +150,24 @@ class GitSafetySetup(BaseSetup):
164
150
  # Create new branch
165
151
  subprocess.run(["git", "checkout", "-b", new_branch], check=True)
166
152
  await ui.success(f"Created and switched to new branch: {new_branch}")
167
-
153
+
168
154
  except subprocess.CalledProcessError as e:
169
155
  await panel(
170
156
  "❌ Failed to Create Branch",
171
157
  f"Could not create branch '{new_branch}': {str(e)}\n"
172
158
  "Continuing on current branch.",
173
- border_style="red"
159
+ border_style="red",
174
160
  )
175
-
161
+
176
162
  except Exception as e:
177
163
  # Non-fatal error - just warn the user
178
164
  await panel(
179
165
  "⚠️ Git Safety Setup Failed",
180
166
  f"Could not set up Git safety: {str(e)}\n"
181
167
  "TunaCode will continue without branch protection.",
182
- border_style="yellow"
168
+ border_style="yellow",
183
169
  )
184
170
 
185
171
  async def validate(self) -> bool:
186
172
  """Validate git safety setup - always returns True as this is optional."""
187
- return True
173
+ return True
tunacode/core/state.py CHANGED
@@ -8,15 +8,8 @@ import uuid
8
8
  from dataclasses import dataclass, field
9
9
  from typing import Any, Optional
10
10
 
11
- from tunacode.types import (
12
- DeviceId,
13
- InputSessions,
14
- MessageHistory,
15
- ModelName,
16
- SessionId,
17
- ToolName,
18
- UserConfig,
19
- )
11
+ from tunacode.types import (DeviceId, InputSessions, MessageHistory, ModelName, SessionId, ToolName,
12
+ UserConfig)
20
13
 
21
14
 
22
15
  @dataclass
tunacode/exceptions.py CHANGED
@@ -77,8 +77,6 @@ class MCPError(ServiceError):
77
77
  super().__init__(f"MCP server '{server_name}' error: {message}")
78
78
 
79
79
 
80
-
81
-
82
80
  class GitOperationError(ServiceError):
83
81
  """Raised when Git operations fail."""
84
82
 
@@ -1 +1,10 @@
1
- """TunaCode tools package."""
1
+ """TunaCode tools package. Implements lazy loading of submodules for faster startup."""
2
+
3
+ import importlib
4
+
5
+
6
+ def __getattr__(name):
7
+ try:
8
+ return importlib.import_module(f".{name}", __name__)
9
+ except ImportError as e:
10
+ raise AttributeError(f"module {{__name__}} has no attribute '{{name}}'") from e
tunacode/tools/base.py CHANGED
@@ -137,7 +137,7 @@ class FileBasedTool(BaseTool):
137
137
  """Base class for tools that work with files.
138
138
 
139
139
  Provides common file-related functionality like:
140
- - Path validation
140
+ - Path validation
141
141
  - File existence checking
142
142
  - Directory creation
143
143
  - Encoding handling
tunacode/tools/bash.py CHANGED
@@ -94,9 +94,7 @@ class BashTool(BaseTool):
94
94
  )
95
95
 
96
96
  try:
97
- stdout, stderr = await asyncio.wait_for(
98
- process.communicate(), timeout=timeout
99
- )
97
+ stdout, stderr = await asyncio.wait_for(process.communicate(), timeout=timeout)
100
98
  except asyncio.TimeoutError:
101
99
  # Kill the process if it times out
102
100
  process.kill()
@@ -246,7 +244,9 @@ async def bash(
246
244
  """
247
245
  tool = BashTool()
248
246
  try:
249
- return await tool.execute(command, cwd=cwd, env=env, timeout=timeout, capture_output=capture_output)
247
+ return await tool.execute(
248
+ command, cwd=cwd, env=env, timeout=timeout, capture_output=capture_output
249
+ )
250
250
  except ToolExecutionError as e:
251
251
  # Return error message for pydantic-ai compatibility
252
- return str(e)
252
+ return str(e)