wcgw 1.2.2__py3-none-any.whl → 1.4.0__py3-none-any.whl
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/__init__.py +1 -0
- wcgw/client/anthropic_client.py +6 -5
- wcgw/client/diff-instructions.txt +5 -1
- wcgw/client/mcp_server/Readme.md +26 -0
- wcgw/client/mcp_server/__init__.py +11 -0
- wcgw/client/mcp_server/server.py +222 -0
- wcgw/client/openai_client.py +6 -6
- wcgw/client/tools.py +65 -45
- wcgw/relay/serve.py +36 -14
- wcgw/types_.py +4 -5
- {wcgw-1.2.2.dist-info → wcgw-1.4.0.dist-info}/METADATA +6 -2
- wcgw-1.4.0.dist-info/RECORD +20 -0
- {wcgw-1.2.2.dist-info → wcgw-1.4.0.dist-info}/entry_points.txt +1 -0
- wcgw-1.2.2.dist-info/RECORD +0 -17
- {wcgw-1.2.2.dist-info → wcgw-1.4.0.dist-info}/WHEEL +0 -0
wcgw/__init__.py
CHANGED
wcgw/client/anthropic_client.py
CHANGED
|
@@ -29,7 +29,6 @@ from ..types_ import (
|
|
|
29
29
|
FileEdit,
|
|
30
30
|
ReadFile,
|
|
31
31
|
ReadImage,
|
|
32
|
-
Writefile,
|
|
33
32
|
ResetShell,
|
|
34
33
|
)
|
|
35
34
|
|
|
@@ -165,6 +164,7 @@ def loop(
|
|
|
165
164
|
- Optionally `exit shell has restarted` is the output, in which case environment resets, you can run fresh commands.
|
|
166
165
|
- The first line might be `(...truncated)` if the output is too long.
|
|
167
166
|
- Always run `pwd` if you get any file or directory not found error to make sure you're not lost.
|
|
167
|
+
- The control will return to you in 5 seconds regardless of the status. For heavy commands, keep checking status using BashInteraction till they are finished.
|
|
168
168
|
""",
|
|
169
169
|
),
|
|
170
170
|
ToolParam(
|
|
@@ -193,7 +193,7 @@ def loop(
|
|
|
193
193
|
- Write content to a new file. Provide file path and content. Use this instead of BashCommand for writing new files.
|
|
194
194
|
- This doesn't create any directories, please create directories using `mkdir -p` BashCommand.
|
|
195
195
|
- Provide absolute file path only.
|
|
196
|
-
- For editing existing files, use FileEdit.
|
|
196
|
+
- For editing existing files, use FileEdit instead of this tool.
|
|
197
197
|
""",
|
|
198
198
|
),
|
|
199
199
|
ToolParam(
|
|
@@ -219,7 +219,7 @@ def loop(
|
|
|
219
219
|
uname_machine = os.uname().machine
|
|
220
220
|
|
|
221
221
|
system = f"""
|
|
222
|
-
You're
|
|
222
|
+
You're an expert software engineer with shell and code knowledge.
|
|
223
223
|
|
|
224
224
|
Instructions:
|
|
225
225
|
|
|
@@ -227,10 +227,11 @@ Instructions:
|
|
|
227
227
|
- First understand about the project by getting the folder structure (ignoring .git, node_modules, venv, etc.)
|
|
228
228
|
- Always read relevant files before editing.
|
|
229
229
|
- Do not provide code snippets unless asked by the user, instead directly edit the code.
|
|
230
|
-
|
|
230
|
+
|
|
231
231
|
System information:
|
|
232
232
|
- System: {uname_sysname}
|
|
233
233
|
- Machine: {uname_machine}
|
|
234
|
+
- Current directory: {os.getcwd()}
|
|
234
235
|
"""
|
|
235
236
|
|
|
236
237
|
with open(os.path.join(os.path.dirname(__file__), "diff-instructions.txt")) as f:
|
|
@@ -361,7 +362,7 @@ System information:
|
|
|
361
362
|
enc,
|
|
362
363
|
limit - cost,
|
|
363
364
|
loop,
|
|
364
|
-
max_tokens=
|
|
365
|
+
max_tokens=8000,
|
|
365
366
|
)
|
|
366
367
|
except Exception as e:
|
|
367
368
|
output_or_done = (
|
|
@@ -4,6 +4,7 @@ Instructions for editing files.
|
|
|
4
4
|
|
|
5
5
|
Only edit the files using the following SEARCH/REPLACE blocks.
|
|
6
6
|
```
|
|
7
|
+
file_edit_using_search_replace_blocks="""
|
|
7
8
|
<<<<<<< SEARCH
|
|
8
9
|
def hello():
|
|
9
10
|
"print a greeting"
|
|
@@ -32,6 +33,7 @@ def call_hello_renamed():
|
|
|
32
33
|
hello_renamed()
|
|
33
34
|
impl2()
|
|
34
35
|
>>>>>>> REPLACE
|
|
36
|
+
"""
|
|
35
37
|
```
|
|
36
38
|
|
|
37
39
|
# *SEARCH/REPLACE block* Rules:
|
|
@@ -43,7 +45,7 @@ Every *SEARCH/REPLACE block* must use this format:
|
|
|
43
45
|
4. The lines to replace into the source code
|
|
44
46
|
5. The end of the replace block: >>>>>>> REPLACE
|
|
45
47
|
|
|
46
|
-
Every "<<<<<<< SEARCH" section must *EXACTLY MATCH* the existing file content, character for character, including all comments, docstrings, etc.
|
|
48
|
+
Every "<<<<<<< SEARCH" section must *EXACTLY MATCH* the existing file content, character for character, including all comments, docstrings, whitespaces, etc.
|
|
47
49
|
|
|
48
50
|
*SEARCH/REPLACE* blocks will *only* replace the first match occurrence.
|
|
49
51
|
Including multiple unique *SEARCH/REPLACE* blocks if needed.
|
|
@@ -53,3 +55,5 @@ Keep *SEARCH/REPLACE* blocks concise.
|
|
|
53
55
|
Break large *SEARCH/REPLACE* blocks into a series of smaller blocks that each change a small portion of the file.
|
|
54
56
|
Include just the changing lines, and a few surrounding lines if needed for uniqueness.
|
|
55
57
|
Do not include long runs of unchanging lines in *SEARCH/REPLACE* blocks.
|
|
58
|
+
|
|
59
|
+
Preserve leading spaces and indentations in both SEARCH and REPLACE blocks.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Claude desktop support
|
|
2
|
+
|
|
3
|
+
## Setup
|
|
4
|
+
|
|
5
|
+
Update `claude_desktop_config.json`
|
|
6
|
+
|
|
7
|
+
```json
|
|
8
|
+
{
|
|
9
|
+
"mcpServers": {
|
|
10
|
+
"wcgw": {
|
|
11
|
+
"command": "uvx",
|
|
12
|
+
"args": ["--from", "wcgw", "wcgw_mcp"]
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Then restart claude app.
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
You should be able to see this icon if everything goes right.
|
|
23
|
+
|
|
24
|
+

|
|
25
|
+
|
|
26
|
+
Then ask claude to execute shell commands, read files, edit files, run your code, etc.
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import importlib
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import traceback
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from mcp.server.models import InitializationOptions
|
|
9
|
+
import mcp.types as types
|
|
10
|
+
from mcp.types import Tool as ToolParam
|
|
11
|
+
from mcp.server import NotificationOptions, Server
|
|
12
|
+
from pydantic import AnyUrl, ValidationError
|
|
13
|
+
import mcp.server.stdio
|
|
14
|
+
from ..tools import DoneFlag, get_tool_output, which_tool_name, default_enc
|
|
15
|
+
from ...types_ import (
|
|
16
|
+
BashCommand,
|
|
17
|
+
BashInteraction,
|
|
18
|
+
CreateFileNew,
|
|
19
|
+
FileEdit,
|
|
20
|
+
ReadFile,
|
|
21
|
+
ReadImage,
|
|
22
|
+
ResetShell,
|
|
23
|
+
Initialize,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
server = Server("wcgw")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@server.list_resources()
|
|
31
|
+
async def handle_list_resources() -> list[types.Resource]:
|
|
32
|
+
return []
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@server.read_resource()
|
|
36
|
+
async def handle_read_resource(uri: AnyUrl) -> str:
|
|
37
|
+
raise ValueError("No resources available")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@server.list_prompts()
|
|
41
|
+
async def handle_list_prompts() -> list[types.Prompt]:
|
|
42
|
+
return []
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@server.get_prompt()
|
|
46
|
+
async def handle_get_prompt(
|
|
47
|
+
name: str, arguments: dict[str, str] | None
|
|
48
|
+
) -> types.GetPromptResult:
|
|
49
|
+
types.GetPromptResult(messages=[])
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@server.list_tools()
|
|
53
|
+
async def handle_list_tools() -> list[types.Tool]:
|
|
54
|
+
"""
|
|
55
|
+
List available tools.
|
|
56
|
+
Each tool specifies its arguments using JSON Schema validation.
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
with open(
|
|
60
|
+
os.path.join(
|
|
61
|
+
os.path.dirname(os.path.dirname(__file__)), "diff-instructions.txt"
|
|
62
|
+
)
|
|
63
|
+
) as f:
|
|
64
|
+
diffinstructions = f.read()
|
|
65
|
+
return [
|
|
66
|
+
ToolParam(
|
|
67
|
+
inputSchema=Initialize.model_json_schema(),
|
|
68
|
+
name="Initialize",
|
|
69
|
+
description="""
|
|
70
|
+
- Always call this at the start of the conversation before anything else.
|
|
71
|
+
""",
|
|
72
|
+
),
|
|
73
|
+
ToolParam(
|
|
74
|
+
inputSchema=BashCommand.model_json_schema(),
|
|
75
|
+
name="BashCommand",
|
|
76
|
+
description="""
|
|
77
|
+
- Execute a bash command. This is stateful (beware with subsequent calls).
|
|
78
|
+
- Do not use interactive commands like nano. Prefer writing simpler commands.
|
|
79
|
+
- Status of the command and the current working directory will always be returned at the end.
|
|
80
|
+
- Optionally `exit shell has restarted` is the output, in which case environment resets, you can run fresh commands.
|
|
81
|
+
- The first line might be `(...truncated)` if the output is too long.
|
|
82
|
+
- Always run `pwd` if you get any file or directory not found error to make sure you're not lost.
|
|
83
|
+
- The control will return to you in 5 seconds regardless of the status. For heavy commands, keep checking status using BashInteraction till they are finished.
|
|
84
|
+
""",
|
|
85
|
+
),
|
|
86
|
+
ToolParam(
|
|
87
|
+
inputSchema=BashInteraction.model_json_schema(),
|
|
88
|
+
name="BashInteraction",
|
|
89
|
+
description="""
|
|
90
|
+
- Interact with running program using this tool
|
|
91
|
+
- Special keys like arrows, interrupts, enter, etc.
|
|
92
|
+
- Send text input to the running program.
|
|
93
|
+
- Send send_specials=["Enter"] to recheck status of a running program.
|
|
94
|
+
- Only one of send_text, send_specials, send_ascii should be provided.
|
|
95
|
+
""",
|
|
96
|
+
),
|
|
97
|
+
ToolParam(
|
|
98
|
+
inputSchema=ReadFile.model_json_schema(),
|
|
99
|
+
name="ReadFile",
|
|
100
|
+
description="""
|
|
101
|
+
- Read full file content
|
|
102
|
+
- Provide absolute file path only
|
|
103
|
+
""",
|
|
104
|
+
),
|
|
105
|
+
ToolParam(
|
|
106
|
+
inputSchema=CreateFileNew.model_json_schema(),
|
|
107
|
+
name="CreateFileNew",
|
|
108
|
+
description="""
|
|
109
|
+
- Write content to a new file. Provide file path and content. Use this instead of BashCommand for writing new files.
|
|
110
|
+
- This doesn't create any directories, please create directories using `mkdir -p` BashCommand.
|
|
111
|
+
- Provide absolute file path only.
|
|
112
|
+
- For editing existing files, use FileEdit instead of this tool.
|
|
113
|
+
""",
|
|
114
|
+
),
|
|
115
|
+
ToolParam(
|
|
116
|
+
inputSchema=ReadImage.model_json_schema(),
|
|
117
|
+
name="ReadImage",
|
|
118
|
+
description="Read an image from the shell.",
|
|
119
|
+
),
|
|
120
|
+
ToolParam(
|
|
121
|
+
inputSchema=ResetShell.model_json_schema(),
|
|
122
|
+
name="ResetShell",
|
|
123
|
+
description="Resets the shell. Use only if all interrupts and prompt reset attempts have failed repeatedly.",
|
|
124
|
+
),
|
|
125
|
+
ToolParam(
|
|
126
|
+
inputSchema=FileEdit.model_json_schema(),
|
|
127
|
+
name="FileEdit",
|
|
128
|
+
description="""
|
|
129
|
+
- Use absolute file path only.
|
|
130
|
+
- Use SEARCH/REPLACE blocks to edit the file.
|
|
131
|
+
"""
|
|
132
|
+
+ diffinstructions,
|
|
133
|
+
),
|
|
134
|
+
ToolParam(
|
|
135
|
+
inputSchema=ReadImage.model_json_schema(),
|
|
136
|
+
name="ReadImage",
|
|
137
|
+
description="""
|
|
138
|
+
- Read an image from the shell.
|
|
139
|
+
""",
|
|
140
|
+
),
|
|
141
|
+
]
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
@server.call_tool()
|
|
145
|
+
async def handle_call_tool(
|
|
146
|
+
name: str, arguments: dict | None
|
|
147
|
+
) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
|
|
148
|
+
if not arguments:
|
|
149
|
+
raise ValueError("Missing arguments")
|
|
150
|
+
|
|
151
|
+
tool_type = which_tool_name(name)
|
|
152
|
+
|
|
153
|
+
try:
|
|
154
|
+
tool_call = tool_type(**arguments)
|
|
155
|
+
except ValidationError:
|
|
156
|
+
|
|
157
|
+
def try_json(x: str) -> Any:
|
|
158
|
+
try:
|
|
159
|
+
return json.loads(x)
|
|
160
|
+
except json.JSONDecodeError:
|
|
161
|
+
return x
|
|
162
|
+
|
|
163
|
+
tool_call = tool_type(**{k: try_json(v) for k, v in arguments.items()})
|
|
164
|
+
|
|
165
|
+
try:
|
|
166
|
+
output_or_done, _ = get_tool_output(
|
|
167
|
+
tool_call, default_enc, 0.0, lambda x, y: ("", 0), 8000
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
except Exception as e:
|
|
171
|
+
output_or_done = f"GOT EXCEPTION while calling tool. Error: {e}"
|
|
172
|
+
tb = traceback.format_exc()
|
|
173
|
+
print(output_or_done + "\n" + tb)
|
|
174
|
+
|
|
175
|
+
assert not isinstance(output_or_done, DoneFlag)
|
|
176
|
+
if isinstance(output_or_done, str):
|
|
177
|
+
if issubclass(tool_type, Initialize):
|
|
178
|
+
output_or_done += """
|
|
179
|
+
|
|
180
|
+
You're an expert software engineer with shell and code knowledge.
|
|
181
|
+
|
|
182
|
+
Instructions:
|
|
183
|
+
|
|
184
|
+
- You should use the provided bash execution, reading and writing file tools to complete objective.
|
|
185
|
+
- First understand about the project by getting the folder structure (ignoring .git, node_modules, venv, etc.)
|
|
186
|
+
- Always read relevant files before editing.
|
|
187
|
+
- Do not provide code snippets unless asked by the user, instead directly edit the code.
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
Additional instructions:
|
|
191
|
+
Always run `pwd` if you get any file or directory not found error to make sure you're not lost, or to get absolute cwd.
|
|
192
|
+
|
|
193
|
+
Always write production ready, syntactically correct code.
|
|
194
|
+
"""
|
|
195
|
+
|
|
196
|
+
return [types.TextContent(type="text", text=output_or_done)]
|
|
197
|
+
|
|
198
|
+
return [
|
|
199
|
+
types.ImageContent(
|
|
200
|
+
type="image",
|
|
201
|
+
data=output_or_done.data,
|
|
202
|
+
mimeType=output_or_done.media_type,
|
|
203
|
+
)
|
|
204
|
+
]
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
async def main() -> None:
|
|
208
|
+
version = importlib.metadata.version("wcgw")
|
|
209
|
+
# Run the server using stdin/stdout streams
|
|
210
|
+
async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
|
|
211
|
+
await server.run(
|
|
212
|
+
read_stream,
|
|
213
|
+
write_stream,
|
|
214
|
+
InitializationOptions(
|
|
215
|
+
server_name="wcgw",
|
|
216
|
+
server_version=version,
|
|
217
|
+
capabilities=server.get_capabilities(
|
|
218
|
+
notification_options=NotificationOptions(),
|
|
219
|
+
experimental_capabilities={},
|
|
220
|
+
),
|
|
221
|
+
),
|
|
222
|
+
)
|
wcgw/client/openai_client.py
CHANGED
|
@@ -27,7 +27,6 @@ from ..types_ import (
|
|
|
27
27
|
FileEdit,
|
|
28
28
|
ReadImage,
|
|
29
29
|
ReadFile,
|
|
30
|
-
Writefile,
|
|
31
30
|
ResetShell,
|
|
32
31
|
)
|
|
33
32
|
|
|
@@ -148,8 +147,7 @@ def loop(
|
|
|
148
147
|
my_dir = os.path.dirname(__file__)
|
|
149
148
|
|
|
150
149
|
config = Config(
|
|
151
|
-
model=cast(
|
|
152
|
-
Models, os.getenv("OPENAI_MODEL", "gpt-4o-2024-08-06").lower()),
|
|
150
|
+
model=cast(Models, os.getenv("OPENAI_MODEL", "gpt-4o-2024-08-06").lower()),
|
|
153
151
|
cost_limit=0.1,
|
|
154
152
|
cost_unit="$",
|
|
155
153
|
cost_file={
|
|
@@ -177,6 +175,7 @@ def loop(
|
|
|
177
175
|
- Optionally `exit shell has restarted` is the output, in which case environment resets, you can run fresh commands.
|
|
178
176
|
- The first line might be `(...truncated)` if the output is too long.
|
|
179
177
|
- Always run `pwd` if you get any file or directory not found error to make sure you're not lost.
|
|
178
|
+
- The control will return to you in 5 seconds regardless of the status. For heavy commands, keep checking status using BashInteraction till they are finished.
|
|
180
179
|
""",
|
|
181
180
|
),
|
|
182
181
|
openai.pydantic_function_tool(
|
|
@@ -201,7 +200,7 @@ def loop(
|
|
|
201
200
|
- Write content to a new file. Provide file path and content. Use this instead of BashCommand for writing new files.
|
|
202
201
|
- This doesn't create any directories, please create directories using `mkdir -p` BashCommand.
|
|
203
202
|
- Provide absolute file path only.
|
|
204
|
-
- For editing existing files, use FileEdit.""",
|
|
203
|
+
- For editing existing files, use FileEdit instead of this tool.""",
|
|
205
204
|
),
|
|
206
205
|
openai.pydantic_function_tool(
|
|
207
206
|
FileEdit,
|
|
@@ -223,7 +222,7 @@ def loop(
|
|
|
223
222
|
uname_machine = os.uname().machine
|
|
224
223
|
|
|
225
224
|
system = f"""
|
|
226
|
-
You're
|
|
225
|
+
You're an expert software engineer with shell and code knowledge.
|
|
227
226
|
|
|
228
227
|
Instructions:
|
|
229
228
|
|
|
@@ -235,6 +234,7 @@ Instructions:
|
|
|
235
234
|
System information:
|
|
236
235
|
- System: {uname_sysname}
|
|
237
236
|
- Machine: {uname_machine}
|
|
237
|
+
- Current directory: {os.getcwd()}
|
|
238
238
|
|
|
239
239
|
"""
|
|
240
240
|
|
|
@@ -341,7 +341,7 @@ System information:
|
|
|
341
341
|
enc,
|
|
342
342
|
limit - cost,
|
|
343
343
|
loop,
|
|
344
|
-
max_tokens=
|
|
344
|
+
max_tokens=8000,
|
|
345
345
|
)
|
|
346
346
|
except Exception as e:
|
|
347
347
|
output_or_done = (
|
wcgw/client/tools.py
CHANGED
|
@@ -45,7 +45,7 @@ from openai.types.chat import (
|
|
|
45
45
|
ChatCompletionMessage,
|
|
46
46
|
ParsedChatCompletionMessage,
|
|
47
47
|
)
|
|
48
|
-
from nltk.metrics.distance import edit_distance
|
|
48
|
+
from nltk.metrics.distance import edit_distance # type: ignore[import-untyped]
|
|
49
49
|
|
|
50
50
|
from ..types_ import (
|
|
51
51
|
BashCommand,
|
|
@@ -53,10 +53,10 @@ from ..types_ import (
|
|
|
53
53
|
CreateFileNew,
|
|
54
54
|
FileEditFindReplace,
|
|
55
55
|
FileEdit,
|
|
56
|
+
Initialize,
|
|
56
57
|
ReadFile,
|
|
57
58
|
ReadImage,
|
|
58
59
|
ResetShell,
|
|
59
|
-
Writefile,
|
|
60
60
|
)
|
|
61
61
|
|
|
62
62
|
from .common import CostData, Models, discard_input
|
|
@@ -95,7 +95,8 @@ def ask_confirmation(prompt: Confirmation) -> str:
|
|
|
95
95
|
return "Yes" if response.lower() == "y" else "No"
|
|
96
96
|
|
|
97
97
|
|
|
98
|
-
|
|
98
|
+
PROMPT_CONST = "#@wcgw@#"
|
|
99
|
+
PROMPT = PROMPT_CONST
|
|
99
100
|
|
|
100
101
|
|
|
101
102
|
def start_shell() -> pexpect.spawn: # type: ignore
|
|
@@ -124,7 +125,7 @@ def _is_int(mystr: str) -> bool:
|
|
|
124
125
|
|
|
125
126
|
|
|
126
127
|
def _get_exit_code() -> int:
|
|
127
|
-
if PROMPT !=
|
|
128
|
+
if PROMPT != PROMPT_CONST:
|
|
128
129
|
return 0
|
|
129
130
|
# First reset the prompt in case venv was sourced or other reasons.
|
|
130
131
|
SHELL.sendline(f"export PS1={PROMPT}")
|
|
@@ -155,6 +156,16 @@ BASH_STATE: BASH_CLF_OUTPUT = "repl"
|
|
|
155
156
|
CWD = os.getcwd()
|
|
156
157
|
|
|
157
158
|
|
|
159
|
+
def initial_info() -> str:
|
|
160
|
+
uname_sysname = os.uname().sysname
|
|
161
|
+
uname_machine = os.uname().machine
|
|
162
|
+
return f"""
|
|
163
|
+
System: {uname_sysname}
|
|
164
|
+
Machine: {uname_machine}
|
|
165
|
+
Current working directory: {CWD}
|
|
166
|
+
"""
|
|
167
|
+
|
|
168
|
+
|
|
158
169
|
def reset_shell() -> str:
|
|
159
170
|
global SHELL, BASH_STATE, CWD
|
|
160
171
|
SHELL.close(True)
|
|
@@ -306,9 +317,7 @@ def execute_bash(
|
|
|
306
317
|
updated_repl_mode = update_repl_prompt(bash_arg.send_text)
|
|
307
318
|
if updated_repl_mode:
|
|
308
319
|
BASH_STATE = "repl"
|
|
309
|
-
response =
|
|
310
|
-
"Prompt updated, you can execute REPL lines using BashCommand now"
|
|
311
|
-
)
|
|
320
|
+
response = "Prompt updated, you can execute REPL lines using BashCommand now"
|
|
312
321
|
console.print(response)
|
|
313
322
|
return (
|
|
314
323
|
response,
|
|
@@ -334,7 +343,7 @@ def execute_bash(
|
|
|
334
343
|
tokens = enc.encode(text)
|
|
335
344
|
|
|
336
345
|
if max_tokens and len(tokens) >= max_tokens:
|
|
337
|
-
text = "...(truncated)\n" + enc.decode(tokens[-(max_tokens - 1):])
|
|
346
|
+
text = "...(truncated)\n" + enc.decode(tokens[-(max_tokens - 1) :])
|
|
338
347
|
|
|
339
348
|
if is_interrupt:
|
|
340
349
|
text = (
|
|
@@ -361,7 +370,7 @@ Otherwise, you may want to try Ctrl-c again or program specific exit interactive
|
|
|
361
370
|
|
|
362
371
|
tokens = enc.encode(output)
|
|
363
372
|
if max_tokens and len(tokens) >= max_tokens:
|
|
364
|
-
output = "...(truncated)\n" + enc.decode(tokens[-(max_tokens - 1):])
|
|
373
|
+
output = "...(truncated)\n" + enc.decode(tokens[-(max_tokens - 1) :])
|
|
365
374
|
|
|
366
375
|
try:
|
|
367
376
|
exit_status = get_status()
|
|
@@ -436,10 +445,10 @@ def read_image_from_shell(file_path: str) -> ImageData:
|
|
|
436
445
|
image_bytes = image_file.read()
|
|
437
446
|
image_b64 = base64.b64encode(image_bytes).decode("utf-8")
|
|
438
447
|
image_type = mimetypes.guess_type(file_path)[0]
|
|
439
|
-
return ImageData(media_type=image_type, data=image_b64)
|
|
448
|
+
return ImageData(media_type=image_type, data=image_b64) # type: ignore
|
|
440
449
|
|
|
441
450
|
|
|
442
|
-
def write_file(writefile:
|
|
451
|
+
def write_file(writefile: CreateFileNew, error_on_exist: bool) -> str:
|
|
443
452
|
if not os.path.isabs(writefile.file_path):
|
|
444
453
|
return f"Failure: file_path should be absolute path, current working directory is {CWD}"
|
|
445
454
|
else:
|
|
@@ -488,15 +497,21 @@ def find_least_edit_distance_substring(
|
|
|
488
497
|
edit_distance_sum = 0
|
|
489
498
|
for j in range(len(find_lines)):
|
|
490
499
|
if (i + j) < len(content_lines):
|
|
491
|
-
edit_distance_sum += edit_distance(
|
|
492
|
-
content_lines[i + j], find_lines[j])
|
|
500
|
+
edit_distance_sum += edit_distance(content_lines[i + j], find_lines[j])
|
|
493
501
|
else:
|
|
494
502
|
edit_distance_sum += len(find_lines[j])
|
|
495
503
|
if edit_distance_sum < min_edit_distance:
|
|
496
504
|
min_edit_distance = edit_distance_sum
|
|
497
505
|
orig_start_index = new_to_original_indices[i]
|
|
498
|
-
orig_end_index =
|
|
499
|
-
|
|
506
|
+
orig_end_index = (
|
|
507
|
+
new_to_original_indices.get(
|
|
508
|
+
i + len(find_lines) - 1, len(orig_content_lines) - 1
|
|
509
|
+
)
|
|
510
|
+
+ 1
|
|
511
|
+
)
|
|
512
|
+
min_edit_distance_lines = orig_content_lines[
|
|
513
|
+
orig_start_index:orig_end_index
|
|
514
|
+
]
|
|
500
515
|
return "\n".join(min_edit_distance_lines), min_edit_distance
|
|
501
516
|
|
|
502
517
|
|
|
@@ -525,7 +540,8 @@ def do_diff_edit(fedit: FileEdit) -> str:
|
|
|
525
540
|
|
|
526
541
|
if not os.path.isabs(fedit.file_path):
|
|
527
542
|
raise Exception(
|
|
528
|
-
f"Failure: file_path should be absolute path, current working directory is {CWD}"
|
|
543
|
+
f"Failure: file_path should be absolute path, current working directory is {CWD}"
|
|
544
|
+
)
|
|
529
545
|
else:
|
|
530
546
|
path_ = fedit.file_path
|
|
531
547
|
|
|
@@ -535,13 +551,16 @@ def do_diff_edit(fedit: FileEdit) -> str:
|
|
|
535
551
|
with open(path_) as f:
|
|
536
552
|
apply_diff_to = f.read()
|
|
537
553
|
|
|
554
|
+
fedit.file_edit_using_search_replace_blocks = (
|
|
555
|
+
fedit.file_edit_using_search_replace_blocks.strip()
|
|
556
|
+
)
|
|
538
557
|
lines = fedit.file_edit_using_search_replace_blocks.split("\n")
|
|
539
558
|
|
|
540
559
|
if not lines or not re.match(r"^<<<<<<+\s*SEARCH\s*$", lines[0]):
|
|
541
560
|
raise Exception(
|
|
542
561
|
"Error: first line should be `<<<<<< SEARCH` to start a search-replace block"
|
|
543
562
|
)
|
|
544
|
-
|
|
563
|
+
|
|
545
564
|
n_lines = len(lines)
|
|
546
565
|
i = 0
|
|
547
566
|
replacement_count = 0
|
|
@@ -561,15 +580,14 @@ def do_diff_edit(fedit: FileEdit) -> str:
|
|
|
561
580
|
|
|
562
581
|
for line in search_block:
|
|
563
582
|
console.log("> " + line)
|
|
564
|
-
console.log("
|
|
583
|
+
console.log("=======")
|
|
565
584
|
for line in replace_block:
|
|
566
585
|
console.log("< " + line)
|
|
567
|
-
|
|
586
|
+
console.log("\n\n\n\n")
|
|
568
587
|
search_block_ = "\n".join(search_block)
|
|
569
588
|
replace_block_ = "\n".join(replace_block)
|
|
570
589
|
|
|
571
|
-
apply_diff_to = edit_content(
|
|
572
|
-
apply_diff_to, search_block_, replace_block_)
|
|
590
|
+
apply_diff_to = edit_content(apply_diff_to, search_block_, replace_block_)
|
|
573
591
|
replacement_count += 1
|
|
574
592
|
else:
|
|
575
593
|
i += 1
|
|
@@ -588,7 +606,8 @@ def do_diff_edit(fedit: FileEdit) -> str:
|
|
|
588
606
|
def file_edit(fedit: FileEditFindReplace) -> str:
|
|
589
607
|
if not os.path.isabs(fedit.file_path):
|
|
590
608
|
raise Exception(
|
|
591
|
-
f"Failure: file_path should be absolute path, current working directory is {CWD}"
|
|
609
|
+
f"Failure: file_path should be absolute path, current working directory is {CWD}"
|
|
610
|
+
)
|
|
592
611
|
else:
|
|
593
612
|
path_ = fedit.file_path
|
|
594
613
|
|
|
@@ -598,17 +617,14 @@ def file_edit(fedit: FileEditFindReplace) -> str:
|
|
|
598
617
|
if not fedit.find_lines:
|
|
599
618
|
raise Exception("Error: `find_lines` cannot be empty")
|
|
600
619
|
|
|
601
|
-
out_string = "\n".join(
|
|
602
|
-
|
|
603
|
-
in_string = "\n".join(
|
|
604
|
-
"< " + line for line in fedit.replace_with_lines.split("\n"))
|
|
620
|
+
out_string = "\n".join("> " + line for line in fedit.find_lines.split("\n"))
|
|
621
|
+
in_string = "\n".join("< " + line for line in fedit.replace_with_lines.split("\n"))
|
|
605
622
|
console.log(f"Editing file: {path_}\n---\n{out_string}\n---\n{in_string}\n---")
|
|
606
623
|
try:
|
|
607
624
|
with open(path_) as f:
|
|
608
625
|
content = f.read()
|
|
609
626
|
|
|
610
|
-
content = edit_content(content, fedit.find_lines,
|
|
611
|
-
fedit.replace_with_lines)
|
|
627
|
+
content = edit_content(content, fedit.find_lines, fedit.replace_with_lines)
|
|
612
628
|
|
|
613
629
|
with open(path_, "w") as f:
|
|
614
630
|
f.write(content)
|
|
@@ -645,7 +661,6 @@ TOOLS = (
|
|
|
645
661
|
| BashCommand
|
|
646
662
|
| BashInteraction
|
|
647
663
|
| ResetShell
|
|
648
|
-
| Writefile
|
|
649
664
|
| CreateFileNew
|
|
650
665
|
| FileEditFindReplace
|
|
651
666
|
| FileEdit
|
|
@@ -653,6 +668,7 @@ TOOLS = (
|
|
|
653
668
|
| DoneFlag
|
|
654
669
|
| ReadImage
|
|
655
670
|
| ReadFile
|
|
671
|
+
| Initialize
|
|
656
672
|
)
|
|
657
673
|
|
|
658
674
|
|
|
@@ -670,8 +686,6 @@ def which_tool_name(name: str) -> Type[TOOLS]:
|
|
|
670
686
|
return BashInteraction
|
|
671
687
|
elif name == "ResetShell":
|
|
672
688
|
return ResetShell
|
|
673
|
-
elif name == "Writefile":
|
|
674
|
-
return Writefile
|
|
675
689
|
elif name == "CreateFileNew":
|
|
676
690
|
return CreateFileNew
|
|
677
691
|
elif name == "FileEditFindReplace":
|
|
@@ -686,6 +700,8 @@ def which_tool_name(name: str) -> Type[TOOLS]:
|
|
|
686
700
|
return ReadImage
|
|
687
701
|
elif name == "ReadFile":
|
|
688
702
|
return ReadFile
|
|
703
|
+
elif name == "Initialize":
|
|
704
|
+
return Initialize
|
|
689
705
|
else:
|
|
690
706
|
raise ValueError(f"Unknown tool name: {name}")
|
|
691
707
|
|
|
@@ -696,13 +712,13 @@ def get_tool_output(
|
|
|
696
712
|
| BashCommand
|
|
697
713
|
| BashInteraction
|
|
698
714
|
| ResetShell
|
|
699
|
-
| Writefile
|
|
700
715
|
| CreateFileNew
|
|
701
716
|
| FileEditFindReplace
|
|
702
717
|
| FileEdit
|
|
703
718
|
| AIAssistant
|
|
704
719
|
| DoneFlag
|
|
705
720
|
| ReadImage
|
|
721
|
+
| Initialize
|
|
706
722
|
| ReadFile,
|
|
707
723
|
enc: tiktoken.Encoding,
|
|
708
724
|
limit: float,
|
|
@@ -715,7 +731,6 @@ def get_tool_output(
|
|
|
715
731
|
| BashCommand
|
|
716
732
|
| BashInteraction
|
|
717
733
|
| ResetShell
|
|
718
|
-
| Writefile
|
|
719
734
|
| CreateFileNew
|
|
720
735
|
| FileEditFindReplace
|
|
721
736
|
| FileEdit
|
|
@@ -723,12 +738,12 @@ def get_tool_output(
|
|
|
723
738
|
| DoneFlag
|
|
724
739
|
| ReadImage
|
|
725
740
|
| ReadFile
|
|
741
|
+
| Initialize
|
|
726
742
|
](
|
|
727
743
|
Confirmation
|
|
728
744
|
| BashCommand
|
|
729
745
|
| BashInteraction
|
|
730
746
|
| ResetShell
|
|
731
|
-
| Writefile
|
|
732
747
|
| CreateFileNew
|
|
733
748
|
| FileEditFindReplace
|
|
734
749
|
| FileEdit
|
|
@@ -736,6 +751,7 @@ def get_tool_output(
|
|
|
736
751
|
| DoneFlag
|
|
737
752
|
| ReadImage
|
|
738
753
|
| ReadFile
|
|
754
|
+
| Initialize
|
|
739
755
|
)
|
|
740
756
|
arg = adapter.validate_python(args)
|
|
741
757
|
else:
|
|
@@ -747,9 +763,6 @@ def get_tool_output(
|
|
|
747
763
|
elif isinstance(arg, (BashCommand | BashInteraction)):
|
|
748
764
|
console.print("Calling execute bash tool")
|
|
749
765
|
output = execute_bash(enc, arg, max_tokens)
|
|
750
|
-
elif isinstance(arg, Writefile):
|
|
751
|
-
console.print("Calling write file tool")
|
|
752
|
-
output = write_file(arg, False), 0
|
|
753
766
|
elif isinstance(arg, CreateFileNew):
|
|
754
767
|
console.print("Calling write file tool")
|
|
755
768
|
output = write_file(arg, True), 0
|
|
@@ -770,10 +783,13 @@ def get_tool_output(
|
|
|
770
783
|
output = read_image_from_shell(arg.file_path), 0.0
|
|
771
784
|
elif isinstance(arg, ReadFile):
|
|
772
785
|
console.print("Calling read file tool")
|
|
773
|
-
output = read_file(arg), 0.0
|
|
786
|
+
output = read_file(arg, max_tokens), 0.0
|
|
774
787
|
elif isinstance(arg, ResetShell):
|
|
775
788
|
console.print("Calling reset shell tool")
|
|
776
789
|
output = reset_shell(), 0.0
|
|
790
|
+
elif isinstance(arg, Initialize):
|
|
791
|
+
console.print("Calling initial info tool")
|
|
792
|
+
output = initial_info(), 0.0
|
|
777
793
|
else:
|
|
778
794
|
raise ValueError(f"Unknown tool: {arg}")
|
|
779
795
|
|
|
@@ -785,8 +801,7 @@ History = list[ChatCompletionMessageParam]
|
|
|
785
801
|
|
|
786
802
|
default_enc = tiktoken.encoding_for_model("gpt-4o")
|
|
787
803
|
default_model: Models = "gpt-4o-2024-08-06"
|
|
788
|
-
default_cost = CostData(cost_per_1m_input_tokens=0.15,
|
|
789
|
-
cost_per_1m_output_tokens=0.6)
|
|
804
|
+
default_cost = CostData(cost_per_1m_input_tokens=0.15, cost_per_1m_output_tokens=0.6)
|
|
790
805
|
curr_cost = 0.0
|
|
791
806
|
|
|
792
807
|
|
|
@@ -794,13 +809,13 @@ class Mdata(BaseModel):
|
|
|
794
809
|
data: (
|
|
795
810
|
BashCommand
|
|
796
811
|
| BashInteraction
|
|
797
|
-
| Writefile
|
|
798
812
|
| CreateFileNew
|
|
799
813
|
| ResetShell
|
|
800
814
|
| FileEditFindReplace
|
|
801
815
|
| FileEdit
|
|
802
816
|
| str
|
|
803
817
|
| ReadFile
|
|
818
|
+
| Initialize
|
|
804
819
|
)
|
|
805
820
|
|
|
806
821
|
|
|
@@ -829,8 +844,7 @@ def register_client(server_url: str, client_uuid: str = "") -> None:
|
|
|
829
844
|
raise Exception(mdata)
|
|
830
845
|
try:
|
|
831
846
|
output, cost = get_tool_output(
|
|
832
|
-
mdata.data, default_enc, 0.0, lambda x, y: (
|
|
833
|
-
"", 0), None
|
|
847
|
+
mdata.data, default_enc, 0.0, lambda x, y: ("", 0), 8000
|
|
834
848
|
)
|
|
835
849
|
curr_cost += cost
|
|
836
850
|
print(f"{curr_cost=}")
|
|
@@ -863,8 +877,7 @@ def app(
|
|
|
863
877
|
register_client(server_url, client_uuid or "")
|
|
864
878
|
|
|
865
879
|
|
|
866
|
-
def read_file(readfile: ReadFile) -> str:
|
|
867
|
-
|
|
880
|
+
def read_file(readfile: ReadFile, max_tokens: Optional[int]) -> str:
|
|
868
881
|
console.print(f"Reading file: {readfile.file_path}")
|
|
869
882
|
|
|
870
883
|
if not os.path.isabs(readfile.file_path):
|
|
@@ -876,4 +889,11 @@ def read_file(readfile: ReadFile) -> str:
|
|
|
876
889
|
|
|
877
890
|
with path.open("r") as f:
|
|
878
891
|
content = f.read()
|
|
892
|
+
|
|
893
|
+
if max_tokens is not None:
|
|
894
|
+
tokens = default_enc.encode(content)
|
|
895
|
+
if len(tokens) > max_tokens:
|
|
896
|
+
content = default_enc.decode(tokens[: max_tokens - 5])
|
|
897
|
+
content += "\n...(truncated)"
|
|
898
|
+
|
|
879
899
|
return content
|
wcgw/relay/serve.py
CHANGED
|
@@ -20,9 +20,9 @@ from ..types_ import (
|
|
|
20
20
|
CreateFileNew,
|
|
21
21
|
FileEditFindReplace,
|
|
22
22
|
FileEdit,
|
|
23
|
+
Initialize,
|
|
23
24
|
ReadFile,
|
|
24
25
|
ResetShell,
|
|
25
|
-
Writefile,
|
|
26
26
|
Specials,
|
|
27
27
|
)
|
|
28
28
|
|
|
@@ -31,12 +31,12 @@ class Mdata(BaseModel):
|
|
|
31
31
|
data: (
|
|
32
32
|
BashCommand
|
|
33
33
|
| BashInteraction
|
|
34
|
-
| Writefile
|
|
35
34
|
| CreateFileNew
|
|
36
35
|
| ResetShell
|
|
37
36
|
| FileEditFindReplace
|
|
38
37
|
| FileEdit
|
|
39
38
|
| ReadFile
|
|
39
|
+
| Initialize
|
|
40
40
|
| str
|
|
41
41
|
)
|
|
42
42
|
user_id: UUID
|
|
@@ -51,7 +51,7 @@ gpts: dict[UUID, Callable[[str], None]] = {}
|
|
|
51
51
|
images: DefaultDict[UUID, dict[str, dict[str, Any]]] = DefaultDict(dict)
|
|
52
52
|
|
|
53
53
|
|
|
54
|
-
CLIENT_VERSION_MINIMUM = "1.
|
|
54
|
+
CLIENT_VERSION_MINIMUM = "1.3.0"
|
|
55
55
|
|
|
56
56
|
|
|
57
57
|
@app.websocket("/v1/register/{uuid}")
|
|
@@ -65,14 +65,12 @@ async def register_websocket(websocket: WebSocket, uuid: UUID) -> None:
|
|
|
65
65
|
# receive client version
|
|
66
66
|
client_version = await websocket.receive_text()
|
|
67
67
|
sem_version_client = semantic_version.Version.coerce(client_version)
|
|
68
|
-
sem_version_server = semantic_version.Version.coerce(
|
|
69
|
-
CLIENT_VERSION_MINIMUM)
|
|
68
|
+
sem_version_server = semantic_version.Version.coerce(CLIENT_VERSION_MINIMUM)
|
|
70
69
|
if sem_version_client < sem_version_server:
|
|
71
70
|
await websocket.send_text(
|
|
72
71
|
Mdata(
|
|
73
72
|
user_id=uuid,
|
|
74
|
-
data=f"Client version {client_version} is outdated. Please upgrade to {
|
|
75
|
-
CLIENT_VERSION_MINIMUM} or higher.",
|
|
73
|
+
data=f"Client version {client_version} is outdated. Please upgrade to {CLIENT_VERSION_MINIMUM} or higher.",
|
|
76
74
|
).model_dump_json()
|
|
77
75
|
)
|
|
78
76
|
await websocket.close(
|
|
@@ -92,8 +90,7 @@ async def register_websocket(websocket: WebSocket, uuid: UUID) -> None:
|
|
|
92
90
|
while True:
|
|
93
91
|
received_data = await websocket.receive_text()
|
|
94
92
|
if uuid not in gpts:
|
|
95
|
-
raise fastapi.HTTPException(
|
|
96
|
-
status_code=400, detail="No call made")
|
|
93
|
+
raise fastapi.HTTPException(status_code=400, detail="No call made")
|
|
97
94
|
gpts[uuid](received_data)
|
|
98
95
|
except WebSocketDisconnect:
|
|
99
96
|
# Remove the client if the WebSocket is disconnected
|
|
@@ -280,11 +277,36 @@ async def read_file_endpoint(read_file_data: ReadFileWithUUID) -> str:
|
|
|
280
277
|
|
|
281
278
|
gpts[user_id] = put_results
|
|
282
279
|
|
|
283
|
-
await clients[user_id](
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
280
|
+
await clients[user_id](Mdata(data=read_file_data, user_id=user_id))
|
|
281
|
+
|
|
282
|
+
start_time = time.time()
|
|
283
|
+
while time.time() - start_time < 30:
|
|
284
|
+
if results is not None:
|
|
285
|
+
return results
|
|
286
|
+
await asyncio.sleep(0.1)
|
|
287
|
+
|
|
288
|
+
raise fastapi.HTTPException(status_code=500, detail="Timeout error")
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
class InitializeWithUUID(Initialize):
|
|
292
|
+
user_id: UUID
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
@app.post("/v1/initialize")
|
|
296
|
+
async def initialize(initialize_data: InitializeWithUUID) -> str:
|
|
297
|
+
user_id = initialize_data.user_id
|
|
298
|
+
if user_id not in clients:
|
|
299
|
+
return "Failure: id not found, ask the user to check it."
|
|
300
|
+
|
|
301
|
+
results: Optional[str] = None
|
|
302
|
+
|
|
303
|
+
def put_results(result: str) -> None:
|
|
304
|
+
nonlocal results
|
|
305
|
+
results = result
|
|
306
|
+
|
|
307
|
+
gpts[user_id] = put_results
|
|
308
|
+
|
|
309
|
+
await clients[user_id](Mdata(data=initialize_data, user_id=user_id))
|
|
288
310
|
|
|
289
311
|
start_time = time.time()
|
|
290
312
|
while time.time() - start_time < 30:
|
wcgw/types_.py
CHANGED
|
@@ -24,11 +24,6 @@ class ReadImage(BaseModel):
|
|
|
24
24
|
type: Literal["ReadImage"]
|
|
25
25
|
|
|
26
26
|
|
|
27
|
-
class Writefile(BaseModel):
|
|
28
|
-
file_path: str
|
|
29
|
-
file_content: str
|
|
30
|
-
|
|
31
|
-
|
|
32
27
|
class CreateFileNew(BaseModel):
|
|
33
28
|
file_path: str
|
|
34
29
|
file_content: str
|
|
@@ -52,3 +47,7 @@ class ResetShell(BaseModel):
|
|
|
52
47
|
class FileEdit(BaseModel):
|
|
53
48
|
file_path: str
|
|
54
49
|
file_edit_using_search_replace_blocks: str
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class Initialize(BaseModel):
|
|
53
|
+
type: Literal["Initialize"]
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: wcgw
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.4.0
|
|
4
4
|
Summary: What could go wrong giving full shell access to 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.10
|
|
8
8
|
Requires-Dist: anthropic>=0.39.0
|
|
9
9
|
Requires-Dist: fastapi>=0.115.0
|
|
10
|
+
Requires-Dist: mcp>=1.0.0
|
|
10
11
|
Requires-Dist: mypy>=1.11.2
|
|
11
12
|
Requires-Dist: nltk>=3.9.1
|
|
12
13
|
Requires-Dist: openai>=1.46.0
|
|
@@ -50,7 +51,7 @@ You need to keep running this client for GPT to access your shell. Run it in a v
|
|
|
50
51
|
### Option 1: using uv [Recommended]
|
|
51
52
|
```sh
|
|
52
53
|
$ curl -LsSf https://astral.sh/uv/install.sh | sh
|
|
53
|
-
$
|
|
54
|
+
$ uvx wcgw@latest
|
|
54
55
|
```
|
|
55
56
|
|
|
56
57
|
### Option 2: using pip
|
|
@@ -109,6 +110,9 @@ If you don't have public ip and domain name, you can use `ngrok` or similar serv
|
|
|
109
110
|
The specify the server url in the `wcgw` command like so
|
|
110
111
|
`wcgw --server-url https://your-url/v1/register`
|
|
111
112
|
|
|
113
|
+
# Claude Support
|
|
114
|
+
WCGW now supports Claude Desktop through the MCP protocol, allowing you to use Claude's capabilities directly from your desktop environment. This integration enables seamless interaction between Claude and your local shell.
|
|
115
|
+
|
|
112
116
|
# [Optional] Local shell access with openai API key
|
|
113
117
|
|
|
114
118
|
Add `OPENAI_API_KEY` and `OPENAI_ORG_ID` env variables.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
wcgw/__init__.py,sha256=9K2QW7QuSLhMTVbKbBYd9UUp-ZyrfBrxcjuD_xk458k,118
|
|
2
|
+
wcgw/types_.py,sha256=AWQUtzv7S_YQ2_36RMQOAA2gJ26ZQZcOA6l5EvFR6hk,1054
|
|
3
|
+
wcgw/client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
wcgw/client/__main__.py,sha256=ngI_vBcLAv7fJgmS4w4U7tuWtalGB8c7W5qebuT6Z6o,30
|
|
5
|
+
wcgw/client/anthropic_client.py,sha256=G7uCtUd5pt7ZQO7k2TJI3FtgK4QtzdIvd4Z1S18xGNU,15595
|
|
6
|
+
wcgw/client/cli.py,sha256=Oja42CHkVO8puqOXflko9NeephYCMa85aBmQTEjBZtI,932
|
|
7
|
+
wcgw/client/common.py,sha256=grH-yV_4tnTQZ29xExn4YicGLxEq98z-HkEZwH0ReSg,1410
|
|
8
|
+
wcgw/client/diff-instructions.txt,sha256=s5AJKG23JsjwRYhFZFQVvwDpF67vElawrmdXwvukR1A,1683
|
|
9
|
+
wcgw/client/openai_client.py,sha256=kQCtQX0x-jsnw-tHBxi41XqnOkCaoTUqY8K4gHeI4xM,17703
|
|
10
|
+
wcgw/client/openai_utils.py,sha256=YNwCsA-Wqq7jWrxP0rfQmBTb1dI0s7dWXzQqyTzOZT4,2629
|
|
11
|
+
wcgw/client/tools.py,sha256=jjQ8eFQvcE8xfoGR5WkoOgWYE0dOIThxFrrppY4gQDQ,27954
|
|
12
|
+
wcgw/client/mcp_server/Readme.md,sha256=GH59caNySMMphqiimsVKrteU-jmNC7kcdBu4MPxtZZ8,477
|
|
13
|
+
wcgw/client/mcp_server/__init__.py,sha256=cQ7PUrEmXUpio8x0SEoGWP5hCRPd7z2bAkNCbYbtTys,236
|
|
14
|
+
wcgw/client/mcp_server/server.py,sha256=G8fyXQzVurM8rzekiXU62YaseDIT2mthc0lj64FDJKs,7089
|
|
15
|
+
wcgw/relay/serve.py,sha256=RUcUeyL4Xt0EEo12Ul6VQjb4tRle4uIdsa85v7XXxEw,8771
|
|
16
|
+
wcgw/relay/static/privacy.txt,sha256=s9qBdbx2SexCpC_z33sg16TptmAwDEehMCLz4L50JLc,529
|
|
17
|
+
wcgw-1.4.0.dist-info/METADATA,sha256=I31G-r47C-9acBusZsKFt2BjbqgP1S83amCnQJuL_XQ,5501
|
|
18
|
+
wcgw-1.4.0.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
|
|
19
|
+
wcgw-1.4.0.dist-info/entry_points.txt,sha256=eKo1omwbAggWlQ0l7GKoR7uV1-j16nk9tK0BhC2Oz_E,120
|
|
20
|
+
wcgw-1.4.0.dist-info/RECORD,,
|
wcgw-1.2.2.dist-info/RECORD
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
wcgw/__init__.py,sha256=PNWvBvjUKA3aj4bHOtIqBKCAtOW88pr0hAXZ7RylVr8,68
|
|
2
|
-
wcgw/types_.py,sha256=HND4V3KruqkAhzzEf2ve7HhF8y_o5w11Fu784U8xN_I,1062
|
|
3
|
-
wcgw/client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
-
wcgw/client/__main__.py,sha256=ngI_vBcLAv7fJgmS4w4U7tuWtalGB8c7W5qebuT6Z6o,30
|
|
5
|
-
wcgw/client/anthropic_client.py,sha256=YQ2puVKlrgbI6QvV_DQjWkvq2qw6KhPPrivm0OGfRsc,15353
|
|
6
|
-
wcgw/client/cli.py,sha256=Oja42CHkVO8puqOXflko9NeephYCMa85aBmQTEjBZtI,932
|
|
7
|
-
wcgw/client/common.py,sha256=grH-yV_4tnTQZ29xExn4YicGLxEq98z-HkEZwH0ReSg,1410
|
|
8
|
-
wcgw/client/diff-instructions.txt,sha256=Fy6op3wkr5gYgMHCRcitG4804zLrTxMbs-VSMB1oIzA,1548
|
|
9
|
-
wcgw/client/openai_client.py,sha256=v6ZBW4oh1CJpyWLTt16S-7ew8Gmq6YrJzaTux2ffywI,17470
|
|
10
|
-
wcgw/client/openai_utils.py,sha256=YNwCsA-Wqq7jWrxP0rfQmBTb1dI0s7dWXzQqyTzOZT4,2629
|
|
11
|
-
wcgw/client/tools.py,sha256=zeUOemmFwReY1jEJnnAkhPbBwkL4pjZvQ6G5BWPux4c,27301
|
|
12
|
-
wcgw/relay/serve.py,sha256=sMbERQTM1GhpnPWLzdtpcb23TIsgbP-ZuqVj-vjp5Rw,8186
|
|
13
|
-
wcgw/relay/static/privacy.txt,sha256=s9qBdbx2SexCpC_z33sg16TptmAwDEehMCLz4L50JLc,529
|
|
14
|
-
wcgw-1.2.2.dist-info/METADATA,sha256=0zQpOcQD8eRiQMVibiksflPhrngU49FSgpEHAmXQ_DA,5255
|
|
15
|
-
wcgw-1.2.2.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
|
|
16
|
-
wcgw-1.2.2.dist-info/entry_points.txt,sha256=WlIB825-Vm9ZtNzgENQsbHj4DRMkbpVR7uSkQyBlaPA,93
|
|
17
|
-
wcgw-1.2.2.dist-info/RECORD,,
|
|
File without changes
|