wcgw 2.5.0__py3-none-any.whl → 2.6.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.
- wcgw/client/mcp_server/server.py +34 -29
- wcgw/client/openai_client.py +17 -29
- wcgw/client/repo_ops/display_tree.py +127 -0
- wcgw/client/repo_ops/path_prob.py +58 -0
- wcgw/client/repo_ops/paths_model.vocab +20000 -0
- wcgw/client/repo_ops/paths_tokens.model +80042 -0
- wcgw/client/repo_ops/repo_context.py +148 -0
- wcgw/client/tools.py +70 -41
- wcgw/types_.py +3 -1
- {wcgw-2.5.0.dist-info → wcgw-2.6.1.dist-info}/METADATA +17 -54
- {wcgw-2.5.0.dist-info → wcgw-2.6.1.dist-info}/RECORD +14 -9
- wcgw-2.6.1.dist-info/licenses/LICENSE +213 -0
- wcgw-2.5.0.dist-info/licenses/LICENSE +0 -243
- {wcgw-2.5.0.dist-info → wcgw-2.6.1.dist-info}/WHEEL +0 -0
- {wcgw-2.5.0.dist-info → wcgw-2.6.1.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from collections import deque
|
|
3
|
+
from pathlib import Path # Still needed for other parts
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from pygit2 import GitError, Repository
|
|
7
|
+
|
|
8
|
+
from .display_tree import DirectoryTree
|
|
9
|
+
from .path_prob import FastPathAnalyzer
|
|
10
|
+
|
|
11
|
+
curr_folder = Path(__file__).parent
|
|
12
|
+
vocab_file = curr_folder / "paths_model.vocab"
|
|
13
|
+
model_file = curr_folder / "paths_tokens.model"
|
|
14
|
+
PATH_SCORER = FastPathAnalyzer(str(model_file), str(vocab_file))
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def find_ancestor_with_git(path: Path) -> Optional[Repository]:
|
|
18
|
+
if path.is_file():
|
|
19
|
+
path = path.parent
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
return Repository(str(path))
|
|
23
|
+
except GitError:
|
|
24
|
+
return None
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
MAX_ENTRIES_CHECK = 100_000
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def get_all_files_max_depth(
|
|
31
|
+
abs_folder: str,
|
|
32
|
+
max_depth: int,
|
|
33
|
+
repo: Optional[Repository],
|
|
34
|
+
) -> list[str]:
|
|
35
|
+
"""BFS implementation using deque that maintains relative paths during traversal.
|
|
36
|
+
Returns (files_list, total_files_found) to track file count."""
|
|
37
|
+
all_files = []
|
|
38
|
+
# Queue stores: (folder_path, depth, rel_path_prefix)
|
|
39
|
+
queue = deque([(abs_folder, 0, "")])
|
|
40
|
+
entries_check = 0
|
|
41
|
+
while queue and entries_check < MAX_ENTRIES_CHECK:
|
|
42
|
+
current_folder, depth, prefix = queue.popleft()
|
|
43
|
+
|
|
44
|
+
if depth > max_depth:
|
|
45
|
+
continue
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
entries = list(os.scandir(current_folder))
|
|
49
|
+
except PermissionError:
|
|
50
|
+
continue
|
|
51
|
+
except OSError:
|
|
52
|
+
continue
|
|
53
|
+
# Split into files and folders with single scan
|
|
54
|
+
files = []
|
|
55
|
+
folders = []
|
|
56
|
+
for entry in entries:
|
|
57
|
+
entries_check += 1
|
|
58
|
+
try:
|
|
59
|
+
is_file = entry.is_file(follow_symlinks=False)
|
|
60
|
+
except OSError:
|
|
61
|
+
continue
|
|
62
|
+
name = entry.name
|
|
63
|
+
rel_path = f"{prefix}{name}" if prefix else name
|
|
64
|
+
|
|
65
|
+
if repo and repo.path_is_ignored(rel_path):
|
|
66
|
+
continue
|
|
67
|
+
|
|
68
|
+
if is_file:
|
|
69
|
+
files.append(rel_path)
|
|
70
|
+
else:
|
|
71
|
+
folders.append((entry.path, rel_path))
|
|
72
|
+
|
|
73
|
+
# Process files first (maintain priority)
|
|
74
|
+
chunk = files[: min(10_000, max(0, MAX_ENTRIES_CHECK - entries_check))]
|
|
75
|
+
all_files.extend(chunk)
|
|
76
|
+
|
|
77
|
+
# Add folders to queue for BFS traversal
|
|
78
|
+
for folder_path, folder_rel_path in folders:
|
|
79
|
+
next_prefix = f"{folder_rel_path}/"
|
|
80
|
+
queue.append((folder_path, depth + 1, next_prefix))
|
|
81
|
+
|
|
82
|
+
return all_files
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def get_repo_context(file_or_repo_path: str, max_files: int) -> tuple[str, Path]:
|
|
86
|
+
file_or_repo_path_ = Path(file_or_repo_path).absolute()
|
|
87
|
+
|
|
88
|
+
repo = find_ancestor_with_git(file_or_repo_path_)
|
|
89
|
+
|
|
90
|
+
if repo is not None:
|
|
91
|
+
context_dir = Path(repo.path).parent
|
|
92
|
+
else:
|
|
93
|
+
if file_or_repo_path_.is_file():
|
|
94
|
+
context_dir = file_or_repo_path_.parent
|
|
95
|
+
else:
|
|
96
|
+
context_dir = file_or_repo_path_
|
|
97
|
+
|
|
98
|
+
all_files = get_all_files_max_depth(str(context_dir), 10, repo)
|
|
99
|
+
|
|
100
|
+
# Calculate probabilities in batch
|
|
101
|
+
path_scores = PATH_SCORER.calculate_path_probabilities_batch(all_files)
|
|
102
|
+
|
|
103
|
+
# Create list of (path, score) tuples and sort by score
|
|
104
|
+
path_with_scores = list(zip(all_files, (score[0] for score in path_scores)))
|
|
105
|
+
sorted_files = [
|
|
106
|
+
path for path, _ in sorted(path_with_scores, key=lambda x: x[1], reverse=True)
|
|
107
|
+
]
|
|
108
|
+
|
|
109
|
+
top_files = sorted_files[:max_files]
|
|
110
|
+
|
|
111
|
+
directory_printer = DirectoryTree(context_dir, max_files=max_files)
|
|
112
|
+
for file in top_files:
|
|
113
|
+
directory_printer.expand(file)
|
|
114
|
+
|
|
115
|
+
return directory_printer.display(), context_dir
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
if __name__ == "__main__":
|
|
119
|
+
import cProfile
|
|
120
|
+
import pstats
|
|
121
|
+
import sys
|
|
122
|
+
|
|
123
|
+
from line_profiler import LineProfiler
|
|
124
|
+
|
|
125
|
+
folder = sys.argv[1]
|
|
126
|
+
|
|
127
|
+
# Profile using cProfile for overall function statistics
|
|
128
|
+
profiler = cProfile.Profile()
|
|
129
|
+
profiler.enable()
|
|
130
|
+
result = get_repo_context(folder, 200)[0]
|
|
131
|
+
profiler.disable()
|
|
132
|
+
|
|
133
|
+
# Print cProfile stats
|
|
134
|
+
stats = pstats.Stats(profiler)
|
|
135
|
+
stats.sort_stats("cumulative")
|
|
136
|
+
print("\n=== Function-level profiling ===")
|
|
137
|
+
stats.print_stats(20) # Print top 20 functions
|
|
138
|
+
|
|
139
|
+
# Profile using line_profiler for line-by-line statistics
|
|
140
|
+
lp = LineProfiler()
|
|
141
|
+
lp_wrapper = lp(get_repo_context)
|
|
142
|
+
lp_wrapper(folder, 200)
|
|
143
|
+
|
|
144
|
+
print("\n=== Line-by-line profiling ===")
|
|
145
|
+
lp.print_stats()
|
|
146
|
+
|
|
147
|
+
print("\n=== Result ===")
|
|
148
|
+
print(result)
|
wcgw/client/tools.py
CHANGED
|
@@ -1,61 +1,57 @@
|
|
|
1
1
|
import base64
|
|
2
2
|
import datetime
|
|
3
|
+
import importlib.metadata
|
|
3
4
|
import json
|
|
4
5
|
import mimetypes
|
|
5
|
-
|
|
6
|
+
import os
|
|
6
7
|
import re
|
|
7
8
|
import shlex
|
|
8
|
-
import importlib.metadata
|
|
9
9
|
import time
|
|
10
10
|
import traceback
|
|
11
|
+
import uuid
|
|
12
|
+
from difflib import SequenceMatcher
|
|
13
|
+
from pathlib import Path
|
|
11
14
|
from tempfile import NamedTemporaryFile, TemporaryDirectory
|
|
12
15
|
from typing import (
|
|
13
16
|
Callable,
|
|
14
|
-
DefaultDict,
|
|
15
17
|
Literal,
|
|
16
18
|
Optional,
|
|
17
19
|
ParamSpec,
|
|
18
20
|
Type,
|
|
19
21
|
TypeVar,
|
|
20
22
|
)
|
|
21
|
-
import uuid
|
|
22
23
|
|
|
23
|
-
from pydantic import BaseModel, TypeAdapter
|
|
24
|
-
import typer
|
|
25
|
-
from .computer_use import run_computer_tool
|
|
26
|
-
from websockets.sync.client import connect as syncconnect
|
|
27
|
-
|
|
28
|
-
import os
|
|
29
|
-
import tokenizers # type: ignore
|
|
30
24
|
import pexpect
|
|
31
|
-
from typer import Typer
|
|
32
|
-
import websockets
|
|
33
|
-
|
|
34
|
-
import rich
|
|
35
25
|
import pyte
|
|
36
|
-
|
|
37
|
-
|
|
26
|
+
import rich
|
|
27
|
+
import tokenizers # type: ignore
|
|
28
|
+
import typer
|
|
29
|
+
import websockets
|
|
38
30
|
from openai.types.chat import (
|
|
39
31
|
ChatCompletionMessageParam,
|
|
40
32
|
)
|
|
41
|
-
from
|
|
33
|
+
from pydantic import BaseModel, TypeAdapter
|
|
34
|
+
from syntax_checker import check_syntax
|
|
35
|
+
from typer import Typer
|
|
36
|
+
from websockets.sync.client import connect as syncconnect
|
|
42
37
|
|
|
43
38
|
from ..types_ import (
|
|
44
39
|
BashCommand,
|
|
45
40
|
BashInteraction,
|
|
46
|
-
WriteIfEmpty,
|
|
47
|
-
FileEditFindReplace,
|
|
48
41
|
FileEdit,
|
|
42
|
+
FileEditFindReplace,
|
|
43
|
+
GetScreenInfo,
|
|
49
44
|
Initialize,
|
|
45
|
+
Keyboard,
|
|
46
|
+
Mouse,
|
|
50
47
|
ReadFiles,
|
|
51
48
|
ReadImage,
|
|
52
49
|
ResetShell,
|
|
53
|
-
Mouse,
|
|
54
|
-
Keyboard,
|
|
55
50
|
ScreenShot,
|
|
56
|
-
|
|
51
|
+
WriteIfEmpty,
|
|
57
52
|
)
|
|
58
|
-
|
|
53
|
+
from .computer_use import run_computer_tool
|
|
54
|
+
from .repo_ops.repo_context import get_repo_context
|
|
59
55
|
from .sys_utils import command_run
|
|
60
56
|
|
|
61
57
|
|
|
@@ -178,19 +174,23 @@ def _ensure_env_and_bg_jobs(shell: pexpect.spawn) -> Optional[int]: # type: ign
|
|
|
178
174
|
shell.expect(PROMPT, timeout=0.2)
|
|
179
175
|
shell.sendline("jobs | wc -l")
|
|
180
176
|
before = ""
|
|
177
|
+
|
|
181
178
|
while not _is_int(before): # Consume all previous output
|
|
182
179
|
try:
|
|
183
180
|
shell.expect(PROMPT, timeout=0.2)
|
|
184
181
|
except pexpect.TIMEOUT:
|
|
185
182
|
console.print(f"Couldn't get exit code, before: {before}")
|
|
186
183
|
raise
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
184
|
+
|
|
185
|
+
before_val = shell.before
|
|
186
|
+
if not isinstance(before_val, str):
|
|
187
|
+
before_val = str(before_val)
|
|
188
|
+
assert isinstance(before_val, str)
|
|
189
|
+
before_lines = render_terminal_output(before_val)
|
|
190
190
|
before = "\n".join(before_lines).strip()
|
|
191
191
|
|
|
192
192
|
try:
|
|
193
|
-
return int(
|
|
193
|
+
return int(before)
|
|
194
194
|
except ValueError:
|
|
195
195
|
raise ValueError(f"Malformed output: {before}")
|
|
196
196
|
|
|
@@ -244,10 +244,12 @@ class BashState:
|
|
|
244
244
|
return self._cwd
|
|
245
245
|
|
|
246
246
|
def update_cwd(self) -> str:
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
247
|
+
self.shell.sendline("pwd")
|
|
248
|
+
self.shell.expect(PROMPT, timeout=0.2)
|
|
249
|
+
before_val = self.shell.before
|
|
250
|
+
if not isinstance(before_val, str):
|
|
251
|
+
before_val = str(before_val)
|
|
252
|
+
before_lines = render_terminal_output(before_val)
|
|
251
253
|
current_dir = "\n".join(before_lines).strip()
|
|
252
254
|
self._cwd = current_dir
|
|
253
255
|
return current_dir
|
|
@@ -287,20 +289,42 @@ class BashState:
|
|
|
287
289
|
BASH_STATE = BashState()
|
|
288
290
|
|
|
289
291
|
|
|
290
|
-
def initialize(
|
|
292
|
+
def initialize(
|
|
293
|
+
any_workspace_path: str, read_files_: list[str], max_tokens: Optional[int]
|
|
294
|
+
) -> str:
|
|
291
295
|
reset_shell()
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
+
|
|
297
|
+
repo_context = ""
|
|
298
|
+
|
|
299
|
+
if any_workspace_path:
|
|
300
|
+
if os.path.exists(any_workspace_path):
|
|
301
|
+
repo_context, folder_to_start = get_repo_context(any_workspace_path, 200)
|
|
302
|
+
|
|
303
|
+
BASH_STATE.shell.sendline(f"cd {shlex.quote(str(folder_to_start))}")
|
|
304
|
+
BASH_STATE.shell.expect(PROMPT, timeout=0.2)
|
|
305
|
+
BASH_STATE.update_cwd()
|
|
306
|
+
|
|
307
|
+
repo_context = f"---\n# Workspace structure\n{repo_context}\n---\n"
|
|
308
|
+
else:
|
|
309
|
+
return f"\nInfo: Workspace path {any_workspace_path} does not exist\n"
|
|
310
|
+
|
|
311
|
+
initial_files_context = ""
|
|
312
|
+
if read_files_:
|
|
313
|
+
initial_files = read_files(read_files_, max_tokens)
|
|
314
|
+
initial_files_context = f"---\n# Requested files\n{initial_files}\n---\n"
|
|
296
315
|
|
|
297
316
|
uname_sysname = os.uname().sysname
|
|
298
317
|
uname_machine = os.uname().machine
|
|
299
318
|
|
|
300
319
|
output = f"""
|
|
320
|
+
# Environment
|
|
301
321
|
System: {uname_sysname}
|
|
302
322
|
Machine: {uname_machine}
|
|
303
323
|
Current working directory: {BASH_STATE.cwd}
|
|
324
|
+
|
|
325
|
+
{repo_context}
|
|
326
|
+
|
|
327
|
+
{initial_files_context}
|
|
304
328
|
"""
|
|
305
329
|
|
|
306
330
|
return output
|
|
@@ -567,7 +591,9 @@ def execute_bash(
|
|
|
567
591
|
|
|
568
592
|
return incremental_text, 0
|
|
569
593
|
|
|
570
|
-
|
|
594
|
+
if not isinstance(BASH_STATE.shell.before, str):
|
|
595
|
+
BASH_STATE.shell.before = str(BASH_STATE.shell.before)
|
|
596
|
+
|
|
571
597
|
output = _incremental_text(BASH_STATE.shell.before, BASH_STATE.pending_output)
|
|
572
598
|
BASH_STATE.set_repl()
|
|
573
599
|
|
|
@@ -578,7 +604,7 @@ def execute_bash(
|
|
|
578
604
|
try:
|
|
579
605
|
exit_status = get_status()
|
|
580
606
|
output += exit_status
|
|
581
|
-
except ValueError
|
|
607
|
+
except ValueError:
|
|
582
608
|
console.print(output)
|
|
583
609
|
console.print(traceback.format_exc())
|
|
584
610
|
console.print("Malformed output, restarting shell", style="red")
|
|
@@ -1189,7 +1215,10 @@ def get_tool_output(
|
|
|
1189
1215
|
output = reset_shell(), 0.0
|
|
1190
1216
|
elif isinstance(arg, Initialize):
|
|
1191
1217
|
console.print("Calling initial info tool")
|
|
1192
|
-
output =
|
|
1218
|
+
output = (
|
|
1219
|
+
initialize(arg.any_workspace_path, arg.initial_files_to_read, max_tokens),
|
|
1220
|
+
0.0,
|
|
1221
|
+
)
|
|
1193
1222
|
elif isinstance(arg, (Mouse, Keyboard, ScreenShot, GetScreenInfo)):
|
|
1194
1223
|
console.print(f"Calling {type(arg).__name__} tool")
|
|
1195
1224
|
outputs_cost = run_computer_tool(arg), 0.0
|
|
@@ -1331,7 +1360,7 @@ def read_files(file_paths: list[str], max_tokens: Optional[int]) -> str:
|
|
|
1331
1360
|
for i, file in enumerate(file_paths):
|
|
1332
1361
|
try:
|
|
1333
1362
|
content, truncated, tokens = read_file(file, max_tokens)
|
|
1334
|
-
except
|
|
1363
|
+
except Exception as e:
|
|
1335
1364
|
message += f"\n{file}: {str(e)}\n"
|
|
1336
1365
|
continue
|
|
1337
1366
|
|
wcgw/types_.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import re
|
|
2
1
|
from typing import Literal, Optional, Sequence
|
|
2
|
+
|
|
3
3
|
from pydantic import BaseModel
|
|
4
4
|
|
|
5
5
|
|
|
@@ -53,6 +53,8 @@ class FileEdit(BaseModel):
|
|
|
53
53
|
|
|
54
54
|
class Initialize(BaseModel):
|
|
55
55
|
type: Literal["Initialize"]
|
|
56
|
+
any_workspace_path: str
|
|
57
|
+
initial_files_to_read: list[str]
|
|
56
58
|
|
|
57
59
|
|
|
58
60
|
class GetScreenInfo(BaseModel):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: wcgw
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.6.1
|
|
4
4
|
Summary: Shell and coding agent on claude and chatgpt
|
|
5
5
|
Project-URL: Homepage, https://github.com/rusiaaman/wcgw
|
|
6
6
|
Author-email: Aman Rusia <gapypi@arcfu.com>
|
|
@@ -12,6 +12,7 @@ Requires-Dist: openai>=1.46.0
|
|
|
12
12
|
Requires-Dist: petname>=2.6
|
|
13
13
|
Requires-Dist: pexpect>=4.9.0
|
|
14
14
|
Requires-Dist: pydantic>=2.9.2
|
|
15
|
+
Requires-Dist: pygit2>=1.16.0
|
|
15
16
|
Requires-Dist: pyte>=0.8.2
|
|
16
17
|
Requires-Dist: python-dotenv>=1.0.1
|
|
17
18
|
Requires-Dist: rich>=13.8.1
|
|
@@ -28,7 +29,7 @@ Description-Content-Type: text/markdown
|
|
|
28
29
|
|
|
29
30
|
# Shell and Coding agent for Claude and Chatgpt
|
|
30
31
|
|
|
31
|
-
- Claude - An MCP server on claude desktop for autonomous shell
|
|
32
|
+
- Claude - An MCP server on claude desktop for autonomous shell and coding agent. (mac only)
|
|
32
33
|
- Chatgpt - Allows custom gpt to talk to your shell via a relay server. (linux or mac)
|
|
33
34
|
|
|
34
35
|
|
|
@@ -37,6 +38,7 @@ Description-Content-Type: text/markdown
|
|
|
37
38
|
[](https://github.com/rusiaaman/wcgw/actions/workflows/python-tests.yml)
|
|
38
39
|
[](https://github.com/rusiaaman/wcgw/actions/workflows/python-types.yml)
|
|
39
40
|
[](https://github.com/rusiaaman/wcgw/actions/workflows/python-publish.yml)
|
|
41
|
+
[](https://codecov.io/gh/rusiaaman/wcgw)
|
|
40
42
|
|
|
41
43
|
## Updates
|
|
42
44
|
|
|
@@ -48,12 +50,11 @@ Description-Content-Type: text/markdown
|
|
|
48
50
|
|
|
49
51
|
## 🚀 Highlights
|
|
50
52
|
|
|
51
|
-
- ⚡ **Full Shell Access**: No restrictions, complete control.
|
|
52
|
-
- ⚡ **Desktop control on Claude**: Screen capture, mouse control, keyboard control on claude desktop (on mac with docker linux)
|
|
53
53
|
- ⚡ **Create, Execute, Iterate**: Ask claude 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.
|
|
54
54
|
- ⚡ **Large file edit**: Supports large file incremental edits to avoid token limit issues. Faster than full file write.
|
|
55
|
+
- ⚡ **Syntax checking on edits**: Reports feedback to the LLM if its edits have any syntax errors, so that it can redo it.
|
|
55
56
|
- ⚡ **Interactive Command Handling**: Supports interactive commands using arrow keys, interrupt, and ansi escape sequences.
|
|
56
|
-
- ⚡ **
|
|
57
|
+
- ⚡ **Full Shell Access**: No restrictions, complete control.
|
|
57
58
|
|
|
58
59
|
## Top use cases examples
|
|
59
60
|
|
|
@@ -70,9 +71,11 @@ Description-Content-Type: text/markdown
|
|
|
70
71
|
- Using 'screen' run my server in background instead, then run another api server in bg, finally run the frontend build. Keep checking logs for any issues in all three
|
|
71
72
|
- Create repo wide unittest cases. Keep iterating through files and creating cases. Also keep running the tests after each update. Do not modify original code.
|
|
72
73
|
|
|
73
|
-
## Claude
|
|
74
|
+
## Claude setup (using mcp)
|
|
74
75
|
|
|
75
|
-
First install `uv`
|
|
76
|
+
First install `uv` using homebrew `brew install uv`
|
|
77
|
+
|
|
78
|
+
(**Important:** use homebrew to install uv. Otherwise make sure `uv` is present in a global location like /usr/bin/)
|
|
76
79
|
|
|
77
80
|
Then update `claude_desktop_config.json` (~/Library/Application Support/Claude/claude_desktop_config.json)
|
|
78
81
|
|
|
@@ -99,53 +102,21 @@ Then restart claude app.
|
|
|
99
102
|
|
|
100
103
|
_If there's an error in setting up_
|
|
101
104
|
|
|
102
|
-
-
|
|
103
|
-
Otherwise, re-install uv and follow instructions to add it into your .zshrc or .bashrc
|
|
105
|
+
- 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.
|
|
104
106
|
- If there's still an issue, check that `uv tool run --from wcgw@latest --python 3.12 wcgw_mcp` runs in your terminal. It should have no output and shouldn't exit.
|
|
105
107
|
- Debug the mcp server using `npx @modelcontextprotocol/inspector@0.1.7 uv tool run --from wcgw@latest --python 3.12 wcgw_mcp`
|
|
106
108
|
|
|
107
|
-
###
|
|
108
|
-
|
|
109
|
-
Computer use is disabled by default. Add `--computer-use` to enable it. This will add necessary tools to Claude including ScreenShot, Mouse and Keyboard control.
|
|
110
|
-
|
|
111
|
-
```json
|
|
112
|
-
{
|
|
113
|
-
"mcpServers": {
|
|
114
|
-
"wcgw": {
|
|
115
|
-
"command": "uv",
|
|
116
|
-
"args": [
|
|
117
|
-
"tool",
|
|
118
|
-
"run",
|
|
119
|
-
"--from",
|
|
120
|
-
"wcgw@latest",
|
|
121
|
-
"--python",
|
|
122
|
-
"3.12",
|
|
123
|
-
"wcgw_mcp",
|
|
124
|
-
"--computer-use"
|
|
125
|
-
]
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
```
|
|
109
|
+
### Alternative configuration using smithery (npx required)
|
|
110
|
+
[](https://smithery.ai/server/wcgw)
|
|
130
111
|
|
|
131
|
-
|
|
112
|
+
You need to first install uv using homebrew. `brew install uv`
|
|
132
113
|
|
|
133
|
-
|
|
114
|
+
Then to configure wcgw for Claude Desktop automatically via [Smithery](https://smithery.ai/server/wcgw):
|
|
134
115
|
|
|
135
|
-
```
|
|
136
|
-
|
|
116
|
+
```bash
|
|
117
|
+
npx -y @smithery/cli install wcgw --client claude
|
|
137
118
|
```
|
|
138
119
|
|
|
139
|
-
Then ask claude desktop app to control the docker os. It'll connect to the docker container and control it.
|
|
140
|
-
|
|
141
|
-
Connect to `http://localhost:6080/vnc.html` for desktop view (VNC) of the system running in the docker.
|
|
142
|
-
|
|
143
|
-
The following requirements should be installed and working in the linux docker image:
|
|
144
|
-
|
|
145
|
-
1. Needs `xdotool` to execute commands on the desktop.
|
|
146
|
-
2. Needs `scrot` to take screenshots.
|
|
147
|
-
3. Needs `convert` from imagemagick to convert images.
|
|
148
|
-
|
|
149
120
|
### Usage
|
|
150
121
|
|
|
151
122
|
Wait for a few seconds. You should be able to see this icon if everything goes right.
|
|
@@ -157,8 +128,6 @@ over here
|
|
|
157
128
|
|
|
158
129
|
Then ask claude to execute shell commands, read files, edit files, run your code, etc.
|
|
159
130
|
|
|
160
|
-
If you've run the docker for LLM to access, you can ask it to control the "docker os". If you don't provide the docker container id to it, it'll try to search for available docker using `docker ps` command.
|
|
161
|
-
|
|
162
131
|
|
|
163
132
|
### [Optional] Vs code extension
|
|
164
133
|
https://marketplace.visualstudio.com/items?itemName=AmanRusia.wcgw
|
|
@@ -172,12 +141,6 @@ Read here: https://github.com/rusiaaman/wcgw/blob/main/openai.md
|
|
|
172
141
|
|
|
173
142
|
## Examples
|
|
174
143
|
|
|
175
|
-
### Computer use example
|
|
176
|
-
|
|
177
|
-

|
|
178
|
-
|
|
179
|
-
### Shell example
|
|
180
|
-
|
|
181
144
|

|
|
182
145
|
|
|
183
146
|
## [Optional] Local shell access with openai API key or anthropic API key
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
wcgw/__init__.py,sha256=9K2QW7QuSLhMTVbKbBYd9UUp-ZyrfBrxcjuD_xk458k,118
|
|
2
|
-
wcgw/types_.py,sha256=
|
|
2
|
+
wcgw/types_.py,sha256=PJpnzF_TJ_L-AhruPlfD-zkGYj0_YEUAXTbvOtb_Zpo,1891
|
|
3
3
|
wcgw/client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
4
|
wcgw/client/__main__.py,sha256=wcCrL4PjG51r5wVKqJhcoJPTLfHW0wNbD31DrUN0MWI,28
|
|
5
5
|
wcgw/client/anthropic_client.py,sha256=kmS93LEVwXCppfbgCsfWe_SVmTAtsI3x8PrP8DJDYMY,21041
|
|
@@ -7,13 +7,18 @@ wcgw/client/cli.py,sha256=-z0kpDAW3mzfQrQeZfaVJhBCAQY3HXnt9GdgQ8s-u0Y,1003
|
|
|
7
7
|
wcgw/client/common.py,sha256=OCH7Tx64jojz3M3iONUrGMadE07W21DiZs5sOxWX1Qc,1456
|
|
8
8
|
wcgw/client/computer_use.py,sha256=35NKAlMrxwD0TBlMMRnbCwz4g8TBRGOlcy-cmS-yJ_A,15247
|
|
9
9
|
wcgw/client/diff-instructions.txt,sha256=s5AJKG23JsjwRYhFZFQVvwDpF67vElawrmdXwvukR1A,1683
|
|
10
|
-
wcgw/client/openai_client.py,sha256=
|
|
10
|
+
wcgw/client/openai_client.py,sha256=AP0B-fKNNlYCAEL0MrT3UxeCcvca7jJZp9kYkinUqSM,17706
|
|
11
11
|
wcgw/client/openai_utils.py,sha256=KfMB1-p2zDiA7pPWwAVarochf7-qeL1UMgtlDV9DtKA,2662
|
|
12
12
|
wcgw/client/sys_utils.py,sha256=GajPntKhaTUMn6EOmopENWZNR2G_BJyuVbuot0x6veI,1376
|
|
13
|
-
wcgw/client/tools.py,sha256=
|
|
13
|
+
wcgw/client/tools.py,sha256=oJ9Zg03CEbzMh8NXQc1lfZQv8C46jQ24PRXGYMiHJ9k,47987
|
|
14
14
|
wcgw/client/mcp_server/Readme.md,sha256=I8N4dHkTUVGNQ63BQkBMBhCCBTgqGOSF_pUR6iOEiUk,2495
|
|
15
15
|
wcgw/client/mcp_server/__init__.py,sha256=hyPPwO9cabAJsOMWhKyat9yl7OlSmIobaoAZKHu3DMc,381
|
|
16
|
-
wcgw/client/mcp_server/server.py,sha256=
|
|
16
|
+
wcgw/client/mcp_server/server.py,sha256=bhswcJQt2jWIwVcQRrEaa9E6LkuE_vK7fNG51bsV4hw,12414
|
|
17
|
+
wcgw/client/repo_ops/display_tree.py,sha256=5FD4hfMkM2cIZnXlu7WfJswJLthj0SkuHlkGH6dpWQU,4632
|
|
18
|
+
wcgw/client/repo_ops/path_prob.py,sha256=SWf0CDn37rtlsYRQ51ufSxay-heaQoVIhr1alB9tZ4M,2144
|
|
19
|
+
wcgw/client/repo_ops/paths_model.vocab,sha256=M1pXycYDQehMXtpp-qAgU7rtzeBbCOiJo4qcYFY0kqk,315087
|
|
20
|
+
wcgw/client/repo_ops/paths_tokens.model,sha256=jiwwE4ae8ADKuTZISutXuM5Wfyc_FBmN5rxTjoNnCos,1569052
|
|
21
|
+
wcgw/client/repo_ops/repo_context.py,sha256=5NqRxBY0K-SBFXJ0Ybt7llzYOBD8pRkTpruMMJHWxv4,4336
|
|
17
22
|
wcgw/relay/serve.py,sha256=CYY0mAAzR6nXkdGqLA9dXkgBcMCKPXEAmBcDyutUnjQ,8769
|
|
18
23
|
wcgw/relay/static/privacy.txt,sha256=s9qBdbx2SexCpC_z33sg16TptmAwDEehMCLz4L50JLc,529
|
|
19
24
|
mcp_wcgw/__init__.py,sha256=fKCgOdN7cn7gR3YGFaGyV5Goe8A2sEyllLcsRkN0i-g,2601
|
|
@@ -38,8 +43,8 @@ mcp_wcgw/shared/memory.py,sha256=dBsOghxHz8-tycdSVo9kSujbsC8xb_tYsGmuJobuZnw,281
|
|
|
38
43
|
mcp_wcgw/shared/progress.py,sha256=ymxOsb8XO5Mhlop7fRfdbmvPodANj7oq6O4dD0iUcnw,1048
|
|
39
44
|
mcp_wcgw/shared/session.py,sha256=e44a0LQOW8gwdLs9_DE9oDsxqW2U8mXG3d5KT95bn5o,10393
|
|
40
45
|
mcp_wcgw/shared/version.py,sha256=d2LZii-mgsPIxpshjkXnOTUmk98i0DT4ff8VpA_kAvE,111
|
|
41
|
-
wcgw-2.
|
|
42
|
-
wcgw-2.
|
|
43
|
-
wcgw-2.
|
|
44
|
-
wcgw-2.
|
|
45
|
-
wcgw-2.
|
|
46
|
+
wcgw-2.6.1.dist-info/METADATA,sha256=AY-QOt4XUDIE6Pd7axFCXgSj25XkI9k9gsJCz3LHxjM,6762
|
|
47
|
+
wcgw-2.6.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
48
|
+
wcgw-2.6.1.dist-info/entry_points.txt,sha256=eKo1omwbAggWlQ0l7GKoR7uV1-j16nk9tK0BhC2Oz_E,120
|
|
49
|
+
wcgw-2.6.1.dist-info/licenses/LICENSE,sha256=BvY8xqjOfc3X2qZpGpX3MZEmF-4Dp0LqgKBbT6L_8oI,11142
|
|
50
|
+
wcgw-2.6.1.dist-info/RECORD,,
|