wcgw 2.0.3__tar.gz → 2.1.0__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.0.3 → wcgw-2.1.0}/PKG-INFO +37 -7
- {wcgw-2.0.3 → wcgw-2.1.0}/README.md +34 -5
- {wcgw-2.0.3 → wcgw-2.1.0}/openai.md +2 -2
- {wcgw-2.0.3 → wcgw-2.1.0}/pyproject.toml +3 -2
- {wcgw-2.0.3 → wcgw-2.1.0}/src/wcgw/client/anthropic_client.py +0 -2
- {wcgw-2.0.3 → wcgw-2.1.0}/src/wcgw/client/computer_use.py +0 -1
- {wcgw-2.0.3 → wcgw-2.1.0}/src/wcgw/client/openai_client.py +0 -2
- {wcgw-2.0.3 → wcgw-2.1.0}/src/wcgw/client/tools.py +157 -95
- {wcgw-2.0.3 → wcgw-2.1.0}/uv.lock +12 -1
- {wcgw-2.0.3 → wcgw-2.1.0}/.github/workflows/python-publish.yml +0 -0
- {wcgw-2.0.3 → wcgw-2.1.0}/.github/workflows/python-tests.yml +0 -0
- {wcgw-2.0.3 → wcgw-2.1.0}/.github/workflows/python-types.yml +0 -0
- {wcgw-2.0.3 → wcgw-2.1.0}/.gitignore +0 -0
- {wcgw-2.0.3 → wcgw-2.1.0}/.python-version +0 -0
- {wcgw-2.0.3 → wcgw-2.1.0}/.vscode/settings.json +0 -0
- {wcgw-2.0.3 → wcgw-2.1.0}/gpt_action_json_schema.json +0 -0
- {wcgw-2.0.3 → wcgw-2.1.0}/gpt_instructions.txt +0 -0
- {wcgw-2.0.3 → wcgw-2.1.0}/src/__init__.py +0 -0
- {wcgw-2.0.3 → wcgw-2.1.0}/src/wcgw/__init__.py +0 -0
- {wcgw-2.0.3 → wcgw-2.1.0}/src/wcgw/client/__init__.py +0 -0
- {wcgw-2.0.3 → wcgw-2.1.0}/src/wcgw/client/__main__.py +0 -0
- {wcgw-2.0.3 → wcgw-2.1.0}/src/wcgw/client/cli.py +0 -0
- {wcgw-2.0.3 → wcgw-2.1.0}/src/wcgw/client/common.py +0 -0
- {wcgw-2.0.3 → wcgw-2.1.0}/src/wcgw/client/diff-instructions.txt +0 -0
- {wcgw-2.0.3 → wcgw-2.1.0}/src/wcgw/client/mcp_server/Readme.md +0 -0
- {wcgw-2.0.3 → wcgw-2.1.0}/src/wcgw/client/mcp_server/__init__.py +0 -0
- {wcgw-2.0.3 → wcgw-2.1.0}/src/wcgw/client/mcp_server/server.py +0 -0
- {wcgw-2.0.3 → wcgw-2.1.0}/src/wcgw/client/openai_utils.py +0 -0
- {wcgw-2.0.3 → wcgw-2.1.0}/src/wcgw/client/sys_utils.py +0 -0
- {wcgw-2.0.3 → wcgw-2.1.0}/src/wcgw/relay/serve.py +0 -0
- {wcgw-2.0.3 → wcgw-2.1.0}/src/wcgw/relay/static/privacy.txt +0 -0
- {wcgw-2.0.3 → wcgw-2.1.0}/src/wcgw/types_.py +0 -0
- {wcgw-2.0.3 → wcgw-2.1.0}/static/claude-ss.jpg +0 -0
- {wcgw-2.0.3 → wcgw-2.1.0}/static/computer-use.jpg +0 -0
- {wcgw-2.0.3 → wcgw-2.1.0}/static/example.jpg +0 -0
- {wcgw-2.0.3 → wcgw-2.1.0}/static/rocket-icon.png +0 -0
- {wcgw-2.0.3 → wcgw-2.1.0}/static/ss1.png +0 -0
- {wcgw-2.0.3 → wcgw-2.1.0}/tests/test_basic.py +0 -0
- {wcgw-2.0.3 → wcgw-2.1.0}/tests/test_tools.py +0 -0
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: wcgw
|
|
3
|
-
Version: 2.0
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 2.1.0
|
|
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>
|
|
7
7
|
Requires-Python: <3.13,>=3.11
|
|
8
8
|
Requires-Dist: anthropic>=0.39.0
|
|
9
9
|
Requires-Dist: fastapi>=0.115.0
|
|
10
|
+
Requires-Dist: humanize>=4.11.0
|
|
10
11
|
Requires-Dist: mcp
|
|
11
12
|
Requires-Dist: mypy>=1.11.2
|
|
12
13
|
Requires-Dist: nltk>=3.9.1
|
|
@@ -29,8 +30,8 @@ Description-Content-Type: text/markdown
|
|
|
29
30
|
|
|
30
31
|
# Shell and Coding agent for Claude and Chatgpt
|
|
31
32
|
|
|
32
|
-
- Claude - An MCP server on claude desktop for autonomous shell, coding and desktop control agent.
|
|
33
|
-
- Chatgpt - Allows custom gpt to talk to your shell via a relay server.
|
|
33
|
+
- Claude - An MCP server on claude desktop for autonomous shell, coding and desktop control agent. (mac only)
|
|
34
|
+
- Chatgpt - Allows custom gpt to talk to your shell via a relay server. (linux or mac)
|
|
34
35
|
|
|
35
36
|
[](https://github.com/rusiaaman/wcgw/actions/workflows/python-tests.yml)
|
|
36
37
|
[](https://github.com/rusiaaman/wcgw/actions/workflows/python-types.yml)
|
|
@@ -47,12 +48,30 @@ Description-Content-Type: text/markdown
|
|
|
47
48
|
- ⚡ **Full Shell Access**: No restrictions, complete control.
|
|
48
49
|
- ⚡ **Desktop control on Claude**: Screen capture, mouse control, keyboard control on claude desktop (on mac with docker linux)
|
|
49
50
|
- ⚡ **Create, Execute, Iterate**: Ask claude to keep running compiler checks till all errors are fixed, or ask it to keep checking for the status of a long running command till it's done.
|
|
51
|
+
- ⚡ **Large file edit**: Supports large file incremental edits to avoid token limit issues. Faster than full file write.
|
|
50
52
|
- ⚡ **Interactive Command Handling**: Supports interactive commands using arrow keys, interrupt, and ansi escape sequences.
|
|
51
53
|
- ⚡ **REPL support**: [beta] Supports python/node and other REPL execution.
|
|
52
54
|
|
|
55
|
+
## Top use cases examples
|
|
56
|
+
|
|
57
|
+
- Solve problem X using python, create and run test cases and fix any issues. Do it in a temporary directory
|
|
58
|
+
- Find instances of code with X behavior in my repository
|
|
59
|
+
- Git clone https://github.com/my/repo in my home directory, then understand the project, set up the environment and build
|
|
60
|
+
- Create a golang htmx tailwind webapp, then open browser to see if it works (use with puppeteer mcp)
|
|
61
|
+
- Edit or update a large file
|
|
62
|
+
- In a separate branch create feature Y, then use github cli to create a PR to original branch
|
|
63
|
+
- Command X is failing in Y directory, please run and fix issues
|
|
64
|
+
- Using X virtual environment run Y command
|
|
65
|
+
- Using cli tools, create build and test an android app. Finally run it using emulator for me to use
|
|
66
|
+
- Fix all mypy issues in my repo at X path.
|
|
67
|
+
- Using 'screen' run my server in background instead, then run another api server in bg, finally run the frontend build. Keep checking logs for any issues in all three
|
|
68
|
+
- Create repo wide unittest cases. Keep iterating through files and creating cases. Also keep running the tests after each update. Do not modify original code.
|
|
69
|
+
|
|
53
70
|
## Claude Setup
|
|
54
71
|
|
|
55
|
-
|
|
72
|
+
First install `uv` https://docs.astral.sh/uv/getting-started/installation/#installation-methods
|
|
73
|
+
|
|
74
|
+
Then update `claude_desktop_config.json` (~/Library/Application Support/Claude/claude_desktop_config.json)
|
|
56
75
|
|
|
57
76
|
```json
|
|
58
77
|
{
|
|
@@ -75,6 +94,13 @@ Update `claude_desktop_config.json` (~/Library/Application Support/Claude/claude
|
|
|
75
94
|
|
|
76
95
|
Then restart claude app.
|
|
77
96
|
|
|
97
|
+
_If there's an error in setting up_
|
|
98
|
+
|
|
99
|
+
- Make sure `uv` in the system PATH by running `uv --version` and also ensure `uv tool run wcgw --version` works globally.
|
|
100
|
+
Otherwise, re-install uv and follow instructions to add it into your .zshrc or .bashrc
|
|
101
|
+
- If there's still an issue, check that `uv tool run --from wcgw@latest --python 3.12 wcgw_mcp` runs in your terminal. It should have no output and shouldn't exit.
|
|
102
|
+
- Debug the mcp server using `npx @modelcontextprotocol/inspector@0.1.7 uv tool run --from wcgw@latest --python 3.12 wcgw_mcp`
|
|
103
|
+
|
|
78
104
|
### [Optional] Computer use support using desktop on docker
|
|
79
105
|
|
|
80
106
|
Computer use is disabled by default. Add `--computer-use` to enable it. This will add necessary tools to Claude including ScreenShot, Mouse and Keyboard control.
|
|
@@ -111,6 +137,12 @@ Then ask claude desktop app to control the docker os. It'll connect to the docke
|
|
|
111
137
|
|
|
112
138
|
Connect to `http://localhost:6080/vnc.html` for desktop view (VNC) of the system running in the docker.
|
|
113
139
|
|
|
140
|
+
The following requirements should be installed and working in the linux docker image:
|
|
141
|
+
|
|
142
|
+
1. Needs `xdotool` to execute commands on the desktop.
|
|
143
|
+
2. Needs `scrot` to take screenshots.
|
|
144
|
+
3. Needs `convert` from imagemagick to convert images.
|
|
145
|
+
|
|
114
146
|
## Usage
|
|
115
147
|
|
|
116
148
|
Wait for a few seconds. You should be able to see this icon if everything goes right.
|
|
@@ -124,7 +156,6 @@ Then ask claude to execute shell commands, read files, edit files, run your code
|
|
|
124
156
|
|
|
125
157
|
If you've run the docker for LLM to access, you can ask it to control the "docker os". If you don't provide the docker container id to it, it'll try to search for available docker using `docker ps` command.
|
|
126
158
|
|
|
127
|
-
|
|
128
159
|
## Chatgpt Setup
|
|
129
160
|
|
|
130
161
|
Read here: https://github.com/rusiaaman/wcgw/blob/main/openai.md
|
|
@@ -139,7 +170,6 @@ Read here: https://github.com/rusiaaman/wcgw/blob/main/openai.md
|
|
|
139
170
|
|
|
140
171
|

|
|
141
172
|
|
|
142
|
-
|
|
143
173
|
## [Optional] Local shell access with openai API key or anthropic API key
|
|
144
174
|
|
|
145
175
|
### Openai
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Shell and Coding agent for Claude and Chatgpt
|
|
2
2
|
|
|
3
|
-
- Claude - An MCP server on claude desktop for autonomous shell, coding and desktop control agent.
|
|
4
|
-
- Chatgpt - Allows custom gpt to talk to your shell via a relay server.
|
|
3
|
+
- Claude - An MCP server on claude desktop for autonomous shell, coding and desktop control agent. (mac only)
|
|
4
|
+
- Chatgpt - Allows custom gpt to talk to your shell via a relay server. (linux or mac)
|
|
5
5
|
|
|
6
6
|
[](https://github.com/rusiaaman/wcgw/actions/workflows/python-tests.yml)
|
|
7
7
|
[](https://github.com/rusiaaman/wcgw/actions/workflows/python-types.yml)
|
|
@@ -18,12 +18,30 @@
|
|
|
18
18
|
- ⚡ **Full Shell Access**: No restrictions, complete control.
|
|
19
19
|
- ⚡ **Desktop control on Claude**: Screen capture, mouse control, keyboard control on claude desktop (on mac with docker linux)
|
|
20
20
|
- ⚡ **Create, Execute, Iterate**: Ask claude to keep running compiler checks till all errors are fixed, or ask it to keep checking for the status of a long running command till it's done.
|
|
21
|
+
- ⚡ **Large file edit**: Supports large file incremental edits to avoid token limit issues. Faster than full file write.
|
|
21
22
|
- ⚡ **Interactive Command Handling**: Supports interactive commands using arrow keys, interrupt, and ansi escape sequences.
|
|
22
23
|
- ⚡ **REPL support**: [beta] Supports python/node and other REPL execution.
|
|
23
24
|
|
|
25
|
+
## Top use cases examples
|
|
26
|
+
|
|
27
|
+
- Solve problem X using python, create and run test cases and fix any issues. Do it in a temporary directory
|
|
28
|
+
- Find instances of code with X behavior in my repository
|
|
29
|
+
- Git clone https://github.com/my/repo in my home directory, then understand the project, set up the environment and build
|
|
30
|
+
- Create a golang htmx tailwind webapp, then open browser to see if it works (use with puppeteer mcp)
|
|
31
|
+
- Edit or update a large file
|
|
32
|
+
- In a separate branch create feature Y, then use github cli to create a PR to original branch
|
|
33
|
+
- Command X is failing in Y directory, please run and fix issues
|
|
34
|
+
- Using X virtual environment run Y command
|
|
35
|
+
- Using cli tools, create build and test an android app. Finally run it using emulator for me to use
|
|
36
|
+
- Fix all mypy issues in my repo at X path.
|
|
37
|
+
- Using 'screen' run my server in background instead, then run another api server in bg, finally run the frontend build. Keep checking logs for any issues in all three
|
|
38
|
+
- Create repo wide unittest cases. Keep iterating through files and creating cases. Also keep running the tests after each update. Do not modify original code.
|
|
39
|
+
|
|
24
40
|
## Claude Setup
|
|
25
41
|
|
|
26
|
-
|
|
42
|
+
First install `uv` https://docs.astral.sh/uv/getting-started/installation/#installation-methods
|
|
43
|
+
|
|
44
|
+
Then update `claude_desktop_config.json` (~/Library/Application Support/Claude/claude_desktop_config.json)
|
|
27
45
|
|
|
28
46
|
```json
|
|
29
47
|
{
|
|
@@ -46,6 +64,13 @@ Update `claude_desktop_config.json` (~/Library/Application Support/Claude/claude
|
|
|
46
64
|
|
|
47
65
|
Then restart claude app.
|
|
48
66
|
|
|
67
|
+
_If there's an error in setting up_
|
|
68
|
+
|
|
69
|
+
- Make sure `uv` in the system PATH by running `uv --version` and also ensure `uv tool run wcgw --version` works globally.
|
|
70
|
+
Otherwise, re-install uv and follow instructions to add it into your .zshrc or .bashrc
|
|
71
|
+
- If there's still an issue, check that `uv tool run --from wcgw@latest --python 3.12 wcgw_mcp` runs in your terminal. It should have no output and shouldn't exit.
|
|
72
|
+
- Debug the mcp server using `npx @modelcontextprotocol/inspector@0.1.7 uv tool run --from wcgw@latest --python 3.12 wcgw_mcp`
|
|
73
|
+
|
|
49
74
|
### [Optional] Computer use support using desktop on docker
|
|
50
75
|
|
|
51
76
|
Computer use is disabled by default. Add `--computer-use` to enable it. This will add necessary tools to Claude including ScreenShot, Mouse and Keyboard control.
|
|
@@ -82,6 +107,12 @@ Then ask claude desktop app to control the docker os. It'll connect to the docke
|
|
|
82
107
|
|
|
83
108
|
Connect to `http://localhost:6080/vnc.html` for desktop view (VNC) of the system running in the docker.
|
|
84
109
|
|
|
110
|
+
The following requirements should be installed and working in the linux docker image:
|
|
111
|
+
|
|
112
|
+
1. Needs `xdotool` to execute commands on the desktop.
|
|
113
|
+
2. Needs `scrot` to take screenshots.
|
|
114
|
+
3. Needs `convert` from imagemagick to convert images.
|
|
115
|
+
|
|
85
116
|
## Usage
|
|
86
117
|
|
|
87
118
|
Wait for a few seconds. You should be able to see this icon if everything goes right.
|
|
@@ -95,7 +126,6 @@ Then ask claude to execute shell commands, read files, edit files, run your code
|
|
|
95
126
|
|
|
96
127
|
If you've run the docker for LLM to access, you can ask it to control the "docker os". If you don't provide the docker container id to it, it'll try to search for available docker using `docker ps` command.
|
|
97
128
|
|
|
98
|
-
|
|
99
129
|
## Chatgpt Setup
|
|
100
130
|
|
|
101
131
|
Read here: https://github.com/rusiaaman/wcgw/blob/main/openai.md
|
|
@@ -110,7 +140,6 @@ Read here: https://github.com/rusiaaman/wcgw/blob/main/openai.md
|
|
|
110
140
|
|
|
111
141
|

|
|
112
142
|
|
|
113
|
-
|
|
114
143
|
## [Optional] Local shell access with openai API key or anthropic API key
|
|
115
144
|
|
|
116
145
|
### Openai
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
1. Run a relay server with a domain name and https support (or use ngrok) use the instructions in next section.
|
|
6
6
|
2. Create a custom gpt that connects to the relay server, instructions in next sections.
|
|
7
|
-
3. Run the
|
|
8
|
-
4. The custom GPT can now run any command on your
|
|
7
|
+
3. Run the client in any directory of choice. `uvx wcgw@latest`
|
|
8
|
+
4. The custom GPT can now run any command on your terminal
|
|
9
9
|
|
|
10
10
|
## Creating the relay server
|
|
11
11
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
authors = [{ name = "Aman Rusia", email = "gapypi@arcfu.com" }]
|
|
3
3
|
name = "wcgw"
|
|
4
|
-
version = "2.0
|
|
5
|
-
description = "
|
|
4
|
+
version = "2.1.0"
|
|
5
|
+
description = "Shell and coding agent on claude and chatgpt"
|
|
6
6
|
readme = "README.md"
|
|
7
7
|
requires-python = ">=3.11, <3.13"
|
|
8
8
|
dependencies = [
|
|
@@ -26,6 +26,7 @@ dependencies = [
|
|
|
26
26
|
"nltk>=3.9.1",
|
|
27
27
|
"anthropic>=0.39.0",
|
|
28
28
|
"mcp",
|
|
29
|
+
"humanize>=4.11.0",
|
|
29
30
|
]
|
|
30
31
|
|
|
31
32
|
[project.urls]
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import base64
|
|
3
3
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
4
|
+
import datetime
|
|
4
5
|
from io import BytesIO
|
|
5
6
|
import json
|
|
6
7
|
import mimetypes
|
|
@@ -23,6 +24,7 @@ from typing import (
|
|
|
23
24
|
TypedDict,
|
|
24
25
|
)
|
|
25
26
|
import uuid
|
|
27
|
+
import humanize
|
|
26
28
|
from pydantic import BaseModel, TypeAdapter
|
|
27
29
|
import typer
|
|
28
30
|
from .computer_use import run_computer_tool
|
|
@@ -106,20 +108,31 @@ PROMPT = PROMPT_CONST
|
|
|
106
108
|
|
|
107
109
|
|
|
108
110
|
def start_shell() -> pexpect.spawn: # type: ignore
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
111
|
+
try:
|
|
112
|
+
shell = pexpect.spawn(
|
|
113
|
+
"/bin/bash",
|
|
114
|
+
env={**os.environ, **{"PS1": PROMPT}}, # type: ignore[arg-type]
|
|
115
|
+
echo=False,
|
|
116
|
+
encoding="utf-8",
|
|
117
|
+
timeout=TIMEOUT,
|
|
118
|
+
)
|
|
119
|
+
shell.sendline(f"export PS1={PROMPT}")
|
|
120
|
+
except Exception as e:
|
|
121
|
+
traceback.print_exc()
|
|
122
|
+
console.log(f"Error starting shell: {e}. Retrying without rc ...")
|
|
123
|
+
|
|
124
|
+
shell = pexpect.spawn(
|
|
125
|
+
"/bin/bash --noprofile --norc",
|
|
126
|
+
env={**os.environ, **{"PS1": PROMPT}}, # type: ignore[arg-type]
|
|
127
|
+
echo=False,
|
|
128
|
+
encoding="utf-8",
|
|
129
|
+
timeout=TIMEOUT,
|
|
130
|
+
)
|
|
121
131
|
|
|
122
|
-
|
|
132
|
+
shell.expect(PROMPT, timeout=TIMEOUT)
|
|
133
|
+
shell.sendline("stty -icanon -echo")
|
|
134
|
+
shell.expect(PROMPT, timeout=TIMEOUT)
|
|
135
|
+
return shell
|
|
123
136
|
|
|
124
137
|
|
|
125
138
|
def _is_int(mystr: str) -> bool:
|
|
@@ -130,26 +143,26 @@ def _is_int(mystr: str) -> bool:
|
|
|
130
143
|
return False
|
|
131
144
|
|
|
132
145
|
|
|
133
|
-
def _get_exit_code() -> int:
|
|
146
|
+
def _get_exit_code(shell: pexpect.spawn) -> int: # type: ignore
|
|
134
147
|
if PROMPT != PROMPT_CONST:
|
|
135
148
|
return 0
|
|
136
149
|
# First reset the prompt in case venv was sourced or other reasons.
|
|
137
|
-
|
|
138
|
-
|
|
150
|
+
shell.sendline(f"export PS1={PROMPT}")
|
|
151
|
+
shell.expect(PROMPT, timeout=0.2)
|
|
139
152
|
# Reset echo also if it was enabled
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
153
|
+
shell.sendline("stty -icanon -echo")
|
|
154
|
+
shell.expect(PROMPT, timeout=0.2)
|
|
155
|
+
shell.sendline("echo $?")
|
|
143
156
|
before = ""
|
|
144
157
|
while not _is_int(before): # Consume all previous output
|
|
145
158
|
try:
|
|
146
|
-
|
|
159
|
+
shell.expect(PROMPT, timeout=0.2)
|
|
147
160
|
except pexpect.TIMEOUT:
|
|
148
161
|
print(f"Couldn't get exit code, before: {before}")
|
|
149
162
|
raise
|
|
150
|
-
assert isinstance(
|
|
163
|
+
assert isinstance(shell.before, str)
|
|
151
164
|
# Render because there could be some anscii escape sequences still set like in google colab env
|
|
152
|
-
before = render_terminal_output(
|
|
165
|
+
before = render_terminal_output(shell.before).strip()
|
|
153
166
|
|
|
154
167
|
try:
|
|
155
168
|
return int((before))
|
|
@@ -158,9 +171,71 @@ def _get_exit_code() -> int:
|
|
|
158
171
|
|
|
159
172
|
|
|
160
173
|
BASH_CLF_OUTPUT = Literal["repl", "pending"]
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
class BashState:
|
|
177
|
+
def __init__(self) -> None:
|
|
178
|
+
self._init()
|
|
179
|
+
|
|
180
|
+
def _init(self) -> None:
|
|
181
|
+
self._state: Literal["repl"] | datetime.datetime = "repl"
|
|
182
|
+
self._is_in_docker: Optional[str] = ""
|
|
183
|
+
self._cwd: str = os.getcwd()
|
|
184
|
+
self._shell = start_shell()
|
|
185
|
+
|
|
186
|
+
# Get exit info to ensure shell is ready
|
|
187
|
+
_get_exit_code(self._shell)
|
|
188
|
+
|
|
189
|
+
@property
|
|
190
|
+
def shell(self) -> pexpect.spawn: # type: ignore
|
|
191
|
+
return self._shell
|
|
192
|
+
|
|
193
|
+
def set_pending(self) -> None:
|
|
194
|
+
if not isinstance(self._state, datetime.datetime):
|
|
195
|
+
self._state = datetime.datetime.now()
|
|
196
|
+
|
|
197
|
+
def set_repl(self) -> None:
|
|
198
|
+
self._state = "repl"
|
|
199
|
+
|
|
200
|
+
@property
|
|
201
|
+
def state(self) -> BASH_CLF_OUTPUT:
|
|
202
|
+
if self._state == "repl":
|
|
203
|
+
return "repl"
|
|
204
|
+
return "pending"
|
|
205
|
+
|
|
206
|
+
@property
|
|
207
|
+
def is_in_docker(self) -> Optional[str]:
|
|
208
|
+
return self._is_in_docker
|
|
209
|
+
|
|
210
|
+
def set_in_docker(self, docker_image_id: str) -> None:
|
|
211
|
+
self._is_in_docker = docker_image_id
|
|
212
|
+
|
|
213
|
+
@property
|
|
214
|
+
def cwd(self) -> str:
|
|
215
|
+
return self._cwd
|
|
216
|
+
|
|
217
|
+
def update_cwd(self) -> str:
|
|
218
|
+
BASH_STATE.shell.sendline("pwd")
|
|
219
|
+
BASH_STATE.shell.expect(PROMPT, timeout=0.2)
|
|
220
|
+
assert isinstance(BASH_STATE.shell.before, str)
|
|
221
|
+
current_dir = render_terminal_output(BASH_STATE.shell.before).strip()
|
|
222
|
+
self._cwd = current_dir
|
|
223
|
+
return current_dir
|
|
224
|
+
|
|
225
|
+
def reset(self) -> None:
|
|
226
|
+
self.shell.close(True)
|
|
227
|
+
self._init()
|
|
228
|
+
|
|
229
|
+
def get_pending_for(self) -> str:
|
|
230
|
+
if isinstance(self._state, datetime.datetime):
|
|
231
|
+
timedelta = datetime.datetime.now() - self._state
|
|
232
|
+
return humanize.naturaldelta(
|
|
233
|
+
timedelta + datetime.timedelta(seconds=TIMEOUT)
|
|
234
|
+
)
|
|
235
|
+
return "Not pending"
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
BASH_STATE = BashState()
|
|
164
239
|
|
|
165
240
|
|
|
166
241
|
def initial_info() -> str:
|
|
@@ -169,18 +244,13 @@ def initial_info() -> str:
|
|
|
169
244
|
return f"""
|
|
170
245
|
System: {uname_sysname}
|
|
171
246
|
Machine: {uname_machine}
|
|
172
|
-
Current working directory: {
|
|
247
|
+
Current working directory: {BASH_STATE.cwd}
|
|
173
248
|
wcgw version: {importlib.metadata.version("wcgw")}
|
|
174
249
|
"""
|
|
175
250
|
|
|
176
251
|
|
|
177
252
|
def reset_shell() -> str:
|
|
178
|
-
|
|
179
|
-
SHELL.close(True)
|
|
180
|
-
SHELL = start_shell()
|
|
181
|
-
BASH_STATE = "repl"
|
|
182
|
-
IS_IN_DOCKER = ""
|
|
183
|
-
CWD = os.getcwd()
|
|
253
|
+
BASH_STATE.reset()
|
|
184
254
|
return "Reset successful" + get_status()
|
|
185
255
|
|
|
186
256
|
|
|
@@ -195,11 +265,11 @@ WAITING_INPUT_MESSAGE = """A command is already running. NOTE: You can't run mul
|
|
|
195
265
|
def update_repl_prompt(command: str) -> bool:
|
|
196
266
|
global PROMPT
|
|
197
267
|
if re.match(r"^wcgw_update_prompt\(\)$", command.strip()):
|
|
198
|
-
|
|
199
|
-
index =
|
|
268
|
+
BASH_STATE.shell.sendintr()
|
|
269
|
+
index = BASH_STATE.shell.expect([PROMPT, pexpect.TIMEOUT], timeout=0.2)
|
|
200
270
|
if index == 0:
|
|
201
271
|
return False
|
|
202
|
-
before =
|
|
272
|
+
before = BASH_STATE.shell.before or ""
|
|
203
273
|
assert before, "Something went wrong updating repl prompt"
|
|
204
274
|
PROMPT = before.split("\n")[-1].strip()
|
|
205
275
|
# Escape all regex
|
|
@@ -208,33 +278,24 @@ def update_repl_prompt(command: str) -> bool:
|
|
|
208
278
|
index = 0
|
|
209
279
|
while index == 0:
|
|
210
280
|
# Consume all REPL prompts till now
|
|
211
|
-
index =
|
|
281
|
+
index = BASH_STATE.shell.expect([PROMPT, pexpect.TIMEOUT], timeout=0.2)
|
|
212
282
|
print(f"Prompt updated to: {PROMPT}")
|
|
213
283
|
return True
|
|
214
284
|
return False
|
|
215
285
|
|
|
216
286
|
|
|
217
|
-
def get_cwd() -> str:
|
|
218
|
-
SHELL.sendline("pwd")
|
|
219
|
-
SHELL.expect(PROMPT, timeout=0.2)
|
|
220
|
-
assert isinstance(SHELL.before, str)
|
|
221
|
-
current_dir = render_terminal_output(SHELL.before).strip()
|
|
222
|
-
return current_dir
|
|
223
|
-
|
|
224
|
-
|
|
225
287
|
def get_status() -> str:
|
|
226
|
-
global CWD
|
|
227
288
|
exit_code: Optional[int] = None
|
|
228
289
|
|
|
229
290
|
status = "\n\n---\n\n"
|
|
230
|
-
if BASH_STATE == "pending":
|
|
291
|
+
if BASH_STATE.state == "pending":
|
|
231
292
|
status += "status = still running\n"
|
|
232
|
-
status += "
|
|
293
|
+
status += "running for = " + BASH_STATE.get_pending_for() + "\n"
|
|
294
|
+
status += "cwd = " + BASH_STATE.cwd + "\n"
|
|
233
295
|
else:
|
|
234
|
-
exit_code = _get_exit_code()
|
|
296
|
+
exit_code = _get_exit_code(BASH_STATE.shell)
|
|
235
297
|
status += f"status = exited with code {exit_code}\n"
|
|
236
|
-
|
|
237
|
-
status += "cwd = " + CWD + "\n"
|
|
298
|
+
status += "cwd = " + BASH_STATE.update_cwd() + "\n"
|
|
238
299
|
|
|
239
300
|
return status.rstrip()
|
|
240
301
|
|
|
@@ -245,13 +306,12 @@ def execute_bash(
|
|
|
245
306
|
max_tokens: Optional[int],
|
|
246
307
|
timeout_s: Optional[float],
|
|
247
308
|
) -> tuple[str, float]:
|
|
248
|
-
global SHELL, BASH_STATE, CWD
|
|
249
309
|
try:
|
|
250
310
|
is_interrupt = False
|
|
251
311
|
if isinstance(bash_arg, BashCommand):
|
|
252
312
|
updated_repl_mode = update_repl_prompt(bash_arg.command)
|
|
253
313
|
if updated_repl_mode:
|
|
254
|
-
BASH_STATE
|
|
314
|
+
BASH_STATE.set_repl()
|
|
255
315
|
response = (
|
|
256
316
|
"Prompt updated, you can execute REPL lines using BashCommand now"
|
|
257
317
|
)
|
|
@@ -262,7 +322,7 @@ def execute_bash(
|
|
|
262
322
|
)
|
|
263
323
|
|
|
264
324
|
console.print(f"$ {bash_arg.command}")
|
|
265
|
-
if BASH_STATE == "pending":
|
|
325
|
+
if BASH_STATE.state == "pending":
|
|
266
326
|
raise ValueError(WAITING_INPUT_MESSAGE)
|
|
267
327
|
command = bash_arg.command.strip()
|
|
268
328
|
|
|
@@ -271,7 +331,7 @@ def execute_bash(
|
|
|
271
331
|
"Command should not contain newline character in middle. Run only one command at a time."
|
|
272
332
|
)
|
|
273
333
|
|
|
274
|
-
|
|
334
|
+
BASH_STATE.shell.sendline(command)
|
|
275
335
|
|
|
276
336
|
else:
|
|
277
337
|
if (
|
|
@@ -292,29 +352,29 @@ def execute_bash(
|
|
|
292
352
|
console.print(f"Sending special sequence: {bash_arg.send_specials}")
|
|
293
353
|
for char in bash_arg.send_specials:
|
|
294
354
|
if char == "Key-up":
|
|
295
|
-
|
|
355
|
+
BASH_STATE.shell.send("\033[A")
|
|
296
356
|
elif char == "Key-down":
|
|
297
|
-
|
|
357
|
+
BASH_STATE.shell.send("\033[B")
|
|
298
358
|
elif char == "Key-left":
|
|
299
|
-
|
|
359
|
+
BASH_STATE.shell.send("\033[D")
|
|
300
360
|
elif char == "Key-right":
|
|
301
|
-
|
|
361
|
+
BASH_STATE.shell.send("\033[C")
|
|
302
362
|
elif char == "Enter":
|
|
303
|
-
|
|
363
|
+
BASH_STATE.shell.send("\n")
|
|
304
364
|
elif char == "Ctrl-c":
|
|
305
|
-
|
|
365
|
+
BASH_STATE.shell.sendintr()
|
|
306
366
|
is_interrupt = True
|
|
307
367
|
elif char == "Ctrl-d":
|
|
308
|
-
|
|
368
|
+
BASH_STATE.shell.sendintr()
|
|
309
369
|
is_interrupt = True
|
|
310
370
|
elif char == "Ctrl-z":
|
|
311
|
-
|
|
371
|
+
BASH_STATE.shell.send("\x1a")
|
|
312
372
|
else:
|
|
313
373
|
raise Exception(f"Unknown special character: {char}")
|
|
314
374
|
elif bash_arg.send_ascii:
|
|
315
375
|
console.print(f"Sending ASCII sequence: {bash_arg.send_ascii}")
|
|
316
376
|
for ascii_char in bash_arg.send_ascii:
|
|
317
|
-
|
|
377
|
+
BASH_STATE.shell.send(chr(ascii_char))
|
|
318
378
|
if ascii_char == 3:
|
|
319
379
|
is_interrupt = True
|
|
320
380
|
else:
|
|
@@ -326,7 +386,7 @@ def execute_bash(
|
|
|
326
386
|
|
|
327
387
|
updated_repl_mode = update_repl_prompt(bash_arg.send_text)
|
|
328
388
|
if updated_repl_mode:
|
|
329
|
-
BASH_STATE
|
|
389
|
+
BASH_STATE.set_repl()
|
|
330
390
|
response = "Prompt updated, you can execute REPL lines using BashCommand now"
|
|
331
391
|
console.print(response)
|
|
332
392
|
return (
|
|
@@ -334,20 +394,18 @@ def execute_bash(
|
|
|
334
394
|
0,
|
|
335
395
|
)
|
|
336
396
|
console.print(f"Interact text: {bash_arg.send_text}")
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
BASH_STATE = "repl"
|
|
397
|
+
BASH_STATE.shell.sendline(bash_arg.send_text)
|
|
340
398
|
|
|
341
399
|
except KeyboardInterrupt:
|
|
342
|
-
|
|
343
|
-
|
|
400
|
+
BASH_STATE.shell.sendintr()
|
|
401
|
+
BASH_STATE.shell.expect(PROMPT)
|
|
344
402
|
return "---\n\nFailure: user interrupted the execution", 0.0
|
|
345
403
|
|
|
346
404
|
wait = timeout_s or TIMEOUT
|
|
347
|
-
index =
|
|
405
|
+
index = BASH_STATE.shell.expect([PROMPT, pexpect.TIMEOUT], timeout=wait)
|
|
348
406
|
if index == 1:
|
|
349
|
-
BASH_STATE
|
|
350
|
-
text =
|
|
407
|
+
BASH_STATE.set_pending()
|
|
408
|
+
text = BASH_STATE.shell.before or ""
|
|
351
409
|
|
|
352
410
|
text = render_terminal_output(text[-100_000:])
|
|
353
411
|
tokens = enc.encode(text)
|
|
@@ -372,11 +430,13 @@ Otherwise, you may want to try Ctrl-c again or program specific exit interactive
|
|
|
372
430
|
|
|
373
431
|
return text, 0
|
|
374
432
|
|
|
433
|
+
BASH_STATE.set_repl()
|
|
434
|
+
|
|
375
435
|
if is_interrupt:
|
|
376
436
|
return "Interrupt successful", 0.0
|
|
377
437
|
|
|
378
|
-
assert isinstance(
|
|
379
|
-
output = render_terminal_output(
|
|
438
|
+
assert isinstance(BASH_STATE.shell.before, str)
|
|
439
|
+
output = render_terminal_output(BASH_STATE.shell.before)
|
|
380
440
|
|
|
381
441
|
tokens = enc.encode(output)
|
|
382
442
|
if max_tokens and len(tokens) >= max_tokens:
|
|
@@ -390,8 +450,7 @@ Otherwise, you may want to try Ctrl-c again or program specific exit interactive
|
|
|
390
450
|
traceback.print_exc()
|
|
391
451
|
console.print("Malformed output, restarting shell", style="red")
|
|
392
452
|
# Malformed output, restart shell
|
|
393
|
-
|
|
394
|
-
SHELL = start_shell()
|
|
453
|
+
BASH_STATE.reset()
|
|
395
454
|
output = "(exit shell has restarted)"
|
|
396
455
|
return output, 0
|
|
397
456
|
|
|
@@ -435,8 +494,7 @@ T = TypeVar("T")
|
|
|
435
494
|
|
|
436
495
|
def ensure_no_previous_output(func: Callable[Param, T]) -> Callable[Param, T]:
|
|
437
496
|
def wrapper(*args: Param.args, **kwargs: Param.kwargs) -> T:
|
|
438
|
-
|
|
439
|
-
if BASH_STATE == "pending":
|
|
497
|
+
if BASH_STATE.state == "pending":
|
|
440
498
|
raise ValueError(WAITING_INPUT_MESSAGE)
|
|
441
499
|
|
|
442
500
|
return func(*args, **kwargs)
|
|
@@ -446,9 +504,9 @@ def ensure_no_previous_output(func: Callable[Param, T]) -> Callable[Param, T]:
|
|
|
446
504
|
|
|
447
505
|
def read_image_from_shell(file_path: str) -> ImageData:
|
|
448
506
|
if not os.path.isabs(file_path):
|
|
449
|
-
file_path = os.path.join(
|
|
507
|
+
file_path = os.path.join(BASH_STATE.cwd, file_path)
|
|
450
508
|
|
|
451
|
-
if not
|
|
509
|
+
if not BASH_STATE.is_in_docker:
|
|
452
510
|
if not os.path.exists(file_path):
|
|
453
511
|
raise ValueError(f"File {file_path} does not exist")
|
|
454
512
|
|
|
@@ -459,7 +517,9 @@ def read_image_from_shell(file_path: str) -> ImageData:
|
|
|
459
517
|
return ImageData(media_type=image_type, data=image_b64) # type: ignore
|
|
460
518
|
else:
|
|
461
519
|
with TemporaryDirectory() as tmpdir:
|
|
462
|
-
rcode = os.system(
|
|
520
|
+
rcode = os.system(
|
|
521
|
+
f"docker cp {BASH_STATE.is_in_docker}:{file_path} {tmpdir}"
|
|
522
|
+
)
|
|
463
523
|
if rcode != 0:
|
|
464
524
|
raise Exception(f"Error: Read failed with code {rcode}")
|
|
465
525
|
path_ = os.path.join(tmpdir, os.path.basename(file_path))
|
|
@@ -472,11 +532,11 @@ def read_image_from_shell(file_path: str) -> ImageData:
|
|
|
472
532
|
|
|
473
533
|
def write_file(writefile: CreateFileNew, error_on_exist: bool) -> str:
|
|
474
534
|
if not os.path.isabs(writefile.file_path):
|
|
475
|
-
return f"Failure: file_path should be absolute path, current working directory is {
|
|
535
|
+
return f"Failure: file_path should be absolute path, current working directory is {BASH_STATE.cwd}"
|
|
476
536
|
else:
|
|
477
537
|
path_ = writefile.file_path
|
|
478
538
|
|
|
479
|
-
if not
|
|
539
|
+
if not BASH_STATE.is_in_docker:
|
|
480
540
|
if error_on_exist and os.path.exists(path_):
|
|
481
541
|
file_data = Path(path_).read_text()
|
|
482
542
|
if file_data:
|
|
@@ -494,7 +554,7 @@ def write_file(writefile: CreateFileNew, error_on_exist: bool) -> str:
|
|
|
494
554
|
if error_on_exist:
|
|
495
555
|
# Check if it exists using os.system
|
|
496
556
|
cmd = f"test -f {path_}"
|
|
497
|
-
status = os.system(f'docker exec {
|
|
557
|
+
status = os.system(f'docker exec {BASH_STATE.is_in_docker} bash -c "{cmd}"')
|
|
498
558
|
if status == 0:
|
|
499
559
|
return f"Error: can't write to existing file {path_}, use other functions to edit the file"
|
|
500
560
|
|
|
@@ -504,11 +564,13 @@ def write_file(writefile: CreateFileNew, error_on_exist: bool) -> str:
|
|
|
504
564
|
f.write(writefile.file_content)
|
|
505
565
|
os.chmod(tmppath, 0o777)
|
|
506
566
|
parent_dir = os.path.dirname(path_)
|
|
507
|
-
rcode = os.system(
|
|
567
|
+
rcode = os.system(
|
|
568
|
+
f"docker exec {BASH_STATE.is_in_docker} mkdir -p {parent_dir}"
|
|
569
|
+
)
|
|
508
570
|
if rcode != 0:
|
|
509
571
|
return f"Error: Write failed with code while creating dirs {rcode}"
|
|
510
572
|
|
|
511
|
-
rcode = os.system(f"docker cp {tmppath} {
|
|
573
|
+
rcode = os.system(f"docker cp {tmppath} {BASH_STATE.is_in_docker}:{path_}")
|
|
512
574
|
if rcode != 0:
|
|
513
575
|
return f"Error: Write failed with code {rcode}"
|
|
514
576
|
|
|
@@ -585,12 +647,12 @@ def do_diff_edit(fedit: FileEdit) -> str:
|
|
|
585
647
|
|
|
586
648
|
if not os.path.isabs(fedit.file_path):
|
|
587
649
|
raise Exception(
|
|
588
|
-
f"Failure: file_path should be absolute path, current working directory is {
|
|
650
|
+
f"Failure: file_path should be absolute path, current working directory is {BASH_STATE.cwd}"
|
|
589
651
|
)
|
|
590
652
|
else:
|
|
591
653
|
path_ = fedit.file_path
|
|
592
654
|
|
|
593
|
-
if not
|
|
655
|
+
if not BASH_STATE.is_in_docker:
|
|
594
656
|
if not os.path.exists(path_):
|
|
595
657
|
raise Exception(f"Error: file {path_} does not exist")
|
|
596
658
|
|
|
@@ -599,7 +661,7 @@ def do_diff_edit(fedit: FileEdit) -> str:
|
|
|
599
661
|
else:
|
|
600
662
|
# Copy from docker
|
|
601
663
|
with TemporaryDirectory() as tmpdir:
|
|
602
|
-
rcode = os.system(f"docker cp {
|
|
664
|
+
rcode = os.system(f"docker cp {BASH_STATE.is_in_docker}:{path_} {tmpdir}")
|
|
603
665
|
if rcode != 0:
|
|
604
666
|
raise Exception(f"Error: Read failed with code {rcode}")
|
|
605
667
|
path_tmp = os.path.join(tmpdir, os.path.basename(path_))
|
|
@@ -652,7 +714,7 @@ def do_diff_edit(fedit: FileEdit) -> str:
|
|
|
652
714
|
"Error: no valid search-replace blocks found, please check your syntax for FileEdit"
|
|
653
715
|
)
|
|
654
716
|
|
|
655
|
-
if not
|
|
717
|
+
if not BASH_STATE.is_in_docker:
|
|
656
718
|
with open(path_, "w") as f:
|
|
657
719
|
f.write(apply_diff_to)
|
|
658
720
|
else:
|
|
@@ -662,7 +724,7 @@ def do_diff_edit(fedit: FileEdit) -> str:
|
|
|
662
724
|
f.write(apply_diff_to)
|
|
663
725
|
os.chmod(path_tmp, 0o777)
|
|
664
726
|
# Copy to docker using docker cp
|
|
665
|
-
rcode = os.system(f"docker cp {path_tmp} {
|
|
727
|
+
rcode = os.system(f"docker cp {path_tmp} {BASH_STATE.is_in_docker}:{path_}")
|
|
666
728
|
if rcode != 0:
|
|
667
729
|
raise Exception(f"Error: Write failed with code {rcode}")
|
|
668
730
|
|
|
@@ -856,7 +918,7 @@ def get_tool_output(
|
|
|
856
918
|
if imgBs64:
|
|
857
919
|
console.print("Captured screenshot")
|
|
858
920
|
outputs.append(ImageData(media_type="image/png", data=imgBs64))
|
|
859
|
-
if not
|
|
921
|
+
if not BASH_STATE.is_in_docker and isinstance(arg, GetScreenInfo):
|
|
860
922
|
try:
|
|
861
923
|
# At this point we should go into the docker env
|
|
862
924
|
res, _ = execute_bash(
|
|
@@ -882,7 +944,7 @@ def get_tool_output(
|
|
|
882
944
|
raise Exception(
|
|
883
945
|
f"Some error happened while going inside docker. I've reset the shell. Please start again. Error {e}"
|
|
884
946
|
)
|
|
885
|
-
|
|
947
|
+
BASH_STATE.set_in_docker(arg.docker_image_id)
|
|
886
948
|
return outputs, outputs_cost[1]
|
|
887
949
|
else:
|
|
888
950
|
raise ValueError(f"Unknown tool: {arg}")
|
|
@@ -987,9 +1049,9 @@ def read_file(readfile: ReadFile, max_tokens: Optional[int]) -> str:
|
|
|
987
1049
|
console.print(f"Reading file: {readfile.file_path}")
|
|
988
1050
|
|
|
989
1051
|
if not os.path.isabs(readfile.file_path):
|
|
990
|
-
return f"Failure: file_path should be absolute path, current working directory is {
|
|
1052
|
+
return f"Failure: file_path should be absolute path, current working directory is {BASH_STATE.cwd}"
|
|
991
1053
|
|
|
992
|
-
if not
|
|
1054
|
+
if not BASH_STATE.is_in_docker:
|
|
993
1055
|
path = Path(readfile.file_path)
|
|
994
1056
|
if not path.exists():
|
|
995
1057
|
return f"Error: file {readfile.file_path} does not exist"
|
|
@@ -234,6 +234,15 @@ wheels = [
|
|
|
234
234
|
{ url = "https://files.pythonhosted.org/packages/e1/9b/a181f281f65d776426002f330c31849b86b31fc9d848db62e16f03ff739f/httpx_sse-0.4.0-py3-none-any.whl", hash = "sha256:f329af6eae57eaa2bdfd962b42524764af68075ea87370a2de920af5341e318f", size = 7819 },
|
|
235
235
|
]
|
|
236
236
|
|
|
237
|
+
[[package]]
|
|
238
|
+
name = "humanize"
|
|
239
|
+
version = "4.11.0"
|
|
240
|
+
source = { registry = "https://pypi.org/simple" }
|
|
241
|
+
sdist = { url = "https://files.pythonhosted.org/packages/6a/40/64a912b9330786df25e58127194d4a5a7441f818b400b155e748a270f924/humanize-4.11.0.tar.gz", hash = "sha256:e66f36020a2d5a974c504bd2555cf770621dbdbb6d82f94a6857c0b1ea2608be", size = 80374 }
|
|
242
|
+
wheels = [
|
|
243
|
+
{ url = "https://files.pythonhosted.org/packages/92/75/4bc3e242ad13f2e6c12e0b0401ab2c5e5c6f0d7da37ec69bc808e24e0ccb/humanize-4.11.0-py3-none-any.whl", hash = "sha256:b53caaec8532bcb2fff70c8826f904c35943f8cecaca29d272d9df38092736c0", size = 128055 },
|
|
244
|
+
]
|
|
245
|
+
|
|
237
246
|
[[package]]
|
|
238
247
|
name = "idna"
|
|
239
248
|
version = "3.10"
|
|
@@ -860,11 +869,12 @@ wheels = [
|
|
|
860
869
|
|
|
861
870
|
[[package]]
|
|
862
871
|
name = "wcgw"
|
|
863
|
-
version = "2.0
|
|
872
|
+
version = "2.1.0"
|
|
864
873
|
source = { editable = "." }
|
|
865
874
|
dependencies = [
|
|
866
875
|
{ name = "anthropic" },
|
|
867
876
|
{ name = "fastapi" },
|
|
877
|
+
{ name = "humanize" },
|
|
868
878
|
{ name = "mcp" },
|
|
869
879
|
{ name = "mypy" },
|
|
870
880
|
{ name = "nltk" },
|
|
@@ -898,6 +908,7 @@ dev = [
|
|
|
898
908
|
requires-dist = [
|
|
899
909
|
{ name = "anthropic", specifier = ">=0.39.0" },
|
|
900
910
|
{ name = "fastapi", specifier = ">=0.115.0" },
|
|
911
|
+
{ name = "humanize", specifier = ">=4.11.0" },
|
|
901
912
|
{ name = "mcp", git = "https://github.com/rusiaaman/python-sdk?rev=53b69f397eae6ac81a51b84b34ff52b3119f11cb" },
|
|
902
913
|
{ name = "mypy", specifier = ">=1.11.2" },
|
|
903
914
|
{ name = "nltk", specifier = ">=3.9.1" },
|
|
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
|