grasp-sdk 0.2.0b2__tar.gz → 0.2.0b3__tar.gz
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 grasp-sdk might be problematic. Click here for more details.
- {grasp_sdk-0.2.0b2/grasp_sdk.egg-info → grasp_sdk-0.2.0b3}/PKG-INFO +1 -1
- grasp_sdk-0.2.0b3/examples/grasp-mouse-usage-2.py +151 -0
- {grasp_sdk-0.2.0b2 → grasp_sdk-0.2.0b3}/examples/test_terminal_updates.py +2 -2
- {grasp_sdk-0.2.0b2 → grasp_sdk-0.2.0b3}/grasp_sdk/__init__.py +1 -2
- {grasp_sdk-0.2.0b2 → grasp_sdk-0.2.0b3}/grasp_sdk/grasp/server.py +2 -0
- {grasp_sdk-0.2.0b2 → grasp_sdk-0.2.0b3}/grasp_sdk/models/__init__.py +2 -2
- {grasp_sdk-0.2.0b2 → grasp_sdk-0.2.0b3}/grasp_sdk/services/browser.py +1 -1
- {grasp_sdk-0.2.0b2 → grasp_sdk-0.2.0b3}/grasp_sdk/services/sandbox.py +6 -6
- {grasp_sdk-0.2.0b2 → grasp_sdk-0.2.0b3/grasp_sdk.egg-info}/PKG-INFO +1 -1
- {grasp_sdk-0.2.0b2 → grasp_sdk-0.2.0b3}/grasp_sdk.egg-info/SOURCES.txt +2 -0
- {grasp_sdk-0.2.0b2 → grasp_sdk-0.2.0b3}/grasp_sdk.egg-info/top_level.txt +1 -0
- {grasp_sdk-0.2.0b2 → grasp_sdk-0.2.0b3}/pyproject.toml +1 -1
- {grasp_sdk-0.2.0b2 → grasp_sdk-0.2.0b3}/MANIFEST.in +0 -0
- {grasp_sdk-0.2.0b2 → grasp_sdk-0.2.0b3}/README.md +0 -0
- {grasp_sdk-0.2.0b2 → grasp_sdk-0.2.0b3}/build_and_publish.py +0 -0
- {grasp_sdk-0.2.0b2 → grasp_sdk-0.2.0b3}/examples/example_async_context.py +0 -0
- {grasp_sdk-0.2.0b2 → grasp_sdk-0.2.0b3}/examples/example_binary_file_support.py +0 -0
- {grasp_sdk-0.2.0b2 → grasp_sdk-0.2.0b3}/examples/example_grasp_usage.py +0 -0
- {grasp_sdk-0.2.0b2 → grasp_sdk-0.2.0b3}/examples/example_readfile_usage.py +0 -0
- {grasp_sdk-0.2.0b2 → grasp_sdk-0.2.0b3}/examples/grasp_terminal.py +0 -0
- {grasp_sdk-0.2.0b2 → grasp_sdk-0.2.0b3}/examples/grasp_usage.py +0 -0
- {grasp_sdk-0.2.0b2 → grasp_sdk-0.2.0b3}/examples/test_async_context.py +0 -0
- {grasp_sdk-0.2.0b2 → grasp_sdk-0.2.0b3}/examples/test_grasp_classes.py +0 -0
- {grasp_sdk-0.2.0b2 → grasp_sdk-0.2.0b3}/examples/test_python_script.py +0 -0
- {grasp_sdk-0.2.0b2 → grasp_sdk-0.2.0b3}/examples/test_removed_methods.py +0 -0
- {grasp_sdk-0.2.0b2 → grasp_sdk-0.2.0b3}/grasp_sdk/grasp/__init__.py +0 -0
- {grasp_sdk-0.2.0b2 → grasp_sdk-0.2.0b3}/grasp_sdk/grasp/browser.py +0 -0
- {grasp_sdk-0.2.0b2 → grasp_sdk-0.2.0b3}/grasp_sdk/grasp/index.py +0 -0
- {grasp_sdk-0.2.0b2 → grasp_sdk-0.2.0b3}/grasp_sdk/grasp/session.py +0 -0
- {grasp_sdk-0.2.0b2 → grasp_sdk-0.2.0b3}/grasp_sdk/grasp/utils.py +0 -0
- {grasp_sdk-0.2.0b2 → grasp_sdk-0.2.0b3}/grasp_sdk/services/__init__.py +0 -0
- {grasp_sdk-0.2.0b2 → grasp_sdk-0.2.0b3}/grasp_sdk/services/filesystem.py +0 -0
- {grasp_sdk-0.2.0b2 → grasp_sdk-0.2.0b3}/grasp_sdk/services/terminal.py +0 -0
- {grasp_sdk-0.2.0b2 → grasp_sdk-0.2.0b3}/grasp_sdk/utils/__init__.py +0 -0
- {grasp_sdk-0.2.0b2 → grasp_sdk-0.2.0b3}/grasp_sdk/utils/auth.py +0 -0
- {grasp_sdk-0.2.0b2 → grasp_sdk-0.2.0b3}/grasp_sdk/utils/config.py +0 -0
- {grasp_sdk-0.2.0b2 → grasp_sdk-0.2.0b3}/grasp_sdk/utils/logger.py +0 -0
- {grasp_sdk-0.2.0b2 → grasp_sdk-0.2.0b3}/grasp_sdk.egg-info/dependency_links.txt +0 -0
- {grasp_sdk-0.2.0b2 → grasp_sdk-0.2.0b3}/grasp_sdk.egg-info/entry_points.txt +0 -0
- {grasp_sdk-0.2.0b2 → grasp_sdk-0.2.0b3}/grasp_sdk.egg-info/not-zip-safe +0 -0
- {grasp_sdk-0.2.0b2 → grasp_sdk-0.2.0b3}/grasp_sdk.egg-info/requires.txt +0 -0
- {grasp_sdk-0.2.0b2 → grasp_sdk-0.2.0b3}/py.typed +0 -0
- {grasp_sdk-0.2.0b2 → grasp_sdk-0.2.0b3}/requirements.txt +0 -0
- {grasp_sdk-0.2.0b2 → grasp_sdk-0.2.0b3}/setup.cfg +0 -0
- {grasp_sdk-0.2.0b2 → grasp_sdk-0.2.0b3}/setup.py +0 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import subprocess
|
|
3
|
+
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from dotenv import load_dotenv
|
|
7
|
+
|
|
8
|
+
# 添加父目录到 Python 路径,以便导入 grasp_sdk
|
|
9
|
+
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
10
|
+
|
|
11
|
+
from grasp_sdk import Grasp
|
|
12
|
+
from playwright.async_api import async_playwright
|
|
13
|
+
import math
|
|
14
|
+
|
|
15
|
+
# 加载环境变量
|
|
16
|
+
load_dotenv("../.env.grasp")
|
|
17
|
+
|
|
18
|
+
async def main():
|
|
19
|
+
grasp = Grasp()
|
|
20
|
+
|
|
21
|
+
session = await grasp.launch({
|
|
22
|
+
"browser": {
|
|
23
|
+
# "type": "chrome-stable",
|
|
24
|
+
"headless": False,
|
|
25
|
+
# "args": [
|
|
26
|
+
# "--enable-webgl",
|
|
27
|
+
# "--ignore-gpu-blacklist",
|
|
28
|
+
# "--use-gl=swiftshader",
|
|
29
|
+
# "--headless=false",
|
|
30
|
+
# ],
|
|
31
|
+
# "adblock": True,
|
|
32
|
+
"liveview": True,
|
|
33
|
+
},
|
|
34
|
+
"debug": True,
|
|
35
|
+
"keepAliveMS": 10000,
|
|
36
|
+
"timeout": 3600000,
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
terminal = session.terminal
|
|
40
|
+
# response = await terminal.run_command(
|
|
41
|
+
# # "node -e \"console.log(process.env)\""
|
|
42
|
+
# "Xvfb :1 -screen 0 1024x768x24 & export DISPLAY=:1 && glxinfo | grep \"OpenGL renderer\""
|
|
43
|
+
# )
|
|
44
|
+
# response.stdout.pipe(process.stdout)
|
|
45
|
+
# response.stderr.pipe(process.stderr)
|
|
46
|
+
# print(await response.json())
|
|
47
|
+
|
|
48
|
+
# await session.files.download_file(
|
|
49
|
+
# "/home/user/.env.json",
|
|
50
|
+
# "./output/.env.json"
|
|
51
|
+
# )
|
|
52
|
+
|
|
53
|
+
ws_url = session.browser.get_endpoint()
|
|
54
|
+
print(ws_url)
|
|
55
|
+
|
|
56
|
+
async with async_playwright() as p:
|
|
57
|
+
browser = await p.chromium.connect_over_cdp(ws_url, timeout=150000)
|
|
58
|
+
|
|
59
|
+
url = await session.browser.get_liveview_page_url()
|
|
60
|
+
if url:
|
|
61
|
+
# 使用 subprocess 打开 URL(替代 Node.js 的 open 包)
|
|
62
|
+
subprocess.run(["open", url]) # macOS
|
|
63
|
+
# subprocess.run(["xdg-open", url]) # Linux
|
|
64
|
+
# subprocess.run(["start", url], shell=True) # Windows
|
|
65
|
+
|
|
66
|
+
page = await browser.new_page()
|
|
67
|
+
|
|
68
|
+
# 打开一个可以玩拖拽的网页
|
|
69
|
+
await page.goto("https://the-internet.herokuapp.com/drag_and_drop")
|
|
70
|
+
|
|
71
|
+
box_a = page.locator("#column-a")
|
|
72
|
+
box_b = page.locator("#column-b")
|
|
73
|
+
|
|
74
|
+
box_a_box = await box_a.bounding_box()
|
|
75
|
+
box_b_box = await box_b.bounding_box()
|
|
76
|
+
|
|
77
|
+
if not box_a_box or not box_b_box:
|
|
78
|
+
raise Exception("Could not get bounding boxes for elements")
|
|
79
|
+
|
|
80
|
+
mouse = page.mouse
|
|
81
|
+
|
|
82
|
+
# 🎯 Step 1: 炫技滑动到 Box A
|
|
83
|
+
await mouse.move(0, 0, steps=20)
|
|
84
|
+
await mouse.move(box_a_box["x"] + 30, box_a_box["y"] + 30, steps=40)
|
|
85
|
+
|
|
86
|
+
# ⚡ Step 2: 快速点击一次
|
|
87
|
+
await mouse.click(box_a_box["x"] + 30, box_a_box["y"] + 30)
|
|
88
|
+
|
|
89
|
+
# 🧲 Step 3: 拖拽 Box A 到 Box B
|
|
90
|
+
await mouse.move(box_a_box["x"] + 30, box_a_box["y"] + 30)
|
|
91
|
+
await mouse.down()
|
|
92
|
+
await mouse.move(box_b_box["x"] + 30, box_b_box["y"] + 30, steps=50)
|
|
93
|
+
await mouse.up()
|
|
94
|
+
|
|
95
|
+
# 🎡 Step 4: 鼠标绕 Box B 旋转一圈
|
|
96
|
+
center_x = box_b_box["x"] + box_b_box["width"] / 2
|
|
97
|
+
center_y = box_b_box["y"] + box_b_box["height"] / 2
|
|
98
|
+
radius = 40
|
|
99
|
+
for angle in range(0, 361, 10):
|
|
100
|
+
rad = (angle * math.pi) / 180
|
|
101
|
+
x = center_x + radius * math.cos(rad)
|
|
102
|
+
y = center_y + radius * math.sin(rad)
|
|
103
|
+
await mouse.move(x, y)
|
|
104
|
+
|
|
105
|
+
# 👋 Step 5: 优雅滑出屏幕
|
|
106
|
+
await mouse.move(center_x + 100, center_y + 500, steps=30)
|
|
107
|
+
|
|
108
|
+
# 下载liveview 截屏
|
|
109
|
+
# screenshots_dir = await session.browser.get_replay_screenshots()
|
|
110
|
+
# print(screenshots_dir)
|
|
111
|
+
|
|
112
|
+
command = await terminal.run_command(
|
|
113
|
+
"cd /home/user/downloads/grasp-screenshots && ls -1 | grep -v '^filelist.txt$' | sort | awk '{print \"file '\\''\" $0 \"'\\''\"}' > filelist.txt"
|
|
114
|
+
)
|
|
115
|
+
await command.end()
|
|
116
|
+
|
|
117
|
+
command2 = await terminal.run_command(
|
|
118
|
+
"cd /home/user/downloads/grasp-screenshots && ffmpeg -r 25 -f concat -safe 0 -i filelist.txt -vsync vfr -pix_fmt yuv420p output.mp4"
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
# Python 中处理流输出的方式
|
|
122
|
+
def handle_stdout(data):
|
|
123
|
+
print(data, end="")
|
|
124
|
+
|
|
125
|
+
def handle_stderr(data):
|
|
126
|
+
print(data, end="")
|
|
127
|
+
|
|
128
|
+
# 注册事件处理器
|
|
129
|
+
command2.on('stdout', handle_stdout)
|
|
130
|
+
command2.on('stderr', handle_stderr)
|
|
131
|
+
|
|
132
|
+
await command2.end()
|
|
133
|
+
|
|
134
|
+
# 创建 ./output 目录
|
|
135
|
+
Path("./output").mkdir(parents=True, exist_ok=True)
|
|
136
|
+
|
|
137
|
+
await asyncio.gather(
|
|
138
|
+
session.files.download_file(
|
|
139
|
+
"/home/user/downloads/grasp-screenshots/filelist.txt",
|
|
140
|
+
"./output/filelist.txt"
|
|
141
|
+
),
|
|
142
|
+
session.files.download_file(
|
|
143
|
+
"/home/user/downloads/grasp-screenshots/output.mp4",
|
|
144
|
+
"./output/output.mp4"
|
|
145
|
+
),
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
await session.close()
|
|
149
|
+
|
|
150
|
+
if __name__ == "__main__":
|
|
151
|
+
asyncio.run(main())
|
|
@@ -111,7 +111,7 @@ async def test_terminal_service():
|
|
|
111
111
|
mock_sandbox.run_command.return_value = mock_emitter
|
|
112
112
|
|
|
113
113
|
# Test run_command
|
|
114
|
-
result = await terminal.run_command('echo "Hello World"', {'
|
|
114
|
+
result = await terminal.run_command('echo "Hello World"', {'timeoutMs': 5000})
|
|
115
115
|
print("✓ run_command executed successfully")
|
|
116
116
|
|
|
117
117
|
# Verify the command was called with correct options
|
|
@@ -152,7 +152,7 @@ async def test_command_options_compatibility():
|
|
|
152
152
|
# Test that ICommandOptions supports 'inBackground' parameter
|
|
153
153
|
options: ICommandOptions = {
|
|
154
154
|
'inBackground': True,
|
|
155
|
-
'
|
|
155
|
+
'timeoutMs': 5000,
|
|
156
156
|
'cwd': '/tmp',
|
|
157
157
|
'nohup': False
|
|
158
158
|
}
|
|
@@ -25,7 +25,7 @@ from .grasp import (
|
|
|
25
25
|
# Import CDPConnection for type annotation
|
|
26
26
|
from .services.browser import CDPConnection
|
|
27
27
|
|
|
28
|
-
__version__ = "0.2.
|
|
28
|
+
__version__ = "0.2.0b3"
|
|
29
29
|
__author__ = "Grasp Team"
|
|
30
30
|
__email__ = "team@grasp.dev"
|
|
31
31
|
|
|
@@ -165,7 +165,6 @@ __all__ = [
|
|
|
165
165
|
'Grasp',
|
|
166
166
|
'GraspSession',
|
|
167
167
|
'GraspBrowser',
|
|
168
|
-
'GraspTerminal',
|
|
169
168
|
'launch_browser',
|
|
170
169
|
'shutdown',
|
|
171
170
|
]
|
|
@@ -30,6 +30,7 @@ class GraspServer:
|
|
|
30
30
|
browser_type = sandbox_config.pop('type', 'chromium')
|
|
31
31
|
headless = sandbox_config.pop('headless', True)
|
|
32
32
|
adblock = sandbox_config.pop('adblock', False)
|
|
33
|
+
liveview = sandbox_config.pop('liveview', False)
|
|
33
34
|
logLevel = sandbox_config.pop('logLevel', '')
|
|
34
35
|
keepAliveMS = sandbox_config.pop('keepAliveMS', 0)
|
|
35
36
|
|
|
@@ -45,6 +46,7 @@ class GraspServer:
|
|
|
45
46
|
'envs': {
|
|
46
47
|
'ADBLOCK': 'true' if adblock else 'false',
|
|
47
48
|
'KEEP_ALIVE_MS': str(keepAliveMS),
|
|
49
|
+
'ENABLE_LIVEVIEW': 'true' if liveview else 'false',
|
|
48
50
|
}
|
|
49
51
|
}
|
|
50
52
|
|
|
@@ -37,7 +37,7 @@ class IBrowserConfig(TypedDict):
|
|
|
37
37
|
class ICommandOptions(TypedDict):
|
|
38
38
|
"""Command execution options interface."""
|
|
39
39
|
inBackground: NotRequired[bool] # Whether to run command in background (corresponds to 'background' in TypeScript)
|
|
40
|
-
|
|
40
|
+
timeoutMs: NotRequired[int] # Timeout in milliseconds (corresponds to 'timeoutMs' in TypeScript)
|
|
41
41
|
cwd: NotRequired[str] # Working directory
|
|
42
42
|
user: NotRequired[str] # User to run the command as (default: 'user')
|
|
43
43
|
envs: NotRequired[Dict[str, str]] # Environment variables for the command
|
|
@@ -49,7 +49,7 @@ class IScriptOptions(TypedDict):
|
|
|
49
49
|
"""Script execution options interface."""
|
|
50
50
|
type: str # Required: Script type: 'cjs' for CommonJS, 'esm' for ES Modules, 'py' for Python
|
|
51
51
|
cwd: NotRequired[str] # Working directory
|
|
52
|
-
|
|
52
|
+
timeoutMs: NotRequired[int] # Timeout in milliseconds
|
|
53
53
|
background: NotRequired[bool] # Run in background
|
|
54
54
|
user: NotRequired[str] # User to run the script as (default: 'user')
|
|
55
55
|
nohup: NotRequired[bool] # Use nohup for background execution (Grasp-specific extension)
|
|
@@ -286,7 +286,7 @@ class SandboxService:
|
|
|
286
286
|
options = {}
|
|
287
287
|
|
|
288
288
|
cwd = options.get('cwd', self.DEFAULT_WORKING_DIRECTORY)
|
|
289
|
-
|
|
289
|
+
timeoutMs = options.get('timeoutMs', 0)
|
|
290
290
|
use_nohup = options.get('nohup', False)
|
|
291
291
|
in_background = options.get('inBackground', False)
|
|
292
292
|
envs = options.get('envs', {})
|
|
@@ -299,7 +299,7 @@ class SandboxService:
|
|
|
299
299
|
self.logger.debug('Running command in sandbox', {
|
|
300
300
|
'command': command,
|
|
301
301
|
'cwd': cwd,
|
|
302
|
-
'timeout':
|
|
302
|
+
'timeout': timeoutMs,
|
|
303
303
|
'nohup': use_nohup,
|
|
304
304
|
'background': in_background
|
|
305
305
|
})
|
|
@@ -319,7 +319,7 @@ class SandboxService:
|
|
|
319
319
|
result = await self.sandbox.commands.run(
|
|
320
320
|
final_command,
|
|
321
321
|
cwd=cwd,
|
|
322
|
-
timeout=
|
|
322
|
+
timeout=timeoutMs // 1000,
|
|
323
323
|
envs=envs,
|
|
324
324
|
background=in_background,
|
|
325
325
|
user=user,
|
|
@@ -345,7 +345,7 @@ class SandboxService:
|
|
|
345
345
|
await self.sandbox.commands.run(
|
|
346
346
|
nohup_command,
|
|
347
347
|
cwd=cwd,
|
|
348
|
-
timeout=
|
|
348
|
+
timeout=timeoutMs // 1000,
|
|
349
349
|
envs=envs,
|
|
350
350
|
background=in_background,
|
|
351
351
|
user=user,
|
|
@@ -365,7 +365,7 @@ class SandboxService:
|
|
|
365
365
|
handle = await commands_attr.run(
|
|
366
366
|
command,
|
|
367
367
|
cwd=cwd,
|
|
368
|
-
timeout=
|
|
368
|
+
timeout=timeoutMs // 1000,
|
|
369
369
|
background=True,
|
|
370
370
|
envs=envs,
|
|
371
371
|
user=user,
|
|
@@ -473,7 +473,7 @@ class SandboxService:
|
|
|
473
473
|
# Prepare command options
|
|
474
474
|
cmd_options: Optional[Any] = {
|
|
475
475
|
'cwd': options.get('cwd'),
|
|
476
|
-
'
|
|
476
|
+
'timeoutMs': options.get('timeoutMs', 0),
|
|
477
477
|
'inBackground': options.get('background', False),
|
|
478
478
|
'nohup': options.get('nohup', False),
|
|
479
479
|
'envs': envs,
|
|
@@ -12,6 +12,7 @@ setup.py
|
|
|
12
12
|
./examples/example_binary_file_support.py
|
|
13
13
|
./examples/example_grasp_usage.py
|
|
14
14
|
./examples/example_readfile_usage.py
|
|
15
|
+
./examples/grasp-mouse-usage-2.py
|
|
15
16
|
./examples/grasp_terminal.py
|
|
16
17
|
./examples/grasp_usage.py
|
|
17
18
|
./examples/test_async_context.py
|
|
@@ -40,6 +41,7 @@ examples/example_async_context.py
|
|
|
40
41
|
examples/example_binary_file_support.py
|
|
41
42
|
examples/example_grasp_usage.py
|
|
42
43
|
examples/example_readfile_usage.py
|
|
44
|
+
examples/grasp-mouse-usage-2.py
|
|
43
45
|
examples/grasp_terminal.py
|
|
44
46
|
examples/grasp_usage.py
|
|
45
47
|
examples/test_async_context.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|