quash-mcp 0.2.14__py3-none-any.whl → 0.3.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.
@@ -212,7 +212,7 @@ class BackendClient:
212
212
 
213
213
  # Prepare files dict (only screenshot if provided)
214
214
  files = {}
215
- if screenshot_bytes:
215
+ if screenshot_bytes and len(screenshot_bytes) > 0:
216
216
  files["screenshot"] = ("screenshot.png", screenshot_bytes, "image/png")
217
217
 
218
218
  async with httpx.AsyncClient(timeout=self.timeout) as client:
@@ -1,11 +1,7 @@
1
- """
2
- Device state capture utilities.
3
- Captures UI state and screenshots from Android devices.
4
- """
5
-
1
+ import json
6
2
  import logging
7
3
  import requests
8
- from typing import Dict, Any, Optional, Tuple
4
+ from typing import Dict, Any, Optional, Tuple, List
9
5
  from adbutils import adb
10
6
 
11
7
  logger = logging.getLogger("quash-device")
@@ -34,7 +30,7 @@ def get_current_package(serial: str) -> str:
34
30
  return "unknown"
35
31
 
36
32
 
37
- def get_accessibility_tree(serial: str, tcp_port: int = 8080) -> str:
33
+ def get_accessibility_tree(serial: str, tcp_port: int = 8080) -> List[Dict[str, Any]]:
38
34
  """
39
35
  Get accessibility tree from Portal app via TCP.
40
36
 
@@ -43,7 +39,7 @@ def get_accessibility_tree(serial: str, tcp_port: int = 8080) -> str:
43
39
  tcp_port: Local TCP port for Portal communication
44
40
 
45
41
  Returns:
46
- Accessibility tree XML string
42
+ Accessibility tree as a list of dictionaries, or an empty list if failed
47
43
  """
48
44
  try:
49
45
  device = adb.device(serial)
@@ -55,20 +51,27 @@ def get_accessibility_tree(serial: str, tcp_port: int = 8080) -> str:
55
51
  )
56
52
 
57
53
  if response.status_code == 200:
58
- # Portal returns JSON with status and data fields
59
54
  data = response.json()
60
55
  if data.get("status") == "success":
61
- return data.get("data", "<hierarchy></hierarchy>")
56
+ # The 'data' field should contain the JSON string of the a11y_tree
57
+ a11y_tree_json_str = data.get("data", "[]")
58
+ try:
59
+ parsed_tree = json.loads(a11y_tree_json_str)
60
+ logger.debug(f"get_accessibility_tree returning tree of length: {len(parsed_tree)}")
61
+ return parsed_tree
62
+ except json.JSONDecodeError:
63
+ logger.warning(f"Failed to parse a11y_tree JSON string: {a11y_tree_json_str}")
64
+ return []
62
65
  else:
63
66
  logger.warning(f"Portal error: {data.get('error', 'Unknown error')}")
64
- return "<hierarchy></hierarchy>"
67
+ return []
65
68
  else:
66
69
  logger.warning(f"Failed to get accessibility tree: HTTP {response.status_code}")
67
- return "<hierarchy></hierarchy>"
70
+ return []
68
71
 
69
72
  except Exception as e:
70
73
  logger.warning(f"Failed to get accessibility tree: {e}")
71
- return "<hierarchy></hierarchy>"
74
+ return []
72
75
 
73
76
 
74
77
  def capture_screenshot(serial: str) -> Optional[bytes]:
@@ -83,7 +86,10 @@ def capture_screenshot(serial: str) -> Optional[bytes]:
83
86
  """
84
87
  try:
85
88
  device = adb.device(serial)
86
- screenshot_bytes = device.shell("screencap -p", stream=True)
89
+ # device.shell("screencap -p", stream=True) returns an AdbConnection object (file-like)
90
+ # We need to read the bytes from it.
91
+ with device.shell("screencap -p", stream=True) as conn:
92
+ screenshot_bytes = conn.read(1024 * 1024 * 10) # Read up to 10MB
87
93
  return screenshot_bytes
88
94
  except Exception as e:
89
95
  logger.error(f"Failed to capture screenshot: {e}")
quash_mcp/models.py CHANGED
@@ -16,7 +16,7 @@ class ConfigInfo(BaseModel):
16
16
  debug: bool = False
17
17
 
18
18
  class UIStateInfo(BaseModel):
19
- a11y_tree: str
19
+ a11y_tree: List[Dict[str, Any]]
20
20
  phone_state: Dict[str, Any]
21
21
 
22
22
  class ChatHistoryMessage(BaseModel):
@@ -31,6 +31,12 @@ class AgentStepDTO(BaseModel):
31
31
  cost: float
32
32
  timestamp: datetime = Field(default_factory=datetime.utcnow)
33
33
 
34
+ class ReflectionInfo(BaseModel):
35
+ """Information about a reflection step."""
36
+ goal_achieved: bool
37
+ advice: Optional[str] = None
38
+ summary: Optional[str] = None
39
+
34
40
  class SessionDTO(BaseModel):
35
41
  session_id: str
36
42
  api_key: str
@@ -39,4 +45,9 @@ class SessionDTO(BaseModel):
39
45
  config: ConfigInfo
40
46
  chat_history: List[ChatHistoryMessage] = []
41
47
  steps: List[AgentStepDTO] = []
48
+ current_plan: Optional[List[str]] = None
49
+ current_task_index: int = 0
42
50
  ui_state: Optional[UIStateInfo] = None
51
+ last_reflection: Optional[ReflectionInfo] = None # Store the last reflection for the session
52
+
53
+ last_action_completed: Optional[bool] = None
@@ -182,7 +182,6 @@ from ..models import SessionDTO, UIStateInfo, ChatHistoryMessage, ConfigInfo, Ag
182
182
 
183
183
  async def execute_v3(
184
184
  task: str,
185
- max_steps: int = 15,
186
185
  progress_callback: Optional[Callable[[str], None]] = None
187
186
  ) -> Dict[str, Any]:
188
187
  """
@@ -223,6 +222,7 @@ async def execute_v3(
223
222
  "reflection": state.config["reflection"],
224
223
  "debug": state.config["debug"]
225
224
  }
225
+ max_steps = state.config.get("max_steps", 15)
226
226
 
227
227
  # Validate API key
228
228
  validation_result = await backend.validate_api_key(quash_api_key)
@@ -256,80 +256,73 @@ async def execute_v3(
256
256
  log_progress(f"🚀 Starting task: {task}")
257
257
  log_progress(f"📱 Device: {state.device_serial}")
258
258
  log_progress(f"🧠 Model: {config['model']}")
259
-
260
259
  log_progress(f"🔢 Max steps: {max_steps}")
261
260
 
262
261
  # Initialize Session DTO
263
-
264
262
  session = SessionDTO(
265
263
  session_id=f"session_{uuid.uuid4().hex[:12]}",
266
264
  api_key=quash_api_key,
267
265
  task=task,
268
266
  device_serial=state.device_serial,
269
- config=ConfigInfo(**config)
267
+ config=ConfigInfo(**config),
268
+ last_action_completed=None # Explicitly initialize the new field
270
269
  )
271
270
 
272
- # Initialize local ADB tools for code execution
273
- adb_tools = AdbTools(serial=state.device_serial, use_tcp=True)
271
+ # Initialize a single, powerful ADB tools instance from Mahoraga
272
+ mahoraga_tools = None
273
+ try:
274
+ mahoraga_tools = MahoragaAdbTools(
275
+ serial=state.device_serial,
276
+ use_tcp=True,
277
+ remote_tcp_port=8080
278
+ )
279
+ except Exception as e:
280
+ log_progress(f"⚠️ CRITICAL: Failed to initialize MahoragaAdbTools: {e}")
281
+ return {
282
+ "status": "error",
283
+ "message": f"💥 Failed to initialize ADB tools: {e}",
284
+ }
274
285
 
275
286
  # Code executor namespace - add tool functions so generated code can call them
276
287
  executor_globals = {
277
288
  "__builtins__": __builtins__,
278
- "adb_tools": adb_tools
279
289
  }
280
290
 
281
- # Add tool functions to executor namespace (like start_app, swipe, etc.)
282
- if describe_tools and DEFAULT and MahoragaAdbTools:
283
- try:
284
- # Create a mahoraga AdbTools instance for tool execution
285
- mahoraga_tools = MahoragaAdbTools(
286
- serial=state.device_serial,
287
- use_tcp=True,
288
- remote_tcp_port=8080
289
- )
290
-
291
- # Get all tool functions from mahoraga AdbTools instance
292
- tool_list = describe_tools(mahoraga_tools, exclude_tools=None)
293
-
294
- # Filter by allowed tools from DEFAULT persona
295
- allowed_tool_names = DEFAULT.allowed_tools if hasattr(DEFAULT, 'allowed_tools') else []
296
- filtered_tools = {name: func for name, func in tool_list.items() if name in allowed_tool_names}
297
-
298
- # Add each tool function to executor globals with print wrapper
299
- for tool_name, tool_function in filtered_tools.items():
300
- # Convert async functions to sync if needed
301
- if asyncio.iscoroutinefunction(tool_function):
302
- if async_to_sync:
303
- tool_function = async_to_sync(tool_function)
304
-
305
- # Wrap tool function to print its return value
306
- def make_printing_wrapper(func):
307
- """Wrap a tool function to print its return value."""
308
- def wrapper(*args, **kwargs):
309
- result = func(*args, **kwargs)
310
- # Print the result so stdout captures it
311
- if result is not None:
312
- print(result)
313
- return result
314
- return wrapper
315
-
316
- # Add wrapped function to globals so code can call it directly
317
- executor_globals[tool_name] = make_printing_wrapper(tool_function)
318
-
319
- # Override the 'complete' function to be a no-op
320
- # The backend already handles completion via the 'completed' flag
321
- def complete_no_op(success=True, reason=""):
322
- """No-op wrapper for complete() - completion is handled by backend."""
323
- print(f"complete() called: success={success}, reason='{reason}'")
324
- return None
325
-
326
- executor_globals['complete'] = complete_no_op
327
-
328
- log_progress(f"🔧 Loaded {len(filtered_tools)} tool functions: {list(filtered_tools.keys())}")
329
- except Exception as e:
330
- log_progress(f"⚠️ Warning: Could not load tool functions: {e}")
331
- import traceback
332
- log_progress(f"Traceback: {traceback.format_exc()}")
291
+ # Add tool functions to executor namespace
292
+ try:
293
+ # Get all tool functions from the single mahoraga_tools instance
294
+ tool_list = describe_tools(mahoraga_tools, exclude_tools=None)
295
+
296
+ # Filter by allowed tools from DEFAULT persona
297
+ allowed_tool_names = DEFAULT.allowed_tools if hasattr(DEFAULT, 'allowed_tools') else []
298
+ filtered_tools = {name: func for name, func in tool_list.items() if name in allowed_tool_names}
299
+
300
+ # Add each tool function to executor globals with print wrapper
301
+ for tool_name, tool_function in filtered_tools.items():
302
+ # Convert async functions to sync if needed
303
+ if asyncio.iscoroutinefunction(tool_function):
304
+ if async_to_sync:
305
+ tool_function = async_to_sync(tool_function)
306
+
307
+ # Wrap tool function to print its return value
308
+ def make_printing_wrapper(func):
309
+ """Wrap a tool function to print its return value."""
310
+ def wrapper(*args, **kwargs):
311
+ result = func(*args, **kwargs)
312
+ # Print the result so stdout captures it
313
+ if result is not None:
314
+ print(result)
315
+ return result
316
+ return wrapper
317
+
318
+ # Add wrapped function to globals so code can call it directly
319
+ executor_globals[tool_name] = make_printing_wrapper(tool_function)
320
+
321
+ log_progress(f"🔧 Loaded {len(filtered_tools)} tool functions: {list(filtered_tools.keys())}")
322
+ except Exception as e:
323
+ log_progress(f"⚠️ Warning: Could not load tool functions: {e}")
324
+ import traceback
325
+ log_progress(f"Traceback: {traceback.format_exc()}")
333
326
 
334
327
  executor_locals = {}
335
328
 
@@ -349,14 +342,14 @@ async def execute_v3(
349
342
  ui_state_dict, screenshot_bytes = get_device_state(state.device_serial)
350
343
 
351
344
  session.ui_state = UIStateInfo(**ui_state_dict)
345
+
352
346
  # Update local tools with new state
353
- if mahoraga_tools and "a11y_tree" in ui_state_dict and isinstance(ui_state_dict["a11y_tree"], str):
347
+ if mahoraga_tools and "a11y_tree" in ui_state_dict and isinstance(ui_state_dict["a11y_tree"], list):
354
348
  try:
355
- import json
356
- a11y_tree_obj = json.loads(ui_state_dict["a11y_tree"])
349
+ a11y_tree_obj = ui_state_dict["a11y_tree"]
357
350
  mahoraga_tools.update_state(a11y_tree_obj)
358
- except (json.JSONDecodeError, TypeError):
359
- pass # Ignore if not a valid JSON string
351
+ except Exception as e:
352
+ log_progress(f"⚠️ Warning: Failed to update mahoraga_tools state: {e}")
360
353
 
361
354
  if not config["vision"]:
362
355
  screenshot_bytes = None
@@ -367,7 +360,7 @@ async def execute_v3(
367
360
  except Exception as e:
368
361
  log_progress(f"⚠️ Warning: Failed to capture device state: {e}")
369
362
  session.ui_state = UIStateInfo(
370
- a11y_tree="<error>Failed to capture UI</error>",
363
+ a11y_tree=[],
371
364
  phone_state={"package": "unknown"}
372
365
  )
373
366
  screenshot_bytes = None
@@ -391,14 +384,29 @@ async def execute_v3(
391
384
  "duration_seconds": time.time() - start_time
392
385
  }
393
386
 
394
- # Update Session DTO with new step and chat history
387
+ # CRITICAL: Update the client's session DTO with the one returned from the backend
388
+ updated_session_data = step_result.get("updated_session")
389
+ if updated_session_data:
390
+ # Ensure last_action_completed field exists
391
+ if "last_action_completed" not in updated_session_data:
392
+ updated_session_data["last_action_completed"] = None
393
+ session = SessionDTO(**updated_session_data)
394
+ else:
395
+ # Fallback: if updated_session not returned, update locally
396
+ new_step_data = step_result.get("new_step")
397
+ if new_step_data:
398
+ new_step = AgentStepDTO(**new_step_data)
399
+ session.steps.append(new_step)
400
+ assistant_response = step_result.get("assistant_response", "")
401
+ session.chat_history.append(ChatHistoryMessage(role="assistant", content=assistant_response))
402
+
403
+ # CRITICAL FIX: Handle plan generation responses (which have new_step=None)
404
+ # These don't create actual steps, just show the plan
395
405
  new_step_data = step_result.get("new_step")
396
- if new_step_data:
397
- new_step = AgentStepDTO(**new_step_data)
398
- session.steps.append(new_step)
399
- assistant_response = step_result.get("assistant_response", "")
400
- session.chat_history.append(ChatHistoryMessage(role="assistant", content=assistant_response))
401
-
406
+ if new_step_data is None and not updated_session_data:
407
+ # Plan was generated but no step was added
408
+ # This is normal - plan is informational only
409
+ pass
402
410
 
403
411
  # Get action from backend
404
412
  action = step_result.get("action", {})
@@ -406,18 +414,18 @@ async def execute_v3(
406
414
  code = action.get("code")
407
415
  reasoning = action.get("reasoning")
408
416
 
409
-
410
417
  # Log reasoning
411
418
  if reasoning:
412
419
  log_progress(f"🤔 Reasoning: {reasoning}")
413
420
 
421
+ # CRITICAL FIX: Reset completion flag before executing
422
+ session.last_action_completed = False
414
423
 
415
- # 3. Execute action locally FIRST (if provided)
416
- # NOTE: Backend should have already removed complete() from the code
417
- if code and action_type == "execute_code":
418
- log_progress(f"⚡ Executing action...")
424
+ # 3. Execute action locally (if provided)
425
+ if code and (action_type == "execute_code" or action_type == "complete"):
419
426
 
420
- log_progress(f"```python\n{code}\n```") # Log the code
427
+ log_progress(f"⚡ Executing action...")
428
+ log_progress(f"```python\n{code}\n```")
421
429
 
422
430
  old_ui_state = session.ui_state.model_dump().copy()
423
431
 
@@ -434,6 +442,13 @@ async def execute_v3(
434
442
  execution_output = stdout.getvalue()
435
443
  error_output = stderr.getvalue()
436
444
 
445
+ # CRITICAL FIX: Check if complete() was actually called
446
+ if mahoraga_tools and mahoraga_tools.finished:
447
+ log_progress("✅ Agent has signaled task completion via complete()")
448
+ session.last_action_completed = True
449
+ else:
450
+ session.last_action_completed = False
451
+
437
452
  log_progress(f"⏳ Waiting for UI state to update...")
438
453
  try:
439
454
  new_ui_state_dict, _, state_changed = wait_for_action_effect(
@@ -453,7 +468,6 @@ async def execute_v3(
453
468
  log_progress(f"✅ State changed: App switched ({old_pkg} → {new_pkg})")
454
469
  else:
455
470
  log_progress(f"✅ State changed: UI updated")
456
-
457
471
  else:
458
472
  log_progress(f"⚠️ WARNING: State did NOT change after action (timeout)")
459
473
  log_progress(f" This might mean the action had no effect or took too long")
@@ -468,7 +482,10 @@ async def execute_v3(
468
482
  if execution_output:
469
483
  feedback_parts.append(f"Action output: {execution_output.strip()}")
470
484
 
471
- if state_changed:
485
+ # CRITICAL FIX: Report completion status in feedback
486
+ if session.last_action_completed:
487
+ feedback_parts.append("Sub-task completed successfully (complete() was called)")
488
+ elif state_changed:
472
489
  feedback_parts.append("UI state updated successfully")
473
490
  else:
474
491
  feedback_parts.append("WARNING: UI state did not change (action may have failed)")
@@ -480,71 +497,86 @@ async def execute_v3(
480
497
 
481
498
  log_progress(f"✅ {feedback[:200]}")
482
499
 
483
- session.chat_history.append(ChatHistoryMessage(role="user", content=f"Execution Result:\n```\n{feedback}\n```"))
500
+ session.chat_history.append(ChatHistoryMessage(
501
+ role="user",
502
+ content=f"Execution Result:\n```\n{feedback}\n```"
503
+ ))
484
504
 
485
- # Introduce a small delay to allow UI effects to settle before checking completion
486
- time.sleep(1.0) # Added delay
505
+ time.sleep(0.5)
487
506
 
488
507
  except Exception as e:
489
508
  error_msg = f"Error during execution: {str(e)}"
490
509
  log_progress(f"💥 Action failed: {error_msg}")
491
-
492
- session.chat_history.append(ChatHistoryMessage(role="user", content=f"Execution Error:\n```\n{error_msg}\n```"))
493
-
494
- elif not code:
495
- log_progress("⚠️ No action code provided by backend")
496
- session.chat_history.append(ChatHistoryMessage(role="user", content="No code was provided. Please provide code to execute."))
497
-
498
-
499
- # 4. Check if task is complete AFTER executing action
500
- if step_result.get("completed", False):
501
- success = step_result.get("success", False)
502
- final_message = step_result.get("final_message", "Task completed")
503
-
510
+ session.last_action_completed = False
511
+
512
+ session.chat_history.append(ChatHistoryMessage(
513
+ role="user",
514
+ content=f"Execution Error:\n```\n{error_output.strip()}\n```"
515
+ ))
516
+
517
+ # 4. Check if overall task is complete
518
+ # CRITICAL FIX: In reasoning mode with planning, DON'T exit on first complete() call
519
+ # The backend controls when all tasks are done via the "complete" action type
520
+ should_exit = False
521
+
522
+ if mahoraga_tools and mahoraga_tools.finished:
523
+ # Check if this is the FINAL completion from the backend
524
+ # In reasoning mode, the backend returns action.type="complete" when ALL tasks are done
525
+ action_type = action.get("type", "")
526
+
527
+ if action_type == "complete":
528
+ # Backend explicitly says we're done with ALL tasks
529
+ should_exit = True
530
+ success = mahoraga_tools.success
531
+ final_message = mahoraga_tools.reason
532
+ elif config["reasoning"] and session.current_plan:
533
+ # In reasoning mode with a plan, a single complete() call is just for one sub-task
534
+ # Continue the loop - the backend will advance to the next task
535
+ log_progress(f"✅ Sub-task completed. Moving to next task...")
536
+ should_exit = False
537
+ else:
538
+ # Non-reasoning mode: first complete() means done
539
+ should_exit = True
540
+ success = mahoraga_tools.success
541
+ final_message = mahoraga_tools.reason
542
+
543
+ if should_exit and mahoraga_tools and mahoraga_tools.finished:
544
+ success = mahoraga_tools.success
545
+ final_message = mahoraga_tools.reason
504
546
  duration = time.time() - start_time
505
547
 
506
548
  if success:
507
549
  log_progress(f"✅ Task completed successfully!")
508
550
  else:
509
- log_progress(f"❌ Task marked as failed")
551
+ log_progress(f"❌ Task marked as failed: {final_message}")
510
552
 
511
553
  # Finalize session on backend
512
554
  finalize_result = await backend.finalize_session(session=session)
555
+ total_tokens = finalize_result.get("total_tokens", {})
556
+ total_cost = finalize_result.get("total_cost", 0)
513
557
 
514
- if success:
515
- log_progress(f"✅ Task completed successfully in {len(session.steps)} steps")
516
- log_progress(f"💰 Usage: {finalize_result.get('total_tokens', {}).get('total')} tokens, ${finalize_result.get('total_cost', 0):.4f}")
517
-
518
- return {
519
- "status": "success",
520
- "steps_taken": len(session.steps),
521
- "final_message": final_message,
522
- "message": f"✅ Success: {final_message}",
523
- "tokens": finalize_result.get("total_tokens"),
524
- "cost": finalize_result.get("total_cost"),
525
- "duration_seconds": duration
526
- }
527
- else:
528
- log_progress(f"❌ Task failed: {final_message}")
529
- log_progress(f"💰 Usage: {finalize_result.get('total_tokens', {}).get('total')} tokens, ${finalize_result.get('total_cost', 0):.4f}")
530
-
531
- return {
532
- "status": "failed",
533
- "steps_taken": len(session.steps),
534
- "final_message": final_message,
535
- "message": f"❌ Failed: {final_message}",
536
- "tokens": finalize_result.get("total_tokens"),
537
- "cost": finalize_result.get("total_cost"),
538
- "duration_seconds": duration
539
- }
558
+ log_progress(f"💰 Usage: {total_tokens.get('total')} tokens, ${total_cost:.4f}")
540
559
 
560
+ return {
561
+ "status": "success" if success else "failed",
562
+ "steps_taken": len(session.steps),
563
+ "final_message": final_message,
564
+ "message": f"✅ Success: {final_message}" if success else f"❌ Failed: {final_message}",
565
+ "tokens": total_tokens,
566
+ "cost": total_cost,
567
+ "duration_seconds": duration
568
+ }
569
+
570
+ elif not code:
571
+ log_progress("⚠️ No action code provided by backend")
572
+ session.chat_history.append(ChatHistoryMessage(
573
+ role="user",
574
+ content="No code was provided. Please provide code to execute."
575
+ ))
541
576
 
542
577
  # Max steps reached
543
578
  log_progress(f"⚠️ Reached maximum steps ({max_steps})")
544
-
545
579
  duration = time.time() - start_time
546
-
547
- # Finalize session on backend
548
580
  finalize_result = await backend.finalize_session(session=session)
549
581
 
550
582
  return {
@@ -560,8 +592,6 @@ async def execute_v3(
560
592
  except KeyboardInterrupt:
561
593
  log_progress("ℹ️ Task interrupted by user")
562
594
  duration = time.time() - start_time
563
-
564
- # Finalize session on backend
565
595
  finalize_result = await backend.finalize_session(session=session)
566
596
 
567
597
  return {
@@ -577,8 +607,6 @@ async def execute_v3(
577
607
  error_msg = str(e)
578
608
  log_progress(f"💥 Error: {error_msg}")
579
609
  duration = time.time() - start_time
580
-
581
- # Finalize session on backend
582
610
  finalize_result = await backend.finalize_session(session=session)
583
611
 
584
612
  return {
@@ -593,5 +621,5 @@ async def execute_v3(
593
621
 
594
622
  finally:
595
623
  # Cleanup TCP forwarding
596
- if adb_tools:
597
- adb_tools.teardown_tcp_forward()
624
+ if mahoraga_tools:
625
+ mahoraga_tools.teardown_tcp_forward()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quash-mcp
3
- Version: 0.2.14
3
+ Version: 0.3.0
4
4
  Summary: Model Context Protocol server for Quash - AI-powered mobile automation agent
5
5
  Project-URL: Homepage, https://quashbugs.com
6
6
  Project-URL: Repository, https://github.com/quash/quash-mcp
@@ -1,13 +1,13 @@
1
1
  quash_mcp/__init__.py,sha256=LImiWCRgjAbb5DZXBq2DktUEAbftvnO61Vil4Ayun9A,39
2
2
  quash_mcp/__main__.py,sha256=WCg5OlnXhr6i0XJHAUGpbhliMy3qE2SJkFzVD4wO-lw,239
3
- quash_mcp/backend_client.py,sha256=jQ_OFOhdbGlTr42VCZMu5XP3_TJPsWCpTX_2iBppafo,9949
4
- quash_mcp/models.py,sha256=0S7uCZZRRtQuSwIwKTDKZnX2HRMYOBDoDcl7-b8Tzpk,1001
3
+ quash_mcp/backend_client.py,sha256=_smBbhyJxN1dj89cJNVvqRZVX92oc1bw2SI1vzX2Rek,9979
4
+ quash_mcp/models.py,sha256=zqi0-DCmgOaq4TiuJsb9QsQxMxcJ82B3NeRwbnrfJQc,1414
5
5
  quash_mcp/server.py,sha256=scUGnplxjsvyYLK2q6hrjl-5Chkdnat9pODDtLzsQFY,15519
6
6
  quash_mcp/state.py,sha256=Tnt795GnZcas-h62Y6KYyIZVopeoWPM0TbRwOeVFYj4,4394
7
7
  quash_mcp/device/__init__.py,sha256=6e8CtHolt-vJKPxZUU_Vsd6-QGqos9VrFykaLTT90rk,772
8
8
  quash_mcp/device/adb_tools.py,sha256=SsYnzGjG3XsfbiAHiC7PpgLdC149kRH-YkoXQZvxvWc,5439
9
9
  quash_mcp/device/portal.py,sha256=sDLJOruUwwNNxIDriiXB4vT0BZYILidgzVgdhHCEkDY,5241
10
- quash_mcp/device/state_capture.py,sha256=WUNewRlgi_A6L8usWsga-9iqMTroCuSaZ0OdY8EheU0,3473
10
+ quash_mcp/device/state_capture.py,sha256=NwuhjCBI576w9eexhdVOxfsOmABTW1A4SWRpcjadg-w,4016
11
11
  quash_mcp/tools/__init__.py,sha256=r4fMAjHDjHUbimRwYW7VYUDkQHs12UVsG_IBmWpeX9s,249
12
12
  quash_mcp/tools/build.py,sha256=M6tGXWrQNkdtCYYrK14gUaoufQvyoor_hNN0lBPSVHY,30321
13
13
  quash_mcp/tools/build_old.py,sha256=6M9gaqZ_dX4B7UFTxSMD8T1BX0zEwQUL7RJ8ItNfB54,6016
@@ -15,10 +15,10 @@ quash_mcp/tools/configure.py,sha256=cv4RTolu6qae-XzyACSJUDrALfd0gYC-XE5s66_zfNk,
15
15
  quash_mcp/tools/connect.py,sha256=Kc7RGRUgtd2sR_bv6U4CB4kWSaLfsDc5kBo9u4FEjzs,4799
16
16
  quash_mcp/tools/execute.py,sha256=kR3VzIl31Lek-js4Hgxs-S_ls4YwKnbqkt79KFbvFuM,909
17
17
  quash_mcp/tools/execute_v2_backup.py,sha256=waWnaD0dEVcOJgRBbqZo3HnxME1s6YUOn8aRbm4R3X4,6081
18
- quash_mcp/tools/execute_v3.py,sha256=zlkQqjdohE6bFQKMwWf4MeL59iCAbcfOVW6dCNl_veQ,22661
18
+ quash_mcp/tools/execute_v3.py,sha256=KMS7Zru2GiDcp1IqDvtGPhKlzbXEilCRp8hkvZShI2Q,24404
19
19
  quash_mcp/tools/runsuite.py,sha256=gohLk9FpN8v7F0a69fspqOqUexTcslpYf3qU-iIZZ3s,7220
20
20
  quash_mcp/tools/usage.py,sha256=g76A6FO36fThoyRFG7q92QmS3Kh1pIKOrhYOzUdIubA,1155
21
- quash_mcp-0.2.14.dist-info/METADATA,sha256=r-rZWyw5mSL799jHIeqqSYvt0q_G23VevDXfaPTBr3w,8424
22
- quash_mcp-0.2.14.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
23
- quash_mcp-0.2.14.dist-info/entry_points.txt,sha256=9sbDxrx0ApGDVRS-IE3mQgSao3DwKnnV_k-_ipFn9QI,52
24
- quash_mcp-0.2.14.dist-info/RECORD,,
21
+ quash_mcp-0.3.0.dist-info/METADATA,sha256=wLT-0D39eXubj1b8LiX57vBUae0ITz_1x1yCozxFUwM,8423
22
+ quash_mcp-0.3.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
23
+ quash_mcp-0.3.0.dist-info/entry_points.txt,sha256=9sbDxrx0ApGDVRS-IE3mQgSao3DwKnnV_k-_ipFn9QI,52
24
+ quash_mcp-0.3.0.dist-info/RECORD,,