wcgw 3.0.5__tar.gz → 3.0.7__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 (123) hide show
  1. {wcgw-3.0.5 → wcgw-3.0.7}/Dockerfile +20 -4
  2. {wcgw-3.0.5 → wcgw-3.0.7}/PKG-INFO +28 -1
  3. {wcgw-3.0.5 → wcgw-3.0.7}/README.md +27 -0
  4. {wcgw-3.0.5 → wcgw-3.0.7}/pyproject.toml +1 -1
  5. {wcgw-3.0.5 → wcgw-3.0.7}/src/wcgw/client/bash_state/bash_state.py +47 -25
  6. {wcgw-3.0.5 → wcgw-3.0.7}/src/wcgw/client/tools.py +64 -10
  7. {wcgw-3.0.5 → wcgw-3.0.7}/src/wcgw/relay/serve.py +2 -1
  8. {wcgw-3.0.5 → wcgw-3.0.7}/src/wcgw_cli/anthropic_client.py +0 -11
  9. {wcgw-3.0.5 → wcgw-3.0.7}/src/wcgw_cli/openai_client.py +0 -11
  10. {wcgw-3.0.5 → wcgw-3.0.7}/tests/test_edit.py +1 -1
  11. {wcgw-3.0.5 → wcgw-3.0.7}/uv.lock +1 -1
  12. {wcgw-3.0.5 → wcgw-3.0.7}/.github/workflows/python-publish.yml +0 -0
  13. {wcgw-3.0.5 → wcgw-3.0.7}/.github/workflows/python-tests.yml +0 -0
  14. {wcgw-3.0.5 → wcgw-3.0.7}/.github/workflows/python-types.yml +0 -0
  15. {wcgw-3.0.5 → wcgw-3.0.7}/.gitignore +0 -0
  16. {wcgw-3.0.5 → wcgw-3.0.7}/.gitmodules +0 -0
  17. {wcgw-3.0.5 → wcgw-3.0.7}/.python-version +0 -0
  18. {wcgw-3.0.5 → wcgw-3.0.7}/.vscode/settings.json +0 -0
  19. {wcgw-3.0.5 → wcgw-3.0.7}/LICENSE +0 -0
  20. {wcgw-3.0.5 → wcgw-3.0.7}/gpt_action_json_schema.json +0 -0
  21. {wcgw-3.0.5 → wcgw-3.0.7}/gpt_instructions.txt +0 -0
  22. {wcgw-3.0.5 → wcgw-3.0.7}/openai.md +0 -0
  23. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/.git +0 -0
  24. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  25. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  26. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/.github/workflows/main-checks.yml +0 -0
  27. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/.github/workflows/publish-pypi.yml +0 -0
  28. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/.github/workflows/pull-request-checks.yml +0 -0
  29. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/.github/workflows/shared.yml +0 -0
  30. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/.gitignore +0 -0
  31. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/.python-version +0 -0
  32. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/CODE_OF_CONDUCT.md +0 -0
  33. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/CONTRIBUTING.md +0 -0
  34. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/LICENSE +0 -0
  35. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/README.md +0 -0
  36. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/RELEASE.md +0 -0
  37. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/SECURITY.md +0 -0
  38. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/examples/README.md +0 -0
  39. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/examples/servers/simple-prompt/.python-version +0 -0
  40. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/examples/servers/simple-prompt/README.md +0 -0
  41. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/examples/servers/simple-prompt/mcp_simple_prompt/__init__.py +0 -0
  42. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/examples/servers/simple-prompt/mcp_simple_prompt/__main__.py +0 -0
  43. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/examples/servers/simple-prompt/mcp_simple_prompt/server.py +0 -0
  44. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/examples/servers/simple-prompt/pyproject.toml +0 -0
  45. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/examples/servers/simple-resource/.python-version +0 -0
  46. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/examples/servers/simple-resource/README.md +0 -0
  47. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/examples/servers/simple-resource/mcp_simple_resource/__init__.py +0 -0
  48. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/examples/servers/simple-resource/mcp_simple_resource/__main__.py +0 -0
  49. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/examples/servers/simple-resource/mcp_simple_resource/server.py +0 -0
  50. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/examples/servers/simple-resource/pyproject.toml +0 -0
  51. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/examples/servers/simple-tool/.python-version +0 -0
  52. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/examples/servers/simple-tool/README.md +0 -0
  53. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/examples/servers/simple-tool/mcp_simple_tool/__init__.py +0 -0
  54. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/examples/servers/simple-tool/mcp_simple_tool/__main__.py +0 -0
  55. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/examples/servers/simple-tool/mcp_simple_tool/server.py +0 -0
  56. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/examples/servers/simple-tool/pyproject.toml +0 -0
  57. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/pyproject.toml +0 -0
  58. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/src/mcp_wcgw/__init__.py +0 -0
  59. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/src/mcp_wcgw/client/__init__.py +0 -0
  60. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/src/mcp_wcgw/client/__main__.py +0 -0
  61. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/src/mcp_wcgw/client/session.py +0 -0
  62. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/src/mcp_wcgw/client/sse.py +0 -0
  63. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/src/mcp_wcgw/client/stdio.py +0 -0
  64. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/src/mcp_wcgw/py.typed +0 -0
  65. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/src/mcp_wcgw/server/__init__.py +0 -0
  66. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/src/mcp_wcgw/server/__main__.py +0 -0
  67. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/src/mcp_wcgw/server/models.py +0 -0
  68. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/src/mcp_wcgw/server/session.py +0 -0
  69. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/src/mcp_wcgw/server/sse.py +0 -0
  70. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/src/mcp_wcgw/server/stdio.py +0 -0
  71. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/src/mcp_wcgw/server/websocket.py +0 -0
  72. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/src/mcp_wcgw/shared/__init__.py +0 -0
  73. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/src/mcp_wcgw/shared/context.py +0 -0
  74. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/src/mcp_wcgw/shared/exceptions.py +0 -0
  75. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/src/mcp_wcgw/shared/memory.py +0 -0
  76. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/src/mcp_wcgw/shared/progress.py +0 -0
  77. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/src/mcp_wcgw/shared/session.py +0 -0
  78. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/src/mcp_wcgw/shared/version.py +0 -0
  79. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/src/mcp_wcgw/types.py +0 -0
  80. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/tests/__init__.py +0 -0
  81. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/tests/client/__init__.py +0 -0
  82. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/tests/client/test_session.py +0 -0
  83. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/tests/client/test_stdio.py +0 -0
  84. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/tests/conftest.py +0 -0
  85. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/tests/server/__init__.py +0 -0
  86. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/tests/server/test_session.py +0 -0
  87. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/tests/server/test_stdio.py +0 -0
  88. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/tests/shared/test_memory.py +0 -0
  89. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/tests/test_types.py +0 -0
  90. {wcgw-3.0.5 → wcgw-3.0.7}/src/mcp_wcgw_fork/uv.lock +0 -0
  91. {wcgw-3.0.5 → wcgw-3.0.7}/src/wcgw/__init__.py +0 -0
  92. {wcgw-3.0.5 → wcgw-3.0.7}/src/wcgw/client/__init__.py +0 -0
  93. {wcgw-3.0.5 → wcgw-3.0.7}/src/wcgw/client/common.py +0 -0
  94. {wcgw-3.0.5 → wcgw-3.0.7}/src/wcgw/client/diff-instructions.txt +0 -0
  95. {wcgw-3.0.5 → wcgw-3.0.7}/src/wcgw/client/encoder/__init__.py +0 -0
  96. {wcgw-3.0.5 → wcgw-3.0.7}/src/wcgw/client/file_ops/diff_edit.py +0 -0
  97. {wcgw-3.0.5 → wcgw-3.0.7}/src/wcgw/client/file_ops/search_replace.py +0 -0
  98. {wcgw-3.0.5 → wcgw-3.0.7}/src/wcgw/client/mcp_server/Readme.md +0 -0
  99. {wcgw-3.0.5 → wcgw-3.0.7}/src/wcgw/client/mcp_server/__init__.py +0 -0
  100. {wcgw-3.0.5 → wcgw-3.0.7}/src/wcgw/client/mcp_server/server.py +0 -0
  101. {wcgw-3.0.5 → wcgw-3.0.7}/src/wcgw/client/memory.py +0 -0
  102. {wcgw-3.0.5 → wcgw-3.0.7}/src/wcgw/client/modes.py +0 -0
  103. {wcgw-3.0.5 → wcgw-3.0.7}/src/wcgw/client/repo_ops/display_tree.py +0 -0
  104. {wcgw-3.0.5 → wcgw-3.0.7}/src/wcgw/client/repo_ops/path_prob.py +0 -0
  105. {wcgw-3.0.5 → wcgw-3.0.7}/src/wcgw/client/repo_ops/paths_model.vocab +0 -0
  106. {wcgw-3.0.5 → wcgw-3.0.7}/src/wcgw/client/repo_ops/paths_tokens.model +0 -0
  107. {wcgw-3.0.5 → wcgw-3.0.7}/src/wcgw/client/repo_ops/repo_context.py +0 -0
  108. {wcgw-3.0.5 → wcgw-3.0.7}/src/wcgw/client/tool_prompts.py +0 -0
  109. {wcgw-3.0.5 → wcgw-3.0.7}/src/wcgw/py.typed +0 -0
  110. {wcgw-3.0.5 → wcgw-3.0.7}/src/wcgw/relay/client.py +0 -0
  111. {wcgw-3.0.5 → wcgw-3.0.7}/src/wcgw/relay/static/privacy.txt +0 -0
  112. {wcgw-3.0.5 → wcgw-3.0.7}/src/wcgw/types_.py +0 -0
  113. {wcgw-3.0.5 → wcgw-3.0.7}/src/wcgw_cli/__init__.py +0 -0
  114. {wcgw-3.0.5 → wcgw-3.0.7}/src/wcgw_cli/__main__.py +0 -0
  115. {wcgw-3.0.5 → wcgw-3.0.7}/src/wcgw_cli/cli.py +0 -0
  116. {wcgw-3.0.5 → wcgw-3.0.7}/src/wcgw_cli/openai_utils.py +0 -0
  117. {wcgw-3.0.5 → wcgw-3.0.7}/static/claude-ss.jpg +0 -0
  118. {wcgw-3.0.5 → wcgw-3.0.7}/static/computer-use.jpg +0 -0
  119. {wcgw-3.0.5 → wcgw-3.0.7}/static/example.jpg +0 -0
  120. {wcgw-3.0.5 → wcgw-3.0.7}/static/rocket-icon.png +0 -0
  121. {wcgw-3.0.5 → wcgw-3.0.7}/static/ss1.png +0 -0
  122. {wcgw-3.0.5 → wcgw-3.0.7}/tests/test_mcp_server.py +0 -0
  123. {wcgw-3.0.5 → wcgw-3.0.7}/tests/test_tools.py +0 -0
@@ -8,17 +8,30 @@ WORKDIR /app
8
8
  # Copy the project's pyproject.toml and lock file for dependency installation
9
9
  COPY pyproject.toml /app/
10
10
  COPY uv.lock /app/
11
+ COPY README.md /app/
11
12
 
12
13
  # Enable bytecode compilation and set link mode to copy for dependencies
13
14
  ENV UV_COMPILE_BYTECODE=1
14
15
  ENV UV_LINK_MODE=copy
15
16
 
17
+ # Install dependencies including git
18
+ RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/*
19
+
16
20
  # Install dependencies
17
21
  RUN --mount=type=cache,target=/root/.cache/uv \
18
22
  uv sync --frozen --no-install-project --no-dev --no-editable
19
23
 
20
24
  # Copy the entire project into the container
21
- COPY . /app
25
+ COPY src /app/src
26
+
27
+ # Check if src/mcp_wcgw_fork is empty and clone the repository if needed
28
+ RUN if [ ! -d "/app/src/mcp_wcgw_fork" ] || [ -z "$(ls -A /app/src/mcp_wcgw_fork)" ]; then \
29
+ mkdir -p /app/src/mcp_wcgw_fork && \
30
+ git clone https://github.com/rusiaaman/python-sdk.git /app/src/mcp_wcgw_fork && \
31
+ echo "Repository cloned successfully"; \
32
+ else \
33
+ echo "src/mcp_wcgw_fork already exists and is not empty"; \
34
+ fi
22
35
 
23
36
  # Install the project
24
37
  RUN --mount=type=cache,target=/root/.cache/uv \
@@ -27,15 +40,18 @@ RUN --mount=type=cache,target=/root/.cache/uv \
27
40
  # Use a smaller image to run the application
28
41
  FROM python:3.12-slim-bookworm
29
42
 
43
+ RUN apt-get update && apt-get install -y screen && rm -rf /var/lib/apt/lists/*
44
+
30
45
  # Set the working directory in the container
31
- WORKDIR /app
46
+ WORKDIR /workspace
32
47
 
33
48
  # Copy the installed application from the previous stage
34
- COPY --from=uv /root/.local /root/.local
35
49
  COPY --from=uv --chown=app:app /app/.venv /app/.venv
50
+ # Copy the cloned repository if it exists
51
+ COPY --from=uv --chown=app:app /app/src/mcp_wcgw_fork /app/src/mcp_wcgw_fork
36
52
 
37
53
  # Add the virtual environment to the PATH
38
54
  ENV PATH="/app/.venv/bin:$PATH"
39
55
 
40
56
  # Specify the command to run on container start
41
- ENTRYPOINT ["wcgw_mcp"]
57
+ ENTRYPOINT ["wcgw_mcp"]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wcgw
3
- Version: 3.0.5
3
+ Version: 3.0.7
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>
@@ -204,6 +204,32 @@ Read here: https://github.com/rusiaaman/wcgw/blob/main/openai.md
204
204
 
205
205
  ![example](https://github.com/rusiaaman/wcgw/blob/main/static/example.jpg?raw=true)
206
206
 
207
+
208
+ ## Using mcp server over docker
209
+
210
+ First build the docker image `docker build -t wcgw https://github.com/rusiaaman/wcgw.git`
211
+
212
+ Then you can update `/Users/username/Library/Application Support/Claude/claude_desktop_config.json` to have
213
+ ```
214
+ {
215
+ "mcpServers": {
216
+ "filesystem": {
217
+ "command": "docker",
218
+ "args": [
219
+ "run",
220
+ "-i",
221
+ "--rm",
222
+ "--mount",
223
+ "type=bind,src=/Users/username/Desktop,dst=/workspace/Desktop",
224
+ "wcgw",
225
+ ]
226
+ }
227
+ }
228
+ }
229
+ ```
230
+
231
+
232
+
207
233
  ## [Optional] Local shell access with openai API key or anthropic API key
208
234
 
209
235
  ### Openai
@@ -255,3 +281,4 @@ The server provides the following MCP tools:
255
281
  - Parameters: `id` (string), `project_root_path` (string), `description` (string), `relevant_file_globs` (string[])
256
282
 
257
283
  All tools support absolute paths and include built-in protections against common errors. See the [MCP specification](https://modelcontextprotocol.io/) for detailed protocol information.
284
+
@@ -176,6 +176,32 @@ Read here: https://github.com/rusiaaman/wcgw/blob/main/openai.md
176
176
 
177
177
  ![example](https://github.com/rusiaaman/wcgw/blob/main/static/example.jpg?raw=true)
178
178
 
179
+
180
+ ## Using mcp server over docker
181
+
182
+ First build the docker image `docker build -t wcgw https://github.com/rusiaaman/wcgw.git`
183
+
184
+ Then you can update `/Users/username/Library/Application Support/Claude/claude_desktop_config.json` to have
185
+ ```
186
+ {
187
+ "mcpServers": {
188
+ "filesystem": {
189
+ "command": "docker",
190
+ "args": [
191
+ "run",
192
+ "-i",
193
+ "--rm",
194
+ "--mount",
195
+ "type=bind,src=/Users/username/Desktop,dst=/workspace/Desktop",
196
+ "wcgw",
197
+ ]
198
+ }
199
+ }
200
+ }
201
+ ```
202
+
203
+
204
+
179
205
  ## [Optional] Local shell access with openai API key or anthropic API key
180
206
 
181
207
  ### Openai
@@ -227,3 +253,4 @@ The server provides the following MCP tools:
227
253
  - Parameters: `id` (string), `project_root_path` (string), `description` (string), `relevant_file_globs` (string[])
228
254
 
229
255
  All tools support absolute paths and include built-in protections against common errors. See the [MCP specification](https://modelcontextprotocol.io/) for detailed protocol information.
256
+
@@ -1,7 +1,7 @@
1
1
  [project]
2
2
  authors = [{ name = "Aman Rusia", email = "gapypi@arcfu.com" }]
3
3
  name = "wcgw"
4
- version = "3.0.5"
4
+ version = "3.0.7"
5
5
  description = "Shell and coding agent on claude and chatgpt"
6
6
  readme = "README.md"
7
7
  requires-python = ">=3.11"
@@ -78,7 +78,9 @@ def get_tmpdir() -> str:
78
78
 
79
79
  def check_if_screen_command_available() -> bool:
80
80
  try:
81
- subprocess.run(["screen", "-v"], capture_output=True, check=True, timeout=0.2)
81
+ subprocess.run(
82
+ ["which", "screen"], capture_output=True, check=True, timeout=0.2
83
+ )
82
84
  return True
83
85
  except (subprocess.CalledProcessError, FileNotFoundError):
84
86
  return False
@@ -137,7 +139,7 @@ def cleanup_all_screens_with_name(name: str, console: Console) -> None:
137
139
 
138
140
  def start_shell(
139
141
  is_restricted_mode: bool, initial_dir: str, console: Console, over_screen: bool
140
- ) -> tuple[pexpect.spawn, str]: # type: ignore[type-arg]
142
+ ) -> tuple["pexpect.spawn[str]", str]:
141
143
  cmd = "/bin/bash"
142
144
  if is_restricted_mode:
143
145
  cmd += " -r"
@@ -232,6 +234,7 @@ class BashState:
232
234
  use_screen: bool,
233
235
  whitelist_for_overwrite: Optional[set[str]] = None,
234
236
  ) -> None:
237
+ self._last_command: str = ""
235
238
  self.console = console
236
239
  self._cwd = working_dir or os.getcwd()
237
240
  self._bash_command_mode: BashCommandMode = bash_command_mode or BashCommandMode(
@@ -253,13 +256,17 @@ class BashState:
253
256
  output = self._shell.expect(pattern, timeout)
254
257
  return output
255
258
 
256
- def send(self, s: str | bytes) -> int:
259
+ def send(self, s: str | bytes, set_as_command: Optional[str]) -> int:
257
260
  self.close_bg_expect_thread()
261
+ if set_as_command is not None:
262
+ self._last_command = set_as_command
258
263
  output = self._shell.send(s)
259
264
  return output
260
265
 
261
- def sendline(self, s: str | bytes) -> int:
266
+ def sendline(self, s: str | bytes, set_as_command: Optional[str]) -> int:
262
267
  self.close_bg_expect_thread()
268
+ if set_as_command is not None:
269
+ self._last_command = set_as_command
263
270
  output = self._shell.sendline(s)
264
271
  return output
265
272
 
@@ -273,7 +280,10 @@ class BashState:
273
280
 
274
281
  @property
275
282
  def before(self) -> Optional[str]:
276
- return self._shell.before
283
+ before = self._shell.before
284
+ if before and before.startswith(self._last_command):
285
+ return before[len(self._last_command) :]
286
+ return before
277
287
 
278
288
  def run_bg_expect_thread(self) -> None:
279
289
  """
@@ -333,11 +343,11 @@ class BashState:
333
343
  def ensure_env_and_bg_jobs(self) -> Optional[int]:
334
344
  quick_timeout = 0.2 if not self.over_screen else 1
335
345
  # First reset the prompt in case venv was sourced or other reasons.
336
- self.sendline(PROMPT_STATEMENT)
346
+ self.sendline(PROMPT_STATEMENT, set_as_command=PROMPT_STATEMENT)
337
347
  self.expect(PROMPT_CONST, timeout=quick_timeout)
338
348
  # Reset echo also if it was enabled
339
349
  command = "jobs | wc -l"
340
- self.sendline(command)
350
+ self.sendline(command, set_as_command=command)
341
351
  before = ""
342
352
  counts = 0
343
353
  while not _is_int(before): # Consume all previous output
@@ -365,6 +375,7 @@ class BashState:
365
375
 
366
376
  def _init_shell(self) -> None:
367
377
  self._state: Literal["repl"] | datetime.datetime = "repl"
378
+ self._last_command = ""
368
379
  # Ensure self._cwd exists
369
380
  os.makedirs(self._cwd, exist_ok=True)
370
381
  try:
@@ -404,6 +415,7 @@ class BashState:
404
415
  def set_repl(self) -> None:
405
416
  self._state = "repl"
406
417
  self._pending_output = ""
418
+ self._last_command = ""
407
419
 
408
420
  @property
409
421
  def state(self) -> BASH_CLF_OUTPUT:
@@ -420,7 +432,7 @@ class BashState:
420
432
  return PROMPT_CONST
421
433
 
422
434
  def update_cwd(self) -> str:
423
- self.sendline("pwd")
435
+ self.sendline("pwd", set_as_command="pwd")
424
436
  self.expect(PROMPT_CONST, timeout=0.2)
425
437
  before_val = self.before
426
438
  if not isinstance(before_val, str):
@@ -505,8 +517,8 @@ class BashState:
505
517
 
506
518
 
507
519
  WAITING_INPUT_MESSAGE = """A command is already running. NOTE: You can't run multiple shell sessions, likely a previous program hasn't exited.
508
- 1. Get its output using `send_ascii: [10] or send_specials: ["Enter"]`
509
- 2. Use `send_ascii` or `send_specials` to give inputs to the running program, don't use `BashCommand` OR
520
+ 1. Get its output using status check.
521
+ 2. Use `send_ascii` or `send_specials` to give inputs to the running program OR
510
522
  3. kill the previous program by sending ctrl+c first using `send_ascii` or `send_specials`
511
523
  4. Interrupt and run the process in background by re-running it using screen
512
524
  """
@@ -538,6 +550,11 @@ def _incremental_text(text: str, last_pending_output: str) -> str:
538
550
  # text = render_terminal_output(text[-100_000:])
539
551
  text = text[-100_000:]
540
552
 
553
+ if not last_pending_output:
554
+ # This is the first call. We need to offset the position where this program
555
+ # is being rendered for the new screen versions
556
+ # Caveat: no difference in output between a program with leading whitespace and one without.
557
+ return rstrip(render_terminal_output(text)).lstrip()
541
558
  last_rendered_lines = render_terminal_output(last_pending_output)
542
559
  last_pending_output_rendered = "\n".join(last_rendered_lines)
543
560
  if not last_rendered_lines:
@@ -580,7 +597,10 @@ def is_status_check(arg: BashCommand) -> bool:
580
597
  isinstance(arg.action_json, SendSpecials)
581
598
  and arg.action_json.send_specials == ["Enter"]
582
599
  )
583
- or (isinstance(arg.action_json, SendAscii) and arg.action_json.send_ascii == [10])
600
+ or (
601
+ isinstance(arg.action_json, SendAscii)
602
+ and arg.action_json.send_ascii == [10]
603
+ )
584
604
  )
585
605
 
586
606
 
@@ -632,8 +652,8 @@ def _execute_bash(
632
652
  )
633
653
 
634
654
  for i in range(0, len(command), 128):
635
- bash_state.send(command[i : i + 128])
636
- bash_state.send(bash_state.linesep)
655
+ bash_state.send(command[i : i + 128], set_as_command=None)
656
+ bash_state.send(bash_state.linesep, set_as_command=command)
637
657
 
638
658
  elif isinstance(command_data, StatusCheck):
639
659
  bash_state.console.print("Checking status")
@@ -646,8 +666,10 @@ def _execute_bash(
646
666
 
647
667
  bash_state.console.print(f"Interact text: {command_data.send_text}")
648
668
  for i in range(0, len(command_data.send_text), 128):
649
- bash_state.send(command_data.send_text[i : i + 128])
650
- bash_state.send(bash_state.linesep)
669
+ bash_state.send(
670
+ command_data.send_text[i : i + 128], set_as_command=None
671
+ )
672
+ bash_state.send(bash_state.linesep, set_as_command=None)
651
673
 
652
674
  elif isinstance(command_data, SendSpecials):
653
675
  if not command_data.send_specials:
@@ -658,15 +680,15 @@ def _execute_bash(
658
680
  )
659
681
  for char in command_data.send_specials:
660
682
  if char == "Key-up":
661
- bash_state.send("\033[A")
683
+ bash_state.send("\033[A", set_as_command=None)
662
684
  elif char == "Key-down":
663
- bash_state.send("\033[B")
685
+ bash_state.send("\033[B", set_as_command=None)
664
686
  elif char == "Key-left":
665
- bash_state.send("\033[D")
687
+ bash_state.send("\033[D", set_as_command=None)
666
688
  elif char == "Key-right":
667
- bash_state.send("\033[C")
689
+ bash_state.send("\033[C", set_as_command=None)
668
690
  elif char == "Enter":
669
- bash_state.send("\n")
691
+ bash_state.send("\n", set_as_command=None)
670
692
  elif char == "Ctrl-c":
671
693
  bash_state.sendintr()
672
694
  is_interrupt = True
@@ -674,7 +696,7 @@ def _execute_bash(
674
696
  bash_state.sendintr()
675
697
  is_interrupt = True
676
698
  elif char == "Ctrl-z":
677
- bash_state.send("\x1a")
699
+ bash_state.send("\x1a", set_as_command=None)
678
700
  else:
679
701
  raise Exception(f"Unknown special character: {char}")
680
702
 
@@ -686,7 +708,7 @@ def _execute_bash(
686
708
  f"Sending ASCII sequence: {command_data.send_ascii}"
687
709
  )
688
710
  for ascii_char in command_data.send_ascii:
689
- bash_state.send(chr(ascii_char))
711
+ bash_state.send(chr(ascii_char), set_as_command=None)
690
712
  if ascii_char == 3:
691
713
  is_interrupt = True
692
714
  else:
@@ -747,9 +769,9 @@ def _execute_bash(
747
769
  incremental_text = (
748
770
  incremental_text
749
771
  + """---
750
- ----
751
- Failure interrupting.
752
- You may want to try Ctrl-c again or program specific exit interactive commands.
772
+ ----
773
+ Failure interrupting.
774
+ You may want to try Ctrl-c again or program specific exit interactive commands.
753
775
  """
754
776
  )
755
777
 
@@ -20,6 +20,7 @@ from typing import (
20
20
  TypeVar,
21
21
  )
22
22
 
23
+ import rich
23
24
  from openai.types.chat import (
24
25
  ChatCompletionMessageParam,
25
26
  )
@@ -31,6 +32,7 @@ from wcgw.client.bash_state.bash_state import get_status
31
32
  from ..types_ import (
32
33
  BashCommand,
33
34
  CodeWriterMode,
35
+ Command,
34
36
  Console,
35
37
  ContextSave,
36
38
  FileEdit,
@@ -195,7 +197,10 @@ def initialize(
195
197
  if read_files_:
196
198
  if folder_to_start:
197
199
  read_files_ = [
198
- os.path.join(folder_to_start, f) if not os.path.isabs(f) else f
200
+ # Expand the path before checking if it's absolute
201
+ os.path.join(folder_to_start, f)
202
+ if not os.path.isabs(expand_user(f))
203
+ else expand_user(f)
199
204
  for f in read_files_
200
205
  ]
201
206
  initial_files = read_files(read_files_, max_tokens, context)
@@ -362,9 +367,10 @@ def truncate_if_over(content: str, max_tokens: Optional[int]) -> str:
362
367
 
363
368
 
364
369
  def read_image_from_shell(file_path: str, context: Context) -> ImageData:
365
- # Expand the path
370
+ # Expand the path before checking if it's absolute
366
371
  file_path = expand_user(file_path)
367
372
 
373
+ # If not absolute after expansion, join with current working directory
368
374
  if not os.path.isabs(file_path):
369
375
  file_path = os.path.join(context.bash_state.cwd, file_path)
370
376
 
@@ -400,10 +406,10 @@ def write_file(
400
406
  max_tokens: Optional[int],
401
407
  context: Context,
402
408
  ) -> str:
403
- if not os.path.isabs(writefile.file_path):
409
+ # Expand the path before checking if it's absolute
410
+ path_ = expand_user(writefile.file_path)
411
+ if not os.path.isabs(path_):
404
412
  return f"Failure: file_path should be absolute path, current working directory is {context.bash_state.cwd}"
405
- else:
406
- path_ = expand_user(writefile.file_path)
407
413
 
408
414
  error_on_exist_ = (
409
415
  error_on_exist and path_ not in context.bash_state.whitelist_for_overwrite
@@ -452,6 +458,9 @@ def write_file(
452
458
  syntax_errors = check.description
453
459
 
454
460
  if syntax_errors:
461
+ if extension in {"tsx", "ts"}:
462
+ syntax_errors += "\nNote: Ignore if 'tagged template literals' are used, they may raise false positive errors in tree-sitter."
463
+
455
464
  context_for_errors = get_context_for_errors(
456
465
  check.errors, writefile.file_content, max_tokens
457
466
  )
@@ -502,12 +511,12 @@ def do_diff_edit(fedit: FileEdit, max_tokens: Optional[int], context: Context) -
502
511
  def _do_diff_edit(fedit: FileEdit, max_tokens: Optional[int], context: Context) -> str:
503
512
  context.console.log(f"Editing file: {fedit.file_path}")
504
513
 
505
- if not os.path.isabs(fedit.file_path):
514
+ # Expand the path before checking if it's absolute
515
+ path_ = expand_user(fedit.file_path)
516
+ if not os.path.isabs(path_):
506
517
  raise Exception(
507
518
  f"Failure: file_path should be absolute path, current working directory is {context.bash_state.cwd}"
508
519
  )
509
- else:
510
- path_ = expand_user(fedit.file_path)
511
520
 
512
521
  # Validate using file_edit_mode
513
522
  allowed_globs = context.bash_state.file_edit_mode.allowed_globs
@@ -548,11 +557,13 @@ def _do_diff_edit(fedit: FileEdit, max_tokens: Optional[int], context: Context)
548
557
  context_for_errors = get_context_for_errors(
549
558
  check.errors, apply_diff_to, max_tokens
550
559
  )
560
+ if extension in {"tsx", "ts"}:
561
+ syntax_errors += "\nNote: Ignore if 'tagged template literals' are used, they may raise false positive errors in tree-sitter."
551
562
 
552
563
  context.console.print(f"W: Syntax errors encountered: {syntax_errors}")
553
564
  return f"""{comments}
554
565
  ---
555
- Tree-sitter reported syntax errors, please re-read the file and fix if there are any errors.
566
+ Warning: tree-sitter reported syntax errors, please re-read the file and fix if there are any errors.
556
567
  Syntax errors:
557
568
  {syntax_errors}
558
569
 
@@ -698,9 +709,12 @@ def get_tool_output(
698
709
  context.console.print("Calling task memory tool")
699
710
  relevant_files = []
700
711
  warnings = ""
712
+ # Expand user in project root path
701
713
  arg.project_root_path = os.path.expanduser(arg.project_root_path)
702
714
  for fglob in arg.relevant_file_globs:
715
+ # Expand user in glob pattern before checking if it's absolute
703
716
  fglob = expand_user(fglob)
717
+ # If not absolute after expansion, join with project root path
704
718
  if not os.path.isabs(fglob) and arg.project_root_path:
705
719
  fglob = os.path.join(arg.project_root_path, fglob)
706
720
  globs = glob.glob(fglob, recursive=True)
@@ -794,6 +808,46 @@ def read_file(
794
808
  rest = save_out_of_context(
795
809
  default_enc.decoder(tokens[max_tokens:]), Path(file_path).suffix
796
810
  )
797
- content += f"\n(...truncated)\n---\nI've saved the continuation in a new file. Please read: `{rest}`"
811
+ content += f"\n(...truncated)\n---\nI've saved the continuation in a new file. You may want to read: `{rest}`"
798
812
  truncated = True
799
813
  return content, truncated, tokens_counts
814
+
815
+
816
+ if __name__ == "__main__":
817
+ with BashState(
818
+ rich.console.Console(style="blue", highlight=False, markup=False),
819
+ "",
820
+ None,
821
+ None,
822
+ None,
823
+ None,
824
+ True,
825
+ None,
826
+ ) as BASH_STATE:
827
+ print(
828
+ get_tool_output(
829
+ Context(BASH_STATE, BASH_STATE.console),
830
+ Initialize(
831
+ type="first_call",
832
+ any_workspace_path="",
833
+ initial_files_to_read=[],
834
+ task_id_to_resume="",
835
+ mode_name="wcgw",
836
+ code_writer_config=None,
837
+ ),
838
+ default_enc,
839
+ 0,
840
+ lambda x, y: ("", 0),
841
+ None,
842
+ )
843
+ )
844
+ print(
845
+ get_tool_output(
846
+ Context(BASH_STATE, BASH_STATE.console),
847
+ BashCommand(action_json=Command(command="pwd")),
848
+ default_enc,
849
+ 0,
850
+ lambda x, y: ("", 0),
851
+ None,
852
+ )
853
+ )
@@ -1,4 +1,5 @@
1
1
  import asyncio
2
+ import os
2
3
  import threading
3
4
  import time
4
5
  from importlib import metadata
@@ -281,7 +282,7 @@ async def context_save(context_save_data: ContextSaveWithUUID) -> str:
281
282
  raise fastapi.HTTPException(status_code=500, detail="Timeout error")
282
283
 
283
284
 
284
- app.mount("/static", StaticFiles(directory="static"), name="static")
285
+ app.mount("/static", StaticFiles(directory=os.path.join(os.path.dirname(__file__), "static")), name="static")
285
286
 
286
287
 
287
288
  def run() -> None:
@@ -227,17 +227,6 @@ def loop(
227
227
  mode="wcgw",
228
228
  )
229
229
 
230
- with open(
231
- os.path.join(
232
- os.path.dirname(__file__),
233
- "..",
234
- "wcgw",
235
- "client",
236
- "diff-instructions.txt",
237
- )
238
- ) as f:
239
- system += f.read()
240
-
241
230
  if history:
242
231
  if (
243
232
  (last_msg := history[-1])["role"] == "user"
@@ -191,17 +191,6 @@ def loop(
191
191
  mode="wcgw",
192
192
  )
193
193
 
194
- with open(
195
- os.path.join(
196
- os.path.dirname(__file__),
197
- "..",
198
- "wcgw",
199
- "client",
200
- "diff-instructions.txt",
201
- )
202
- ) as f:
203
- system += f.read()
204
-
205
194
  if not history:
206
195
  history = [{"role": "system", "content": system}]
207
196
  else:
@@ -165,7 +165,7 @@ def hello():
165
165
  )
166
166
 
167
167
  assert len(outputs) == 1
168
- assert "Tree-sitter reported syntax errors" in outputs[0]
168
+ assert "Warning: tree-sitter reported syntax errors" in outputs[0]
169
169
 
170
170
  # Verify the change
171
171
  with open(test_file) as f:
@@ -1179,7 +1179,7 @@ wheels = [
1179
1179
 
1180
1180
  [[package]]
1181
1181
  name = "wcgw"
1182
- version = "3.0.5"
1182
+ version = "3.0.7"
1183
1183
  source = { editable = "." }
1184
1184
  dependencies = [
1185
1185
  { name = "anthropic" },
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes