tunacode-cli 0.0.9__py3-none-any.whl → 0.0.11__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 (46) hide show
  1. tunacode/cli/commands.py +34 -165
  2. tunacode/cli/main.py +15 -38
  3. tunacode/cli/repl.py +24 -18
  4. tunacode/configuration/defaults.py +1 -1
  5. tunacode/configuration/models.py +4 -11
  6. tunacode/configuration/settings.py +10 -3
  7. tunacode/constants.py +6 -4
  8. tunacode/context.py +3 -1
  9. tunacode/core/agents/main.py +94 -52
  10. tunacode/core/setup/agent_setup.py +1 -1
  11. tunacode/core/setup/config_setup.py +161 -81
  12. tunacode/core/setup/coordinator.py +4 -2
  13. tunacode/core/setup/environment_setup.py +1 -1
  14. tunacode/core/setup/git_safety_setup.py +51 -39
  15. tunacode/exceptions.py +2 -0
  16. tunacode/prompts/system.txt +1 -1
  17. tunacode/services/undo_service.py +16 -13
  18. tunacode/setup.py +6 -2
  19. tunacode/tools/base.py +20 -11
  20. tunacode/tools/update_file.py +14 -24
  21. tunacode/tools/write_file.py +7 -9
  22. tunacode/ui/completers.py +33 -98
  23. tunacode/ui/input.py +9 -13
  24. tunacode/ui/keybindings.py +3 -1
  25. tunacode/ui/lexers.py +17 -16
  26. tunacode/ui/output.py +8 -14
  27. tunacode/ui/panels.py +7 -5
  28. tunacode/ui/prompt_manager.py +4 -8
  29. tunacode/ui/tool_ui.py +3 -3
  30. tunacode/utils/system.py +0 -40
  31. tunacode_cli-0.0.11.dist-info/METADATA +387 -0
  32. tunacode_cli-0.0.11.dist-info/RECORD +65 -0
  33. {tunacode_cli-0.0.9.dist-info → tunacode_cli-0.0.11.dist-info}/licenses/LICENSE +1 -1
  34. tunacode/cli/model_selector.py +0 -178
  35. tunacode/core/agents/tinyagent_main.py +0 -194
  36. tunacode/core/setup/optimized_coordinator.py +0 -73
  37. tunacode/services/enhanced_undo_service.py +0 -322
  38. tunacode/services/project_undo_service.py +0 -311
  39. tunacode/tools/tinyagent_tools.py +0 -103
  40. tunacode/utils/lazy_imports.py +0 -59
  41. tunacode/utils/regex_cache.py +0 -33
  42. tunacode_cli-0.0.9.dist-info/METADATA +0 -321
  43. tunacode_cli-0.0.9.dist-info/RECORD +0 -73
  44. {tunacode_cli-0.0.9.dist-info → tunacode_cli-0.0.11.dist-info}/WHEEL +0 -0
  45. {tunacode_cli-0.0.9.dist-info → tunacode_cli-0.0.11.dist-info}/entry_points.txt +0 -0
  46. {tunacode_cli-0.0.9.dist-info → tunacode_cli-0.0.11.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", "/m"],
390
- description="List and select AI models interactively",
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 enhanced model list
402
- await self._show_model_list(selector, context.state_manager)
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
- # Find model by query (index, name, or fuzzy match)
406
- query = args[0]
407
- model_info = selector.find_model(query)
408
-
409
- if not model_info:
410
- # Try to provide helpful suggestions
411
- await ui.error(f"Model '{query}' not found")
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 = model_info.id
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(model_info.id, context.state_manager)
424
- await ui.success(
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 details
430
- cost_emoji = selector.get_cost_emoji(model_info.cost_tier)
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 if cmd.__class__ != command.__class__
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
- update: bool = typer.Option(False, "--update", "--upgrade", help="Update TunaCode to the latest version."),
29
- model: str = typer.Option(None, "--model", "-m", help="Set the model to use (e.g., 'openai:gpt-4o', 'openrouter:anthropic/claude-3.5-sonnet')."),
30
- base_url: str = typer.Option(None, "--base-url", help="Override the API base URL for OpenAI-compatible endpoints (e.g., 'https://openrouter.ai/api/v1')."),
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
- # Check if this is a model behavior error from tinyAgent
187
- if "model" in str(e).lower() or "unexpected" in str(e).lower():
188
- error_message = str(e)
189
- await ui.muted(error_message)
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
- # Hacky startup message
211
- await ui.warning("⚠️ tunaCode v0.1 - BETA SOFTWARE")
212
- await ui.muted(" All changes will be made on a new branch for safety")
213
- await ui.muted(" Use with caution! This tool can modify your codebase")
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
- await ui.success("ready to hack...")
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("Thanks for all the fish.")
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": "openrouter:openai/gpt-4.1",
13
+ "default_model": "openai:gpt-4.1",
14
14
  "env": {
15
15
  "ANTHROPIC_API_KEY": "",
16
16
  "GEMINI_API_KEY": "",
@@ -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
- if ModelRegistry._models_cache is None:
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: sidekick.configuration.settings
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 (APP_NAME, APP_VERSION, CONFIG_FILE_NAME, TOOL_READ_FILE,
11
- TOOL_RUN_COMMAND, TOOL_UPDATE_FILE, TOOL_WRITE_FILE)
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.0.9"
10
+ APP_VERSION = "0.0.11"
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": "#10b981", # Emerald green
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": "#334155", # Slate 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 = "It might be a binary file or use a different encoding. {error}"
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]: