tunacode-cli 0.0.48__py3-none-any.whl → 0.0.49__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 +28 -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 +1 -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.49.dist-info}/METADATA +2 -2
  37. {tunacode_cli-0.0.48.dist-info → tunacode_cli-0.0.49.dist-info}/RECORD +41 -29
  38. {tunacode_cli-0.0.48.dist-info → tunacode_cli-0.0.49.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.49.dist-info}/WHEEL +0 -0
  44. {tunacode_cli-0.0.48.dist-info → tunacode_cli-0.0.49.dist-info}/entry_points.txt +0 -0
  45. {tunacode_cli-0.0.48.dist-info → tunacode_cli-0.0.49.dist-info}/licenses/LICENSE +0 -0
api/auth.py ADDED
@@ -0,0 +1,13 @@
1
+ import jwt
2
+
3
+
4
+ def authenticate(username, password):
5
+ # TODO: Add password hashing
6
+ if username == "admin" and password == "admin":
7
+ return generate_token(username)
8
+ return None
9
+
10
+
11
+ def generate_token(username):
12
+ # TODO: Add expiration
13
+ return jwt.encode({"user": username}, "secret")
api/users.py ADDED
@@ -0,0 +1,8 @@
1
+ from .auth import authenticate
2
+
3
+
4
+ class UserManager:
5
+ def login(self, username, password):
6
+ token = authenticate(username, password)
7
+ # TODO: Store session
8
+ return token
tunacode/__init__.py CHANGED
@@ -0,0 +1,4 @@
1
+ # Initialize unified logging system on package import
2
+ from tunacode.core.logging import setup_logging
3
+
4
+ setup_logging()
tunacode/cli/main.py CHANGED
@@ -5,6 +5,7 @@ Enhanced CLI entry point with better styling while staying CLI-based.
5
5
  """
6
6
 
7
7
  import asyncio
8
+ import logging
8
9
 
9
10
  import typer
10
11
 
@@ -36,6 +37,9 @@ def main(
36
37
  ):
37
38
  """Start TunaCode - Your AI-powered development assistant"""
38
39
 
40
+ # Configure logging to suppress INFO messages by default
41
+ logging.basicConfig(level=logging.WARNING, force=True)
42
+
39
43
  async def async_main():
40
44
  if version:
41
45
  await ui.version()
tunacode/cli/repl.py CHANGED
@@ -90,6 +90,12 @@ def _parse_args(args) -> ToolArgs:
90
90
 
91
91
  async def _tool_handler(part, state_manager: StateManager):
92
92
  """Handle tool execution with separated business logic and UI."""
93
+ # Check for cancellation before tool execution (only if explicitly set to True)
94
+ operation_cancelled = getattr(state_manager.session, "operation_cancelled", False)
95
+ if operation_cancelled is True:
96
+ logger.debug("Tool execution cancelled")
97
+ raise CancelledError("Operation was cancelled")
98
+
93
99
  tool_handler = ToolHandler(state_manager)
94
100
 
95
101
  if tool_handler.should_confirm(part.tool_name):
@@ -249,6 +255,12 @@ async def _display_agent_output(res, enable_streaming: bool) -> None:
249
255
  async def process_request(text: str, state_manager: StateManager, output: bool = True):
250
256
  """Process input using the agent, handling cancellation safely."""
251
257
 
258
+ # Check for cancellation before starting (only if explicitly set to True)
259
+ operation_cancelled = getattr(state_manager.session, "operation_cancelled", False)
260
+ if operation_cancelled is True:
261
+ logger.debug("Operation cancelled before processing started")
262
+ raise CancelledError("Operation was cancelled")
263
+
252
264
  state_manager.session.spinner = await ui.spinner(
253
265
  True, state_manager.session.spinner, state_manager
254
266
  )
@@ -275,6 +287,12 @@ async def process_request(text: str, state_manager: StateManager, output: bool =
275
287
  await ui.error(str(e))
276
288
  return
277
289
 
290
+ # Check for cancellation before proceeding with agent call (only if explicitly set to True)
291
+ operation_cancelled = getattr(state_manager.session, "operation_cancelled", False)
292
+ if operation_cancelled is True:
293
+ logger.debug("Operation cancelled before agent processing")
294
+ raise CancelledError("Operation was cancelled")
295
+
278
296
  enable_streaming = state_manager.session.user_config.get("settings", {}).get(
279
297
  "enable_streaming", True
280
298
  )
@@ -338,7 +356,7 @@ async def process_request(text: str, state_manager: StateManager, output: bool =
338
356
  # Always show files in context after agent response
339
357
  if state_manager.session.files_in_context:
340
358
  filenames = [Path(f).name for f in sorted(state_manager.session.files_in_context)]
341
- await ui.muted(f"\nFiles in context: {', '.join(filenames)}")
359
+ await ui.muted(f"Files in context: {', '.join(filenames)}")
342
360
 
343
361
  # --- ERROR HANDLING ---
344
362
  except CancelledError:
@@ -360,6 +378,9 @@ async def process_request(text: str, state_manager: StateManager, output: bool =
360
378
  finally:
361
379
  await ui.spinner(False, state_manager.session.spinner, state_manager)
362
380
  state_manager.session.current_task = None
381
+ # Reset cancellation flag when task completes (if attribute exists)
382
+ if hasattr(state_manager.session, "operation_cancelled"):
383
+ state_manager.session.operation_cancelled = False
363
384
 
364
385
  if "multiline" in state_manager.session.input_sessions:
365
386
  await run_in_terminal(
@@ -385,9 +406,13 @@ async def repl(state_manager: StateManager):
385
406
 
386
407
  state_manager.session.update_token_count()
387
408
  context_display = get_context_window_display(state_manager.session.total_tokens, max_tokens)
388
- await ui.muted(f"• Model: {model_name} • {context_display}")
389
- await ui.success("Ready to assist")
390
- await ui.line()
409
+
410
+ # Only show startup info if thoughts are enabled or on first run
411
+ if state_manager.session.show_thoughts or not hasattr(state_manager.session, "_startup_shown"):
412
+ await ui.muted(f"• Model: {model_name} • {context_display}")
413
+ await ui.success("Ready to assist")
414
+ await ui.line()
415
+ state_manager.session._startup_shown = True
391
416
 
392
417
  instance = agent.get_or_create_agent(state_manager.session.current_model, state_manager)
393
418
 
@@ -456,6 +481,10 @@ async def repl(state_manager: StateManager):
456
481
  await ui.muted(MSG_AGENT_BUSY)
457
482
  continue
458
483
 
484
+ # Reset cancellation flag for new operations (if attribute exists)
485
+ if hasattr(state_manager.session, "operation_cancelled"):
486
+ state_manager.session.operation_cancelled = False
487
+
459
488
  state_manager.session.current_task = get_app().create_background_task(
460
489
  process_request(line, state_manager)
461
490
  )
@@ -465,7 +494,11 @@ async def repl(state_manager: StateManager):
465
494
  context_display = get_context_window_display(
466
495
  state_manager.session.total_tokens, state_manager.session.max_tokens
467
496
  )
468
- await ui.muted(f"• Model: {state_manager.session.current_model} {context_display}")
497
+ # Only show model/context info if thoughts are enabled
498
+ if state_manager.session.show_thoughts:
499
+ await ui.muted(
500
+ f"• Model: {state_manager.session.current_model} • {context_display}"
501
+ )
469
502
 
470
503
  if action == "restart":
471
504
  await repl(state_manager)
@@ -480,7 +513,7 @@ async def repl(state_manager: StateManager):
480
513
  total_cost = float(session_total.get("cost", 0) or 0)
481
514
 
482
515
  # Only show summary if we have actual token usage
483
- if total_tokens > 0 or total_cost > 0:
516
+ if state_manager.session.show_thoughts and (total_tokens > 0 or total_cost > 0):
484
517
  summary = (
485
518
  f"\n[bold cyan]TunaCode Session Summary[/bold cyan]\n"
486
519
  f" - Total Tokens: {total_tokens:,}\n"
@@ -24,7 +24,6 @@ DEFAULT_USER_CONFIG: UserConfig = {
24
24
  "fallback_response": True,
25
25
  "fallback_verbosity": "normal", # Options: minimal, normal, detailed
26
26
  "context_window_size": 200000,
27
- "use_dspy_optimization": True, # Enable DSPy tool selection optimization
28
27
  },
29
28
  "mcpServers": {},
30
29
  }
tunacode/constants.py CHANGED
@@ -7,7 +7,8 @@ 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.48"
10
+ APP_VERSION = "0.0.49"
11
+
11
12
 
12
13
  # File patterns
13
14
  GUIDE_FILE_PATTERN = "{name}.md"
@@ -157,3 +158,8 @@ MAX_TODOS_PER_SESSION = 100
157
158
 
158
159
  # Maximum length for todo content
159
160
  MAX_TODO_CONTENT_LENGTH = 500
161
+
162
+ # JSON parsing retry configuration
163
+ JSON_PARSE_MAX_RETRIES = 10
164
+ JSON_PARSE_BASE_DELAY = 0.1 # Initial delay in seconds
165
+ JSON_PARSE_MAX_DELAY = 5.0 # Maximum delay in seconds