wcgw 2.1.2__py3-none-any.whl → 2.2.1__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.

@@ -24,7 +24,7 @@ import uuid
24
24
  from ..types_ import (
25
25
  BashCommand,
26
26
  BashInteraction,
27
- CreateFileNew,
27
+ WriteIfEmpty,
28
28
  FileEditFindReplace,
29
29
  FileEdit,
30
30
  Keyboard,
@@ -186,7 +186,7 @@ def loop(
186
186
  - Only one of send_text, send_specials, send_ascii should be provided.
187
187
  - This returns within 5 seconds, for heavy programs keep checking status for upto 10 turns before asking user to continue checking again.
188
188
  - Programs don't hang easily, so most likely explanation for no output is usually that the program is still running, and you need to check status again using ["Enter"].
189
-
189
+ - Do not send Ctrl-c before checking for status till 10 minutes or whatever is appropriate for the program to finish.
190
190
  """,
191
191
  ),
192
192
  ToolParam(
@@ -198,10 +198,10 @@ def loop(
198
198
  """,
199
199
  ),
200
200
  ToolParam(
201
- input_schema=CreateFileNew.model_json_schema(),
202
- name="CreateFileNew",
201
+ input_schema=WriteIfEmpty.model_json_schema(),
202
+ name="WriteIfEmpty",
203
203
  description="""
204
- - Write content to a new file. Provide file path and content. Use this instead of BashCommand for writing new files.
204
+ - Write content to an empty or non-existent file. Provide file path and content. Use this instead of BashCommand for writing new files.
205
205
  - Provide absolute file path only.
206
206
  - For editing existing files, use FileEdit instead of this tool.
207
207
  """,
@@ -222,6 +222,7 @@ def loop(
222
222
  description="""
223
223
  - Use absolute file path only.
224
224
  - Use SEARCH/REPLACE blocks to edit the file.
225
+ - If the edit fails due to block not matching, please retry with correct block till it matches. Re-read the file to ensure you've all the lines correct.
225
226
  """,
226
227
  ),
227
228
  ]
@@ -495,7 +496,12 @@ System information:
495
496
  )
496
497
  else:
497
498
  _histories.append(
498
- {"role": "assistant", "content": full_response}
499
+ {
500
+ "role": "assistant",
501
+ "content": full_response
502
+ if full_response.strip()
503
+ else "...",
504
+ } # Fixes anthropic issue of non empty response only
499
505
  )
500
506
 
501
507
  except KeyboardInterrupt:
@@ -17,7 +17,7 @@ from ..tools import DoneFlag, get_tool_output, which_tool_name, default_enc
17
17
  from ...types_ import (
18
18
  BashCommand,
19
19
  BashInteraction,
20
- CreateFileNew,
20
+ WriteIfEmpty,
21
21
  FileEdit,
22
22
  Keyboard,
23
23
  Mouse,
@@ -106,6 +106,7 @@ async def handle_list_tools() -> list[types.Tool]:
106
106
  - Only one of send_text, send_specials, send_ascii should be provided.
107
107
  - This returns within 3 seconds, for heavy programs keep checking status for upto 10 turns before asking user to continue checking again.
108
108
  - Programs don't hang easily, so most likely explanation for no output is usually that the program is still running, and you need to check status again using ["Enter"].
109
+ - Do not send Ctrl-c before checking for status till 10 minutes or whatever is appropriate for the program to finish.
109
110
  """,
110
111
  ),
111
112
  ToolParam(
@@ -117,10 +118,10 @@ async def handle_list_tools() -> list[types.Tool]:
117
118
  """,
118
119
  ),
119
120
  ToolParam(
120
- inputSchema=CreateFileNew.model_json_schema(),
121
- name="CreateFileNew",
121
+ inputSchema=WriteIfEmpty.model_json_schema(),
122
+ name="WriteIfEmpty",
122
123
  description="""
123
- - Write content to a new file. Provide file path and content. Use this instead of BashCommand for writing new files.
124
+ - Write content to an empty or non-existent file. Provide file path and content. Use this instead of BashCommand for writing new files.
124
125
  - Provide absolute file path only.
125
126
  - For editing existing files, use FileEdit instead of this tool.
126
127
  """,
@@ -141,6 +142,7 @@ async def handle_list_tools() -> list[types.Tool]:
141
142
  description="""
142
143
  - Use absolute file path only.
143
144
  - Use SEARCH/REPLACE blocks to edit the file.
145
+ - If the edit fails due to block not matching, please retry with correct block till it matches. Re-read the file to ensure you've all the lines correct.
144
146
  """
145
147
  + diffinstructions,
146
148
  ),
@@ -23,7 +23,7 @@ import uuid
23
23
  from ..types_ import (
24
24
  BashCommand,
25
25
  BashInteraction,
26
- CreateFileNew,
26
+ WriteIfEmpty,
27
27
  FileEdit,
28
28
  ReadImage,
29
29
  ReadFile,
@@ -195,9 +195,9 @@ def loop(
195
195
  """,
196
196
  ),
197
197
  openai.pydantic_function_tool(
198
- CreateFileNew,
198
+ WriteIfEmpty,
199
199
  description="""
200
- - Write content to a new file. Provide file path and content. Use this instead of BashCommand for writing new files.
200
+ - Write content to an empty or non-existent file. Provide file path and content. Use this instead of BashCommand for writing new files.
201
201
  - Provide absolute file path only.
202
202
  - For editing existing files, use FileEdit instead of this tool.""",
203
203
  ),
wcgw/client/tools.py CHANGED
@@ -55,7 +55,7 @@ from nltk.metrics.distance import edit_distance # type: ignore[import-untyped]
55
55
  from ..types_ import (
56
56
  BashCommand,
57
57
  BashInteraction,
58
- CreateFileNew,
58
+ WriteIfEmpty,
59
59
  FileEditFindReplace,
60
60
  FileEdit,
61
61
  Initialize,
@@ -531,12 +531,21 @@ def read_image_from_shell(file_path: str) -> ImageData:
531
531
  return ImageData(media_type=image_type, data=image_b64) # type: ignore
532
532
 
533
533
 
534
- def write_file(writefile: CreateFileNew, error_on_exist: bool) -> str:
534
+ def write_file(writefile: WriteIfEmpty, error_on_exist: bool) -> str:
535
535
  if not os.path.isabs(writefile.file_path):
536
536
  return f"Failure: file_path should be absolute path, current working directory is {BASH_STATE.cwd}"
537
537
  else:
538
538
  path_ = writefile.file_path
539
539
 
540
+ error_on_exist = (
541
+ not (
542
+ len(TOOL_CALLS) > 1
543
+ and isinstance(TOOL_CALLS[-2], FileEdit)
544
+ and TOOL_CALLS[-2].file_path == path_
545
+ )
546
+ and error_on_exist
547
+ )
548
+
540
549
  if not BASH_STATE.is_in_docker:
541
550
  if error_on_exist and os.path.exists(path_):
542
551
  file_data = Path(path_).read_text()
@@ -598,7 +607,7 @@ def find_least_edit_distance_substring(
598
607
  content_lines = new_content_lines
599
608
  find_lines = find_str.split("\n")
600
609
  find_lines = [
601
- line.strip() for line in find_lines
610
+ line.strip() for line in find_lines if line.strip()
602
611
  ] # Remove trailing and leading space for calculating edit distance
603
612
  # Slide window and find one with sum of edit distance least
604
613
  min_edit_distance = float("inf")
@@ -620,7 +629,7 @@ def find_least_edit_distance_substring(
620
629
  + 1
621
630
  )
622
631
  min_edit_distance_lines = orig_content_lines[
623
- orig_start_index:orig_end_index
632
+ max(0, orig_start_index - 10) : (orig_end_index + 10)
624
633
  ]
625
634
  return "\n".join(min_edit_distance_lines), min_edit_distance
626
635
 
@@ -638,7 +647,11 @@ def edit_content(content: str, find_lines: str, replace_with_lines: str) -> str:
638
647
  f"Exact match not found, found with whitespace removed edit distance: {min_edit_distance}"
639
648
  )
640
649
  raise Exception(
641
- f"Error: no match found for the provided `find_lines` in the file. Closest match:\n---\n{closest_match}\n---\nFile not edited"
650
+ f"""Error: no match found for the provided search block.
651
+ Requested search block: \n```\n{find_lines}\n```
652
+ Possible relevant section in the file:\n---\n```\n{closest_match}\n```\n---\nFile not edited
653
+ \nPlease retry with exact search. Re-read the file if unsure.
654
+ """
642
655
  )
643
656
 
644
657
  content = content.replace(find_lines, replace_with_lines, 1)
@@ -646,6 +659,24 @@ def edit_content(content: str, find_lines: str, replace_with_lines: str) -> str:
646
659
 
647
660
 
648
661
  def do_diff_edit(fedit: FileEdit) -> str:
662
+ try:
663
+ return _do_diff_edit(fedit)
664
+ except Exception as e:
665
+ # Try replacing \"
666
+ try:
667
+ fedit = FileEdit(
668
+ file_path=fedit.file_path,
669
+ file_edit_using_search_replace_blocks=fedit.file_edit_using_search_replace_blocks.replace(
670
+ '\\"', '"'
671
+ ),
672
+ )
673
+ return _do_diff_edit(fedit)
674
+ except Exception:
675
+ pass
676
+ raise e
677
+
678
+
679
+ def _do_diff_edit(fedit: FileEdit) -> str:
649
680
  console.log(f"Editing file: {fedit.file_path}")
650
681
 
651
682
  if not os.path.isabs(fedit.file_path):
@@ -765,7 +796,7 @@ TOOLS = (
765
796
  | BashCommand
766
797
  | BashInteraction
767
798
  | ResetShell
768
- | CreateFileNew
799
+ | WriteIfEmpty
769
800
  | FileEditFindReplace
770
801
  | FileEdit
771
802
  | AIAssistant
@@ -794,8 +825,8 @@ def which_tool_name(name: str) -> Type[TOOLS]:
794
825
  return BashInteraction
795
826
  elif name == "ResetShell":
796
827
  return ResetShell
797
- elif name == "CreateFileNew":
798
- return CreateFileNew
828
+ elif name == "WriteIfEmpty":
829
+ return WriteIfEmpty
799
830
  elif name == "FileEditFindReplace":
800
831
  return FileEditFindReplace
801
832
  elif name == "FileEdit":
@@ -822,77 +853,31 @@ def which_tool_name(name: str) -> Type[TOOLS]:
822
853
  raise ValueError(f"Unknown tool name: {name}")
823
854
 
824
855
 
856
+ TOOL_CALLS: list[TOOLS] = []
857
+
858
+
825
859
  def get_tool_output(
826
- args: dict[object, object]
827
- | Confirmation
828
- | BashCommand
829
- | BashInteraction
830
- | ResetShell
831
- | CreateFileNew
832
- | FileEditFindReplace
833
- | FileEdit
834
- | AIAssistant
835
- | DoneFlag
836
- | ReadImage
837
- | Initialize
838
- | ReadFile
839
- | Mouse
840
- | Keyboard
841
- | ScreenShot
842
- | GetScreenInfo,
860
+ args: dict[object, object] | TOOLS,
843
861
  enc: tiktoken.Encoding,
844
862
  limit: float,
845
863
  loop_call: Callable[[str, float], tuple[str, float]],
846
864
  max_tokens: Optional[int],
847
865
  ) -> tuple[list[str | ImageData | DoneFlag], float]:
848
- global IS_IN_DOCKER
866
+ global IS_IN_DOCKER, TOOL_CALLS
849
867
  if isinstance(args, dict):
850
- adapter = TypeAdapter[
851
- Confirmation
852
- | BashCommand
853
- | BashInteraction
854
- | ResetShell
855
- | CreateFileNew
856
- | FileEditFindReplace
857
- | FileEdit
858
- | AIAssistant
859
- | DoneFlag
860
- | ReadImage
861
- | ReadFile
862
- | Initialize
863
- | Mouse
864
- | Keyboard
865
- | ScreenShot
866
- | GetScreenInfo,
867
- ](
868
- Confirmation
869
- | BashCommand
870
- | BashInteraction
871
- | ResetShell
872
- | CreateFileNew
873
- | FileEditFindReplace
874
- | FileEdit
875
- | AIAssistant
876
- | DoneFlag
877
- | ReadImage
878
- | ReadFile
879
- | Initialize
880
- | Mouse
881
- | Keyboard
882
- | ScreenShot
883
- | GetScreenInfo
884
- )
868
+ adapter = TypeAdapter[TOOLS](TOOLS)
885
869
  arg = adapter.validate_python(args)
886
870
  else:
887
871
  arg = args
888
872
  output: tuple[str | DoneFlag | ImageData, float]
873
+ TOOL_CALLS.append(arg)
889
874
  if isinstance(arg, Confirmation):
890
875
  console.print("Calling ask confirmation tool")
891
876
  output = ask_confirmation(arg), 0.0
892
877
  elif isinstance(arg, (BashCommand | BashInteraction)):
893
878
  console.print("Calling execute bash tool")
894
879
  output = execute_bash(enc, arg, max_tokens, None)
895
- elif isinstance(arg, CreateFileNew):
880
+ elif isinstance(arg, WriteIfEmpty):
896
881
  console.print("Calling write file tool")
897
882
  output = write_file(arg, True), 0
898
883
  elif isinstance(arg, FileEdit):
@@ -915,6 +900,8 @@ def get_tool_output(
915
900
  output = reset_shell(), 0.0
916
901
  elif isinstance(arg, Initialize):
917
902
  console.print("Calling initial info tool")
903
+ # First force reset
904
+ reset_shell()
918
905
  output = initial_info(), 0.0
919
906
  elif isinstance(arg, (Mouse, Keyboard, ScreenShot, GetScreenInfo)):
920
907
  console.print(f"Calling {type(arg).__name__} tool")
@@ -974,7 +961,7 @@ class Mdata(BaseModel):
974
961
  data: (
975
962
  BashCommand
976
963
  | BashInteraction
977
- | CreateFileNew
964
+ | WriteIfEmpty
978
965
  | ResetShell
979
966
  | FileEditFindReplace
980
967
  | FileEdit
wcgw/relay/serve.py CHANGED
@@ -17,7 +17,7 @@ from dotenv import load_dotenv
17
17
  from ..types_ import (
18
18
  BashCommand,
19
19
  BashInteraction,
20
- CreateFileNew,
20
+ WriteIfEmpty,
21
21
  FileEditFindReplace,
22
22
  FileEdit,
23
23
  Initialize,
@@ -31,7 +31,7 @@ class Mdata(BaseModel):
31
31
  data: (
32
32
  BashCommand
33
33
  | BashInteraction
34
- | CreateFileNew
34
+ | WriteIfEmpty
35
35
  | ResetShell
36
36
  | FileEditFindReplace
37
37
  | FileEdit
@@ -99,12 +99,12 @@ async def register_websocket(websocket: WebSocket, uuid: UUID) -> None:
99
99
  print(f"Client {uuid} disconnected")
100
100
 
101
101
 
102
- class CreateFileNewWithUUID(CreateFileNew):
102
+ class WriteIfEmptyWithUUID(WriteIfEmpty):
103
103
  user_id: UUID
104
104
 
105
105
 
106
106
  @app.post("/v1/create_file")
107
- async def create_file(write_file_data: CreateFileNewWithUUID) -> str:
107
+ async def create_file(write_file_data: WriteIfEmptyWithUUID) -> str:
108
108
  user_id = write_file_data.user_id
109
109
  if user_id not in clients:
110
110
  return "Failure: id not found, ask the user to check it."
wcgw/types_.py CHANGED
@@ -24,7 +24,7 @@ class ReadImage(BaseModel):
24
24
  type: Literal["ReadImage"]
25
25
 
26
26
 
27
- class CreateFileNew(BaseModel):
27
+ class WriteIfEmpty(BaseModel):
28
28
  file_path: str
29
29
  file_content: str
30
30
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: wcgw
3
- Version: 2.1.2
3
+ Version: 2.2.1
4
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>
@@ -9,7 +9,6 @@ Requires-Dist: anthropic>=0.39.0
9
9
  Requires-Dist: fastapi>=0.115.0
10
10
  Requires-Dist: humanize>=4.11.0
11
11
  Requires-Dist: mcp
12
- Requires-Dist: mypy>=1.11.2
13
12
  Requires-Dist: nltk>=3.9.1
14
13
  Requires-Dist: openai>=1.46.0
15
14
  Requires-Dist: petname>=2.6
@@ -33,12 +32,17 @@ Description-Content-Type: text/markdown
33
32
  - Claude - An MCP server on claude desktop for autonomous shell, coding and desktop control agent. (mac only)
34
33
  - Chatgpt - Allows custom gpt to talk to your shell via a relay server. (linux or mac)
35
34
 
35
+
36
+ ⚠️ Warning: do not use this repo if you aren't scared of "Autonomous shell command execution"
37
+
36
38
  [![Tests](https://github.com/rusiaaman/wcgw/actions/workflows/python-tests.yml/badge.svg?branch=main)](https://github.com/rusiaaman/wcgw/actions/workflows/python-tests.yml)
37
39
  [![Mypy strict](https://github.com/rusiaaman/wcgw/actions/workflows/python-types.yml/badge.svg?branch=main)](https://github.com/rusiaaman/wcgw/actions/workflows/python-types.yml)
38
40
  [![Build](https://github.com/rusiaaman/wcgw/actions/workflows/python-publish.yml/badge.svg)](https://github.com/rusiaaman/wcgw/actions/workflows/python-publish.yml)
39
41
 
40
42
  ## Updates
41
43
 
44
+ - [9 Dec 2024] [Vscode extension to paste context on Claude app](https://marketplace.visualstudio.com/items?itemName=AmanRusia.wcgw)
45
+
42
46
  - [01 Dec 2024] Removed author hosted relay server for chatgpt.
43
47
 
44
48
  - [26 Nov 2024] Introduced claude desktop support through mcp
@@ -143,7 +147,7 @@ The following requirements should be installed and working in the linux docker i
143
147
  2. Needs `scrot` to take screenshots.
144
148
  3. Needs `convert` from imagemagick to convert images.
145
149
 
146
- ## Usage
150
+ ### Usage
147
151
 
148
152
  Wait for a few seconds. You should be able to see this icon if everything goes right.
149
153
 
@@ -156,6 +160,13 @@ Then ask claude to execute shell commands, read files, edit files, run your code
156
160
 
157
161
  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.
158
162
 
163
+
164
+ ### [Optional] Vs code extension
165
+ https://marketplace.visualstudio.com/items?itemName=AmanRusia.wcgw
166
+
167
+ Commands:
168
+ - Select a text and press `cmd+'` and then enter instructions. This will switch the app to Claude and paste a text containing your instructions, file path, workspace dir, and the selected text.
169
+
159
170
  ## Chatgpt Setup
160
171
 
161
172
  Read here: https://github.com/rusiaaman/wcgw/blob/main/openai.md
@@ -1,22 +1,22 @@
1
1
  wcgw/__init__.py,sha256=9K2QW7QuSLhMTVbKbBYd9UUp-ZyrfBrxcjuD_xk458k,118
2
- wcgw/types_.py,sha256=rDz4olJS2zvYC13jzeOppA2tci-tVDyWAqeA5BesAaU,1773
2
+ wcgw/types_.py,sha256=5QM97K3kOJ7vNBgZ1NYmKPqwSe9WfzDImAIvjbRcClo,1772
3
3
  wcgw/client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
4
  wcgw/client/__main__.py,sha256=wcCrL4PjG51r5wVKqJhcoJPTLfHW0wNbD31DrUN0MWI,28
5
- wcgw/client/anthropic_client.py,sha256=xhJdQps422d0R6YaQX6tpoJdYp4dgX0dgEtODx3WmEM,20381
5
+ wcgw/client/anthropic_client.py,sha256=68JNQAdH9Cg5lWTMKX3SvGNbYqPGyGe02VWWUbLaNss,20938
6
6
  wcgw/client/cli.py,sha256=-z0kpDAW3mzfQrQeZfaVJhBCAQY3HXnt9GdgQ8s-u0Y,1003
7
7
  wcgw/client/common.py,sha256=grH-yV_4tnTQZ29xExn4YicGLxEq98z-HkEZwH0ReSg,1410
8
8
  wcgw/client/computer_use.py,sha256=35NKAlMrxwD0TBlMMRnbCwz4g8TBRGOlcy-cmS-yJ_A,15247
9
9
  wcgw/client/diff-instructions.txt,sha256=s5AJKG23JsjwRYhFZFQVvwDpF67vElawrmdXwvukR1A,1683
10
- wcgw/client/openai_client.py,sha256=u2YQrAswfNil_j6bMxOA4rxscT0TWA-wCgEKbr5rwrE,17868
10
+ wcgw/client/openai_client.py,sha256=xfB96A4qZM7-CUJhyDMqX9rUIzhCw4AtV9ryTKE8SOM,17885
11
11
  wcgw/client/openai_utils.py,sha256=YNwCsA-Wqq7jWrxP0rfQmBTb1dI0s7dWXzQqyTzOZT4,2629
12
12
  wcgw/client/sys_utils.py,sha256=GajPntKhaTUMn6EOmopENWZNR2G_BJyuVbuot0x6veI,1376
13
- wcgw/client/tools.py,sha256=kokbgxxBikIK39moSeg5uGvtBUzLXHnP5-d4jywZYRQ,35762
13
+ wcgw/client/tools.py,sha256=e5ul5ZNjvP7UJ8-gubHiNCYjcBIjAPkbkBckKnwKJp4,35724
14
14
  wcgw/client/mcp_server/Readme.md,sha256=I8N4dHkTUVGNQ63BQkBMBhCCBTgqGOSF_pUR6iOEiUk,2495
15
15
  wcgw/client/mcp_server/__init__.py,sha256=hyPPwO9cabAJsOMWhKyat9yl7OlSmIobaoAZKHu3DMc,381
16
- wcgw/client/mcp_server/server.py,sha256=_pzphHJxPyqpLP0YtyddYyaCh83K_OyHKkTuWeSVRHw,10935
17
- wcgw/relay/serve.py,sha256=RUcUeyL4Xt0EEo12Ul6VQjb4tRle4uIdsa85v7XXxEw,8771
16
+ wcgw/client/mcp_server/server.py,sha256=DCclYqC8mRzCti2pztnm8ZkwGeXnzzNlkvi5Yh02D20,11226
17
+ wcgw/relay/serve.py,sha256=KLYjTvM9CfqdxgFOfHM8LUkFGZ9kKyyJunpNdEIFQUk,8766
18
18
  wcgw/relay/static/privacy.txt,sha256=s9qBdbx2SexCpC_z33sg16TptmAwDEehMCLz4L50JLc,529
19
- wcgw-2.1.2.dist-info/METADATA,sha256=i7GFT8uKfejFukjIHWRGMCBDEQXdfAPw_kI8hb_ZRrI,7421
20
- wcgw-2.1.2.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
21
- wcgw-2.1.2.dist-info/entry_points.txt,sha256=eKo1omwbAggWlQ0l7GKoR7uV1-j16nk9tK0BhC2Oz_E,120
22
- wcgw-2.1.2.dist-info/RECORD,,
19
+ wcgw-2.2.1.dist-info/METADATA,sha256=ZI81P-DAaTxkmZmU8tBJ8tYAchOEJ3p0VeRGXpWi3IQ,7939
20
+ wcgw-2.2.1.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
21
+ wcgw-2.2.1.dist-info/entry_points.txt,sha256=eKo1omwbAggWlQ0l7GKoR7uV1-j16nk9tK0BhC2Oz_E,120
22
+ wcgw-2.2.1.dist-info/RECORD,,
File without changes