wcgw 1.3.0__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 CHANGED
@@ -1,2 +1,3 @@
1
1
  from .client.cli import app
2
2
  from .client.tools import run as listen
3
+ from .client.mcp_server import main as mcp_server
@@ -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
 
@@ -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
+ ![mcp icon](https://github.com/rusiaaman/wcgw/blob/main/static/rocket-icon.png?raw=true)
25
+
26
+ Then ask claude to execute shell commands, read files, edit files, run your code, etc.
@@ -0,0 +1,11 @@
1
+ from wcgw.client.mcp_server import server
2
+ import asyncio
3
+
4
+
5
+ def main():
6
+ """Main entry point for the package."""
7
+ asyncio.run(server.main())
8
+
9
+
10
+ # Optionally expose other important items at package level
11
+ __all__ = ["main", "server"]
@@ -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
+ )
@@ -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
 
wcgw/client/tools.py CHANGED
@@ -57,7 +57,6 @@ from ..types_ import (
57
57
  ReadFile,
58
58
  ReadImage,
59
59
  ResetShell,
60
- Writefile,
61
60
  )
62
61
 
63
62
  from .common import CostData, Models, discard_input
@@ -96,7 +95,8 @@ def ask_confirmation(prompt: Confirmation) -> str:
96
95
  return "Yes" if response.lower() == "y" else "No"
97
96
 
98
97
 
99
- PROMPT = "#@@"
98
+ PROMPT_CONST = "#@wcgw@#"
99
+ PROMPT = PROMPT_CONST
100
100
 
101
101
 
102
102
  def start_shell() -> pexpect.spawn: # type: ignore
@@ -125,7 +125,7 @@ def _is_int(mystr: str) -> bool:
125
125
 
126
126
 
127
127
  def _get_exit_code() -> int:
128
- if PROMPT != "#@@":
128
+ if PROMPT != PROMPT_CONST:
129
129
  return 0
130
130
  # First reset the prompt in case venv was sourced or other reasons.
131
131
  SHELL.sendline(f"export PS1={PROMPT}")
@@ -448,7 +448,7 @@ def read_image_from_shell(file_path: str) -> ImageData:
448
448
  return ImageData(media_type=image_type, data=image_b64) # type: ignore
449
449
 
450
450
 
451
- def write_file(writefile: Writefile | CreateFileNew, error_on_exist: bool) -> str:
451
+ def write_file(writefile: CreateFileNew, error_on_exist: bool) -> str:
452
452
  if not os.path.isabs(writefile.file_path):
453
453
  return f"Failure: file_path should be absolute path, current working directory is {CWD}"
454
454
  else:
@@ -661,7 +661,6 @@ TOOLS = (
661
661
  | BashCommand
662
662
  | BashInteraction
663
663
  | ResetShell
664
- | Writefile
665
664
  | CreateFileNew
666
665
  | FileEditFindReplace
667
666
  | FileEdit
@@ -687,8 +686,6 @@ def which_tool_name(name: str) -> Type[TOOLS]:
687
686
  return BashInteraction
688
687
  elif name == "ResetShell":
689
688
  return ResetShell
690
- elif name == "Writefile":
691
- return Writefile
692
689
  elif name == "CreateFileNew":
693
690
  return CreateFileNew
694
691
  elif name == "FileEditFindReplace":
@@ -715,7 +712,6 @@ def get_tool_output(
715
712
  | BashCommand
716
713
  | BashInteraction
717
714
  | ResetShell
718
- | Writefile
719
715
  | CreateFileNew
720
716
  | FileEditFindReplace
721
717
  | FileEdit
@@ -735,7 +731,6 @@ def get_tool_output(
735
731
  | BashCommand
736
732
  | BashInteraction
737
733
  | ResetShell
738
- | Writefile
739
734
  | CreateFileNew
740
735
  | FileEditFindReplace
741
736
  | FileEdit
@@ -749,7 +744,6 @@ def get_tool_output(
749
744
  | BashCommand
750
745
  | BashInteraction
751
746
  | ResetShell
752
- | Writefile
753
747
  | CreateFileNew
754
748
  | FileEditFindReplace
755
749
  | FileEdit
@@ -769,9 +763,6 @@ def get_tool_output(
769
763
  elif isinstance(arg, (BashCommand | BashInteraction)):
770
764
  console.print("Calling execute bash tool")
771
765
  output = execute_bash(enc, arg, max_tokens)
772
- elif isinstance(arg, Writefile):
773
- console.print("Calling write file tool")
774
- output = write_file(arg, False), 0
775
766
  elif isinstance(arg, CreateFileNew):
776
767
  console.print("Calling write file tool")
777
768
  output = write_file(arg, True), 0
@@ -818,7 +809,6 @@ class Mdata(BaseModel):
818
809
  data: (
819
810
  BashCommand
820
811
  | BashInteraction
821
- | Writefile
822
812
  | CreateFileNew
823
813
  | ResetShell
824
814
  | FileEditFindReplace
wcgw/relay/serve.py CHANGED
@@ -23,7 +23,6 @@ from ..types_ import (
23
23
  Initialize,
24
24
  ReadFile,
25
25
  ResetShell,
26
- Writefile,
27
26
  Specials,
28
27
  )
29
28
 
@@ -32,7 +31,6 @@ class Mdata(BaseModel):
32
31
  data: (
33
32
  BashCommand
34
33
  | BashInteraction
35
- | Writefile
36
34
  | CreateFileNew
37
35
  | ResetShell
38
36
  | FileEditFindReplace
@@ -67,8 +65,7 @@ async def register_websocket(websocket: WebSocket, uuid: UUID) -> None:
67
65
  # receive client version
68
66
  client_version = await websocket.receive_text()
69
67
  sem_version_client = semantic_version.Version.coerce(client_version)
70
- sem_version_server = semantic_version.Version.coerce(
71
- CLIENT_VERSION_MINIMUM)
68
+ sem_version_server = semantic_version.Version.coerce(CLIENT_VERSION_MINIMUM)
72
69
  if sem_version_client < sem_version_server:
73
70
  await websocket.send_text(
74
71
  Mdata(
@@ -93,8 +90,7 @@ async def register_websocket(websocket: WebSocket, uuid: UUID) -> None:
93
90
  while True:
94
91
  received_data = await websocket.receive_text()
95
92
  if uuid not in gpts:
96
- raise fastapi.HTTPException(
97
- status_code=400, detail="No call made")
93
+ raise fastapi.HTTPException(status_code=400, detail="No call made")
98
94
  gpts[uuid](received_data)
99
95
  except WebSocketDisconnect:
100
96
  # Remove the client if the WebSocket is disconnected
@@ -281,9 +277,7 @@ async def read_file_endpoint(read_file_data: ReadFileWithUUID) -> str:
281
277
 
282
278
  gpts[user_id] = put_results
283
279
 
284
- await clients[user_id](
285
- Mdata(data=read_file_data, user_id=user_id)
286
- )
280
+ await clients[user_id](Mdata(data=read_file_data, user_id=user_id))
287
281
 
288
282
  start_time = time.time()
289
283
  while time.time() - start_time < 30:
@@ -293,6 +287,7 @@ async def read_file_endpoint(read_file_data: ReadFileWithUUID) -> str:
293
287
 
294
288
  raise fastapi.HTTPException(status_code=500, detail="Timeout error")
295
289
 
290
+
296
291
  class InitializeWithUUID(Initialize):
297
292
  user_id: UUID
298
293
 
@@ -311,9 +306,7 @@ async def initialize(initialize_data: InitializeWithUUID) -> str:
311
306
 
312
307
  gpts[user_id] = put_results
313
308
 
314
- await clients[user_id](
315
- Mdata(data=initialize_data, user_id=user_id)
316
- )
309
+ await clients[user_id](Mdata(data=initialize_data, user_id=user_id))
317
310
 
318
311
  start_time = time.time()
319
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
@@ -55,4 +50,4 @@ class FileEdit(BaseModel):
55
50
 
56
51
 
57
52
  class Initialize(BaseModel):
58
- type: Literal["Initialize"]
53
+ type: Literal["Initialize"]
@@ -1,12 +1,13 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: wcgw
3
- Version: 1.3.0
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
- $ uv tool run --python 3.12 wcgw@latest
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,,
@@ -1,4 +1,5 @@
1
1
  [console_scripts]
2
2
  wcgw = wcgw:listen
3
3
  wcgw_local = wcgw:app
4
+ wcgw_mcp = wcgw:mcp_server
4
5
  wcgw_relay = wcgw.relay.serve:run
@@ -1,17 +0,0 @@
1
- wcgw/__init__.py,sha256=PNWvBvjUKA3aj4bHOtIqBKCAtOW88pr0hAXZ7RylVr8,68
2
- wcgw/types_.py,sha256=k6YhW9ePQ1oFbsPe7nQKndzJTzhulEdufvnFI3qg9vY,1124
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=yLBygok_z8Tpzedi28fpEP084wNth4qeiivZejFWRGY,15610
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=xuLsu9KHG9wssPOjTrQ6rFi3FtWufZ5RtK29Yb_j5O4,17718
10
- wcgw/client/openai_utils.py,sha256=YNwCsA-Wqq7jWrxP0rfQmBTb1dI0s7dWXzQqyTzOZT4,2629
11
- wcgw/client/tools.py,sha256=Q5qxpCjsz43aBzqpuMQ25cke_pUh3EKYeO8WC9pPpls,28225
12
- wcgw/relay/serve.py,sha256=ArDnLVUAtcSVxyKTzmFN-Rx09zJXRlszmNBXGrH9JtU,8863
13
- wcgw/relay/static/privacy.txt,sha256=s9qBdbx2SexCpC_z33sg16TptmAwDEehMCLz4L50JLc,529
14
- wcgw-1.3.0.dist-info/METADATA,sha256=c7_mF4ki8YX5-OBM606mcK7ZUo4YgAVF-FFeYCMIrhY,5255
15
- wcgw-1.3.0.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
16
- wcgw-1.3.0.dist-info/entry_points.txt,sha256=WlIB825-Vm9ZtNzgENQsbHj4DRMkbpVR7uSkQyBlaPA,93
17
- wcgw-1.3.0.dist-info/RECORD,,
File without changes