tunacode-cli 0.0.70__py3-none-any.whl → 0.0.78.6__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/__init__.py +0 -2
- tunacode/cli/commands/implementations/__init__.py +0 -3
- tunacode/cli/commands/implementations/debug.py +2 -2
- tunacode/cli/commands/implementations/development.py +10 -8
- tunacode/cli/commands/implementations/model.py +357 -29
- tunacode/cli/commands/implementations/system.py +3 -2
- tunacode/cli/commands/implementations/template.py +0 -2
- tunacode/cli/commands/registry.py +8 -7
- tunacode/cli/commands/slash/loader.py +2 -1
- tunacode/cli/commands/slash/validator.py +2 -1
- tunacode/cli/main.py +19 -1
- tunacode/cli/repl.py +90 -229
- tunacode/cli/repl_components/command_parser.py +2 -1
- tunacode/cli/repl_components/error_recovery.py +8 -5
- tunacode/cli/repl_components/output_display.py +1 -10
- tunacode/cli/repl_components/tool_executor.py +1 -13
- tunacode/configuration/defaults.py +2 -2
- tunacode/configuration/key_descriptions.py +284 -0
- tunacode/configuration/settings.py +0 -1
- tunacode/constants.py +6 -42
- tunacode/core/agents/__init__.py +43 -2
- tunacode/core/agents/agent_components/__init__.py +7 -0
- tunacode/core/agents/agent_components/agent_config.py +162 -158
- tunacode/core/agents/agent_components/agent_helpers.py +31 -2
- tunacode/core/agents/agent_components/node_processor.py +180 -146
- tunacode/core/agents/agent_components/response_state.py +123 -6
- tunacode/core/agents/agent_components/state_transition.py +116 -0
- tunacode/core/agents/agent_components/streaming.py +296 -0
- tunacode/core/agents/agent_components/task_completion.py +19 -6
- tunacode/core/agents/agent_components/tool_buffer.py +21 -1
- tunacode/core/agents/agent_components/tool_executor.py +10 -0
- tunacode/core/agents/main.py +522 -370
- tunacode/core/agents/main_legact.py +538 -0
- tunacode/core/agents/prompts.py +66 -0
- tunacode/core/agents/utils.py +29 -122
- tunacode/core/setup/__init__.py +0 -2
- tunacode/core/setup/config_setup.py +88 -227
- tunacode/core/setup/config_wizard.py +230 -0
- tunacode/core/setup/coordinator.py +2 -1
- tunacode/core/state.py +16 -64
- tunacode/core/token_usage/usage_tracker.py +3 -1
- tunacode/core/tool_authorization.py +352 -0
- tunacode/core/tool_handler.py +67 -60
- tunacode/prompts/system.xml +751 -0
- tunacode/services/mcp.py +97 -1
- tunacode/setup.py +0 -23
- tunacode/tools/base.py +54 -1
- tunacode/tools/bash.py +14 -0
- tunacode/tools/glob.py +4 -2
- tunacode/tools/grep.py +7 -17
- tunacode/tools/prompts/glob_prompt.xml +1 -1
- tunacode/tools/prompts/grep_prompt.xml +1 -0
- tunacode/tools/prompts/list_dir_prompt.xml +1 -1
- tunacode/tools/prompts/react_prompt.xml +23 -0
- tunacode/tools/prompts/read_file_prompt.xml +1 -1
- tunacode/tools/react.py +153 -0
- tunacode/tools/run_command.py +15 -0
- tunacode/types.py +14 -79
- tunacode/ui/completers.py +434 -50
- tunacode/ui/config_dashboard.py +585 -0
- tunacode/ui/console.py +63 -11
- tunacode/ui/input.py +8 -3
- tunacode/ui/keybindings.py +0 -18
- tunacode/ui/model_selector.py +395 -0
- tunacode/ui/output.py +40 -19
- tunacode/ui/panels.py +173 -49
- tunacode/ui/path_heuristics.py +91 -0
- tunacode/ui/prompt_manager.py +1 -20
- tunacode/ui/tool_ui.py +30 -8
- tunacode/utils/api_key_validation.py +93 -0
- tunacode/utils/config_comparator.py +340 -0
- tunacode/utils/models_registry.py +593 -0
- tunacode/utils/text_utils.py +18 -1
- {tunacode_cli-0.0.70.dist-info → tunacode_cli-0.0.78.6.dist-info}/METADATA +80 -12
- {tunacode_cli-0.0.70.dist-info → tunacode_cli-0.0.78.6.dist-info}/RECORD +78 -74
- tunacode/cli/commands/implementations/plan.py +0 -50
- tunacode/cli/commands/implementations/todo.py +0 -217
- tunacode/context.py +0 -71
- tunacode/core/setup/git_safety_setup.py +0 -186
- tunacode/prompts/system.md +0 -359
- tunacode/prompts/system.md.bak +0 -487
- tunacode/tools/exit_plan_mode.py +0 -273
- tunacode/tools/present_plan.py +0 -288
- tunacode/tools/prompts/exit_plan_mode_prompt.xml +0 -25
- tunacode/tools/prompts/present_plan_prompt.xml +0 -20
- tunacode/tools/prompts/todo_prompt.xml +0 -96
- tunacode/tools/todo.py +0 -456
- {tunacode_cli-0.0.70.dist-info → tunacode_cli-0.0.78.6.dist-info}/WHEEL +0 -0
- {tunacode_cli-0.0.70.dist-info → tunacode_cli-0.0.78.6.dist-info}/entry_points.txt +0 -0
- {tunacode_cli-0.0.70.dist-info → tunacode_cli-0.0.78.6.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Module: tunacode.utils.config_comparator
|
|
3
|
+
|
|
4
|
+
Configuration comparison utility for analyzing user configurations against defaults.
|
|
5
|
+
Provides detailed analysis of customizations, defaults, and configuration state.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Any, Dict, List, Optional, Set, Union
|
|
12
|
+
|
|
13
|
+
from tunacode.configuration.defaults import DEFAULT_USER_CONFIG
|
|
14
|
+
from tunacode.types import UserConfig
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class ConfigDifference:
|
|
19
|
+
"""Represents a difference between user config and defaults."""
|
|
20
|
+
|
|
21
|
+
key_path: str
|
|
22
|
+
user_value: Any
|
|
23
|
+
default_value: Any
|
|
24
|
+
difference_type: str # "custom", "missing", "extra", "type_mismatch"
|
|
25
|
+
section: str
|
|
26
|
+
description: str
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class ConfigAnalysis:
|
|
31
|
+
"""Complete analysis of configuration state."""
|
|
32
|
+
|
|
33
|
+
user_config: UserConfig
|
|
34
|
+
default_config: UserConfig
|
|
35
|
+
differences: List[ConfigDifference]
|
|
36
|
+
custom_keys: Set[str]
|
|
37
|
+
missing_keys: Set[str]
|
|
38
|
+
extra_keys: Set[str]
|
|
39
|
+
type_mismatches: Set[str]
|
|
40
|
+
sections_analyzed: Set[str]
|
|
41
|
+
total_keys: int
|
|
42
|
+
custom_percentage: float
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class ConfigComparator:
|
|
46
|
+
"""Compares user configuration against defaults to identify customizations."""
|
|
47
|
+
|
|
48
|
+
def __init__(self, default_config: Optional[UserConfig] = None):
|
|
49
|
+
"""Initialize comparator with default configuration."""
|
|
50
|
+
self.default_config = default_config or DEFAULT_USER_CONFIG
|
|
51
|
+
|
|
52
|
+
def _get_key_description(self, key_path: str, difference_type: str) -> str:
|
|
53
|
+
"""Get a descriptive explanation for a configuration key difference."""
|
|
54
|
+
try:
|
|
55
|
+
from tunacode.configuration.key_descriptions import get_key_description
|
|
56
|
+
|
|
57
|
+
desc = get_key_description(key_path)
|
|
58
|
+
|
|
59
|
+
if desc:
|
|
60
|
+
if difference_type == "custom":
|
|
61
|
+
return f"Custom: {desc.description}"
|
|
62
|
+
elif difference_type == "missing":
|
|
63
|
+
return f"Missing: {desc.description}"
|
|
64
|
+
elif difference_type == "extra":
|
|
65
|
+
return f"Extra: {desc.description}"
|
|
66
|
+
elif difference_type == "type_mismatch":
|
|
67
|
+
return f"Type mismatch: {desc.description}"
|
|
68
|
+
|
|
69
|
+
except ImportError:
|
|
70
|
+
pass # Fall back to basic descriptions
|
|
71
|
+
|
|
72
|
+
# Fallback descriptions
|
|
73
|
+
if difference_type == "custom":
|
|
74
|
+
return f"Custom value: {key_path}"
|
|
75
|
+
elif difference_type == "missing":
|
|
76
|
+
return f"Missing configuration key: {key_path}"
|
|
77
|
+
elif difference_type == "extra":
|
|
78
|
+
return f"Extra configuration key: {key_path}"
|
|
79
|
+
elif difference_type == "type_mismatch":
|
|
80
|
+
return f"Type mismatch for: {key_path}"
|
|
81
|
+
|
|
82
|
+
return f"Configuration difference: {key_path}"
|
|
83
|
+
|
|
84
|
+
def analyze_config(self, user_config: UserConfig) -> ConfigAnalysis:
|
|
85
|
+
"""Perform complete analysis of user configuration."""
|
|
86
|
+
differences: list[ConfigDifference] = []
|
|
87
|
+
custom_keys: set[str] = set()
|
|
88
|
+
missing_keys: set[str] = set()
|
|
89
|
+
extra_keys: set[str] = set()
|
|
90
|
+
type_mismatches: set[str] = set()
|
|
91
|
+
|
|
92
|
+
# Analyze each section recursively
|
|
93
|
+
self._analyze_recursive(
|
|
94
|
+
user_config=user_config,
|
|
95
|
+
default_config=self.default_config,
|
|
96
|
+
current_path="",
|
|
97
|
+
differences=differences,
|
|
98
|
+
custom_keys=custom_keys,
|
|
99
|
+
missing_keys=missing_keys,
|
|
100
|
+
extra_keys=extra_keys,
|
|
101
|
+
type_mismatches=type_mismatches,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
# Calculate statistics
|
|
105
|
+
sections_analyzed: set[str] = set()
|
|
106
|
+
self._collect_sections(user_config, sections_analyzed)
|
|
107
|
+
self._collect_sections(self.default_config, sections_analyzed)
|
|
108
|
+
|
|
109
|
+
total_keys = len(custom_keys) + len(missing_keys) + len(extra_keys) + len(type_mismatches)
|
|
110
|
+
custom_percentage = (len(custom_keys) / total_keys * 100) if total_keys > 0 else 0
|
|
111
|
+
|
|
112
|
+
return ConfigAnalysis(
|
|
113
|
+
user_config=user_config,
|
|
114
|
+
default_config=self.default_config,
|
|
115
|
+
differences=differences,
|
|
116
|
+
custom_keys=custom_keys,
|
|
117
|
+
missing_keys=missing_keys,
|
|
118
|
+
extra_keys=extra_keys,
|
|
119
|
+
type_mismatches=type_mismatches,
|
|
120
|
+
sections_analyzed=sections_analyzed,
|
|
121
|
+
total_keys=total_keys,
|
|
122
|
+
custom_percentage=custom_percentage,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
def _analyze_recursive(
|
|
126
|
+
self,
|
|
127
|
+
user_config: Dict[str, Any],
|
|
128
|
+
default_config: Dict[str, Any],
|
|
129
|
+
current_path: str,
|
|
130
|
+
differences: List[ConfigDifference],
|
|
131
|
+
custom_keys: Set[str],
|
|
132
|
+
missing_keys: Set[str],
|
|
133
|
+
extra_keys: Set[str],
|
|
134
|
+
type_mismatches: Set[str],
|
|
135
|
+
) -> None:
|
|
136
|
+
"""Recursively analyze configuration differences."""
|
|
137
|
+
|
|
138
|
+
# Check for missing keys (present in default but not in user)
|
|
139
|
+
for key, default_value in default_config.items():
|
|
140
|
+
full_key = f"{current_path}.{key}" if current_path else key
|
|
141
|
+
|
|
142
|
+
if key not in user_config:
|
|
143
|
+
missing_keys.add(full_key)
|
|
144
|
+
differences.append(
|
|
145
|
+
ConfigDifference(
|
|
146
|
+
key_path=full_key,
|
|
147
|
+
user_value=None,
|
|
148
|
+
default_value=default_value,
|
|
149
|
+
difference_type="missing",
|
|
150
|
+
section=current_path or "root",
|
|
151
|
+
description=self._get_key_description(full_key, "missing"),
|
|
152
|
+
)
|
|
153
|
+
)
|
|
154
|
+
continue
|
|
155
|
+
|
|
156
|
+
user_value = user_config[key]
|
|
157
|
+
|
|
158
|
+
# Recursively analyze nested dictionaries
|
|
159
|
+
if isinstance(default_value, dict) and isinstance(user_value, dict):
|
|
160
|
+
self._analyze_recursive(
|
|
161
|
+
user_config=user_value,
|
|
162
|
+
default_config=default_value,
|
|
163
|
+
current_path=full_key,
|
|
164
|
+
differences=differences,
|
|
165
|
+
custom_keys=custom_keys,
|
|
166
|
+
missing_keys=missing_keys,
|
|
167
|
+
extra_keys=extra_keys,
|
|
168
|
+
type_mismatches=type_mismatches,
|
|
169
|
+
)
|
|
170
|
+
continue
|
|
171
|
+
|
|
172
|
+
# Check for type mismatches
|
|
173
|
+
if not isinstance(user_value, type(default_value)):
|
|
174
|
+
type_mismatches.add(full_key)
|
|
175
|
+
differences.append(
|
|
176
|
+
ConfigDifference(
|
|
177
|
+
key_path=full_key,
|
|
178
|
+
user_value=user_value,
|
|
179
|
+
default_value=default_value,
|
|
180
|
+
difference_type="type_mismatch",
|
|
181
|
+
section=current_path or "root",
|
|
182
|
+
description=self._get_key_description(full_key, "type_mismatch"),
|
|
183
|
+
)
|
|
184
|
+
)
|
|
185
|
+
continue
|
|
186
|
+
|
|
187
|
+
# Check for custom values
|
|
188
|
+
if user_value != default_value:
|
|
189
|
+
custom_keys.add(full_key)
|
|
190
|
+
differences.append(
|
|
191
|
+
ConfigDifference(
|
|
192
|
+
key_path=full_key,
|
|
193
|
+
user_value=user_value,
|
|
194
|
+
default_value=default_value,
|
|
195
|
+
difference_type="custom",
|
|
196
|
+
section=current_path or "root",
|
|
197
|
+
description=self._get_key_description(full_key, "custom"),
|
|
198
|
+
)
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
# Check for extra keys (present in user but not in default)
|
|
202
|
+
for key, user_value in user_config.items():
|
|
203
|
+
if key not in default_config:
|
|
204
|
+
full_key = f"{current_path}.{key}" if current_path else key
|
|
205
|
+
extra_keys.add(full_key)
|
|
206
|
+
differences.append(
|
|
207
|
+
ConfigDifference(
|
|
208
|
+
key_path=full_key,
|
|
209
|
+
user_value=user_value,
|
|
210
|
+
default_value=None,
|
|
211
|
+
difference_type="extra",
|
|
212
|
+
section=current_path or "root",
|
|
213
|
+
description=self._get_key_description(full_key, "extra"),
|
|
214
|
+
)
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
def _collect_sections(self, config: Dict[str, Any], sections: Set[str]) -> None:
|
|
218
|
+
"""Collect all section names from configuration."""
|
|
219
|
+
for key, value in config.items():
|
|
220
|
+
if isinstance(value, dict):
|
|
221
|
+
sections.add(key)
|
|
222
|
+
self._collect_sections(value, sections)
|
|
223
|
+
|
|
224
|
+
def get_summary_stats(self, analysis: ConfigAnalysis) -> Dict[str, Any]:
|
|
225
|
+
"""Get summary statistics for the configuration analysis."""
|
|
226
|
+
return {
|
|
227
|
+
"total_keys_analyzed": analysis.total_keys,
|
|
228
|
+
"custom_keys_count": len(analysis.custom_keys),
|
|
229
|
+
"missing_keys_count": len(analysis.missing_keys),
|
|
230
|
+
"extra_keys_count": len(analysis.extra_keys),
|
|
231
|
+
"type_mismatches_count": len(analysis.type_mismatches),
|
|
232
|
+
"custom_percentage": analysis.custom_percentage,
|
|
233
|
+
"sections_analyzed": len(analysis.sections_analyzed),
|
|
234
|
+
"has_issues": bool(analysis.missing_keys or analysis.type_mismatches),
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
def get_section_analysis(
|
|
238
|
+
self, analysis: ConfigAnalysis, section: str
|
|
239
|
+
) -> List[ConfigDifference]:
|
|
240
|
+
"""Get differences for a specific section."""
|
|
241
|
+
return [diff for diff in analysis.differences if diff.section == section]
|
|
242
|
+
|
|
243
|
+
def is_config_healthy(self, analysis: ConfigAnalysis) -> bool:
|
|
244
|
+
"""Check if configuration is healthy (no critical issues)."""
|
|
245
|
+
# Type mismatches are considered critical
|
|
246
|
+
if analysis.type_mismatches:
|
|
247
|
+
return False
|
|
248
|
+
|
|
249
|
+
# Missing keys might be acceptable depending on the context
|
|
250
|
+
# For now, we'll consider missing keys as warnings, not errors
|
|
251
|
+
return True
|
|
252
|
+
|
|
253
|
+
def get_recommendations(self, analysis: ConfigAnalysis) -> List[str]:
|
|
254
|
+
"""Get recommendations based on configuration analysis."""
|
|
255
|
+
recommendations = []
|
|
256
|
+
|
|
257
|
+
if analysis.type_mismatches:
|
|
258
|
+
recommendations.append(
|
|
259
|
+
f"Fix {len(analysis.type_mismatches)} type mismatch(es) in configuration"
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
if analysis.missing_keys:
|
|
263
|
+
recommendations.append(
|
|
264
|
+
f"Consider adding {len(analysis.missing_keys)} missing configuration key(s)"
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
if analysis.custom_percentage > 80:
|
|
268
|
+
recommendations.append(
|
|
269
|
+
"High customization detected - consider documenting your configuration"
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
if analysis.extra_keys:
|
|
273
|
+
recommendations.append(
|
|
274
|
+
f"Found {len(analysis.extra_keys)} unrecognized configuration key(s)"
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
return recommendations
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def load_and_analyze_config(config_path: Optional[Union[str, Path]] = None) -> ConfigAnalysis:
|
|
281
|
+
"""Load configuration from file and analyze it."""
|
|
282
|
+
from tunacode.utils.user_configuration import load_config
|
|
283
|
+
|
|
284
|
+
if config_path:
|
|
285
|
+
try:
|
|
286
|
+
with open(config_path, "r") as f:
|
|
287
|
+
user_config = json.load(f)
|
|
288
|
+
except (FileNotFoundError, json.JSONDecodeError) as e:
|
|
289
|
+
raise ValueError(f"Failed to load config from {config_path}: {e}")
|
|
290
|
+
else:
|
|
291
|
+
user_config = load_config()
|
|
292
|
+
if user_config is None:
|
|
293
|
+
raise ValueError("No user configuration found")
|
|
294
|
+
|
|
295
|
+
comparator = ConfigComparator()
|
|
296
|
+
return comparator.analyze_config(user_config)
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def create_config_report(analysis: ConfigAnalysis) -> str:
|
|
300
|
+
"""Create a human-readable report of configuration analysis."""
|
|
301
|
+
stats = ConfigComparator().get_summary_stats(analysis)
|
|
302
|
+
|
|
303
|
+
report = [
|
|
304
|
+
"Configuration Analysis Report",
|
|
305
|
+
"=" * 50,
|
|
306
|
+
f"Total keys analyzed: {stats['total_keys_analyzed']}",
|
|
307
|
+
f"Custom keys: {stats['custom_keys_count']} ({stats['custom_percentage']:.1f}%)",
|
|
308
|
+
f"Missing keys: {stats['missing_keys_count']}",
|
|
309
|
+
f"Extra keys: {stats['extra_keys_count']}",
|
|
310
|
+
f"Type mismatches: {stats['type_mismatches_count']}",
|
|
311
|
+
f"Sections analyzed: {stats['sections_analyzed']}",
|
|
312
|
+
f"Configuration healthy: {'Yes' if stats['has_issues'] else 'No'}",
|
|
313
|
+
"",
|
|
314
|
+
]
|
|
315
|
+
|
|
316
|
+
if analysis.custom_keys:
|
|
317
|
+
report.append("Custom Values:")
|
|
318
|
+
for key in sorted(analysis.custom_keys):
|
|
319
|
+
report.append(f" ✓ {key}")
|
|
320
|
+
report.append("")
|
|
321
|
+
|
|
322
|
+
if analysis.missing_keys:
|
|
323
|
+
report.append("Missing Keys:")
|
|
324
|
+
for key in sorted(analysis.missing_keys):
|
|
325
|
+
report.append(f" ⚠ {key}")
|
|
326
|
+
report.append("")
|
|
327
|
+
|
|
328
|
+
if analysis.type_mismatches:
|
|
329
|
+
report.append("Type Mismatches:")
|
|
330
|
+
for key in sorted(analysis.type_mismatches):
|
|
331
|
+
report.append(f" ✗ {key}")
|
|
332
|
+
report.append("")
|
|
333
|
+
|
|
334
|
+
recommendations = ConfigComparator().get_recommendations(analysis)
|
|
335
|
+
if recommendations:
|
|
336
|
+
report.append("Recommendations:")
|
|
337
|
+
for rec in recommendations:
|
|
338
|
+
report.append(f" • {rec}")
|
|
339
|
+
|
|
340
|
+
return "\n".join(report)
|