tunacode-cli 0.0.17__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.
- tunacode/cli/commands.py +39 -41
- tunacode/cli/main.py +29 -26
- tunacode/cli/repl.py +35 -10
- tunacode/cli/textual_app.py +69 -66
- tunacode/cli/textual_bridge.py +33 -32
- tunacode/configuration/settings.py +2 -9
- tunacode/constants.py +2 -4
- tunacode/context.py +1 -1
- tunacode/core/agents/main.py +88 -62
- tunacode/core/setup/config_setup.py +79 -44
- tunacode/core/setup/coordinator.py +20 -13
- tunacode/core/setup/git_safety_setup.py +35 -49
- tunacode/core/state.py +2 -9
- tunacode/exceptions.py +0 -2
- tunacode/tools/__init__.py +10 -1
- tunacode/tools/base.py +1 -1
- tunacode/tools/bash.py +5 -5
- tunacode/tools/grep.py +210 -250
- tunacode/tools/read_file.py +2 -8
- tunacode/tools/run_command.py +4 -11
- tunacode/tools/update_file.py +2 -6
- tunacode/ui/completers.py +32 -31
- tunacode/ui/console.py +3 -3
- tunacode/ui/input.py +8 -5
- tunacode/ui/keybindings.py +1 -3
- tunacode/ui/lexers.py +16 -16
- tunacode/ui/output.py +2 -2
- tunacode/ui/panels.py +8 -8
- tunacode/ui/prompt_manager.py +19 -7
- tunacode/utils/import_cache.py +11 -0
- tunacode/utils/user_configuration.py +24 -2
- {tunacode_cli-0.0.17.dist-info → tunacode_cli-0.0.18.dist-info}/METADATA +43 -2
- tunacode_cli-0.0.18.dist-info/RECORD +68 -0
- tunacode_cli-0.0.17.dist-info/RECORD +0 -67
- {tunacode_cli-0.0.17.dist-info → tunacode_cli-0.0.18.dist-info}/WHEEL +0 -0
- {tunacode_cli-0.0.17.dist-info → tunacode_cli-0.0.18.dist-info}/entry_points.txt +0 -0
- {tunacode_cli-0.0.17.dist-info → tunacode_cli-0.0.18.dist-info}/licenses/LICENSE +0 -0
- {tunacode_cli-0.0.17.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
|
-
|
|
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 [
|
|
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 +=
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
-
|
|
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
tunacode/tools/__init__.py
CHANGED
|
@@ -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(
|
|
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)
|