wcgw 2.8.4__tar.gz → 2.8.6__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 wcgw might be problematic. Click here for more details.

Files changed (173) hide show
  1. wcgw-2.8.6/Dockerfile +41 -0
  2. {wcgw-2.8.4 → wcgw-2.8.6}/PKG-INFO +3 -2
  3. {wcgw-2.8.4 → wcgw-2.8.6}/README.md +2 -1
  4. {wcgw-2.8.4 → wcgw-2.8.6}/pyproject.toml +2 -2
  5. {wcgw-2.8.4 → wcgw-2.8.6}/src/wcgw/__init__.py +1 -2
  6. {wcgw-2.8.4 → wcgw-2.8.6}/src/wcgw/client/file_ops/diff_edit.py +7 -3
  7. {wcgw-2.8.4 → wcgw-2.8.6}/src/wcgw/client/file_ops/search_replace.py +50 -9
  8. {wcgw-2.8.4 → wcgw-2.8.6}/src/wcgw/client/mcp_server/server.py +15 -3
  9. {wcgw-2.8.4 → wcgw-2.8.6}/src/wcgw/client/modes.py +2 -2
  10. {wcgw-2.8.4 → wcgw-2.8.6}/src/wcgw/client/tools.py +97 -89
  11. wcgw-2.8.6/src/wcgw_cli/__init__.py +1 -0
  12. {wcgw-2.8.4/src/wcgw/client → wcgw-2.8.6/src/wcgw_cli}/anthropic_client.py +16 -12
  13. {wcgw-2.8.4/src/wcgw/client → wcgw-2.8.6/src/wcgw_cli}/cli.py +4 -4
  14. {wcgw-2.8.4/src/wcgw/client → wcgw-2.8.6/src/wcgw_cli}/openai_client.py +17 -12
  15. {wcgw-2.8.4/src/wcgw/client → wcgw-2.8.6/src/wcgw_cli}/openai_utils.py +4 -15
  16. {wcgw-2.8.4 → wcgw-2.8.6}/tests/client/file_ops/test_search_replace.py +73 -61
  17. {wcgw-2.8.4 → wcgw-2.8.6}/tests/client/test_anthropic_client_utils.py +34 -30
  18. {wcgw-2.8.4 → wcgw-2.8.6}/tests/client/test_openai_utils.py +1 -1
  19. {wcgw-2.8.4 → wcgw-2.8.6}/tests/client/test_tools_extended.py +52 -33
  20. {wcgw-2.8.4 → wcgw-2.8.6}/tests/client/test_tools_shell.py +73 -73
  21. {wcgw-2.8.4 → wcgw-2.8.6}/tests/client/tools/test_command_validation.py +16 -14
  22. wcgw-2.8.6/tests/client/tools/test_docker_operations.py +39 -0
  23. {wcgw-2.8.4 → wcgw-2.8.6}/tests/client/tools/test_error_handling.py +2 -0
  24. wcgw-2.8.6/tests/client/tools/test_error_handling_full.py +152 -0
  25. wcgw-2.8.6/tests/client/tools/test_execute_bash.py +110 -0
  26. wcgw-2.8.6/tests/client/tools/test_file_errors.py +22 -0
  27. wcgw-2.8.6/tests/client/tools/test_full_coverage.py +66 -0
  28. {wcgw-2.8.4 → wcgw-2.8.6}/tests/client/tools/test_is_int.py +3 -3
  29. wcgw-2.8.6/tests/client/tools/test_large_blocks.py +37 -0
  30. wcgw-2.8.6/tests/client/tools/test_prompt_update.py +48 -0
  31. wcgw-2.8.6/tests/client/tools/test_terminal_output_full.py +114 -0
  32. wcgw-2.8.6/tests/client/tools/test_terminal_output_incremental.py +46 -0
  33. wcgw-2.8.6/tests/client/tools/test_terminal_output_raw.py +57 -0
  34. wcgw-2.8.6/tests/client/tools/test_terminal_sequence.py +50 -0
  35. wcgw-2.8.6/tests/client/tools/test_timeout_handling.py +100 -0
  36. wcgw-2.8.6/tests/client/tools/test_timeout_state.py +30 -0
  37. {wcgw-2.8.4 → wcgw-2.8.6}/tests/client/tools/test_user_interaction.py +21 -15
  38. {wcgw-2.8.4 → wcgw-2.8.6}/tests/test_anthropic_client.py +14 -18
  39. {wcgw-2.8.4 → wcgw-2.8.6}/tests/test_basic.py +7 -9
  40. wcgw-2.8.6/tests/test_initialize.py +250 -0
  41. {wcgw-2.8.4 → wcgw-2.8.6}/uv.lock +1 -1
  42. wcgw-2.8.4/tests/client/tools/test_docker_operations.py +0 -22
  43. wcgw-2.8.4/tests/client/tools/test_execute_bash.py +0 -71
  44. wcgw-2.8.4/tests/client/tools/test_large_blocks.py +0 -21
  45. {wcgw-2.8.4 → wcgw-2.8.6}/.github/workflows/python-publish.yml +0 -0
  46. {wcgw-2.8.4 → wcgw-2.8.6}/.github/workflows/python-tests.yml +0 -0
  47. {wcgw-2.8.4 → wcgw-2.8.6}/.github/workflows/python-types.yml +0 -0
  48. {wcgw-2.8.4 → wcgw-2.8.6}/.gitignore +0 -0
  49. {wcgw-2.8.4 → wcgw-2.8.6}/.gitmodules +0 -0
  50. {wcgw-2.8.4 → wcgw-2.8.6}/.python-version +0 -0
  51. {wcgw-2.8.4 → wcgw-2.8.6}/.vscode/settings.json +0 -0
  52. {wcgw-2.8.4 → wcgw-2.8.6}/LICENSE +0 -0
  53. {wcgw-2.8.4 → wcgw-2.8.6}/gpt_action_json_schema.json +0 -0
  54. {wcgw-2.8.4 → wcgw-2.8.6}/gpt_instructions.txt +0 -0
  55. {wcgw-2.8.4 → wcgw-2.8.6}/openai.md +0 -0
  56. {wcgw-2.8.4 → wcgw-2.8.6}/src/__init__.py +0 -0
  57. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/.git +0 -0
  58. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  59. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  60. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/.github/workflows/main-checks.yml +0 -0
  61. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/.github/workflows/publish-pypi.yml +0 -0
  62. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/.github/workflows/pull-request-checks.yml +0 -0
  63. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/.github/workflows/shared.yml +0 -0
  64. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/.gitignore +0 -0
  65. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/.python-version +0 -0
  66. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/CODE_OF_CONDUCT.md +0 -0
  67. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/CONTRIBUTING.md +0 -0
  68. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/LICENSE +0 -0
  69. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/README.md +0 -0
  70. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/RELEASE.md +0 -0
  71. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/SECURITY.md +0 -0
  72. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/examples/README.md +0 -0
  73. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/examples/servers/simple-prompt/.python-version +0 -0
  74. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/examples/servers/simple-prompt/README.md +0 -0
  75. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/examples/servers/simple-prompt/mcp_simple_prompt/__init__.py +0 -0
  76. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/examples/servers/simple-prompt/mcp_simple_prompt/__main__.py +0 -0
  77. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/examples/servers/simple-prompt/mcp_simple_prompt/server.py +0 -0
  78. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/examples/servers/simple-prompt/pyproject.toml +0 -0
  79. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/examples/servers/simple-resource/.python-version +0 -0
  80. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/examples/servers/simple-resource/README.md +0 -0
  81. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/examples/servers/simple-resource/mcp_simple_resource/__init__.py +0 -0
  82. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/examples/servers/simple-resource/mcp_simple_resource/__main__.py +0 -0
  83. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/examples/servers/simple-resource/mcp_simple_resource/server.py +0 -0
  84. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/examples/servers/simple-resource/pyproject.toml +0 -0
  85. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/examples/servers/simple-tool/.python-version +0 -0
  86. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/examples/servers/simple-tool/README.md +0 -0
  87. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/examples/servers/simple-tool/mcp_simple_tool/__init__.py +0 -0
  88. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/examples/servers/simple-tool/mcp_simple_tool/__main__.py +0 -0
  89. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/examples/servers/simple-tool/mcp_simple_tool/server.py +0 -0
  90. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/examples/servers/simple-tool/pyproject.toml +0 -0
  91. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/pyproject.toml +0 -0
  92. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/src/mcp_wcgw/__init__.py +0 -0
  93. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/src/mcp_wcgw/client/__init__.py +0 -0
  94. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/src/mcp_wcgw/client/__main__.py +0 -0
  95. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/src/mcp_wcgw/client/session.py +0 -0
  96. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/src/mcp_wcgw/client/sse.py +0 -0
  97. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/src/mcp_wcgw/client/stdio.py +0 -0
  98. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/src/mcp_wcgw/py.typed +0 -0
  99. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/src/mcp_wcgw/server/__init__.py +0 -0
  100. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/src/mcp_wcgw/server/__main__.py +0 -0
  101. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/src/mcp_wcgw/server/models.py +0 -0
  102. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/src/mcp_wcgw/server/session.py +0 -0
  103. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/src/mcp_wcgw/server/sse.py +0 -0
  104. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/src/mcp_wcgw/server/stdio.py +0 -0
  105. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/src/mcp_wcgw/server/websocket.py +0 -0
  106. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/src/mcp_wcgw/shared/__init__.py +0 -0
  107. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/src/mcp_wcgw/shared/context.py +0 -0
  108. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/src/mcp_wcgw/shared/exceptions.py +0 -0
  109. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/src/mcp_wcgw/shared/memory.py +0 -0
  110. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/src/mcp_wcgw/shared/progress.py +0 -0
  111. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/src/mcp_wcgw/shared/session.py +0 -0
  112. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/src/mcp_wcgw/shared/version.py +0 -0
  113. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/src/mcp_wcgw/types.py +0 -0
  114. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/tests/__init__.py +0 -0
  115. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/tests/client/__init__.py +0 -0
  116. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/tests/client/test_session.py +0 -0
  117. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/tests/client/test_stdio.py +0 -0
  118. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/tests/conftest.py +0 -0
  119. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/tests/server/__init__.py +0 -0
  120. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/tests/server/test_session.py +0 -0
  121. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/tests/server/test_stdio.py +0 -0
  122. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/tests/shared/test_memory.py +0 -0
  123. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/tests/test_types.py +0 -0
  124. {wcgw-2.8.4 → wcgw-2.8.6}/src/mcp_wcgw_fork/uv.lock +0 -0
  125. {wcgw-2.8.4 → wcgw-2.8.6}/src/wcgw/client/__init__.py +0 -0
  126. {wcgw-2.8.4 → wcgw-2.8.6}/src/wcgw/client/common.py +0 -0
  127. {wcgw-2.8.4 → wcgw-2.8.6}/src/wcgw/client/computer_use.py +0 -0
  128. {wcgw-2.8.4 → wcgw-2.8.6}/src/wcgw/client/diff-instructions.txt +0 -0
  129. {wcgw-2.8.4 → wcgw-2.8.6}/src/wcgw/client/mcp_server/Readme.md +0 -0
  130. {wcgw-2.8.4 → wcgw-2.8.6}/src/wcgw/client/mcp_server/__init__.py +0 -0
  131. {wcgw-2.8.4 → wcgw-2.8.6}/src/wcgw/client/memory.py +0 -0
  132. {wcgw-2.8.4 → wcgw-2.8.6}/src/wcgw/client/repo_ops/display_tree.py +0 -0
  133. {wcgw-2.8.4 → wcgw-2.8.6}/src/wcgw/client/repo_ops/path_prob.py +0 -0
  134. {wcgw-2.8.4 → wcgw-2.8.6}/src/wcgw/client/repo_ops/paths_model.vocab +0 -0
  135. {wcgw-2.8.4 → wcgw-2.8.6}/src/wcgw/client/repo_ops/paths_tokens.model +0 -0
  136. {wcgw-2.8.4 → wcgw-2.8.6}/src/wcgw/client/repo_ops/repo_context.py +0 -0
  137. {wcgw-2.8.4 → wcgw-2.8.6}/src/wcgw/client/sys_utils.py +0 -0
  138. {wcgw-2.8.4 → wcgw-2.8.6}/src/wcgw/relay/serve.py +0 -0
  139. {wcgw-2.8.4 → wcgw-2.8.6}/src/wcgw/relay/static/privacy.txt +0 -0
  140. {wcgw-2.8.4 → wcgw-2.8.6}/src/wcgw/types_.py +0 -0
  141. {wcgw-2.8.4/src/wcgw/client → wcgw-2.8.6/src/wcgw_cli}/__main__.py +0 -0
  142. {wcgw-2.8.4 → wcgw-2.8.6}/static/claude-ss.jpg +0 -0
  143. {wcgw-2.8.4 → wcgw-2.8.6}/static/computer-use.jpg +0 -0
  144. {wcgw-2.8.4 → wcgw-2.8.6}/static/example.jpg +0 -0
  145. {wcgw-2.8.4 → wcgw-2.8.6}/static/rocket-icon.png +0 -0
  146. {wcgw-2.8.4 → wcgw-2.8.6}/static/ss1.png +0 -0
  147. {wcgw-2.8.4 → wcgw-2.8.6}/tests/client/file_ops/test_diff_edit.py +0 -0
  148. {wcgw-2.8.4 → wcgw-2.8.6}/tests/client/repo_ops/__init__.py +0 -0
  149. {wcgw-2.8.4 → wcgw-2.8.6}/tests/client/repo_ops/test_display_tree.py +0 -0
  150. {wcgw-2.8.4 → wcgw-2.8.6}/tests/client/repo_ops/test_display_tree_simple.py +0 -0
  151. {wcgw-2.8.4 → wcgw-2.8.6}/tests/client/repo_ops/test_path_prob.py +0 -0
  152. {wcgw-2.8.4 → wcgw-2.8.6}/tests/client/repo_ops/test_repo_context.py +0 -0
  153. {wcgw-2.8.4 → wcgw-2.8.6}/tests/client/test_memory.py +0 -0
  154. {wcgw-2.8.4 → wcgw-2.8.6}/tests/client/test_tools_basic.py +0 -0
  155. {wcgw-2.8.4 → wcgw-2.8.6}/tests/client/test_tools_file_ops.py +0 -0
  156. {wcgw-2.8.4 → wcgw-2.8.6}/tests/client/test_tools_files.py +0 -0
  157. {wcgw-2.8.4 → wcgw-2.8.6}/tests/client/test_tools_validation.py +0 -0
  158. {wcgw-2.8.4 → wcgw-2.8.6}/tests/client/tools/__init__.py +0 -0
  159. {wcgw-2.8.4 → wcgw-2.8.6}/tests/client/tools/test_file_operations.py +0 -0
  160. {wcgw-2.8.4 → wcgw-2.8.6}/tests/client/tools/test_files/test1.py +0 -0
  161. {wcgw-2.8.4 → wcgw-2.8.6}/tests/client/tools/test_files/test2.py +0 -0
  162. {wcgw-2.8.4 → wcgw-2.8.6}/tests/client/tools/test_files/test_file.py +0 -0
  163. {wcgw-2.8.4 → wcgw-2.8.6}/tests/client/tools/test_knowledge_transfer.py +0 -0
  164. {wcgw-2.8.4 → wcgw-2.8.6}/tests/client/tools/test_render_terminal.py +0 -0
  165. {wcgw-2.8.4 → wcgw-2.8.6}/tests/client/tools/test_terminal_output.py +0 -0
  166. {wcgw-2.8.4 → wcgw-2.8.6}/tests/client/tools/test_write_file.py +0 -0
  167. {wcgw-2.8.4 → wcgw-2.8.6}/tests/conftest.py +0 -0
  168. {wcgw-2.8.4 → wcgw-2.8.6}/tests/test_common.py +0 -0
  169. {wcgw-2.8.4 → wcgw-2.8.6}/tests/test_computer_use.py +0 -0
  170. {wcgw-2.8.4 → wcgw-2.8.6}/tests/test_computer_use_base.py +0 -0
  171. {wcgw-2.8.4 → wcgw-2.8.6}/tests/test_computer_use_shell.py +0 -0
  172. {wcgw-2.8.4 → wcgw-2.8.6}/tests/test_sys_utils.py +0 -0
  173. {wcgw-2.8.4 → wcgw-2.8.6}/tests/test_tools.py +0 -0
wcgw-2.8.6/Dockerfile ADDED
@@ -0,0 +1,41 @@
1
+ # Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile
2
+ # Start from a Python base image with the necessary tools
3
+ FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim AS uv
4
+
5
+ # Set the working directory in the container
6
+ WORKDIR /app
7
+
8
+ # Copy the project's pyproject.toml and lock file for dependency installation
9
+ COPY pyproject.toml /app/
10
+ COPY uv.lock /app/
11
+
12
+ # Enable bytecode compilation and set link mode to copy for dependencies
13
+ ENV UV_COMPILE_BYTECODE=1
14
+ ENV UV_LINK_MODE=copy
15
+
16
+ # Install dependencies
17
+ RUN --mount=type=cache,target=/root/.cache/uv \
18
+ uv sync --frozen --no-install-project --no-dev --no-editable
19
+
20
+ # Copy the entire project into the container
21
+ COPY . /app
22
+
23
+ # Install the project
24
+ RUN --mount=type=cache,target=/root/.cache/uv \
25
+ uv sync --frozen --no-dev --no-editable
26
+
27
+ # Use a smaller image to run the application
28
+ FROM python:3.12-slim-bookworm
29
+
30
+ # Set the working directory in the container
31
+ WORKDIR /app
32
+
33
+ # Copy the installed application from the previous stage
34
+ COPY --from=uv /root/.local /root/.local
35
+ COPY --from=uv --chown=app:app /app/.venv /app/.venv
36
+
37
+ # Add the virtual environment to the PATH
38
+ ENV PATH="/app/.venv/bin:$PATH"
39
+
40
+ # Specify the command to run on container start
41
+ ENTRYPOINT ["wcgw_mcp"]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wcgw
3
- Version: 2.8.4
3
+ Version: 2.8.6
4
4
  Summary: Shell and coding agent on claude and chatgpt
5
5
  Project-URL: Homepage, https://github.com/rusiaaman/wcgw
6
6
  Author-email: Aman Rusia <gapypi@arcfu.com>
@@ -28,6 +28,7 @@ Requires-Dist: websockets>=13.1
28
28
  Description-Content-Type: text/markdown
29
29
 
30
30
  # Shell and Coding agent for Claude and Chatgpt
31
+ Empowering chat applications to code, build and run on your local machine.
31
32
 
32
33
  - Claude - An MCP server on claude desktop for autonomous shell and coding agent. (mac only)
33
34
  - Chatgpt - Allows custom gpt to talk to your shell via a relay server. (linux or mac)
@@ -39,6 +40,7 @@ Description-Content-Type: text/markdown
39
40
  [![Mypy strict](https://github.com/rusiaaman/wcgw/actions/workflows/python-types.yml/badge.svg?branch=main)](https://github.com/rusiaaman/wcgw/actions/workflows/python-types.yml)
40
41
  [![Build](https://github.com/rusiaaman/wcgw/actions/workflows/python-publish.yml/badge.svg)](https://github.com/rusiaaman/wcgw/actions/workflows/python-publish.yml)
41
42
  [![codecov](https://codecov.io/gh/rusiaaman/wcgw/graph/badge.svg)](https://codecov.io/gh/rusiaaman/wcgw)
43
+ [![smithery badge](https://smithery.ai/badge/wcgw)](https://smithery.ai/server/wcgw)
42
44
 
43
45
  ## Updates
44
46
  - [15 Jan 2025] Modes introduced: architect, code-writer, and all powerful wcgw mode.
@@ -128,7 +130,6 @@ _If there's an error in setting up_
128
130
  - Debug the mcp server using `npx @modelcontextprotocol/inspector@0.1.7 uv tool run --from wcgw@latest --python 3.12 wcgw_mcp`
129
131
 
130
132
  ### Alternative configuration using smithery (npx required)
131
- [![smithery badge](https://smithery.ai/badge/wcgw)](https://smithery.ai/server/wcgw)
132
133
 
133
134
  You need to first install uv using homebrew. `brew install uv`
134
135
 
@@ -1,4 +1,5 @@
1
1
  # Shell and Coding agent for Claude and Chatgpt
2
+ Empowering chat applications to code, build and run on your local machine.
2
3
 
3
4
  - Claude - An MCP server on claude desktop for autonomous shell and coding agent. (mac only)
4
5
  - Chatgpt - Allows custom gpt to talk to your shell via a relay server. (linux or mac)
@@ -10,6 +11,7 @@
10
11
  [![Mypy strict](https://github.com/rusiaaman/wcgw/actions/workflows/python-types.yml/badge.svg?branch=main)](https://github.com/rusiaaman/wcgw/actions/workflows/python-types.yml)
11
12
  [![Build](https://github.com/rusiaaman/wcgw/actions/workflows/python-publish.yml/badge.svg)](https://github.com/rusiaaman/wcgw/actions/workflows/python-publish.yml)
12
13
  [![codecov](https://codecov.io/gh/rusiaaman/wcgw/graph/badge.svg)](https://codecov.io/gh/rusiaaman/wcgw)
14
+ [![smithery badge](https://smithery.ai/badge/wcgw)](https://smithery.ai/server/wcgw)
13
15
 
14
16
  ## Updates
15
17
  - [15 Jan 2025] Modes introduced: architect, code-writer, and all powerful wcgw mode.
@@ -99,7 +101,6 @@ _If there's an error in setting up_
99
101
  - Debug the mcp server using `npx @modelcontextprotocol/inspector@0.1.7 uv tool run --from wcgw@latest --python 3.12 wcgw_mcp`
100
102
 
101
103
  ### Alternative configuration using smithery (npx required)
102
- [![smithery badge](https://smithery.ai/badge/wcgw)](https://smithery.ai/server/wcgw)
103
104
 
104
105
  You need to first install uv using homebrew. `brew install uv`
105
106
 
@@ -1,7 +1,7 @@
1
1
  [project]
2
2
  authors = [{ name = "Aman Rusia", email = "gapypi@arcfu.com" }]
3
3
  name = "wcgw"
4
- version = "2.8.4"
4
+ version = "2.8.6"
5
5
  description = "Shell and coding agent on claude and chatgpt"
6
6
  readme = "README.md"
7
7
  requires-python = ">=3.11, <3.13"
@@ -42,7 +42,7 @@ packages = ["src/wcgw", "src/mcp_wcgw_fork/src/mcp_wcgw"]
42
42
  "src/wcgw" = "wcgw"
43
43
 
44
44
  [project.scripts]
45
- wcgw_local = "wcgw:app"
45
+ wcgw_local = "wcgw_cli:app"
46
46
  wcgw = "wcgw:listen"
47
47
  wcgw_relay = "wcgw.relay.serve:run"
48
48
  wcgw_mcp = "wcgw:mcp_server"
@@ -1,3 +1,2 @@
1
- from .client.cli import app
2
- from .client.tools import run as listen
3
1
  from .client.mcp_server import main as mcp_server
2
+ from .client.tools import run as listen
@@ -6,6 +6,10 @@ from typing import Callable, DefaultDict, Literal, Optional
6
6
  TOLERANCE_TYPES = Literal["SILENT", "WARNING", "ERROR"]
7
7
 
8
8
 
9
+ class SearchReplaceMatchError(Exception):
10
+ pass
11
+
12
+
9
13
  @dataclass
10
14
  class Tolerance:
11
15
  line_process: Callable[[str], str]
@@ -45,7 +49,7 @@ class FileEditOutput:
45
49
  Got error while processing the following search block:
46
50
  ---
47
51
  ```
48
- {'\n'.join(search_)}
52
+ {"\n".join(search_)}
49
53
  ```
50
54
  ---
51
55
  Error:
@@ -53,7 +57,7 @@ Error:
53
57
  ---
54
58
  """)
55
59
  if len(errors) >= max_errors:
56
- raise Exception("\n".join(errors))
60
+ raise SearchReplaceMatchError("\n".join(errors))
57
61
  if last_idx < span.start:
58
62
  new_lines.extend(self.original_content[last_idx : span.start])
59
63
 
@@ -64,7 +68,7 @@ Error:
64
68
  new_lines.extend(self.original_content[last_idx:])
65
69
 
66
70
  if errors:
67
- raise Exception("\n".join(errors))
71
+ raise SearchReplaceMatchError("\n".join(errors))
68
72
 
69
73
  return new_lines, set(warnings)
70
74
 
@@ -1,32 +1,71 @@
1
1
  import re
2
2
  from typing import Callable
3
3
 
4
- from .diff_edit import FileEditInput, FileEditOutput
4
+ from .diff_edit import FileEditInput, FileEditOutput, SearchReplaceMatchError
5
5
 
6
+ # Global regex patterns
7
+ SEARCH_MARKER = re.compile(r"^<<<<<<+\s*SEARCH\s*$")
8
+ DIVIDER_MARKER = re.compile(r"^======*\s*$")
9
+ REPLACE_MARKER = re.compile(r"^>>>>>>+\s*REPLACE\s*$")
10
+
11
+ class SearchReplaceSyntaxError(Exception):
12
+ def __init__(self, message: str):
13
+ message =f"""Got syntax error while parsing search replace blocks:
14
+ {message}
15
+ ---
16
+
17
+ Make sure blocks are in correct sequence, and the markers are in separate lines:
18
+
19
+ <{'<<<<<< SEARCH'}
20
+ example old
21
+ =======
22
+ example new
23
+ >{'>>>>>> REPLACE'}
24
+
25
+ """
26
+ super().__init__(message)
6
27
 
7
28
  def search_replace_edit(
8
29
  lines: list[str], original_content: str, logger: Callable[[str], object]
9
30
  ) -> tuple[str, str]:
10
31
  if not lines:
11
- raise Exception("Error: No input to search replace edit")
32
+ raise SearchReplaceSyntaxError("Error: No input to search replace edit")
33
+
12
34
  original_lines = original_content.split("\n")
13
35
  n_lines = len(lines)
14
36
  i = 0
15
37
  search_replace_blocks = list[tuple[list[str], list[str]]]()
38
+
16
39
  while i < n_lines:
17
- if re.match(r"^<<<<<<+\s*SEARCH\s*$", lines[i]):
40
+ if SEARCH_MARKER.match(lines[i]):
41
+ line_num = i + 1
18
42
  search_block = []
19
43
  i += 1
20
- while i < n_lines and not re.match(r"^======*\s*$", lines[i]):
44
+
45
+ while i < n_lines and not DIVIDER_MARKER.match(lines[i]):
46
+ if SEARCH_MARKER.match(lines[i]) or REPLACE_MARKER.match(lines[i]):
47
+ raise SearchReplaceSyntaxError(f"Line {i+1}: Found stray marker in SEARCH block: {lines[i]}")
21
48
  search_block.append(lines[i])
22
49
  i += 1
23
- i += 1
50
+
51
+ if i >= n_lines:
52
+ raise SearchReplaceSyntaxError(f"Line {line_num}: Unclosed SEARCH block - missing ======= marker")
53
+
24
54
  if not search_block:
25
- raise Exception("SEARCH block can not be empty")
55
+ raise SearchReplaceSyntaxError(f"Line {line_num}: SEARCH block cannot be empty")
56
+
57
+ i += 1
26
58
  replace_block = []
27
- while i < n_lines and not re.match(r"^>>>>>>+\s*REPLACE\s*$", lines[i]):
59
+
60
+ while i < n_lines and not REPLACE_MARKER.match(lines[i]):
61
+ if SEARCH_MARKER.match(lines[i]) or DIVIDER_MARKER.match(lines[i]):
62
+ raise SearchReplaceSyntaxError(f"Line {i+1}: Found stray marker in REPLACE block: {lines[i]}")
28
63
  replace_block.append(lines[i])
29
64
  i += 1
65
+
66
+ if i >= n_lines:
67
+ raise SearchReplaceSyntaxError(f"Line {line_num}: Unclosed block - missing REPLACE marker")
68
+
30
69
  i += 1
31
70
 
32
71
  for line in search_block:
@@ -38,10 +77,12 @@ def search_replace_edit(
38
77
 
39
78
  search_replace_blocks.append((search_block, replace_block))
40
79
  else:
80
+ if REPLACE_MARKER.match(lines[i]) or DIVIDER_MARKER.match(lines[i]):
81
+ raise SearchReplaceSyntaxError(f"Line {i+1}: Found stray marker outside block: {lines[i]}")
41
82
  i += 1
42
83
 
43
84
  if not search_replace_blocks:
44
- raise Exception(
85
+ raise SearchReplaceSyntaxError(
45
86
  "No valid search replace blocks found, ensure your SEARCH/REPLACE blocks are formatted correctly"
46
87
  )
47
88
 
@@ -80,7 +121,7 @@ def greedy_context_replace(
80
121
  if len(best_matches) > 1:
81
122
  # Duplicate found, try to ground using previous blocks.
82
123
  if current_block_offset == 0:
83
- raise Exception(f"""
124
+ raise SearchReplaceMatchError(f"""
84
125
  The following block matched more than once:
85
126
  ---
86
127
  ```
@@ -1,15 +1,15 @@
1
1
  import importlib
2
2
  import json
3
+ import logging
3
4
  import os
4
5
  from typing import Any
5
6
 
6
- from pydantic import AnyUrl, ValidationError
7
-
8
7
  import mcp_wcgw.server.stdio
9
8
  import mcp_wcgw.types as types
10
9
  from mcp_wcgw.server import NotificationOptions, Server
11
10
  from mcp_wcgw.server.models import InitializationOptions
12
11
  from mcp_wcgw.types import Tool as ToolParam
12
+ from pydantic import AnyUrl, ValidationError
13
13
 
14
14
  from ...types_ import (
15
15
  BashCommand,
@@ -35,6 +35,18 @@ COMPUTER_USE_ON_DOCKER_ENABLED = False
35
35
 
36
36
  server = Server("wcgw")
37
37
 
38
+ # Log only time stamp
39
+ logging.basicConfig(level=logging.INFO, format="%(asctime)s: %(message)s")
40
+ logger = logging.getLogger("wcgw")
41
+
42
+
43
+ class Console:
44
+ def print(self, msg: str, *args: Any, **kwargs: Any) -> None:
45
+ logger.info(msg)
46
+
47
+ def log(self, msg: str, *args: Any, **kwargs: Any) -> None:
48
+ logger.info(msg)
49
+
38
50
 
39
51
  @server.list_resources() # type: ignore
40
52
  async def handle_list_resources() -> list[types.Resource]:
@@ -301,7 +313,7 @@ async def main(computer_use: bool) -> None:
301
313
  tools.TIMEOUT = SLEEP_TIME_MAX_S
302
314
  tools.TIMEOUT_WHILE_OUTPUT = 55
303
315
  tools.OUTPUT_WAIT_PATIENCE = 5
304
- tools.console = tools.DisableConsole()
316
+ tools.console = Console()
305
317
 
306
318
  if computer_use:
307
319
  COMPUTER_USE_ON_DOCKER_ENABLED = True
@@ -51,7 +51,7 @@ def code_writer_prompt(
51
51
  allowed_commands: Literal["all"] | list[str],
52
52
  ) -> str:
53
53
  base = """
54
- You have to run in "code_writer" mode.
54
+ You are now running in "code_writer" mode.
55
55
  """
56
56
 
57
57
  path_prompt = """
@@ -134,7 +134,7 @@ Additional instructions:
134
134
 
135
135
 
136
136
  """
137
- ARCHITECT_PROMPT = """You have to run in "architect" mode. This means
137
+ ARCHITECT_PROMPT = """You are now running in "architect" mode. This means
138
138
  - You are not allowed to edit or update any file. You are not allowed to create any file.
139
139
  - You are not allowed to run any commands that may change disk, system configuration, packages or environment. Only read-only commands are allowed.
140
140
  - Only run commands that allows you to explore the repository, understand the system or read anything of relevance.
@@ -20,6 +20,7 @@ from typing import (
20
20
  Literal,
21
21
  Optional,
22
22
  ParamSpec,
23
+ Protocol,
23
24
  Type,
24
25
  TypeVar,
25
26
  )
@@ -73,17 +74,13 @@ from .repo_ops.repo_context import get_repo_context
73
74
  from .sys_utils import command_run
74
75
 
75
76
 
76
- class DisableConsole:
77
- def print(self, *args, **kwargs): # type: ignore
78
- pass
77
+ class Console(Protocol):
78
+ def print(self, msg: str, *args: Any, **kwargs: Any) -> None: ...
79
79
 
80
- def log(self, *args, **kwargs): # type: ignore
81
- pass
80
+ def log(self, msg: str, *args: Any, **kwargs: Any) -> None: ...
82
81
 
83
82
 
84
- console: rich.console.Console | DisableConsole = rich.console.Console(
85
- style="magenta", highlight=False, markup=False
86
- )
83
+ console: Console = rich.console.Console(style="magenta", highlight=False, markup=False)
87
84
 
88
85
  TIMEOUT = 5
89
86
  TIMEOUT_WHILE_OUTPUT = 20
@@ -133,8 +130,7 @@ def ask_confirmation(prompt: Confirmation) -> str:
133
130
  return "Yes" if response.lower() == "y" else "No"
134
131
 
135
132
 
136
- PROMPT_CONST = "#@wcgw@#"
137
- PROMPT = PROMPT_CONST
133
+ PROMPT_CONST = "#" + "@wcgw@#"
138
134
 
139
135
 
140
136
  def start_shell(is_restricted_mode: bool, initial_dir: str) -> pexpect.spawn: # type: ignore
@@ -145,36 +141,36 @@ def start_shell(is_restricted_mode: bool, initial_dir: str) -> pexpect.spawn: #
145
141
  try:
146
142
  shell = pexpect.spawn(
147
143
  cmd,
148
- env={**os.environ, **{"PS1": PROMPT}}, # type: ignore[arg-type]
144
+ env={**os.environ, **{"PS1": PROMPT_CONST}}, # type: ignore[arg-type]
149
145
  echo=False,
150
146
  encoding="utf-8",
151
147
  timeout=TIMEOUT,
152
148
  cwd=initial_dir,
153
149
  )
154
150
  shell.sendline(
155
- f"export PROMPT_COMMAND= PS1={PROMPT}"
151
+ f"export PROMPT_COMMAND= PS1={PROMPT_CONST}"
156
152
  ) # Unset prompt command to avoid interfering
157
- shell.expect(PROMPT, timeout=TIMEOUT)
153
+ shell.expect(PROMPT_CONST, timeout=TIMEOUT)
158
154
  except Exception as e:
159
155
  console.print(traceback.format_exc())
160
156
  console.log(f"Error starting shell: {e}. Retrying without rc ...")
161
157
 
162
158
  shell = pexpect.spawn(
163
159
  "/bin/bash --noprofile --norc",
164
- env={**os.environ, **{"PS1": PROMPT}}, # type: ignore[arg-type]
160
+ env={**os.environ, **{"PS1": PROMPT_CONST}}, # type: ignore[arg-type]
165
161
  echo=False,
166
162
  encoding="utf-8",
167
163
  timeout=TIMEOUT,
168
164
  )
169
- shell.sendline(f"export PS1={PROMPT}")
170
- shell.expect(PROMPT, timeout=TIMEOUT)
165
+ shell.sendline(f"export PS1={PROMPT_CONST}")
166
+ shell.expect(PROMPT_CONST, timeout=TIMEOUT)
171
167
 
172
168
  shell.sendline("stty -icanon -echo")
173
- shell.expect(PROMPT, timeout=TIMEOUT)
169
+ shell.expect(PROMPT_CONST, timeout=TIMEOUT)
174
170
  shell.sendline("set +o pipefail")
175
- shell.expect(PROMPT, timeout=TIMEOUT)
171
+ shell.expect(PROMPT_CONST, timeout=TIMEOUT)
176
172
  shell.sendline("export GIT_PAGER=cat PAGER=cat")
177
- shell.expect(PROMPT, timeout=TIMEOUT)
173
+ shell.expect(PROMPT_CONST, timeout=TIMEOUT)
178
174
  return shell
179
175
 
180
176
 
@@ -186,42 +182,6 @@ def _is_int(mystr: str) -> bool:
186
182
  return False
187
183
 
188
184
 
189
- def _ensure_env_and_bg_jobs(shell: pexpect.spawn) -> Optional[int]: # type: ignore
190
- if PROMPT != PROMPT_CONST:
191
- return None
192
- # First reset the prompt in case venv was sourced or other reasons.
193
- shell.sendline(f"export PS1={PROMPT}")
194
- shell.expect(PROMPT, timeout=0.2)
195
- # Reset echo also if it was enabled
196
- shell.sendline("stty -icanon -echo")
197
- shell.expect(PROMPT, timeout=0.2)
198
- shell.sendline("set +o pipefail")
199
- shell.expect(PROMPT, timeout=0.2)
200
- shell.sendline("export GIT_PAGER=cat PAGER=cat")
201
- shell.expect(PROMPT, timeout=0.2)
202
- shell.sendline("jobs | wc -l")
203
- before = ""
204
-
205
- while not _is_int(before): # Consume all previous output
206
- try:
207
- shell.expect(PROMPT, timeout=0.2)
208
- except pexpect.TIMEOUT:
209
- console.print(f"Couldn't get exit code, before: {before}")
210
- raise
211
-
212
- before_val = shell.before
213
- if not isinstance(before_val, str):
214
- before_val = str(before_val)
215
- assert isinstance(before_val, str)
216
- before_lines = render_terminal_output(before_val)
217
- before = "\n".join(before_lines).strip()
218
-
219
- try:
220
- return int(before)
221
- except ValueError:
222
- raise ValueError(f"Malformed output: {before}")
223
-
224
-
225
185
  BASH_CLF_OUTPUT = Literal["repl", "pending"]
226
186
 
227
187
 
@@ -245,7 +205,7 @@ class BashState:
245
205
  )
246
206
  self._mode = mode or Modes.wcgw
247
207
  self._whitelist_for_overwrite: set[str] = whitelist_for_overwrite or set()
248
-
208
+ self._prompt = PROMPT_CONST
249
209
  self._init_shell()
250
210
 
251
211
  @property
@@ -264,7 +224,47 @@ class BashState:
264
224
  def write_if_empty_mode(self) -> WriteIfEmptyMode:
265
225
  return self._write_if_empty_mode
266
226
 
227
+ def ensure_env_and_bg_jobs(self) -> Optional[int]:
228
+ if self._prompt != PROMPT_CONST:
229
+ return None
230
+ shell = self.shell
231
+ # First reset the prompt in case venv was sourced or other reasons.
232
+ shell.sendline(f"export PS1={self._prompt}")
233
+ shell.expect(self._prompt, timeout=0.2)
234
+ # Reset echo also if it was enabled
235
+ shell.sendline("stty -icanon -echo")
236
+ shell.expect(self._prompt, timeout=0.2)
237
+ shell.sendline("set +o pipefail")
238
+ shell.expect(self._prompt, timeout=0.2)
239
+ shell.sendline("export GIT_PAGER=cat PAGER=cat")
240
+ shell.expect(self._prompt, timeout=0.2)
241
+ shell.sendline("jobs | wc -l")
242
+ before = ""
243
+ counts = 0
244
+ while not _is_int(before): # Consume all previous output
245
+ try:
246
+ shell.expect(self._prompt, timeout=0.2)
247
+ except pexpect.TIMEOUT:
248
+ console.print(f"Couldn't get exit code, before: {before}")
249
+ raise
250
+
251
+ before_val = shell.before
252
+ if not isinstance(before_val, str):
253
+ before_val = str(before_val)
254
+ assert isinstance(before_val, str)
255
+ before_lines = render_terminal_output(before_val)
256
+ before = "\n".join(before_lines).strip()
257
+ counts += 1
258
+ if counts > 100:
259
+ raise ValueError("Error in understanding shell output. This shouldn't happen, likely shell is in a bad state, please reset it")
260
+
261
+ try:
262
+ return int(before)
263
+ except ValueError:
264
+ raise ValueError(f"Malformed output: {before}")
265
+
267
266
  def _init_shell(self) -> None:
267
+ self._prompt = PROMPT_CONST
268
268
  self._state: Literal["repl"] | datetime.datetime = "repl"
269
269
  self._is_in_docker: Optional[str] = ""
270
270
  # Ensure self._cwd exists
@@ -273,11 +273,11 @@ class BashState:
273
273
  self._bash_command_mode.bash_mode == "restricted_mode",
274
274
  self._cwd,
275
275
  )
276
-
276
+
277
277
  self._pending_output = ""
278
278
 
279
279
  # Get exit info to ensure shell is ready
280
- _ensure_env_and_bg_jobs(self._shell)
280
+ self.ensure_env_and_bg_jobs()
281
281
 
282
282
  @property
283
283
  def shell(self) -> pexpect.spawn: # type: ignore
@@ -309,9 +309,13 @@ class BashState:
309
309
  def cwd(self) -> str:
310
310
  return self._cwd
311
311
 
312
+ @property
313
+ def prompt(self) -> str:
314
+ return self._prompt
315
+
312
316
  def update_cwd(self) -> str:
313
317
  self.shell.sendline("pwd")
314
- self.shell.expect(PROMPT, timeout=0.2)
318
+ self.shell.expect(self._prompt, timeout=0.2)
315
319
  before_val = self.shell.before
316
320
  if not isinstance(before_val, str):
317
321
  before_val = str(before_val)
@@ -391,6 +395,30 @@ class BashState:
391
395
  def pending_output(self) -> str:
392
396
  return self._pending_output
393
397
 
398
+ def update_repl_prompt(self, command: str) -> bool:
399
+ if re.match(r"^wcgw_update_prompt\(\)$", command.strip()):
400
+ self.shell.sendintr()
401
+ index = self.shell.expect([self._prompt, pexpect.TIMEOUT], timeout=0.2)
402
+ if index == 0:
403
+ return True
404
+ before = self.shell.before or ""
405
+ assert before, "Something went wrong updating repl prompt"
406
+ self._prompt = before.split("\n")[-1].strip()
407
+ # Escape all regex
408
+ self._prompt = re.escape(self._prompt)
409
+ console.print(f"Trying to update prompt to: {self._prompt.encode()!r}")
410
+ index = 0
411
+ counts = 0
412
+ while index == 0:
413
+ # Consume all REPL prompts till now
414
+ index = self.shell.expect([self._prompt, pexpect.TIMEOUT], timeout=0.2)
415
+ counts += 1
416
+ if counts > 100:
417
+ raise ValueError("Error in understanding shell output. This shouldn't happen, likely shell is in a bad state, please reset it")
418
+ console.print(f"Prompt updated to: {self._prompt}")
419
+ return True
420
+ return False
421
+
394
422
 
395
423
  BASH_STATE = BashState(os.getcwd(), None, None, None, None)
396
424
  INITIALIZED = False
@@ -547,28 +575,6 @@ WAITING_INPUT_MESSAGE = """A command is already running. NOTE: You can't run mul
547
575
  """
548
576
 
549
577
 
550
- def update_repl_prompt(command: str) -> bool:
551
- global PROMPT
552
- if re.match(r"^wcgw_update_prompt\(\)$", command.strip()):
553
- BASH_STATE.shell.sendintr()
554
- index = BASH_STATE.shell.expect([PROMPT, pexpect.TIMEOUT], timeout=0.2)
555
- if index == 0:
556
- return True
557
- before = BASH_STATE.shell.before or ""
558
- assert before, "Something went wrong updating repl prompt"
559
- PROMPT = before.split("\n")[-1].strip()
560
- # Escape all regex
561
- PROMPT = re.escape(PROMPT)
562
- console.print(f"Trying to update prompt to: {PROMPT.encode()!r}")
563
- index = 0
564
- while index == 0:
565
- # Consume all REPL prompts till now
566
- index = BASH_STATE.shell.expect([PROMPT, pexpect.TIMEOUT], timeout=0.2)
567
- console.print(f"Prompt updated to: {PROMPT}")
568
- return True
569
- return False
570
-
571
-
572
578
  def get_status() -> str:
573
579
  status = "\n\n---\n\n"
574
580
  if BASH_STATE.state == "pending":
@@ -576,7 +582,7 @@ def get_status() -> str:
576
582
  status += "running for = " + BASH_STATE.get_pending_for() + "\n"
577
583
  status += "cwd = " + BASH_STATE.cwd + "\n"
578
584
  else:
579
- bg_jobs = _ensure_env_and_bg_jobs(BASH_STATE.shell)
585
+ bg_jobs = BASH_STATE.ensure_env_and_bg_jobs()
580
586
  bg_desc = ""
581
587
  if bg_jobs and bg_jobs > 0:
582
588
  bg_desc = f"; {bg_jobs} background jobs running"
@@ -646,7 +652,7 @@ def execute_bash(
646
652
  if isinstance(bash_arg, BashCommand):
647
653
  if BASH_STATE.bash_command_mode.allowed_commands == "none":
648
654
  return "Error: BashCommand not allowed in current mode", 0.0
649
- updated_repl_mode = update_repl_prompt(bash_arg.command)
655
+ updated_repl_mode = BASH_STATE.update_repl_prompt(bash_arg.command)
650
656
  if updated_repl_mode:
651
657
  BASH_STATE.set_repl()
652
658
  response = (
@@ -723,7 +729,7 @@ def execute_bash(
723
729
  0.0,
724
730
  )
725
731
 
726
- updated_repl_mode = update_repl_prompt(bash_arg.send_text)
732
+ updated_repl_mode = BASH_STATE.update_repl_prompt(bash_arg.send_text)
727
733
  if updated_repl_mode:
728
734
  BASH_STATE.set_repl()
729
735
  response = "Prompt updated, you can execute REPL lines using BashCommand now"
@@ -739,11 +745,11 @@ def execute_bash(
739
745
 
740
746
  except KeyboardInterrupt:
741
747
  BASH_STATE.shell.sendintr()
742
- BASH_STATE.shell.expect(PROMPT)
748
+ BASH_STATE.shell.expect(BASH_STATE.prompt)
743
749
  return "---\n\nFailure: user interrupted the execution", 0.0
744
750
 
745
751
  wait = min(timeout_s or TIMEOUT, TIMEOUT_WHILE_OUTPUT)
746
- index = BASH_STATE.shell.expect([PROMPT, pexpect.TIMEOUT], timeout=wait)
752
+ index = BASH_STATE.shell.expect([BASH_STATE.prompt, pexpect.TIMEOUT], timeout=wait)
747
753
  if index == 1:
748
754
  text = BASH_STATE.shell.before or ""
749
755
  incremental_text = _incremental_text(text, BASH_STATE.pending_output)
@@ -757,7 +763,9 @@ def execute_bash(
757
763
  patience -= 1
758
764
  itext = incremental_text
759
765
  while remaining > 0 and patience > 0:
760
- index = BASH_STATE.shell.expect([PROMPT, pexpect.TIMEOUT], timeout=wait)
766
+ index = BASH_STATE.shell.expect(
767
+ [BASH_STATE.prompt, pexpect.TIMEOUT], timeout=wait
768
+ )
761
769
  if index == 0:
762
770
  second_wait_success = True
763
771
  break
@@ -1313,7 +1321,7 @@ def get_tool_output(
1313
1321
  # At this point we should go into the docker env
1314
1322
  res, _ = execute_bash(
1315
1323
  enc,
1316
- BashInteraction(send_text=f"export PS1={PROMPT}"),
1324
+ BashInteraction(send_text=f"export PS1={BASH_STATE.prompt}"),
1317
1325
  None,
1318
1326
  0.2,
1319
1327
  )
@@ -1462,7 +1470,7 @@ def read_files(file_paths: list[str], max_tokens: Optional[int]) -> str:
1462
1470
  if truncated or (max_tokens and max_tokens <= 0):
1463
1471
  not_reading = file_paths[i + 1 :]
1464
1472
  if not_reading:
1465
- message += f'\nNot reading the rest of the files: {", ".join(not_reading)} due to token limit, please call again'
1473
+ message += f"\nNot reading the rest of the files: {', '.join(not_reading)} due to token limit, please call again"
1466
1474
  break
1467
1475
  else:
1468
1476
  message += "```"
@@ -0,0 +1 @@
1
+ from .cli import app