tunacode-cli 0.0.76.2__py3-none-any.whl → 0.0.76.3__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.

@@ -5,6 +5,7 @@ CLAUDE_ANCHOR[command-registry]: Central command registration and execution
5
5
 
6
6
  import logging
7
7
  from dataclasses import dataclass
8
+ from difflib import get_close_matches
8
9
  from pathlib import Path
9
10
  from typing import Any, Dict, List, Optional, Type
10
11
 
@@ -303,7 +304,7 @@ class CommandRegistry:
303
304
 
304
305
  def find_matching_commands(self, partial_command: str) -> List[str]:
305
306
  """
306
- Find all commands that start with the given partial command.
307
+ Find commands matching the given partial command.
307
308
 
308
309
  Args:
309
310
  partial_command: The partial command to match
@@ -313,7 +314,17 @@ class CommandRegistry:
313
314
  """
314
315
  self.discover_commands()
315
316
  partial = partial_command.lower()
316
- return [cmd for cmd in self._commands.keys() if cmd.startswith(partial)]
317
+
318
+ # 1) Prefer prefix matches (preserves current behavior)
319
+ prefix_matches = [cmd for cmd in self._commands.keys() if cmd.startswith(partial)]
320
+ if prefix_matches:
321
+ return prefix_matches
322
+
323
+ # 2) Fuzzy fallback for typos and near-misses
324
+ # CLAUDE_ANCHOR[fuzzy-command-matching]: Fuzzy fallback using difflib
325
+ # Keep minimal change: reuse built-ins, avoid new deps.
326
+ fuzzy = get_close_matches(partial, list(self._commands.keys()), n=3, cutoff=0.75)
327
+ return fuzzy
317
328
 
318
329
  def is_command(self, text: str) -> bool:
319
330
  """Check if text starts with a registered command (supports partial matching)."""
tunacode/cli/main.py CHANGED
@@ -19,7 +19,7 @@ from tunacode.ui import console as ui
19
19
  from tunacode.utils.system import check_for_updates
20
20
 
21
21
  app_settings = ApplicationSettings()
22
- app = typer.Typer(help="TunaCode - OS AI-powered development assistant")
22
+ app = typer.Typer(help="TunaCode - OS AI-powered CLI Coding Tool")
23
23
  state_manager = StateManager()
24
24
 
25
25
 
tunacode/constants.py CHANGED
@@ -9,7 +9,7 @@ from enum import Enum
9
9
 
10
10
  # Application info
11
11
  APP_NAME = "TunaCode"
12
- APP_VERSION = "0.0.76.2"
12
+ APP_VERSION = "0.0.76.3"
13
13
 
14
14
 
15
15
  # File patterns
tunacode/ui/completers.py CHANGED
@@ -11,6 +11,8 @@ from prompt_toolkit.completion import (
11
11
  )
12
12
  from prompt_toolkit.document import Document
13
13
 
14
+ from ..utils.fuzzy_utils import find_fuzzy_matches
15
+
14
16
  if TYPE_CHECKING:
15
17
  from ..cli.commands import CommandRegistry
16
18
  from ..utils.models_registry import ModelInfo, ModelsRegistry
@@ -73,7 +75,12 @@ class FileReferenceCompleter(Completer):
73
75
  def get_completions(
74
76
  self, document: Document, _complete_event: CompleteEvent
75
77
  ) -> Iterable[Completion]:
76
- """Get completions for @file references."""
78
+ """Get completions for @file references.
79
+
80
+ Favors file matches before directory matches and supports fuzzy
81
+ matching for near-miss filenames. Order:
82
+ exact files > fuzzy files > exact dirs > fuzzy dirs
83
+ """
77
84
  # Get the word before cursor
78
85
  word_before_cursor = document.get_word_before_cursor(WORD=True)
79
86
 
@@ -94,34 +101,75 @@ class FileReferenceCompleter(Completer):
94
101
  dir_path = "."
95
102
  prefix = path_part
96
103
 
97
- # Get matching files
104
+ # If prefix itself is an existing directory (without trailing slash),
105
+ # treat it as browsing inside that directory
106
+ candidate_dir = os.path.join(dir_path, prefix) if dir_path != "." else prefix
107
+ if prefix and os.path.isdir(candidate_dir) and not path_part.endswith("/"):
108
+ dir_path = candidate_dir
109
+ prefix = ""
110
+
111
+ # Get matching files with fuzzy support
98
112
  try:
99
113
  if os.path.exists(dir_path) and os.path.isdir(dir_path):
100
- for item in sorted(os.listdir(dir_path)):
101
- if item.startswith(prefix):
102
- full_path = os.path.join(dir_path, item) if dir_path != "." else item
103
-
104
- # Skip hidden files unless explicitly requested
105
- if item.startswith(".") and not prefix.startswith("."):
106
- continue
107
-
108
- # Add / for directories
109
- if os.path.isdir(full_path):
110
- display = item + "/"
111
- completion = full_path + "/"
112
- else:
113
- display = item
114
- completion = full_path
115
-
116
- # Calculate how much to replace
117
- start_position = -len(path_part)
118
-
119
- yield Completion(
120
- text=completion,
121
- start_position=start_position,
122
- display=display,
123
- display_meta="dir" if os.path.isdir(full_path) else "file",
124
- )
114
+ items = sorted(os.listdir(dir_path))
115
+
116
+ # Separate files vs dirs; skip hidden unless explicitly requested
117
+ show_hidden = prefix.startswith(".")
118
+ files: List[str] = []
119
+ dirs: List[str] = []
120
+ for item in items:
121
+ if item.startswith(".") and not show_hidden:
122
+ continue
123
+ full_item_path = os.path.join(dir_path, item) if dir_path != "." else item
124
+ if os.path.isdir(full_item_path):
125
+ dirs.append(item)
126
+ else:
127
+ files.append(item)
128
+
129
+ # Exact prefix matches (case-insensitive)
130
+ prefix_lower = prefix.lower()
131
+ exact_files = [f for f in files if f.lower().startswith(prefix_lower)]
132
+ exact_dirs = [d for d in dirs if d.lower().startswith(prefix_lower)]
133
+
134
+ # Fuzzy matches (exclude items already matched exactly)
135
+ fuzzy_file_candidates = [f for f in files if f not in exact_files]
136
+ fuzzy_dir_candidates = [d for d in dirs if d not in exact_dirs]
137
+
138
+ fuzzy_files = (
139
+ find_fuzzy_matches(prefix, fuzzy_file_candidates, n=10, cutoff=0.75)
140
+ if prefix
141
+ else []
142
+ )
143
+ fuzzy_dirs = (
144
+ find_fuzzy_matches(prefix, fuzzy_dir_candidates, n=10, cutoff=0.75)
145
+ if prefix
146
+ else []
147
+ )
148
+
149
+ # Compose ordered results
150
+ ordered: List[tuple[str, str]] = (
151
+ [("file", name) for name in exact_files]
152
+ + [("file", name) for name in fuzzy_files]
153
+ + [("dir", name) for name in exact_dirs]
154
+ + [("dir", name) for name in fuzzy_dirs]
155
+ )
156
+
157
+ start_position = -len(path_part)
158
+ for kind, name in ordered:
159
+ full_path = os.path.join(dir_path, name) if dir_path != "." else name
160
+ if kind == "dir":
161
+ display = name + "/"
162
+ completion_text = full_path + "/"
163
+ else:
164
+ display = name
165
+ completion_text = full_path
166
+
167
+ yield Completion(
168
+ text=completion_text,
169
+ start_position=start_position,
170
+ display=display,
171
+ display_meta="dir" if kind == "dir" else "file",
172
+ )
125
173
  except (OSError, PermissionError):
126
174
  # Silently ignore inaccessible directories
127
175
  pass
@@ -0,0 +1,33 @@
1
+ """Shared fuzzy matching utilities.
2
+
3
+ Minimal helper to provide consistent fuzzy matching behavior across
4
+ components (commands, file completers, etc.) without introducing new
5
+ dependencies. Reuses difflib.get_close_matches with a default cutoff of 0.75
6
+ matching the command registry behavior.
7
+ """
8
+
9
+ from difflib import get_close_matches
10
+ from typing import List, Sequence
11
+
12
+
13
+ def find_fuzzy_matches(
14
+ query: str,
15
+ choices: Sequence[str],
16
+ *,
17
+ n: int = 5,
18
+ cutoff: float = 0.75,
19
+ ) -> List[str]:
20
+ """Return up to ``n`` fuzzy matches from ``choices`` for ``query``.
21
+
22
+ The return order is the order produced by ``difflib.get_close_matches``.
23
+ Matching is case-insensitive; original casing is preserved in the result.
24
+ """
25
+ if not query or not choices:
26
+ return []
27
+
28
+ # Map lower-case to original to do case-insensitive matching while
29
+ # returning original items.
30
+ lower_to_original = {c.lower(): c for c in choices}
31
+ candidates = list(lower_to_original.keys())
32
+ matches_lower = get_close_matches(query.lower(), candidates, n=n, cutoff=cutoff)
33
+ return [lower_to_original[m] for m in matches_lower]
@@ -1,7 +1,6 @@
1
1
  """Models.dev integration for model discovery and validation."""
2
2
 
3
3
  import json
4
- from dataclasses import dataclass, field
5
4
  from datetime import datetime, timedelta
6
5
  from difflib import SequenceMatcher
7
6
  from pathlib import Path
@@ -9,11 +8,14 @@ from typing import Any, Dict, List, Optional
9
8
  from urllib.error import URLError
10
9
  from urllib.request import urlopen
11
10
 
11
+ from pydantic import BaseModel, ConfigDict, Field, field_validator
12
12
 
13
- @dataclass
14
- class ModelCapabilities:
13
+
14
+ class ModelCapabilities(BaseModel):
15
15
  """Model capabilities and features."""
16
16
 
17
+ model_config = ConfigDict(extra="ignore")
18
+
17
19
  attachment: bool = False
18
20
  reasoning: bool = False
19
21
  tool_call: bool = False
@@ -21,14 +23,24 @@ class ModelCapabilities:
21
23
  knowledge: Optional[str] = None
22
24
 
23
25
 
24
- @dataclass
25
- class ModelCost:
26
+ class ModelCost(BaseModel):
26
27
  """Model pricing information."""
27
28
 
29
+ model_config = ConfigDict(extra="ignore")
30
+
28
31
  input: Optional[float] = None
29
32
  output: Optional[float] = None
30
33
  cache: Optional[float] = None
31
34
 
35
+ @field_validator("input", "output", "cache")
36
+ @classmethod
37
+ def _non_negative(cls, v: Optional[float]) -> Optional[float]:
38
+ if v is None:
39
+ return v
40
+ if v < 0:
41
+ raise ValueError("cost values must be non-negative")
42
+ return float(v)
43
+
32
44
  def format_cost(self) -> str:
33
45
  """Format cost as a readable string."""
34
46
  if self.input is None or self.output is None:
@@ -36,16 +48,27 @@ class ModelCost:
36
48
  return f"${self.input}/{self.output} per 1M tokens"
37
49
 
38
50
 
39
- @dataclass
40
- class ModelLimits:
51
+ class ModelLimits(BaseModel):
41
52
  """Model context and output limits."""
42
53
 
54
+ model_config = ConfigDict(extra="ignore")
55
+
43
56
  context: Optional[int] = None
44
57
  output: Optional[int] = None
45
58
 
59
+ @field_validator("context", "output")
60
+ @classmethod
61
+ def _positive_int(cls, v: Optional[int]) -> Optional[int]:
62
+ if v is None:
63
+ return v
64
+ iv = int(v)
65
+ if iv <= 0:
66
+ raise ValueError("limits must be positive integers")
67
+ return iv
68
+
46
69
  def format_limits(self) -> str:
47
70
  """Format limits as a readable string."""
48
- parts = []
71
+ parts: List[str] = []
49
72
  if self.context:
50
73
  parts.append(f"{self.context:,} context")
51
74
  if self.output:
@@ -53,20 +76,21 @@ class ModelLimits:
53
76
  return ", ".join(parts) if parts else "Limits not specified"
54
77
 
55
78
 
56
- @dataclass
57
- class ModelInfo:
79
+ class ModelInfo(BaseModel):
58
80
  """Complete model information."""
59
81
 
82
+ model_config = ConfigDict(extra="ignore")
83
+
60
84
  id: str
61
85
  name: str
62
86
  provider: str
63
- capabilities: ModelCapabilities = field(default_factory=ModelCapabilities)
64
- cost: ModelCost = field(default_factory=ModelCost)
65
- limits: ModelLimits = field(default_factory=ModelLimits)
87
+ capabilities: ModelCapabilities = Field(default_factory=ModelCapabilities)
88
+ cost: ModelCost = Field(default_factory=ModelCost)
89
+ limits: ModelLimits = Field(default_factory=ModelLimits)
66
90
  release_date: Optional[str] = None
67
91
  last_updated: Optional[str] = None
68
92
  open_weights: bool = False
69
- modalities: Dict[str, List[str]] = field(default_factory=dict)
93
+ modalities: Dict[str, List[str]] = Field(default_factory=dict)
70
94
 
71
95
  @property
72
96
  def full_id(self) -> str:
@@ -77,7 +101,7 @@ class ModelInfo:
77
101
  """Format model for display."""
78
102
  display = f"{self.full_id} - {self.name}"
79
103
  if include_details:
80
- details = []
104
+ details: List[str] = []
81
105
  if self.cost.input is not None:
82
106
  details.append(self.cost.format_cost())
83
107
  if self.limits.context:
@@ -86,6 +110,7 @@ class ModelInfo:
86
110
  display += f" ({', '.join(details)})"
87
111
  return display
88
112
 
113
+ # we need to make this lighter for future dev, low priority
89
114
  def matches_search(self, query: str) -> float:
90
115
  """Calculate match score for search query (0-1)."""
91
116
  query_lower = query.lower()
@@ -107,13 +132,14 @@ class ModelInfo:
107
132
  return best_ratio
108
133
 
109
134
 
110
- @dataclass
111
- class ProviderInfo:
135
+ class ProviderInfo(BaseModel):
112
136
  """Provider information."""
113
137
 
138
+ model_config = ConfigDict(extra="ignore")
139
+
114
140
  id: str
115
141
  name: str
116
- env: List[str] = field(default_factory=list)
142
+ env: List[str] = Field(default_factory=list)
117
143
  npm: Optional[str] = None
118
144
  doc: Optional[str] = None
119
145
 
@@ -315,26 +341,26 @@ class ModelsRegistry:
315
341
 
316
342
  # Parse capabilities
317
343
  capabilities = ModelCapabilities(
318
- attachment=model_data.get("attachment", False),
319
- reasoning=model_data.get("reasoning", False),
320
- tool_call=model_data.get("tool_call", False),
321
- temperature=model_data.get("temperature", True),
344
+ attachment=bool(model_data.get("attachment", False)),
345
+ reasoning=bool(model_data.get("reasoning", False)),
346
+ tool_call=bool(model_data.get("tool_call", False)),
347
+ temperature=bool(model_data.get("temperature", True)),
322
348
  knowledge=model_data.get("knowledge"),
323
349
  )
324
350
 
325
351
  # Parse cost
326
352
  cost_data = model_data.get("cost", {})
327
353
  cost = ModelCost(
328
- input=cost_data.get("input") if isinstance(cost_data, dict) else None,
329
- output=cost_data.get("output") if isinstance(cost_data, dict) else None,
330
- cache=cost_data.get("cache") if isinstance(cost_data, dict) else None,
354
+ input=(cost_data.get("input") if isinstance(cost_data, dict) else None),
355
+ output=(cost_data.get("output") if isinstance(cost_data, dict) else None),
356
+ cache=(cost_data.get("cache") if isinstance(cost_data, dict) else None),
331
357
  )
332
358
 
333
359
  # Parse limits
334
360
  limit_data = model_data.get("limit", {})
335
361
  limits = ModelLimits(
336
- context=limit_data.get("context") if isinstance(limit_data, dict) else None,
337
- output=limit_data.get("output") if isinstance(limit_data, dict) else None,
362
+ context=(limit_data.get("context") if isinstance(limit_data, dict) else None),
363
+ output=(limit_data.get("output") if isinstance(limit_data, dict) else None),
338
364
  )
339
365
 
340
366
  # Create model info
@@ -347,8 +373,12 @@ class ModelsRegistry:
347
373
  limits=limits,
348
374
  release_date=model_data.get("release_date"),
349
375
  last_updated=model_data.get("last_updated"),
350
- open_weights=model_data.get("open_weights", False),
351
- modalities=model_data.get("modalities", {}),
376
+ open_weights=bool(model_data.get("open_weights", False)),
377
+ modalities=(
378
+ model_data.get("modalities", {})
379
+ if isinstance(model_data.get("modalities", {}), dict)
380
+ else {}
381
+ ),
352
382
  )
353
383
 
354
384
  # Store with full ID as key
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tunacode-cli
3
- Version: 0.0.76.2
3
+ Version: 0.0.76.3
4
4
  Summary: Your agentic CLI developer.
5
5
  Project-URL: Homepage, https://tunacode.xyz/
6
6
  Project-URL: Repository, https://github.com/alchemiststudiosDOTai/tunacode
@@ -69,7 +69,10 @@ Description-Content-Type: text/markdown
69
69
  # Option 1: One-line install (Linux/macOS)
70
70
  wget -qO- https://raw.githubusercontent.com/alchemiststudiosDOTai/tunacode/master/scripts/install_linux.sh | bash
71
71
 
72
- # Option 2: pip install
72
+ # Option 2: UV install (recommended)
73
+ uv tool install tunacode-cli
74
+
75
+ # Option 3: pip install
73
76
  pip install tunacode-cli
74
77
  ```
75
78
 
@@ -78,8 +81,9 @@ For detailed installation and configuration instructions, see the [**Getting Sta
78
81
  ## Quickstart
79
82
 
80
83
  ```bash
81
- # 1) Install
82
- pip install tunacode-cli
84
+ # 1) Install (choose one)
85
+ uv tool install tunacode-cli # recommended
86
+ # or: pip install tunacode-cli
83
87
 
84
88
  # 2) Launch the CLI
85
89
  tunacode --wizard # guided setup (enter an API key, pick a model)
@@ -105,7 +109,7 @@ For contributors and developers who want to work on TunaCode:
105
109
  git clone https://github.com/alchemiststudiosDOTai/tunacode.git
106
110
  cd tunacode
107
111
 
108
- # Quick setup (recommended)
112
+ # Quick setup (recommended) - uses UV automatically if available
109
113
  ./scripts/setup_dev_env.sh
110
114
 
111
115
  # Or manual setup with UV (recommended)
@@ -113,6 +117,11 @@ uv venv
113
117
  source .venv/bin/activate # On Windows: .venv\Scripts\activate
114
118
  uv pip install -e ".[dev]"
115
119
 
120
+ # Alternative: traditional setup
121
+ python3 -m venv venv
122
+ source venv/bin/activate # On Windows: venv\Scripts\activate
123
+ pip install -e ".[dev]"
124
+
116
125
  # Verify installation
117
126
  tunacode --version
118
127
  ```
@@ -1,16 +1,16 @@
1
1
  tunacode/__init__.py,sha256=yUul8igNYMfUrHnYfioIGAqvrH8b5BKiO_pt1wVnmd0,119
2
- tunacode/constants.py,sha256=ttfY29jCfMQSZY5znsez0icXNHIrrUhggkSZcaR9Q4g,6170
2
+ tunacode/constants.py,sha256=bRNiKjbYi_2hRO7BlYIJ8z8jGPXe-ze4fSoKj5aomh4,6170
3
3
  tunacode/context.py,sha256=YtfRjUiqsSkk2k9Nn_pjb_m-AXyh6XcOBOJWtFI0wVw,2405
4
4
  tunacode/exceptions.py,sha256=m80njR-LqBXhFAEOPqCE7N2QPU4Fkjlf_f6CWKO0_Is,8479
5
5
  tunacode/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
6
  tunacode/setup.py,sha256=F1E4zHVnbByu_Uo6AhCJ-W-lIGF_gV6kB84HLAGLmVY,2103
7
7
  tunacode/types.py,sha256=xNpDRjIRYg4qGNbl3EG8B13CWAWBoob9ekVm8_6dvnc,10496
8
8
  tunacode/cli/__init__.py,sha256=zgs0UbAck8hfvhYsWhWOfBe5oK09ug2De1r4RuQZREA,55
9
- tunacode/cli/main.py,sha256=MAKPFA4kGSFciqMdxnyp2r9XzVp8TfxvK6ztt7dvjwM,3445
9
+ tunacode/cli/main.py,sha256=ZzFjLnn6YTYRrcgt7DfyqrxIPM220kBF2xV9rwF1BmA,3439
10
10
  tunacode/cli/repl.py,sha256=QBw05NNypxuTYoV51FVoTfujJfiOTCIYzAr_1uoi0YA,23515
11
11
  tunacode/cli/commands/__init__.py,sha256=J7MZofTaSgspAKP64OavPukj4l53qvkv_-sCfYEUi10,1794
12
12
  tunacode/cli/commands/base.py,sha256=Ge_lNQA-GDfcb1Ap1oznCH3UrifBiHH3bA9DNL-tCDw,2519
13
- tunacode/cli/commands/registry.py,sha256=oAppbaOb-2blVo7Akthu6dbR9UFvXom6wf8m52qftpU,14962
13
+ tunacode/cli/commands/registry.py,sha256=qey9iqLL-gJF2zWATtY-AjNgE9zfA7197MuJpHVf_C0,15433
14
14
  tunacode/cli/commands/template_shortcut.py,sha256=ApYTPkDVBRaLxa7rWaPrsGcJdkR7eg09k18KyTjYg_E,3447
15
15
  tunacode/cli/commands/implementations/__init__.py,sha256=dFczjIqCJTPrsSycD6PZYnp5_cIEQEGgKr0Y14MRGjs,1088
16
16
  tunacode/cli/commands/implementations/command_reload.py,sha256=GyjeKvJbgE4VYkaasGajspdk9wffumZMNLzfCUeNazM,1555
@@ -124,7 +124,7 @@ tunacode/tutorial/content.py,sha256=qaQewFwXtKKEmzLH-4oMECGAa4Z4nd1qh2HfRWLpwyk,
124
124
  tunacode/tutorial/manager.py,sha256=ZgkzSC6ZtYSDq5Ce_TfYk9O9cvgFSL-pXrLZb7_HStM,6309
125
125
  tunacode/tutorial/steps.py,sha256=l2bbRVJuYlC186A-U1TIoMPBtLl4j053h4Wlzo1VO8c,4393
126
126
  tunacode/ui/__init__.py,sha256=aRNE2pS50nFAX6y--rSGMNYwhz905g14gRd6g4BolYU,13
127
- tunacode/ui/completers.py,sha256=ZkbCrBJJse3ZmGC79p_Bvv_DQOQBBOBZd4BF5Qf8-cI,12663
127
+ tunacode/ui/completers.py,sha256=O5wAlo9cufV2VWbFKMWzZeXRqptdS4CwK6wok1DdDak,14786
128
128
  tunacode/ui/config_dashboard.py,sha256=FhfWwEzPTNjvornTb0njxv_o2SavoEW4EXqyOCrbqk0,21657
129
129
  tunacode/ui/console.py,sha256=HfE30vUy8ebXCobP7psFNJc17-dvH6APChg2tbi7aTw,2632
130
130
  tunacode/ui/constants.py,sha256=A76B_KpM8jCuBYRg4cPmhi8_j6LLyWttO7_jjv47r3w,421
@@ -147,10 +147,11 @@ tunacode/utils/bm25.py,sha256=fd59YQXovC8rXwZrdoqIAfFrLn_WCVjzCh0pkU22APE,1966
147
147
  tunacode/utils/config_comparator.py,sha256=iMShhYCKlo0dXycbfpRu5rj3ckT460FoDvkbr5_-yTY,12879
148
148
  tunacode/utils/diff_utils.py,sha256=V9QqQ0q4MfabVTnWptF3IXDp3estnfOKcJtDe_Sj14I,2372
149
149
  tunacode/utils/file_utils.py,sha256=84g-MQRzmBI2aG_CuXsDl2OhvvWoSL7YdL5Kz_UKSwk,979
150
+ tunacode/utils/fuzzy_utils.py,sha256=Dl2C4ksNVfqdhjn-bVur4_JFiPkQXYerCjR-EOmtmRI,1140
150
151
  tunacode/utils/import_cache.py,sha256=q_xjJbtju05YbFopLDSkIo1hOtCx3DOTl3GQE5FFDgs,295
151
152
  tunacode/utils/json_utils.py,sha256=cMVctSwwV9Z1c-rZdj6UuOlZwsUPSTF5oUruP6uPix0,6470
152
153
  tunacode/utils/message_utils.py,sha256=V4MrZZPmwO22_MVGupMqtE5ltQEBwaSIqGD5LEb_bLw,1050
153
- tunacode/utils/models_registry.py,sha256=7dwQU0a-DYDHzQy3KtaGuPzTzS1pVayJMyUyZYGNgh8,20135
154
+ tunacode/utils/models_registry.py,sha256=zNbujehYZ5FQLU8ywY9cSOcQDRh1ToLoGLGekEfOtbg,21254
154
155
  tunacode/utils/retry.py,sha256=AHdUzY6m-mwlT4OPXdtWWMAafL_NeS7JAMORGyM8c5k,4931
155
156
  tunacode/utils/ripgrep.py,sha256=VdGWYPQ1zCwUidw2QicuVmG5OiAgqI93jAsjS3y3ksE,11001
156
157
  tunacode/utils/security.py,sha256=i3eGKg4o-qY2S_ObTlEaHO93q14iBfiPXR5O7srHn58,6579
@@ -158,8 +159,8 @@ tunacode/utils/system.py,sha256=J8KqJ4ZqQrNSnM5rrJxPeMk9z2xQQp6dWtI1SKBY1-0,1112
158
159
  tunacode/utils/text_utils.py,sha256=HAwlT4QMy41hr53cDbbNeNo05MI461TpI9b_xdIv8EY,7288
159
160
  tunacode/utils/token_counter.py,sha256=dmFuqVz4ywGFdLfAi5Mg9bAGf8v87Ek-mHU-R3fsYjI,2711
160
161
  tunacode/utils/user_configuration.py,sha256=OA-L0BgWNbf9sWpc8lyivgLscwJdpdI8TAYbe0wRs1s,4836
161
- tunacode_cli-0.0.76.2.dist-info/METADATA,sha256=1Het2CEX5MWn9T8AKYxV0w17RR9ZJbXRbFEaGn1oHBk,8605
162
- tunacode_cli-0.0.76.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
163
- tunacode_cli-0.0.76.2.dist-info/entry_points.txt,sha256=hbkytikj4dGu6rizPuAd_DGUPBGF191RTnhr9wdhORY,51
164
- tunacode_cli-0.0.76.2.dist-info/licenses/LICENSE,sha256=Btzdu2kIoMbdSp6OyCLupB1aRgpTCJ_szMimgEnpkkE,1056
165
- tunacode_cli-0.0.76.2.dist-info/RECORD,,
162
+ tunacode_cli-0.0.76.3.dist-info/METADATA,sha256=jomzgPHW7B0qAiE2rjTVw4mZZmnOjerJ8QjgfQ0Bqxg,8913
163
+ tunacode_cli-0.0.76.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
164
+ tunacode_cli-0.0.76.3.dist-info/entry_points.txt,sha256=hbkytikj4dGu6rizPuAd_DGUPBGF191RTnhr9wdhORY,51
165
+ tunacode_cli-0.0.76.3.dist-info/licenses/LICENSE,sha256=Btzdu2kIoMbdSp6OyCLupB1aRgpTCJ_szMimgEnpkkE,1056
166
+ tunacode_cli-0.0.76.3.dist-info/RECORD,,