tunacode-cli 0.0.55__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 (114) hide show
  1. tunacode/cli/commands/__init__.py +2 -2
  2. tunacode/cli/commands/implementations/__init__.py +2 -3
  3. tunacode/cli/commands/implementations/command_reload.py +48 -0
  4. tunacode/cli/commands/implementations/debug.py +2 -2
  5. tunacode/cli/commands/implementations/development.py +10 -8
  6. tunacode/cli/commands/implementations/model.py +357 -29
  7. tunacode/cli/commands/implementations/quickstart.py +43 -0
  8. tunacode/cli/commands/implementations/system.py +96 -3
  9. tunacode/cli/commands/implementations/template.py +0 -2
  10. tunacode/cli/commands/registry.py +139 -5
  11. tunacode/cli/commands/slash/__init__.py +32 -0
  12. tunacode/cli/commands/slash/command.py +157 -0
  13. tunacode/cli/commands/slash/loader.py +135 -0
  14. tunacode/cli/commands/slash/processor.py +294 -0
  15. tunacode/cli/commands/slash/types.py +93 -0
  16. tunacode/cli/commands/slash/validator.py +400 -0
  17. tunacode/cli/main.py +23 -2
  18. tunacode/cli/repl.py +217 -190
  19. tunacode/cli/repl_components/command_parser.py +38 -4
  20. tunacode/cli/repl_components/error_recovery.py +85 -4
  21. tunacode/cli/repl_components/output_display.py +12 -1
  22. tunacode/cli/repl_components/tool_executor.py +1 -1
  23. tunacode/configuration/defaults.py +12 -3
  24. tunacode/configuration/key_descriptions.py +284 -0
  25. tunacode/configuration/settings.py +0 -1
  26. tunacode/constants.py +12 -40
  27. tunacode/core/agents/__init__.py +43 -2
  28. tunacode/core/agents/agent_components/__init__.py +7 -0
  29. tunacode/core/agents/agent_components/agent_config.py +249 -55
  30. tunacode/core/agents/agent_components/agent_helpers.py +43 -13
  31. tunacode/core/agents/agent_components/node_processor.py +179 -139
  32. tunacode/core/agents/agent_components/response_state.py +123 -6
  33. tunacode/core/agents/agent_components/state_transition.py +116 -0
  34. tunacode/core/agents/agent_components/streaming.py +296 -0
  35. tunacode/core/agents/agent_components/task_completion.py +19 -6
  36. tunacode/core/agents/agent_components/tool_buffer.py +21 -1
  37. tunacode/core/agents/agent_components/tool_executor.py +10 -0
  38. tunacode/core/agents/main.py +522 -370
  39. tunacode/core/agents/main_legact.py +538 -0
  40. tunacode/core/agents/prompts.py +66 -0
  41. tunacode/core/agents/utils.py +29 -121
  42. tunacode/core/code_index.py +83 -29
  43. tunacode/core/setup/__init__.py +0 -2
  44. tunacode/core/setup/config_setup.py +110 -20
  45. tunacode/core/setup/config_wizard.py +230 -0
  46. tunacode/core/setup/coordinator.py +14 -5
  47. tunacode/core/state.py +16 -20
  48. tunacode/core/token_usage/usage_tracker.py +5 -3
  49. tunacode/core/tool_authorization.py +352 -0
  50. tunacode/core/tool_handler.py +67 -40
  51. tunacode/exceptions.py +119 -5
  52. tunacode/prompts/system.xml +751 -0
  53. tunacode/services/mcp.py +125 -7
  54. tunacode/setup.py +5 -25
  55. tunacode/tools/base.py +163 -0
  56. tunacode/tools/bash.py +110 -1
  57. tunacode/tools/glob.py +332 -34
  58. tunacode/tools/grep.py +179 -82
  59. tunacode/tools/grep_components/result_formatter.py +98 -4
  60. tunacode/tools/list_dir.py +132 -2
  61. tunacode/tools/prompts/bash_prompt.xml +72 -0
  62. tunacode/tools/prompts/glob_prompt.xml +45 -0
  63. tunacode/tools/prompts/grep_prompt.xml +98 -0
  64. tunacode/tools/prompts/list_dir_prompt.xml +31 -0
  65. tunacode/tools/prompts/react_prompt.xml +23 -0
  66. tunacode/tools/prompts/read_file_prompt.xml +54 -0
  67. tunacode/tools/prompts/run_command_prompt.xml +64 -0
  68. tunacode/tools/prompts/update_file_prompt.xml +53 -0
  69. tunacode/tools/prompts/write_file_prompt.xml +37 -0
  70. tunacode/tools/react.py +153 -0
  71. tunacode/tools/read_file.py +91 -0
  72. tunacode/tools/run_command.py +114 -0
  73. tunacode/tools/schema_assembler.py +167 -0
  74. tunacode/tools/update_file.py +94 -0
  75. tunacode/tools/write_file.py +86 -0
  76. tunacode/tools/xml_helper.py +83 -0
  77. tunacode/tutorial/__init__.py +9 -0
  78. tunacode/tutorial/content.py +98 -0
  79. tunacode/tutorial/manager.py +182 -0
  80. tunacode/tutorial/steps.py +124 -0
  81. tunacode/types.py +20 -27
  82. tunacode/ui/completers.py +434 -50
  83. tunacode/ui/config_dashboard.py +585 -0
  84. tunacode/ui/console.py +63 -11
  85. tunacode/ui/input.py +20 -3
  86. tunacode/ui/keybindings.py +7 -4
  87. tunacode/ui/model_selector.py +395 -0
  88. tunacode/ui/output.py +40 -19
  89. tunacode/ui/panels.py +212 -43
  90. tunacode/ui/path_heuristics.py +91 -0
  91. tunacode/ui/prompt_manager.py +5 -1
  92. tunacode/ui/tool_ui.py +33 -10
  93. tunacode/utils/api_key_validation.py +93 -0
  94. tunacode/utils/config_comparator.py +340 -0
  95. tunacode/utils/json_utils.py +206 -0
  96. tunacode/utils/message_utils.py +14 -4
  97. tunacode/utils/models_registry.py +593 -0
  98. tunacode/utils/ripgrep.py +332 -9
  99. tunacode/utils/text_utils.py +18 -1
  100. tunacode/utils/user_configuration.py +45 -0
  101. tunacode_cli-0.0.78.6.dist-info/METADATA +260 -0
  102. tunacode_cli-0.0.78.6.dist-info/RECORD +158 -0
  103. {tunacode_cli-0.0.55.dist-info → tunacode_cli-0.0.78.6.dist-info}/WHEEL +1 -2
  104. tunacode/cli/commands/implementations/todo.py +0 -217
  105. tunacode/context.py +0 -71
  106. tunacode/core/setup/git_safety_setup.py +0 -182
  107. tunacode/prompts/system.md +0 -731
  108. tunacode/tools/read_file_async_poc.py +0 -196
  109. tunacode/tools/todo.py +0 -349
  110. tunacode_cli-0.0.55.dist-info/METADATA +0 -322
  111. tunacode_cli-0.0.55.dist-info/RECORD +0 -126
  112. tunacode_cli-0.0.55.dist-info/top_level.txt +0 -1
  113. {tunacode_cli-0.0.55.dist-info → tunacode_cli-0.0.78.6.dist-info}/entry_points.txt +0 -0
  114. {tunacode_cli-0.0.55.dist-info → tunacode_cli-0.0.78.6.dist-info}/licenses/LICENSE +0 -0
tunacode/utils/ripgrep.py CHANGED
@@ -1,17 +1,340 @@
1
+ """Ripgrep binary management and execution utilities."""
2
+
3
+ import functools
4
+ import logging
5
+ import os
6
+ import platform
7
+ import shutil
1
8
  import subprocess
2
- from typing import List
9
+ from pathlib import Path
10
+ from typing import List, Optional, Tuple
3
11
 
12
+ logger = logging.getLogger(__name__)
4
13
 
5
- def ripgrep(pattern: str, directory: str = ".") -> List[str]:
6
- """Return a list of file paths matching a pattern using ripgrep."""
14
+
15
+ @functools.lru_cache(maxsize=1)
16
+ def get_platform_identifier() -> Tuple[str, str]:
17
+ """Get the current platform identifier.
18
+
19
+ Returns:
20
+ Tuple of (platform_key, system_name)
21
+ """
22
+ system = platform.system().lower()
23
+ machine = platform.machine().lower()
24
+
25
+ if system == "linux":
26
+ if machine in ["x86_64", "amd64"]:
27
+ return "x64-linux", system
28
+ elif machine in ["aarch64", "arm64"]:
29
+ return "arm64-linux", system
30
+ elif system == "darwin":
31
+ if machine in ["x86_64", "amd64"]:
32
+ return "x64-darwin", system
33
+ elif machine in ["arm64", "aarch64"]:
34
+ return "arm64-darwin", system
35
+ elif system == "windows":
36
+ if machine in ["x86_64", "amd64"]:
37
+ return "x64-win32", system
38
+
39
+ raise ValueError(f"Unsupported platform: {system} {machine}")
40
+
41
+
42
+ @functools.lru_cache(maxsize=1)
43
+ def get_ripgrep_binary_path() -> Optional[Path]:
44
+ """Resolve the path to the ripgrep binary.
45
+
46
+ Resolution order:
47
+ 1. Environment variable override (TUNACODE_RIPGREP_PATH)
48
+ 2. System ripgrep (if newer or equal version)
49
+ 3. Bundled ripgrep binary
50
+ 4. None (fallback to Python-based search)
51
+
52
+ Returns:
53
+ Path to ripgrep binary or None if not available
54
+ """
55
+ # Check for environment variable override
56
+ env_path = os.environ.get("TUNACODE_RIPGREP_PATH")
57
+ if env_path:
58
+ path = Path(env_path)
59
+ if path.exists() and path.is_file():
60
+ logger.debug(f"Using ripgrep from environment variable: {path}")
61
+ return path
62
+ else:
63
+ logger.warning(f"Invalid TUNACODE_RIPGREP_PATH: {env_path}")
64
+
65
+ # Check for system ripgrep
66
+ system_rg = shutil.which("rg")
67
+ if system_rg:
68
+ system_rg_path = Path(system_rg)
69
+ if _check_ripgrep_version(system_rg_path):
70
+ logger.debug(f"Using system ripgrep: {system_rg_path}")
71
+ return system_rg_path
72
+
73
+ # Check for bundled ripgrep
74
+ try:
75
+ platform_key, _ = get_platform_identifier()
76
+ binary_name = "rg.exe" if platform_key == "x64-win32" else "rg"
77
+
78
+ # Look for vendor directory relative to this file
79
+ vendor_dir = (
80
+ Path(__file__).parent.parent.parent.parent / "vendor" / "ripgrep" / platform_key
81
+ )
82
+ bundled_path = vendor_dir / binary_name
83
+
84
+ if bundled_path.exists():
85
+ logger.debug(f"Using bundled ripgrep: {bundled_path}")
86
+ return bundled_path
87
+ except Exception as e:
88
+ logger.debug(f"Could not find bundled ripgrep: {e}")
89
+
90
+ logger.debug("No ripgrep binary found, will use Python fallback")
91
+ return None
92
+
93
+
94
+ def _check_ripgrep_version(rg_path: Path, min_version: str = "13.0.0") -> bool:
95
+ """Check if ripgrep version meets minimum requirement.
96
+
97
+ Args:
98
+ rg_path: Path to ripgrep binary
99
+ min_version: Minimum required version
100
+
101
+ Returns:
102
+ True if version is sufficient, False otherwise
103
+ """
7
104
  try:
8
105
  result = subprocess.run(
9
- ["rg", "--files", "-g", pattern, directory],
106
+ [str(rg_path), "--version"],
10
107
  capture_output=True,
11
108
  text=True,
12
- check=True,
13
- timeout=5,
109
+ timeout=1,
14
110
  )
15
- return [line.strip() for line in result.stdout.splitlines() if line.strip()]
16
- except (subprocess.CalledProcessError, FileNotFoundError, subprocess.TimeoutExpired):
17
- return []
111
+ if result.returncode == 0:
112
+ # Parse version from output like "ripgrep 14.1.1"
113
+ version_line = result.stdout.split("\n")[0]
114
+ version = version_line.split()[-1]
115
+
116
+ # Simple version comparison (works for x.y.z format)
117
+ current = tuple(map(int, version.split(".")))
118
+ required = tuple(map(int, min_version.split(".")))
119
+
120
+ return current >= required
121
+ except Exception as e:
122
+ logger.debug(f"Could not check ripgrep version: {e}")
123
+
124
+ return False
125
+
126
+
127
+ class RipgrepExecutor:
128
+ """Wrapper for executing ripgrep commands with error handling."""
129
+
130
+ def __init__(self, binary_path: Optional[Path] = None):
131
+ """Initialize the executor.
132
+
133
+ Args:
134
+ binary_path: Optional path to ripgrep binary
135
+ """
136
+ self.binary_path = binary_path or get_ripgrep_binary_path()
137
+ self._use_python_fallback = self.binary_path is None
138
+
139
+ if self._use_python_fallback:
140
+ logger.info("Ripgrep binary not available, using Python fallback")
141
+
142
+ def search(
143
+ self,
144
+ pattern: str,
145
+ path: str = ".",
146
+ *,
147
+ timeout: int = 10,
148
+ max_matches: Optional[int] = None,
149
+ file_pattern: Optional[str] = None,
150
+ case_insensitive: bool = False,
151
+ multiline: bool = False,
152
+ context_before: int = 0,
153
+ context_after: int = 0,
154
+ **kwargs,
155
+ ) -> List[str]:
156
+ """Execute a ripgrep search.
157
+
158
+ Args:
159
+ pattern: Search pattern (regex)
160
+ path: Directory or file to search
161
+ timeout: Maximum execution time in seconds
162
+ max_matches: Maximum number of matches to return
163
+ file_pattern: Glob pattern for files to include
164
+ case_insensitive: Case-insensitive search
165
+ multiline: Enable multiline mode
166
+ context_before: Lines of context before match
167
+ context_after: Lines of context after match
168
+ **kwargs: Additional ripgrep arguments
169
+
170
+ Returns:
171
+ List of matching lines or file paths
172
+ """
173
+ if self._use_python_fallback:
174
+ return self._python_fallback_search(
175
+ pattern, path, file_pattern=file_pattern, case_insensitive=case_insensitive
176
+ )
177
+
178
+ try:
179
+ cmd = [str(self.binary_path)]
180
+
181
+ # Add flags
182
+ if case_insensitive:
183
+ cmd.append("-i")
184
+ if multiline:
185
+ cmd.extend(["-U", "--multiline-dotall"])
186
+ if context_before > 0:
187
+ cmd.extend(["-B", str(context_before)])
188
+ if context_after > 0:
189
+ cmd.extend(["-A", str(context_after)])
190
+ if max_matches:
191
+ cmd.extend(["-m", str(max_matches)])
192
+ if file_pattern:
193
+ cmd.extend(["-g", file_pattern])
194
+
195
+ # Add pattern and path
196
+ cmd.extend([pattern, path])
197
+
198
+ logger.debug(f"Executing ripgrep: {' '.join(cmd)}")
199
+
200
+ result = subprocess.run(
201
+ cmd,
202
+ capture_output=True,
203
+ text=True,
204
+ timeout=timeout,
205
+ )
206
+
207
+ if result.returncode in [0, 1]: # 0 = matches found, 1 = no matches
208
+ return [line.strip() for line in result.stdout.splitlines() if line.strip()]
209
+ else:
210
+ logger.warning(f"Ripgrep error: {result.stderr}")
211
+ return []
212
+
213
+ except subprocess.TimeoutExpired:
214
+ logger.warning(f"Ripgrep search timed out after {timeout} seconds")
215
+ return []
216
+ except Exception as e:
217
+ logger.error(f"Ripgrep execution failed: {e}")
218
+ return self._python_fallback_search(pattern, path, file_pattern=file_pattern)
219
+
220
+ def list_files(self, pattern: str, directory: str = ".") -> List[str]:
221
+ """List files matching a glob pattern using ripgrep.
222
+
223
+ Args:
224
+ pattern: Glob pattern for files
225
+ directory: Directory to search
226
+
227
+ Returns:
228
+ List of file paths
229
+ """
230
+ if self._use_python_fallback:
231
+ return self._python_fallback_list_files(pattern, directory)
232
+
233
+ try:
234
+ result = subprocess.run(
235
+ [str(self.binary_path), "--files", "-g", pattern, directory],
236
+ capture_output=True,
237
+ text=True,
238
+ timeout=5,
239
+ )
240
+ return [line.strip() for line in result.stdout.splitlines() if line.strip()]
241
+ except Exception:
242
+ return self._python_fallback_list_files(pattern, directory)
243
+
244
+ def _python_fallback_search(
245
+ self,
246
+ pattern: str,
247
+ path: str,
248
+ file_pattern: Optional[str] = None,
249
+ case_insensitive: bool = False,
250
+ ) -> List[str]:
251
+ """Python-based fallback search implementation."""
252
+ import re
253
+ from pathlib import Path
254
+
255
+ results = []
256
+ path_obj = Path(path)
257
+
258
+ # Compile regex pattern
259
+ flags = re.IGNORECASE if case_insensitive else 0
260
+ try:
261
+ regex = re.compile(pattern, flags)
262
+ except re.error:
263
+ logger.error(f"Invalid regex pattern: {pattern}")
264
+ return []
265
+
266
+ # Search files
267
+ if path_obj.is_file():
268
+ files = [path_obj]
269
+ else:
270
+ glob_pattern = file_pattern or "**/*"
271
+ files = list(path_obj.glob(glob_pattern))
272
+
273
+ for file_path in files:
274
+ if not file_path.is_file():
275
+ continue
276
+
277
+ try:
278
+ with file_path.open("r", encoding="utf-8", errors="ignore") as f:
279
+ for line_num, line in enumerate(f, 1):
280
+ if regex.search(line):
281
+ results.append(f"{file_path}:{line_num}:{line.strip()}")
282
+ except Exception: # nosec B112 - continue on file read errors is appropriate
283
+ continue
284
+
285
+ return results
286
+
287
+ def _python_fallback_list_files(self, pattern: str, directory: str) -> List[str]:
288
+ """Python-based fallback for listing files."""
289
+ from pathlib import Path
290
+
291
+ try:
292
+ base_path = Path(directory)
293
+ return [str(p) for p in base_path.glob(pattern) if p.is_file()]
294
+ except Exception:
295
+ return []
296
+
297
+
298
+ # Maintain backward compatibility
299
+ def ripgrep(pattern: str, directory: str = ".") -> List[str]:
300
+ """Return a list of file paths matching a pattern using ripgrep.
301
+
302
+ This function maintains backward compatibility with the original implementation.
303
+ """
304
+ executor = RipgrepExecutor()
305
+ return executor.list_files(pattern, directory)
306
+
307
+
308
+ # Performance metrics collection
309
+ class RipgrepMetrics:
310
+ """Collect performance metrics for ripgrep operations."""
311
+
312
+ def __init__(self):
313
+ self.search_count = 0
314
+ self.total_search_time = 0.0
315
+ self.fallback_count = 0
316
+
317
+ def record_search(self, duration: float, used_fallback: bool = False):
318
+ """Record a search operation."""
319
+ self.search_count += 1
320
+ self.total_search_time += duration
321
+ if used_fallback:
322
+ self.fallback_count += 1
323
+
324
+ @property
325
+ def average_search_time(self) -> float:
326
+ """Get average search time."""
327
+ if self.search_count == 0:
328
+ return 0.0
329
+ return self.total_search_time / self.search_count
330
+
331
+ @property
332
+ def fallback_rate(self) -> float:
333
+ """Get fallback usage rate."""
334
+ if self.search_count == 0:
335
+ return 0.0
336
+ return self.fallback_count / self.search_count
337
+
338
+
339
+ # Global metrics instance
340
+ metrics = RipgrepMetrics()
@@ -66,10 +66,12 @@ def expand_file_refs(text: str) -> Tuple[str, List[str]]:
66
66
  - List of absolute paths of files that were successfully expanded.
67
67
 
68
68
  Raises:
69
- ValueError: If a referenced path does not exist.
69
+ ValueError: If a referenced path does not exist or if attempting to expand
70
+ TunaCode's own source directory.
70
71
  """
71
72
  import os
72
73
  import re
74
+ from pathlib import Path
73
75
 
74
76
  from tunacode.constants import (
75
77
  ERROR_DIR_TOO_LARGE,
@@ -86,6 +88,21 @@ def expand_file_refs(text: str) -> Tuple[str, List[str]]:
86
88
  def replacer(match: re.Match) -> str:
87
89
  path_spec = match.group(1)
88
90
 
91
+ # Detect if we're expanding TunaCode's own src/ from TunaCode's repo
92
+ # This prevents accidentally expanding TunaCode's source instead of user's project
93
+ if path_spec.startswith("src/") or path_spec.startswith("src/**"):
94
+ cwd = Path.cwd()
95
+ tunacode_marker = cwd / "src" / "tunacode" / "__init__.py"
96
+ if tunacode_marker.exists():
97
+ raise ValueError(
98
+ "Error: TunaCode cannot expand file references when running "
99
+ "from its own source directory.\n"
100
+ "Please run TunaCode from your project directory, not from "
101
+ "the TunaCode repository itself.\n"
102
+ f"Current directory: {cwd}\n"
103
+ "Expected: Your project's root directory"
104
+ )
105
+
89
106
  is_recursive = path_spec.endswith("/**")
90
107
  is_dir = path_spec.endswith("/")
91
108
 
@@ -45,6 +45,10 @@ def load_config() -> Optional[UserConfig]:
45
45
  # else, update fast path
46
46
  _config_fingerprint = new_fp
47
47
  _config_cache = loaded
48
+
49
+ # Initialize onboarding defaults for new configurations
50
+ _ensure_onboarding_defaults(loaded)
51
+
48
52
  return loaded
49
53
  except FileNotFoundError:
50
54
  return None
@@ -91,3 +95,44 @@ def set_default_model(model_name: ModelName, state_manager: "StateManager") -> b
91
95
  except ConfigurationError:
92
96
  # Re-raise ConfigurationError to be handled by caller
93
97
  raise
98
+
99
+
100
+ def _ensure_onboarding_defaults(config: UserConfig) -> None:
101
+ """Ensure onboarding-related default settings are present in config."""
102
+ from datetime import datetime
103
+
104
+ if "settings" not in config:
105
+ config["settings"] = {}
106
+
107
+ settings = config["settings"]
108
+
109
+ # Set tutorial enabled by default for new users
110
+ if "enable_tutorial" not in settings:
111
+ settings["enable_tutorial"] = True
112
+
113
+ # Set first installation date if not present (for new installs)
114
+ if "first_installation_date" not in settings:
115
+ settings["first_installation_date"] = datetime.now().isoformat()
116
+
117
+
118
+ def initialize_first_time_user(state_manager: "StateManager") -> None:
119
+ """Initialize first-time user settings and save configuration."""
120
+ from datetime import datetime
121
+
122
+ # Ensure settings section exists
123
+ if "settings" not in state_manager.session.user_config:
124
+ state_manager.session.user_config["settings"] = {}
125
+
126
+ settings = state_manager.session.user_config["settings"]
127
+
128
+ # Only set installation date if it doesn't exist (true first-time)
129
+ if "first_installation_date" not in settings:
130
+ settings["first_installation_date"] = datetime.now().isoformat()
131
+ settings["enable_tutorial"] = True
132
+
133
+ # Save the updated configuration
134
+ try:
135
+ save_config(state_manager)
136
+ except ConfigurationError:
137
+ # Non-critical error, continue without failing
138
+ pass
@@ -0,0 +1,260 @@
1
+ Metadata-Version: 2.4
2
+ Name: tunacode-cli
3
+ Version: 0.0.78.6
4
+ Summary: Your agentic CLI developer.
5
+ Project-URL: Homepage, https://tunacode.xyz/
6
+ Project-URL: Repository, https://github.com/alchemiststudiosDOTai/tunacode
7
+ Project-URL: Issues, https://github.com/alchemiststudiosDOTai/tunacode/issues
8
+ Project-URL: Documentation, https://github.com/alchemiststudiosDOTai/tunacode#readme
9
+ Author-email: larock22 <noreply@github.com>
10
+ License: MIT
11
+ License-File: LICENSE
12
+ Keywords: agent,automation,cli,development
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Software Development
21
+ Classifier: Topic :: Utilities
22
+ Requires-Python: <3.14,>=3.10
23
+ Requires-Dist: click<9.0.0,>=8.3.1
24
+ Requires-Dist: defusedxml
25
+ Requires-Dist: prompt-toolkit<4.0.0,>=3.0.52
26
+ Requires-Dist: pydantic-ai<2.0.0,>=1.18.0
27
+ Requires-Dist: pydantic<3.0.0,>=2.12.4
28
+ Requires-Dist: pygments<3.0.0,>=2.19.2
29
+ Requires-Dist: rich<15.0.0,>=14.2.0
30
+ Requires-Dist: textual
31
+ Requires-Dist: tiktoken<1.0.0,>=0.12.0
32
+ Requires-Dist: typer<0.10.0,>=0.9.0
33
+ Provides-Extra: dev
34
+ Requires-Dist: autoflake>=2.0.0; extra == 'dev'
35
+ Requires-Dist: bandit; extra == 'dev'
36
+ Requires-Dist: build; extra == 'dev'
37
+ Requires-Dist: dead>=1.5.0; extra == 'dev'
38
+ Requires-Dist: hatch>=1.6.0; extra == 'dev'
39
+ Requires-Dist: mypy; extra == 'dev'
40
+ Requires-Dist: pre-commit; extra == 'dev'
41
+ Requires-Dist: pytest; extra == 'dev'
42
+ Requires-Dist: pytest-asyncio; extra == 'dev'
43
+ Requires-Dist: pytest-cov; extra == 'dev'
44
+ Requires-Dist: ruff; extra == 'dev'
45
+ Requires-Dist: textual-dev; extra == 'dev'
46
+ Requires-Dist: twine; extra == 'dev'
47
+ Requires-Dist: unimport>=1.0.0; extra == 'dev'
48
+ Requires-Dist: vulture>=2.7; extra == 'dev'
49
+ Description-Content-Type: text/markdown
50
+
51
+ # TunaCode CLI
52
+
53
+ <div align="center">
54
+
55
+ [![PyPI version](https://badge.fury.io/py/tunacode-cli.svg)](https://badge.fury.io/py/tunacode-cli)
56
+ [![Downloads](https://pepy.tech/badge/tunacode-cli)](https://pepy.tech/project/tunacode-cli)
57
+ [![Python 3.10+](https://img.shields.io/badge/python-3.10+-blue.svg)](https://www.python.org/downloads/)
58
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
59
+
60
+ **AI-powered CLI coding assistant**
61
+
62
+ ![TunaCode Example](assets/tunacode_example.png)
63
+
64
+ </div>
65
+
66
+ ---
67
+
68
+ ## Quick Install
69
+
70
+ ```bash
71
+ # Option 1: One-line install (Linux/macOS)
72
+ wget -qO- https://raw.githubusercontent.com/alchemiststudiosDOTai/tunacode/master/scripts/install_linux.sh | bash
73
+
74
+ # Option 2: UV install (recommended)
75
+ uv tool install tunacode-cli
76
+
77
+ # Option 3: pip install
78
+ pip install tunacode-cli
79
+ ```
80
+
81
+ For detailed installation and configuration instructions, see the [**Getting Started Guide**](documentation/user/getting-started.md).
82
+
83
+ ## Quickstart
84
+
85
+ ```bash
86
+ # 1) Install (choose one)
87
+ uv tool install tunacode-cli # recommended
88
+ # or: pip install tunacode-cli
89
+
90
+ # 2) Launch the CLI
91
+ tunacode --wizard # guided setup (enter an API key, pick a model)
92
+
93
+ # 3) Try common commands in the REPL
94
+ /help # see commands
95
+ /model # explore models and set a default
96
+ /plan # enter read-only Plan Mode
97
+ ```
98
+
99
+ Tip: You can also skip the wizard and set everything via flags:
100
+
101
+ ```bash
102
+ tunacode --model openai:gpt-4.1 --key sk-your-key
103
+ ```
104
+
105
+ ## Development Installation
106
+
107
+ For contributors and developers who want to work on TunaCode:
108
+
109
+ ```bash
110
+ # Clone the repository
111
+ git clone https://github.com/alchemiststudiosDOTai/tunacode.git
112
+ cd tunacode
113
+
114
+ # Quick setup (recommended) - uses UV automatically if available
115
+ ./scripts/setup_dev_env.sh
116
+
117
+ # Or manual setup with UV (recommended)
118
+ uv venv
119
+ source .venv/bin/activate # On Windows: .venv\Scripts\activate
120
+ uv pip install -e ".[dev]"
121
+
122
+ # Alternative: traditional setup
123
+ python3 -m venv venv
124
+ source venv/bin/activate # On Windows: venv\Scripts\activate
125
+ pip install -e ".[dev]"
126
+
127
+ # Verify installation
128
+ tunacode --version
129
+ ```
130
+
131
+ See the [Hatch Build System Guide](documentation/development/hatch-build-system.md) for detailed instructions on the development environment.
132
+
133
+ ## Configuration
134
+
135
+ Choose your AI provider and set your API key. For more details, see the [Configuration Section](documentation/user/getting-started.md#2-configuration) in the Getting Started Guide. For local models (LM Studio, Ollama, etc.), see the [Local Models Setup Guide](documentation/configuration/local-models.md).
136
+
137
+ ### New: Enhanced Model Selection
138
+
139
+ TunaCode now automatically saves your model selection for future sessions. When you choose a model using `/model <provider:name>`, it will be remembered across restarts.
140
+
141
+ **If you encounter API key errors**, you can manually create a configuration file that matches the current schema:
142
+
143
+ ```bash
144
+ # Create the config file
145
+ cat > ~/.config/tunacode.json << 'EOF'
146
+ {
147
+ "default_model": "openai:gpt-4.1",
148
+ "env": {
149
+ "OPENAI_API_KEY": "your-openai-api-key-here",
150
+ "ANTHROPIC_API_KEY": "",
151
+ "GEMINI_API_KEY": "",
152
+ "OPENROUTER_API_KEY": ""
153
+ },
154
+ "settings": {
155
+ "enable_streaming": true,
156
+ "max_iterations": 40,
157
+ "context_window_size": 200000
158
+ },
159
+ "mcpServers": {}
160
+ }
161
+ EOF
162
+ ```
163
+
164
+ Replace the model and API key with your preferred provider and credentials. Examples:
165
+ - `openai:gpt-4.1` (requires OPENAI_API_KEY)
166
+ - `anthropic:claude-4-sonnet-20250522` (requires ANTHROPIC_API_KEY)
167
+ - `google:gemini-2.5-pro` (requires GEMINI_API_KEY)
168
+
169
+ ### ⚠️ Important Notice
170
+
171
+ I apologize for any recent issues with model selection and configuration. I'm actively working to fix these problems and improve the overall stability of TunaCode. Your patience and feedback are greatly appreciated as I work to make the tool more reliable.
172
+
173
+ ### Recommended Models
174
+
175
+ Based on extensive testing, these models provide the best performance:
176
+
177
+ - `google/gemini-2.5-pro` - Excellent for complex reasoning
178
+ - `openai/gpt-4.1` - Strong general-purpose model
179
+ - `deepseek/deepseek-r1-0528` - Great for code generation
180
+ - `openai/gpt-4.1-mini` - Fast and cost-effective
181
+ - `anthropic/claude-4-sonnet-20250522` - Superior context handling
182
+
183
+ _Note: Formal evaluations coming soon. Any model can work, but these have shown the best results in practice._
184
+
185
+ ## Start Coding
186
+
187
+ ```bash
188
+ tunacode
189
+ ```
190
+
191
+ ## Basic Commands
192
+
193
+ | Command | Description |
194
+ | ------------------------ | ---------------------- |
195
+ | `/help` | Show all commands |
196
+ | `/model <provider:name>` | Switch model |
197
+ | `/clear` | Clear message history |
198
+ | `/compact` | Summarize conversation |
199
+ | `/branch <name>` | Create Git branch |
200
+ | `/yolo` | Skip confirmations |
201
+ | `!<command>` | Run shell command |
202
+ | `exit` | Exit TunaCode |
203
+
204
+ ## Performance
205
+
206
+ TunaCode leverages parallel execution for read-only operations, achieving **3x faster** file operations:
207
+
208
+ ![Parallel Execution Performance](docs/assets/parrelel_work_3x.png)
209
+
210
+ Multiple file reads, directory listings, and searches execute concurrently using async I/O, making code exploration significantly faster.
211
+
212
+ ## Features in Development
213
+
214
+ - **Bug Fixes**: Actively addressing issues - please report any bugs you encounter!
215
+
216
+ _Note: While the tool is fully functional, we're focusing on stability and core features before optimizing for speed._
217
+
218
+ ## Safety First
219
+
220
+ ⚠️ **Important**: TunaCode can modify your codebase. Always:
221
+
222
+ - Use Git branches before making changes
223
+ - Review file modifications before confirming
224
+ - Keep backups of important work
225
+
226
+ ## Documentation
227
+
228
+ For a complete overview of the documentation, see the [**Documentation Hub**](documentation/README.md).
229
+
230
+ ### User Documentation
231
+
232
+ - [**Getting Started**](documentation/user/getting-started.md) - How to install, configure, and use TunaCode.
233
+ - [**Commands**](documentation/user/commands.md) - A complete list of all available commands.
234
+
235
+ ### Developer Documentation
236
+
237
+ - **Architecture** (planned) - The overall architecture of the TunaCode application.
238
+ - **Contributing** (planned) - Guidelines for contributing to the project.
239
+ - **Tools** (planned) - How to create and use custom tools.
240
+ - **Testing** (planned) - Information on the testing philosophy and how to run tests.
241
+
242
+ ### Guides
243
+
244
+ - [**Advanced Configuration**](documentation/configuration/config-file-example.md) - An example of an advanced configuration file.
245
+
246
+ ### Reference
247
+
248
+ - **Changelog** (planned) - A history of changes to the application.
249
+ - **Roadmap** (planned) - The future direction of the project.
250
+ - **Security** (planned) - Information about the security of the application.
251
+
252
+ ## Links
253
+
254
+ - [PyPI Package](https://pypi.org/project/tunacode-cli/)
255
+ - [GitHub Repository](https://github.com/alchemiststudiosDOTai/tunacode)
256
+ - [Report Issues](https://github.com/alchemiststudiosDOTai/tunacode/issues)
257
+
258
+ ---
259
+
260
+ MIT License - see [LICENSE](LICENSE) file