wcgw 1.0.1__py3-none-any.whl → 1.1.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
@@ -1,5 +1,7 @@
1
1
  import asyncio
2
2
  import base64
3
+ from concurrent.futures import ThreadPoolExecutor, as_completed
4
+ from io import BytesIO
3
5
  import json
4
6
  import mimetypes
5
7
  from pathlib import Path
@@ -14,6 +16,7 @@ from typing import (
14
16
  NewType,
15
17
  Optional,
16
18
  ParamSpec,
19
+ Type,
17
20
  TypeVar,
18
21
  TypedDict,
19
22
  )
@@ -42,7 +45,14 @@ from openai.types.chat import (
42
45
  ParsedChatCompletionMessage,
43
46
  )
44
47
  from nltk.metrics.distance import edit_distance
45
- from ..types_ import FileEditFindReplace, ResetShell, Writefile
48
+
49
+ from ..types_ import (
50
+ CreateFileNew,
51
+ FileEditFindReplace,
52
+ FullFileEdit,
53
+ ResetShell,
54
+ Writefile,
55
+ )
46
56
 
47
57
  from ..types_ import BashCommand
48
58
 
@@ -71,7 +81,10 @@ def render_terminal_output(text: str) -> str:
71
81
  break
72
82
  else:
73
83
  i = len(dsp)
74
- return "\n".join(screen.display[: len(dsp) - i])
84
+ lines = screen.display[: len(dsp) - i]
85
+ # Strip trailing space
86
+ lines = [line.rstrip() for line in lines]
87
+ return "\n".join(lines)
75
88
 
76
89
 
77
90
  class Confirmation(BaseModel):
@@ -369,8 +382,16 @@ def serve_image_in_bg(file_path: str, client_uuid: str, name: str) -> None:
369
382
  serve_image_in_bg(file_path, client_uuid, name)
370
383
 
371
384
 
385
+ MEDIA_TYPES = Literal["image/jpeg", "image/png", "image/gif", "image/webp"]
386
+
387
+
372
388
  class ImageData(BaseModel):
373
- dataurl: str
389
+ media_type: MEDIA_TYPES
390
+ data: str
391
+
392
+ @property
393
+ def dataurl(self) -> str:
394
+ return f"data:{self.media_type};base64," + self.data
374
395
 
375
396
 
376
397
  Param = ParamSpec("Param")
@@ -400,15 +421,20 @@ def read_image_from_shell(file_path: str) -> ImageData:
400
421
  image_bytes = image_file.read()
401
422
  image_b64 = base64.b64encode(image_bytes).decode("utf-8")
402
423
  image_type = mimetypes.guess_type(file_path)[0]
403
- return ImageData(dataurl=f"data:{image_type};base64,{image_b64}")
424
+ return ImageData(media_type=image_type, data=image_b64)
404
425
 
405
426
 
406
- def write_file(writefile: Writefile) -> str:
427
+ def write_file(writefile: Writefile | CreateFileNew, error_on_exist: bool) -> str:
407
428
  if not os.path.isabs(writefile.file_path):
408
429
  return "Failure: file_path should be absolute path"
409
430
  else:
410
431
  path_ = writefile.file_path
411
432
 
433
+ if error_on_exist and os.path.exists(path_):
434
+ file_data = Path(path_).read_text()
435
+ if file_data:
436
+ return f"Error: can't write to existing file {path_}, use other functions to edit the file"
437
+
412
438
  path = Path(path_)
413
439
  path.parent.mkdir(parents=True, exist_ok=True)
414
440
 
@@ -421,50 +447,128 @@ def write_file(writefile: Writefile) -> str:
421
447
  return "Success"
422
448
 
423
449
 
424
- def find_least_edit_distance_substring(content: str, find_str: str) -> str:
425
- content_lines = content.split("\n")
450
+ def find_least_edit_distance_substring(
451
+ content: str, find_str: str
452
+ ) -> tuple[str, float]:
453
+ orig_content_lines = content.split("\n")
454
+ content_lines = [
455
+ line.strip() for line in orig_content_lines
456
+ ] # Remove trailing and leading space for calculating edit distance
426
457
  find_lines = find_str.split("\n")
458
+ find_lines = [
459
+ line.strip() for line in find_lines
460
+ ] # Remove trailing and leading space for calculating edit distance
427
461
  # Slide window and find one with sum of edit distance least
428
462
  min_edit_distance = float("inf")
429
463
  min_edit_distance_lines = []
430
- for i in range(len(content_lines) - len(find_lines) + 1):
464
+ for i in range(max(1, len(content_lines) - len(find_lines) + 1)):
431
465
  edit_distance_sum = 0
432
466
  for j in range(len(find_lines)):
433
- edit_distance_sum += edit_distance(content_lines[i + j], find_lines[j])
467
+ if (i + j) < len(content_lines):
468
+ edit_distance_sum += edit_distance(content_lines[i + j], find_lines[j])
469
+ else:
470
+ edit_distance_sum += len(find_lines[j])
434
471
  if edit_distance_sum < min_edit_distance:
435
472
  min_edit_distance = edit_distance_sum
436
- min_edit_distance_lines = content_lines[i : i + len(find_lines)]
437
- return "\n".join(min_edit_distance_lines)
473
+ min_edit_distance_lines = orig_content_lines[i : i + len(find_lines)]
474
+ return "\n".join(min_edit_distance_lines), min_edit_distance
475
+
476
+
477
+ def edit_content(content: str, find_lines: str, replace_with_lines: str) -> str:
478
+ count = content.count(find_lines)
479
+ if count == 0:
480
+ closest_match, min_edit_distance = find_least_edit_distance_substring(
481
+ content, find_lines
482
+ )
483
+ print(
484
+ f"Exact match not found, found with whitespace removed edit distance: {min_edit_distance}"
485
+ )
486
+ if min_edit_distance / len(find_lines) < 1 / 100:
487
+ print("Editing file with closest match")
488
+ return edit_content(content, closest_match, replace_with_lines)
489
+ raise Exception(
490
+ f"Error: no match found for the provided `find_lines` in the file. Closest match:\n---\n{closest_match}\n---\nFile not edited"
491
+ )
492
+
493
+ content = content.replace(find_lines, replace_with_lines, 1)
494
+ return content
495
+
438
496
 
497
+ def do_diff_edit(fedit: FullFileEdit) -> str:
498
+ console.log(f"Editing file: {fedit.file_path}")
439
499
 
440
- def file_edit(file_edit: FileEditFindReplace) -> str:
441
- if not os.path.isabs(file_edit.file_path):
442
- path_ = os.path.join(CWD, file_edit.file_path)
500
+ if not os.path.isabs(fedit.file_path):
501
+ raise Exception("Failure: file_path should be absolute path")
443
502
  else:
444
- path_ = file_edit.file_path
503
+ path_ = fedit.file_path
504
+
505
+ if not os.path.exists(path_):
506
+ raise Exception(f"Error: file {path_} does not exist")
507
+
508
+ with open(path_) as f:
509
+ apply_diff_to = f.read()
510
+
511
+ lines = fedit.file_edit_using_searh_replace_blocks.split("\n")
512
+ n_lines = len(lines)
513
+ i = 0
514
+ while i < n_lines:
515
+ if re.match(r"^<<<<<<+\s*SEARCH\s*$", lines[i]):
516
+ search_block = []
517
+ i += 1
518
+ while i < n_lines and not re.match(r"^======*\s*$", lines[i]):
519
+ search_block.append(lines[i])
520
+ i += 1
521
+ i += 1
522
+ replace_block = []
523
+ while i < n_lines and not re.match(r"^>>>>>>+\s*REPLACE\s*$", lines[i]):
524
+ replace_block.append(lines[i])
525
+ i += 1
526
+ i += 1
527
+
528
+ for line in search_block:
529
+ console.log("> " + line)
530
+ console.log("---")
531
+ for line in replace_block:
532
+ console.log("< " + line)
533
+
534
+ search_block_ = "\n".join(search_block)
535
+ replace_block_ = "\n".join(replace_block)
536
+
537
+ apply_diff_to = edit_content(apply_diff_to, search_block_, replace_block_)
538
+ else:
539
+ i += 1
445
540
 
446
- out_string = "\n".join("> " + line for line in file_edit.find_lines.split("\n"))
447
- in_string = "\n".join(
448
- "< " + line for line in file_edit.replace_with_lines.split("\n")
449
- )
541
+ with open(path_, "w") as f:
542
+ f.write(apply_diff_to)
543
+
544
+ return "Success"
545
+
546
+
547
+ def file_edit(fedit: FileEditFindReplace) -> str:
548
+ if not os.path.isabs(fedit.file_path):
549
+ raise Exception("Failure: file_path should be absolute path")
550
+ else:
551
+ path_ = fedit.file_path
552
+
553
+ if not os.path.exists(path_):
554
+ raise Exception(f"Error: file {path_} does not exist")
555
+
556
+ if not fedit.find_lines:
557
+ raise Exception("Error: `find_lines` cannot be empty")
558
+
559
+ out_string = "\n".join("> " + line for line in fedit.find_lines.split("\n"))
560
+ in_string = "\n".join("< " + line for line in fedit.replace_with_lines.split("\n"))
450
561
  console.log(f"Editing file: {path_}\n---\n{out_string}\n---\n{in_string}\n---")
451
562
  try:
452
563
  with open(path_) as f:
453
564
  content = f.read()
454
- # First find counts
455
- count = content.count(file_edit.find_lines)
456
565
 
457
- if count == 0:
458
- closest_match = find_least_edit_distance_substring(
459
- content, file_edit.find_lines
460
- )
461
- return f"Error: no match found for the provided `find_lines` in the file. Closest match:\n---\n{closest_match}\n---\nFile not edited"
566
+ content = edit_content(content, fedit.find_lines, fedit.replace_with_lines)
462
567
 
463
- content = content.replace(file_edit.find_lines, file_edit.replace_with_lines)
464
568
  with open(path_, "w") as f:
465
569
  f.write(content)
466
570
  except OSError as e:
467
- return f"Error: {e}"
571
+ raise Exception(f"Error: {e}")
468
572
  console.print(f"File written to {path_}")
469
573
  return "Success"
470
574
 
@@ -491,31 +595,53 @@ def take_help_of_ai_assistant(
491
595
  return output, cost
492
596
 
493
597
 
494
- def which_tool(args: str) -> BaseModel:
495
- adapter = TypeAdapter[
496
- Confirmation
497
- | BashCommand
498
- | BashInteraction
499
- | ResetShell
500
- | Writefile
501
- | FileEditFindReplace
502
- | AIAssistant
503
- | DoneFlag
504
- | ReadImage
505
- ](
506
- Confirmation
507
- | BashCommand
508
- | BashInteraction
509
- | ResetShell
510
- | Writefile
511
- | FileEditFindReplace
512
- | AIAssistant
513
- | DoneFlag
514
- | ReadImage
515
- )
598
+ TOOLS = (
599
+ Confirmation
600
+ | BashCommand
601
+ | BashInteraction
602
+ | ResetShell
603
+ | Writefile
604
+ | CreateFileNew
605
+ | FileEditFindReplace
606
+ | FullFileEdit
607
+ | AIAssistant
608
+ | DoneFlag
609
+ | ReadImage
610
+ )
611
+
612
+
613
+ def which_tool(args: str) -> TOOLS:
614
+ adapter = TypeAdapter[TOOLS](TOOLS)
516
615
  return adapter.validate_python(json.loads(args))
517
616
 
518
617
 
618
+ def which_tool_name(name: str) -> Type[TOOLS]:
619
+ if name == "Confirmation":
620
+ return Confirmation
621
+ elif name == "BashCommand":
622
+ return BashCommand
623
+ elif name == "BashInteraction":
624
+ return BashInteraction
625
+ elif name == "ResetShell":
626
+ return ResetShell
627
+ elif name == "Writefile":
628
+ return Writefile
629
+ elif name == "CreateFileNew":
630
+ return CreateFileNew
631
+ elif name == "FileEditFindReplace":
632
+ return FileEditFindReplace
633
+ elif name == "FullFileEdit":
634
+ return FullFileEdit
635
+ elif name == "AIAssistant":
636
+ return AIAssistant
637
+ elif name == "DoneFlag":
638
+ return DoneFlag
639
+ elif name == "ReadImage":
640
+ return ReadImage
641
+ else:
642
+ raise ValueError(f"Unknown tool name: {name}")
643
+
644
+
519
645
  def get_tool_output(
520
646
  args: dict[object, object]
521
647
  | Confirmation
@@ -523,7 +649,9 @@ def get_tool_output(
523
649
  | BashInteraction
524
650
  | ResetShell
525
651
  | Writefile
652
+ | CreateFileNew
526
653
  | FileEditFindReplace
654
+ | FullFileEdit
527
655
  | AIAssistant
528
656
  | DoneFlag
529
657
  | ReadImage,
@@ -539,7 +667,9 @@ def get_tool_output(
539
667
  | BashInteraction
540
668
  | ResetShell
541
669
  | Writefile
670
+ | CreateFileNew
542
671
  | FileEditFindReplace
672
+ | FullFileEdit
543
673
  | AIAssistant
544
674
  | DoneFlag
545
675
  | ReadImage
@@ -549,7 +679,9 @@ def get_tool_output(
549
679
  | BashInteraction
550
680
  | ResetShell
551
681
  | Writefile
682
+ | CreateFileNew
552
683
  | FileEditFindReplace
684
+ | FullFileEdit
553
685
  | AIAssistant
554
686
  | DoneFlag
555
687
  | ReadImage
@@ -566,10 +698,16 @@ def get_tool_output(
566
698
  output = execute_bash(enc, arg, max_tokens)
567
699
  elif isinstance(arg, Writefile):
568
700
  console.print("Calling write file tool")
569
- output = write_file(arg), 0
701
+ output = write_file(arg, False), 0
702
+ elif isinstance(arg, CreateFileNew):
703
+ console.print("Calling write file tool")
704
+ output = write_file(arg, True), 0
570
705
  elif isinstance(arg, FileEditFindReplace):
571
706
  console.print("Calling file edit tool")
572
707
  output = file_edit(arg), 0.0
708
+ elif isinstance(arg, FullFileEdit):
709
+ console.print("Calling full file edit tool")
710
+ output = do_diff_edit(arg), 0.0
573
711
  elif isinstance(arg, DoneFlag):
574
712
  console.print("Calling mark finish tool")
575
713
  output = mark_finish(arg), 0.0
@@ -598,44 +736,29 @@ curr_cost = 0.0
598
736
 
599
737
 
600
738
  class Mdata(BaseModel):
601
- data: BashCommand | BashInteraction | Writefile | ResetShell | FileEditFindReplace
602
-
603
-
604
- execution_lock = threading.Lock()
605
-
606
-
607
- def execute_user_input() -> None:
608
- while True:
609
- discard_input()
610
- user_input = input()
611
- with execution_lock:
612
- try:
613
- console.log(
614
- execute_bash(
615
- default_enc,
616
- BashInteraction(
617
- send_ascii=[ord(x) for x in user_input] + [ord("\n")]
618
- ),
619
- max_tokens=None,
620
- )[0]
621
- )
622
- except Exception as e:
623
- traceback.print_exc()
624
- console.log(f"Error: {e}")
739
+ data: (
740
+ BashCommand
741
+ | BashInteraction
742
+ | Writefile
743
+ | CreateFileNew
744
+ | ResetShell
745
+ | FileEditFindReplace
746
+ | FullFileEdit
747
+ )
625
748
 
626
749
 
627
- async def register_client(server_url: str, client_uuid: str = "") -> None:
750
+ def register_client(server_url: str, client_uuid: str = "") -> None:
628
751
  global default_enc, default_model, curr_cost
629
752
  # Generate a unique UUID for this client
630
753
  if not client_uuid:
631
754
  client_uuid = str(uuid.uuid4())
632
755
 
633
756
  # Create the WebSocket connection
634
- async with websockets.connect(f"{server_url}/{client_uuid}") as websocket:
635
- server_version = str(await websocket.recv())
757
+ with syncconnect(f"{server_url}/{client_uuid}") as websocket:
758
+ server_version = str(websocket.recv())
636
759
  print(f"Server version: {server_version}")
637
760
  client_version = importlib.metadata.version("wcgw")
638
- await websocket.send(client_version)
761
+ websocket.send(client_version)
639
762
 
640
763
  print(
641
764
  f"Connected. Share this user id with the chatbot: {client_uuid} \nLink: https://chatgpt.com/g/g-Us0AAXkRh-wcgw-giving-shell-access"
@@ -643,24 +766,23 @@ async def register_client(server_url: str, client_uuid: str = "") -> None:
643
766
  try:
644
767
  while True:
645
768
  # Wait to receive data from the server
646
- message = await websocket.recv()
769
+ message = websocket.recv()
647
770
  mdata = Mdata.model_validate_json(message)
648
- with execution_lock:
649
- try:
650
- output, cost = get_tool_output(
651
- mdata.data, default_enc, 0.0, lambda x, y: ("", 0), None
652
- )
653
- curr_cost += cost
654
- print(f"{curr_cost=}")
655
- except Exception as e:
656
- output = f"GOT EXCEPTION while calling tool. Error: {e}"
657
- traceback.print_exc()
658
- assert isinstance(output, str)
659
- await websocket.send(output)
771
+ try:
772
+ output, cost = get_tool_output(
773
+ mdata.data, default_enc, 0.0, lambda x, y: ("", 0), None
774
+ )
775
+ curr_cost += cost
776
+ print(f"{curr_cost=}")
777
+ except Exception as e:
778
+ output = f"GOT EXCEPTION while calling tool. Error: {e}"
779
+ traceback.print_exc()
780
+ assert isinstance(output, str)
781
+ websocket.send(output)
660
782
 
661
783
  except (websockets.ConnectionClosed, ConnectionError):
662
784
  print(f"Connection closed for UUID: {client_uuid}, retrying")
663
- await register_client(server_url, client_uuid)
785
+ register_client(server_url, client_uuid)
664
786
 
665
787
 
666
788
  run = Typer(pretty_exceptions_show_locals=False, no_args_is_help=True)
@@ -677,13 +799,4 @@ def app(
677
799
  print(f"wcgw version: {version_}")
678
800
  exit()
679
801
 
680
- thread1 = threading.Thread(target=execute_user_input)
681
- thread2 = threading.Thread(
682
- target=asyncio.run, args=(register_client(server_url, client_uuid or ""),)
683
- )
684
-
685
- thread1.start()
686
- thread2.start()
687
-
688
- thread1.join()
689
- thread2.join()
802
+ register_client(server_url, client_uuid or "")
wcgw/relay/serve.py CHANGED
@@ -17,7 +17,9 @@ from dotenv import load_dotenv
17
17
  from ..types_ import (
18
18
  BashCommand,
19
19
  BashInteraction,
20
+ CreateFileNew,
20
21
  FileEditFindReplace,
22
+ FullFileEdit,
21
23
  ResetShell,
22
24
  Writefile,
23
25
  Specials,
@@ -25,7 +27,15 @@ from ..types_ import (
25
27
 
26
28
 
27
29
  class Mdata(BaseModel):
28
- data: BashCommand | BashInteraction | Writefile | ResetShell | FileEditFindReplace
30
+ data: (
31
+ BashCommand
32
+ | BashInteraction
33
+ | Writefile
34
+ | CreateFileNew
35
+ | ResetShell
36
+ | FileEditFindReplace
37
+ | FullFileEdit
38
+ )
29
39
  user_id: UUID
30
40
 
31
41
 
@@ -38,39 +48,6 @@ gpts: dict[UUID, Callable[[str], None]] = {}
38
48
  images: DefaultDict[UUID, dict[str, dict[str, Any]]] = DefaultDict(dict)
39
49
 
40
50
 
41
- @app.websocket("/register_serve_image/{uuid}")
42
- async def register_serve_image(websocket: WebSocket, uuid: UUID) -> None:
43
- raise Exception("Disabled")
44
- await websocket.accept()
45
- received_data = await websocket.receive_json()
46
- name = received_data["name"]
47
- image_b64 = received_data["image_b64"]
48
- image_bytes = base64.b64decode(image_b64)
49
- images[uuid][name] = {
50
- "content": image_bytes,
51
- "media_type": received_data["media_type"],
52
- }
53
-
54
-
55
- @app.get("/get_image/{uuid}/{name}")
56
- async def get_image(uuid: UUID, name: str) -> fastapi.responses.Response:
57
- return fastapi.responses.Response(
58
- content=images[uuid][name]["content"],
59
- media_type=images[uuid][name]["media_type"],
60
- )
61
-
62
-
63
- @app.websocket("/register/{uuid}")
64
- async def register_websocket_deprecated(websocket: WebSocket, uuid: UUID) -> None:
65
- await websocket.accept()
66
- await websocket.send_text(
67
- "Outdated client used. Deprecated api is being used. Upgrade the wcgw app."
68
- )
69
- await websocket.close(
70
- reason="This endpoint is deprecated. Please use /v1/register/{uuid}", code=1002
71
- )
72
-
73
-
74
51
  CLIENT_VERSION_MINIMUM = "1.0.0"
75
52
 
76
53
 
@@ -116,20 +93,12 @@ async def register_websocket(websocket: WebSocket, uuid: UUID) -> None:
116
93
  print(f"Client {uuid} disconnected")
117
94
 
118
95
 
119
- @app.post("/write_file")
120
- async def write_file_deprecated(write_file_data: Writefile, user_id: UUID) -> Response:
121
- return Response(
122
- content="This version of the API is deprecated. Please upgrade your client.",
123
- status_code=400,
124
- )
125
-
126
-
127
- class WritefileWithUUID(Writefile):
96
+ class CreateFileNewWithUUID(CreateFileNew):
128
97
  user_id: UUID
129
98
 
130
99
 
131
- @app.post("/v1/write_file")
132
- async def write_file(write_file_data: WritefileWithUUID) -> str:
100
+ @app.post("/v1/create_file")
101
+ async def create_file(write_file_data: CreateFileNewWithUUID) -> str:
133
102
  user_id = write_file_data.user_id
134
103
  if user_id not in clients:
135
104
  return "Failure: id not found, ask the user to check it."
@@ -153,13 +122,13 @@ async def write_file(write_file_data: WritefileWithUUID) -> str:
153
122
  raise fastapi.HTTPException(status_code=500, detail="Timeout error")
154
123
 
155
124
 
156
- class FileEditFindReplaceWithUUID(FileEditFindReplace):
125
+ class FullFileEditWithUUID(FullFileEdit):
157
126
  user_id: UUID
158
127
 
159
128
 
160
- @app.post("/v1/file_edit_find_replace")
129
+ @app.post("/v1/full_file_edit")
161
130
  async def file_edit_find_replace(
162
- file_edit_find_replace: FileEditFindReplaceWithUUID,
131
+ file_edit_find_replace: FullFileEditWithUUID,
163
132
  ) -> str:
164
133
  user_id = file_edit_find_replace.user_id
165
134
  if user_id not in clients:
@@ -218,14 +187,6 @@ async def reset_shell(reset_shell: ResetShellWithUUID) -> str:
218
187
  raise fastapi.HTTPException(status_code=500, detail="Timeout error")
219
188
 
220
189
 
221
- @app.post("/execute_bash")
222
- async def execute_bash_deprecated(excute_bash_data: Any, user_id: UUID) -> Response:
223
- return Response(
224
- content="This version of the API is deprecated. Please upgrade your client.",
225
- status_code=400,
226
- )
227
-
228
-
229
190
  class CommandWithUUID(BaseModel):
230
191
  command: str
231
192
  user_id: UUID
wcgw/types_.py CHANGED
@@ -16,6 +16,22 @@ class BashInteraction(BaseModel):
16
16
  send_specials: Optional[Sequence[Specials]] = None
17
17
  send_ascii: Optional[Sequence[int]] = None
18
18
 
19
+ def model_post_init(self, __context: object) -> None:
20
+ # Ensure only one of the fields is set
21
+ if (
22
+ sum(
23
+ [
24
+ int(bool(self.send_text)),
25
+ int(bool(self.send_specials)),
26
+ int(bool(self.send_ascii)),
27
+ ]
28
+ )
29
+ != 1
30
+ ):
31
+ raise ValueError(
32
+ "Exactly one of 'send_text', 'send_specials', or 'send_ascii' must be set"
33
+ )
34
+
19
35
 
20
36
  class ReadImage(BaseModel):
21
37
  file_path: str
@@ -27,6 +43,11 @@ class Writefile(BaseModel):
27
43
  file_content: str
28
44
 
29
45
 
46
+ class CreateFileNew(BaseModel):
47
+ file_path: str
48
+ file_content: str
49
+
50
+
30
51
  class FileEditFindReplace(BaseModel):
31
52
  file_path: str
32
53
  find_lines: str
@@ -35,3 +56,8 @@ class FileEditFindReplace(BaseModel):
35
56
 
36
57
  class ResetShell(BaseModel):
37
58
  should_reset: Literal[True] = True
59
+
60
+
61
+ class FullFileEdit(BaseModel):
62
+ file_path: str
63
+ file_edit_using_searh_replace_blocks: str
@@ -1,10 +1,11 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: wcgw
3
- Version: 1.0.1
3
+ Version: 1.1.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
+ Requires-Dist: anthropic>=0.39.0
8
9
  Requires-Dist: fastapi>=0.115.0
9
10
  Requires-Dist: mypy>=1.11.2
10
11
  Requires-Dist: nltk>=3.9.1
@@ -0,0 +1,17 @@
1
+ wcgw/__init__.py,sha256=PNWvBvjUKA3aj4bHOtIqBKCAtOW88pr0hAXZ7RylVr8,68
2
+ wcgw/types_.py,sha256=-RbRO6aglz1tj014vvr6q8J8ly-tTacSu0hhask46qM,1424
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=kswf7n1W7z1C8POWUnQLyESg7GtlgAoP-Yc4dCO86ts,14999
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=IcoPUZS5Y_fDlA5BuryIEGYykpnDtGUAP_oUNrZyC8U,1564
9
+ wcgw/client/openai_client.py,sha256=Qu5xjfL3zP7YkdDwowtyq2kAGYY1-YvNOt8FwdScoU8,16972
10
+ wcgw/client/openai_utils.py,sha256=YNwCsA-Wqq7jWrxP0rfQmBTb1dI0s7dWXzQqyTzOZT4,2629
11
+ wcgw/client/tools.py,sha256=KxMd8aPJmnkH-R-IisEAJ6KHAX95PRt2kLh9Z1t28hE,24667
12
+ wcgw/relay/serve.py,sha256=Ch-vdUK-W60fkVVwFZIfy2L1VT7Te4Yt4Z8Bb0ykAh8,7131
13
+ wcgw/relay/static/privacy.txt,sha256=s9qBdbx2SexCpC_z33sg16TptmAwDEehMCLz4L50JLc,529
14
+ wcgw-1.1.0.dist-info/METADATA,sha256=uvRAnlHl03aoR4uhhF0qVTK3Et3GF6DHIofx1jHUNQQ,5255
15
+ wcgw-1.1.0.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
16
+ wcgw-1.1.0.dist-info/entry_points.txt,sha256=WlIB825-Vm9ZtNzgENQsbHj4DRMkbpVR7uSkQyBlaPA,93
17
+ wcgw-1.1.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.25.0
2
+ Generator: hatchling 1.26.3
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any