tunacode-cli 0.0.8__py3-none-any.whl → 0.0.10__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.py +34 -165
- tunacode/cli/main.py +15 -38
- tunacode/cli/repl.py +24 -18
- tunacode/configuration/defaults.py +1 -1
- tunacode/configuration/models.py +4 -11
- tunacode/configuration/settings.py +10 -3
- tunacode/constants.py +6 -4
- tunacode/context.py +3 -1
- tunacode/core/agents/main.py +94 -52
- tunacode/core/setup/agent_setup.py +1 -1
- tunacode/core/setup/config_setup.py +148 -78
- tunacode/core/setup/coordinator.py +4 -2
- tunacode/core/setup/environment_setup.py +1 -1
- tunacode/core/setup/git_safety_setup.py +51 -39
- tunacode/exceptions.py +2 -0
- tunacode/prompts/system.txt +1 -1
- tunacode/services/undo_service.py +16 -13
- tunacode/setup.py +6 -2
- tunacode/tools/base.py +20 -11
- tunacode/tools/update_file.py +14 -24
- tunacode/tools/write_file.py +7 -9
- tunacode/ui/completers.py +33 -98
- tunacode/ui/input.py +9 -13
- tunacode/ui/keybindings.py +3 -1
- tunacode/ui/lexers.py +17 -16
- tunacode/ui/output.py +8 -14
- tunacode/ui/panels.py +7 -5
- tunacode/ui/prompt_manager.py +4 -8
- tunacode/ui/tool_ui.py +3 -3
- tunacode/utils/system.py +0 -40
- tunacode_cli-0.0.10.dist-info/METADATA +366 -0
- tunacode_cli-0.0.10.dist-info/RECORD +65 -0
- {tunacode_cli-0.0.8.dist-info → tunacode_cli-0.0.10.dist-info}/licenses/LICENSE +1 -1
- tunacode/cli/model_selector.py +0 -178
- tunacode/core/agents/tinyagent_main.py +0 -194
- tunacode/core/setup/optimized_coordinator.py +0 -73
- tunacode/services/enhanced_undo_service.py +0 -322
- tunacode/services/project_undo_service.py +0 -311
- tunacode/tools/tinyagent_tools.py +0 -103
- tunacode/utils/lazy_imports.py +0 -59
- tunacode/utils/regex_cache.py +0 -33
- tunacode_cli-0.0.8.dist-info/METADATA +0 -321
- tunacode_cli-0.0.8.dist-info/RECORD +0 -73
- {tunacode_cli-0.0.8.dist-info → tunacode_cli-0.0.10.dist-info}/WHEEL +0 -0
- {tunacode_cli-0.0.8.dist-info → tunacode_cli-0.0.10.dist-info}/entry_points.txt +0 -0
- {tunacode_cli-0.0.8.dist-info → tunacode_cli-0.0.10.dist-info}/top_level.txt +0 -0
tunacode/cli/commands.py
CHANGED
|
@@ -6,8 +6,8 @@ from enum import Enum
|
|
|
6
6
|
from typing import Any, Dict, List, Optional, Type
|
|
7
7
|
|
|
8
8
|
from .. import utils
|
|
9
|
+
from ..configuration.models import ModelRegistry
|
|
9
10
|
from ..exceptions import ValidationError
|
|
10
|
-
from ..services.undo_service import perform_undo
|
|
11
11
|
from ..types import CommandArgs, CommandContext, CommandResult, ProcessRequestCallback
|
|
12
12
|
from ..ui import console as ui
|
|
13
13
|
|
|
@@ -232,32 +232,6 @@ class HelpCommand(SimpleCommand):
|
|
|
232
232
|
await ui.help(self._command_registry)
|
|
233
233
|
|
|
234
234
|
|
|
235
|
-
class UndoCommand(SimpleCommand):
|
|
236
|
-
"""Undo the last file operation."""
|
|
237
|
-
|
|
238
|
-
def __init__(self):
|
|
239
|
-
super().__init__(
|
|
240
|
-
CommandSpec(
|
|
241
|
-
name="undo",
|
|
242
|
-
aliases=["/undo"],
|
|
243
|
-
description="Undo the last file operation",
|
|
244
|
-
category=CommandCategory.DEVELOPMENT,
|
|
245
|
-
)
|
|
246
|
-
)
|
|
247
|
-
|
|
248
|
-
async def execute(self, args: List[str], context: CommandContext) -> None:
|
|
249
|
-
success, message = perform_undo(context.state_manager)
|
|
250
|
-
if success:
|
|
251
|
-
await ui.success(message)
|
|
252
|
-
else:
|
|
253
|
-
# Provide more helpful information when undo fails
|
|
254
|
-
await ui.warning(message)
|
|
255
|
-
if "not in a git repository" in message.lower():
|
|
256
|
-
await ui.muted("💡 To enable undo functionality:")
|
|
257
|
-
await ui.muted(" • Run 'git init' to initialize a git repository")
|
|
258
|
-
await ui.muted(" • Or work in a directory that's already a git repository")
|
|
259
|
-
await ui.muted(" • File operations will still work, but can't be undone")
|
|
260
|
-
|
|
261
235
|
|
|
262
236
|
class BranchCommand(SimpleCommand):
|
|
263
237
|
"""Create and switch to a new git branch."""
|
|
@@ -305,50 +279,6 @@ class BranchCommand(SimpleCommand):
|
|
|
305
279
|
await ui.error("Git executable not found")
|
|
306
280
|
|
|
307
281
|
|
|
308
|
-
class InitCommand(SimpleCommand):
|
|
309
|
-
"""Analyse the repository and generate TUNACODE.md."""
|
|
310
|
-
|
|
311
|
-
def __init__(self):
|
|
312
|
-
super().__init__(
|
|
313
|
-
CommandSpec(
|
|
314
|
-
name="init",
|
|
315
|
-
aliases=["/init"],
|
|
316
|
-
description="Analyse the repo and create TUNACODE.md",
|
|
317
|
-
category=CommandCategory.DEVELOPMENT,
|
|
318
|
-
)
|
|
319
|
-
)
|
|
320
|
-
|
|
321
|
-
async def execute(self, args: List[str], context: CommandContext) -> None:
|
|
322
|
-
import json
|
|
323
|
-
from pathlib import Path
|
|
324
|
-
|
|
325
|
-
from .. import context as ctx
|
|
326
|
-
|
|
327
|
-
await ui.info("Gathering repository context")
|
|
328
|
-
data = await ctx.get_context()
|
|
329
|
-
|
|
330
|
-
prompt = (
|
|
331
|
-
"Using the following repository context, summarise build commands "
|
|
332
|
-
"and coding conventions. Return markdown for a TUNACODE.md file.\n\n"
|
|
333
|
-
+ json.dumps(data, indent=2)
|
|
334
|
-
)
|
|
335
|
-
|
|
336
|
-
process_request = context.process_request
|
|
337
|
-
content = ""
|
|
338
|
-
if process_request:
|
|
339
|
-
res = await process_request(prompt, context.state_manager, output=False)
|
|
340
|
-
try:
|
|
341
|
-
content = res.result.output
|
|
342
|
-
except Exception:
|
|
343
|
-
content = ""
|
|
344
|
-
|
|
345
|
-
if not content:
|
|
346
|
-
content = "# TUNACODE\n\n" + json.dumps(data, indent=2)
|
|
347
|
-
|
|
348
|
-
Path("TUNACODE.md").write_text(content, encoding="utf-8")
|
|
349
|
-
await ui.success("TUNACODE.md written")
|
|
350
|
-
|
|
351
|
-
|
|
352
282
|
class CompactCommand(SimpleCommand):
|
|
353
283
|
"""Compact conversation context."""
|
|
354
284
|
|
|
@@ -386,106 +316,47 @@ class ModelCommand(SimpleCommand):
|
|
|
386
316
|
super().__init__(
|
|
387
317
|
CommandSpec(
|
|
388
318
|
name="model",
|
|
389
|
-
aliases=["/model"
|
|
390
|
-
description="
|
|
319
|
+
aliases=["/model"],
|
|
320
|
+
description="Switch model (e.g., /model gpt-4 or /model openai:gpt-4)",
|
|
391
321
|
category=CommandCategory.MODEL,
|
|
392
322
|
)
|
|
393
323
|
)
|
|
394
324
|
|
|
395
325
|
async def execute(self, args: CommandArgs, context: CommandContext) -> Optional[str]:
|
|
396
|
-
from tunacode.cli.model_selector import ModelSelector
|
|
397
|
-
|
|
398
|
-
selector = ModelSelector()
|
|
399
|
-
|
|
400
326
|
if not args:
|
|
401
|
-
# No arguments - show
|
|
402
|
-
|
|
327
|
+
# No arguments - show current model
|
|
328
|
+
current_model = context.state_manager.session.current_model
|
|
329
|
+
await ui.info(f"Current model: {current_model}")
|
|
330
|
+
await ui.muted("Usage: /model <provider:model-name> [default]")
|
|
331
|
+
await ui.muted("Example: /model openai:gpt-4.1")
|
|
403
332
|
return None
|
|
404
333
|
|
|
405
|
-
#
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
if not
|
|
410
|
-
|
|
411
|
-
await ui.
|
|
412
|
-
await ui.muted(
|
|
413
|
-
"Try: /model (to list all), or use a number 0-18, "
|
|
414
|
-
"or model name like 'opus' or 'gpt-4'"
|
|
415
|
-
)
|
|
334
|
+
# Get the model name from args
|
|
335
|
+
model_name = args[0]
|
|
336
|
+
|
|
337
|
+
# Check if provider prefix is present
|
|
338
|
+
if ":" not in model_name:
|
|
339
|
+
await ui.error("Model name must include provider prefix")
|
|
340
|
+
await ui.muted("Format: provider:model-name")
|
|
341
|
+
await ui.muted("Examples: openai:gpt-4.1, anthropic:claude-3-opus, google-gla:gemini-2.0-flash")
|
|
416
342
|
return None
|
|
343
|
+
|
|
344
|
+
# No validation - user is responsible for correct model names
|
|
345
|
+
await ui.warning("Model set without validation - verify the model name is correct")
|
|
417
346
|
|
|
418
347
|
# Set the model
|
|
419
|
-
context.state_manager.session.current_model =
|
|
348
|
+
context.state_manager.session.current_model = model_name
|
|
420
349
|
|
|
421
350
|
# Check if setting as default
|
|
422
351
|
if len(args) > 1 and args[1] == "default":
|
|
423
|
-
utils.user_configuration.set_default_model(
|
|
424
|
-
await ui.
|
|
425
|
-
f"Set default model: {model_info.display_name} {model_info.provider.value[2]}"
|
|
426
|
-
)
|
|
352
|
+
utils.user_configuration.set_default_model(model_name, context.state_manager)
|
|
353
|
+
await ui.muted("Updating default model")
|
|
427
354
|
return "restart"
|
|
428
355
|
else:
|
|
429
|
-
# Show success message with model
|
|
430
|
-
|
|
431
|
-
await ui.success(
|
|
432
|
-
f"Switched to: {model_info.display_name} "
|
|
433
|
-
f"{model_info.provider.value[2]} {cost_emoji}\n"
|
|
434
|
-
f" → {model_info.description}"
|
|
435
|
-
)
|
|
356
|
+
# Show success message with the new model
|
|
357
|
+
await ui.success(f"Switched to model: {model_name}")
|
|
436
358
|
return None
|
|
437
359
|
|
|
438
|
-
async def _show_model_list(self, selector, state_manager) -> None:
|
|
439
|
-
"""Show enhanced model list grouped by provider."""
|
|
440
|
-
from rich.table import Table
|
|
441
|
-
from rich.text import Text
|
|
442
|
-
|
|
443
|
-
# Create table
|
|
444
|
-
table = Table(show_header=True, box=None, padding=(0, 2))
|
|
445
|
-
table.add_column("ID", style="dim", width=3)
|
|
446
|
-
table.add_column("Model", style="bold")
|
|
447
|
-
table.add_column("Short", style="cyan")
|
|
448
|
-
table.add_column("Description", style="dim")
|
|
449
|
-
table.add_column("Cost", justify="center", width=4)
|
|
450
|
-
|
|
451
|
-
# Current model
|
|
452
|
-
current_model = state_manager.session.current_model if state_manager else None
|
|
453
|
-
|
|
454
|
-
# Add models grouped by provider
|
|
455
|
-
model_index = 0
|
|
456
|
-
grouped = selector.get_models_by_provider()
|
|
457
|
-
|
|
458
|
-
for provider in [p for p in grouped if grouped[p]]: # Only show providers with models
|
|
459
|
-
# Add provider header
|
|
460
|
-
table.add_row(
|
|
461
|
-
"",
|
|
462
|
-
Text(f"{provider.value[2]} {provider.value[1]}", style="bold magenta"),
|
|
463
|
-
"",
|
|
464
|
-
"",
|
|
465
|
-
"",
|
|
466
|
-
)
|
|
467
|
-
|
|
468
|
-
# Add models for this provider
|
|
469
|
-
for model in grouped[provider]:
|
|
470
|
-
is_current = model.id == current_model
|
|
471
|
-
style = "bold green" if is_current else ""
|
|
472
|
-
|
|
473
|
-
table.add_row(
|
|
474
|
-
str(model_index),
|
|
475
|
-
Text(model.display_name + (" ← current" if is_current else ""), style=style),
|
|
476
|
-
model.short_name,
|
|
477
|
-
model.description,
|
|
478
|
-
selector.get_cost_emoji(model.cost_tier),
|
|
479
|
-
)
|
|
480
|
-
model_index += 1
|
|
481
|
-
|
|
482
|
-
# Show the table
|
|
483
|
-
await ui.panel("Available Models", table, border_style="cyan")
|
|
484
|
-
|
|
485
|
-
# Show usage hints
|
|
486
|
-
await ui.muted("\n💡 Usage: /model <number|name> [default]")
|
|
487
|
-
await ui.muted(" Examples: /model 3, /model opus, /model gpt-4 default")
|
|
488
|
-
|
|
489
360
|
|
|
490
361
|
@dataclass
|
|
491
362
|
class CommandDependencies:
|
|
@@ -546,7 +417,8 @@ class CommandRegistry:
|
|
|
546
417
|
category_commands = self._categories[command.category]
|
|
547
418
|
# Remove any existing instance of this command class
|
|
548
419
|
self._categories[command.category] = [
|
|
549
|
-
cmd for cmd in category_commands
|
|
420
|
+
cmd for cmd in category_commands
|
|
421
|
+
if cmd.__class__ != command.__class__
|
|
550
422
|
]
|
|
551
423
|
# Add the new instance
|
|
552
424
|
self._categories[command.category].append(command)
|
|
@@ -567,9 +439,7 @@ class CommandRegistry:
|
|
|
567
439
|
DumpCommand,
|
|
568
440
|
ClearCommand,
|
|
569
441
|
HelpCommand,
|
|
570
|
-
UndoCommand,
|
|
571
442
|
BranchCommand,
|
|
572
|
-
InitCommand,
|
|
573
443
|
# TunaCodeCommand, # TODO: Temporarily disabled
|
|
574
444
|
CompactCommand,
|
|
575
445
|
ModelCommand,
|
|
@@ -590,7 +460,7 @@ class CommandRegistry:
|
|
|
590
460
|
# Only update if callback has changed
|
|
591
461
|
if self._factory.dependencies.process_request_callback == callback:
|
|
592
462
|
return
|
|
593
|
-
|
|
463
|
+
|
|
594
464
|
self._factory.update_dependencies(process_request_callback=callback)
|
|
595
465
|
|
|
596
466
|
# Re-register CompactCommand with new dependency if already registered
|
|
@@ -625,10 +495,10 @@ class CommandRegistry:
|
|
|
625
495
|
if command_name in self._commands:
|
|
626
496
|
command = self._commands[command_name]
|
|
627
497
|
return await command.execute(args, context)
|
|
628
|
-
|
|
498
|
+
|
|
629
499
|
# Try partial matching
|
|
630
500
|
matches = self.find_matching_commands(command_name)
|
|
631
|
-
|
|
501
|
+
|
|
632
502
|
if not matches:
|
|
633
503
|
raise ValidationError(f"Unknown command: {command_name}")
|
|
634
504
|
elif len(matches) == 1:
|
|
@@ -638,17 +508,16 @@ class CommandRegistry:
|
|
|
638
508
|
else:
|
|
639
509
|
# Ambiguous - show possibilities
|
|
640
510
|
raise ValidationError(
|
|
641
|
-
f"Ambiguous command '{command_name}'. Did you mean: "
|
|
642
|
-
f"{', '.join(sorted(set(matches)))}?"
|
|
511
|
+
f"Ambiguous command '{command_name}'. Did you mean: {', '.join(sorted(set(matches)))}?"
|
|
643
512
|
)
|
|
644
513
|
|
|
645
514
|
def find_matching_commands(self, partial_command: str) -> List[str]:
|
|
646
515
|
"""
|
|
647
516
|
Find all commands that start with the given partial command.
|
|
648
|
-
|
|
517
|
+
|
|
649
518
|
Args:
|
|
650
519
|
partial_command: The partial command to match
|
|
651
|
-
|
|
520
|
+
|
|
652
521
|
Returns:
|
|
653
522
|
List of matching command names
|
|
654
523
|
"""
|
|
@@ -666,11 +535,11 @@ class CommandRegistry:
|
|
|
666
535
|
return False
|
|
667
536
|
|
|
668
537
|
command_name = parts[0].lower()
|
|
669
|
-
|
|
538
|
+
|
|
670
539
|
# Check exact match first
|
|
671
540
|
if command_name in self._commands:
|
|
672
541
|
return True
|
|
673
|
-
|
|
542
|
+
|
|
674
543
|
# Check partial match
|
|
675
544
|
return len(self.find_matching_commands(command_name)) > 0
|
|
676
545
|
|
tunacode/cli/main.py
CHANGED
|
@@ -6,8 +6,6 @@ Manages application startup, version checking, and REPL initialization.
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import asyncio
|
|
9
|
-
import os
|
|
10
|
-
from pathlib import Path
|
|
11
9
|
|
|
12
10
|
import typer
|
|
13
11
|
|
|
@@ -19,43 +17,21 @@ from tunacode.ui import console as ui
|
|
|
19
17
|
from tunacode.utils.system import check_for_updates
|
|
20
18
|
|
|
21
19
|
app_settings = ApplicationSettings()
|
|
20
|
+
app = typer.Typer(help=app_settings.name)
|
|
22
21
|
state_manager = StateManager()
|
|
23
22
|
|
|
24
23
|
|
|
24
|
+
@app.command()
|
|
25
25
|
def main(
|
|
26
26
|
version: bool = typer.Option(False, "--version", "-v", help="Show version and exit."),
|
|
27
27
|
run_setup: bool = typer.Option(False, "--setup", help="Run setup process."),
|
|
28
|
-
|
|
29
|
-
model: str = typer.Option(None, "--model",
|
|
30
|
-
|
|
28
|
+
baseurl: str = typer.Option(None, "--baseurl", help="API base URL (e.g., https://openrouter.ai/api/v1)"),
|
|
29
|
+
model: str = typer.Option(None, "--model", help="Default model to use (e.g., openai/gpt-4)"),
|
|
30
|
+
key: str = typer.Option(None, "--key", help="API key for the provider"),
|
|
31
31
|
):
|
|
32
|
-
"""TunaCode - Your agentic CLI developer."""
|
|
33
|
-
# Load .env file if it exists
|
|
34
|
-
env_file = Path(".env")
|
|
35
|
-
if env_file.exists():
|
|
36
|
-
try:
|
|
37
|
-
from dotenv import load_dotenv
|
|
38
|
-
load_dotenv()
|
|
39
|
-
except ImportError:
|
|
40
|
-
# dotenv not installed, skip loading
|
|
41
|
-
pass
|
|
42
32
|
if version:
|
|
43
33
|
asyncio.run(ui.version())
|
|
44
34
|
return
|
|
45
|
-
|
|
46
|
-
if update:
|
|
47
|
-
from tunacode.utils.system import update_tunacode
|
|
48
|
-
asyncio.run(update_tunacode())
|
|
49
|
-
return
|
|
50
|
-
|
|
51
|
-
# Apply CLI overrides to state manager before setup
|
|
52
|
-
if model:
|
|
53
|
-
state_manager.session.model = model
|
|
54
|
-
state_manager.session.user_config["default_model"] = model
|
|
55
|
-
|
|
56
|
-
if base_url:
|
|
57
|
-
import os
|
|
58
|
-
os.environ["OPENAI_BASE_URL"] = base_url
|
|
59
35
|
|
|
60
36
|
asyncio.run(ui.banner())
|
|
61
37
|
|
|
@@ -63,20 +39,21 @@ def main(
|
|
|
63
39
|
if has_update:
|
|
64
40
|
asyncio.run(ui.show_update_message(latest_version))
|
|
65
41
|
|
|
42
|
+
# Pass CLI args to setup
|
|
43
|
+
cli_config = {}
|
|
44
|
+
if baseurl or model or key:
|
|
45
|
+
cli_config = {
|
|
46
|
+
"baseurl": baseurl,
|
|
47
|
+
"model": model,
|
|
48
|
+
"key": key
|
|
49
|
+
}
|
|
50
|
+
|
|
66
51
|
try:
|
|
67
|
-
asyncio.run(setup(run_setup, state_manager))
|
|
52
|
+
asyncio.run(setup(run_setup, state_manager, cli_config))
|
|
68
53
|
asyncio.run(repl(state_manager))
|
|
69
54
|
except Exception as e:
|
|
70
55
|
asyncio.run(ui.error(str(e)))
|
|
71
56
|
|
|
72
57
|
|
|
73
|
-
app = typer.Typer(
|
|
74
|
-
name="tunacode",
|
|
75
|
-
help="TunaCode - Your agentic CLI developer",
|
|
76
|
-
add_completion=False,
|
|
77
|
-
)
|
|
78
|
-
|
|
79
|
-
app.command()(main)
|
|
80
|
-
|
|
81
58
|
if __name__ == "__main__":
|
|
82
59
|
app()
|
tunacode/cli/repl.py
CHANGED
|
@@ -10,6 +10,7 @@ from asyncio.exceptions import CancelledError
|
|
|
10
10
|
|
|
11
11
|
from prompt_toolkit.application import run_in_terminal
|
|
12
12
|
from prompt_toolkit.application.current import get_app
|
|
13
|
+
from pydantic_ai.exceptions import UnexpectedModelBehavior
|
|
13
14
|
|
|
14
15
|
from tunacode.configuration.settings import ApplicationSettings
|
|
15
16
|
from tunacode.core.agents import main as agent
|
|
@@ -182,17 +183,15 @@ async def process_request(text: str, state_manager: StateManager, output: bool =
|
|
|
182
183
|
await ui.muted("Request cancelled")
|
|
183
184
|
except UserAbortError:
|
|
184
185
|
await ui.muted("Operation aborted.")
|
|
186
|
+
except UnexpectedModelBehavior as e:
|
|
187
|
+
error_message = str(e)
|
|
188
|
+
await ui.muted(error_message)
|
|
189
|
+
patch_tool_messages(error_message, state_manager)
|
|
185
190
|
except Exception as e:
|
|
186
|
-
#
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
patch_tool_messages(error_message, state_manager)
|
|
191
|
-
else:
|
|
192
|
-
# Wrap unexpected exceptions in AgentError for better tracking
|
|
193
|
-
agent_error = AgentError(f"Agent processing failed: {str(e)}")
|
|
194
|
-
agent_error.__cause__ = e # Preserve the original exception chain
|
|
195
|
-
await ui.error(str(e))
|
|
191
|
+
# Wrap unexpected exceptions in AgentError for better tracking
|
|
192
|
+
agent_error = AgentError(f"Agent processing failed: {str(e)}")
|
|
193
|
+
agent_error.__cause__ = e # Preserve the original exception chain
|
|
194
|
+
await ui.error(str(e))
|
|
196
195
|
finally:
|
|
197
196
|
await ui.spinner(False, state_manager.session.spinner, state_manager)
|
|
198
197
|
state_manager.session.current_task = None
|
|
@@ -207,15 +206,22 @@ async def process_request(text: str, state_manager: StateManager, output: bool =
|
|
|
207
206
|
async def repl(state_manager: StateManager):
|
|
208
207
|
action = None
|
|
209
208
|
|
|
210
|
-
#
|
|
211
|
-
await ui.
|
|
212
|
-
await ui.muted("
|
|
213
|
-
await ui.muted("
|
|
214
|
-
await ui.muted(f"→ Model loaded: {state_manager.session.current_model}")
|
|
209
|
+
# Professional startup information
|
|
210
|
+
await ui.info("TunaCode v0.1 - Beta Release")
|
|
211
|
+
await ui.muted("• Caution: This tool can modify your codebase")
|
|
212
|
+
await ui.muted(f"• Model: {state_manager.session.current_model}")
|
|
215
213
|
await ui.line()
|
|
216
|
-
|
|
214
|
+
|
|
215
|
+
# Important safety warning
|
|
216
|
+
await ui.warning("⚠️ IMPORTANT: The /undo command has been removed for safety reasons")
|
|
217
|
+
await ui.muted("• Always use git branches before making major changes")
|
|
218
|
+
await ui.muted("• Use '/branch <name>' to create a new branch for experiments")
|
|
219
|
+
await ui.muted("• Commit your work frequently to preserve changes")
|
|
217
220
|
await ui.line()
|
|
218
|
-
|
|
221
|
+
|
|
222
|
+
await ui.success("Ready to assist with your development")
|
|
223
|
+
await ui.line()
|
|
224
|
+
|
|
219
225
|
instance = agent.get_or_create_agent(state_manager.session.current_model, state_manager)
|
|
220
226
|
|
|
221
227
|
async with instance.run_mcp_servers():
|
|
@@ -249,4 +255,4 @@ async def repl(state_manager: StateManager):
|
|
|
249
255
|
if action == "restart":
|
|
250
256
|
await repl(state_manager)
|
|
251
257
|
else:
|
|
252
|
-
await ui.info("
|
|
258
|
+
await ui.info("Session ended. Happy coding!")
|
|
@@ -10,7 +10,7 @@ from tunacode.constants import GUIDE_FILE_NAME, TOOL_READ_FILE
|
|
|
10
10
|
from tunacode.types import UserConfig
|
|
11
11
|
|
|
12
12
|
DEFAULT_USER_CONFIG: UserConfig = {
|
|
13
|
-
"default_model": "
|
|
13
|
+
"default_model": "openai:gpt-4.1",
|
|
14
14
|
"env": {
|
|
15
15
|
"ANTHROPIC_API_KEY": "",
|
|
16
16
|
"GEMINI_API_KEY": "",
|
tunacode/configuration/models.py
CHANGED
|
@@ -10,18 +10,8 @@ from tunacode.types import ModelRegistry as ModelRegistryType
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class ModelRegistry:
|
|
13
|
-
_instance = None
|
|
14
|
-
_models_cache = None
|
|
15
|
-
|
|
16
|
-
def __new__(cls):
|
|
17
|
-
if cls._instance is None:
|
|
18
|
-
cls._instance = super(ModelRegistry, cls).__new__(cls)
|
|
19
|
-
return cls._instance
|
|
20
|
-
|
|
21
13
|
def __init__(self):
|
|
22
|
-
|
|
23
|
-
ModelRegistry._models_cache = self._load_default_models()
|
|
24
|
-
self._models = ModelRegistry._models_cache
|
|
14
|
+
self._models = self._load_default_models()
|
|
25
15
|
|
|
26
16
|
def _load_default_models(self) -> ModelRegistryType:
|
|
27
17
|
return {
|
|
@@ -82,6 +72,9 @@ class ModelRegistry:
|
|
|
82
72
|
"openrouter:openai/gpt-4.1-mini": ModelConfig(
|
|
83
73
|
pricing=ModelPricing(input=0.40, cached_input=0.20, output=1.60)
|
|
84
74
|
),
|
|
75
|
+
"openrouter:openai/gpt-4.1-nano": ModelConfig(
|
|
76
|
+
pricing=ModelPricing(input=0.10, cached_input=0.05, output=0.40)
|
|
77
|
+
),
|
|
85
78
|
}
|
|
86
79
|
|
|
87
80
|
def get_model(self, name: ModelName) -> ModelConfig:
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""
|
|
2
|
-
Module:
|
|
2
|
+
Module: tunacode.configuration.settings
|
|
3
3
|
|
|
4
4
|
Application settings management for the Sidekick CLI.
|
|
5
5
|
Manages application paths, tool configurations, and runtime settings.
|
|
@@ -7,8 +7,15 @@ Manages application paths, tool configurations, and runtime settings.
|
|
|
7
7
|
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
|
|
10
|
-
from tunacode.constants import (
|
|
11
|
-
|
|
10
|
+
from tunacode.constants import (
|
|
11
|
+
APP_NAME,
|
|
12
|
+
APP_VERSION,
|
|
13
|
+
CONFIG_FILE_NAME,
|
|
14
|
+
TOOL_READ_FILE,
|
|
15
|
+
TOOL_RUN_COMMAND,
|
|
16
|
+
TOOL_UPDATE_FILE,
|
|
17
|
+
TOOL_WRITE_FILE,
|
|
18
|
+
)
|
|
12
19
|
from tunacode.types import ConfigFile, ConfigPath, ToolName
|
|
13
20
|
|
|
14
21
|
|
tunacode/constants.py
CHANGED
|
@@ -7,7 +7,7 @@ Centralizes all magic strings, UI text, error messages, and application constant
|
|
|
7
7
|
|
|
8
8
|
# Application info
|
|
9
9
|
APP_NAME = "TunaCode"
|
|
10
|
-
APP_VERSION = "0.
|
|
10
|
+
APP_VERSION = "0.8.21"
|
|
11
11
|
|
|
12
12
|
# File patterns
|
|
13
13
|
GUIDE_FILE_PATTERN = "{name}.md"
|
|
@@ -72,13 +72,13 @@ UI_COLORS = {
|
|
|
72
72
|
"primary": "#00d7ff", # Bright cyan
|
|
73
73
|
"secondary": "#64748b", # Slate gray
|
|
74
74
|
"accent": "#7c3aed", # Purple accent
|
|
75
|
-
"success": "#
|
|
75
|
+
"success": "#22c55e", # Modern green
|
|
76
76
|
"warning": "#f59e0b", # Amber
|
|
77
77
|
"error": "#ef4444", # Red
|
|
78
78
|
"muted": "#94a3b8", # Light slate
|
|
79
79
|
"file_ref": "#00d7ff", # Bright cyan
|
|
80
80
|
"background": "#0f172a", # Dark slate
|
|
81
|
-
"border": "#
|
|
81
|
+
"border": "#475569", # Stronger slate border
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
# UI text and formatting
|
|
@@ -103,7 +103,9 @@ ERROR_INVALID_PROVIDER = "Invalid provider number"
|
|
|
103
103
|
ERROR_FILE_NOT_FOUND = "Error: File not found at '{filepath}'."
|
|
104
104
|
ERROR_FILE_TOO_LARGE = "Error: File '{filepath}' is too large (> 100KB)."
|
|
105
105
|
ERROR_FILE_DECODE = "Error reading file '{filepath}': Could not decode using UTF-8."
|
|
106
|
-
ERROR_FILE_DECODE_DETAILS =
|
|
106
|
+
ERROR_FILE_DECODE_DETAILS = (
|
|
107
|
+
"It might be a binary file or use a different encoding. {error}"
|
|
108
|
+
)
|
|
107
109
|
ERROR_COMMAND_NOT_FOUND = "Error: Command not found or failed to execute:"
|
|
108
110
|
ERROR_COMMAND_EXECUTION = (
|
|
109
111
|
"Error: Command not found or failed to execute: {command}. Details: {error}"
|
tunacode/context.py
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
1
3
|
import subprocess
|
|
2
4
|
from pathlib import Path
|
|
3
5
|
from typing import Dict, List
|
|
4
6
|
|
|
5
|
-
from tunacode.utils.ripgrep import ripgrep
|
|
6
7
|
from tunacode.utils.system import list_cwd
|
|
8
|
+
from tunacode.utils.ripgrep import ripgrep
|
|
7
9
|
|
|
8
10
|
|
|
9
11
|
async def get_git_status() -> Dict[str, object]:
|