tunacode-cli 0.0.48__py3-none-any.whl → 0.0.50__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 (45) hide show
  1. api/auth.py +13 -0
  2. api/users.py +8 -0
  3. tunacode/__init__.py +4 -0
  4. tunacode/cli/main.py +4 -0
  5. tunacode/cli/repl.py +39 -6
  6. tunacode/configuration/defaults.py +0 -1
  7. tunacode/constants.py +7 -1
  8. tunacode/core/agents/main.py +268 -245
  9. tunacode/core/agents/utils.py +54 -6
  10. tunacode/core/logging/__init__.py +29 -0
  11. tunacode/core/logging/config.py +57 -0
  12. tunacode/core/logging/formatters.py +48 -0
  13. tunacode/core/logging/handlers.py +83 -0
  14. tunacode/core/logging/logger.py +8 -0
  15. tunacode/core/recursive/__init__.py +18 -0
  16. tunacode/core/recursive/aggregator.py +467 -0
  17. tunacode/core/recursive/budget.py +414 -0
  18. tunacode/core/recursive/decomposer.py +398 -0
  19. tunacode/core/recursive/executor.py +470 -0
  20. tunacode/core/recursive/hierarchy.py +488 -0
  21. tunacode/core/state.py +45 -0
  22. tunacode/exceptions.py +23 -0
  23. tunacode/tools/base.py +7 -1
  24. tunacode/types.py +5 -1
  25. tunacode/ui/completers.py +2 -2
  26. tunacode/ui/console.py +30 -9
  27. tunacode/ui/input.py +2 -1
  28. tunacode/ui/keybindings.py +58 -1
  29. tunacode/ui/logging_compat.py +44 -0
  30. tunacode/ui/output.py +7 -6
  31. tunacode/ui/panels.py +30 -5
  32. tunacode/ui/recursive_progress.py +380 -0
  33. tunacode/utils/retry.py +163 -0
  34. tunacode/utils/security.py +3 -2
  35. tunacode/utils/token_counter.py +1 -2
  36. {tunacode_cli-0.0.48.dist-info → tunacode_cli-0.0.50.dist-info}/METADATA +2 -2
  37. {tunacode_cli-0.0.48.dist-info → tunacode_cli-0.0.50.dist-info}/RECORD +41 -29
  38. {tunacode_cli-0.0.48.dist-info → tunacode_cli-0.0.50.dist-info}/top_level.txt +1 -0
  39. tunacode/core/agents/dspy_integration.py +0 -223
  40. tunacode/core/agents/dspy_tunacode.py +0 -458
  41. tunacode/prompts/dspy_task_planning.md +0 -45
  42. tunacode/prompts/dspy_tool_selection.md +0 -58
  43. {tunacode_cli-0.0.48.dist-info → tunacode_cli-0.0.50.dist-info}/WHEEL +0 -0
  44. {tunacode_cli-0.0.48.dist-info → tunacode_cli-0.0.50.dist-info}/entry_points.txt +0 -0
  45. {tunacode_cli-0.0.48.dist-info → tunacode_cli-0.0.50.dist-info}/licenses/LICENSE +0 -0
tunacode/ui/console.py CHANGED
@@ -9,29 +9,26 @@ from rich.markdown import Markdown
9
9
  # Import and re-export all functions from specialized modules
10
10
  from .input import formatted_text, input, multiline_input
11
11
  from .keybindings import create_key_bindings
12
+
13
+ # Unified UI logger compatibility layer
14
+ from .logging_compat import ui_logger
12
15
  from .output import (
13
16
  banner,
14
17
  clear,
15
- info,
16
18
  line,
17
19
  muted,
18
20
  print,
19
21
  spinner,
20
- success,
21
22
  sync_print,
22
23
  update_available,
23
24
  usage,
24
25
  version,
25
- warning,
26
26
  )
27
-
28
- # Patch banner to use sync fast version
29
27
  from .panels import (
30
28
  StreamingAgentPanel,
31
29
  agent,
32
30
  agent_streaming,
33
31
  dump_messages,
34
- error,
35
32
  help,
36
33
  models,
37
34
  panel,
@@ -42,6 +39,28 @@ from .panels import (
42
39
  from .prompt_manager import PromptConfig, PromptManager
43
40
  from .validators import ModelValidator
44
41
 
42
+
43
+ # Async wrappers for UI logging
44
+ async def info(message: str) -> None:
45
+ await ui_logger.info(message)
46
+
47
+
48
+ async def warning(message: str) -> None:
49
+ await ui_logger.warning(message)
50
+
51
+
52
+ async def error(message: str) -> None:
53
+ await ui_logger.error(message)
54
+
55
+
56
+ async def debug(message: str) -> None:
57
+ await ui_logger.debug(message)
58
+
59
+
60
+ async def success(message: str) -> None:
61
+ await ui_logger.success(message)
62
+
63
+
45
64
  # Create console object for backward compatibility
46
65
  console = RichConsole(force_terminal=True, legacy_windows=False)
47
66
 
@@ -68,22 +87,24 @@ __all__ = [
68
87
  "banner",
69
88
  "clear",
70
89
  "console",
71
- "info",
72
90
  "line",
73
91
  "muted",
74
92
  "print",
75
93
  "spinner",
76
- "success",
77
94
  "sync_print",
78
95
  "update_available",
79
96
  "usage",
80
97
  "version",
98
+ # Unified logging wrappers
99
+ "info",
81
100
  "warning",
101
+ "error",
102
+ "debug",
103
+ "success",
82
104
  # From panels module
83
105
  "agent",
84
106
  "agent_streaming",
85
107
  "dump_messages",
86
- "error",
87
108
  "help",
88
109
  "models",
89
110
  "panel",
tunacode/ui/input.py CHANGED
@@ -75,12 +75,13 @@ async def multiline_input(
75
75
  state_manager: Optional[StateManager] = None, command_registry=None
76
76
  ) -> str:
77
77
  """Get multiline input from the user with @file completion and highlighting."""
78
- kb = create_key_bindings()
78
+ kb = create_key_bindings(state_manager)
79
79
  placeholder = formatted_text(
80
80
  (
81
81
  "<darkgrey>"
82
82
  "<bold>Enter</bold> to submit • "
83
83
  "<bold>Esc + Enter</bold> for new line • "
84
+ "<bold>Esc twice</bold> to cancel • "
84
85
  "<bold>/help</bold> for commands"
85
86
  "</darkgrey>"
86
87
  )
@@ -1,9 +1,17 @@
1
1
  """Key binding handlers for TunaCode UI."""
2
2
 
3
+ import logging
4
+ import time
5
+
6
+ from prompt_toolkit.application import run_in_terminal
3
7
  from prompt_toolkit.key_binding import KeyBindings
4
8
 
9
+ from ..core.state import StateManager
10
+
11
+ logger = logging.getLogger(__name__)
5
12
 
6
- def create_key_bindings() -> KeyBindings:
13
+
14
+ def create_key_bindings(state_manager: StateManager = None) -> KeyBindings:
7
15
  """Create and configure key bindings for the UI."""
8
16
  kb = KeyBindings()
9
17
 
@@ -22,4 +30,53 @@ def create_key_bindings() -> KeyBindings:
22
30
  """Insert a newline when escape then enter is pressed."""
23
31
  event.current_buffer.insert_text("\n")
24
32
 
33
+ @kb.add("escape")
34
+ def _escape(event):
35
+ """Handle ESC key with double-press logic: first press warns, second cancels."""
36
+ if not state_manager:
37
+ logger.debug("Escape key pressed without state manager")
38
+ return
39
+
40
+ current_time = time.time()
41
+ session = state_manager.session
42
+
43
+ # Reset counter if too much time has passed (3 seconds timeout)
44
+ if session.last_esc_time and (current_time - session.last_esc_time) > 3.0:
45
+ session.esc_press_count = 0
46
+
47
+ session.esc_press_count += 1
48
+ session.last_esc_time = current_time
49
+
50
+ logger.debug(f"ESC key pressed: count={session.esc_press_count}, time={current_time}")
51
+
52
+ if session.esc_press_count == 1:
53
+ # First ESC press - show warning message
54
+ from ..ui.output import warning
55
+
56
+ run_in_terminal(lambda: warning("Hit ESC again within 3 seconds to cancel operation"))
57
+ logger.debug("First ESC press - showing warning")
58
+ else:
59
+ # Second ESC press - cancel operation
60
+ session.esc_press_count = 0 # Reset counter
61
+ logger.debug("Second ESC press - initiating cancellation")
62
+
63
+ # Mark the session as being cancelled to prevent new operations
64
+ session.operation_cancelled = True
65
+
66
+ current_task = session.current_task
67
+ if current_task and not current_task.done():
68
+ logger.debug(f"Cancelling current task: {current_task}")
69
+ try:
70
+ current_task.cancel()
71
+ logger.debug("Task cancellation initiated successfully")
72
+ except Exception as e:
73
+ logger.debug(f"Failed to cancel task: {e}")
74
+ else:
75
+ logger.debug(f"No active task to cancel: current_task={current_task}")
76
+
77
+ # Force exit the current input by raising KeyboardInterrupt
78
+ # This will be caught by the prompt manager and converted to UserAbortError
79
+ logger.debug("Raising KeyboardInterrupt to abort current operation")
80
+ raise KeyboardInterrupt()
81
+
25
82
  return kb
@@ -0,0 +1,44 @@
1
+ """
2
+ UILogger compatibility layer for unified logging.
3
+
4
+ Implements the UILogger protocol using the unified logging system,
5
+ preserving UI formatting and behavior for all log levels.
6
+ """
7
+
8
+ from tunacode.core.logging.logger import get_logger
9
+ from tunacode.types import UILogger
10
+
11
+
12
+ class UnifiedUILogger(UILogger):
13
+ """
14
+ UILogger implementation that routes all UI log calls through the unified logging system.
15
+ Preserves UI conventions for info, error, warning, debug, and success.
16
+ """
17
+
18
+ def __init__(self, name: str = "ui"):
19
+ self.logger = get_logger(name)
20
+
21
+ async def info(self, message: str) -> None:
22
+ # Standard info log
23
+ self.logger.info(message)
24
+
25
+ async def error(self, message: str) -> None:
26
+ # Standard error log
27
+ self.logger.error(message)
28
+
29
+ async def warning(self, message: str) -> None:
30
+ # Standard warning log
31
+ self.logger.warning(message)
32
+
33
+ async def debug(self, message: str) -> None:
34
+ # Standard debug log
35
+ self.logger.debug(message)
36
+
37
+ async def success(self, message: str) -> None:
38
+ # "Success" is a UI convention; log as info with a marker for UI formatting
39
+ # Add a special prefix or extra field for downstream handlers/formatters
40
+ self.logger.info(f"[SUCCESS] {message}", extra={"ui_success": True})
41
+
42
+
43
+ # Singleton instance for convenience
44
+ ui_logger: UILogger = UnifiedUILogger()
tunacode/ui/output.py CHANGED
@@ -18,6 +18,7 @@ from tunacode.utils.token_counter import format_token_count
18
18
 
19
19
  from .constants import SPINNER_TYPE
20
20
  from .decorators import create_sync_wrapper
21
+ from .logging_compat import ui_logger
21
22
 
22
23
  # Create console with explicit settings to ensure ANSI codes work properly
23
24
  console = Console(force_terminal=True, legacy_windows=False)
@@ -54,18 +55,18 @@ async def line() -> None:
54
55
 
55
56
 
56
57
  async def info(text: str) -> None:
57
- """Print an informational message."""
58
- await print(f"[{colors.primary}]●[/{colors.primary}] {text}", style=colors.muted)
58
+ """Unified logging: informational message."""
59
+ await ui_logger.info(text)
59
60
 
60
61
 
61
62
  async def success(message: str) -> None:
62
- """Print a success message."""
63
- await print(f"[{colors.success}]✓[/{colors.success}] {message}")
63
+ """Unified logging: success message."""
64
+ await ui_logger.success(message)
64
65
 
65
66
 
66
67
  async def warning(text: str) -> None:
67
- """Print a warning message."""
68
- await print(f"[{colors.warning}]⚠[/{colors.warning}] {text}")
68
+ """Unified logging: warning message."""
69
+ await ui_logger.warning(text)
69
70
 
70
71
 
71
72
  async def muted(text: str) -> None:
tunacode/ui/panels.py CHANGED
@@ -30,7 +30,6 @@ from tunacode.constants import (
30
30
  DESC_MODEL_SWITCH,
31
31
  DESC_YOLO,
32
32
  PANEL_AVAILABLE_COMMANDS,
33
- PANEL_ERROR,
34
33
  PANEL_MESSAGE_HISTORY,
35
34
  PANEL_MODELS,
36
35
  UI_COLORS,
@@ -87,11 +86,17 @@ class StreamingAgentPanel:
87
86
  def _create_panel(self) -> Panel:
88
87
  """Create a Rich panel with current content."""
89
88
  # Use the UI_THINKING_MESSAGE constant instead of hardcoded text
89
+ from rich.text import Text
90
+
90
91
  from tunacode.constants import UI_THINKING_MESSAGE
91
92
 
92
- markdown_content = Markdown(self.content or UI_THINKING_MESSAGE)
93
+ # Handle the default thinking message with Rich markup
94
+ if not self.content:
95
+ content_renderable = Text.from_markup(UI_THINKING_MESSAGE)
96
+ else:
97
+ content_renderable = Markdown(self.content)
93
98
  panel_obj = Panel(
94
- Padding(markdown_content, (0, 1, 0, 1)),
99
+ Padding(content_renderable, (0, 1, 0, 1)),
95
100
  title=f"[bold]{self.title}[/bold]",
96
101
  title_align="left",
97
102
  border_style=colors.primary,
@@ -130,7 +135,25 @@ class StreamingAgentPanel:
130
135
  async def stop(self):
131
136
  """Stop the live streaming display."""
132
137
  if self.live:
138
+ # Get the console before stopping the live display
139
+ from .output import console
140
+
141
+ # Stop the live display
133
142
  self.live.stop()
143
+
144
+ # Comprehensive cleanup to prevent extra lines
145
+ console.print("", end="") # Reset the current line without newline
146
+ if hasattr(console, "file") and hasattr(console.file, "flush"):
147
+ console.file.flush() # Ensure output is flushed
148
+
149
+ # Mark that we just finished streaming (for output control)
150
+ try:
151
+ from tunacode.core.logging.handlers import _streaming_context
152
+
153
+ _streaming_context["just_finished"] = True
154
+ except ImportError:
155
+ pass # If we can't import, continue without the optimization
156
+
134
157
  self.live = None
135
158
 
136
159
 
@@ -152,8 +175,10 @@ async def agent_streaming(content_stream, bottom: int = 1):
152
175
 
153
176
 
154
177
  async def error(text: str) -> None:
155
- """Display an error panel."""
156
- await panel(PANEL_ERROR, text, style=colors.error)
178
+ """Unified logging: error message."""
179
+ from .logging_compat import ui_logger
180
+
181
+ await ui_logger.error(text)
157
182
 
158
183
 
159
184
  async def dump_messages(messages_list=None, state_manager: StateManager = None) -> None: