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.

Files changed (90) hide show
  1. tunacode/cli/commands/__init__.py +0 -2
  2. tunacode/cli/commands/implementations/__init__.py +0 -3
  3. tunacode/cli/commands/implementations/debug.py +2 -2
  4. tunacode/cli/commands/implementations/development.py +10 -8
  5. tunacode/cli/commands/implementations/model.py +357 -29
  6. tunacode/cli/commands/implementations/system.py +3 -2
  7. tunacode/cli/commands/implementations/template.py +0 -2
  8. tunacode/cli/commands/registry.py +8 -7
  9. tunacode/cli/commands/slash/loader.py +2 -1
  10. tunacode/cli/commands/slash/validator.py +2 -1
  11. tunacode/cli/main.py +19 -1
  12. tunacode/cli/repl.py +90 -229
  13. tunacode/cli/repl_components/command_parser.py +2 -1
  14. tunacode/cli/repl_components/error_recovery.py +8 -5
  15. tunacode/cli/repl_components/output_display.py +1 -10
  16. tunacode/cli/repl_components/tool_executor.py +1 -13
  17. tunacode/configuration/defaults.py +2 -2
  18. tunacode/configuration/key_descriptions.py +284 -0
  19. tunacode/configuration/settings.py +0 -1
  20. tunacode/constants.py +6 -42
  21. tunacode/core/agents/__init__.py +43 -2
  22. tunacode/core/agents/agent_components/__init__.py +7 -0
  23. tunacode/core/agents/agent_components/agent_config.py +162 -158
  24. tunacode/core/agents/agent_components/agent_helpers.py +31 -2
  25. tunacode/core/agents/agent_components/node_processor.py +180 -146
  26. tunacode/core/agents/agent_components/response_state.py +123 -6
  27. tunacode/core/agents/agent_components/state_transition.py +116 -0
  28. tunacode/core/agents/agent_components/streaming.py +296 -0
  29. tunacode/core/agents/agent_components/task_completion.py +19 -6
  30. tunacode/core/agents/agent_components/tool_buffer.py +21 -1
  31. tunacode/core/agents/agent_components/tool_executor.py +10 -0
  32. tunacode/core/agents/main.py +522 -370
  33. tunacode/core/agents/main_legact.py +538 -0
  34. tunacode/core/agents/prompts.py +66 -0
  35. tunacode/core/agents/utils.py +29 -122
  36. tunacode/core/setup/__init__.py +0 -2
  37. tunacode/core/setup/config_setup.py +88 -227
  38. tunacode/core/setup/config_wizard.py +230 -0
  39. tunacode/core/setup/coordinator.py +2 -1
  40. tunacode/core/state.py +16 -64
  41. tunacode/core/token_usage/usage_tracker.py +3 -1
  42. tunacode/core/tool_authorization.py +352 -0
  43. tunacode/core/tool_handler.py +67 -60
  44. tunacode/prompts/system.xml +751 -0
  45. tunacode/services/mcp.py +97 -1
  46. tunacode/setup.py +0 -23
  47. tunacode/tools/base.py +54 -1
  48. tunacode/tools/bash.py +14 -0
  49. tunacode/tools/glob.py +4 -2
  50. tunacode/tools/grep.py +7 -17
  51. tunacode/tools/prompts/glob_prompt.xml +1 -1
  52. tunacode/tools/prompts/grep_prompt.xml +1 -0
  53. tunacode/tools/prompts/list_dir_prompt.xml +1 -1
  54. tunacode/tools/prompts/react_prompt.xml +23 -0
  55. tunacode/tools/prompts/read_file_prompt.xml +1 -1
  56. tunacode/tools/react.py +153 -0
  57. tunacode/tools/run_command.py +15 -0
  58. tunacode/types.py +14 -79
  59. tunacode/ui/completers.py +434 -50
  60. tunacode/ui/config_dashboard.py +585 -0
  61. tunacode/ui/console.py +63 -11
  62. tunacode/ui/input.py +8 -3
  63. tunacode/ui/keybindings.py +0 -18
  64. tunacode/ui/model_selector.py +395 -0
  65. tunacode/ui/output.py +40 -19
  66. tunacode/ui/panels.py +173 -49
  67. tunacode/ui/path_heuristics.py +91 -0
  68. tunacode/ui/prompt_manager.py +1 -20
  69. tunacode/ui/tool_ui.py +30 -8
  70. tunacode/utils/api_key_validation.py +93 -0
  71. tunacode/utils/config_comparator.py +340 -0
  72. tunacode/utils/models_registry.py +593 -0
  73. tunacode/utils/text_utils.py +18 -1
  74. {tunacode_cli-0.0.70.dist-info → tunacode_cli-0.0.78.6.dist-info}/METADATA +80 -12
  75. {tunacode_cli-0.0.70.dist-info → tunacode_cli-0.0.78.6.dist-info}/RECORD +78 -74
  76. tunacode/cli/commands/implementations/plan.py +0 -50
  77. tunacode/cli/commands/implementations/todo.py +0 -217
  78. tunacode/context.py +0 -71
  79. tunacode/core/setup/git_safety_setup.py +0 -186
  80. tunacode/prompts/system.md +0 -359
  81. tunacode/prompts/system.md.bak +0 -487
  82. tunacode/tools/exit_plan_mode.py +0 -273
  83. tunacode/tools/present_plan.py +0 -288
  84. tunacode/tools/prompts/exit_plan_mode_prompt.xml +0 -25
  85. tunacode/tools/prompts/present_plan_prompt.xml +0 -20
  86. tunacode/tools/prompts/todo_prompt.xml +0 -96
  87. tunacode/tools/todo.py +0 -456
  88. {tunacode_cli-0.0.70.dist-info → tunacode_cli-0.0.78.6.dist-info}/WHEEL +0 -0
  89. {tunacode_cli-0.0.70.dist-info → tunacode_cli-0.0.78.6.dist-info}/entry_points.txt +0 -0
  90. {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)