tunacode-cli 0.0.76.2__py3-none-any.whl → 0.0.76.4__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/registry.py +13 -2
- tunacode/cli/main.py +1 -1
- tunacode/constants.py +1 -1
- tunacode/ui/completers.py +75 -27
- tunacode/utils/fuzzy_utils.py +33 -0
- tunacode/utils/models_registry.py +59 -29
- {tunacode_cli-0.0.76.2.dist-info → tunacode_cli-0.0.76.4.dist-info}/METADATA +14 -5
- {tunacode_cli-0.0.76.2.dist-info → tunacode_cli-0.0.76.4.dist-info}/RECORD +11 -10
- {tunacode_cli-0.0.76.2.dist-info → tunacode_cli-0.0.76.4.dist-info}/WHEEL +0 -0
- {tunacode_cli-0.0.76.2.dist-info → tunacode_cli-0.0.76.4.dist-info}/entry_points.txt +0 -0
- {tunacode_cli-0.0.76.2.dist-info → tunacode_cli-0.0.76.4.dist-info}/licenses/LICENSE +0 -0
|
@@ -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
|
|
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
|
-
|
|
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
|
|
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
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
|
-
#
|
|
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
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
64
|
-
cost: ModelCost =
|
|
65
|
-
limits: 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]] =
|
|
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
|
-
|
|
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] =
|
|
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=
|
|
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.
|
|
3
|
+
Version: 0.0.76.4
|
|
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:
|
|
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
|
-
|
|
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=
|
|
2
|
+
tunacode/constants.py,sha256=UjCi8qEonZUeA-UkPukT3l2ZoQeD43gmLCValHmJ6gs,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=
|
|
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=
|
|
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=
|
|
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=
|
|
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.
|
|
162
|
-
tunacode_cli-0.0.76.
|
|
163
|
-
tunacode_cli-0.0.76.
|
|
164
|
-
tunacode_cli-0.0.76.
|
|
165
|
-
tunacode_cli-0.0.76.
|
|
162
|
+
tunacode_cli-0.0.76.4.dist-info/METADATA,sha256=2QbpnZ143R18XCvsBBeke9uhJ2wd7RL2hy1HPPdnODQ,8913
|
|
163
|
+
tunacode_cli-0.0.76.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
164
|
+
tunacode_cli-0.0.76.4.dist-info/entry_points.txt,sha256=hbkytikj4dGu6rizPuAd_DGUPBGF191RTnhr9wdhORY,51
|
|
165
|
+
tunacode_cli-0.0.76.4.dist-info/licenses/LICENSE,sha256=Btzdu2kIoMbdSp6OyCLupB1aRgpTCJ_szMimgEnpkkE,1056
|
|
166
|
+
tunacode_cli-0.0.76.4.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|