mobile-mcp-ai 2.6.12__py3-none-any.whl → 2.7.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.
@@ -2197,6 +2197,13 @@ class BasicMobileToolsLite:
2197
2197
  def wait(self, seconds: float) -> Dict:
2198
2198
  """等待指定时间"""
2199
2199
  time.sleep(seconds)
2200
+ # 记录等待操作
2201
+ record = {
2202
+ 'action': 'wait',
2203
+ 'timestamp': datetime.now().isoformat(),
2204
+ 'seconds': seconds,
2205
+ }
2206
+ self.operation_history.append(record)
2200
2207
  return {"success": True}
2201
2208
 
2202
2209
  # ==================== 应用管理 ====================
@@ -2353,6 +2360,15 @@ class BasicMobileToolsLite:
2353
2360
  '_shadow', 'shadow_', '_divider', 'divider_', '_line', 'line_'
2354
2361
  }
2355
2362
 
2363
+ # 状态栏相关关键词(这些元素对测试没有意义,直接过滤)
2364
+ STATUS_BAR_KEYWORDS = {
2365
+ 'status_bar', 'statusbar', 'notification_icon', 'notificationicons',
2366
+ 'system_icons', 'statusicons', 'battery', 'wifi_', 'wifi_combo',
2367
+ 'wifi_group', 'wifi_signal', 'wifi_in', 'wifi_out', 'signal_',
2368
+ 'clock', 'cutout', 'networkspeed', 'speed_container',
2369
+ 'carrier', 'operator', 'sim_', 'mobile_signal'
2370
+ }
2371
+
2356
2372
  # Token 优化:构建精简元素(只返回非空字段)
2357
2373
  def build_compact_element(resource_id, text, content_desc, bounds, likely_click, class_name):
2358
2374
  """只返回有值的字段,节省 token"""
@@ -2390,6 +2406,12 @@ class BasicMobileToolsLite:
2390
2406
  if bounds == '[0,0][0,0]':
2391
2407
  continue
2392
2408
 
2409
+ # 1.5 过滤状态栏元素(对测试没有意义)
2410
+ if resource_id:
2411
+ resource_id_lower = resource_id.lower()
2412
+ if any(keyword in resource_id_lower for keyword in STATUS_BAR_KEYWORDS):
2413
+ continue
2414
+
2393
2415
  # 2. 检查是否是功能控件(直接保留)
2394
2416
  if class_name in FUNCTIONAL_WIDGETS:
2395
2417
  # 使用启发式判断可点击性(替代不准确的 clickable 属性)
@@ -3379,6 +3401,15 @@ class BasicMobileToolsLite:
3379
3401
 
3380
3402
  # 生成脚本
3381
3403
  safe_name = re.sub(r'[^\w\s-]', '', test_name).strip().replace(' ', '_')
3404
+ # 确保 safe_name 不为空,否则使用默认名称
3405
+ if not safe_name:
3406
+ safe_name = 'generated_case'
3407
+
3408
+ # 提前处理文件名,确保文档字符串中的文件名正确
3409
+ if not filename.endswith('.py'):
3410
+ filename = f"{filename}.py"
3411
+ if not filename.startswith('test_'):
3412
+ filename = f"test_{filename}"
3382
3413
 
3383
3414
  script_lines = [
3384
3415
  "#!/usr/bin/env python3",
@@ -3393,8 +3424,8 @@ class BasicMobileToolsLite:
3393
3424
  "3. 百分比定位 - 跨分辨率兼容(坐标自动转换)",
3394
3425
  "",
3395
3426
  "运行方式:",
3396
- " pytest {filename} -v # 使用 pytest 运行",
3397
- " python {filename} # 直接运行",
3427
+ f" pytest {filename} -v # 使用 pytest 运行",
3428
+ f" python {filename} # 直接运行",
3398
3429
  f'"""',
3399
3430
  "import time",
3400
3431
  "import pytest",
@@ -3655,6 +3686,12 @@ class BasicMobileToolsLite:
3655
3686
  script_lines.append(f" d.press('{key}')")
3656
3687
  script_lines.append(" time.sleep(0.5)")
3657
3688
  script_lines.append(" ")
3689
+
3690
+ elif action == 'wait':
3691
+ seconds = op.get('seconds', 1)
3692
+ script_lines.append(f" # 步骤{step_num}: 等待 {seconds} 秒")
3693
+ script_lines.append(f" time.sleep({seconds})")
3694
+ script_lines.append(" ")
3658
3695
 
3659
3696
  script_lines.extend([
3660
3697
  " print('✅ 测试完成')",
@@ -3678,12 +3715,6 @@ class BasicMobileToolsLite:
3678
3715
  output_dir = Path("tests")
3679
3716
  output_dir.mkdir(exist_ok=True)
3680
3717
 
3681
- # 确保文件名符合 pytest 规范(以 test_ 开头)
3682
- if not filename.endswith('.py'):
3683
- filename = f"{filename}.py"
3684
- if not filename.startswith('test_'):
3685
- filename = f"test_{filename}"
3686
-
3687
3718
  file_path = output_dir / filename
3688
3719
  file_path.write_text(script, encoding='utf-8')
3689
3720
 
@@ -204,6 +204,115 @@ class MobileMCPServer:
204
204
 
205
205
  return "android"
206
206
 
207
+ def _open_new_chat(self, message: str = "继续执行飞书用例", delay: float = 5) -> dict:
208
+ """
209
+ 打开新Chat窗口并发送消息
210
+
211
+ 原理:用后台线程延迟执行键盘操作,避免打断当前AI响应
212
+ 跨平台统一用 pyautogui + pyperclip,配合系统API激活窗口
213
+ """
214
+ import threading
215
+ import platform
216
+
217
+ def delayed_action():
218
+ import time
219
+ time.sleep(delay) # 等待当前响应结束
220
+
221
+ try:
222
+ system = platform.system()
223
+
224
+ if system == "Darwin": # ========== macOS: 纯 AppleScript(无需额外安装)==========
225
+ import subprocess
226
+
227
+ # AppleScript: 激活Cursor → Cmd+L聚焦Chat → Cmd+N新建 → 粘贴 → 回车
228
+ script = f'''
229
+ -- 设置剪贴板(支持中文)
230
+ set the clipboard to "{message}"
231
+
232
+ -- 激活 Cursor
233
+ tell application "Cursor" to activate
234
+ delay 0.5
235
+
236
+ tell application "System Events"
237
+ tell process "Cursor"
238
+ -- Cmd+L 聚焦 Chat
239
+ keystroke "l" using command down
240
+ delay 0.5
241
+ -- Cmd+N 新建对话
242
+ keystroke "n" using command down
243
+ delay 1
244
+ -- Cmd+V 粘贴
245
+ keystroke "v" using command down
246
+ delay 0.3
247
+ -- Enter 发送
248
+ key code 36
249
+ end tell
250
+ end tell
251
+ '''
252
+ result = subprocess.run(['osascript', '-e', script], capture_output=True, text=True)
253
+ if result.returncode == 0:
254
+ print(f"[open_new_chat] macOS成功: {message}", file=sys.stderr)
255
+ else:
256
+ print(f"[open_new_chat] AppleScript错误: {result.stderr}", file=sys.stderr)
257
+
258
+ elif system == "Windows": # ========== Windows: 需要 pip install pyautogui pyperclip ==========
259
+ import pyautogui
260
+ import pyperclip
261
+
262
+ # 尝试激活 Cursor 窗口
263
+ try:
264
+ import pygetwindow as gw
265
+ windows = gw.getWindowsWithTitle('Cursor')
266
+ if windows:
267
+ windows[0].activate()
268
+ time.sleep(0.5)
269
+ except ImportError:
270
+ pass
271
+
272
+ pyautogui.PAUSE = 0.3
273
+
274
+ # Ctrl+L 聚焦 Chat → Ctrl+N 新建
275
+ pyautogui.hotkey('ctrl', 'l')
276
+ time.sleep(0.5)
277
+ pyautogui.hotkey('ctrl', 'n')
278
+ time.sleep(1)
279
+
280
+ # 粘贴消息
281
+ pyperclip.copy(message)
282
+ pyautogui.hotkey('ctrl', 'v')
283
+ time.sleep(0.3)
284
+ pyautogui.press('enter')
285
+
286
+ print(f"[open_new_chat] Windows成功: {message}", file=sys.stderr)
287
+
288
+ else: # Linux: 写信号文件
289
+ from pathlib import Path
290
+ signal_file = Path(__file__).parent.parent / ".new_chat_signal"
291
+ signal_file.write_text(message)
292
+ print(f"[open_new_chat] Linux: 已写入信号文件", file=sys.stderr)
293
+
294
+ except ImportError as e:
295
+ # Windows 缺少依赖时写信号文件
296
+ from pathlib import Path
297
+ signal_file = Path(__file__).parent.parent / ".new_chat_signal"
298
+ signal_file.write_text(message)
299
+ print(f"[open_new_chat] Windows缺少依赖,请运行: pip install pyautogui pyperclip", file=sys.stderr)
300
+
301
+ except Exception as e:
302
+ # 写错误日志
303
+ import sys
304
+ print(f"[open_new_chat] 错误: {e}", file=sys.stderr)
305
+
306
+ # 后台线程执行,不阻塞MCP响应
307
+ thread = threading.Thread(target=delayed_action, daemon=True)
308
+ thread.start()
309
+
310
+ return {
311
+ "success": True,
312
+ "message": f"⏰ {delay}秒后将打开新Chat并发送: {message}",
313
+ "tip": "请不要手动操作,等待自动执行"
314
+ }
315
+
207
316
  def get_tools(self):
208
317
  """注册 MCP 工具"""
209
318
  tools = []
@@ -226,36 +335,7 @@ class MobileMCPServer:
226
335
 
227
336
  tools.append(Tool(
228
337
  name="mobile_list_elements",
229
- description="""📋 列出页面所有可交互元素(⭐⭐ 第一优先级!)
230
-
231
- ⚠️ 【核心原则】优先使用控件树定位,截图仅作为兜底方案
232
-
233
- 🎯 使用场景(优先使用):
234
- 1. ✅ 首次进入页面时:获取所有可交互元素
235
- 2. ✅ 查找特定元素时:检查元素是否存在
236
- 3. ✅ 验证操作结果时:确认元素是否出现/消失
237
- 4. ✅ 需要精确点击时:获取元素的 resource_id 或 text
238
-
239
- 📌 控件树定位优势:
240
- - ⚡ 速度快:直接获取 XML,无需截图和图像处理(快 5-20 倍)
241
- - 🎯 准确:获取完整的元素属性(resource_id、text、bounds、clickable 等)
242
- - 💾 高效:不生成图片文件,节省存储和传输
243
- - 🔄 可复用:一次获取的元素列表可用于多次操作
244
-
245
- ⚡ 推荐流程:
246
- 1. 调用 mobile_list_elements() 获取元素列表
247
- 2. 从元素列表中找到目标元素(通过 text 或 resource_id)
248
- 3. 使用 mobile_click_by_text() 或 mobile_click_by_id() 点击
249
- 4. 仅在控件树找不到元素时,才使用 mobile_screenshot_with_som()
250
-
251
- ❌ 错误用法:
252
- - 每次都先截图,不先调用 list_elements
253
- - 重复调用 list_elements,不复用已获取的元素列表
254
-
255
- ✅ 正确用法:
256
- elements = mobile_list_elements() # 一次获取
257
- mobile_click_by_text("设置") # 直接点击
258
- mobile_click_by_text("退出登录") # 复用元素列表""",
338
+ description=desc_list_elements,
259
339
  inputSchema={"type": "object", "properties": {}, "required": []}
260
340
  ))
261
341
 
@@ -309,49 +389,8 @@ mobile_click_by_text("退出登录") # 复用元素列表""",
309
389
 
310
390
  tools.append(Tool(
311
391
  name="mobile_screenshot_with_som",
312
- description="""📸🏷️ Set-of-Mark 截图(⚠️ 兜底方案,非首选)
313
-
314
- ⚠️ 【重要】仅在控件树定位失败时使用!
315
-
316
- 🎯 使用场景(兜底方案):
317
- 1. ⚠️ 控件树定位失败:
318
- - mobile_list_elements() 返回空或找不到目标元素
319
- - 元素不在控件树中(如游戏、Unity 应用)
320
- 2. ✅ 需要视觉确认:
321
- - 首次进入新页面,需要了解整体布局
322
- - 操作后需要视觉确认页面变化
323
- - 需要给用户展示页面状态
324
-
325
- ⚡ 推荐流程(优先使用控件树):
326
- 1. 先调用 mobile_list_elements() 获取元素列表
327
- 2. 如果找到目标元素 → 使用 mobile_click_by_text() 或 mobile_click_by_id()
328
- 3. 如果找不到 → 才调用 mobile_screenshot_with_som()
329
- 4. 从截图分析找到元素编号 → 使用 mobile_click_by_som(编号)
330
-
331
- ❌ 错误用法:
332
- - 每次都先截图,不先调用 list_elements
333
- - 在控件树能找到元素时也使用截图
334
-
335
- ✅ 正确用法:
336
- elements = mobile_list_elements() # 先尝试控件树
337
- if not find_target(elements):
338
- screenshot = mobile_screenshot_with_som() # 控件树失败才截图
339
- mobile_click_by_som(index)
340
-
341
- 💡 弹窗检测:
342
- - check_popup=True: 明确弹窗场景时使用(如调用 mobile_close_popup 前)
343
- - check_popup=False: 普通截图,不检测弹窗(默认)""",
344
- inputSchema={
345
- "type": "object",
346
- "properties": {
347
- "check_popup": {
348
- "type": "boolean",
349
- "description": "是否检测弹窗,默认 False。仅在明确弹窗场景时设置为 True",
350
- "default": False
351
- }
352
- },
353
- "required": []
354
- }
392
+ description=desc_som,
393
+ inputSchema={"type": "object", "properties": {}, "required": []}
355
394
  ))
356
395
 
357
396
  tools.append(Tool(
@@ -397,31 +436,7 @@ if not find_target(elements):
397
436
 
398
437
  tools.append(Tool(
399
438
  name="mobile_click_by_text",
400
- description="""👆 通过文本点击元素(⭐⭐ 第一优先级点击方式)
401
-
402
- ✅ 最稳定的定位方式,跨设备兼容
403
- ✅ 实时检测元素是否存在,元素不存在会报错
404
- ✅ 不会误点击到其他位置
405
-
406
- ⚡ 使用流程(必须):
407
- 1. 先调用 mobile_list_elements() 获取元素列表
408
- 2. 从元素列表中找到目标元素的 text
409
- 3. 调用 mobile_click_by_text("文本") 点击
410
-
411
- 💡 定位优先级:文本 > ID > 百分比 > 坐标
412
-
413
- 📍 当页面有多个相同文案时,可使用 position 参数指定位置:
414
- - 垂直方向: "top"/"upper"/"上", "bottom"/"lower"/"下", "middle"/"center"/"中"
415
- - 水平方向: "left"/"左", "right"/"右", "center"/"中"
416
- 例如:点击"底部"的"微剧"tab,使用 position="bottom"
417
-
418
- ❌ 错误用法:
419
- - 不先调用 list_elements,直接猜测文本点击
420
- - 在控件树能找到元素时使用截图+坐标点击
421
-
422
- ✅ 正确用法:
423
- elements = mobile_list_elements() # 先获取元素列表
424
- mobile_click_by_text("设置") # 从元素列表中找到后直接点击""",
439
+ description=desc_click_text,
425
440
  inputSchema={
426
441
  "type": "object",
427
442
  "properties": {
@@ -881,6 +896,20 @@ mobile_click_by_text("设置") # 从元素列表中找到后直接点击"
881
896
  }
882
897
  ))
883
898
 
899
+ # ==================== Cursor自动化 ====================
900
+ tools.append(Tool(
901
+ name="mobile_open_new_chat",
902
+ description="🔄 打开新Chat继续执行。执行完10个用例后调用,会延迟5秒后按Cmd+T打开新会话并输入继续命令。",
903
+ inputSchema={
904
+ "type": "object",
905
+ "properties": {
906
+ "message": {"type": "string", "description": "新会话中要发送的消息", "default": "继续执行飞书用例"},
907
+ "delay": {"type": "number", "description": "延迟秒数(等待当前响应结束)", "default": 5}
908
+ },
909
+ "required": []
910
+ }
911
+ ))
912
+
884
913
  return tools
885
914
 
886
915
  async def handle_tool_call(self, name: str, arguments: dict):
@@ -1145,6 +1174,13 @@ mobile_click_by_text("设置") # 从元素列表中找到后直接点击"
1145
1174
  result = {"success": False, "error": "请提供 x_percent/y_percent 或 screenshot_path/x/y/width/height"}
1146
1175
  return [TextContent(type="text", text=self.format_response(result))]
1147
1176
 
1177
+ # Cursor自动化:打开新Chat
1178
+ elif name == "mobile_open_new_chat":
1179
+ message = arguments.get("message", "继续执行飞书用例")
1180
+ delay = arguments.get("delay", 5)
1181
+ result = self._open_new_chat(message, delay)
1182
+ return [TextContent(type="text", text=self.format_response(result))]
1183
+
1148
1184
  else:
1149
1185
  return [TextContent(type="text", text=f"❌ 未知工具: {name}")]
1150
1186
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mobile-mcp-ai
3
- Version: 2.6.12
3
+ Version: 2.7.0
4
4
  Summary: 移动端自动化 MCP Server - 支持 Android/iOS,AI 功能可选(基础工具不需要 AI)
5
5
  Home-page: https://github.com/test111ddff-hash/mobile-mcp-ai
6
6
  Author: douzi
@@ -46,6 +46,10 @@ Requires-Dist: facebook-wda>=1.4.0; extra == "ios"
46
46
  Provides-Extra: h5
47
47
  Requires-Dist: Appium-Python-Client>=3.0.0; extra == "h5"
48
48
  Requires-Dist: selenium>=4.0.0; extra == "h5"
49
+ Provides-Extra: windows
50
+ Requires-Dist: pyautogui>=0.9.0; extra == "windows"
51
+ Requires-Dist: pyperclip>=1.8.0; extra == "windows"
52
+ Requires-Dist: pygetwindow>=0.0.9; extra == "windows"
49
53
  Provides-Extra: all
50
54
  Requires-Dist: dashscope>=1.10.0; extra == "all"
51
55
  Requires-Dist: openai>=1.0.0; extra == "all"
@@ -55,6 +59,9 @@ Requires-Dist: selenium>=4.0.0; extra == "all"
55
59
  Requires-Dist: pytest>=8.0.0; extra == "all"
56
60
  Requires-Dist: pytest-asyncio>=0.21.0; extra == "all"
57
61
  Requires-Dist: allure-pytest>=2.13.0; extra == "all"
62
+ Requires-Dist: pyautogui>=0.9.0; extra == "all"
63
+ Requires-Dist: pyperclip>=1.8.0; extra == "all"
64
+ Requires-Dist: pygetwindow>=0.0.9; extra == "all"
58
65
  Dynamic: author
59
66
  Dynamic: author-email
60
67
  Dynamic: classifier
@@ -336,6 +343,34 @@ tidevice list
336
343
 
337
344
  保存后**重启 Cursor**。
338
345
 
346
+ ### 批量执行用例(飞书集成)
347
+
348
+ 如果你需要从飞书多维表格批量执行用例,`mobile_open_new_chat` 功能会自动打开新会话继续执行。
349
+
350
+ **macOS 用户:** 需要开启辅助功能权限
351
+
352
+ | 步骤 | 操作 |
353
+ |:---:|------|
354
+ | 1 | 打开「系统设置」 |
355
+ | 2 | 点击「隐私与安全性」 |
356
+ | 3 | 点击「辅助功能」 |
357
+ | 4 | 点击 + 号,添加 **Cursor.app** |
358
+ | 5 | 确保开关已打开 ✅ |
359
+
360
+ > ⚠️ 没有此权限,无法自动打开新会话继续执行
361
+
362
+ **Windows 用户:** 需要安装额外依赖
363
+
364
+ ```bash
365
+ pip install mobile-mcp-ai[windows]
366
+ ```
367
+
368
+ 或手动安装:
369
+
370
+ ```bash
371
+ pip install pyautogui pyperclip pygetwindow
372
+ ```
373
+
339
374
  ---
340
375
 
341
376
  ## 🚀 使用示例
@@ -404,24 +439,17 @@ tidevice list
404
439
 
405
440
  ## 🛠️ 工具列表
406
441
 
407
- | 类别 | 工具 | 说明 | 优先级 |
408
- |:---:|------|------|:---:|
409
- | 📋 | `mobile_list_elements` | 列出页面元素 | ⭐⭐⭐ 第一优先级 |
410
- | 👆 | `mobile_click_by_text` | 文本点击 | ⭐⭐⭐ 第一优先级 |
411
- | 👆 | `mobile_click_by_id` | ID 点击 | ⭐⭐⭐ 第一优先级 |
412
- | 📸 | `mobile_screenshot_with_som` | Set-of-Mark 截图(智能标注) | ⚠️ 兜底方案 |
413
- | 📸 | `mobile_take_screenshot` | 截图 | ⚠️ 兜底方案 |
414
- | 📸 | `mobile_screenshot_with_grid` | 带网格坐标的截图 | ⚠️ 兜底方案 |
415
- | 👆 | `mobile_click_by_som` | SoM 编号点击 | ⚠️ 兜底方案 |
416
- | 👆 | `mobile_click_by_percent` | 百分比点击 | ⚠️ 最后兜底 |
417
- | 👆 | `mobile_click_at_coords` | 坐标点击 | ⚠️ 最后兜底 |
418
- | 📐 | `mobile_get_screen_size` | 屏幕尺寸 | 辅助工具 |
419
-
420
- > 💡 **工具选择策略**:优先使用控件树定位(`list_elements` + `click_by_text/id`),截图仅作为兜底方案。
421
- >
422
- > 📖 详细指南:
423
- > - [工具选择策略指南](docs/TOOL_SELECTION_STRATEGY.md) - 详细的工具选择策略和决策树
424
- > - [用例执行最佳实践](docs/EXECUTION_BEST_PRACTICES.md) - 用例执行流程和优化建议
442
+ | 类别 | 工具 | 说明 |
443
+ |:---:|------|------|
444
+ | 📋 | `mobile_list_elements` | 列出页面元素 |
445
+ | 📸 | `mobile_take_screenshot` | 截图 |
446
+ | 📸 | `mobile_screenshot_with_som` | Set-of-Mark 截图(智能标注) |
447
+ | 📸 | `mobile_screenshot_with_grid` | 带网格坐标的截图 |
448
+ | 📐 | `mobile_get_screen_size` | 屏幕尺寸 |
449
+ | 👆 | `mobile_click_by_text` | 文本点击 |
450
+ | 👆 | `mobile_click_by_id` | ID 点击 |
451
+ | 👆 | `mobile_click_at_coords` | 坐标点击 |
452
+ | 👆 | `mobile_click_by_percent` | 百分比点击 |
425
453
  | 👆 | `mobile_click_by_som` | SoM 编号点击 |
426
454
  | 👆 | `mobile_long_press_by_id` | ID 长按 |
427
455
  | 👆 | `mobile_long_press_by_text` | 文本长按 |
@@ -1,14 +1,13 @@
1
1
  mobile_mcp/__init__.py,sha256=sQJZTL_sxQFzmcS7jOtS2AHCfUySz40vhX96N6u1qy4,816
2
2
  mobile_mcp/config.py,sha256=-xSl9vahp3EFAA97P1ahcnQC-HHAFvccGHpnFAXeKHU,5841
3
3
  mobile_mcp/core/__init__.py,sha256=ndMy-cLAIsQDG5op7gM_AIplycqZSZPWEkec1pEhvEY,170
4
- mobile_mcp/core/basic_tools_lite.py,sha256=WXOXY-yr97HRQ2TCWgKrTYGWV8ZvvrwpCuhxNiv_mbw,192203
4
+ mobile_mcp/core/basic_tools_lite.py,sha256=8l0zxzFBB-41c_uVcS-5Z4slzawNGa7yu1GglYAkhME,193754
5
5
  mobile_mcp/core/device_manager.py,sha256=xG5DoeNFs45pl-FTEhEWblqVwxtFK-FmVEGlNL6EqRI,8798
6
6
  mobile_mcp/core/dynamic_config.py,sha256=Ja1n1pfb0HspGByqk2_A472mYVniKmGtNEWyjUjmgK8,9811
7
7
  mobile_mcp/core/ios_client_wda.py,sha256=Nq9WxevhTWpVpolM-Ymp-b0nUQV3tXLFszmJHbDC4wA,18770
8
8
  mobile_mcp/core/ios_device_manager_wda.py,sha256=A44glqI-24un7qST-E3w6BQD8mV92YVUbxy4rLlTScY,11264
9
9
  mobile_mcp/core/mobile_client.py,sha256=AaBntQSW2loAw7xL3j_IABNzrJO_Uukf9-F1z1xl6xE,63672
10
10
  mobile_mcp/core/template_matcher.py,sha256=tv8RU6zdeDobqphaP4Y8sicb1esg3gcQlZae1tNyitM,14559
11
- mobile_mcp/core/tool_selection_helper.py,sha256=k5_E4oK_fGg4n6qembvudt6Fw-QXq7_SLJDt4FO3cHc,5764
12
11
  mobile_mcp/core/templates/close_buttons/auto_x_0112_151217.png,sha256=s7tBVaYLBApNSEXjwi5kX8GXwUqgbNyNVEhXYjN9nd4,27373
13
12
  mobile_mcp/core/templates/close_buttons/auto_x_0112_152037.png,sha256=s7tBVaYLBApNSEXjwi5kX8GXwUqgbNyNVEhXYjN9nd4,27373
14
13
  mobile_mcp/core/templates/close_buttons/auto_x_0112_152840.png,sha256=s7tBVaYLBApNSEXjwi5kX8GXwUqgbNyNVEhXYjN9nd4,27373
@@ -20,14 +19,14 @@ mobile_mcp/core/utils/logger.py,sha256=XXQAHUwT1jc70pq_tYFmL6f_nKrFlYm3hcgl-5RYR
20
19
  mobile_mcp/core/utils/operation_history_manager.py,sha256=gi8S8HJAMqvkUrY7_-kVbko3Xt7c4GAUziEujRd-N-Y,4792
21
20
  mobile_mcp/core/utils/smart_wait.py,sha256=N5wKTUYrNWPruBILqrAjpvtso8Z3GRWCfMIR_aZxPLg,8649
22
21
  mobile_mcp/mcp_tools/__init__.py,sha256=xkro8Rwqv_55YlVyhh-3DgRFSsLE3h1r31VIb3bpM6E,143
23
- mobile_mcp/mcp_tools/mcp_server.py,sha256=GAbNZfCkCCsVPZL4s8c0EyDyk7F3otmvos-ZQsdeVoY,51300
22
+ mobile_mcp/mcp_tools/mcp_server.py,sha256=nY_4jrdn4siJtm2HvettTv5bTN0rq9iJvwqBMlI7sMc,53175
24
23
  mobile_mcp/utils/__init__.py,sha256=8EH0i7UGtx1y_j_GEgdN-cZdWn2sRtZSEOLlNF9HRnY,158
25
24
  mobile_mcp/utils/logger.py,sha256=Sqq2Nr0Y4p03erqcrbYKVPCGiFaNGHMcE_JwCkeOfU4,3626
26
25
  mobile_mcp/utils/xml_formatter.py,sha256=uwTRb3vLbqhT8O-udzWT7s7LsV-DyDUz2DkofD3hXOE,4556
27
26
  mobile_mcp/utils/xml_parser.py,sha256=QhL8CWbdmNDzmBLjtx6mEnjHgMFZzJeHpCL15qfXSpI,3926
28
- mobile_mcp_ai-2.6.12.dist-info/licenses/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
29
- mobile_mcp_ai-2.6.12.dist-info/METADATA,sha256=U5l7Vu6A-giC5RiEOq30bgjr3lpuZhge9UhiFFewLXw,11166
30
- mobile_mcp_ai-2.6.12.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
31
- mobile_mcp_ai-2.6.12.dist-info/entry_points.txt,sha256=KB_FglozgPHBprSM1vFbIzGyheFuHFmGanscRdMJ_8A,68
32
- mobile_mcp_ai-2.6.12.dist-info/top_level.txt,sha256=lLm6YpbTv855Lbh8BIA0rPxhybIrvYUzMEk9OErHT94,11
33
- mobile_mcp_ai-2.6.12.dist-info/RECORD,,
27
+ mobile_mcp_ai-2.7.0.dist-info/licenses/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
28
+ mobile_mcp_ai-2.7.0.dist-info/METADATA,sha256=-yl4iprOIClkYPv7YmyRLrDF9QBlRvhkro86tuqYr6U,11505
29
+ mobile_mcp_ai-2.7.0.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
30
+ mobile_mcp_ai-2.7.0.dist-info/entry_points.txt,sha256=KB_FglozgPHBprSM1vFbIzGyheFuHFmGanscRdMJ_8A,68
31
+ mobile_mcp_ai-2.7.0.dist-info/top_level.txt,sha256=lLm6YpbTv855Lbh8BIA0rPxhybIrvYUzMEk9OErHT94,11
32
+ mobile_mcp_ai-2.7.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.9.0)
2
+ Generator: setuptools (80.10.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,168 +0,0 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
- """
4
- 工具选择辅助函数 - 帮助 AI 选择正确的工具
5
-
6
- 这个模块提供工具选择建议,帮助 AI 在执行用例时遵循最佳实践。
7
- """
8
-
9
- from typing import Dict, List, Optional, Tuple
10
-
11
-
12
- class ToolSelectionHelper:
13
- """工具选择辅助类"""
14
-
15
- @staticmethod
16
- def should_use_list_elements(scenario: str) -> Tuple[bool, str]:
17
- """判断是否应该使用 mobile_list_elements()
18
-
19
- Args:
20
- scenario: 使用场景描述
21
-
22
- Returns:
23
- (should_use, reason) 元组
24
- """
25
- scenarios_should_use = [
26
- "查找元素", "定位元素", "点击元素", "查找按钮", "查找文本",
27
- "检查元素", "验证元素", "确认元素", "获取元素",
28
- "进入页面", "新页面", "页面加载",
29
- ]
30
-
31
- scenario_lower = scenario.lower()
32
- for keyword in scenarios_should_use:
33
- if keyword in scenario_lower:
34
- return True, f"场景'{scenario}'应该优先使用 mobile_list_elements() 获取元素列表"
35
-
36
- return False, ""
37
-
38
- @staticmethod
39
- def should_use_screenshot(scenario: str) -> Tuple[bool, str]:
40
- """判断是否应该使用截图
41
-
42
- Args:
43
- scenario: 使用场景描述
44
-
45
- Returns:
46
- (should_use, reason) 元组
47
- """
48
- scenarios_should_use = [
49
- "视觉确认", "查看页面", "页面截图", "展示页面",
50
- "控件树找不到", "元素不存在", "游戏", "Unity",
51
- ]
52
-
53
- scenario_lower = scenario.lower()
54
- for keyword in scenarios_should_use:
55
- if keyword in scenario_lower:
56
- return True, f"场景'{scenario}'可以使用截图作为兜底方案"
57
-
58
- return False, ""
59
-
60
- @staticmethod
61
- def get_tool_selection_advice(action: str, elements: Optional[List[Dict]] = None) -> str:
62
- """获取工具选择建议
63
-
64
- Args:
65
- action: 要执行的操作(如"点击设置按钮")
66
- elements: 已有的元素列表(如果有)
67
-
68
- Returns:
69
- 工具选择建议文本
70
- """
71
- advice = []
72
-
73
- # 如果还没有元素列表,建议先获取
74
- if not elements:
75
- advice.append("💡 建议:先调用 mobile_list_elements() 获取元素列表")
76
- advice.append(" 然后使用 mobile_click_by_text() 或 mobile_click_by_id() 点击")
77
- advice.append(" 仅在控件树找不到元素时,才使用 mobile_screenshot_with_som()")
78
- else:
79
- advice.append("✅ 已有元素列表,可以直接使用 mobile_click_by_text() 或 mobile_click_by_id()")
80
- advice.append(" 无需再次调用 mobile_list_elements() 或截图")
81
-
82
- return "\n".join(advice)
83
-
84
- @staticmethod
85
- def find_element_in_list(elements: List[Dict],
86
- text: Optional[str] = None,
87
- resource_id: Optional[str] = None,
88
- content_desc: Optional[str] = None) -> Optional[Dict]:
89
- """在元素列表中查找元素
90
-
91
- Args:
92
- elements: 元素列表
93
- text: 要查找的文本
94
- resource_id: 要查找的 resource_id
95
- content_desc: 要查找的 content_desc
96
-
97
- Returns:
98
- 找到的元素,如果未找到返回 None
99
- """
100
- for elem in elements:
101
- if text and elem.get("text") == text:
102
- return elem
103
- if resource_id and elem.get("resource_id") == resource_id:
104
- return elem
105
- if content_desc and elem.get("content_desc") == content_desc:
106
- return elem
107
-
108
- return None
109
-
110
- @staticmethod
111
- def suggest_click_method(elements: List[Dict], target_text: str) -> Dict:
112
- """建议点击方法
113
-
114
- Args:
115
- elements: 元素列表
116
- target_text: 目标文本
117
-
118
- Returns:
119
- 建议信息字典
120
- """
121
- # 查找元素
122
- target_elem = ToolSelectionHelper.find_element_in_list(elements, text=target_text)
123
-
124
- if target_elem:
125
- # 元素存在,建议使用文本点击
126
- return {
127
- "found": True,
128
- "method": "mobile_click_by_text",
129
- "params": {"text": target_text},
130
- "reason": f"元素'{target_text}'在控件树中找到,建议使用 mobile_click_by_text('{target_text}')"
131
- }
132
- else:
133
- # 元素不存在,建议使用截图
134
- return {
135
- "found": False,
136
- "method": "mobile_screenshot_with_som",
137
- "params": {},
138
- "reason": f"元素'{target_text}'在控件树中未找到,建议使用 mobile_screenshot_with_som() 作为兜底方案"
139
- }
140
-
141
-
142
- # 导出辅助函数
143
- def should_use_list_elements_first(action: str) -> bool:
144
- """判断是否应该优先使用 list_elements
145
-
146
- Args:
147
- action: 要执行的操作描述
148
-
149
- Returns:
150
- 是否应该优先使用 list_elements
151
- """
152
- helper = ToolSelectionHelper()
153
- should_use, _ = helper.should_use_list_elements(action)
154
- return should_use
155
-
156
-
157
- def get_click_suggestion(elements: List[Dict], target_text: str) -> Dict:
158
- """获取点击建议
159
-
160
- Args:
161
- elements: 元素列表
162
- target_text: 目标文本
163
-
164
- Returns:
165
- 点击建议字典
166
- """
167
- helper = ToolSelectionHelper()
168
- return helper.suggest_click_method(elements, target_text)