wcgw 1.4.0__py3-none-any.whl → 1.5.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/client/tools.py CHANGED
@@ -11,6 +11,7 @@ import threading
11
11
  import importlib.metadata
12
12
  import time
13
13
  import traceback
14
+ from tempfile import TemporaryDirectory
14
15
  from typing import (
15
16
  Callable,
16
17
  Literal,
@@ -24,6 +25,7 @@ from typing import (
24
25
  import uuid
25
26
  from pydantic import BaseModel, TypeAdapter
26
27
  import typer
28
+ from .computer_use import run_computer_tool
27
29
  from websockets.sync.client import connect as syncconnect
28
30
 
29
31
  import os
@@ -57,10 +59,14 @@ from ..types_ import (
57
59
  ReadFile,
58
60
  ReadImage,
59
61
  ResetShell,
62
+ Mouse,
63
+ Keyboard,
64
+ ScreenShot,
65
+ GetScreenInfo,
60
66
  )
61
67
 
62
68
  from .common import CostData, Models, discard_input
63
-
69
+ from .sys_utils import command_run
64
70
  from .openai_utils import get_input_cost, get_output_cost
65
71
 
66
72
  console = rich.console.Console(style="magenta", highlight=False, markup=False)
@@ -153,6 +159,7 @@ def _get_exit_code() -> int:
153
159
 
154
160
  BASH_CLF_OUTPUT = Literal["repl", "pending"]
155
161
  BASH_STATE: BASH_CLF_OUTPUT = "repl"
162
+ IS_IN_DOCKER: Optional[str] = ""
156
163
  CWD = os.getcwd()
157
164
 
158
165
 
@@ -167,10 +174,11 @@ Current working directory: {CWD}
167
174
 
168
175
 
169
176
  def reset_shell() -> str:
170
- global SHELL, BASH_STATE, CWD
177
+ global SHELL, BASH_STATE, CWD, IS_IN_DOCKER
171
178
  SHELL.close(True)
172
179
  SHELL = start_shell()
173
180
  BASH_STATE = "repl"
181
+ IS_IN_DOCKER = ""
174
182
  CWD = os.getcwd()
175
183
  return "Reset successful" + get_status()
176
184
 
@@ -234,6 +242,7 @@ def execute_bash(
234
242
  enc: tiktoken.Encoding,
235
243
  bash_arg: BashCommand | BashInteraction,
236
244
  max_tokens: Optional[int],
245
+ timeout_s: Optional[float],
237
246
  ) -> tuple[str, float]:
238
247
  global SHELL, BASH_STATE, CWD
239
248
  try:
@@ -333,7 +342,7 @@ def execute_bash(
333
342
  SHELL.expect(PROMPT)
334
343
  return "---\n\nFailure: user interrupted the execution", 0.0
335
344
 
336
- wait = 5
345
+ wait = timeout_s or 5
337
346
  index = SHELL.expect([PROMPT, pexpect.TIMEOUT], timeout=wait)
338
347
  if index == 1:
339
348
  BASH_STATE = "pending"
@@ -438,14 +447,26 @@ def read_image_from_shell(file_path: str) -> ImageData:
438
447
  if not os.path.isabs(file_path):
439
448
  file_path = os.path.join(CWD, file_path)
440
449
 
441
- if not os.path.exists(file_path):
442
- raise ValueError(f"File {file_path} does not exist")
450
+ if not IS_IN_DOCKER:
451
+ if not os.path.exists(file_path):
452
+ raise ValueError(f"File {file_path} does not exist")
443
453
 
444
- with open(file_path, "rb") as image_file:
445
- image_bytes = image_file.read()
446
- image_b64 = base64.b64encode(image_bytes).decode("utf-8")
447
- image_type = mimetypes.guess_type(file_path)[0]
448
- return ImageData(media_type=image_type, data=image_b64) # type: ignore
454
+ with open(file_path, "rb") as image_file:
455
+ image_bytes = image_file.read()
456
+ image_b64 = base64.b64encode(image_bytes).decode("utf-8")
457
+ image_type = mimetypes.guess_type(file_path)[0]
458
+ return ImageData(media_type=image_type, data=image_b64) # type: ignore
459
+ else:
460
+ with TemporaryDirectory() as tmpdir:
461
+ rcode = os.system(f"docker cp {IS_IN_DOCKER}:{file_path} {tmpdir}")
462
+ if rcode != 0:
463
+ raise Exception(f"Error: Read failed with code {rcode}")
464
+ path_ = os.path.join(tmpdir, os.path.basename(file_path))
465
+ with open(path_, "rb") as f:
466
+ image_bytes = f.read()
467
+ image_b64 = base64.b64encode(image_bytes).decode("utf-8")
468
+ image_type = mimetypes.guess_type(file_path)[0]
469
+ return ImageData(media_type=image_type, data=image_b64) # type: ignore
449
470
 
450
471
 
451
472
  def write_file(writefile: CreateFileNew, error_on_exist: bool) -> str:
@@ -454,19 +475,42 @@ def write_file(writefile: CreateFileNew, error_on_exist: bool) -> str:
454
475
  else:
455
476
  path_ = writefile.file_path
456
477
 
457
- if error_on_exist and os.path.exists(path_):
458
- file_data = Path(path_).read_text()
459
- if file_data:
460
- return f"Error: can't write to existing file {path_}, use other functions to edit the file"
478
+ if not IS_IN_DOCKER:
479
+ if error_on_exist and os.path.exists(path_):
480
+ file_data = Path(path_).read_text()
481
+ if file_data:
482
+ return f"Error: can't write to existing file {path_}, use other functions to edit the file"
461
483
 
462
- path = Path(path_)
463
- path.parent.mkdir(parents=True, exist_ok=True)
484
+ path = Path(path_)
485
+ path.parent.mkdir(parents=True, exist_ok=True)
486
+
487
+ try:
488
+ with path.open("w") as f:
489
+ f.write(writefile.file_content)
490
+ except OSError as e:
491
+ return f"Error: {e}"
492
+ else:
493
+ if error_on_exist:
494
+ # Check if it exists using os.system
495
+ cmd = f"test -f {path_}"
496
+ status = os.system(f'docker exec {IS_IN_DOCKER} bash -c "{cmd}"')
497
+ if status == 0:
498
+ return f"Error: can't write to existing file {path_}, use other functions to edit the file"
499
+
500
+ with TemporaryDirectory() as tmpdir:
501
+ tmppath = os.path.join(tmpdir, os.path.basename(path_))
502
+ with open(tmppath, "w") as f:
503
+ f.write(writefile.file_content)
504
+ os.chmod(tmppath, 0o777)
505
+ parent_dir = os.path.dirname(path_)
506
+ rcode = os.system(f"docker exec {IS_IN_DOCKER} mkdir -p {parent_dir}")
507
+ if rcode != 0:
508
+ return f"Error: Write failed with code while creating dirs {rcode}"
509
+
510
+ rcode = os.system(f"docker cp {tmppath} {IS_IN_DOCKER}:{path_}")
511
+ if rcode != 0:
512
+ return f"Error: Write failed with code {rcode}"
464
513
 
465
- try:
466
- with path.open("w") as f:
467
- f.write(writefile.file_content)
468
- except OSError as e:
469
- return f"Error: {e}"
470
514
  console.print(f"File written to {path_}")
471
515
  return "Success"
472
516
 
@@ -545,11 +589,21 @@ def do_diff_edit(fedit: FileEdit) -> str:
545
589
  else:
546
590
  path_ = fedit.file_path
547
591
 
548
- if not os.path.exists(path_):
549
- raise Exception(f"Error: file {path_} does not exist")
592
+ if not IS_IN_DOCKER:
593
+ if not os.path.exists(path_):
594
+ raise Exception(f"Error: file {path_} does not exist")
550
595
 
551
- with open(path_) as f:
552
- apply_diff_to = f.read()
596
+ with open(path_) as f:
597
+ apply_diff_to = f.read()
598
+ else:
599
+ # Copy from docker
600
+ with TemporaryDirectory() as tmpdir:
601
+ rcode = os.system(f"docker cp {IS_IN_DOCKER}:{path_} {tmpdir}")
602
+ if rcode != 0:
603
+ raise Exception(f"Error: Read failed with code {rcode}")
604
+ path_tmp = os.path.join(tmpdir, os.path.basename(path_))
605
+ with open(path_tmp, "r") as f:
606
+ apply_diff_to = f.read()
553
607
 
554
608
  fedit.file_edit_using_search_replace_blocks = (
555
609
  fedit.file_edit_using_search_replace_blocks.strip()
@@ -597,40 +651,20 @@ def do_diff_edit(fedit: FileEdit) -> str:
597
651
  "Error: no valid search-replace blocks found, please check your syntax for FileEdit"
598
652
  )
599
653
 
600
- with open(path_, "w") as f:
601
- f.write(apply_diff_to)
602
-
603
- return "Success"
604
-
605
-
606
- def file_edit(fedit: FileEditFindReplace) -> str:
607
- if not os.path.isabs(fedit.file_path):
608
- raise Exception(
609
- f"Failure: file_path should be absolute path, current working directory is {CWD}"
610
- )
654
+ if not IS_IN_DOCKER:
655
+ with open(path_, "w") as f:
656
+ f.write(apply_diff_to)
611
657
  else:
612
- path_ = fedit.file_path
658
+ with TemporaryDirectory() as tmpdir:
659
+ path_tmp = os.path.join(tmpdir, os.path.basename(path_))
660
+ with open(path_tmp, "w") as f:
661
+ f.write(apply_diff_to)
662
+ os.chmod(path_tmp, 0o777)
663
+ # Copy to docker using docker cp
664
+ rcode = os.system(f"docker cp {path_tmp} {IS_IN_DOCKER}:{path_}")
665
+ if rcode != 0:
666
+ raise Exception(f"Error: Write failed with code {rcode}")
613
667
 
614
- if not os.path.exists(path_):
615
- raise Exception(f"Error: file {path_} does not exist")
616
-
617
- if not fedit.find_lines:
618
- raise Exception("Error: `find_lines` cannot be empty")
619
-
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"))
622
- console.log(f"Editing file: {path_}\n---\n{out_string}\n---\n{in_string}\n---")
623
- try:
624
- with open(path_) as f:
625
- content = f.read()
626
-
627
- content = edit_content(content, fedit.find_lines, fedit.replace_with_lines)
628
-
629
- with open(path_, "w") as f:
630
- f.write(content)
631
- except OSError as e:
632
- raise Exception(f"Error: {e}")
633
- console.print(f"File written to {path_}")
634
668
  return "Success"
635
669
 
636
670
 
@@ -669,6 +703,10 @@ TOOLS = (
669
703
  | ReadImage
670
704
  | ReadFile
671
705
  | Initialize
706
+ | Mouse
707
+ | Keyboard
708
+ | ScreenShot
709
+ | GetScreenInfo
672
710
  )
673
711
 
674
712
 
@@ -702,6 +740,14 @@ def which_tool_name(name: str) -> Type[TOOLS]:
702
740
  return ReadFile
703
741
  elif name == "Initialize":
704
742
  return Initialize
743
+ elif name == "Mouse":
744
+ return Mouse
745
+ elif name == "Keyboard":
746
+ return Keyboard
747
+ elif name == "ScreenShot":
748
+ return ScreenShot
749
+ elif name == "GetScreenInfo":
750
+ return GetScreenInfo
705
751
  else:
706
752
  raise ValueError(f"Unknown tool name: {name}")
707
753
 
@@ -719,12 +765,17 @@ def get_tool_output(
719
765
  | DoneFlag
720
766
  | ReadImage
721
767
  | Initialize
722
- | ReadFile,
768
+ | ReadFile
769
+ | Mouse
770
+ | Keyboard
771
+ | ScreenShot
772
+ | GetScreenInfo,
723
773
  enc: tiktoken.Encoding,
724
774
  limit: float,
725
775
  loop_call: Callable[[str, float], tuple[str, float]],
726
776
  max_tokens: Optional[int],
727
- ) -> tuple[str | ImageData | DoneFlag, float]:
777
+ ) -> tuple[list[str | ImageData | DoneFlag], float]:
778
+ global IS_IN_DOCKER
728
779
  if isinstance(args, dict):
729
780
  adapter = TypeAdapter[
730
781
  Confirmation
@@ -739,6 +790,10 @@ def get_tool_output(
739
790
  | ReadImage
740
791
  | ReadFile
741
792
  | Initialize
793
+ | Mouse
794
+ | Keyboard
795
+ | ScreenShot
796
+ | GetScreenInfo,
742
797
  ](
743
798
  Confirmation
744
799
  | BashCommand
@@ -752,6 +807,10 @@ def get_tool_output(
752
807
  | ReadImage
753
808
  | ReadFile
754
809
  | Initialize
810
+ | Mouse
811
+ | Keyboard
812
+ | ScreenShot
813
+ | GetScreenInfo
755
814
  )
756
815
  arg = adapter.validate_python(args)
757
816
  else:
@@ -762,13 +821,10 @@ def get_tool_output(
762
821
  output = ask_confirmation(arg), 0.0
763
822
  elif isinstance(arg, (BashCommand | BashInteraction)):
764
823
  console.print("Calling execute bash tool")
765
- output = execute_bash(enc, arg, max_tokens)
824
+ output = execute_bash(enc, arg, max_tokens, None)
766
825
  elif isinstance(arg, CreateFileNew):
767
826
  console.print("Calling write file tool")
768
827
  output = write_file(arg, True), 0
769
- elif isinstance(arg, FileEditFindReplace):
770
- console.print("Calling file edit tool")
771
- output = file_edit(arg), 0.0
772
828
  elif isinstance(arg, FileEdit):
773
829
  console.print("Calling full file edit tool")
774
830
  output = do_diff_edit(arg), 0.0
@@ -790,11 +846,50 @@ def get_tool_output(
790
846
  elif isinstance(arg, Initialize):
791
847
  console.print("Calling initial info tool")
792
848
  output = initial_info(), 0.0
849
+ elif isinstance(arg, (Mouse, Keyboard, ScreenShot, GetScreenInfo)):
850
+ console.print(f"Calling {type(arg).__name__} tool")
851
+ outputs_cost = run_computer_tool(arg), 0.0
852
+ console.print(outputs_cost[0][0])
853
+ outputs: list[ImageData | str | DoneFlag] = [outputs_cost[0][0]]
854
+ imgBs64 = outputs_cost[0][1]
855
+ if imgBs64:
856
+ console.print("Captured screenshot")
857
+ outputs.append(ImageData(media_type="image/png", data=imgBs64))
858
+ if not IS_IN_DOCKER:
859
+ try:
860
+ # At this point we should go into the docker env
861
+ res, _ = execute_bash(
862
+ enc,
863
+ BashCommand(
864
+ command=f"docker exec -it {arg.docker_image_id} sh"
865
+ ),
866
+ None,
867
+ 0.2,
868
+ )
869
+ # At this point we should go into the docker env
870
+ res, _ = execute_bash(
871
+ enc,
872
+ BashInteraction(
873
+ send_text=f"export PS1={PROMPT}", type="BashInteraction"
874
+ ),
875
+ None,
876
+ 0.2,
877
+ )
878
+ # Do chown of home dir
879
+ except Exception as e:
880
+ reset_shell()
881
+ raise Exception(
882
+ f"Some error happened while going inside docker. I've reset the shell. Please start again. Error {e}"
883
+ )
884
+ IS_IN_DOCKER = arg.docker_image_id
885
+ return outputs, outputs_cost[1]
793
886
  else:
794
887
  raise ValueError(f"Unknown tool: {arg}")
795
-
796
- console.print(str(output[0]))
797
- return output
888
+ if isinstance(output[0], str):
889
+ console.print(str(output[0]))
890
+ else:
891
+ console.print(f"Received {type(output[0])} from tool")
892
+ return [output[0]], output[1]
798
893
 
799
894
 
800
895
  History = list[ChatCompletionMessageParam]
@@ -843,9 +938,10 @@ def register_client(server_url: str, client_uuid: str = "") -> None:
843
938
  if isinstance(mdata.data, str):
844
939
  raise Exception(mdata)
845
940
  try:
846
- output, cost = get_tool_output(
941
+ outputs, cost = get_tool_output(
847
942
  mdata.data, default_enc, 0.0, lambda x, y: ("", 0), 8000
848
943
  )
944
+ output = outputs[0]
849
945
  curr_cost += cost
850
946
  print(f"{curr_cost=}")
851
947
  except Exception as e:
@@ -883,12 +979,22 @@ def read_file(readfile: ReadFile, max_tokens: Optional[int]) -> str:
883
979
  if not os.path.isabs(readfile.file_path):
884
980
  return f"Failure: file_path should be absolute path, current working directory is {CWD}"
885
981
 
886
- path = Path(readfile.file_path)
887
- if not path.exists():
888
- return f"Error: file {readfile.file_path} does not exist"
982
+ if not IS_IN_DOCKER:
983
+ path = Path(readfile.file_path)
984
+ if not path.exists():
985
+ return f"Error: file {readfile.file_path} does not exist"
889
986
 
890
- with path.open("r") as f:
891
- content = f.read()
987
+ with path.open("r") as f:
988
+ content = f.read()
989
+
990
+ else:
991
+ return_code, content, stderr = command_run(
992
+ f"cat {readfile.file_path}",
993
+ )
994
+ if return_code != 0:
995
+ raise Exception(
996
+ f"Error: cat {readfile.file_path} failed with code {return_code}\nstdout: {content}\nstderr: {stderr}"
997
+ )
892
998
 
893
999
  if max_tokens is not None:
894
1000
  tokens = default_enc.encode(content)
wcgw/types_.py CHANGED
@@ -51,3 +51,44 @@ class FileEdit(BaseModel):
51
51
 
52
52
  class Initialize(BaseModel):
53
53
  type: Literal["Initialize"]
54
+
55
+
56
+ class GetScreenInfo(BaseModel):
57
+ type: Literal["GetScreenInfo"]
58
+ docker_image_id: str
59
+
60
+
61
+ class ScreenShot(BaseModel):
62
+ type: Literal["ScreenShot"]
63
+ docker_image_id: str
64
+
65
+
66
+ class MouseMove(BaseModel):
67
+ x: int
68
+ y: int
69
+ type: Literal["MouseMove"]
70
+
71
+
72
+ class LeftClickDrag(BaseModel):
73
+ x: int
74
+ y: int
75
+
76
+
77
+ class MouseButton(BaseModel):
78
+ button_type: Literal[
79
+ "left_click",
80
+ "right_click",
81
+ "middle_click",
82
+ "double_click",
83
+ "scroll_up",
84
+ "scroll_down",
85
+ ]
86
+
87
+
88
+ class Mouse(BaseModel):
89
+ action: MouseButton | LeftClickDrag | MouseMove
90
+
91
+
92
+ class Keyboard(BaseModel):
93
+ action: Literal["key", "type"]
94
+ text: str
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: wcgw
3
- Version: 1.4.0
3
+ Version: 1.5.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
- Requires-Python: <3.13,>=3.10
7
+ Requires-Python: <3.13,>=3.11
8
8
  Requires-Dist: anthropic>=0.39.0
9
9
  Requires-Dist: fastapi>=0.115.0
10
10
  Requires-Dist: mcp>=1.0.0
@@ -27,82 +27,124 @@ Requires-Dist: uvicorn>=0.31.0
27
27
  Requires-Dist: websockets>=13.1
28
28
  Description-Content-Type: text/markdown
29
29
 
30
- # Enable shell access on chatgpt.com
31
- A custom gpt on chatgpt web app to interact with your local shell.
30
+ # Shell and Coding agent on Chatgpt and Claude desktop apps
31
+
32
+ A custom gpt on chatgpt web/desktop apps to interact with your local shell, edit files, run code, etc.
32
33
 
33
34
  [![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)
34
35
  [![Build](https://github.com/rusiaaman/wcgw/actions/workflows/python-publish.yml/badge.svg)](https://github.com/rusiaaman/wcgw/actions/workflows/python-publish.yml)
35
36
 
37
+ [New feature] [26-Nov-2024] Claude desktop support for shell, computer-control, coding agent.
38
+ [src/wcgw/client/mcp_server/Readme.md](src/wcgw/client/mcp_server/Readme.md)
39
+
36
40
  ### 🚀 Highlights
41
+
37
42
  - ⚡ **Full Shell Access**: No restrictions, complete control.
38
43
  - ⚡ **Create, Execute, Iterate**: Ask the gpt to keep running compiler checks till all errors are fixed, or ask it to keep checking for the status of a long running command till it's done.
39
- - ⚡ **Interactive Command Handling**: Supports interactive commands using arrow keys, interrupt, and ansi escape sequences.
44
+ - ⚡ **Interactive Command Handling**: Supports interactive commands using arrow keys, interrupt, and ansi escape sequences.
40
45
  - ⚡ **REPL support**: [beta] Supports python/node and other REPL execution.
41
46
 
42
- ### 🪜 Steps:
47
+ ## Claude
48
+ Full readme [src/wcgw/client/mcp_server/Readme.md](src/wcgw/client/mcp_server/Readme.md)
49
+ ### Setup
50
+
51
+ Update `claude_desktop_config.json`
52
+
53
+ ```json
54
+ {
55
+ "mcpServers": {
56
+ "wcgw": {
57
+ "command": "uvx",
58
+ "args": ["--from", "wcgw@latest", "wcgw_mcp"]
59
+ }
60
+ }
61
+ }
62
+ ```
63
+
64
+ Then restart claude app.
65
+ You can then ask claude to execute shell commands, read files, edit files, run your code, etc.
66
+
67
+ ## ChatGPT
68
+
69
+ ### 🪜 Steps:
70
+
43
71
  1. Run the [cli client](https://github.com/rusiaaman/wcgw?tab=readme-ov-file#client) in any directory of choice.
44
72
  2. Share the generated id with this GPT: `https://chatgpt.com/g/g-Us0AAXkRh-wcgw-giving-shell-access`
45
73
  3. The custom GPT can now run any command on your cli
46
74
 
75
+ ### Client
47
76
 
48
- ## Client
49
77
  You need to keep running this client for GPT to access your shell. Run it in a version controlled project's root.
50
78
 
51
- ### Option 1: using uv [Recommended]
79
+ #### Option 1: using uv [Recommended]
80
+
52
81
  ```sh
53
82
  $ curl -LsSf https://astral.sh/uv/install.sh | sh
54
83
  $ uvx wcgw@latest
55
84
  ```
56
85
 
57
- ### Option 2: using pip
86
+ #### Option 2: using pip
87
+
58
88
  Supports python >=3.10 and <3.13
89
+
59
90
  ```sh
60
91
  $ pip3 install wcgw
61
92
  $ wcgw
62
93
  ```
63
94
 
64
-
65
95
  This will print a UUID that you need to share with the gpt.
66
96
 
97
+ ### Chat
67
98
 
68
- ## Chat
69
99
  Open the following link or search the "wcgw" custom gpt using "Explore GPTs" on chatgpt.com
70
100
 
71
101
  https://chatgpt.com/g/g-Us0AAXkRh-wcgw-giving-shell-access
72
102
 
73
103
  Finally, let the chatgpt know your user id in any format. E.g., "user_id=<your uuid>" followed by rest of your instructions.
74
104
 
75
- NOTE: you can resume a broken connection
105
+ NOTE: you can resume a broken connection
76
106
  `wcgw --client-uuid $previous_uuid`
77
107
 
78
- # How it works
108
+ ### How it works on chatgpt app?
109
+
79
110
  Your commands are relayed through a server to the terminal client. [You could host the server on your own](https://github.com/rusiaaman/wcgw?tab=readme-ov-file#creating-your-own-custom-gpt-and-the-relay-server). For public convenience I've hosted one at https://wcgw.arcfu.com thanks to the gcloud free tier plan.
80
111
 
81
112
  Chatgpt sends a request to the relay server using the user id that you share with it. The relay server holds a websocket with the terminal client against the user id and acts as a proxy to pass the request.
82
113
 
83
- It's secure in both the directions. Either a malicious actor or a malicious Chatgpt has to correctly guess your UUID for any security breach.
114
+ It's secure in both the directions. Either a malicious actor or a malicious Chatgpt has to correctly guess your UUID for any security breach.
84
115
 
85
116
  # Showcase
86
117
 
87
- ## Unit tests and github actions
118
+ ## Claude desktop
119
+
120
+ ### Resize image and move it to a new dir
121
+
122
+ ![example](https://github.com/rusiaaman/wcgw/blob/main/static/example.jpg?raw=true)
123
+
124
+ ## Chatgpt app
125
+
126
+ ### Unit tests and github actions
127
+
88
128
  [The first version of unit tests and github workflow to test on multiple python versions were written by the custom chatgpt](https://chatgpt.com/share/6717f922-8998-8005-b825-45d4b348b4dd)
89
129
 
90
- ## Create a todo app using react + typescript + vite
91
- ![Screenshot](https://github.com/rusiaaman/wcgw/blob/main/static/ss1.png?raw=true)
130
+ ### Create a todo app using react + typescript + vite
92
131
 
132
+ ![Screenshot](https://github.com/rusiaaman/wcgw/blob/main/static/ss1.png?raw=true)
93
133
 
94
134
  # Privacy
135
+
95
136
  The relay server doesn't store any data. I can't access any information passing through it and only secure channels are used to communicate.
96
137
 
97
138
  You may host the server on your own and create a custom gpt using the following section.
98
139
 
99
140
  # Creating your own custom gpt and the relay server.
141
+
100
142
  I've used the following instructions and action json schema to create the custom GPT. (Replace wcgw.arcfu.com with the address to your server)
101
143
 
102
144
  https://github.com/rusiaaman/wcgw/blob/main/gpt_instructions.txt
103
145
  https://github.com/rusiaaman/wcgw/blob/main/gpt_action_json_schema.json
104
146
 
105
- Run the server
147
+ Run the server
106
148
  `gunicorn --worker-class uvicorn.workers.UvicornWorker --bind 0.0.0.0:443 src.wcgw.relay.serve:app --certfile fullchain.pem --keyfile privkey.pem`
107
149
 
108
150
  If you don't have public ip and domain name, you can use `ngrok` or similar services to get a https address to the api.
@@ -110,19 +152,24 @@ If you don't have public ip and domain name, you can use `ngrok` or similar serv
110
152
  The specify the server url in the `wcgw` command like so
111
153
  `wcgw --server-url https://your-url/v1/register`
112
154
 
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.
155
+ # [Optional] Local shell access with openai API key or anthropic API key
115
156
 
116
- # [Optional] Local shell access with openai API key
157
+ ## Openai
117
158
 
118
159
  Add `OPENAI_API_KEY` and `OPENAI_ORG_ID` env variables.
119
160
 
120
- Clone the repo and run to install `wcgw_local` command
161
+ Then run
162
+
163
+ `uvx --from wcgw@latest wcgw_local --limit 0.1` # Cost limit $0.1
164
+
165
+ You can now directly write messages or press enter key to open vim for multiline message and text pasting.
166
+
167
+ ## Anthropic
121
168
 
122
- `pip install .`
169
+ Add `ANTHROPIC_API_KEY` env variable.
123
170
 
124
- Then run
171
+ Then run
125
172
 
126
- `wcgw_local --limit 0.1` # Cost limit $0.1
173
+ `uvx --from wcgw@latest wcgw_local --claude`
127
174
 
128
175
  You can now directly write messages or press enter key to open vim for multiline message and text pasting.
@@ -0,0 +1,22 @@
1
+ wcgw/__init__.py,sha256=9K2QW7QuSLhMTVbKbBYd9UUp-ZyrfBrxcjuD_xk458k,118
2
+ wcgw/types_.py,sha256=STKGeVdQNK_k8mcmAXpKZJY9YUpU7-mbJcFpXfzBuys,1732
3
+ wcgw/client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ wcgw/client/__main__.py,sha256=wcCrL4PjG51r5wVKqJhcoJPTLfHW0wNbD31DrUN0MWI,28
5
+ wcgw/client/anthropic_client.py,sha256=owR-nIxQVGgw_ned8JOQ-QmmCBQvZSqgD08kYpU_Rbg,17730
6
+ wcgw/client/cli.py,sha256=Oja42CHkVO8puqOXflko9NeephYCMa85aBmQTEjBZtI,932
7
+ wcgw/client/common.py,sha256=grH-yV_4tnTQZ29xExn4YicGLxEq98z-HkEZwH0ReSg,1410
8
+ wcgw/client/computer_use.py,sha256=jZCw4p7qfHEmhv9eqc1zyPyLTUyb54Ipw5UnRqzRWqc,14399
9
+ wcgw/client/diff-instructions.txt,sha256=s5AJKG23JsjwRYhFZFQVvwDpF67vElawrmdXwvukR1A,1683
10
+ wcgw/client/openai_client.py,sha256=L61ajFVQW2QPS3C0n1YsjgF4vQKfMIZHmp6iFBHutX8,17748
11
+ wcgw/client/openai_utils.py,sha256=YNwCsA-Wqq7jWrxP0rfQmBTb1dI0s7dWXzQqyTzOZT4,2629
12
+ wcgw/client/sys_utils.py,sha256=7_7o1Au33OkZUsW5nKW55xW_YRYZlvUpY6tHVLIILm8,1254
13
+ wcgw/client/tools.py,sha256=PbS1YBELqibShPrpKCGq3KeXLS4t6AhTTkB5pQMgX-g,32476
14
+ wcgw/client/mcp_server/Readme.md,sha256=dK9NqFQ0_tblLOgPI6DjWrSYs2UWKEJkvgnLW3T-EZk,1944
15
+ wcgw/client/mcp_server/__init__.py,sha256=cQ7PUrEmXUpio8x0SEoGWP5hCRPd7z2bAkNCbYbtTys,236
16
+ wcgw/client/mcp_server/server.py,sha256=EzO4VNuSfcIMTue3sCsCqTTMku_AmsgJctBuPQfEQ8w,10160
17
+ wcgw/relay/serve.py,sha256=RUcUeyL4Xt0EEo12Ul6VQjb4tRle4uIdsa85v7XXxEw,8771
18
+ wcgw/relay/static/privacy.txt,sha256=s9qBdbx2SexCpC_z33sg16TptmAwDEehMCLz4L50JLc,529
19
+ wcgw-1.5.0.dist-info/METADATA,sha256=_bTBKk1cMtbqt7dgM-wge3LuNCTe4s1CDzdSyR7P3WU,6292
20
+ wcgw-1.5.0.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
21
+ wcgw-1.5.0.dist-info/entry_points.txt,sha256=eKo1omwbAggWlQ0l7GKoR7uV1-j16nk9tK0BhC2Oz_E,120
22
+ wcgw-1.5.0.dist-info/RECORD,,
@@ -1,20 +0,0 @@
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,,
File without changes