wcgw 2.8.3__tar.gz → 2.8.5__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 (162) hide show
  1. wcgw-2.8.5/Dockerfile +41 -0
  2. {wcgw-2.8.3 → wcgw-2.8.5}/PKG-INFO +2 -2
  3. {wcgw-2.8.3 → wcgw-2.8.5}/pyproject.toml +2 -2
  4. {wcgw-2.8.3 → wcgw-2.8.5}/src/wcgw/__init__.py +1 -2
  5. {wcgw-2.8.3 → wcgw-2.8.5}/src/wcgw/client/mcp_server/server.py +15 -3
  6. {wcgw-2.8.3 → wcgw-2.8.5}/src/wcgw/client/modes.py +3 -3
  7. {wcgw-2.8.3 → wcgw-2.8.5}/src/wcgw/client/tools.py +5 -8
  8. wcgw-2.8.5/src/wcgw_cli/__init__.py +1 -0
  9. {wcgw-2.8.3/src/wcgw/client → wcgw-2.8.5/src/wcgw_cli}/anthropic_client.py +16 -12
  10. {wcgw-2.8.3/src/wcgw/client → wcgw-2.8.5/src/wcgw_cli}/cli.py +4 -4
  11. {wcgw-2.8.3/src/wcgw/client → wcgw-2.8.5/src/wcgw_cli}/openai_client.py +17 -12
  12. {wcgw-2.8.3/src/wcgw/client → wcgw-2.8.5/src/wcgw_cli}/openai_utils.py +4 -15
  13. {wcgw-2.8.3 → wcgw-2.8.5}/tests/client/test_anthropic_client_utils.py +34 -30
  14. {wcgw-2.8.3 → wcgw-2.8.5}/tests/client/test_openai_utils.py +1 -1
  15. {wcgw-2.8.3 → wcgw-2.8.5}/tests/client/test_tools_extended.py +1 -2
  16. wcgw-2.8.5/tests/client/tools/test_full_coverage.py +66 -0
  17. wcgw-2.8.5/tests/client/tools/test_terminal_output_full.py +114 -0
  18. {wcgw-2.8.3 → wcgw-2.8.5}/tests/test_anthropic_client.py +14 -18
  19. {wcgw-2.8.3 → wcgw-2.8.5}/tests/test_basic.py +7 -9
  20. wcgw-2.8.5/tests/test_initialize.py +281 -0
  21. {wcgw-2.8.3 → wcgw-2.8.5}/uv.lock +1 -1
  22. {wcgw-2.8.3 → wcgw-2.8.5}/.github/workflows/python-publish.yml +0 -0
  23. {wcgw-2.8.3 → wcgw-2.8.5}/.github/workflows/python-tests.yml +0 -0
  24. {wcgw-2.8.3 → wcgw-2.8.5}/.github/workflows/python-types.yml +0 -0
  25. {wcgw-2.8.3 → wcgw-2.8.5}/.gitignore +0 -0
  26. {wcgw-2.8.3 → wcgw-2.8.5}/.gitmodules +0 -0
  27. {wcgw-2.8.3 → wcgw-2.8.5}/.python-version +0 -0
  28. {wcgw-2.8.3 → wcgw-2.8.5}/.vscode/settings.json +0 -0
  29. {wcgw-2.8.3 → wcgw-2.8.5}/LICENSE +0 -0
  30. {wcgw-2.8.3 → wcgw-2.8.5}/README.md +1 -1
  31. {wcgw-2.8.3 → wcgw-2.8.5}/gpt_action_json_schema.json +0 -0
  32. {wcgw-2.8.3 → wcgw-2.8.5}/gpt_instructions.txt +0 -0
  33. {wcgw-2.8.3 → wcgw-2.8.5}/openai.md +0 -0
  34. {wcgw-2.8.3 → wcgw-2.8.5}/src/__init__.py +0 -0
  35. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/.git +0 -0
  36. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  37. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  38. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/.github/workflows/main-checks.yml +0 -0
  39. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/.github/workflows/publish-pypi.yml +0 -0
  40. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/.github/workflows/pull-request-checks.yml +0 -0
  41. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/.github/workflows/shared.yml +0 -0
  42. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/.gitignore +0 -0
  43. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/.python-version +0 -0
  44. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/CODE_OF_CONDUCT.md +0 -0
  45. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/CONTRIBUTING.md +0 -0
  46. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/LICENSE +0 -0
  47. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/README.md +0 -0
  48. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/RELEASE.md +0 -0
  49. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/SECURITY.md +0 -0
  50. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/examples/README.md +0 -0
  51. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/examples/servers/simple-prompt/.python-version +0 -0
  52. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/examples/servers/simple-prompt/README.md +0 -0
  53. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/examples/servers/simple-prompt/mcp_simple_prompt/__init__.py +0 -0
  54. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/examples/servers/simple-prompt/mcp_simple_prompt/__main__.py +0 -0
  55. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/examples/servers/simple-prompt/mcp_simple_prompt/server.py +0 -0
  56. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/examples/servers/simple-prompt/pyproject.toml +0 -0
  57. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/examples/servers/simple-resource/.python-version +0 -0
  58. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/examples/servers/simple-resource/README.md +0 -0
  59. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/examples/servers/simple-resource/mcp_simple_resource/__init__.py +0 -0
  60. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/examples/servers/simple-resource/mcp_simple_resource/__main__.py +0 -0
  61. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/examples/servers/simple-resource/mcp_simple_resource/server.py +0 -0
  62. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/examples/servers/simple-resource/pyproject.toml +0 -0
  63. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/examples/servers/simple-tool/.python-version +0 -0
  64. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/examples/servers/simple-tool/README.md +0 -0
  65. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/examples/servers/simple-tool/mcp_simple_tool/__init__.py +0 -0
  66. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/examples/servers/simple-tool/mcp_simple_tool/__main__.py +0 -0
  67. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/examples/servers/simple-tool/mcp_simple_tool/server.py +0 -0
  68. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/examples/servers/simple-tool/pyproject.toml +0 -0
  69. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/pyproject.toml +0 -0
  70. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/src/mcp_wcgw/__init__.py +0 -0
  71. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/src/mcp_wcgw/client/__init__.py +0 -0
  72. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/src/mcp_wcgw/client/__main__.py +0 -0
  73. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/src/mcp_wcgw/client/session.py +0 -0
  74. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/src/mcp_wcgw/client/sse.py +0 -0
  75. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/src/mcp_wcgw/client/stdio.py +0 -0
  76. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/src/mcp_wcgw/py.typed +0 -0
  77. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/src/mcp_wcgw/server/__init__.py +0 -0
  78. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/src/mcp_wcgw/server/__main__.py +0 -0
  79. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/src/mcp_wcgw/server/models.py +0 -0
  80. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/src/mcp_wcgw/server/session.py +0 -0
  81. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/src/mcp_wcgw/server/sse.py +0 -0
  82. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/src/mcp_wcgw/server/stdio.py +0 -0
  83. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/src/mcp_wcgw/server/websocket.py +0 -0
  84. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/src/mcp_wcgw/shared/__init__.py +0 -0
  85. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/src/mcp_wcgw/shared/context.py +0 -0
  86. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/src/mcp_wcgw/shared/exceptions.py +0 -0
  87. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/src/mcp_wcgw/shared/memory.py +0 -0
  88. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/src/mcp_wcgw/shared/progress.py +0 -0
  89. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/src/mcp_wcgw/shared/session.py +0 -0
  90. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/src/mcp_wcgw/shared/version.py +0 -0
  91. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/src/mcp_wcgw/types.py +0 -0
  92. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/tests/__init__.py +0 -0
  93. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/tests/client/__init__.py +0 -0
  94. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/tests/client/test_session.py +0 -0
  95. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/tests/client/test_stdio.py +0 -0
  96. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/tests/conftest.py +0 -0
  97. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/tests/server/__init__.py +0 -0
  98. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/tests/server/test_session.py +0 -0
  99. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/tests/server/test_stdio.py +0 -0
  100. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/tests/shared/test_memory.py +0 -0
  101. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/tests/test_types.py +0 -0
  102. {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/uv.lock +0 -0
  103. {wcgw-2.8.3 → wcgw-2.8.5}/src/wcgw/client/__init__.py +0 -0
  104. {wcgw-2.8.3 → wcgw-2.8.5}/src/wcgw/client/common.py +0 -0
  105. {wcgw-2.8.3 → wcgw-2.8.5}/src/wcgw/client/computer_use.py +0 -0
  106. {wcgw-2.8.3 → wcgw-2.8.5}/src/wcgw/client/diff-instructions.txt +0 -0
  107. {wcgw-2.8.3 → wcgw-2.8.5}/src/wcgw/client/file_ops/diff_edit.py +0 -0
  108. {wcgw-2.8.3 → wcgw-2.8.5}/src/wcgw/client/file_ops/search_replace.py +0 -0
  109. {wcgw-2.8.3 → wcgw-2.8.5}/src/wcgw/client/mcp_server/Readme.md +0 -0
  110. {wcgw-2.8.3 → wcgw-2.8.5}/src/wcgw/client/mcp_server/__init__.py +0 -0
  111. {wcgw-2.8.3 → wcgw-2.8.5}/src/wcgw/client/memory.py +0 -0
  112. {wcgw-2.8.3 → wcgw-2.8.5}/src/wcgw/client/repo_ops/display_tree.py +0 -0
  113. {wcgw-2.8.3 → wcgw-2.8.5}/src/wcgw/client/repo_ops/path_prob.py +0 -0
  114. {wcgw-2.8.3 → wcgw-2.8.5}/src/wcgw/client/repo_ops/paths_model.vocab +0 -0
  115. {wcgw-2.8.3 → wcgw-2.8.5}/src/wcgw/client/repo_ops/paths_tokens.model +0 -0
  116. {wcgw-2.8.3 → wcgw-2.8.5}/src/wcgw/client/repo_ops/repo_context.py +0 -0
  117. {wcgw-2.8.3 → wcgw-2.8.5}/src/wcgw/client/sys_utils.py +0 -0
  118. {wcgw-2.8.3 → wcgw-2.8.5}/src/wcgw/relay/serve.py +0 -0
  119. {wcgw-2.8.3 → wcgw-2.8.5}/src/wcgw/relay/static/privacy.txt +0 -0
  120. {wcgw-2.8.3 → wcgw-2.8.5}/src/wcgw/types_.py +0 -0
  121. {wcgw-2.8.3/src/wcgw/client → wcgw-2.8.5/src/wcgw_cli}/__main__.py +0 -0
  122. {wcgw-2.8.3 → wcgw-2.8.5}/static/claude-ss.jpg +0 -0
  123. {wcgw-2.8.3 → wcgw-2.8.5}/static/computer-use.jpg +0 -0
  124. {wcgw-2.8.3 → wcgw-2.8.5}/static/example.jpg +0 -0
  125. {wcgw-2.8.3 → wcgw-2.8.5}/static/rocket-icon.png +0 -0
  126. {wcgw-2.8.3 → wcgw-2.8.5}/static/ss1.png +0 -0
  127. {wcgw-2.8.3 → wcgw-2.8.5}/tests/client/file_ops/test_diff_edit.py +0 -0
  128. {wcgw-2.8.3 → wcgw-2.8.5}/tests/client/file_ops/test_search_replace.py +0 -0
  129. {wcgw-2.8.3 → wcgw-2.8.5}/tests/client/repo_ops/__init__.py +0 -0
  130. {wcgw-2.8.3 → wcgw-2.8.5}/tests/client/repo_ops/test_display_tree.py +0 -0
  131. {wcgw-2.8.3 → wcgw-2.8.5}/tests/client/repo_ops/test_display_tree_simple.py +0 -0
  132. {wcgw-2.8.3 → wcgw-2.8.5}/tests/client/repo_ops/test_path_prob.py +0 -0
  133. {wcgw-2.8.3 → wcgw-2.8.5}/tests/client/repo_ops/test_repo_context.py +0 -0
  134. {wcgw-2.8.3 → wcgw-2.8.5}/tests/client/test_memory.py +0 -0
  135. {wcgw-2.8.3 → wcgw-2.8.5}/tests/client/test_tools_basic.py +0 -0
  136. {wcgw-2.8.3 → wcgw-2.8.5}/tests/client/test_tools_file_ops.py +0 -0
  137. {wcgw-2.8.3 → wcgw-2.8.5}/tests/client/test_tools_files.py +0 -0
  138. {wcgw-2.8.3 → wcgw-2.8.5}/tests/client/test_tools_shell.py +0 -0
  139. {wcgw-2.8.3 → wcgw-2.8.5}/tests/client/test_tools_validation.py +0 -0
  140. {wcgw-2.8.3 → wcgw-2.8.5}/tests/client/tools/__init__.py +0 -0
  141. {wcgw-2.8.3 → wcgw-2.8.5}/tests/client/tools/test_command_validation.py +0 -0
  142. {wcgw-2.8.3 → wcgw-2.8.5}/tests/client/tools/test_docker_operations.py +0 -0
  143. {wcgw-2.8.3 → wcgw-2.8.5}/tests/client/tools/test_error_handling.py +0 -0
  144. {wcgw-2.8.3 → wcgw-2.8.5}/tests/client/tools/test_execute_bash.py +0 -0
  145. {wcgw-2.8.3 → wcgw-2.8.5}/tests/client/tools/test_file_operations.py +0 -0
  146. {wcgw-2.8.3 → wcgw-2.8.5}/tests/client/tools/test_files/test1.py +0 -0
  147. {wcgw-2.8.3 → wcgw-2.8.5}/tests/client/tools/test_files/test2.py +0 -0
  148. {wcgw-2.8.3 → wcgw-2.8.5}/tests/client/tools/test_files/test_file.py +0 -0
  149. {wcgw-2.8.3 → wcgw-2.8.5}/tests/client/tools/test_is_int.py +0 -0
  150. {wcgw-2.8.3 → wcgw-2.8.5}/tests/client/tools/test_knowledge_transfer.py +0 -0
  151. {wcgw-2.8.3 → wcgw-2.8.5}/tests/client/tools/test_large_blocks.py +0 -0
  152. {wcgw-2.8.3 → wcgw-2.8.5}/tests/client/tools/test_render_terminal.py +0 -0
  153. {wcgw-2.8.3 → wcgw-2.8.5}/tests/client/tools/test_terminal_output.py +0 -0
  154. {wcgw-2.8.3 → wcgw-2.8.5}/tests/client/tools/test_user_interaction.py +0 -0
  155. {wcgw-2.8.3 → wcgw-2.8.5}/tests/client/tools/test_write_file.py +0 -0
  156. {wcgw-2.8.3 → wcgw-2.8.5}/tests/conftest.py +0 -0
  157. {wcgw-2.8.3 → wcgw-2.8.5}/tests/test_common.py +0 -0
  158. {wcgw-2.8.3 → wcgw-2.8.5}/tests/test_computer_use.py +0 -0
  159. {wcgw-2.8.3 → wcgw-2.8.5}/tests/test_computer_use_base.py +0 -0
  160. {wcgw-2.8.3 → wcgw-2.8.5}/tests/test_computer_use_shell.py +0 -0
  161. {wcgw-2.8.3 → wcgw-2.8.5}/tests/test_sys_utils.py +0 -0
  162. {wcgw-2.8.3 → wcgw-2.8.5}/tests/test_tools.py +0 -0
wcgw-2.8.5/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.3
3
+ Version: 2.8.5
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>
@@ -39,6 +39,7 @@ Description-Content-Type: text/markdown
39
39
  [![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
40
  [![Build](https://github.com/rusiaaman/wcgw/actions/workflows/python-publish.yml/badge.svg)](https://github.com/rusiaaman/wcgw/actions/workflows/python-publish.yml)
41
41
  [![codecov](https://codecov.io/gh/rusiaaman/wcgw/graph/badge.svg)](https://codecov.io/gh/rusiaaman/wcgw)
42
+ [![smithery badge](https://smithery.ai/badge/wcgw)](https://smithery.ai/server/wcgw)
42
43
 
43
44
  ## Updates
44
45
  - [15 Jan 2025] Modes introduced: architect, code-writer, and all powerful wcgw mode.
@@ -128,7 +129,6 @@ _If there's an error in setting up_
128
129
  - Debug the mcp server using `npx @modelcontextprotocol/inspector@0.1.7 uv tool run --from wcgw@latest --python 3.12 wcgw_mcp`
129
130
 
130
131
  ### Alternative configuration using smithery (npx required)
131
- [![smithery badge](https://smithery.ai/badge/wcgw)](https://smithery.ai/server/wcgw)
132
132
 
133
133
  You need to first install uv using homebrew. `brew install uv`
134
134
 
@@ -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.3"
4
+ version = "2.8.5"
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
@@ -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.
@@ -203,7 +203,7 @@ Format the `description` field using Markdown with the following sections.
203
203
  - "# Objective" section containing project and task objective.
204
204
  - "# All user instructions" section should be provided containing all instructions user shared in the conversation.
205
205
  - "# Current status of the task" should be provided containing only what is already achieved, not what's remaining.
206
- - "# All issues with snippets" section containing snippets of error, traceback, file snippets, commands, etc. But no comments or solutions.
206
+ - "# Pending issues with snippets" section containing snippets of pending errors, traceback, file snippets, commands, etc. But no comments or solutions.
207
207
  - Be very verbose in the all issues with snippets section providing as much error context as possible.
208
208
  - "# Build and development instructions" section containing instructions to build or run project or run tests, or envrionment related information. Only include what's known. Leave empty if unknown.
209
209
  - Any other relevant sections following the above.
@@ -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
@@ -0,0 +1 @@
1
+ from .cli import app
@@ -22,7 +22,17 @@ from anthropic.types import (
22
22
  from dotenv import load_dotenv
23
23
  from typer import Typer
24
24
 
25
- from ..types_ import (
25
+ from wcgw.client.common import discard_input
26
+ from wcgw.client.memory import load_memory
27
+ from wcgw.client.tools import (
28
+ DoneFlag,
29
+ ImageData,
30
+ default_enc,
31
+ get_tool_output,
32
+ initialize,
33
+ which_tool_name,
34
+ )
35
+ from wcgw.types_ import (
26
36
  BashCommand,
27
37
  BashInteraction,
28
38
  ContextSave,
@@ -36,16 +46,6 @@ from ..types_ import (
36
46
  ScreenShot,
37
47
  WriteIfEmpty,
38
48
  )
39
- from .common import discard_input
40
- from .memory import load_memory
41
- from .tools import (
42
- DoneFlag,
43
- ImageData,
44
- default_enc,
45
- get_tool_output,
46
- initialize,
47
- which_tool_name,
48
- )
49
49
 
50
50
  History = list[MessageParam]
51
51
 
@@ -290,7 +290,11 @@ Saves provided description and file contents of all the relevant file paths or g
290
290
  mode="wcgw",
291
291
  )
292
292
 
293
- with open(os.path.join(os.path.dirname(__file__), "diff-instructions.txt")) as f:
293
+ with open(
294
+ os.path.join(
295
+ os.path.dirname(__file__), "..", "wcgw", "client", "diff-instructions.txt"
296
+ )
297
+ ) as f:
294
298
  system += f.read()
295
299
 
296
300
  if history:
@@ -1,11 +1,11 @@
1
1
  import importlib
2
2
  from typing import Optional
3
- from typer import Typer
4
- import typer
5
3
 
6
- from .openai_client import loop as openai_loop
7
- from .anthropic_client import loop as claude_loop
4
+ import typer
5
+ from typer import Typer
8
6
 
7
+ from wcgw_cli.anthropic_client import loop as claude_loop
8
+ from wcgw_cli.openai_client import loop as openai_loop
9
9
 
10
10
  app = Typer(pretty_exceptions_show_locals=False)
11
11
 
@@ -23,7 +23,17 @@ from openai.types.chat import (
23
23
  from pydantic import BaseModel
24
24
  from typer import Typer
25
25
 
26
- from ..types_ import (
26
+ from wcgw.client.common import CostData, History, Models, discard_input
27
+ from wcgw.client.memory import load_memory
28
+ from wcgw.client.tools import (
29
+ DoneFlag,
30
+ ImageData,
31
+ default_enc,
32
+ get_tool_output,
33
+ initialize,
34
+ which_tool,
35
+ )
36
+ from wcgw.types_ import (
27
37
  BashCommand,
28
38
  BashInteraction,
29
39
  ContextSave,
@@ -33,17 +43,8 @@ from ..types_ import (
33
43
  ResetShell,
34
44
  WriteIfEmpty,
35
45
  )
36
- from .common import CostData, History, Models, discard_input
37
- from .memory import load_memory
46
+
38
47
  from .openai_utils import get_input_cost, get_output_cost
39
- from .tools import (
40
- DoneFlag,
41
- ImageData,
42
- default_enc,
43
- get_tool_output,
44
- initialize,
45
- which_tool,
46
- )
47
48
 
48
49
 
49
50
  class Config(BaseModel):
@@ -235,7 +236,11 @@ Saves provided description and file contents of all the relevant file paths or g
235
236
  mode="wcgw",
236
237
  )
237
238
 
238
- with open(os.path.join(os.path.dirname(__file__), "diff-instructions.txt")) as f:
239
+ with open(
240
+ os.path.join(
241
+ os.path.dirname(__file__), "..", "wcgw", "client", "diff-instructions.txt"
242
+ )
243
+ ) as f:
239
244
  system += f.read()
240
245
 
241
246
  if not history:
@@ -1,25 +1,14 @@
1
- import json
2
- from pathlib import Path
3
- import select
4
- import sys
5
- import termios
6
- import traceback
7
- import tty
8
- from typing import Callable, DefaultDict, Literal, Optional, cast
9
- import openai
10
- from openai import OpenAI
1
+ from typing import cast
2
+
11
3
  from openai.types.chat import (
12
- ChatCompletionMessageParam,
13
4
  ChatCompletionAssistantMessageParam,
14
5
  ChatCompletionMessage,
6
+ ChatCompletionMessageParam,
15
7
  ParsedChatCompletionMessage,
16
8
  )
17
- import rich
18
9
  from tokenizers import Tokenizer # type: ignore[import-untyped]
19
- from typer import Typer
20
- import uuid
21
10
 
22
- from .common import CostData, History
11
+ from wcgw.client.common import CostData, History
23
12
 
24
13
 
25
14
  def get_input_cost(
@@ -1,15 +1,15 @@
1
1
  import json
2
- import os
3
- import pytest
4
- from pathlib import Path
5
2
  import tempfile
6
- from wcgw.client.anthropic_client import (
7
- text_from_editor,
8
- save_history,
3
+ from unittest.mock import mock_open, patch
4
+
5
+ import pytest
6
+ import rich.console
7
+
8
+ from wcgw_cli.anthropic_client import (
9
9
  parse_user_message_special,
10
+ save_history,
11
+ text_from_editor,
10
12
  )
11
- import rich.console
12
- from unittest.mock import patch, mock_open, MagicMock
13
13
 
14
14
 
15
15
  @pytest.fixture
@@ -18,20 +18,24 @@ def console():
18
18
 
19
19
 
20
20
  def test_text_from_editor_direct_input(console):
21
- with patch('builtins.input', return_value="Test input"):
21
+ with patch("builtins.input", return_value="Test input"):
22
22
  result = text_from_editor(console)
23
23
  assert result == "Test input"
24
24
 
25
25
 
26
26
  def test_text_from_editor_with_editor():
27
- with tempfile.NamedTemporaryFile(mode='w', suffix='.tmp') as tf:
27
+ with tempfile.NamedTemporaryFile(mode="w", suffix=".tmp") as tf:
28
28
  tf.write("Test content from editor")
29
29
  tf.flush()
30
-
31
- with patch('builtins.input', return_value=""):
32
- with patch('os.environ.get', return_value="cat"): # Use 'cat' as editor for testing
33
- with patch('subprocess.run') as mock_run:
34
- with patch('builtins.open', mock_open(read_data="Test content from editor")):
30
+
31
+ with patch("builtins.input", return_value=""):
32
+ with patch(
33
+ "os.environ.get", return_value="cat"
34
+ ): # Use 'cat' as editor for testing
35
+ with patch("subprocess.run") as mock_run:
36
+ with patch(
37
+ "builtins.open", mock_open(read_data="Test content from editor")
38
+ ):
35
39
  console = rich.console.Console()
36
40
  result = text_from_editor(console)
37
41
  assert result == "Test content from editor"
@@ -43,28 +47,28 @@ def test_save_history(tmp_path):
43
47
  history = [
44
48
  {"role": "system", "content": "System message"},
45
49
  {"role": "user", "content": "Test message"},
46
- {"role": "assistant", "content": "Test response"}
50
+ {"role": "assistant", "content": "Test response"},
47
51
  ]
48
52
  session_id = "test123"
49
-
53
+
50
54
  # Create the .wcgw directory
51
- wcgw_dir = tmp_path / '.wcgw'
55
+ wcgw_dir = tmp_path / ".wcgw"
52
56
  wcgw_dir.mkdir(parents=True, exist_ok=True)
53
-
57
+
54
58
  # Patch Path to return our temp directory
55
- with patch('pathlib.Path', return_value=wcgw_dir):
59
+ with patch("pathlib.Path", return_value=wcgw_dir):
56
60
  # Create a mock file context
57
- with patch('builtins.open', mock_open()) as mock_file:
61
+ with patch("builtins.open", mock_open()) as mock_file:
58
62
  save_history(history, session_id)
59
-
63
+
60
64
  # Verify the file was opened
61
65
  mock_file.assert_called_once()
62
-
66
+
63
67
  # Get all the written content by joining all write calls
64
- written_content = ''
68
+ written_content = ""
65
69
  for call_args in mock_file().write.call_args_list:
66
70
  written_content += call_args[0][0]
67
-
71
+
68
72
  # Verify the content written was valid JSON
69
73
  parsed_content = json.loads(written_content)
70
74
  assert len(parsed_content) == len(history)
@@ -86,11 +90,11 @@ def test_parse_user_message_special_with_image(tmp_path):
86
90
  image_path = tmp_path / "test.png"
87
91
  with open(image_path, "wb") as f:
88
92
  f.write(b"fake image data")
89
-
93
+
90
94
  msg = f"%image {str(image_path)}\nSome text"
91
-
92
- with patch('base64.b64encode', return_value=b"fake_base64"):
93
- with patch('mimetypes.guess_type', return_value=("image/png", None)):
95
+
96
+ with patch("base64.b64encode", return_value=b"fake_base64"):
97
+ with patch("mimetypes.guess_type", return_value=("image/png", None)):
94
98
  result = parse_user_message_special(msg)
95
99
  assert result["role"] == "user"
96
100
  assert isinstance(result["content"], list)
@@ -100,4 +104,4 @@ def test_parse_user_message_special_with_image(tmp_path):
100
104
  assert result["content"][0]["source"]["media_type"] == "image/png"
101
105
  assert result["content"][0]["source"]["data"] == "fake_base64"
102
106
  assert result["content"][1]["type"] == "text"
103
- assert result["content"][1]["text"] == "Some text"
107
+ assert result["content"][1]["text"] == "Some text"
@@ -2,7 +2,7 @@ import pytest
2
2
  from typing import cast
3
3
  from openai.types.chat import ChatCompletionMessage, ChatCompletionMessageParam, ParsedChatCompletionMessage
4
4
  from tokenizers import Tokenizer
5
- from wcgw.client.openai_utils import get_input_cost, get_output_cost
5
+ from wcgw_cli.openai_utils import get_input_cost, get_output_cost
6
6
  from wcgw.client.common import CostData
7
7
 
8
8
 
@@ -290,10 +290,9 @@ class TestToolsExtended(unittest.TestCase):
290
290
 
291
291
  @patch("wcgw.client.tools.command_run")
292
292
  def test_read_files_docker(self, mock_command_run):
293
- from wcgw.client.tools import BASH_STATE, DisableConsole, read_files
293
+ from wcgw.client.tools import BASH_STATE, read_files
294
294
 
295
295
  # Setup mocks and test environment
296
- console = DisableConsole()
297
296
  BASH_STATE.set_in_docker("test_container")
298
297
  mock_command_run.return_value = (0, "file content", "")
299
298
 
@@ -0,0 +1,66 @@
1
+ import logging
2
+ import unittest
3
+ from unittest.mock import MagicMock, patch
4
+
5
+ logging.basicConfig(level=logging.DEBUG)
6
+ logger = logging.getLogger(__name__)
7
+
8
+ from wcgw.client.tools import (
9
+ get_incremental_output,
10
+ render_terminal_output,
11
+ truncate_if_over,
12
+ )
13
+
14
+
15
+ class TestFullCoverage(unittest.TestCase):
16
+ def setUp(self):
17
+ self.maxDiff = None
18
+
19
+ def test_incremental_output_single_case(self):
20
+ """Simple test for get_incremental_output function"""
21
+ old_output = ["line1", "line2"]
22
+ new_output = ["line2", "line3"]
23
+ result = get_incremental_output(old_output, new_output)
24
+ self.assertEqual(result, ["line3"])
25
+
26
+ def test_render_terminal_basic(self):
27
+ """Test render_terminal_output function"""
28
+ # Test basic output
29
+ result = render_terminal_output("Hello\nWorld")
30
+ self.assertEqual([line.rstrip() for line in result], ["Hello", "World"])
31
+
32
+ # Test with ANSI escape codes
33
+ result = render_terminal_output("\x1b[32mColored\x1b[0m\nText")
34
+ self.assertEqual([line.rstrip() for line in result], ["Colored", "Text"])
35
+
36
+ def test_truncate_if_over(self):
37
+ """Test truncate_if_over function"""
38
+ # Test with no truncation needed
39
+ with patch("wcgw.client.tools.default_enc") as mock_enc:
40
+ mock_enc.encode.return_value = [1, 2, 3] # Under limit
41
+ result = truncate_if_over("short content", max_tokens=100)
42
+ self.assertEqual(result, "short content")
43
+
44
+ # Test with truncation needed
45
+ with patch("wcgw.client.tools.default_enc") as mock_enc:
46
+ mock_encoding = MagicMock()
47
+ mock_encoding.ids = list(range(200)) # Over limit
48
+ mock_encoding.__len__.return_value = 200
49
+ mock_enc.encode.return_value = mock_encoding
50
+ mock_enc.decode.return_value = "truncated"
51
+
52
+ result = truncate_if_over("long content", max_tokens=50)
53
+ self.assertIn("truncated", result)
54
+ self.assertIn("(...truncated)", result)
55
+
56
+ # Test with None max_tokens
57
+ result = truncate_if_over("any content", max_tokens=None)
58
+ self.assertEqual(result, "any content")
59
+
60
+ # Test with zero max_tokens
61
+ result = truncate_if_over("any content", max_tokens=0)
62
+ self.assertEqual(result, "any content")
63
+
64
+
65
+ if __name__ == "__main__":
66
+ unittest.main()
@@ -0,0 +1,114 @@
1
+ """Additional tests for terminal output handling in tools.py"""
2
+
3
+ import unittest
4
+ from unittest.mock import patch
5
+
6
+ from wcgw.client.tools import (
7
+ Confirmation,
8
+ _incremental_text,
9
+ _is_int,
10
+ ask_confirmation,
11
+ get_incremental_output,
12
+ render_terminal_output,
13
+ )
14
+
15
+
16
+ class TestTerminalOutputFull(unittest.TestCase):
17
+ def setUp(self):
18
+ """Set up test environment before each test case"""
19
+ self.maxDiff = None
20
+
21
+ def test_get_incremental_output_break_conditions(self):
22
+ """Test break conditions in get_incremental_output"""
23
+ # Test case 1: No match found - returns entire new output
24
+ old_output = ["old"]
25
+ new_output = ["diff1", "diff2"]
26
+ result = get_incremental_output(old_output, new_output)
27
+ self.assertEqual(result, new_output) # No match, return all
28
+
29
+ # Test case 2: Last line matches but not complete sequence - returns entire new output
30
+ old_output = ["start", "last"]
31
+ new_output = [
32
+ "other",
33
+ "last",
34
+ "extra",
35
+ ] # Matches last line but not full sequence
36
+ result = get_incremental_output(old_output, new_output)
37
+ self.assertEqual(result, new_output) # Incomplete match, return all
38
+
39
+ # Test case 3: Empty old output - returns entire new output
40
+ old_output = []
41
+ new_output = ["line1", "line2"]
42
+ result = get_incremental_output(old_output, new_output)
43
+ self.assertEqual(result, new_output) # Empty old, return all
44
+
45
+ # Test case 4: Complete sequence match - returns only new content after match
46
+ old_output = ["start", "middle"]
47
+ new_output = ["start", "middle", "extra"] # Complete sequence matches
48
+ result = get_incremental_output(old_output, new_output)
49
+ self.assertEqual(result, ["extra"]) # Complete match, return only new content
50
+
51
+ def test_incremental_terminal_output(self):
52
+ """Test incremental terminal output handling"""
53
+ # Test with completely empty old output
54
+ result = _incremental_text("new text", "")
55
+ self.assertEqual(result.rstrip(), "new text")
56
+
57
+ # Test with new output matching end of old
58
+ result = _incremental_text(
59
+ "some existing text\nnew text", "some existing text\n"
60
+ )
61
+ self.assertEqual(result.rstrip(), "new text")
62
+
63
+ def test_ask_confirmation(self):
64
+ """Test ask_confirmation function"""
65
+ with patch("builtins.input", return_value="y"):
66
+ result = ask_confirmation(Confirmation(prompt="Test prompt"))
67
+ self.assertEqual(result, "Yes")
68
+
69
+ with patch("builtins.input", return_value="n"):
70
+ result = ask_confirmation(Confirmation(prompt="Test prompt"))
71
+ self.assertEqual(result, "No")
72
+
73
+ # Test with other input that should be treated as no
74
+ with patch("builtins.input", return_value="anything"):
75
+ result = ask_confirmation(Confirmation(prompt="Test prompt"))
76
+ self.assertEqual(result, "No")
77
+
78
+ def test_terminal_empty_output(self):
79
+ """Test render_terminal_output with empty lines and whitespace"""
80
+ # Test with completely empty output
81
+ result = render_terminal_output("")
82
+ self.assertEqual(result, [])
83
+
84
+ # Test with non-empty followed by empty lines
85
+ result = render_terminal_output("line1\nline2\n \n\n")
86
+ result = [line.rstrip() for line in result] # Strip trailing spaces
87
+ self.assertEqual(result, ["line1", "line2"]) # Empty lines at end are filtered
88
+
89
+ # Test with content between empty lines
90
+ result = render_terminal_output("\n\nline1\n\nline2\n")
91
+ result = [line.rstrip() for line in result]
92
+ self.assertEqual(
93
+ result, ["", "", "line1", "", "line2"]
94
+ ) # Empty at start kept, at end filtered
95
+
96
+ # Test with trailing whitespace lines
97
+ result = render_terminal_output("line1\n \n ")
98
+ result = [line.rstrip() for line in result]
99
+ self.assertEqual(result, ["line1"]) # Whitespace at end filtered
100
+
101
+ def test_is_int_function(self):
102
+ """Test _is_int function with various inputs"""
103
+ self.assertTrue(_is_int("123")) # Valid integer
104
+ self.assertTrue(_is_int("-123")) # Negative integer
105
+ self.assertTrue(_is_int("0")) # Zero
106
+
107
+ self.assertFalse(_is_int("abc")) # Letters
108
+ self.assertFalse(_is_int("12.3")) # Float
109
+ self.assertFalse(_is_int("")) # Empty string
110
+ self.assertFalse(_is_int(" ")) # Whitespace
111
+
112
+
113
+ if __name__ == "__main__":
114
+ unittest.main()