tunacode-cli 0.0.34__tar.gz → 0.0.35__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 (193) hide show
  1. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/CLAUDE.md +12 -245
  2. {tunacode_cli-0.0.34/src/tunacode_cli.egg-info → tunacode_cli-0.0.35}/PKG-INFO +1 -1
  3. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/pyproject.toml +1 -1
  4. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/cli/commands.py +37 -21
  5. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/cli/repl.py +17 -5
  6. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/constants.py +1 -1
  7. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/tools/run_command.py +18 -8
  8. tunacode_cli-0.0.35/src/tunacode/utils/security.py +208 -0
  9. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35/src/tunacode_cli.egg-info}/PKG-INFO +1 -1
  10. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode_cli.egg-info/SOURCES.txt +2 -0
  11. tunacode_cli-0.0.35/tests/test_security.py +192 -0
  12. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/LICENSE +0 -0
  13. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/MANIFEST.in +0 -0
  14. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/README.md +0 -0
  15. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/TUNACODE.md +0 -0
  16. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/setup.cfg +0 -0
  17. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/setup.py +0 -0
  18. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/__init__.py +0 -0
  19. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/cli/__init__.py +0 -0
  20. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/cli/main.py +0 -0
  21. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/cli/textual_app.py +0 -0
  22. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/cli/textual_bridge.py +0 -0
  23. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/configuration/__init__.py +0 -0
  24. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/configuration/defaults.py +0 -0
  25. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/configuration/models.py +0 -0
  26. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/configuration/settings.py +0 -0
  27. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/context.py +0 -0
  28. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/core/__init__.py +0 -0
  29. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/core/agents/__init__.py +0 -0
  30. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/core/agents/main.py +0 -0
  31. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/core/background/__init__.py +0 -0
  32. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/core/background/manager.py +0 -0
  33. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/core/code_index.py +0 -0
  34. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/core/llm/__init__.py +0 -0
  35. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/core/setup/__init__.py +0 -0
  36. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/core/setup/agent_setup.py +0 -0
  37. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/core/setup/base.py +0 -0
  38. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/core/setup/config_setup.py +0 -0
  39. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/core/setup/coordinator.py +0 -0
  40. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/core/setup/environment_setup.py +0 -0
  41. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/core/setup/git_safety_setup.py +0 -0
  42. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/core/state.py +0 -0
  43. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/core/tool_handler.py +0 -0
  44. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/exceptions.py +0 -0
  45. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/prompts/system.md +0 -0
  46. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/py.typed +0 -0
  47. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/services/__init__.py +0 -0
  48. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/services/mcp.py +0 -0
  49. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/setup.py +0 -0
  50. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/tools/__init__.py +0 -0
  51. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/tools/base.py +0 -0
  52. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/tools/bash.py +0 -0
  53. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/tools/glob.py +0 -0
  54. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/tools/grep.py +0 -0
  55. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/tools/list_dir.py +0 -0
  56. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/tools/read_file.py +0 -0
  57. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/tools/read_file_async_poc.py +0 -0
  58. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/tools/update_file.py +0 -0
  59. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/tools/write_file.py +0 -0
  60. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/types.py +0 -0
  61. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/ui/__init__.py +0 -0
  62. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/ui/completers.py +0 -0
  63. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/ui/console.py +0 -0
  64. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/ui/constants.py +0 -0
  65. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/ui/decorators.py +0 -0
  66. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/ui/input.py +0 -0
  67. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/ui/keybindings.py +0 -0
  68. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/ui/lexers.py +0 -0
  69. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/ui/output.py +0 -0
  70. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/ui/panels.py +0 -0
  71. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/ui/prompt_manager.py +0 -0
  72. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/ui/tool_ui.py +0 -0
  73. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/ui/validators.py +0 -0
  74. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/utils/__init__.py +0 -0
  75. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/utils/bm25.py +0 -0
  76. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/utils/diff_utils.py +0 -0
  77. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/utils/file_utils.py +0 -0
  78. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/utils/import_cache.py +0 -0
  79. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/utils/ripgrep.py +0 -0
  80. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/utils/system.py +0 -0
  81. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/utils/text_utils.py +0 -0
  82. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/utils/token_counter.py +0 -0
  83. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode/utils/user_configuration.py +0 -0
  84. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode_cli.egg-info/dependency_links.txt +0 -0
  85. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode_cli.egg-info/entry_points.txt +0 -0
  86. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode_cli.egg-info/requires.txt +0 -0
  87. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/src/tunacode_cli.egg-info/top_level.txt +0 -0
  88. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/characterization/agent/__init__.py +0 -0
  89. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/characterization/agent/conftest.py +0 -0
  90. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/characterization/agent/test_agent_creation.py +0 -0
  91. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/characterization/agent/test_json_tool_parsing.py +0 -0
  92. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/characterization/agent/test_process_node.py +0 -0
  93. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/characterization/agent/test_process_request.py +0 -0
  94. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/characterization/agent/test_tool_message_patching.py +0 -0
  95. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/characterization/background/test_background_edge_cases.py +0 -0
  96. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/characterization/background/test_cleanup.py +0 -0
  97. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/characterization/background/test_task_cancellation.py +0 -0
  98. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/characterization/background/test_task_creation.py +0 -0
  99. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/characterization/background/test_task_execution.py +0 -0
  100. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/characterization/code_index/test_cache_management.py +0 -0
  101. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/characterization/code_index/test_file_scanning.py +0 -0
  102. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/characterization/code_index/test_index_building.py +0 -0
  103. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/characterization/code_index/test_search_operations.py +0 -0
  104. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/characterization/code_index/test_symbol_extraction.py +0 -0
  105. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/characterization/commands/__init__.py +0 -0
  106. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/characterization/commands/test_init_command.py +0 -0
  107. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/characterization/conftest.py +0 -0
  108. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/characterization/context/__init__.py +0 -0
  109. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/characterization/context/test_context_acceptance.py +0 -0
  110. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/characterization/context/test_context_integration.py +0 -0
  111. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/characterization/context/test_context_loading.py +0 -0
  112. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/characterization/context/test_tunacode_logging.py +0 -0
  113. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/characterization/repl/test_command_parsing.py +0 -0
  114. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/characterization/repl/test_input_handling.py +0 -0
  115. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/characterization/repl/test_keyboard_interrupts.py +0 -0
  116. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/characterization/repl/test_multiline_input.py +0 -0
  117. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/characterization/repl/test_repl_initialization.py +0 -0
  118. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/characterization/repl/test_session_flow.py +0 -0
  119. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/characterization/services/test_error_recovery.py +0 -0
  120. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/characterization/services/test_llm_routing.py +0 -0
  121. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/characterization/services/test_mcp_integration.py +0 -0
  122. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/characterization/services/test_service_lifecycle.py +0 -0
  123. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/characterization/state/test_agent_tracking.py +0 -0
  124. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/characterization/state/test_message_history.py +0 -0
  125. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/characterization/state/test_permissions.py +0 -0
  126. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/characterization/state/test_session_management.py +0 -0
  127. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/characterization/state/test_state_initialization.py +0 -0
  128. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/characterization/state/test_user_config.py +0 -0
  129. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/characterization/test_characterization_commands.py +0 -0
  130. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/characterization/ui/test_async_ui.py +0 -0
  131. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/characterization/ui/test_console_output.py +0 -0
  132. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/characterization/ui/test_diff_display.py +0 -0
  133. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/characterization/ui/test_prompt_rendering.py +0 -0
  134. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/characterization/ui/test_tool_confirmations.py +0 -0
  135. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/characterization/utils/test_file_operations.py +0 -0
  136. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/characterization/utils/test_git_commands.py +0 -0
  137. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/characterization/utils/test_token_counting.py +0 -0
  138. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/characterization/utils/test_utils_edge_cases.py +0 -0
  139. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/conftest.py +0 -0
  140. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/crud/test_core_file_operations.py +0 -0
  141. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/fixtures/__init__.py +0 -0
  142. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/fixtures/file_operations.py +0 -0
  143. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/integration/test_error_recovery_flow.py +0 -0
  144. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/integration/test_full_session_flow.py +0 -0
  145. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/integration/test_mcp_tool_flow.py +0 -0
  146. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/integration/test_multi_tool_operations.py +0 -0
  147. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/integration/test_performance_scenarios.py +0 -0
  148. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/test_actual_parallelism.py +0 -0
  149. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/test_agent_initialization.py +0 -0
  150. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/test_background_manager.py +0 -0
  151. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/test_characterization_agent_main.py +0 -0
  152. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/test_characterization_bash.py +0 -0
  153. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/test_characterization_commands_system.py +0 -0
  154. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/test_characterization_glob.py +0 -0
  155. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/test_characterization_grep.py +0 -0
  156. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/test_characterization_grep_performance.py +0 -0
  157. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/test_characterization_iteration_limits.py +0 -0
  158. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/test_characterization_list_dir.py +0 -0
  159. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/test_characterization_read_file.py +0 -0
  160. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/test_characterization_repl_utils.py +0 -0
  161. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/test_characterization_run_command.py +0 -0
  162. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/test_characterization_setup_system.py +0 -0
  163. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/test_characterization_tool_ui_behavior.py +0 -0
  164. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/test_characterization_update_file.py +0 -0
  165. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/test_characterization_utilities.py +0 -0
  166. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/test_characterization_write_file.py +0 -0
  167. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/test_cli_command_flow.py +0 -0
  168. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/test_cli_file_operations_integration.py +0 -0
  169. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/test_config_directory_creation.py +0 -0
  170. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/test_config_setup_async.py +0 -0
  171. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/test_enhanced_visual_feedback.py +0 -0
  172. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/test_fallback_responses.py +0 -0
  173. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/test_fast_glob_search.py +0 -0
  174. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/test_file_operations_edge_cases.py +0 -0
  175. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/test_file_operations_stress.py +0 -0
  176. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/test_file_reference_context_tracking.py +0 -0
  177. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/test_file_reference_expansion.py +0 -0
  178. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/test_grep_fast_glob.py +0 -0
  179. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/test_grep_legacy_compat.py +0 -0
  180. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/test_grep_timeout.py +0 -0
  181. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/test_json_tool_parsing.py +0 -0
  182. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/test_list_dir.py +0 -0
  183. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/test_parallel_execution_demo.py +0 -0
  184. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/test_parallel_execution_freeze_fix.py +0 -0
  185. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/test_parallel_execution_integration.py +0 -0
  186. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/test_parallel_read_only_tools.py +0 -0
  187. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/test_parallel_tool_execution.py +0 -0
  188. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/test_read_only_confirmation.py +0 -0
  189. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/test_tool_categorization.py +0 -0
  190. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/test_tool_combinations.py +0 -0
  191. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/test_tool_handler_ui_messages.py +0 -0
  192. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/test_update_command.py +0 -0
  193. {tunacode_cli-0.0.34 → tunacode_cli-0.0.35}/tests/test_visual_parallel_feedback.py +0 -0
@@ -10,251 +10,18 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
10
10
  # Install development environment (recommended approach)
11
11
  ./scripts/setup_dev_env.sh # Creates fresh venv, installs deps, verifies setup
12
12
 
13
- # You must always follow the flow for working
14
- Project structure:
15
- agent-tools/
16
- ├── wakeup.sh # Read memory bank
17
- ├── scratchpad.sh # Task logging
18
- ├── check_workflow.sh # Simple verification
19
- ├── bankctl.sh # Memory bank control
20
- └── WORKFLOW_GUIDE.md # Complete workflow documentation
21
-
22
- memory-bank/
23
- ├── project_brief.md
24
- ├── tech_context.md
25
- ├── product_context.md
26
- ├── current_state_summary.md
27
- └── progress_overview.md
28
-
29
- 🚀 Quick start:
30
- 1. Edit memory-bank/*.md files with your project details
31
- 2. ./wakeup.sh # Read current context
32
- 3. ./scratchpad.sh start 'Task' # Begin new task
33
- 4. ./scratchpad.sh step 'Action' # Log progress
34
- 5. ./scratchpad.sh close 'Done' # Complete task
35
-
36
- 📖 Full guide: cat agent-tools/WORKFLOW_GUIDE.md
37
- <?xml version="1.0" encoding="UTF-8"?>
38
- <system_prompt>
39
-
40
- ###Instruction###
41
-
42
- You are an expert software engineering assistant equipped with specialized bash tools for memory management and task tracking. Your primary goal is to maintain persistent context across sessions while following a structured workflow.
43
-
44
- You MUST use these tools proactively and frequently. You will be penalized for failing to use appropriate tools when they would improve task outcomes.
45
-
46
- <role>Expert Software Engineering Assistant with Memory Management Tools</role>
47
-
48
- <available_tools>
49
- 1. wakeup.sh - Read memory bank to regain project context
50
- 2. scratchpad.sh - Task logging and progress tracking
51
- 3. check_workflow.sh - Simple verification check
52
- 4. bankctl.sh - Memory bank initialization and management
53
- </available_tools>
54
-
55
- <critical_requirements>
56
- - Think step by step when approaching any task
57
- - Always run wakeup.sh at the start of a new session to regain context
58
- - Use scratchpad.sh for EVERY task to maintain detailed work logs
59
- - Update memory-bank/current_state_summary.md after completing tasks
60
- - Occasionally run check_workflow.sh to verify nothing was missed
61
- - Ensure that your approach is unbiased and does not rely on stereotypes
62
- </critical_requirements>
63
-
64
- ###Example###
65
-
66
- <example_workflow>
67
- User: "Help me implement a new user registration feature"
68
-
69
- CORRECT APPROACH:
70
- 1. ./wakeup.sh (read memory bank to understand project context)
71
- 2. ./scratchpad.sh start "Implement user registration feature"
72
- 3. ./scratchpad.sh plan "1. Create user model 2. Design API endpoint 3. Add validation 4. Write tests"
73
- 4. ./scratchpad.sh step "Created User model in models.py with email, username, password_hash"
74
- 5. ./scratchpad.sh step "Implemented POST /register endpoint with input validation"
75
- 6. ./scratchpad.sh step "Added password hashing using bcrypt"
76
- 7. ./scratchpad.sh step "Wrote unit tests for registration flow"
77
- 8. ./scratchpad.sh close "User registration feature complete"
78
- 9. Update memory-bank/current_state_summary.md with session outcome
79
- 10. ./check_workflow.sh (verify workflow was followed)
80
-
81
- INCORRECT APPROACH:
82
- - Starting work without reading memory bank
83
- - Making changes without tracking steps in scratchpad
84
- - Not updating current_state_summary.md after task completion
85
- - Never checking if workflow was properly followed
86
- </example_workflow>
87
-
88
- ###Guidelines###
89
-
90
- <wakeup_usage>
91
- WHEN TO USE:
92
- - At the start of EVERY new session
93
- - When returning to a project after any break
94
- - To understand project context and current state
95
-
96
- OUTPUT:
97
- - Reads all memory bank files in priority order
98
- - Shows current_state_summary.md first (most important)
99
- - Displays project brief, technical context, product context, and progress
100
-
101
- You MUST:
102
- - Always run wakeup.sh before starting any work
103
- - Pay special attention to current_state_summary.md
104
- - Use the context to inform your approach
105
- </wakeup_usage>
106
-
107
- <scratchpad_usage>
108
- WHEN TO USE:
109
- - For EVERY task, regardless of complexity
110
- - Even for single-step tasks (maintains history)
111
- - When exploring, debugging, or implementing features
112
-
113
- COMMANDS:
114
- - start "task_name": Begin new task tracking
115
- - plan "plan_details": Document your approach
116
- - step "action_taken": Log each action/decision
117
- - close "completion_message": Archive the task
118
-
119
- You MUST:
120
- - Start scratchpad for every task
121
- - Log detailed steps as you work
122
- - Close and archive when complete
123
- - Note: close command auto-sanitizes filenames
124
- </scratchpad_usage>
125
-
126
- <check_workflow_usage>
127
- WHEN TO USE:
128
- - After completing a few tasks
129
- - When you want to verify workflow compliance
130
- - Periodically to ensure nothing was missed
131
-
132
- OUTPUT:
133
- - Shows when memory bank was last updated
134
- - Lists recent archived scratchpads
135
- - Displays current state summary
136
-
137
- You SHOULD:
138
- - Run this occasionally (not after every single task)
139
- - Use it as a sanity check for workflow adherence
140
- - Pay attention if updates are getting stale
141
- </check_workflow_usage>
142
-
143
- <bankctl_usage>
144
- WHEN TO USE:
145
- - First time setup of a project
146
- - When memory bank structure needs initialization
147
- - For memory bank maintenance tasks
148
-
149
- COMMANDS:
150
- - init: Initialize memory bank structure
151
- - Other commands vary by implementation
152
-
153
- You MUST:
154
- - Use bankctl.sh init for new projects
155
- - Ensure memory bank exists before using other tools
156
- </bankctl_usage>
157
-
158
- <memory_bank_structure>
159
- CORE FILES:
160
- 1. project_brief.md - What & why of the project
161
- 2. tech_context.md - Technical decisions & architecture
162
- 3. product_context.md - User experience goals
163
- 4. current_state_summary.md - CRITICAL: Latest state & next steps
164
- 5. progress_overview.md - Feature/task tracker
165
-
166
- UPDATE STRATEGY:
167
- - current_state_summary.md: Update after EVERY session
168
- - progress_overview.md: Update when features complete
169
- - Other files: Update only when fundamentals change
170
-
171
- You MUST:
172
- - Keep current_state_summary.md concise but complete
173
- - Include session outcomes and immediate next steps
174
- - Archive detailed logs in scratchpad, not memory bank
175
- </memory_bank_structure>
176
-
177
- ###Workflow_Patterns###
178
-
179
- <pattern name="new_session_startup">
180
- 1. ./wakeup.sh
181
- 2. Review current_state_summary.md carefully
182
- 3. Identify immediate next objectives
183
- 4. ./scratchpad.sh start "[next_task_from_summary]"
184
- 5. Continue with task implementation
185
- </pattern>
186
-
187
- <pattern name="feature_implementation">
188
- 1. ./wakeup.sh
189
- 2. ./scratchpad.sh start "Implement [feature_name]"
190
- 3. ./scratchpad.sh plan "Steps: 1. [step1] 2. [step2] 3. [step3]"
191
- 4. ./scratchpad.sh step "Completed [specific action]"
192
- 5. [continue logging each step]
193
- 6. ./scratchpad.sh close "[feature_name] implementation complete"
194
- 7. Update memory-bank/current_state_summary.md
195
- 8. Update memory-bank/progress_overview.md
196
- 9. ./check_workflow.sh (occasionally, to verify)
197
- </pattern>
198
-
199
- <pattern name="debugging_session">
200
- 1. ./wakeup.sh
201
- 2. ./scratchpad.sh start "Debug [issue_description]"
202
- 3. ./scratchpad.sh step "Reproduced issue: [details]"
203
- 4. ./scratchpad.sh step "Identified root cause: [cause]"
204
- 5. ./scratchpad.sh step "Applied fix: [solution]"
205
- 6. ./scratchpad.sh step "Verified fix works"
206
- 7. ./scratchpad.sh close "Fixed [issue_description]"
207
- 8. Update memory-bank/current_state_summary.md
208
- </pattern>
209
-
210
- <pattern name="project_initialization">
211
- 1. ./bankctl.sh init
212
- 2. Edit memory-bank/project_brief.md
213
- 3. Edit memory-bank/tech_context.md
214
- 4. Edit memory-bank/product_context.md
215
- 5. Edit memory-bank/current_state_summary.md
216
- 6. Edit memory-bank/progress_overview.md
217
- 7. ./wakeup.sh (verify setup)
218
- </pattern>
219
-
220
- ###Penalties###
221
-
222
- You will be penalized for:
223
- - Not running wakeup.sh at session start
224
- - Starting any task without scratchpad.sh
225
- - Failing to update current_state_summary.md after tasks
226
- - Not archiving completed scratchpads
227
- - Keeping detailed logs in memory bank instead of scratchpad
228
- - Never running check_workflow.sh to verify compliance
229
-
230
- ###Output_Format###
231
-
232
- When using tools, always show:
233
- 1. The exact command being executed
234
- 2. Brief explanation of why you're using it
235
- 3. Key findings or results
236
-
237
- ###Memory_Management_Philosophy###
238
-
239
- This workflow is designed for agents that experience complete memory loss between sessions. The system provides:
240
-
241
- 1. **Memory Bank** - Persistent, summarized knowledge base
242
- - Project context and goals
243
- - Current state and next steps
244
- - High-level progress tracking
245
-
246
- 2. **Scratchpad** - Detailed, temporary work logs
247
- - Step-by-step task documentation
248
- - Decisions and observations
249
- - Archived after completion
250
-
251
- The key is maintaining clear separation between long-term strategic memory (Memory Bank) and short-term operational memory (Scratchpad).
252
-
253
- Answer questions in a natural, human-like manner while maintaining technical accuracy.
254
-
255
- I'm going to tip $200000 for exceptional workflow adherence that demonstrates mastery of memory management!
256
-
257
- </system_prompt>
13
+ # You must always follow the agent tooling workflow specified in agent_tools_prompt.xml
14
+
15
+ When the directory "llm-agent-tools" exists, you MUST use this tooling flow and will be penalized if you don't:
16
+
17
+ 🚀 Available Tools:
18
+ 1. scratchpad-multi.sh - Task tracking with multi-agent support (use --agent <name> for agent-specific work)
19
+ 2. knowledge.sh - Knowledge base with private/shared storage (use --agent <name> for agent-specific work)
20
+ 3. codemap.sh - Lightweight code intelligence for instant roadmaps
21
+ 4. context.sh - Context gathering for debugging issues
22
+ 5. researcher.sh - Online research and multimodal analysis via OpenRouter API
23
+
24
+ 📖 Full workflow guide: see llm-agent-tools/agent_tools_prompt.xml
258
25
 
259
26
  For new feature YOU MUST folow this flow
260
27
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tunacode-cli
3
- Version: 0.0.34
3
+ Version: 0.0.35
4
4
  Summary: Your agentic CLI developer.
5
5
  Author-email: larock22 <noreply@github.com>
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "tunacode-cli"
7
- version = "0.0.34"
7
+ version = "0.0.35"
8
8
  description = "Your agentic CLI developer."
9
9
  keywords = ["cli", "agent", "development", "automation"]
10
10
  readme = "README.md"
@@ -181,26 +181,29 @@ class IterationsCommand(SimpleCommand):
181
181
 
182
182
  async def execute(self, args: List[str], context: CommandContext) -> None:
183
183
  state = context.state_manager.session
184
- if args:
185
- try:
186
- new_limit = int(args[0])
187
- if new_limit < 1 or new_limit > 100:
188
- await ui.error("Iterations must be between 1 and 100")
189
- return
190
-
191
- # Update the user config
192
- if "settings" not in state.user_config:
193
- state.user_config["settings"] = {}
194
- state.user_config["settings"]["max_iterations"] = new_limit
195
-
196
- await ui.success(f"Maximum iterations set to {new_limit}")
197
- await ui.muted("Higher values allow more complex reasoning but may be slower")
198
- except ValueError:
199
- await ui.error("Please provide a valid number")
200
- else:
184
+
185
+ # Guard clause - handle "no args" case first and return early
186
+ if not args:
201
187
  current = state.user_config.get("settings", {}).get("max_iterations", 40)
202
188
  await ui.info(f"Current maximum iterations: {current}")
203
189
  await ui.muted("Usage: /iterations <number> (1-100)")
190
+ return
191
+
192
+ # update the logic to not be as nested messely, the above guars needing to get as messy
193
+ try:
194
+ new_limit = int(args[0])
195
+ if new_limit < 1 or new_limit > 100:
196
+ await ui.error("Iterations must be between 1 and 100")
197
+ return
198
+
199
+ # Update the user config
200
+ if "settings" not in state.user_config:
201
+ state.user_config["settings"] = {}
202
+ state.user_config["settings"]["max_iterations"] = new_limit
203
+
204
+ await ui.success(f"Maximum iterations set to {new_limit}")
205
+ except ValueError:
206
+ await ui.error("Please provide a valid number")
204
207
 
205
208
 
206
209
  class ClearCommand(SimpleCommand):
@@ -288,7 +291,9 @@ class ParseToolsCommand(SimpleCommand):
288
291
 
289
292
  try:
290
293
  await extract_and_execute_tool_calls(
291
- part.content, tool_callback_with_state, context.state_manager
294
+ part.content,
295
+ tool_callback_with_state,
296
+ context.state_manager,
292
297
  )
293
298
  await ui.success("JSON tool parsing completed")
294
299
  found_content = True
@@ -524,7 +529,8 @@ class UpdateCommand(SimpleCommand):
524
529
  result = subprocess.run(
525
530
  ["pipx", "list"], capture_output=True, text=True, timeout=10
526
531
  )
527
- if "tunacode" in result.stdout.lower():
532
+ pipx_installed = "tunacode" in result.stdout.lower()
533
+ if pipx_installed:
528
534
  installation_method = "pipx"
529
535
  except (subprocess.TimeoutExpired, subprocess.CalledProcessError):
530
536
  pass
@@ -555,12 +561,22 @@ class UpdateCommand(SimpleCommand):
555
561
  if installation_method == "pipx":
556
562
  await ui.info("Updating via pipx...")
557
563
  result = subprocess.run(
558
- ["pipx", "upgrade", "tunacode"], capture_output=True, text=True, timeout=60
564
+ ["pipx", "upgrade", "tunacode"],
565
+ capture_output=True,
566
+ text=True,
567
+ timeout=60,
559
568
  )
560
569
  else: # pip
561
570
  await ui.info("Updating via pip...")
562
571
  result = subprocess.run(
563
- [sys.executable, "-m", "pip", "install", "--upgrade", "tunacode-cli"],
572
+ [
573
+ sys.executable,
574
+ "-m",
575
+ "pip",
576
+ "install",
577
+ "--upgrade",
578
+ "tunacode-cli",
579
+ ],
564
580
  capture_output=True,
565
581
  text=True,
566
582
  timeout=60,
@@ -22,6 +22,7 @@ from tunacode.core.tool_handler import ToolHandler
22
22
  from tunacode.exceptions import AgentError, UserAbortError, ValidationError
23
23
  from tunacode.ui import console as ui
24
24
  from tunacode.ui.tool_ui import ToolUI
25
+ from tunacode.utils.security import CommandSecurityError, safe_subprocess_run
25
26
 
26
27
  from ..types import CommandContext, CommandResult, StateManager, ToolArgs
27
28
  from .commands import CommandRegistry
@@ -320,13 +321,24 @@ async def repl(state_manager: StateManager):
320
321
  def run_shell():
321
322
  try:
322
323
  if command:
323
- result = subprocess.run(command, shell=True, capture_output=False)
324
- if result.returncode != 0:
325
- # Use print directly since we're in a terminal context
326
- print(f"\nCommand exited with code {result.returncode}")
324
+ # Use secure subprocess execution for shell commands
325
+ # Note: User shell commands are inherently risky but this is by design
326
+ # We validate but allow shell features since it's explicit user intent
327
+ try:
328
+ result = safe_subprocess_run(
329
+ command,
330
+ shell=True,
331
+ validate=True, # Still validate for basic safety
332
+ capture_output=False,
333
+ )
334
+ if result.returncode != 0:
335
+ print(f"\nCommand exited with code {result.returncode}")
336
+ except CommandSecurityError as e:
337
+ print(f"\nSecurity validation failed: {str(e)}")
338
+ print("If you need to run this command, please ensure it's safe.")
327
339
  else:
328
340
  shell = os.environ.get("SHELL", "bash")
329
- subprocess.run(shell)
341
+ subprocess.run(shell) # Interactive shell is safe
330
342
  except Exception as e:
331
343
  print(f"\nShell command failed: {str(e)}")
332
344
 
@@ -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.34"
10
+ APP_VERSION = "0.0.35"
11
11
 
12
12
  # File patterns
13
13
  GUIDE_FILE_PATTERN = "{name}.md"
@@ -14,6 +14,7 @@ from tunacode.constants import (CMD_OUTPUT_FORMAT, CMD_OUTPUT_NO_ERRORS, CMD_OUT
14
14
  from tunacode.exceptions import ToolExecutionError
15
15
  from tunacode.tools.base import BaseTool
16
16
  from tunacode.types import ToolResult
17
+ from tunacode.utils.security import CommandSecurityError, safe_subprocess_popen
17
18
 
18
19
 
19
20
  class RunCommandTool(BaseTool):
@@ -34,16 +35,23 @@ class RunCommandTool(BaseTool):
34
35
 
35
36
  Raises:
36
37
  FileNotFoundError: If command not found
38
+ CommandSecurityError: If command fails security validation
37
39
  Exception: Any command execution errors
38
40
  """
39
- process = subprocess.Popen(
40
- command,
41
- shell=True,
42
- stdout=subprocess.PIPE,
43
- stderr=subprocess.PIPE,
44
- text=True,
45
- )
46
- stdout, stderr = process.communicate()
41
+ try:
42
+ # Use secure subprocess execution with validation
43
+ process = safe_subprocess_popen(
44
+ command,
45
+ shell=True, # CLI tool requires shell features
46
+ validate=True, # Enable security validation
47
+ stdout=subprocess.PIPE,
48
+ stderr=subprocess.PIPE,
49
+ text=True,
50
+ )
51
+ stdout, stderr = process.communicate()
52
+ except CommandSecurityError as e:
53
+ # Security validation failed - return error without execution
54
+ return f"Security validation failed: {str(e)}"
47
55
  output = stdout.strip() or CMD_OUTPUT_NO_OUTPUT
48
56
  error = stderr.strip() or CMD_OUTPUT_NO_ERRORS
49
57
  resp = CMD_OUTPUT_FORMAT.format(output=output, error=error).strip()
@@ -70,6 +78,8 @@ class RunCommandTool(BaseTool):
70
78
  """
71
79
  if isinstance(error, FileNotFoundError):
72
80
  err_msg = ERROR_COMMAND_EXECUTION.format(command=command, error=error)
81
+ elif isinstance(error, CommandSecurityError):
82
+ err_msg = f"Command blocked for security: {str(error)}"
73
83
  else:
74
84
  # Use parent class handling for other errors
75
85
  await super()._handle_error(error, command)
@@ -0,0 +1,208 @@
1
+ """
2
+ Security utilities for safe command execution and input validation.
3
+ Provides defensive measures against command injection attacks.
4
+ """
5
+
6
+ import logging
7
+ import re
8
+ import shlex
9
+ import subprocess
10
+ from typing import List, Optional
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+ # Dangerous shell metacharacters that indicate potential injection
15
+ DANGEROUS_CHARS = [
16
+ ";",
17
+ "&",
18
+ "|",
19
+ "`",
20
+ "$",
21
+ "(",
22
+ ")",
23
+ "{",
24
+ "}",
25
+ "<",
26
+ ">",
27
+ "\n",
28
+ "\r",
29
+ "\\",
30
+ '"',
31
+ "'",
32
+ ]
33
+
34
+ # Common injection patterns
35
+ INJECTION_PATTERNS = [
36
+ r";\s*\w+", # Command chaining with semicolon
37
+ r"&&\s*\w+", # Command chaining with &&
38
+ r"\|\s*\w+", # Piping to another command
39
+ r"`[^`]+`", # Command substitution with backticks
40
+ r"\$\([^)]+\)", # Command substitution with $()
41
+ r">\s*[/\w]", # Output redirection
42
+ r"<\s*[/\w]", # Input redirection
43
+ ]
44
+
45
+
46
+ class CommandSecurityError(Exception):
47
+ """Raised when a command fails security validation."""
48
+
49
+ pass
50
+
51
+
52
+ def validate_command_safety(command: str, allow_shell_features: bool = False) -> None:
53
+ """
54
+ Validate that a command is safe to execute.
55
+
56
+ Args:
57
+ command: The command string to validate
58
+ allow_shell_features: If True, allows some shell features like pipes
59
+
60
+ Raises:
61
+ CommandSecurityError: If the command contains potentially dangerous patterns
62
+ """
63
+ if not command or not command.strip():
64
+ raise CommandSecurityError("Empty command not allowed")
65
+
66
+ # Log the command being validated
67
+ logger.info(f"Validating command: {command[:100]}...")
68
+
69
+ # Always check for the most dangerous patterns regardless of shell features
70
+ dangerous_patterns = [
71
+ r"rm\s+-rf\s+/", # Dangerous rm commands
72
+ r"sudo\s+rm", # Sudo rm commands
73
+ r">\s*/dev/sd[a-z]", # Writing to disk devices
74
+ r"dd\s+.*of=/dev/", # DD to devices
75
+ r"mkfs\.", # Format filesystem
76
+ r"fdisk", # Partition manipulation
77
+ r":\(\)\{.*\}\;", # Fork bomb pattern
78
+ ]
79
+
80
+ for pattern in dangerous_patterns:
81
+ if re.search(pattern, command, re.IGNORECASE):
82
+ logger.error(f"Highly dangerous pattern '{pattern}' detected in command")
83
+ raise CommandSecurityError("Command contains dangerous pattern and is blocked")
84
+
85
+ if not allow_shell_features:
86
+ # Check for dangerous characters (but allow some for CLI tools)
87
+ restricted_chars = [";", "&", "`", "$", "{", "}"] # More permissive for CLI
88
+ for char in restricted_chars:
89
+ if char in command:
90
+ logger.warning(f"Potentially dangerous character '{char}' detected in command")
91
+ raise CommandSecurityError(f"Potentially unsafe character '{char}' in command")
92
+
93
+ # Check for injection patterns (more selective)
94
+ strict_patterns = [
95
+ r";\s*rm\s+", # Command chaining to rm
96
+ r"&&\s*rm\s+", # Command chaining to rm
97
+ r"`[^`]*rm[^`]*`", # Command substitution with rm
98
+ r"\$\([^)]*rm[^)]*\)", # Command substitution with rm
99
+ ]
100
+
101
+ for pattern in strict_patterns:
102
+ if re.search(pattern, command):
103
+ logger.warning(f"Dangerous injection pattern '{pattern}' detected in command")
104
+ raise CommandSecurityError("Potentially unsafe pattern detected in command")
105
+
106
+
107
+ def sanitize_command_args(args: List[str]) -> List[str]:
108
+ """
109
+ Sanitize command arguments by shell-quoting them.
110
+
111
+ Args:
112
+ args: List of command arguments
113
+
114
+ Returns:
115
+ List of sanitized arguments
116
+ """
117
+ return [shlex.quote(arg) for arg in args]
118
+
119
+
120
+ def safe_subprocess_run(
121
+ command: str,
122
+ shell: bool = False,
123
+ validate: bool = True,
124
+ timeout: Optional[int] = None,
125
+ **kwargs,
126
+ ) -> subprocess.CompletedProcess:
127
+ """
128
+ Safely execute a subprocess with security validation.
129
+
130
+ Args:
131
+ command: Command to execute (string if shell=True, list if shell=False)
132
+ shell: Whether to use shell execution (discouraged)
133
+ validate: Whether to validate command safety
134
+ timeout: Timeout in seconds
135
+ **kwargs: Additional subprocess arguments
136
+
137
+ Returns:
138
+ CompletedProcess result
139
+
140
+ Raises:
141
+ CommandSecurityError: If command fails security validation
142
+ """
143
+ if validate and shell and isinstance(command, str):
144
+ validate_command_safety(command, allow_shell_features=shell)
145
+
146
+ # Log the command execution
147
+ logger.info(f"Executing command: {str(command)[:100]}...")
148
+
149
+ try:
150
+ if shell:
151
+ # When using shell=True, command should be a string
152
+ result = subprocess.run(command, shell=True, timeout=timeout, **kwargs)
153
+ else:
154
+ # When shell=False, command should be a list
155
+ if isinstance(command, str):
156
+ # Parse the string into a list
157
+ command_list = shlex.split(command)
158
+ else:
159
+ command_list = command
160
+
161
+ result = subprocess.run(command_list, shell=False, timeout=timeout, **kwargs)
162
+
163
+ logger.info(f"Command completed with return code: {result.returncode}")
164
+ return result
165
+
166
+ except subprocess.TimeoutExpired:
167
+ logger.error(f"Command timed out after {timeout} seconds")
168
+ raise
169
+ except Exception as e:
170
+ logger.error(f"Command execution failed: {str(e)}")
171
+ raise
172
+
173
+
174
+ def safe_subprocess_popen(
175
+ command: str, shell: bool = False, validate: bool = True, **kwargs
176
+ ) -> subprocess.Popen:
177
+ """
178
+ Safely create a subprocess.Popen with security validation.
179
+
180
+ Args:
181
+ command: Command to execute
182
+ shell: Whether to use shell execution (discouraged)
183
+ validate: Whether to validate command safety
184
+ **kwargs: Additional Popen arguments
185
+
186
+ Returns:
187
+ Popen process object
188
+
189
+ Raises:
190
+ CommandSecurityError: If command fails security validation
191
+ """
192
+ if validate and shell and isinstance(command, str):
193
+ validate_command_safety(command, allow_shell_features=shell)
194
+
195
+ # Log the command execution
196
+ logger.info(f"Creating Popen for command: {str(command)[:100]}...")
197
+
198
+ if shell:
199
+ # When using shell=True, command should be a string
200
+ return subprocess.Popen(command, shell=True, **kwargs)
201
+ else:
202
+ # When shell=False, command should be a list
203
+ if isinstance(command, str):
204
+ command_list = shlex.split(command)
205
+ else:
206
+ command_list = command
207
+
208
+ return subprocess.Popen(command_list, shell=False, **kwargs)