wcgw 5.4.5__py3-none-any.whl → 5.5.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.

@@ -3,13 +3,15 @@ import json
3
3
  import os
4
4
  import platform
5
5
  import random
6
+ import re
7
+ import shlex
6
8
  import subprocess
7
9
  import tempfile
8
10
  import threading
9
11
  import time
10
12
  import traceback
11
13
  from dataclasses import dataclass
12
- from hashlib import sha256
14
+ from hashlib import md5, sha256
13
15
  from typing import (
14
16
  Any,
15
17
  Literal,
@@ -17,6 +19,7 @@ from typing import (
17
19
  ParamSpec,
18
20
  TypeVar,
19
21
  )
22
+ from uuid import uuid4
20
23
 
21
24
  import pexpect
22
25
  import psutil
@@ -36,8 +39,9 @@ from ..encoder import EncoderDecoder
36
39
  from ..modes import BashCommandMode, FileEditMode, WriteIfEmptyMode
37
40
  from .parser.bash_statement_parser import BashStatementParser
38
41
 
39
- PROMPT_CONST = "wcgw→" + " "
40
- PROMPT_STATEMENT = "export GIT_PAGER=cat PAGER=cat PROMPT_COMMAND= PS1='wcgw→'' '"
42
+ PROMPT_CONST = re.compile(r" ([^\n]*)──➤")
43
+ PROMPT_COMMAND = "printf '◉ '\"$(pwd)\"'──➤'' \r\\e[2K'"
44
+ PROMPT_STATEMENT = ""
41
45
  BASH_CLF_OUTPUT = Literal["repl", "pending"]
42
46
  os.environ["TOKENIZERS_PARALLELISM"] = "false"
43
47
 
@@ -85,8 +89,23 @@ def get_tmpdir() -> str:
85
89
  def check_if_screen_command_available() -> bool:
86
90
  try:
87
91
  subprocess.run(
88
- ["which", "screen"], capture_output=True, check=True, timeout=0.2
92
+ ["which", "screen"],
93
+ capture_output=True,
94
+ check=True,
95
+ timeout=CONFIG.timeout,
89
96
  )
97
+
98
+ # Check if screenrc exists, create it if it doesn't
99
+ home_dir = os.path.expanduser("~")
100
+ screenrc_path = os.path.join(home_dir, ".screenrc")
101
+
102
+ if not os.path.exists(screenrc_path):
103
+ screenrc_content = """defscrollback 10000
104
+ termcapinfo xterm* ti@:te@
105
+ """
106
+ with open(screenrc_path, "w") as f:
107
+ f.write(screenrc_content)
108
+
90
109
  return True
91
110
  except (subprocess.CalledProcessError, FileNotFoundError, TimeoutError):
92
111
  return False
@@ -215,7 +234,7 @@ def cleanup_all_screens_with_name(name: str, console: Console) -> None:
215
234
  capture_output=True,
216
235
  text=True,
217
236
  check=True,
218
- timeout=0.2,
237
+ timeout=CONFIG.timeout,
219
238
  )
220
239
  output = result.stdout
221
240
  except subprocess.CalledProcessError as e:
@@ -252,18 +271,112 @@ def cleanup_all_screens_with_name(name: str, console: Console) -> None:
252
271
  console.log(f"Failed to kill screen session: {session}\n{e}")
253
272
 
254
273
 
274
+ def get_rc_file_path(shell_path: str) -> Optional[str]:
275
+ """
276
+ Get the rc file path for the given shell.
277
+
278
+ Args:
279
+ shell_path: Path to the shell executable
280
+
281
+ Returns:
282
+ Path to the rc file or None if not supported
283
+ """
284
+ shell_name = os.path.basename(shell_path)
285
+ home_dir = os.path.expanduser("~")
286
+
287
+ if shell_name == "zsh":
288
+ return os.path.join(home_dir, ".zshrc")
289
+ elif shell_name == "bash":
290
+ return os.path.join(home_dir, ".bashrc")
291
+ else:
292
+ return None
293
+
294
+
295
+ def ensure_wcgw_block_in_rc_file(shell_path: str, console: Console) -> None:
296
+ """
297
+ Ensure the WCGW environment block exists in the appropriate rc file.
298
+
299
+ Args:
300
+ shell_path: Path to the shell executable
301
+ console: Console for logging
302
+ """
303
+ rc_file_path = get_rc_file_path(shell_path)
304
+ if not rc_file_path:
305
+ return
306
+
307
+ shell_name = os.path.basename(shell_path)
308
+
309
+ # Define the WCGW block with marker comments
310
+ marker_start = "# --WCGW_ENVIRONMENT_START--"
311
+ marker_end = "# --WCGW_ENVIRONMENT_END--"
312
+
313
+ if shell_name == "zsh":
314
+ wcgw_block = f"""{marker_start}
315
+ if [ -n "$IN_WCGW_ENVIRONMENT" ]; then
316
+ PROMPT_COMMAND='printf "◉ $(pwd)──➤ \\r\\e[2K"'
317
+ prmptcmdwcgw() {{ eval "$PROMPT_COMMAND" }}
318
+ add-zsh-hook -d precmd prmptcmdwcgw
319
+ precmd_functions+=prmptcmdwcgw
320
+ fi
321
+ {marker_end}
322
+ """
323
+ elif shell_name == "bash":
324
+ wcgw_block = f"""{marker_start}
325
+ if [ -n "$IN_WCGW_ENVIRONMENT" ]; then
326
+ PROMPT_COMMAND='printf "◉ $(pwd)──➤ \\r\\e[2K"'
327
+ fi
328
+ {marker_end}
329
+ """
330
+ else:
331
+ return
332
+
333
+ # Check if rc file exists
334
+ if not os.path.exists(rc_file_path):
335
+ # Create the rc file with the WCGW block
336
+ try:
337
+ with open(rc_file_path, "w") as f:
338
+ f.write(wcgw_block)
339
+ console.log(f"Created {rc_file_path} with WCGW environment block")
340
+ except Exception as e:
341
+ console.log(f"Failed to create {rc_file_path}: {e}")
342
+ return
343
+
344
+ # Check if the block already exists
345
+ try:
346
+ with open(rc_file_path) as f:
347
+ content = f.read()
348
+
349
+ if marker_start in content:
350
+ # Block already exists
351
+ return
352
+
353
+ # Append the block to the file
354
+ with open(rc_file_path, "a") as f:
355
+ f.write("\n" + wcgw_block)
356
+ console.log(f"Added WCGW environment block to {rc_file_path}")
357
+ except Exception as e:
358
+ console.log(f"Failed to update {rc_file_path}: {e}")
359
+
360
+
255
361
  def start_shell(
256
- is_restricted_mode: bool, initial_dir: str, console: Console, over_screen: bool
362
+ is_restricted_mode: bool,
363
+ initial_dir: str,
364
+ console: Console,
365
+ over_screen: bool,
366
+ shell_path: str,
257
367
  ) -> tuple["pexpect.spawn[str]", str]:
258
- cmd = "/bin/bash"
259
- if is_restricted_mode:
368
+ cmd = shell_path
369
+ if is_restricted_mode and cmd.split("/")[-1] == "bash":
260
370
  cmd += " -r"
261
371
 
262
372
  overrideenv = {
263
373
  **os.environ,
264
- "PS1": PROMPT_CONST,
374
+ "PROMPT_COMMAND": PROMPT_COMMAND,
265
375
  "TMPDIR": get_tmpdir(),
266
376
  "TERM": "xterm-256color",
377
+ "IN_WCGW_ENVIRONMENT": "1",
378
+ "GIT_PAGER": "cat",
379
+ "PAGER": "cat",
267
380
  }
268
381
  try:
269
382
  shell = pexpect.spawn(
@@ -277,7 +390,7 @@ def start_shell(
277
390
  dimensions=(500, 160),
278
391
  )
279
392
  shell.sendline(PROMPT_STATEMENT) # Unset prompt command to avoid interfering
280
- shell.expect(PROMPT_CONST, timeout=0.2)
393
+ shell.expect(PROMPT_CONST, timeout=CONFIG.timeout)
281
394
  except Exception as e:
282
395
  console.print(traceback.format_exc())
283
396
  console.log(f"Error starting shell: {e}. Retrying without rc ...")
@@ -291,30 +404,31 @@ def start_shell(
291
404
  codec_errors="backslashreplace",
292
405
  )
293
406
  shell.sendline(PROMPT_STATEMENT)
294
- shell.expect(PROMPT_CONST, timeout=0.2)
407
+ shell.expect(PROMPT_CONST, timeout=CONFIG.timeout)
295
408
 
296
- shellid = "wcgw." + time.strftime("%H%M%S")
409
+ initialdir_hash = md5(
410
+ os.path.normpath(os.path.abspath(initial_dir)).encode()
411
+ ).hexdigest()[:5]
412
+ shellid = shlex.quote(
413
+ "wcgw."
414
+ + time.strftime("%d-%Hh%Mm%Ss")
415
+ + f".{initialdir_hash[:3]}."
416
+ + os.path.basename(initial_dir)
417
+ )
297
418
  if over_screen:
298
419
  if not check_if_screen_command_available():
299
420
  raise ValueError("Screen command not available")
300
421
  # shellid is just hour, minute, second number
301
- shell.sendline(f"trap 'screen -X -S {shellid} quit' EXIT")
302
- shell.expect(PROMPT_CONST, timeout=0.2)
303
-
304
- shell.sendline(f"screen -q -S {shellid} /bin/bash --noprofile --norc")
422
+ while True:
423
+ output = shell.expect([PROMPT_CONST, pexpect.TIMEOUT], timeout=0.1)
424
+ if output == 1:
425
+ break
426
+ shell.sendline(f"screen -q -S {shellid} {shell_path}")
305
427
  shell.expect(PROMPT_CONST, timeout=CONFIG.timeout)
306
428
 
307
429
  return shell, shellid
308
430
 
309
431
 
310
- def _is_int(mystr: str) -> bool:
311
- try:
312
- int(mystr)
313
- return True
314
- except ValueError:
315
- return False
316
-
317
-
318
432
  def render_terminal_output(text: str) -> list[str]:
319
433
  screen = pyte.Screen(160, 500)
320
434
  screen.set_mode(pyte.modes.LNM)
@@ -390,8 +504,9 @@ class BashState:
390
504
  use_screen: bool,
391
505
  whitelist_for_overwrite: Optional[dict[str, "FileWhitelistData"]] = None,
392
506
  thread_id: Optional[str] = None,
507
+ shell_path: Optional[str] = None,
393
508
  ) -> None:
394
- self._last_command: str = ""
509
+ self.last_command: str = ""
395
510
  self.console = console
396
511
  self._cwd = working_dir or os.getcwd()
397
512
  # Store the workspace root separately from the current working directory
@@ -403,7 +518,7 @@ class BashState:
403
518
  self._write_if_empty_mode: WriteIfEmptyMode = (
404
519
  write_if_empty_mode or WriteIfEmptyMode("all")
405
520
  )
406
- self._mode = mode or "wcgw"
521
+ self._mode: Modes = mode or "wcgw"
407
522
  self._whitelist_for_overwrite: dict[str, FileWhitelistData] = (
408
523
  whitelist_for_overwrite or {}
409
524
  )
@@ -414,31 +529,78 @@ class BashState:
414
529
  self._bg_expect_thread: Optional[threading.Thread] = None
415
530
  self._bg_expect_thread_stop_event = threading.Event()
416
531
  self._use_screen = use_screen
532
+ # Ensure shell_path is always a str, never None
533
+ self._shell_path: str = (
534
+ shell_path if shell_path else os.environ.get("SHELL", "/bin/bash")
535
+ )
536
+ if get_rc_file_path(self._shell_path) is None:
537
+ console.log(
538
+ f"Warning: Unsupported shell: {self._shell_path}, defaulting to /bin/bash"
539
+ )
540
+ self._shell_path = "/bin/bash"
541
+
542
+ self.background_shells = dict[str, BashState]()
417
543
  self._init_shell()
418
544
 
419
- def expect(self, pattern: Any, timeout: Optional[float] = -1) -> int:
545
+ def start_new_bg_shell(self, working_dir: str) -> "BashState":
546
+ cid = uuid4().hex[:10]
547
+ state = BashState(
548
+ self.console,
549
+ working_dir=working_dir,
550
+ bash_command_mode=self.bash_command_mode,
551
+ file_edit_mode=self.file_edit_mode,
552
+ write_if_empty_mode=self.write_if_empty_mode,
553
+ mode=self.mode,
554
+ use_screen=self.over_screen,
555
+ whitelist_for_overwrite=None,
556
+ thread_id=cid,
557
+ shell_path=self._shell_path,
558
+ )
559
+ self.background_shells[cid] = state
560
+ return state
561
+
562
+ def expect(
563
+ self, pattern: Any, timeout: Optional[float] = -1, flush_rem_prompt: bool = True
564
+ ) -> int:
420
565
  self.close_bg_expect_thread()
421
566
  try:
422
567
  output = self._shell.expect(pattern, timeout)
568
+ if isinstance(self._shell.match, re.Match) and self._shell.match.groups():
569
+ cwd = self._shell.match.group(1)
570
+ if cwd.strip():
571
+ self._cwd = cwd
572
+ # We can safely flush current prompt
573
+ if flush_rem_prompt:
574
+ temp_before = self._shell.before
575
+ self.flush_prompt()
576
+ self._shell.before = temp_before
423
577
  except pexpect.TIMEOUT:
424
578
  # Edge case: gets raised when the child fd is not ready in some timeout
425
579
  # pexpect/utils.py:143
426
580
  return 1
427
581
  return output
428
582
 
583
+ def flush_prompt(self) -> None:
584
+ # Flush remaining prompt
585
+ for _ in range(200):
586
+ try:
587
+ output = self.expect([" ", pexpect.TIMEOUT], 0.1)
588
+ if output == 1:
589
+ return
590
+ except pexpect.TIMEOUT:
591
+ return
592
+
429
593
  def send(self, s: str | bytes, set_as_command: Optional[str]) -> int:
430
- self.close_bg_expect_thread()
431
594
  if set_as_command is not None:
432
- self._last_command = set_as_command
595
+ self.last_command = set_as_command
433
596
  # if s == "\n":
434
597
  # return self._shell.sendcontrol("m")
435
598
  output = self._shell.send(s)
436
599
  return output
437
600
 
438
601
  def sendline(self, s: str | bytes, set_as_command: Optional[str]) -> int:
439
- self.close_bg_expect_thread()
440
602
  if set_as_command is not None:
441
- self._last_command = set_as_command
603
+ self.last_command = set_as_command
442
604
  output = self._shell.sendline(s)
443
605
  return output
444
606
 
@@ -453,8 +615,8 @@ class BashState:
453
615
  @property
454
616
  def before(self) -> Optional[str]:
455
617
  before = self._shell.before
456
- if before and before.startswith(self._last_command):
457
- return before[len(self._last_command) :]
618
+ if before and before.startswith(self.last_command):
619
+ return before[len(self.last_command) :]
458
620
  return before
459
621
 
460
622
  def run_bg_expect_thread(self) -> None:
@@ -477,6 +639,8 @@ class BashState:
477
639
  target=_bg_expect_thread_handler,
478
640
  )
479
641
  self._bg_expect_thread.start()
642
+ for k, v in self.background_shells.items():
643
+ v.run_bg_expect_thread()
480
644
 
481
645
  def close_bg_expect_thread(self) -> None:
482
646
  if self._bg_expect_thread:
@@ -484,9 +648,10 @@ class BashState:
484
648
  self._bg_expect_thread.join()
485
649
  self._bg_expect_thread = None
486
650
  self._bg_expect_thread_stop_event = threading.Event()
651
+ for k, v in self.background_shells.items():
652
+ v.close_bg_expect_thread()
487
653
 
488
654
  def cleanup(self) -> None:
489
- cleanup_all_screens_with_name(self._shell_id, self.console)
490
655
  self.close_bg_expect_thread()
491
656
  self._shell.close(True)
492
657
 
@@ -512,45 +677,15 @@ class BashState:
512
677
  def write_if_empty_mode(self) -> WriteIfEmptyMode:
513
678
  return self._write_if_empty_mode
514
679
 
515
- def ensure_env_and_bg_jobs(self) -> Optional[int]:
516
- quick_timeout = 0.2 if not self.over_screen else 1
517
- # First reset the prompt in case venv was sourced or other reasons.
518
- self.sendline(PROMPT_STATEMENT, set_as_command=PROMPT_STATEMENT)
519
- self.expect(PROMPT_CONST, timeout=quick_timeout)
520
- # Reset echo also if it was enabled
521
- command = "jobs | wc -l"
522
- self.sendline(command, set_as_command=command)
523
- before = ""
524
- counts = 0
525
- while not _is_int(before): # Consume all previous output
526
- try:
527
- self.expect(PROMPT_CONST, timeout=quick_timeout)
528
- except pexpect.TIMEOUT:
529
- self.console.print(f"Couldn't get exit code, before: {before}")
530
- raise
531
-
532
- before_val = self.before
533
- if not isinstance(before_val, str):
534
- before_val = str(before_val)
535
- assert isinstance(before_val, str)
536
- before_lines = render_terminal_output(before_val)
537
- before = "\n".join(before_lines).replace(command, "").strip()
538
- counts += 1
539
- if counts > 100:
540
- raise ValueError(
541
- "Error in understanding shell output. This shouldn't happen, likely shell is in a bad state, please reset it"
542
- )
543
- try:
544
- return int(before)
545
- except ValueError:
546
- raise ValueError(f"Malformed output: {before}")
547
-
548
680
  def _init_shell(self) -> None:
549
681
  self._state: Literal["repl"] | datetime.datetime = "repl"
550
- self._last_command = ""
682
+ self.last_command = ""
551
683
  # Ensure self._cwd exists
552
684
  os.makedirs(self._cwd, exist_ok=True)
553
685
 
686
+ # Ensure WCGW block exists in rc file
687
+ ensure_wcgw_block_in_rc_file(self._shell_path, self.console)
688
+
554
689
  # Clean up orphaned WCGW screen sessions
555
690
  if check_if_screen_command_available():
556
691
  cleanup_orphaned_wcgw_screens(self.console)
@@ -561,6 +696,7 @@ class BashState:
561
696
  self._cwd,
562
697
  self.console,
563
698
  over_screen=self._use_screen,
699
+ shell_path=self._shell_path,
564
700
  )
565
701
  self.over_screen = self._use_screen
566
702
  except Exception as e:
@@ -573,14 +709,11 @@ class BashState:
573
709
  self._cwd,
574
710
  self.console,
575
711
  over_screen=False,
712
+ shell_path=self._shell_path,
576
713
  )
577
714
  self.over_screen = False
578
715
 
579
716
  self._pending_output = ""
580
- try:
581
- self.ensure_env_and_bg_jobs()
582
- except ValueError as e:
583
- self.console.log("Error while running _ensure_env_and_bg_jobs" + str(e))
584
717
 
585
718
  self.run_bg_expect_thread()
586
719
 
@@ -592,7 +725,40 @@ class BashState:
592
725
  def set_repl(self) -> None:
593
726
  self._state = "repl"
594
727
  self._pending_output = ""
595
- self._last_command = ""
728
+ self.last_command = ""
729
+
730
+ def clear_to_run(self) -> None:
731
+ """Check if prompt is clear to enter new command otherwise send ctrl c"""
732
+ # First clear
733
+ starttime = time.time()
734
+ self.close_bg_expect_thread()
735
+ try:
736
+ while True:
737
+ try:
738
+ output = self.expect(
739
+ [PROMPT_CONST, pexpect.TIMEOUT], 0.1, flush_rem_prompt=False
740
+ )
741
+ if output == 1:
742
+ break
743
+ except pexpect.TIMEOUT:
744
+ break
745
+ if time.time() - starttime > CONFIG.timeout:
746
+ self.console.log(
747
+ f"Error: could not clear output in {CONFIG.timeout} seconds. Resetting"
748
+ )
749
+ self.reset_shell()
750
+ return
751
+ output = self.expect([" ", pexpect.TIMEOUT], 0.1)
752
+ if output != 1:
753
+ # Then we got something new send ctrl-c
754
+ self.send("\x03", None)
755
+
756
+ output = self.expect([PROMPT_CONST, pexpect.TIMEOUT], CONFIG.timeout)
757
+ if output == 1:
758
+ self.console.log("Error: could not clear output. Resetting")
759
+ self.reset_shell()
760
+ finally:
761
+ self.run_bg_expect_thread()
596
762
 
597
763
  @property
598
764
  def state(self) -> BASH_CLF_OUTPUT:
@@ -614,22 +780,9 @@ class BashState:
614
780
  self._workspace_root = workspace_root
615
781
 
616
782
  @property
617
- def prompt(self) -> str:
783
+ def prompt(self) -> re.Pattern[str]:
618
784
  return PROMPT_CONST
619
785
 
620
- def update_cwd(self) -> str:
621
- self.sendline("pwd", set_as_command="pwd")
622
- self.expect(PROMPT_CONST, timeout=0.2)
623
- before_val = self.before
624
- if not isinstance(before_val, str):
625
- before_val = str(before_val)
626
- before_lines = render_terminal_output(before_val)
627
- current_dir = "\n".join(before_lines).strip()
628
- if current_dir.startswith("pwd"):
629
- current_dir = current_dir[3:].strip()
630
- self._cwd = current_dir
631
- return current_dir
632
-
633
786
  def reset_shell(self) -> None:
634
787
  self.cleanup()
635
788
  self._init_shell()
@@ -964,19 +1117,21 @@ def _incremental_text(text: str, last_pending_output: str) -> str:
964
1117
  return rstrip(rendered)
965
1118
 
966
1119
 
967
- def get_status(bash_state: BashState) -> str:
1120
+ def get_status(bash_state: BashState, is_bg: bool) -> str:
968
1121
  status = "\n\n---\n\n"
1122
+ if is_bg:
1123
+ status += f"bg_command_id = {bash_state.current_thread_id}\n"
969
1124
  if bash_state.state == "pending":
970
1125
  status += "status = still running\n"
971
1126
  status += "running for = " + bash_state.get_pending_for() + "\n"
972
1127
  status += "cwd = " + bash_state.cwd + "\n"
973
1128
  else:
974
- bg_jobs = bash_state.ensure_env_and_bg_jobs()
975
1129
  bg_desc = ""
976
- if bg_jobs and bg_jobs > 0:
977
- bg_desc = f"; {bg_jobs} background jobs running"
978
1130
  status += "status = process exited" + bg_desc + "\n"
979
- status += "cwd = " + bash_state.update_cwd() + "\n"
1131
+ status += "cwd = " + bash_state.cwd + "\n"
1132
+
1133
+ if not is_bg:
1134
+ status += "This is the main shell. " + get_bg_running_commandsinfo(bash_state)
980
1135
 
981
1136
  return status.rstrip()
982
1137
 
@@ -1022,9 +1177,47 @@ def execute_bash(
1022
1177
 
1023
1178
  finally:
1024
1179
  bash_state.run_bg_expect_thread()
1180
+ if bash_state.over_screen:
1181
+ thread = threading.Thread(
1182
+ target=cleanup_orphaned_wcgw_screens,
1183
+ args=(bash_state.console,),
1184
+ daemon=True,
1185
+ )
1186
+ thread.start()
1025
1187
  return output, cost
1026
1188
 
1027
1189
 
1190
+ def assert_single_statement(command: str) -> None:
1191
+ # Check for multiple statements using the bash statement parser
1192
+ if "\n" in command:
1193
+ try:
1194
+ parser = BashStatementParser()
1195
+ statements = parser.parse_string(command)
1196
+ except Exception:
1197
+ # Fall back to simple newline check if something goes wrong
1198
+ raise ValueError(
1199
+ "Command should not contain newline character in middle. Run only one command at a time."
1200
+ )
1201
+ if len(statements) > 1:
1202
+ raise ValueError(
1203
+ "Error: Command contains multiple statements. Please run only one bash statement at a time."
1204
+ )
1205
+
1206
+
1207
+ def get_bg_running_commandsinfo(bash_state: BashState) -> str:
1208
+ msg = ""
1209
+ running = []
1210
+ for id_, state in bash_state.background_shells.items():
1211
+ running.append(f"Command: {state.last_command}, bg_command_id: {id_}")
1212
+ if running:
1213
+ msg = (
1214
+ "Following background commands are attached:\n" + "\n".join(running) + "\n"
1215
+ )
1216
+ else:
1217
+ msg = "No command running in background.\n"
1218
+ return msg
1219
+
1220
+
1028
1221
  def _execute_bash(
1029
1222
  bash_state: BashState,
1030
1223
  enc: EncoderDecoder[int],
@@ -1035,6 +1228,21 @@ def _execute_bash(
1035
1228
  try:
1036
1229
  is_interrupt = False
1037
1230
  command_data = bash_arg.action_json
1231
+ is_bg = False
1232
+ og_bash_state = bash_state
1233
+
1234
+ if not isinstance(command_data, Command) and command_data.bg_command_id:
1235
+ if command_data.bg_command_id not in bash_state.background_shells:
1236
+ error = f"No shell found running with command id {command_data.bg_command_id}.\n"
1237
+ if bash_state.background_shells:
1238
+ error += get_bg_running_commandsinfo(bash_state)
1239
+ if bash_state.state == "pending":
1240
+ error += f"On the main thread a command is already running ({bash_state.last_command})"
1241
+ else:
1242
+ error += "On the main thread no command is running."
1243
+ raise Exception(error)
1244
+ bash_state = bash_state.background_shells[command_data.bg_command_id]
1245
+ is_bg = True
1038
1246
 
1039
1247
  if isinstance(command_data, Command):
1040
1248
  if bash_state.bash_command_mode.allowed_commands == "none":
@@ -1047,26 +1255,16 @@ def _execute_bash(
1047
1255
 
1048
1256
  command = command_data.command.strip()
1049
1257
 
1050
- # Check for multiple statements using the bash statement parser
1051
- if "\n" in command:
1052
- try:
1053
- parser = BashStatementParser()
1054
- statements = parser.parse_string(command)
1055
- if len(statements) > 1:
1056
- return (
1057
- "Error: Command contains multiple statements. Please run only one bash statement at a time.",
1058
- 0.0,
1059
- )
1060
- except Exception:
1061
- # Fall back to simple newline check if something goes wrong
1062
- raise ValueError(
1063
- "Command should not contain newline character in middle. Run only one command at a time."
1064
- )
1258
+ assert_single_statement(command)
1065
1259
 
1066
- for i in range(0, len(command), 128):
1067
- bash_state.send(command[i : i + 128], set_as_command=None)
1068
- bash_state.send(bash_state.linesep, set_as_command=command)
1260
+ if command_data.is_background:
1261
+ bash_state = bash_state.start_new_bg_shell(bash_state.cwd)
1262
+ is_bg = True
1069
1263
 
1264
+ bash_state.clear_to_run()
1265
+ for i in range(0, len(command), 64):
1266
+ bash_state.send(command[i : i + 64], set_as_command=None)
1267
+ bash_state.send(bash_state.linesep, set_as_command=command)
1070
1268
  elif isinstance(command_data, StatusCheck):
1071
1269
  bash_state.console.print("Checking status")
1072
1270
  if bash_state.state != "pending":
@@ -1100,7 +1298,7 @@ def _execute_bash(
1100
1298
  elif char == "Key-right":
1101
1299
  bash_state.send("\033[C", set_as_command=None)
1102
1300
  elif char == "Enter":
1103
- bash_state.send("\n", set_as_command=None)
1301
+ bash_state.send("\x0d", set_as_command=None)
1104
1302
  elif char == "Ctrl-c":
1105
1303
  bash_state.sendintr()
1106
1304
  is_interrupt = True
@@ -1187,8 +1385,14 @@ You may want to try Ctrl-c again or program specific exit interactive commands.
1187
1385
  """
1188
1386
  )
1189
1387
 
1190
- exit_status = get_status(bash_state)
1388
+ exit_status = get_status(bash_state, is_bg)
1191
1389
  incremental_text += exit_status
1390
+ if is_bg and bash_state.state == "repl":
1391
+ try:
1392
+ bash_state.cleanup()
1393
+ og_bash_state.background_shells.pop(bash_state.current_thread_id)
1394
+ except Exception as e:
1395
+ bash_state.console.log(f"error while cleaning up {e}")
1192
1396
 
1193
1397
  return incremental_text, 0
1194
1398
 
@@ -1202,8 +1406,14 @@ You may want to try Ctrl-c again or program specific exit interactive commands.
1202
1406
  output = "(...truncated)\n" + enc.decoder(tokens[-(max_tokens - 1) :])
1203
1407
 
1204
1408
  try:
1205
- exit_status = get_status(bash_state)
1409
+ exit_status = get_status(bash_state, is_bg)
1206
1410
  output += exit_status
1411
+ if is_bg and bash_state.state == "repl":
1412
+ try:
1413
+ bash_state.cleanup()
1414
+ og_bash_state.background_shells.pop(bash_state.current_thread_id)
1415
+ except Exception as e:
1416
+ bash_state.console.log(f"error while cleaning up {e}")
1207
1417
  except ValueError:
1208
1418
  bash_state.console.print(output)
1209
1419
  bash_state.console.print(traceback.format_exc())
@@ -59,13 +59,15 @@ def call_hello_renamed():
59
59
  ```
60
60
 
61
61
  # *SEARCH/REPLACE block* Rules:
62
+ Every "<<<<<<< SEARCH" section must *EXACTLY MATCH* the existing file content, character for character, including all comments, docstrings, whitespaces, etc.
63
+
64
+ Including multiple unique *SEARCH/REPLACE* blocks if needed.
65
+ Include enough and only enough lines in each SEARCH section to uniquely match each set of lines that need to change.
66
+
67
+ Keep *SEARCH/REPLACE* blocks concise.
68
+ Break large *SEARCH/REPLACE* blocks into a series of smaller blocks that each change a small portion of the file.
69
+ Include just the changing lines, and a few surrounding lines (0-3 lines) if needed for uniqueness.
70
+ Other than for uniqueness, avoid including those lines which do not change in search (and replace) blocks. Target 0-3 non trivial extra lines per block.
71
+
72
+ Preserve leading spaces and indentations in both SEARCH and REPLACE blocks.
62
73
 
63
- - Every "SEARCH" section must *EXACTLY MATCH* the existing file content, character for character, including all comments, docstrings, whitespaces, etc.
64
- - Use multiple search/replace blocks in a single FileWriteOrEdit tool call to edit in a single file in multiple places from top to bottom (separate calls are slower).
65
- - Including multiple unique *SEARCH/REPLACE* blocks if needed.
66
- - Include enough and only enough lines in each SEARCH section to uniquely match each set of lines that need to change.
67
- - Keep *SEARCH/REPLACE* blocks concise.
68
- - Break large *SEARCH/REPLACE* blocks into a series of smaller blocks that each change a small portion of the file.
69
- - Include just the changing lines, and a few surrounding lines (0-3 lines) if needed for uniqueness.
70
- - Other than for uniqueness, avoid including those lines which do not change in search (and replace) blocks. Target 0-3 non trivial extra lines per block.
71
- - Preserve leading spaces and indentations in both SEARCH and REPLACE blocks.
@@ -15,6 +15,9 @@ def app(
15
15
  version: bool = typer.Option(
16
16
  False, "--version", "-v", help="Show version and exit"
17
17
  ),
18
+ shell: str = typer.Option(
19
+ "", "--shell", help="Path to shell executable (defaults to $SHELL or /bin/bash)"
20
+ ),
18
21
  ) -> None:
19
22
  """Main entry point for the package."""
20
23
  if version:
@@ -22,7 +25,7 @@ def app(
22
25
  print(f"wcgw version: {version_}")
23
26
  raise typer.Exit()
24
27
 
25
- asyncio.run(server.main())
28
+ asyncio.run(server.main(shell))
26
29
 
27
30
 
28
31
  # Optionally expose other important items at package level
@@ -151,7 +151,7 @@ BASH_STATE = None
151
151
  CUSTOM_INSTRUCTIONS = None
152
152
 
153
153
 
154
- async def main() -> None:
154
+ async def main(shell_path: str = "") -> None:
155
155
  global BASH_STATE, CUSTOM_INSTRUCTIONS
156
156
  CONFIG.update(3, 55, 5)
157
157
  version = str(importlib.metadata.version("wcgw"))
@@ -164,7 +164,7 @@ async def main() -> None:
164
164
  starting_dir = os.path.join(tmp_dir, "claude_playground")
165
165
 
166
166
  with BashState(
167
- Console(), starting_dir, None, None, None, None, True, None
167
+ Console(), starting_dir, None, None, None, None, True, None, None, shell_path or None
168
168
  ) as BASH_STATE:
169
169
  BASH_STATE.console.log("wcgw version: " + version)
170
170
  # Run the server using stdin/stdout streams
wcgw/client/modes.py CHANGED
@@ -86,7 +86,7 @@ You are now running in "code_writer" mode.
86
86
 
87
87
  run_command_common = """
88
88
  - Do not use Ctrl-c interrupt commands without asking the user, because often the programs don't show any update but they still are running.
89
- - Do not use echo to write multi-line files, always use FileWriteOrEdit tool to update a code.
89
+ - Do not use echo/cat to write any file, always use FileWriteOrEdit tool to create/update files.
90
90
  - Do not provide code snippets unless asked by the user, instead directly add/edit the code.
91
91
  - You should use the provided bash execution, reading and writing file tools to complete objective.
92
92
  - Do not use artifacts if you have access to the repository and not asked by the user to provide artifacts/snippets. Directly create/update using wcgw tools.
@@ -120,7 +120,8 @@ WCGW_PROMPT = """
120
120
  - Do not install new tools/packages before ensuring no such tools/package or an alternative already exists.
121
121
  - Do not use artifacts if you have access to the repository and not asked by the user to provide artifacts/snippets. Directly create/update using wcgw tools
122
122
  - Do not use Ctrl-c or interrupt commands without asking the user, because often the programs don't show any update but they still are running.
123
- - Do not use echo to write multi-line files, always use FileWriteOrEdit tool to update a code.
123
+ - Do not use echo/cat to write any file, always use FileWriteOrEdit tool to create/update files.
124
+ - You can share task summary directly without creating any file.
124
125
  - Provide as many file paths as you need in ReadFiles in one go.
125
126
 
126
127
  Additional instructions:
@@ -42,12 +42,14 @@ TOOL_PROMPTS = [
42
42
  - Status of the command and the current working directory will always be returned at the end.
43
43
  - The first or the last line might be `(...truncated)` if the output is too long.
44
44
  - Always run `pwd` if you get any file or directory not found error to make sure you're not lost.
45
- - Run long running commands in background using screen instead of "&".
46
- - Do not use 'cat' to read files, use ReadFiles tool instead
45
+ - Do not run bg commands using "&", instead use this tool.
46
+ - You must not use echo/cat to read/write files, use ReadFiles/FileWriteOrEdit
47
47
  - In order to check status of previous command, use `status_check` with empty command argument.
48
48
  - Only command is allowed to run at a time. You need to wait for any previous command to finish before running a new one.
49
49
  - 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.
50
50
  - Do not send Ctrl-c before checking for status till 10 minutes or whatever is appropriate for the program to finish.
51
+ - Only run long running commands in background. Each background command is run in a new non-reusable shell.
52
+ - On running a bg command you'll get a bg command id that you should use to get status or interact.
51
53
  """,
52
54
  annotations=ToolAnnotations(destructiveHint=True, openWorldHint=True),
53
55
  ),
wcgw/client/tools.py CHANGED
@@ -392,7 +392,7 @@ def reset_wcgw(
392
392
  f"Reset successful with mode change to {mode_name}.\n"
393
393
  + mode_prompt
394
394
  + "\n"
395
- + get_status(context.bash_state)
395
+ + get_status(context.bash_state, is_bg=False)
396
396
  )
397
397
  else:
398
398
  # Regular reset without mode change - keep same mode but update directory
@@ -412,7 +412,7 @@ def reset_wcgw(
412
412
  starting_directory,
413
413
  thread_id,
414
414
  )
415
- return "Reset successful" + get_status(context.bash_state)
415
+ return "Reset successful" + get_status(context.bash_state, is_bg=False)
416
416
 
417
417
 
418
418
  T = TypeVar("T")
@@ -1374,24 +1374,23 @@ if __name__ == "__main__":
1374
1374
  print(
1375
1375
  get_tool_output(
1376
1376
  Context(BASH_STATE, BASH_STATE.console),
1377
- ReadFiles(
1378
- file_paths=["/Users/arusia/repos/wcgw/src/wcgw/client/tools.py"],
1377
+ BashCommand(
1378
+ action_json=Command(command="source .venv/bin/activate"),
1379
+ thread_id=BASH_STATE.current_thread_id,
1379
1380
  ),
1380
1381
  default_enc,
1381
1382
  0,
1382
1383
  lambda x, y: ("", 0),
1383
1384
  24000, # coding_max_tokens
1384
1385
  8000, # noncoding_max_tokens
1385
- )[0][0]
1386
+ )
1386
1387
  )
1387
1388
 
1388
1389
  print(
1389
1390
  get_tool_output(
1390
1391
  Context(BASH_STATE, BASH_STATE.console),
1391
- FileWriteOrEdit(
1392
- file_path="/Users/arusia/repos/wcgw/src/wcgw/client/tools.py",
1393
- text_or_search_replace_blocks="""test""",
1394
- percentage_to_change=100,
1392
+ BashCommand(
1393
+ action_json=Command(command="pwd"),
1395
1394
  thread_id=BASH_STATE.current_thread_id,
1396
1395
  ),
1397
1396
  default_enc,
@@ -1399,5 +1398,35 @@ if __name__ == "__main__":
1399
1398
  lambda x, y: ("", 0),
1400
1399
  24000, # coding_max_tokens
1401
1400
  8000, # noncoding_max_tokens
1402
- )[0][0]
1401
+ )
1402
+ )
1403
+
1404
+ print(
1405
+ get_tool_output(
1406
+ Context(BASH_STATE, BASH_STATE.console),
1407
+ BashCommand(
1408
+ action_json=Command(command="take src"),
1409
+ thread_id=BASH_STATE.current_thread_id,
1410
+ ),
1411
+ default_enc,
1412
+ 0,
1413
+ lambda x, y: ("", 0),
1414
+ 24000, # coding_max_tokens
1415
+ 8000, # noncoding_max_tokens
1416
+ )
1417
+ )
1418
+
1419
+ print(
1420
+ get_tool_output(
1421
+ Context(BASH_STATE, BASH_STATE.console),
1422
+ BashCommand(
1423
+ action_json=Command(command="pwd"),
1424
+ thread_id=BASH_STATE.current_thread_id,
1425
+ ),
1426
+ default_enc,
1427
+ 0,
1428
+ lambda x, y: ("", 0),
1429
+ 24000, # coding_max_tokens
1430
+ 8000, # noncoding_max_tokens
1431
+ )
1403
1432
  )
wcgw/types_.py CHANGED
@@ -86,16 +86,19 @@ class Initialize(BaseModel):
86
86
  class Command(BaseModel):
87
87
  command: str
88
88
  type: Literal["command"] = "command"
89
+ is_background: bool = False
89
90
 
90
91
 
91
92
  class StatusCheck(BaseModel):
92
93
  status_check: Literal[True] = True
93
94
  type: Literal["status_check"] = "status_check"
95
+ bg_command_id: str | None = None
94
96
 
95
97
 
96
98
  class SendText(BaseModel):
97
99
  send_text: str
98
100
  type: Literal["send_text"] = "send_text"
101
+ bg_command_id: str | None = None
99
102
 
100
103
 
101
104
  Specials = Literal[
@@ -106,11 +109,13 @@ Specials = Literal[
106
109
  class SendSpecials(BaseModel):
107
110
  send_specials: Sequence[Specials]
108
111
  type: Literal["send_specials"] = "send_specials"
112
+ bg_command_id: str | None = None
109
113
 
110
114
 
111
115
  class SendAscii(BaseModel):
112
116
  send_ascii: Sequence[int]
113
117
  type: Literal["send_ascii"] = "send_ascii"
118
+ bg_command_id: str | None = None
114
119
 
115
120
 
116
121
  class ActionJsonSchema(BaseModel):
@@ -132,6 +137,14 @@ class ActionJsonSchema(BaseModel):
132
137
  send_ascii: Optional[Sequence[int]] = Field(
133
138
  default=None, description='Set only if type="send_ascii"'
134
139
  )
140
+ is_background: bool = Field(
141
+ default=False,
142
+ description='Set only if type="command" and running the command in background',
143
+ )
144
+ bg_command_id: str | None = Field(
145
+ default=None,
146
+ description='Set only if type!="command" and doing action on a running background command',
147
+ )
135
148
 
136
149
 
137
150
  class BashCommandOverride(BaseModel):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wcgw
3
- Version: 5.4.5
3
+ Version: 5.5.1
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>
@@ -49,6 +49,8 @@ wcgw is an MCP server with tightly integrated shell and code editing tools.
49
49
 
50
50
  ## Updates
51
51
 
52
+ - [6 Oct 2025] Model can now run multiple commands in background. ZSH is now a supported shell. Multiplexing improvements.
53
+
52
54
  - [27 Apr 2025] Removed support for GPTs over relay server. Only MCP server is supported in version >= 5.
53
55
 
54
56
  - [24 Mar 2025] Improved writing and editing experience for sonnet 3.7, CLAUDE.md gets loaded automatically.
@@ -77,16 +79,16 @@ wcgw is an MCP server with tightly integrated shell and code editing tools.
77
79
  - File edit has spacing tolerant matching, with warning on issues like indentation mismatch. If there's no match, the closest match is returned to the AI to fix its mistakes.
78
80
  - Using Aider-like search and replace, which has better performance than tool call based search and replace.
79
81
  - ⚡ **Shell optimizations**:
80
- - Only one command is allowed to be run at a time, simplifying management and avoiding rogue processes. There's only single shell instance at any point of time.
81
82
  - Current working directory is always returned after any shell command to prevent AI from getting lost.
82
83
  - Command polling exits after a quick timeout to avoid slow feedback. However, status checking has wait tolerance based on fresh output streaming from a command. Both of these approach combined provides a good shell interaction experience.
84
+ - Supports multiple concurrent background commands alongside the main interactive shell.
83
85
  - ⚡ **Saving repo context in a single file**: Task checkpointing using "ContextSave" tool saves detailed context in a single file. Tasks can later be resumed in a new chat asking "Resume `task id`". The saved file can be used to do other kinds of knowledge transfer, such as taking help from another AI.
84
86
  - ⚡ **Easily switch between various modes**:
85
87
  - Ask it to run in 'architect' mode for planning. Inspired by adier's architect mode, work with Claude to come up with a plan first. Leads to better accuracy and prevents premature file editing.
86
88
  - Ask it to run in 'code-writer' mode for code editing and project building. You can provide specific paths with wild card support to prevent other files getting edited.
87
89
  - By default it runs in 'wcgw' mode that has no restrictions and full authorisation.
88
90
  - More details in [Modes section](#modes)
89
- - ⚡ **Runs in multiplex terminal** Run `screen -x` to attach to the terminal that the AI runs commands on. See history or interrupt process or interact with the same terminal that AI uses.
91
+ - ⚡ **Runs in multiplex terminal** Use [vscode extension](https://marketplace.visualstudio.com/items?itemName=AmanRusia.wcgw) or run `screen -x` to attach to the terminal that the AI runs commands on. See history or interrupt process or interact with the same terminal that AI uses.
90
92
  - ⚡ **Automatically load CLAUDE.md/AGENTS.md** Loads "CLAUDE.md" or "AGENTS.md" file in project root and sends as instructions during initialisation. Instructions in a global "~/.wcgw/CLAUDE.md" or "~/.wcgw/AGENTS.md" file are loaded and added along with project specific CLAUDE.md. The file name is case sensitive. CLAUDE.md is attached if it's present otherwise AGENTS.md is attached.
91
93
 
92
94
  ## Top use cases examples
@@ -118,8 +120,8 @@ Then create or update `claude_desktop_config.json` (~/Library/Application Suppor
118
120
  {
119
121
  "mcpServers": {
120
122
  "wcgw": {
121
- "command": "uv",
122
- "args": ["tool", "run", "--python", "3.12", "wcgw"]
123
+ "command": "uvx",
124
+ "args": ["wcgw@latest"]
123
125
  }
124
126
  }
125
127
  }
@@ -127,6 +129,21 @@ Then create or update `claude_desktop_config.json` (~/Library/Application Suppor
127
129
 
128
130
  Then restart claude app.
129
131
 
132
+ **Optional: Force a specific shell**
133
+
134
+ To use a specific shell (bash or zsh), add the `--shell` argument:
135
+
136
+ ```json
137
+ {
138
+ "mcpServers": {
139
+ "wcgw": {
140
+ "command": "uvx",
141
+ "args": ["wcgw@latest", "--shell", "/bin/bash"]
142
+ }
143
+ }
144
+ }
145
+ ```
146
+
130
147
  _If there's an error in setting up_
131
148
 
132
149
  - If there's an error like "uv ENOENT", make sure `uv` is installed. Then run 'which uv' in the terminal, and use its output in place of "uv" in the configuration.
@@ -148,7 +165,7 @@ Then add or update the claude config file `%APPDATA%\Claude\claude_desktop_confi
148
165
  "mcpServers": {
149
166
  "wcgw": {
150
167
  "command": "wsl.exe",
151
- "args": ["uv", "tool", "run", "--python", "3.12", "wcgw"]
168
+ "args": ["uvx", "wcgw@latest"]
152
169
  }
153
170
  }
154
171
  }
@@ -210,6 +227,9 @@ Note: in code-writer mode either all commands are allowed or none are allowed fo
210
227
 
211
228
  #### Attach to the working terminal to investigate
212
229
 
230
+ NEW: the [vscode extension](https://marketplace.visualstudio.com/items?itemName=AmanRusia.wcgw) now automatically attach the running terminal
231
+ if workspace path matches.
232
+
213
233
  If you've `screen` command installed, wcgw runs on a screen instance automatically. If you've started wcgw mcp server, you can list the screen sessions:
214
234
 
215
235
  `screen -ls`
@@ -220,7 +240,7 @@ You can then attach to the session using `screen -x 93358.wcgw.235521`
220
240
 
221
241
  You may interrupt any running command safely.
222
242
 
223
- You can interact with the terminal but beware that the AI might be running in parallel and it may conflict with what you're doing. It's recommended to keep your interactions to minimum.
243
+ You can interact with the terminal safely, for example for entering passwords, or entering some text. (Warning: If you run a new command, any new LLM command will interrupt it.)
224
244
 
225
245
  You shouldn't exit the session using `exit `or Ctrl-d, instead you should use `ctrl+a+d` to safely detach without destroying the screen session.
226
246
 
@@ -1,15 +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=dvFpZPywqczpX7Sv6-e-r0WZxOZ0Eu10DlpSLtl9zJs,9607
3
+ wcgw/types_.py,sha256=sSgmWC8ciQ7b6yarVzmYGad81h7jlPK_XyftT28HerA,10104
4
4
  wcgw/client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  wcgw/client/common.py,sha256=OCH7Tx64jojz3M3iONUrGMadE07W21DiZs5sOxWX1Qc,1456
6
- wcgw/client/diff-instructions.txt,sha256=wCmB-Vj8HNSTvxjZIH0fX_J7v9jbsJOJeNj46Un1AJ0,1858
6
+ wcgw/client/diff-instructions.txt,sha256=EU9WmJMbsmAj07CyuGWL6zd4eVQ8Ie3HZzd0k0WugX0,1687
7
7
  wcgw/client/memory.py,sha256=U2Nw2si3Zg7n_RhNAuaYcmrrDtZ_Mooi-kfAOKflT-I,3079
8
- wcgw/client/modes.py,sha256=roH6SPBokJMr5IzAlccdI-vJyvyS5vqSMMyth7TE86A,10315
8
+ wcgw/client/modes.py,sha256=3eS5ttttltKAZB41h7-XwtdjMVYrUtd6xMush7k_vvw,10388
9
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=J_Ofbs7jBQXLfS4x9d7gZtTxNiaVbAmc0puCiyum9Ls,50038
12
- wcgw/client/bash_state/bash_state.py,sha256=bd5RtLbaRzCtrmeDTl3JKZwzmIR-8iAMQpl7Fqyt56M,41918
10
+ wcgw/client/tool_prompts.py,sha256=zi4L98DwNePr5Yhwx7wl4jAgmlMjILPnKTLZGf0L4SA,4971
11
+ wcgw/client/tools.py,sha256=YTjtAPSR8divbfDDOII9-iZG6B1ucPemdZ5pef6lEyQ,50904
12
+ wcgw/client/bash_state/bash_state.py,sha256=tVVaUwSw2st9vPEkGQPbA0Qtb4gbgjCmP1TGgSQ_hU8,48872
13
13
  wcgw/client/bash_state/parser/__init__.py,sha256=AnlNSmoQTSoqqlLOLX4P1uXfzc5VGeCGJsGgtisq2zE,207
14
14
  wcgw/client/bash_state/parser/bash_statement_parser.py,sha256=9a8vPO1r3_tXmaAcubTQ5UY-NseWlalgm8LZA17LXuY,6058
15
15
  wcgw/client/encoder/__init__.py,sha256=Y-8f43I6gMssUCWpX5rLYiAFv3D-JPRs4uNEejPlke8,1514
@@ -17,8 +17,8 @@ wcgw/client/file_ops/diff_edit.py,sha256=AwLq6-pY7czv1y-JA5O2Q4rgbvn82YmSL9jD8XB
17
17
  wcgw/client/file_ops/extensions.py,sha256=CmfD7ON6SY24Prh2tRZdV9KbhuOrWqqk8qL1VtshzB8,3608
18
18
  wcgw/client/file_ops/search_replace.py,sha256=5LFg-_U_ijnNrkYei4SWCPGKPGgDzJs49EDsIBzLmuY,6822
19
19
  wcgw/client/mcp_server/Readme.md,sha256=2Z88jj1mf9daYGW1CWaldcJ0moy8owDumhR2glBY3A8,109
20
- wcgw/client/mcp_server/__init__.py,sha256=rSNET0SVjUTDn2HAfREisTXTBs89TVsWegDfYFMvy5w,621
21
- wcgw/client/mcp_server/server.py,sha256=RgsMDmNuYyg4VT1KorcLzh1Xfv49QASi0-FTLz_tlIo,5525
20
+ wcgw/client/mcp_server/__init__.py,sha256=be5D_r2HNhWAi8uX2qz28z1hSjh_rfUDqqeBVyEyzkk,753
21
+ wcgw/client/mcp_server/server.py,sha256=qREy9CQckvpLYx1VaujDljK6eTQSLA53C8KuK8dkU3U,5571
22
22
  wcgw/client/repo_ops/display_tree.py,sha256=g282qCKLCwo8O9NHUBnkG_NkIusroVzz3NZi8VIcmAI,4066
23
23
  wcgw/client/repo_ops/file_stats.py,sha256=AUA0Br7zFRpylWFYZPGMeGPJy3nWp9e2haKi34JptHE,4887
24
24
  wcgw/client/repo_ops/path_prob.py,sha256=SWf0CDn37rtlsYRQ51ufSxay-heaQoVIhr1alB9tZ4M,2144
@@ -27,12 +27,12 @@ wcgw/client/repo_ops/paths_tokens.model,sha256=jiwwE4ae8ADKuTZISutXuM5Wfyc_FBmN5
27
27
  wcgw/client/repo_ops/repo_context.py,sha256=e_w-1VfxWQiZT3r66N13nlmPt6AGm0uvG3A7aYSgaCI,9632
28
28
  wcgw_cli/__init__.py,sha256=TNxXsTPgb52OhakIda9wTRh91cqoBqgQRx5TxjzQQFU,21
29
29
  wcgw_cli/__main__.py,sha256=wcCrL4PjG51r5wVKqJhcoJPTLfHW0wNbD31DrUN0MWI,28
30
- wcgw_cli/anthropic_client.py,sha256=8bjDY59-aioyTJgpB-NBHZNhZaq6rqcTJcOf81kzCyA,19886
30
+ wcgw_cli/anthropic_client.py,sha256=Si3u3K3OWe0gNBmwh892QG34yRirUQkhWKXHypg4tZM,19888
31
31
  wcgw_cli/cli.py,sha256=-7FBe_lahKyUOhf65iurTA1M1gXXXAiT0OVKQVcZKKo,948
32
- wcgw_cli/openai_client.py,sha256=GOqoSFazTV-cFjpdZGPM0DIwec8Up2TEcKUbsN40AGY,15990
32
+ wcgw_cli/openai_client.py,sha256=QBRI_LIGI04iBSHZIXv5cGIFaRrPXZLwRgqnDf34uWc,15992
33
33
  wcgw_cli/openai_utils.py,sha256=xGOb3W5ALrIozV7oszfGYztpj0FnXdD7jAxm5lEIVKY,2439
34
- wcgw-5.4.5.dist-info/METADATA,sha256=xyfj7lO9bVE6gJ8EKwkCHMwjkkZMQtRmojieBYyQ6y4,15888
35
- wcgw-5.4.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
36
- wcgw-5.4.5.dist-info/entry_points.txt,sha256=UnjK-MAH4Qssh0tGJDMeij1oi-oRKokItkknP_BwShE,94
37
- wcgw-5.4.5.dist-info/licenses/LICENSE,sha256=BvY8xqjOfc3X2qZpGpX3MZEmF-4Dp0LqgKBbT6L_8oI,11142
38
- wcgw-5.4.5.dist-info/RECORD,,
34
+ wcgw-5.5.1.dist-info/METADATA,sha256=j-jNe7lVsoSLGlT_xsh1Sqq2G4j8GbePY7FmOvsSeoI,16385
35
+ wcgw-5.5.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
36
+ wcgw-5.5.1.dist-info/entry_points.txt,sha256=UnjK-MAH4Qssh0tGJDMeij1oi-oRKokItkknP_BwShE,94
37
+ wcgw-5.5.1.dist-info/licenses/LICENSE,sha256=BvY8xqjOfc3X2qZpGpX3MZEmF-4Dp0LqgKBbT6L_8oI,11142
38
+ wcgw-5.5.1.dist-info/RECORD,,
@@ -227,7 +227,7 @@ def loop(
227
227
  24000, # coding_max_tokens
228
228
  8000, # noncoding_max_tokens
229
229
  mode="wcgw",
230
- chat_id="",
230
+ thread_id="",
231
231
  )
232
232
 
233
233
  if history:
wcgw_cli/openai_client.py CHANGED
@@ -191,7 +191,7 @@ def loop(
191
191
  24000, # coding_max_tokens
192
192
  8000, # noncoding_max_tokens
193
193
  mode="wcgw",
194
- chat_id="",
194
+ thread_id="",
195
195
  )
196
196
 
197
197
  if not history:
File without changes