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.
- wcgw-2.8.5/Dockerfile +41 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/PKG-INFO +2 -2
- {wcgw-2.8.3 → wcgw-2.8.5}/pyproject.toml +2 -2
- {wcgw-2.8.3 → wcgw-2.8.5}/src/wcgw/__init__.py +1 -2
- {wcgw-2.8.3 → wcgw-2.8.5}/src/wcgw/client/mcp_server/server.py +15 -3
- {wcgw-2.8.3 → wcgw-2.8.5}/src/wcgw/client/modes.py +3 -3
- {wcgw-2.8.3 → wcgw-2.8.5}/src/wcgw/client/tools.py +5 -8
- wcgw-2.8.5/src/wcgw_cli/__init__.py +1 -0
- {wcgw-2.8.3/src/wcgw/client → wcgw-2.8.5/src/wcgw_cli}/anthropic_client.py +16 -12
- {wcgw-2.8.3/src/wcgw/client → wcgw-2.8.5/src/wcgw_cli}/cli.py +4 -4
- {wcgw-2.8.3/src/wcgw/client → wcgw-2.8.5/src/wcgw_cli}/openai_client.py +17 -12
- {wcgw-2.8.3/src/wcgw/client → wcgw-2.8.5/src/wcgw_cli}/openai_utils.py +4 -15
- {wcgw-2.8.3 → wcgw-2.8.5}/tests/client/test_anthropic_client_utils.py +34 -30
- {wcgw-2.8.3 → wcgw-2.8.5}/tests/client/test_openai_utils.py +1 -1
- {wcgw-2.8.3 → wcgw-2.8.5}/tests/client/test_tools_extended.py +1 -2
- wcgw-2.8.5/tests/client/tools/test_full_coverage.py +66 -0
- wcgw-2.8.5/tests/client/tools/test_terminal_output_full.py +114 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/tests/test_anthropic_client.py +14 -18
- {wcgw-2.8.3 → wcgw-2.8.5}/tests/test_basic.py +7 -9
- wcgw-2.8.5/tests/test_initialize.py +281 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/uv.lock +1 -1
- {wcgw-2.8.3 → wcgw-2.8.5}/.github/workflows/python-publish.yml +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/.github/workflows/python-tests.yml +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/.github/workflows/python-types.yml +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/.gitignore +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/.gitmodules +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/.python-version +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/.vscode/settings.json +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/LICENSE +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/README.md +1 -1
- {wcgw-2.8.3 → wcgw-2.8.5}/gpt_action_json_schema.json +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/gpt_instructions.txt +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/openai.md +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/__init__.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/.git +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/.github/workflows/main-checks.yml +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/.github/workflows/publish-pypi.yml +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/.github/workflows/pull-request-checks.yml +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/.github/workflows/shared.yml +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/.gitignore +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/.python-version +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/CODE_OF_CONDUCT.md +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/CONTRIBUTING.md +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/LICENSE +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/README.md +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/RELEASE.md +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/SECURITY.md +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/examples/README.md +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/examples/servers/simple-prompt/.python-version +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/examples/servers/simple-prompt/README.md +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/examples/servers/simple-prompt/mcp_simple_prompt/__init__.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/examples/servers/simple-prompt/mcp_simple_prompt/__main__.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/examples/servers/simple-prompt/mcp_simple_prompt/server.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/examples/servers/simple-prompt/pyproject.toml +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/examples/servers/simple-resource/.python-version +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/examples/servers/simple-resource/README.md +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/examples/servers/simple-resource/mcp_simple_resource/__init__.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/examples/servers/simple-resource/mcp_simple_resource/__main__.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/examples/servers/simple-resource/mcp_simple_resource/server.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/examples/servers/simple-resource/pyproject.toml +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/examples/servers/simple-tool/.python-version +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/examples/servers/simple-tool/README.md +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/examples/servers/simple-tool/mcp_simple_tool/__init__.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/examples/servers/simple-tool/mcp_simple_tool/__main__.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/examples/servers/simple-tool/mcp_simple_tool/server.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/examples/servers/simple-tool/pyproject.toml +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/pyproject.toml +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/src/mcp_wcgw/__init__.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/src/mcp_wcgw/client/__init__.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/src/mcp_wcgw/client/__main__.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/src/mcp_wcgw/client/session.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/src/mcp_wcgw/client/sse.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/src/mcp_wcgw/client/stdio.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/src/mcp_wcgw/py.typed +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/src/mcp_wcgw/server/__init__.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/src/mcp_wcgw/server/__main__.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/src/mcp_wcgw/server/models.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/src/mcp_wcgw/server/session.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/src/mcp_wcgw/server/sse.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/src/mcp_wcgw/server/stdio.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/src/mcp_wcgw/server/websocket.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/src/mcp_wcgw/shared/__init__.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/src/mcp_wcgw/shared/context.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/src/mcp_wcgw/shared/exceptions.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/src/mcp_wcgw/shared/memory.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/src/mcp_wcgw/shared/progress.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/src/mcp_wcgw/shared/session.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/src/mcp_wcgw/shared/version.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/src/mcp_wcgw/types.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/tests/__init__.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/tests/client/__init__.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/tests/client/test_session.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/tests/client/test_stdio.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/tests/conftest.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/tests/server/__init__.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/tests/server/test_session.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/tests/server/test_stdio.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/tests/shared/test_memory.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/tests/test_types.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/mcp_wcgw_fork/uv.lock +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/wcgw/client/__init__.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/wcgw/client/common.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/wcgw/client/computer_use.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/wcgw/client/diff-instructions.txt +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/wcgw/client/file_ops/diff_edit.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/wcgw/client/file_ops/search_replace.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/wcgw/client/mcp_server/Readme.md +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/wcgw/client/mcp_server/__init__.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/wcgw/client/memory.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/wcgw/client/repo_ops/display_tree.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/wcgw/client/repo_ops/path_prob.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/wcgw/client/repo_ops/paths_model.vocab +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/wcgw/client/repo_ops/paths_tokens.model +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/wcgw/client/repo_ops/repo_context.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/wcgw/client/sys_utils.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/wcgw/relay/serve.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/wcgw/relay/static/privacy.txt +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/src/wcgw/types_.py +0 -0
- {wcgw-2.8.3/src/wcgw/client → wcgw-2.8.5/src/wcgw_cli}/__main__.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/static/claude-ss.jpg +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/static/computer-use.jpg +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/static/example.jpg +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/static/rocket-icon.png +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/static/ss1.png +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/tests/client/file_ops/test_diff_edit.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/tests/client/file_ops/test_search_replace.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/tests/client/repo_ops/__init__.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/tests/client/repo_ops/test_display_tree.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/tests/client/repo_ops/test_display_tree_simple.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/tests/client/repo_ops/test_path_prob.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/tests/client/repo_ops/test_repo_context.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/tests/client/test_memory.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/tests/client/test_tools_basic.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/tests/client/test_tools_file_ops.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/tests/client/test_tools_files.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/tests/client/test_tools_shell.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/tests/client/test_tools_validation.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/tests/client/tools/__init__.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/tests/client/tools/test_command_validation.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/tests/client/tools/test_docker_operations.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/tests/client/tools/test_error_handling.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/tests/client/tools/test_execute_bash.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/tests/client/tools/test_file_operations.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/tests/client/tools/test_files/test1.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/tests/client/tools/test_files/test2.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/tests/client/tools/test_files/test_file.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/tests/client/tools/test_is_int.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/tests/client/tools/test_knowledge_transfer.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/tests/client/tools/test_large_blocks.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/tests/client/tools/test_render_terminal.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/tests/client/tools/test_terminal_output.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/tests/client/tools/test_user_interaction.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/tests/client/tools/test_write_file.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/tests/conftest.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/tests/test_common.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/tests/test_computer_use.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/tests/test_computer_use_base.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/tests/test_computer_use_shell.py +0 -0
- {wcgw-2.8.3 → wcgw-2.8.5}/tests/test_sys_utils.py +0 -0
- {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
|
+
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
|
[](https://github.com/rusiaaman/wcgw/actions/workflows/python-types.yml)
|
|
40
40
|
[](https://github.com/rusiaaman/wcgw/actions/workflows/python-publish.yml)
|
|
41
41
|
[](https://codecov.io/gh/rusiaaman/wcgw)
|
|
42
|
+
[](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
|
-
[](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.
|
|
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 = "
|
|
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,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 =
|
|
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
|
|
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
|
|
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
|
-
- "#
|
|
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
|
|
77
|
-
def print(self, *args, **kwargs
|
|
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
|
|
81
|
-
pass
|
|
80
|
+
def log(self, msg: str, *args: Any, **kwargs: Any) -> None: ...
|
|
82
81
|
|
|
83
82
|
|
|
84
|
-
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
|
|
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(
|
|
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
|
-
|
|
7
|
-
from
|
|
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
|
|
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
|
-
|
|
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(
|
|
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
|
|
2
|
-
|
|
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
|
|
7
|
-
|
|
8
|
-
|
|
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(
|
|
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=
|
|
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(
|
|
32
|
-
with patch(
|
|
33
|
-
|
|
34
|
-
|
|
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 /
|
|
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(
|
|
59
|
+
with patch("pathlib.Path", return_value=wcgw_dir):
|
|
56
60
|
# Create a mock file context
|
|
57
|
-
with patch(
|
|
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(
|
|
93
|
-
with patch(
|
|
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
|
|
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,
|
|
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()
|