wcgw 5.4.1__py3-none-any.whl → 5.4.3__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/client/bash_state/bash_state.py +3 -2
- wcgw/client/mcp_server/__init__.py +15 -2
- wcgw/client/schema_generator.py +63 -0
- wcgw/client/tool_prompts.py +7 -6
- wcgw/client/tools.py +38 -21
- wcgw/types_.py +3 -1
- {wcgw-5.4.1.dist-info → wcgw-5.4.3.dist-info}/METADATA +1 -1
- {wcgw-5.4.1.dist-info → wcgw-5.4.3.dist-info}/RECORD +11 -10
- {wcgw-5.4.1.dist-info → wcgw-5.4.3.dist-info}/WHEEL +0 -0
- {wcgw-5.4.1.dist-info → wcgw-5.4.3.dist-info}/entry_points.txt +0 -0
- {wcgw-5.4.1.dist-info → wcgw-5.4.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -4,6 +4,7 @@ import os
|
|
|
4
4
|
import platform
|
|
5
5
|
import random
|
|
6
6
|
import subprocess
|
|
7
|
+
import tempfile
|
|
7
8
|
import threading
|
|
8
9
|
import time
|
|
9
10
|
import traceback
|
|
@@ -65,7 +66,7 @@ def is_mac() -> bool:
|
|
|
65
66
|
def get_tmpdir() -> str:
|
|
66
67
|
current_tmpdir = os.environ.get("TMPDIR", "")
|
|
67
68
|
if current_tmpdir or not is_mac():
|
|
68
|
-
return
|
|
69
|
+
return tempfile.gettempdir()
|
|
69
70
|
try:
|
|
70
71
|
# Fix issue while running ocrmypdf -> tesseract -> leptonica, set TMPDIR
|
|
71
72
|
# https://github.com/tesseract-ocr/tesseract/issues/4333
|
|
@@ -78,7 +79,7 @@ def get_tmpdir() -> str:
|
|
|
78
79
|
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
79
80
|
return "//tmp"
|
|
80
81
|
except Exception:
|
|
81
|
-
return
|
|
82
|
+
return tempfile.gettempdir()
|
|
82
83
|
|
|
83
84
|
|
|
84
85
|
def check_if_screen_command_available() -> bool:
|
|
@@ -1,14 +1,27 @@
|
|
|
1
1
|
# mypy: disable-error-code="import-untyped"
|
|
2
|
-
from wcgw.client.mcp_server import server
|
|
3
2
|
import asyncio
|
|
3
|
+
import importlib
|
|
4
|
+
|
|
5
|
+
import typer
|
|
4
6
|
from typer import Typer
|
|
5
7
|
|
|
8
|
+
from wcgw.client.mcp_server import server
|
|
9
|
+
|
|
6
10
|
main = Typer()
|
|
7
11
|
|
|
8
12
|
|
|
9
13
|
@main.command()
|
|
10
|
-
def app(
|
|
14
|
+
def app(
|
|
15
|
+
version: bool = typer.Option(
|
|
16
|
+
False, "--version", "-v", help="Show version and exit"
|
|
17
|
+
),
|
|
18
|
+
) -> None:
|
|
11
19
|
"""Main entry point for the package."""
|
|
20
|
+
if version:
|
|
21
|
+
version_ = importlib.metadata.version("wcgw")
|
|
22
|
+
print(f"wcgw version: {version_}")
|
|
23
|
+
raise typer.Exit()
|
|
24
|
+
|
|
12
25
|
asyncio.run(server.main())
|
|
13
26
|
|
|
14
27
|
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Custom JSON schema generator to remove title fields from Pydantic models.
|
|
3
|
+
|
|
4
|
+
This module provides utilities to remove auto-generated title fields from JSON schemas,
|
|
5
|
+
making them more suitable for tool schemas where titles are not needed.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import copy
|
|
9
|
+
from typing import Any, Dict
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def recursive_purge_dict_key(d: Dict[str, Any], k: str) -> None:
|
|
13
|
+
"""
|
|
14
|
+
Remove a key from a dictionary recursively, but only from JSON schema metadata.
|
|
15
|
+
|
|
16
|
+
This function removes the specified key from dictionaries that appear to be
|
|
17
|
+
JSON schema objects (have "type" or "$ref" or are property definitions).
|
|
18
|
+
This prevents removing legitimate data fields that happen to have the same name.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
d: The dictionary to clean
|
|
22
|
+
k: The key to remove (typically "title")
|
|
23
|
+
"""
|
|
24
|
+
if isinstance(d, dict):
|
|
25
|
+
# Only remove the key if this looks like a JSON schema object
|
|
26
|
+
# This includes objects with "type", "$ref", or if we're in a "properties" context
|
|
27
|
+
is_schema_object = (
|
|
28
|
+
"type" in d or
|
|
29
|
+
"$ref" in d or
|
|
30
|
+
any(schema_key in d for schema_key in ["properties", "items", "additionalProperties", "enum", "const", "anyOf", "allOf", "oneOf"])
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
if is_schema_object and k in d:
|
|
34
|
+
del d[k]
|
|
35
|
+
|
|
36
|
+
# Recursively process all values, regardless of key names
|
|
37
|
+
# This ensures we catch all nested structures
|
|
38
|
+
for key, value in d.items():
|
|
39
|
+
if isinstance(value, dict):
|
|
40
|
+
recursive_purge_dict_key(value, k)
|
|
41
|
+
elif isinstance(value, list):
|
|
42
|
+
for item in value:
|
|
43
|
+
if isinstance(item, dict):
|
|
44
|
+
recursive_purge_dict_key(item, k)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def remove_titles_from_schema(schema: Dict[str, Any]) -> Dict[str, Any]:
|
|
48
|
+
"""
|
|
49
|
+
Remove all 'title' keys from a JSON schema dictionary.
|
|
50
|
+
|
|
51
|
+
This function creates a copy of the schema and removes all title keys
|
|
52
|
+
recursively, making it suitable for use with APIs that don't need titles.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
schema: The JSON schema dictionary to clean
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
A new dictionary with all title keys removed
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
schema_copy = copy.deepcopy(schema)
|
|
62
|
+
recursive_purge_dict_key(schema_copy, "title")
|
|
63
|
+
return schema_copy
|
wcgw/client/tool_prompts.py
CHANGED
|
@@ -10,6 +10,7 @@ from ..types_ import (
|
|
|
10
10
|
ReadFiles,
|
|
11
11
|
ReadImage,
|
|
12
12
|
)
|
|
13
|
+
from .schema_generator import remove_titles_from_schema
|
|
13
14
|
|
|
14
15
|
with open(os.path.join(os.path.dirname(__file__), "diff-instructions.txt")) as f:
|
|
15
16
|
diffinstructions = f.read()
|
|
@@ -17,7 +18,7 @@ with open(os.path.join(os.path.dirname(__file__), "diff-instructions.txt")) as f
|
|
|
17
18
|
|
|
18
19
|
TOOL_PROMPTS = [
|
|
19
20
|
Tool(
|
|
20
|
-
inputSchema=Initialize.model_json_schema(),
|
|
21
|
+
inputSchema=remove_titles_from_schema(Initialize.model_json_schema()),
|
|
21
22
|
name="Initialize",
|
|
22
23
|
description="""
|
|
23
24
|
- Always call this at the start of the conversation before using any of the shell tools from wcgw.
|
|
@@ -34,7 +35,7 @@ TOOL_PROMPTS = [
|
|
|
34
35
|
annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=False),
|
|
35
36
|
),
|
|
36
37
|
Tool(
|
|
37
|
-
inputSchema=BashCommand.model_json_schema(),
|
|
38
|
+
inputSchema=remove_titles_from_schema(BashCommand.model_json_schema()),
|
|
38
39
|
name="BashCommand",
|
|
39
40
|
description="""
|
|
40
41
|
- Execute a bash command. This is stateful (beware with subsequent calls).
|
|
@@ -51,7 +52,7 @@ TOOL_PROMPTS = [
|
|
|
51
52
|
annotations=ToolAnnotations(destructiveHint=True, openWorldHint=True),
|
|
52
53
|
),
|
|
53
54
|
Tool(
|
|
54
|
-
inputSchema=ReadFiles.model_json_schema(),
|
|
55
|
+
inputSchema=remove_titles_from_schema(ReadFiles.model_json_schema()),
|
|
55
56
|
name="ReadFiles",
|
|
56
57
|
description="""
|
|
57
58
|
- Read full file content of one or more files.
|
|
@@ -62,13 +63,13 @@ TOOL_PROMPTS = [
|
|
|
62
63
|
annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=False),
|
|
63
64
|
),
|
|
64
65
|
Tool(
|
|
65
|
-
inputSchema=ReadImage.model_json_schema(),
|
|
66
|
+
inputSchema=remove_titles_from_schema(ReadImage.model_json_schema()),
|
|
66
67
|
name="ReadImage",
|
|
67
68
|
description="Read an image from the shell.",
|
|
68
69
|
annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=False),
|
|
69
70
|
),
|
|
70
71
|
Tool(
|
|
71
|
-
inputSchema=FileWriteOrEdit.model_json_schema(),
|
|
72
|
+
inputSchema=remove_titles_from_schema(FileWriteOrEdit.model_json_schema()),
|
|
72
73
|
name="FileWriteOrEdit",
|
|
73
74
|
description="""
|
|
74
75
|
- Writes or edits a file based on the percentage of changes.
|
|
@@ -84,7 +85,7 @@ TOOL_PROMPTS = [
|
|
|
84
85
|
),
|
|
85
86
|
),
|
|
86
87
|
Tool(
|
|
87
|
-
inputSchema=ContextSave.model_json_schema(),
|
|
88
|
+
inputSchema=remove_titles_from_schema(ContextSave.model_json_schema()),
|
|
88
89
|
name="ContextSave",
|
|
89
90
|
description="""
|
|
90
91
|
Saves provided description and file contents of all the relevant file paths or globs in a single text file.
|
wcgw/client/tools.py
CHANGED
|
@@ -6,6 +6,7 @@ import mimetypes
|
|
|
6
6
|
import os
|
|
7
7
|
import subprocess
|
|
8
8
|
import traceback
|
|
9
|
+
import uuid
|
|
9
10
|
from dataclasses import dataclass
|
|
10
11
|
from hashlib import sha256
|
|
11
12
|
from os.path import expanduser
|
|
@@ -34,6 +35,7 @@ from ..client.bash_state.bash_state import (
|
|
|
34
35
|
execute_bash,
|
|
35
36
|
generate_thread_id,
|
|
36
37
|
get_status,
|
|
38
|
+
get_tmpdir,
|
|
37
39
|
)
|
|
38
40
|
from ..client.repo_ops.file_stats import (
|
|
39
41
|
FileStats,
|
|
@@ -153,6 +155,12 @@ def initialize(
|
|
|
153
155
|
)
|
|
154
156
|
|
|
155
157
|
folder_to_start = None
|
|
158
|
+
if type == "first_call" and not any_workspace_path:
|
|
159
|
+
tmp_dir = get_tmpdir()
|
|
160
|
+
any_workspace_path = os.path.join(
|
|
161
|
+
tmp_dir, "claude-playground-" + uuid.uuid4().hex[:4]
|
|
162
|
+
)
|
|
163
|
+
|
|
156
164
|
if any_workspace_path:
|
|
157
165
|
if os.path.exists(any_workspace_path):
|
|
158
166
|
if os.path.isfile(any_workspace_path):
|
|
@@ -271,29 +279,38 @@ def initialize(
|
|
|
271
279
|
except Exception:
|
|
272
280
|
pass
|
|
273
281
|
|
|
274
|
-
#
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
282
|
+
# Check for global alignment doc in ~/.wcgw: prefer CLAUDE.md, else AGENTS.md
|
|
283
|
+
try:
|
|
284
|
+
global_dir = os.path.join(expanduser("~"), ".wcgw")
|
|
285
|
+
for fname in ("CLAUDE.md", "AGENTS.md"):
|
|
286
|
+
global_alignment_file_path = os.path.join(global_dir, fname)
|
|
287
|
+
if os.path.exists(global_alignment_file_path):
|
|
288
|
+
with open(global_alignment_file_path, "r") as f:
|
|
289
|
+
global_alignment_content = f.read()
|
|
290
|
+
alignment_context += f"---\n# Important guidelines from the user\n```\n{global_alignment_content}\n```\n---\n\n"
|
|
291
|
+
break
|
|
292
|
+
except Exception as e:
|
|
293
|
+
# Log any errors when reading the global file
|
|
294
|
+
context.console.log(f"Error reading global alignment file: {e}")
|
|
284
295
|
|
|
285
|
-
# Then check for workspace-specific CLAUDE.md
|
|
296
|
+
# Then check for workspace-specific alignment doc: prefer CLAUDE.md, else AGENTS.md
|
|
286
297
|
if folder_to_start:
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
298
|
+
try:
|
|
299
|
+
base_dir = str(folder_to_start)
|
|
300
|
+
selected_name = ""
|
|
301
|
+
alignment_content = ""
|
|
302
|
+
for fname in ("CLAUDE.md", "AGENTS.md"):
|
|
303
|
+
alignment_file_path = os.path.join(base_dir, fname)
|
|
304
|
+
if os.path.exists(alignment_file_path):
|
|
305
|
+
with open(alignment_file_path, "r") as f:
|
|
306
|
+
alignment_content = f.read()
|
|
307
|
+
selected_name = fname
|
|
308
|
+
break
|
|
309
|
+
if alignment_content:
|
|
310
|
+
alignment_context += f"---\n# {selected_name} - user shared project guidelines to follow\n```\n{alignment_content}\n```\n---\n\n"
|
|
311
|
+
except Exception as e:
|
|
312
|
+
# Log any errors when reading the workspace file
|
|
313
|
+
context.console.log(f"Error reading workspace alignment file: {e}")
|
|
297
314
|
|
|
298
315
|
uname_sysname = os.uname().sysname
|
|
299
316
|
uname_machine = os.uname().machine
|
wcgw/types_.py
CHANGED
|
@@ -49,7 +49,9 @@ class Initialize(BaseModel):
|
|
|
49
49
|
"reset_shell",
|
|
50
50
|
"user_asked_change_workspace",
|
|
51
51
|
]
|
|
52
|
-
any_workspace_path: str
|
|
52
|
+
any_workspace_path: str = Field(
|
|
53
|
+
description="Workspce to initialise in. Don't use ~ by default, instead use empty string"
|
|
54
|
+
)
|
|
53
55
|
initial_files_to_read: list[str]
|
|
54
56
|
task_id_to_resume: str
|
|
55
57
|
mode_name: Literal["wcgw", "architect", "code_writer"]
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
wcgw/__init__.py,sha256=JgAY25VsA208v8E7QTIU0E50nsk-TCJ4FWTEHmnssYU,127
|
|
2
2
|
wcgw/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
-
wcgw/types_.py,sha256=
|
|
3
|
+
wcgw/types_.py,sha256=dvFpZPywqczpX7Sv6-e-r0WZxOZ0Eu10DlpSLtl9zJs,9607
|
|
4
4
|
wcgw/client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
5
|
wcgw/client/common.py,sha256=OCH7Tx64jojz3M3iONUrGMadE07W21DiZs5sOxWX1Qc,1456
|
|
6
6
|
wcgw/client/diff-instructions.txt,sha256=wCmB-Vj8HNSTvxjZIH0fX_J7v9jbsJOJeNj46Un1AJ0,1858
|
|
7
7
|
wcgw/client/memory.py,sha256=U2Nw2si3Zg7n_RhNAuaYcmrrDtZ_Mooi-kfAOKflT-I,3079
|
|
8
8
|
wcgw/client/modes.py,sha256=roH6SPBokJMr5IzAlccdI-vJyvyS5vqSMMyth7TE86A,10315
|
|
9
|
-
wcgw/client/
|
|
10
|
-
wcgw/client/
|
|
11
|
-
wcgw/client/
|
|
9
|
+
wcgw/client/schema_generator.py,sha256=mEIy6BgHlfJeAjJtwY_VwoIDmu-Fax2H9bVtj7IMuEo,2282
|
|
10
|
+
wcgw/client/tool_prompts.py,sha256=1EFQZeXlebOvrDb9t4g63FyzRWCnTwDzwrqwPHg-7sE,4757
|
|
11
|
+
wcgw/client/tools.py,sha256=GrJ9bXAkayd4bgKFdRXJ1wtdUmeE5dD7l2ef-dMOjZQ,49877
|
|
12
|
+
wcgw/client/bash_state/bash_state.py,sha256=bd5RtLbaRzCtrmeDTl3JKZwzmIR-8iAMQpl7Fqyt56M,41918
|
|
12
13
|
wcgw/client/bash_state/parser/__init__.py,sha256=AnlNSmoQTSoqqlLOLX4P1uXfzc5VGeCGJsGgtisq2zE,207
|
|
13
14
|
wcgw/client/bash_state/parser/bash_statement_parser.py,sha256=9a8vPO1r3_tXmaAcubTQ5UY-NseWlalgm8LZA17LXuY,6058
|
|
14
15
|
wcgw/client/encoder/__init__.py,sha256=Y-8f43I6gMssUCWpX5rLYiAFv3D-JPRs4uNEejPlke8,1514
|
|
@@ -16,7 +17,7 @@ wcgw/client/file_ops/diff_edit.py,sha256=AwLq6-pY7czv1y-JA5O2Q4rgbvn82YmSL9jD8XB
|
|
|
16
17
|
wcgw/client/file_ops/extensions.py,sha256=CmfD7ON6SY24Prh2tRZdV9KbhuOrWqqk8qL1VtshzB8,3608
|
|
17
18
|
wcgw/client/file_ops/search_replace.py,sha256=5LFg-_U_ijnNrkYei4SWCPGKPGgDzJs49EDsIBzLmuY,6822
|
|
18
19
|
wcgw/client/mcp_server/Readme.md,sha256=2Z88jj1mf9daYGW1CWaldcJ0moy8owDumhR2glBY3A8,109
|
|
19
|
-
wcgw/client/mcp_server/__init__.py,sha256=
|
|
20
|
+
wcgw/client/mcp_server/__init__.py,sha256=rSNET0SVjUTDn2HAfREisTXTBs89TVsWegDfYFMvy5w,621
|
|
20
21
|
wcgw/client/mcp_server/server.py,sha256=RgsMDmNuYyg4VT1KorcLzh1Xfv49QASi0-FTLz_tlIo,5525
|
|
21
22
|
wcgw/client/repo_ops/display_tree.py,sha256=g282qCKLCwo8O9NHUBnkG_NkIusroVzz3NZi8VIcmAI,4066
|
|
22
23
|
wcgw/client/repo_ops/file_stats.py,sha256=AUA0Br7zFRpylWFYZPGMeGPJy3nWp9e2haKi34JptHE,4887
|
|
@@ -30,8 +31,8 @@ wcgw_cli/anthropic_client.py,sha256=8bjDY59-aioyTJgpB-NBHZNhZaq6rqcTJcOf81kzCyA,
|
|
|
30
31
|
wcgw_cli/cli.py,sha256=-7FBe_lahKyUOhf65iurTA1M1gXXXAiT0OVKQVcZKKo,948
|
|
31
32
|
wcgw_cli/openai_client.py,sha256=GOqoSFazTV-cFjpdZGPM0DIwec8Up2TEcKUbsN40AGY,15990
|
|
32
33
|
wcgw_cli/openai_utils.py,sha256=xGOb3W5ALrIozV7oszfGYztpj0FnXdD7jAxm5lEIVKY,2439
|
|
33
|
-
wcgw-5.4.
|
|
34
|
-
wcgw-5.4.
|
|
35
|
-
wcgw-5.4.
|
|
36
|
-
wcgw-5.4.
|
|
37
|
-
wcgw-5.4.
|
|
34
|
+
wcgw-5.4.3.dist-info/METADATA,sha256=fNaQqNspbdo8tWK0-dVsYeaFr6i3kPdNffHoHn-JYss,15679
|
|
35
|
+
wcgw-5.4.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
36
|
+
wcgw-5.4.3.dist-info/entry_points.txt,sha256=UnjK-MAH4Qssh0tGJDMeij1oi-oRKokItkknP_BwShE,94
|
|
37
|
+
wcgw-5.4.3.dist-info/licenses/LICENSE,sha256=BvY8xqjOfc3X2qZpGpX3MZEmF-4Dp0LqgKBbT6L_8oI,11142
|
|
38
|
+
wcgw-5.4.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|