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.

@@ -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 current_tmpdir
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() -> None:
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
@@ -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
- # First check for global CLAUDE.md in ~/.wcgw/CLAUDE.md
275
- global_alignment_file_path = os.path.join(expanduser("~"), ".wcgw", "CLAUDE.md")
276
- if os.path.exists(global_alignment_file_path):
277
- try:
278
- with open(global_alignment_file_path, "r") as f:
279
- global_alignment_content = f.read()
280
- alignment_context += f"---\n# Important guidelines from the user\n```\n{global_alignment_content}\n```\n---\n\n"
281
- except Exception:
282
- # Handle any errors when reading the global file
283
- pass
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
- alignment_file_path = os.path.join(folder_to_start, "CLAUDE.md")
288
- if os.path.exists(alignment_file_path):
289
- try:
290
- # Read the CLAUDE.md file content
291
- with open(alignment_file_path, "r") as f:
292
- alignment_content = f.read()
293
- alignment_context += f"---\n# CLAUDE.md - user shared project guidelines to follow\n```\n{alignment_content}\n```\n---\n\n"
294
- except Exception:
295
- # Handle any errors when reading the file
296
- pass
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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wcgw
3
- Version: 5.4.1
3
+ Version: 5.4.3
4
4
  Summary: Shell and coding agent for Claude and other mcp clients
5
5
  Project-URL: Homepage, https://github.com/rusiaaman/wcgw
6
6
  Author-email: Aman Rusia <gapypi@arcfu.com>
@@ -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=zsQVdY1TwDgsUBiKEjn0tbH7_UKSCJ68PD6-OGN9ln4,9494
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/tool_prompts.py,sha256=7tq9ijSo4CznVlMFcWpauELfCT_QAqwi8Chsl3SRfBY,4539
10
- wcgw/client/tools.py,sha256=WAVnSqisV4hw4mPhbCzDDZy2gFkxXwTfzAajLPciCSw,49107
11
- wcgw/client/bash_state/bash_state.py,sha256=U-h9uqEcJbZGIVle6vZKYmlLHNO91zvqaUe6zlvLMl4,41876
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=mm7xhBIPwJpRT3u-Qsj4cKVMpVyucJoKRlbMP_gRRB0,343
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.1.dist-info/METADATA,sha256=kBQDxqLeVpBkBg-QoL34gM1mpdGC6zzXHh4UHgJ5mOc,15679
34
- wcgw-5.4.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
35
- wcgw-5.4.1.dist-info/entry_points.txt,sha256=UnjK-MAH4Qssh0tGJDMeij1oi-oRKokItkknP_BwShE,94
36
- wcgw-5.4.1.dist-info/licenses/LICENSE,sha256=BvY8xqjOfc3X2qZpGpX3MZEmF-4Dp0LqgKBbT6L_8oI,11142
37
- wcgw-5.4.1.dist-info/RECORD,,
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