tunacode-cli 0.0.47__tar.gz → 0.0.49__tar.gz

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 (241) hide show
  1. {tunacode_cli-0.0.47/src/tunacode_cli.egg-info → tunacode_cli-0.0.49}/PKG-INFO +2 -2
  2. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/README.md +1 -1
  3. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/pyproject.toml +34 -1
  4. tunacode_cli-0.0.49/src/api/auth.py +13 -0
  5. tunacode_cli-0.0.49/src/api/users.py +8 -0
  6. tunacode_cli-0.0.49/src/tunacode/__init__.py +4 -0
  7. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/cli/repl.py +28 -2
  8. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/constants.py +2 -1
  9. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/core/agents/main.py +33 -84
  10. tunacode_cli-0.0.49/src/tunacode/core/logging/__init__.py +29 -0
  11. tunacode_cli-0.0.49/src/tunacode/core/logging/config.py +28 -0
  12. tunacode_cli-0.0.49/src/tunacode/core/logging/formatters.py +48 -0
  13. tunacode_cli-0.0.49/src/tunacode/core/logging/handlers.py +83 -0
  14. tunacode_cli-0.0.49/src/tunacode/core/logging/logger.py +8 -0
  15. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/core/recursive/hierarchy.py +2 -1
  16. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/core/state.py +4 -0
  17. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/tools/base.py +7 -1
  18. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/types.py +1 -1
  19. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/ui/completers.py +2 -2
  20. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/ui/console.py +30 -9
  21. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/ui/input.py +1 -1
  22. tunacode_cli-0.0.49/src/tunacode/ui/keybindings.py +82 -0
  23. tunacode_cli-0.0.49/src/tunacode/ui/logging_compat.py +44 -0
  24. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/ui/output.py +7 -6
  25. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/ui/panels.py +28 -10
  26. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/utils/security.py +3 -2
  27. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49/src/tunacode_cli.egg-info}/PKG-INFO +2 -2
  28. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode_cli.egg-info/SOURCES.txt +8 -60
  29. tunacode_cli-0.0.49/src/tunacode_cli.egg-info/top_level.txt +3 -0
  30. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/context/test_tunacode_logging.py +10 -15
  31. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/repl/test_error_handling.py +12 -4
  32. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/repl/test_input_handling.py +6 -4
  33. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/repl/test_multiline_input.py +11 -4
  34. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/repl/test_output_display_logic.py +1 -1
  35. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/ui/test_async_ui.py +4 -4
  36. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/conftest.py +4 -2
  37. tunacode_cli-0.0.47/src/tunacode/ui/keybindings.py +0 -41
  38. tunacode_cli-0.0.47/src/tunacode/utils/__init__.py +0 -0
  39. tunacode_cli-0.0.47/src/tunacode_cli.egg-info/top_level.txt +0 -1
  40. tunacode_cli-0.0.47/tests/crud/test_core_file_operations.py +0 -452
  41. tunacode_cli-0.0.47/tests/fixtures/__init__.py +0 -1
  42. tunacode_cli-0.0.47/tests/fixtures/file_operations.py +0 -332
  43. tunacode_cli-0.0.47/tests/integration/test_error_recovery_flow.py +0 -43
  44. tunacode_cli-0.0.47/tests/integration/test_full_session_flow.py +0 -68
  45. tunacode_cli-0.0.47/tests/integration/test_mcp_tool_flow.py +0 -51
  46. tunacode_cli-0.0.47/tests/integration/test_multi_tool_operations.py +0 -61
  47. tunacode_cli-0.0.47/tests/integration/test_performance_scenarios.py +0 -62
  48. tunacode_cli-0.0.47/tests/integration/test_usage_tracking_integration.py +0 -134
  49. tunacode_cli-0.0.47/tests/test_actual_parallelism.py +0 -260
  50. tunacode_cli-0.0.47/tests/test_agent_initialization.py +0 -152
  51. tunacode_cli-0.0.47/tests/test_api_response_parser.py +0 -82
  52. tunacode_cli-0.0.47/tests/test_background_manager.py +0 -27
  53. tunacode_cli-0.0.47/tests/test_characterization_agent_main.py +0 -57
  54. tunacode_cli-0.0.47/tests/test_characterization_bash.py +0 -227
  55. tunacode_cli-0.0.47/tests/test_characterization_commands_system.py +0 -90
  56. tunacode_cli-0.0.47/tests/test_characterization_glob.py +0 -395
  57. tunacode_cli-0.0.47/tests/test_characterization_grep.py +0 -329
  58. tunacode_cli-0.0.47/tests/test_characterization_grep_performance.py +0 -296
  59. tunacode_cli-0.0.47/tests/test_characterization_iteration_limits.py +0 -141
  60. tunacode_cli-0.0.47/tests/test_characterization_list_dir.py +0 -289
  61. tunacode_cli-0.0.47/tests/test_characterization_read_file.py +0 -201
  62. tunacode_cli-0.0.47/tests/test_characterization_repl_utils.py +0 -41
  63. tunacode_cli-0.0.47/tests/test_characterization_run_command.py +0 -241
  64. tunacode_cli-0.0.47/tests/test_characterization_setup_system.py +0 -76
  65. tunacode_cli-0.0.47/tests/test_characterization_tool_ui_behavior.py +0 -381
  66. tunacode_cli-0.0.47/tests/test_characterization_update_file.py +0 -231
  67. tunacode_cli-0.0.47/tests/test_characterization_utilities.py +0 -64
  68. tunacode_cli-0.0.47/tests/test_characterization_write_file.py +0 -224
  69. tunacode_cli-0.0.47/tests/test_cli_command_flow.py +0 -312
  70. tunacode_cli-0.0.47/tests/test_cli_file_operations_integration.py +0 -283
  71. tunacode_cli-0.0.47/tests/test_config_directory_creation.py +0 -105
  72. tunacode_cli-0.0.47/tests/test_config_setup_async.py +0 -120
  73. tunacode_cli-0.0.47/tests/test_cost_calculator.py +0 -32
  74. tunacode_cli-0.0.47/tests/test_enhanced_visual_feedback.py +0 -95
  75. tunacode_cli-0.0.47/tests/test_fallback_responses.py +0 -48
  76. tunacode_cli-0.0.47/tests/test_fast_glob_search.py +0 -129
  77. tunacode_cli-0.0.47/tests/test_file_operations_edge_cases.py +0 -393
  78. tunacode_cli-0.0.47/tests/test_file_operations_stress.py +0 -333
  79. tunacode_cli-0.0.47/tests/test_file_reference_context_tracking.py +0 -146
  80. tunacode_cli-0.0.47/tests/test_file_reference_expansion.py +0 -208
  81. tunacode_cli-0.0.47/tests/test_grep_fast_glob.py +0 -380
  82. tunacode_cli-0.0.47/tests/test_grep_legacy_compat.py +0 -61
  83. tunacode_cli-0.0.47/tests/test_grep_timeout.py +0 -180
  84. tunacode_cli-0.0.47/tests/test_json_tool_parsing.py +0 -214
  85. tunacode_cli-0.0.47/tests/test_list_dir.py +0 -188
  86. tunacode_cli-0.0.47/tests/test_parallel_execution_demo.py +0 -151
  87. tunacode_cli-0.0.47/tests/test_parallel_execution_freeze_fix.py +0 -153
  88. tunacode_cli-0.0.47/tests/test_parallel_execution_integration.py +0 -202
  89. tunacode_cli-0.0.47/tests/test_parallel_read_only_tools.py +0 -248
  90. tunacode_cli-0.0.47/tests/test_parallel_tool_execution.py +0 -151
  91. tunacode_cli-0.0.47/tests/test_read_only_confirmation.py +0 -67
  92. tunacode_cli-0.0.47/tests/test_streaming_panel_tool_confirmation.py +0 -176
  93. tunacode_cli-0.0.47/tests/test_streaming_spinner_conflict.py +0 -117
  94. tunacode_cli-0.0.47/tests/test_todo_functionality.py +0 -441
  95. tunacode_cli-0.0.47/tests/test_tool_categorization.py +0 -110
  96. tunacode_cli-0.0.47/tests/test_tool_combinations.py +0 -575
  97. tunacode_cli-0.0.47/tests/test_tool_handler_ui_messages.py +0 -113
  98. tunacode_cli-0.0.47/tests/test_update_command.py +0 -48
  99. tunacode_cli-0.0.47/tests/test_visual_parallel_feedback.py +0 -172
  100. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/CLAUDE.md +0 -0
  101. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/LICENSE +0 -0
  102. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/MANIFEST.in +0 -0
  103. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/TUNACODE.md +0 -0
  104. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/setup.cfg +0 -0
  105. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/setup.py +0 -0
  106. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/cli/__init__.py +0 -0
  107. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/cli/commands/__init__.py +0 -0
  108. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/cli/commands/base.py +0 -0
  109. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/cli/commands/implementations/__init__.py +0 -0
  110. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/cli/commands/implementations/conversation.py +0 -0
  111. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/cli/commands/implementations/debug.py +0 -0
  112. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/cli/commands/implementations/development.py +0 -0
  113. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/cli/commands/implementations/model.py +0 -0
  114. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/cli/commands/implementations/system.py +0 -0
  115. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/cli/commands/implementations/todo.py +0 -0
  116. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/cli/commands/registry.py +0 -0
  117. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/cli/main.py +0 -0
  118. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/configuration/__init__.py +0 -0
  119. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/configuration/defaults.py +0 -0
  120. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/configuration/models.py +0 -0
  121. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/configuration/settings.py +0 -0
  122. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/context.py +0 -0
  123. {tunacode_cli-0.0.47/src/tunacode → tunacode_cli-0.0.49/src/tunacode/core}/__init__.py +0 -0
  124. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/core/agents/__init__.py +0 -0
  125. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/core/agents/utils.py +0 -0
  126. {tunacode_cli-0.0.47/src/tunacode/core → tunacode_cli-0.0.49/src/tunacode/core/background}/__init__.py +0 -0
  127. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/core/background/manager.py +0 -0
  128. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/core/code_index.py +0 -0
  129. {tunacode_cli-0.0.47/src/tunacode/core/background → tunacode_cli-0.0.49/src/tunacode/core/llm}/__init__.py +0 -0
  130. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/core/recursive/__init__.py +0 -0
  131. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/core/recursive/aggregator.py +0 -0
  132. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/core/recursive/budget.py +0 -0
  133. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/core/recursive/decomposer.py +0 -0
  134. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/core/recursive/executor.py +0 -0
  135. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/core/setup/__init__.py +0 -0
  136. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/core/setup/agent_setup.py +0 -0
  137. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/core/setup/base.py +0 -0
  138. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/core/setup/config_setup.py +0 -0
  139. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/core/setup/coordinator.py +0 -0
  140. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/core/setup/environment_setup.py +0 -0
  141. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/core/setup/git_safety_setup.py +0 -0
  142. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/core/token_usage/api_response_parser.py +0 -0
  143. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/core/token_usage/cost_calculator.py +0 -0
  144. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/core/token_usage/usage_tracker.py +0 -0
  145. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/core/tool_handler.py +0 -0
  146. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/exceptions.py +0 -0
  147. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/prompts/system.md +0 -0
  148. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/py.typed +0 -0
  149. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/services/__init__.py +0 -0
  150. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/services/mcp.py +0 -0
  151. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/setup.py +0 -0
  152. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/tools/__init__.py +0 -0
  153. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/tools/bash.py +0 -0
  154. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/tools/glob.py +0 -0
  155. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/tools/grep.py +0 -0
  156. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/tools/list_dir.py +0 -0
  157. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/tools/read_file.py +0 -0
  158. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/tools/read_file_async_poc.py +0 -0
  159. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/tools/run_command.py +0 -0
  160. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/tools/todo.py +0 -0
  161. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/tools/update_file.py +0 -0
  162. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/tools/write_file.py +0 -0
  163. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/ui/__init__.py +0 -0
  164. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/ui/constants.py +0 -0
  165. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/ui/decorators.py +0 -0
  166. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/ui/lexers.py +0 -0
  167. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/ui/prompt_manager.py +0 -0
  168. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/ui/recursive_progress.py +0 -0
  169. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/ui/tool_ui.py +0 -0
  170. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/ui/utils.py +0 -0
  171. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/ui/validators.py +0 -0
  172. {tunacode_cli-0.0.47/src/tunacode/core/llm → tunacode_cli-0.0.49/src/tunacode/utils}/__init__.py +0 -0
  173. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/utils/bm25.py +0 -0
  174. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/utils/diff_utils.py +0 -0
  175. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/utils/file_utils.py +0 -0
  176. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/utils/import_cache.py +0 -0
  177. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/utils/message_utils.py +0 -0
  178. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/utils/retry.py +0 -0
  179. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/utils/ripgrep.py +0 -0
  180. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/utils/system.py +0 -0
  181. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/utils/text_utils.py +0 -0
  182. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/utils/token_counter.py +0 -0
  183. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode/utils/user_configuration.py +0 -0
  184. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode_cli.egg-info/dependency_links.txt +0 -0
  185. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode_cli.egg-info/entry_points.txt +0 -0
  186. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/src/tunacode_cli.egg-info/requires.txt +0 -0
  187. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/agent/__init__.py +0 -0
  188. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/agent/conftest.py +0 -0
  189. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/agent/test_agent_creation.py +0 -0
  190. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/agent/test_json_tool_parsing.py +0 -0
  191. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/agent/test_process_node.py +0 -0
  192. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/agent/test_process_request.py +0 -0
  193. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/agent/test_tool_message_patching.py +0 -0
  194. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/background/test_background_edge_cases.py +0 -0
  195. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/background/test_cleanup.py +0 -0
  196. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/background/test_task_cancellation.py +0 -0
  197. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/background/test_task_creation.py +0 -0
  198. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/background/test_task_execution.py +0 -0
  199. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/code_index/test_cache_management.py +0 -0
  200. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/code_index/test_file_scanning.py +0 -0
  201. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/code_index/test_index_building.py +0 -0
  202. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/code_index/test_search_operations.py +0 -0
  203. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/code_index/test_symbol_extraction.py +0 -0
  204. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/commands/__init__.py +0 -0
  205. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/commands/test_init_command.py +0 -0
  206. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/conftest.py +0 -0
  207. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/context/__init__.py +0 -0
  208. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/context/test_context_acceptance.py +0 -0
  209. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/context/test_context_integration.py +0 -0
  210. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/context/test_context_loading.py +0 -0
  211. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/repl/test_command_parsing.py +0 -0
  212. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/repl/test_keyboard_interrupts.py +0 -0
  213. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/repl/test_repl_initialization.py +0 -0
  214. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/repl/test_session_flow.py +0 -0
  215. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/services/test_error_recovery.py +0 -0
  216. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/services/test_llm_routing.py +0 -0
  217. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/services/test_mcp_integration.py +0 -0
  218. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/services/test_service_lifecycle.py +0 -0
  219. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/state/test_agent_tracking.py +0 -0
  220. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/state/test_message_history.py +0 -0
  221. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/state/test_permissions.py +0 -0
  222. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/state/test_session_management.py +0 -0
  223. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/state/test_state_initialization.py +0 -0
  224. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/state/test_user_config.py +0 -0
  225. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/test_characterization_commands.py +0 -0
  226. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/ui/test_console_output.py +0 -0
  227. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/ui/test_diff_display.py +0 -0
  228. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/ui/test_prompt_rendering.py +0 -0
  229. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/ui/test_tool_confirmations.py +0 -0
  230. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/utils/conftest.py +0 -0
  231. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/utils/test_expand_file_refs.py +0 -0
  232. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/utils/test_file_operations.py +0 -0
  233. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/utils/test_git_commands.py +0 -0
  234. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/utils/test_token_counting.py +0 -0
  235. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/characterization/utils/test_utils_edge_cases.py +0 -0
  236. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/test_agent_output_formatting.py +0 -0
  237. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/test_json_retry.py +0 -0
  238. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/test_prompt_changes_validation.py +0 -0
  239. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/test_security.py +0 -0
  240. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/test_tool_batching_retry.py +0 -0
  241. {tunacode_cli-0.0.47 → tunacode_cli-0.0.49}/tests/unit/test_recursive_executor.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tunacode-cli
3
- Version: 0.0.47
3
+ Version: 0.0.49
4
4
  Summary: Your agentic CLI developer.
5
5
  Author-email: larock22 <noreply@github.com>
6
6
  License: MIT
@@ -37,7 +37,7 @@ Requires-Dist: textual-dev; extra == "dev"
37
37
  Requires-Dist: pre-commit; extra == "dev"
38
38
  Dynamic: license-file
39
39
 
40
- # TunaCode
40
+ # TunaCode CLI
41
41
 
42
42
  <div align="center">
43
43
 
@@ -1,4 +1,4 @@
1
- # TunaCode
1
+ # TunaCode CLI
2
2
 
3
3
  <div align="center">
4
4
 
@@ -4,7 +4,8 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "tunacode-cli"
7
- version = "0.0.47"
7
+
8
+ version = "0.0.49"
8
9
  description = "Your agentic CLI developer."
9
10
  keywords = ["cli", "agent", "development", "automation"]
10
11
  readme = "README.md"
@@ -77,3 +78,35 @@ exclude = [
77
78
  "dist",
78
79
  "venv",
79
80
  ]
81
+
82
+ [tool.vulture]
83
+ min_confidence = 80
84
+ paths = ["src", "tests"]
85
+ exclude = ["venv/", "build/", "dist/", ".git/", "__pycache__/", "*.egg-info/"]
86
+ # Whitelist of false positives
87
+ ignore_names = [
88
+ # TYPE_CHECKING imports
89
+ "ReadStream",
90
+ "WriteStream",
91
+ "StateManager",
92
+ "ModelRequest",
93
+ "CommandRegistry",
94
+ # Pytest fixtures
95
+ "caplog",
96
+ "temp_workspace",
97
+ "setup_test_environment",
98
+ "excinfo",
99
+ # Common patterns
100
+ "_complete_event", # prompt_toolkit API requirement
101
+ "kw", # **kw pattern
102
+ "kwargs", # **kwargs pattern
103
+ "message", # exception handling
104
+ "response_obj", # API responses
105
+ # Test parameters and mock arguments
106
+ "should_restart", # test parameter
107
+ "exc", # exception handling in __aexit__
108
+ "exc_type", # exception handling in __aexit__
109
+ "tb", # traceback in __aexit__
110
+ "style_dict", # Style.from_dict parameter
111
+ "style_str", # get_attrs_for_style_str parameter
112
+ ]
@@ -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")
@@ -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
@@ -0,0 +1,4 @@
1
+ # Initialize unified logging system on package import
2
+ from tunacode.core.logging import setup_logging
3
+
4
+ setup_logging()
@@ -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,13 +356,13 @@ 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:
345
363
  await ui.muted(MSG_REQUEST_CANCELLED)
346
364
  except UserAbortError:
347
- await ui.muted(MSG_OPERATION_ABORTED_BY_USER)
365
+ await ui.muted(MSG_OPERATION_ABORTED)
348
366
  except UnexpectedModelBehavior as e:
349
367
  error_message = str(e)
350
368
  await ui.muted(error_message)
@@ -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(
@@ -460,9 +481,14 @@ async def repl(state_manager: StateManager):
460
481
  await ui.muted(MSG_AGENT_BUSY)
461
482
  continue
462
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
+
463
488
  state_manager.session.current_task = get_app().create_background_task(
464
489
  process_request(line, state_manager)
465
490
  )
491
+ await state_manager.session.current_task
466
492
 
467
493
  state_manager.session.update_token_count()
468
494
  context_display = get_context_window_display(
@@ -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.47"
10
+ APP_VERSION = "0.0.49"
11
+
11
12
 
12
13
  # File patterns
13
14
  GUIDE_FILE_PATTERN = "{name}.md"
@@ -6,7 +6,6 @@ Handles agent creation, configuration, and request processing.
6
6
 
7
7
  import asyncio
8
8
  import json
9
- import logging
10
9
  import os
11
10
  import re
12
11
  from datetime import datetime, timezone
@@ -15,6 +14,8 @@ from typing import Any, Iterator, List, Optional, Tuple
15
14
 
16
15
  from pydantic_ai import Agent
17
16
 
17
+ from tunacode.core.logging.logger import get_logger
18
+
18
19
  # Import streaming types with fallback for older versions
19
20
  try:
20
21
  from pydantic_ai.messages import (
@@ -30,7 +31,6 @@ except ImportError:
30
31
  STREAMING_AVAILABLE = False
31
32
 
32
33
  from tunacode.constants import READ_ONLY_TOOLS
33
- from tunacode.core.recursive import RecursiveTaskExecutor
34
34
  from tunacode.core.state import StateManager
35
35
  from tunacode.core.token_usage.api_response_parser import ApiResponseParser
36
36
  from tunacode.core.token_usage.cost_calculator import CostCalculator
@@ -60,7 +60,7 @@ from tunacode.types import (
60
60
  )
61
61
 
62
62
  # Configure logging
63
- logger = logging.getLogger(__name__)
63
+ logger = get_logger(__name__)
64
64
 
65
65
 
66
66
  class ToolBuffer:
@@ -528,12 +528,12 @@ def get_or_create_agent(model: ModelName, state_manager: StateManager) -> Pydant
528
528
  tunacode_content = tunacode_path.read_text(encoding="utf-8")
529
529
  if tunacode_content.strip():
530
530
  # Log that we found TUNACODE.md
531
- print("📄 TUNACODE.md located: Loading context...")
531
+ logger.info("📄 TUNACODE.md located: Loading context...")
532
532
 
533
533
  system_prompt += "\n\n# Project Context from TUNACODE.md\n" + tunacode_content
534
534
  else:
535
535
  # Log that TUNACODE.md was not found
536
- print("📄 TUNACODE.md not found: Using default context")
536
+ logger.info("📄 TUNACODE.md not found: Using default context")
537
537
  except Exception as e:
538
538
  # Log errors loading TUNACODE.md at debug level
539
539
  logger.debug(f"Error loading TUNACODE.md: {e}")
@@ -547,9 +547,8 @@ def get_or_create_agent(model: ModelName, state_manager: StateManager) -> Pydant
547
547
  system_prompt += f'\n\n# Current Todo List\n\nYou have existing todos that need attention:\n\n{current_todos}\n\nRemember to check progress on these todos and update them as you work. Use todo("list") to see current status anytime.'
548
548
  except Exception as e:
549
549
  # Log error but don't fail agent creation
550
- import sys
551
550
 
552
- print(f"Warning: Failed to load todos: {e}", file=sys.stderr)
551
+ logger.warning(f"Warning: Failed to load todos: {e}")
553
552
 
554
553
  state_manager.session.agents[model] = Agent(
555
554
  model=model,
@@ -763,55 +762,6 @@ async def process_request(
763
762
  "fallback_response", True
764
763
  )
765
764
 
766
- # Check if recursive execution is enabled
767
- use_recursive = state_manager.session.user_config.get("settings", {}).get(
768
- "use_recursive_execution", True
769
- )
770
- recursive_threshold = state_manager.session.user_config.get("settings", {}).get(
771
- "recursive_complexity_threshold", 0.7
772
- )
773
-
774
- # Check if recursive execution should be used
775
- if use_recursive and state_manager.session.current_recursion_depth == 0:
776
- try:
777
- # Initialize recursive executor
778
- recursive_executor = RecursiveTaskExecutor(
779
- state_manager=state_manager,
780
- max_depth=state_manager.session.max_recursion_depth,
781
- min_complexity_threshold=recursive_threshold,
782
- default_iteration_budget=max_iterations,
783
- )
784
-
785
- # Analyze task complexity
786
- complexity_result = await recursive_executor.decomposer.analyze_and_decompose(
787
- message
788
- )
789
-
790
- if (
791
- complexity_result.should_decompose
792
- and complexity_result.total_complexity >= recursive_threshold
793
- ):
794
- if state_manager.session.show_thoughts:
795
- from tunacode.ui import console as ui
796
-
797
- await ui.muted(
798
- f"\n🔄 RECURSIVE EXECUTION: Task complexity {complexity_result.total_complexity:.2f} >= {recursive_threshold}"
799
- )
800
- await ui.muted(f"Reasoning: {complexity_result.reasoning}")
801
- await ui.muted(f"Subtasks: {len(complexity_result.subtasks)}")
802
-
803
- # Execute recursively
804
- success, result, error = await recursive_executor.execute_task(
805
- request=message, parent_task_id=None, depth=0
806
- )
807
-
808
- # For now, fall back to normal execution
809
- # TODO: Properly integrate recursive execution results
810
- pass
811
- except Exception as e:
812
- logger.warning(f"Recursive execution failed, falling back to normal: {e}")
813
- # Continue with normal execution
814
-
815
765
  from tunacode.configuration.models import ModelRegistry
816
766
  from tunacode.core.token_usage.usage_tracker import UsageTracker
817
767
 
@@ -910,28 +860,27 @@ async def process_request(
910
860
  buffered_tasks = tool_buffer.flush()
911
861
  start_time = time.time()
912
862
 
913
- if state_manager.session.show_thoughts:
914
- await ui.muted("\n" + "=" * 60)
915
- await ui.muted(
916
- f"🚀 FINAL BATCH: Executing {len(buffered_tasks)} buffered read-only tools"
917
- )
918
- await ui.muted("=" * 60)
863
+ await ui.muted("\n" + "=" * 60)
864
+ await ui.muted(
865
+ f"🚀 FINAL BATCH: Executing {len(buffered_tasks)} buffered read-only tools"
866
+ )
867
+ await ui.muted("=" * 60)
919
868
 
920
- for idx, (part, node) in enumerate(buffered_tasks, 1):
921
- tool_desc = f" [{idx}] {part.tool_name}"
922
- if hasattr(part, "args") and isinstance(part.args, dict):
923
- if part.tool_name == "read_file" and "file_path" in part.args:
924
- tool_desc += f" → {part.args['file_path']}"
925
- elif part.tool_name == "grep" and "pattern" in part.args:
926
- tool_desc += f" → pattern: '{part.args['pattern']}'"
927
- if "include_files" in part.args:
928
- tool_desc += f", files: '{part.args['include_files']}'"
929
- elif part.tool_name == "list_dir" and "directory" in part.args:
930
- tool_desc += f" → {part.args['directory']}"
931
- elif part.tool_name == "glob" and "pattern" in part.args:
932
- tool_desc += f" → pattern: '{part.args['pattern']}'"
933
- await ui.muted(tool_desc)
934
- await ui.muted("=" * 60)
869
+ for idx, (part, node) in enumerate(buffered_tasks, 1):
870
+ tool_desc = f" [{idx}] {part.tool_name}"
871
+ if hasattr(part, "args") and isinstance(part.args, dict):
872
+ if part.tool_name == "read_file" and "file_path" in part.args:
873
+ tool_desc += f" → {part.args['file_path']}"
874
+ elif part.tool_name == "grep" and "pattern" in part.args:
875
+ tool_desc += f" → pattern: '{part.args['pattern']}'"
876
+ if "include_files" in part.args:
877
+ tool_desc += f", files: '{part.args['include_files']}'"
878
+ elif part.tool_name == "list_dir" and "directory" in part.args:
879
+ tool_desc += f" → {part.args['directory']}"
880
+ elif part.tool_name == "glob" and "pattern" in part.args:
881
+ tool_desc += f" → pattern: '{part.args['pattern']}'"
882
+ await ui.muted(tool_desc)
883
+ await ui.muted("=" * 60)
935
884
 
936
885
  await execute_tools_parallel(buffered_tasks, tool_callback)
937
886
 
@@ -939,11 +888,10 @@ async def process_request(
939
888
  sequential_estimate = len(buffered_tasks) * 100
940
889
  speedup = sequential_estimate / elapsed_time if elapsed_time > 0 else 1.0
941
890
 
942
- if state_manager.session.show_thoughts:
943
- await ui.muted(
944
- f"✅ Final batch completed in {elapsed_time:.0f}ms "
945
- f"(~{speedup:.1f}x faster than sequential)\n"
946
- )
891
+ await ui.muted(
892
+ f"✅ Final batch completed in {elapsed_time:.0f}ms "
893
+ f"(~{speedup:.1f}x faster than sequential)\n"
894
+ )
947
895
 
948
896
  # If we need to add a fallback response, create a wrapper
949
897
  if not response_state.has_user_response and i >= max_iterations and fallback_enabled:
@@ -1089,6 +1037,7 @@ async def process_request(
1089
1037
  f"'{type(self).__name__}' object has no attribute '{name}'"
1090
1038
  )
1091
1039
 
1092
- return AgentRunWithState(agent_run)
1040
+ return AgentRunWithState(agent_run)
1093
1041
  except asyncio.CancelledError:
1094
- raise UserAbortError("User aborted the request.")
1042
+ # When task is cancelled, raise UserAbortError instead
1043
+ raise UserAbortError("Operation was cancelled by user")
@@ -0,0 +1,29 @@
1
+ import logging
2
+
3
+ # Custom log level: THOUGHT
4
+ THOUGHT = 25
5
+ logging.addLevelName(THOUGHT, "THOUGHT")
6
+
7
+
8
+ def thought(self, message, *args, **kwargs):
9
+ if self.isEnabledFor(THOUGHT):
10
+ self._log(THOUGHT, message, args, **kwargs)
11
+
12
+
13
+ logging.Logger.thought = thought
14
+
15
+
16
+ # RichHandler for UI output (stub, real implementation in handlers.py)
17
+ class RichHandler(logging.Handler):
18
+ def emit(self, record):
19
+ # Actual implementation in handlers.py
20
+ pass
21
+
22
+
23
+ def setup_logging(config_path=None):
24
+ """
25
+ Set up logging configuration from YAML file.
26
+ """
27
+ from .config import LogConfig
28
+
29
+ LogConfig.load(config_path)
@@ -0,0 +1,28 @@
1
+ import logging
2
+ import logging.config
3
+ import os
4
+
5
+ import yaml
6
+
7
+ DEFAULT_CONFIG_PATH = os.path.join(
8
+ os.path.dirname(os.path.dirname(os.path.dirname(__file__))), "config", "logging.yaml"
9
+ )
10
+
11
+
12
+ class LogConfig:
13
+ @staticmethod
14
+ def load(config_path=None):
15
+ """
16
+ Load logging configuration from YAML file and apply it.
17
+ """
18
+ path = config_path or DEFAULT_CONFIG_PATH
19
+ if not os.path.exists(path):
20
+ raise FileNotFoundError(f"Logging config file not found: {path}")
21
+ with open(path, "r") as f:
22
+ config = yaml.safe_load(f)
23
+ logging_config = config.get("logging", config)
24
+ try:
25
+ logging.config.dictConfig(logging_config)
26
+ except Exception as e:
27
+ print(f"Failed to configure logging: {e}")
28
+ logging.basicConfig(level=logging.INFO)
@@ -0,0 +1,48 @@
1
+ import logging
2
+
3
+
4
+ class SimpleFormatter(logging.Formatter):
5
+ """
6
+ Simple formatter for UI output.
7
+ """
8
+
9
+ def __init__(self):
10
+ super().__init__("[%(levelname)s] %(message)s")
11
+
12
+
13
+ class DetailedFormatter(logging.Formatter):
14
+ """
15
+ Detailed formatter for backend text logs.
16
+ """
17
+
18
+ def __init__(self):
19
+ super().__init__("[%(asctime)s] [%(levelname)s] [%(name)s:%(lineno)d] - %(message)s")
20
+
21
+
22
+ try:
23
+ from pythonjsonlogger import jsonlogger
24
+
25
+ class JSONFormatter(jsonlogger.JsonFormatter):
26
+ """
27
+ JSON formatter for structured logs.
28
+ """
29
+
30
+ def __init__(self):
31
+ super().__init__("%(asctime)s %(name)s %(levelname)s %(message)s")
32
+ except ImportError:
33
+ import json
34
+
35
+ class JSONFormatter(logging.Formatter):
36
+ """
37
+ Fallback JSON formatter if pythonjsonlogger is not installed.
38
+ """
39
+
40
+ def format(self, record):
41
+ log_entry = {
42
+ "timestamp": self.formatTime(record),
43
+ "level": record.levelname,
44
+ "name": record.name,
45
+ "line": record.lineno,
46
+ "message": record.getMessage(),
47
+ }
48
+ return json.dumps(log_entry)
@@ -0,0 +1,83 @@
1
+ import json
2
+ import logging
3
+
4
+ from rich.console import Console
5
+ from rich.text import Text
6
+
7
+ # Global context for streaming state
8
+ _streaming_context = {"just_finished": False}
9
+
10
+
11
+ class RichHandler(logging.Handler):
12
+ """
13
+ Handler that outputs logs to the console using rich formatting.
14
+ """
15
+
16
+ level_icons = {
17
+ "INFO": "",
18
+ "WARNING": "⚠️",
19
+ "ERROR": "❌",
20
+ "CRITICAL": "🚨",
21
+ "THOUGHT": "🤔",
22
+ "DEBUG": "",
23
+ }
24
+
25
+ def __init__(self, level=logging.NOTSET):
26
+ super().__init__(level)
27
+ self.console = Console()
28
+
29
+ def emit(self, record):
30
+ try:
31
+ icon = self.level_icons.get(record.levelname, "")
32
+ timestamp = self.formatTime(record)
33
+ msg = self.format(record)
34
+ if icon:
35
+ output = f"[{timestamp}] {icon} {msg}"
36
+ else:
37
+ output = f"[{timestamp}] {msg}"
38
+
39
+ # Check if we just finished streaming to avoid extra newlines
40
+ just_finished_streaming = _streaming_context.get("just_finished", False)
41
+ if just_finished_streaming:
42
+ _streaming_context["just_finished"] = False # Reset after use
43
+ # Don't add extra newline when transitioning from streaming
44
+ self.console.print(Text(output), end="\n")
45
+ else:
46
+ self.console.print(Text(output))
47
+ except Exception:
48
+ self.handleError(record)
49
+
50
+ def formatTime(self, record, datefmt=None):
51
+ from datetime import datetime
52
+
53
+ ct = datetime.fromtimestamp(record.created)
54
+ if datefmt:
55
+ return ct.strftime(datefmt)
56
+ return ct.strftime("%Y-%m-%d %H:%M:%S")
57
+
58
+
59
+ class StructuredFileHandler(logging.FileHandler):
60
+ """
61
+ Handler that outputs logs as structured JSON lines.
62
+ """
63
+
64
+ def emit(self, record):
65
+ try:
66
+ log_entry = {
67
+ "timestamp": self.formatTime(record),
68
+ "level": record.levelname,
69
+ "name": record.name,
70
+ "line": record.lineno,
71
+ "message": record.getMessage(),
72
+ "extra_data": getattr(record, "extra", {}),
73
+ }
74
+ self.stream.write(json.dumps(log_entry) + "\n")
75
+ self.flush()
76
+ except Exception:
77
+ self.handleError(record)
78
+
79
+ def formatTime(self, record, datefmt=None):
80
+ from datetime import datetime, timezone
81
+
82
+ ct = datetime.fromtimestamp(record.created, tz=timezone.utc)
83
+ return ct.isoformat()
@@ -0,0 +1,8 @@
1
+ import logging
2
+
3
+
4
+ def get_logger(name=None):
5
+ """
6
+ Get a logger instance with the given name.
7
+ """
8
+ return logging.getLogger(name)
@@ -332,10 +332,11 @@ class TaskHierarchy:
332
332
  """Propagate context from one task to another.
333
333
 
334
334
  Args:
335
- from_task: Source task ID
335
+ from_task: Source task ID (unused, kept for API consistency)
336
336
  to_task: Target task ID
337
337
  context_update: Context to propagate
338
338
  """
339
+ _ = from_task # Unused but kept for API consistency
339
340
  if to_task in self._execution_contexts:
340
341
  self._execution_contexts[to_task].inherited_context.update(context_update)
341
342
 
@@ -41,6 +41,10 @@ class SessionState:
41
41
  input_sessions: InputSessions = field(default_factory=dict)
42
42
  current_task: Optional[Any] = None
43
43
  todos: list[TodoItem] = field(default_factory=list)
44
+ # ESC key tracking for double-press functionality
45
+ esc_press_count: int = 0
46
+ last_esc_time: Optional[float] = None
47
+ operation_cancelled: bool = False
44
48
  # Enhanced tracking for thoughts display
45
49
  files_in_context: set[str] = field(default_factory=set)
46
50
  tool_calls: list[dict[str, Any]] = field(default_factory=list)
@@ -8,6 +8,7 @@ from abc import ABC, abstractmethod
8
8
 
9
9
  from pydantic_ai.exceptions import ModelRetry
10
10
 
11
+ from tunacode.core.logging.logger import get_logger
11
12
  from tunacode.exceptions import FileOperationError, ToolExecutionError
12
13
  from tunacode.types import FilePath, ToolName, ToolResult, UILogger
13
14
 
@@ -22,6 +23,7 @@ class BaseTool(ABC):
22
23
  ui_logger: UI logger instance for displaying messages
23
24
  """
24
25
  self.ui = ui_logger
26
+ self.logger = get_logger(self.__class__.__name__)
25
27
 
26
28
  async def execute(self, *args, **kwargs) -> ToolResult:
27
29
  """Execute the tool with error handling and logging.
@@ -39,14 +41,17 @@ class BaseTool(ABC):
39
41
  ToolExecutionError: Raised for all other errors with structured information
40
42
  """
41
43
  try:
44
+ msg = f"{self.tool_name}({self._format_args(*args, **kwargs)})"
42
45
  if self.ui:
43
- await self.ui.info(f"{self.tool_name}({self._format_args(*args, **kwargs)})")
46
+ await self.ui.info(msg)
47
+ self.logger.info(msg)
44
48
  result = await self._execute(*args, **kwargs)
45
49
  return result
46
50
  except ModelRetry as e:
47
51
  # Log as warning and re-raise for pydantic-ai
48
52
  if self.ui:
49
53
  await self.ui.warning(str(e))
54
+ self.logger.warning(f"ModelRetry: {e}")
50
55
  raise
51
56
  except ToolExecutionError:
52
57
  # Already properly formatted, just re-raise
@@ -90,6 +95,7 @@ class BaseTool(ABC):
90
95
  err_msg = f"Error {self._get_error_context(*args, **kwargs)}: {error}"
91
96
  if self.ui:
92
97
  await self.ui.error(err_msg)
98
+ self.logger.error(err_msg)
93
99
 
94
100
  # Raise proper exception instead of returning string
95
101
  raise ToolExecutionError(tool_name=self.tool_name, message=str(error), original_error=error)
@@ -13,7 +13,7 @@ from typing import Any, Awaitable, Callable, Dict, List, Literal, Optional, Prot
13
13
  # Try to import pydantic-ai types if available
14
14
  try:
15
15
  from pydantic_ai import Agent
16
- from pydantic_ai.messages import ModelRequest, ModelResponse, ToolReturnPart
16
+ from pydantic_ai.messages import ModelRequest, ToolReturnPart
17
17
 
18
18
  PydanticAgent = Agent
19
19
  MessagePart = Union[ToolReturnPart, Any]