mobile-mcp-ai 2.4.2__py3-none-any.whl → 2.5.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.
@@ -0,0 +1,418 @@
1
+ """
2
+ OpenCV 模板匹配器 - 用于精确识别广告弹窗X号
3
+ 核心优势:
4
+ 1. 收集常见X号样式建立模板库
5
+ 2. 多尺度匹配解决分辨率差异
6
+ 3. 返回精确坐标,点击准确率高
7
+ """
8
+
9
+ import os
10
+ import cv2
11
+ import numpy as np
12
+ from typing import Dict, List, Tuple, Optional
13
+ from pathlib import Path
14
+
15
+
16
+ class TemplateMatcher:
17
+ """OpenCV 模板匹配器"""
18
+
19
+ def __init__(self, template_dir: Optional[str] = None):
20
+ """
21
+ 初始化模板匹配器
22
+
23
+ Args:
24
+ template_dir: 模板目录路径,默认为 templates/close_buttons/
25
+ """
26
+ if template_dir is None:
27
+ # 默认模板目录
28
+ base_dir = Path(__file__).parent.parent
29
+ self.template_dir = base_dir / "templates" / "close_buttons"
30
+ else:
31
+ self.template_dir = Path(template_dir)
32
+
33
+ # 确保目录存在
34
+ self.template_dir.mkdir(parents=True, exist_ok=True)
35
+
36
+ # 多尺度匹配的缩放范围
37
+ self.scales = [0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2, 1.3, 1.5, 1.8, 2.0]
38
+
39
+ # 匹配阈值(越高越严格)
40
+ self.match_threshold = 0.75
41
+
42
+ # 缓存加载的模板
43
+ self._template_cache: Dict[str, np.ndarray] = {}
44
+
45
+ def load_templates(self) -> List[Tuple[str, np.ndarray]]:
46
+ """
47
+ 加载所有模板图片
48
+
49
+ Returns:
50
+ List of (template_name, template_image) tuples
51
+ """
52
+ templates = []
53
+
54
+ if not self.template_dir.exists():
55
+ return templates
56
+
57
+ # 支持的图片格式
58
+ extensions = ['.png', '.jpg', '.jpeg', '.bmp']
59
+
60
+ for file in self.template_dir.iterdir():
61
+ if file.suffix.lower() in extensions:
62
+ template_name = file.stem
63
+
64
+ # 使用缓存
65
+ if template_name in self._template_cache:
66
+ templates.append((template_name, self._template_cache[template_name]))
67
+ continue
68
+
69
+ # 读取模板(支持透明通道)
70
+ template = cv2.imread(str(file), cv2.IMREAD_UNCHANGED)
71
+ if template is not None:
72
+ self._template_cache[template_name] = template
73
+ templates.append((template_name, template))
74
+
75
+ return templates
76
+
77
+ def match_single_template(
78
+ self,
79
+ screenshot: np.ndarray,
80
+ template: np.ndarray,
81
+ threshold: Optional[float] = None
82
+ ) -> List[Dict]:
83
+ """
84
+ 单模板多尺度匹配
85
+
86
+ Args:
87
+ screenshot: 截图 (BGR格式)
88
+ template: 模板图片
89
+ threshold: 匹配阈值
90
+
91
+ Returns:
92
+ 匹配结果列表
93
+ """
94
+ if threshold is None:
95
+ threshold = self.match_threshold
96
+
97
+ results = []
98
+
99
+ # 转灰度图
100
+ if len(screenshot.shape) == 3:
101
+ gray_screen = cv2.cvtColor(screenshot, cv2.COLOR_BGR2GRAY)
102
+ else:
103
+ gray_screen = screenshot
104
+
105
+ # 处理模板(可能有透明通道)
106
+ # 注意:不使用 mask,因为 TM_CCOEFF_NORMED + mask 可能返回 INF
107
+ if len(template.shape) == 3:
108
+ if template.shape[2] == 4: # BGRA
109
+ template_gray = cv2.cvtColor(template[:, :, :3], cv2.COLOR_BGR2GRAY)
110
+ else: # BGR
111
+ template_gray = cv2.cvtColor(template, cv2.COLOR_BGR2GRAY)
112
+ else:
113
+ template_gray = template
114
+
115
+ template_h, template_w = template_gray.shape[:2]
116
+
117
+ # 多尺度匹配
118
+ for scale in self.scales:
119
+ # 缩放模板
120
+ new_w = int(template_w * scale)
121
+ new_h = int(template_h * scale)
122
+
123
+ # 跳过太小或太大的模板
124
+ if new_w < 10 or new_h < 10:
125
+ continue
126
+ if new_w > gray_screen.shape[1] or new_h > gray_screen.shape[0]:
127
+ continue
128
+
129
+ resized_template = cv2.resize(template_gray, (new_w, new_h))
130
+
131
+ # 模板匹配
132
+ try:
133
+ result = cv2.matchTemplate(
134
+ gray_screen, resized_template,
135
+ cv2.TM_CCOEFF_NORMED
136
+ )
137
+ except cv2.error:
138
+ continue
139
+
140
+ # 跳过包含 INF/NAN 的结果
141
+ if np.isinf(result).any() or np.isnan(result).any():
142
+ continue
143
+
144
+ # 找所有超过阈值的匹配点
145
+ locations = np.where(result >= threshold)
146
+
147
+ for pt in zip(*locations[::-1]): # (x, y)
148
+ confidence = float(result[pt[1], pt[0]])
149
+ center_x = int(pt[0] + new_w // 2)
150
+ center_y = int(pt[1] + new_h // 2)
151
+
152
+ results.append({
153
+ 'x': center_x,
154
+ 'y': center_y,
155
+ 'width': int(new_w),
156
+ 'height': int(new_h),
157
+ 'scale': float(scale),
158
+ 'confidence': confidence,
159
+ 'top_left': (int(pt[0]), int(pt[1])),
160
+ 'bottom_right': (int(pt[0] + new_w), int(pt[1] + new_h))
161
+ })
162
+
163
+ # 非极大值抑制(去除重叠的检测框)
164
+ results = self._non_max_suppression(results)
165
+
166
+ return results
167
+
168
+ def _non_max_suppression(self, results: List[Dict], overlap_thresh: float = 0.3) -> List[Dict]:
169
+ """
170
+ 非极大值抑制,去除重叠的检测框
171
+ """
172
+ if len(results) == 0:
173
+ return []
174
+
175
+ # 按置信度排序
176
+ results = sorted(results, key=lambda x: x['confidence'], reverse=True)
177
+
178
+ kept = []
179
+ for result in results:
180
+ is_duplicate = False
181
+ for kept_result in kept:
182
+ # 计算中心点距离
183
+ dx = abs(result['x'] - kept_result['x'])
184
+ dy = abs(result['y'] - kept_result['y'])
185
+
186
+ # 如果中心点距离小于框的一半大小,认为是重复
187
+ avg_size = (result['width'] + result['height'] +
188
+ kept_result['width'] + kept_result['height']) / 4
189
+
190
+ if dx < avg_size * overlap_thresh and dy < avg_size * overlap_thresh:
191
+ is_duplicate = True
192
+ break
193
+
194
+ if not is_duplicate:
195
+ kept.append(result)
196
+
197
+ return kept
198
+
199
+ def find_close_buttons(
200
+ self,
201
+ screenshot_path: str,
202
+ threshold: Optional[float] = None
203
+ ) -> Dict:
204
+ """
205
+ 在截图中查找所有关闭按钮
206
+
207
+ Args:
208
+ screenshot_path: 截图路径
209
+ threshold: 匹配阈值 (0-1)
210
+
211
+ Returns:
212
+ 匹配结果
213
+ """
214
+ # 读取截图
215
+ screenshot = cv2.imread(screenshot_path)
216
+ if screenshot is None:
217
+ return {
218
+ "success": False,
219
+ "error": f"无法读取截图: {screenshot_path}"
220
+ }
221
+
222
+ img_height, img_width = screenshot.shape[:2]
223
+
224
+ # 加载模板
225
+ templates = self.load_templates()
226
+ if not templates:
227
+ return {
228
+ "success": False,
229
+ "error": "没有找到模板图片,请在 templates/close_buttons/ 目录添加X号模板",
230
+ "template_dir": str(self.template_dir),
231
+ "tip": "添加常见X号截图到模板目录,命名如 x_circle.png, x_white.png 等"
232
+ }
233
+
234
+ all_matches = []
235
+
236
+ for template_name, template in templates:
237
+ matches = self.match_single_template(screenshot, template, threshold)
238
+ for match in matches:
239
+ match['template'] = template_name
240
+ all_matches.append(match)
241
+
242
+ # 按置信度排序
243
+ all_matches = sorted(all_matches, key=lambda x: x['confidence'], reverse=True)
244
+
245
+ # 再次 NMS 去除不同模板的重复检测
246
+ all_matches = self._non_max_suppression(all_matches)
247
+
248
+ if not all_matches:
249
+ return {
250
+ "success": False,
251
+ "message": "未找到匹配的关闭按钮",
252
+ "templates_used": [t[0] for t in templates],
253
+ "threshold": threshold or self.match_threshold,
254
+ "tip": "可能需要添加新的X号模板,或降低匹配阈值"
255
+ }
256
+
257
+ # 计算百分比坐标
258
+ for match in all_matches:
259
+ match['x_percent'] = round(match['x'] / img_width * 100, 1)
260
+ match['y_percent'] = round(match['y'] / img_height * 100, 1)
261
+
262
+ best = all_matches[0]
263
+
264
+ return {
265
+ "success": True,
266
+ "message": f"✅ 找到 {len(all_matches)} 个关闭按钮",
267
+ "best_match": {
268
+ "template": best['template'],
269
+ "center": {"x": int(best['x']), "y": int(best['y'])},
270
+ "percent": {"x": float(best['x_percent']), "y": float(best['y_percent'])},
271
+ "size": f"{best['width']}x{best['height']}",
272
+ "confidence": float(round(best['confidence'] * 100, 1))
273
+ },
274
+ "click_command": f"mobile_click_by_percent({best['x_percent']}, {best['y_percent']})",
275
+ "all_matches": [
276
+ {
277
+ "template": m['template'],
278
+ "percent": f"({m['x_percent']}%, {m['y_percent']}%)",
279
+ "confidence": f"{m['confidence']*100:.1f}%"
280
+ }
281
+ for m in all_matches[:5] # 最多返回5个
282
+ ],
283
+ "image_size": {"width": img_width, "height": img_height}
284
+ }
285
+
286
+ def add_template(self, image_path: str, template_name: str) -> Dict:
287
+ """
288
+ 添加新模板到模板库
289
+
290
+ Args:
291
+ image_path: 图片路径(可以是截图的一部分)
292
+ template_name: 模板名称
293
+
294
+ Returns:
295
+ 结果
296
+ """
297
+ # 读取图片
298
+ img = cv2.imread(image_path)
299
+ if img is None:
300
+ return {"success": False, "error": f"无法读取图片: {image_path}"}
301
+
302
+ # 保存到模板目录
303
+ output_path = self.template_dir / f"{template_name}.png"
304
+ cv2.imwrite(str(output_path), img)
305
+
306
+ # 清除缓存
307
+ self._template_cache.clear()
308
+
309
+ return {
310
+ "success": True,
311
+ "message": f"✅ 模板已保存: {output_path}",
312
+ "template_name": template_name
313
+ }
314
+
315
+ def crop_and_add_template(
316
+ self,
317
+ screenshot_path: str,
318
+ x: int, y: int,
319
+ width: int, height: int,
320
+ template_name: str
321
+ ) -> Dict:
322
+ """
323
+ 从截图中裁剪区域并添加为模板
324
+
325
+ Args:
326
+ screenshot_path: 截图路径
327
+ x, y: 左上角坐标
328
+ width, height: 裁剪尺寸
329
+ template_name: 模板名称
330
+
331
+ Returns:
332
+ 结果
333
+ """
334
+ img = cv2.imread(screenshot_path)
335
+ if img is None:
336
+ return {"success": False, "error": f"无法读取截图: {screenshot_path}"}
337
+
338
+ # 裁剪
339
+ cropped = img[y:y+height, x:x+width]
340
+
341
+ if cropped.size == 0:
342
+ return {"success": False, "error": "裁剪区域无效"}
343
+
344
+ # 保存
345
+ output_path = self.template_dir / f"{template_name}.png"
346
+ cv2.imwrite(str(output_path), cropped)
347
+
348
+ # 清除缓存
349
+ self._template_cache.clear()
350
+
351
+ return {
352
+ "success": True,
353
+ "message": f"✅ 模板已保存: {output_path}",
354
+ "template_name": template_name,
355
+ "size": f"{width}x{height}"
356
+ }
357
+
358
+ def list_templates(self) -> Dict:
359
+ """列出所有模板"""
360
+ templates = self.load_templates()
361
+
362
+ if not templates:
363
+ return {
364
+ "success": True,
365
+ "templates": [],
366
+ "message": "模板库为空",
367
+ "template_dir": str(self.template_dir)
368
+ }
369
+
370
+ template_info = []
371
+ for name, img in templates:
372
+ h, w = img.shape[:2]
373
+ template_info.append({
374
+ "name": name,
375
+ "size": f"{w}x{h}",
376
+ "path": str(self.template_dir / f"{name}.png")
377
+ })
378
+
379
+ return {
380
+ "success": True,
381
+ "templates": template_info,
382
+ "count": len(template_info),
383
+ "template_dir": str(self.template_dir)
384
+ }
385
+
386
+ def delete_template(self, template_name: str) -> Dict:
387
+ """删除模板"""
388
+ # 查找模板文件
389
+ for ext in ['.png', '.jpg', '.jpeg', '.bmp']:
390
+ path = self.template_dir / f"{template_name}{ext}"
391
+ if path.exists():
392
+ path.unlink()
393
+ self._template_cache.pop(template_name, None)
394
+ return {
395
+ "success": True,
396
+ "message": f"✅ 已删除模板: {template_name}"
397
+ }
398
+
399
+ return {
400
+ "success": False,
401
+ "error": f"模板不存在: {template_name}"
402
+ }
403
+
404
+
405
+ # 便捷函数
406
+ def match_close_button(screenshot_path: str, threshold: float = 0.75) -> Dict:
407
+ """
408
+ 快速匹配关闭按钮
409
+
410
+ 用法:
411
+ from core.template_matcher import match_close_button
412
+ result = match_close_button("screenshot.png")
413
+ if result["success"]:
414
+ print(result["click_command"])
415
+ """
416
+ matcher = TemplateMatcher()
417
+ return matcher.find_close_buttons(screenshot_path, threshold)
418
+
@@ -162,12 +162,14 @@ class MobileMCPServer:
162
162
  "SoM 截图会给元素标号,AI 可以直接说'点击几号',更精准!\n\n"
163
163
  "🎯 本工具仅用于:\n"
164
164
  "- 快速确认页面状态(不需要点击时)\n"
165
- "- 操作后确认结果\n\n"
165
+ "- 操作后确认结果\n"
166
+ "- compress=false 时可获取原始分辨率截图(用于添加模板)\n\n"
166
167
  "💡 如需点击元素,请用 mobile_screenshot_with_som + mobile_click_by_som",
167
168
  inputSchema={
168
169
  "type": "object",
169
170
  "properties": {
170
171
  "description": {"type": "string", "description": "截图描述(可选)"},
172
+ "compress": {"type": "boolean", "description": "是否压缩,默认 true。设为 false 可获取原始分辨率(用于模板添加)", "default": True},
171
173
  "crop_x": {"type": "integer", "description": "局部裁剪中心 X 坐标(屏幕坐标,0 表示不裁剪)"},
172
174
  "crop_y": {"type": "integer", "description": "局部裁剪中心 Y 坐标(屏幕坐标,0 表示不裁剪)"},
173
175
  "crop_size": {"type": "integer", "description": "裁剪区域大小(推荐 200-400,0 表示不裁剪)"}
@@ -629,6 +631,90 @@ class MobileMCPServer:
629
631
  }
630
632
  ))
631
633
 
634
+ # ==================== 广告弹窗关闭工具 ====================
635
+ tools.append(Tool(
636
+ name="mobile_close_ad",
637
+ description="""🚫 【推荐】智能关闭广告弹窗
638
+
639
+ 专门用于关闭广告弹窗,按优先级自动尝试多种方式:
640
+
641
+ 1️⃣ **控件树查找**(最可靠)
642
+ - 自动查找"关闭"、"跳过"、"×"等关闭按钮
643
+ - 找到直接点击,实时可靠
644
+
645
+ 2️⃣ **模板匹配**(次优)
646
+ - 用 OpenCV 匹配已保存的 X 按钮模板
647
+ - 需要积累模板库,模板越多成功率越高
648
+
649
+ 3️⃣ **返回截图供 AI 分析**(兜底)
650
+ - 如果前两步失败,返回截图
651
+ - AI 分析后用 mobile_click_by_percent 点击
652
+ - 点击成功后用 mobile_template_add 添加模板(自动学习)
653
+
654
+ 💡 使用流程:
655
+ 1. 遇到广告弹窗 → 调用此工具
656
+ 2. 如果成功 → 完成
657
+ 3. 如果失败 → 看截图找 X → 点击 → 添加模板""",
658
+ inputSchema={
659
+ "type": "object",
660
+ "properties": {},
661
+ "required": []
662
+ }
663
+ ))
664
+
665
+ tools.append(Tool(
666
+ name="mobile_template_close",
667
+ description="""🎯 模板匹配关闭弹窗(仅模板匹配)
668
+
669
+ 只用 OpenCV 模板匹配,不走控件树。
670
+ 一般建议用 mobile_close_ad 代替(会自动先查控件树)。
671
+
672
+ ⚙️ 参数:
673
+ - click: 是否点击,默认 true
674
+ - threshold: 匹配阈值 0-1,默认 0.75""",
675
+ inputSchema={
676
+ "type": "object",
677
+ "properties": {
678
+ "click": {"type": "boolean", "description": "是否点击,默认 true"},
679
+ "threshold": {"type": "number", "description": "匹配阈值 0-1,默认 0.75"}
680
+ },
681
+ "required": []
682
+ }
683
+ ))
684
+
685
+ tools.append(Tool(
686
+ name="mobile_template_add",
687
+ description="""➕ 添加 X 号模板
688
+
689
+ 遇到新样式 X 号时,截图并添加到模板库。
690
+
691
+ ⚙️ 两种方式(二选一):
692
+ 1. 百分比定位(推荐):提供 x_percent, y_percent, size
693
+ 2. 像素定位:提供 screenshot_path, x, y, width, height
694
+
695
+ 📋 流程:
696
+ 1. mobile_screenshot_with_grid 查看 X 号位置
697
+ 2. 调用此工具添加模板
698
+ 3. 下次同样 X 号就能自动匹配
699
+
700
+ 💡 百分比示例:X 在右上角 → x_percent=85, y_percent=12, size=80""",
701
+ inputSchema={
702
+ "type": "object",
703
+ "properties": {
704
+ "template_name": {"type": "string", "description": "模板名称"},
705
+ "x_percent": {"type": "number", "description": "X号中心水平百分比 (0-100)"},
706
+ "y_percent": {"type": "number", "description": "X号中心垂直百分比 (0-100)"},
707
+ "size": {"type": "integer", "description": "裁剪正方形边长(像素)"},
708
+ "screenshot_path": {"type": "string", "description": "截图路径(像素定位时用)"},
709
+ "x": {"type": "integer", "description": "左上角 X 坐标"},
710
+ "y": {"type": "integer", "description": "左上角 Y 坐标"},
711
+ "width": {"type": "integer", "description": "裁剪宽度"},
712
+ "height": {"type": "integer", "description": "裁剪高度"}
713
+ },
714
+ "required": ["template_name"]
715
+ }
716
+ ))
717
+
632
718
  return tools
633
719
 
634
720
  async def handle_tool_call(self, name: str, arguments: dict):
@@ -643,6 +729,7 @@ class MobileMCPServer:
643
729
  if name == "mobile_take_screenshot":
644
730
  result = self.tools.take_screenshot(
645
731
  description=arguments.get("description", ""),
732
+ compress=arguments.get("compress", True),
646
733
  crop_x=arguments.get("crop_x", 0),
647
734
  crop_y=arguments.get("crop_y", 0),
648
735
  crop_size=arguments.get("crop_size", 0)
@@ -809,6 +896,46 @@ class MobileMCPServer:
809
896
  )
810
897
  return [TextContent(type="text", text=self.format_response(result))]
811
898
 
899
+ # 智能关闭广告弹窗
900
+ elif name == "mobile_close_ad":
901
+ result = self.tools.close_ad_popup(auto_learn=True)
902
+ return [TextContent(type="text", text=self.format_response(result))]
903
+
904
+ # 模板匹配(精简版)
905
+ elif name == "mobile_template_close":
906
+ click = arguments.get("click", True)
907
+ threshold = arguments.get("threshold", 0.75)
908
+ if click:
909
+ result = self.tools.template_click_close(threshold=threshold)
910
+ else:
911
+ result = self.tools.template_match_close(threshold=threshold)
912
+ return [TextContent(type="text", text=self.format_response(result))]
913
+
914
+ elif name == "mobile_template_add":
915
+ template_name = arguments["template_name"]
916
+ # 判断使用哪种方式
917
+ if "x_percent" in arguments and "y_percent" in arguments:
918
+ # 百分比方式
919
+ result = self.tools.template_add_by_percent(
920
+ arguments["x_percent"],
921
+ arguments["y_percent"],
922
+ arguments.get("size", 80),
923
+ template_name
924
+ )
925
+ elif "screenshot_path" in arguments:
926
+ # 像素方式
927
+ result = self.tools.template_add(
928
+ arguments["screenshot_path"],
929
+ arguments["x"],
930
+ arguments["y"],
931
+ arguments["width"],
932
+ arguments["height"],
933
+ template_name
934
+ )
935
+ else:
936
+ result = {"success": False, "error": "请提供 x_percent/y_percent 或 screenshot_path/x/y/width/height"}
937
+ return [TextContent(type="text", text=self.format_response(result))]
938
+
812
939
  else:
813
940
  return [TextContent(type="text", text=f"❌ 未知工具: {name}")]
814
941
 
@@ -831,7 +958,7 @@ async def async_main():
831
958
  async def call_tool(name: str, arguments: dict):
832
959
  return await server.handle_tool_call(name, arguments)
833
960
 
834
- print("🚀 Mobile MCP Server 启动中... [24 个工具]", file=sys.stderr)
961
+ print("🚀 Mobile MCP Server 启动中... [26 个工具]", file=sys.stderr)
835
962
  print("📱 支持 Android / iOS", file=sys.stderr)
836
963
  print("👁️ 完全依赖 Cursor 视觉能力,无需 AI 密钥", file=sys.stderr)
837
964
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mobile-mcp-ai
3
- Version: 2.4.2
3
+ Version: 2.5.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
@@ -1,25 +1,26 @@
1
1
  mobile_mcp/__init__.py,sha256=sQJZTL_sxQFzmcS7jOtS2AHCfUySz40vhX96N6u1qy4,816
2
2
  mobile_mcp/config.py,sha256=yaFLAV4bc2wX0GQPtZDo7OYF9E88tXV-av41fQsJwK4,4480
3
3
  mobile_mcp/core/__init__.py,sha256=ndMy-cLAIsQDG5op7gM_AIplycqZSZPWEkec1pEhvEY,170
4
- mobile_mcp/core/basic_tools_lite.py,sha256=zFBh2GV_MNjy_kstTaudwkMmxJ7lx5Y8QTlSTcL8pAw,128383
4
+ mobile_mcp/core/basic_tools_lite.py,sha256=BzPT180GPQjhTNHaAKl46m1jvCM5KoQIPgySGdNSD30,149836
5
5
  mobile_mcp/core/device_manager.py,sha256=PX3-B5bJFnKNt6C8fT7FSY8JwD-ngZ3toF88bcOV9qA,8766
6
6
  mobile_mcp/core/dynamic_config.py,sha256=Ja1n1pfb0HspGByqk2_A472mYVniKmGtNEWyjUjmgK8,9811
7
- mobile_mcp/core/ios_client_wda.py,sha256=6xocIy6rgXQr-2f5oEmRlYymJhekf-KXBn_MzRALbmc,18761
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=bno3HvU-QSAC3G4TnoFngTxqXeu-ZP5rGlEWdWh8jOo,62570
10
+ mobile_mcp/core/template_matcher.py,sha256=dGYrae6cAWiPhF6U4WtYFz_4o7a-LQc3FdZnFq4nHNE,14018
10
11
  mobile_mcp/core/utils/__init__.py,sha256=RhMMsPszmEn8Q8GoNufypVSHJxyM9lio9U6jjpnuoPI,378
11
12
  mobile_mcp/core/utils/logger.py,sha256=XXQAHUwT1jc70pq_tYFmL6f_nKrFlYm3hcgl-5RYRg0,3402
12
13
  mobile_mcp/core/utils/operation_history_manager.py,sha256=gi8S8HJAMqvkUrY7_-kVbko3Xt7c4GAUziEujRd-N-Y,4792
13
14
  mobile_mcp/core/utils/smart_wait.py,sha256=PvKXImfN9Irru3bQJUjf4FLGn8LjY2VLzUNEl-i7xLE,8601
14
15
  mobile_mcp/mcp_tools/__init__.py,sha256=xkro8Rwqv_55YlVyhh-3DgRFSsLE3h1r31VIb3bpM6E,143
15
- mobile_mcp/mcp_tools/mcp_server.py,sha256=QEehNZCSQjyaH-ST7kmAc6lu0kbgpCpCi3MCHDmA1ZI,39504
16
+ mobile_mcp/mcp_tools/mcp_server.py,sha256=K53DSxKGT3B_f6mqaoTID2th5luBtwlh5MjoUDGbhl0,45263
16
17
  mobile_mcp/utils/__init__.py,sha256=8EH0i7UGtx1y_j_GEgdN-cZdWn2sRtZSEOLlNF9HRnY,158
17
18
  mobile_mcp/utils/logger.py,sha256=Sqq2Nr0Y4p03erqcrbYKVPCGiFaNGHMcE_JwCkeOfU4,3626
18
19
  mobile_mcp/utils/xml_formatter.py,sha256=uwTRb3vLbqhT8O-udzWT7s7LsV-DyDUz2DkofD3hXOE,4556
19
20
  mobile_mcp/utils/xml_parser.py,sha256=QhL8CWbdmNDzmBLjtx6mEnjHgMFZzJeHpCL15qfXSpI,3926
20
- mobile_mcp_ai-2.4.2.dist-info/licenses/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
21
- mobile_mcp_ai-2.4.2.dist-info/METADATA,sha256=z3H9ls_6ui6xqtBzsk93FBY-E3oHlY2lSGNADimt-hU,9745
22
- mobile_mcp_ai-2.4.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
23
- mobile_mcp_ai-2.4.2.dist-info/entry_points.txt,sha256=KB_FglozgPHBprSM1vFbIzGyheFuHFmGanscRdMJ_8A,68
24
- mobile_mcp_ai-2.4.2.dist-info/top_level.txt,sha256=lLm6YpbTv855Lbh8BIA0rPxhybIrvYUzMEk9OErHT94,11
25
- mobile_mcp_ai-2.4.2.dist-info/RECORD,,
21
+ mobile_mcp_ai-2.5.0.dist-info/licenses/LICENSE,sha256=HrhfyXIkWY2tGFK11kg7vPCqhgh5DcxleloqdhrpyMY,11558
22
+ mobile_mcp_ai-2.5.0.dist-info/METADATA,sha256=nbbNqfi6O5zgrLLGP0QgZdRCUKT_4iYHmySIx5jabJk,9745
23
+ mobile_mcp_ai-2.5.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
24
+ mobile_mcp_ai-2.5.0.dist-info/entry_points.txt,sha256=KB_FglozgPHBprSM1vFbIzGyheFuHFmGanscRdMJ_8A,68
25
+ mobile_mcp_ai-2.5.0.dist-info/top_level.txt,sha256=lLm6YpbTv855Lbh8BIA0rPxhybIrvYUzMEk9OErHT94,11
26
+ mobile_mcp_ai-2.5.0.dist-info/RECORD,,