quash-mcp 0.2.13__tar.gz → 0.2.15__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 quash-mcp might be problematic. Click here for more details.
- {quash_mcp-0.2.13 → quash_mcp-0.2.15}/PKG-INFO +1 -1
- {quash_mcp-0.2.13 → quash_mcp-0.2.15}/local_test.py +5 -3
- {quash_mcp-0.2.13 → quash_mcp-0.2.15}/pyproject.toml +1 -1
- {quash_mcp-0.2.13 → quash_mcp-0.2.15}/quash_mcp/backend_client.py +3 -2
- {quash_mcp-0.2.13 → quash_mcp-0.2.15}/quash_mcp/tools/execute_v3.py +60 -48
- quash_mcp-0.2.13/test_backend_integration.py +0 -100
- quash_mcp-0.2.13/test_tools_loading.py +0 -81
- {quash_mcp-0.2.13 → quash_mcp-0.2.15}/.gitignore +0 -0
- {quash_mcp-0.2.13 → quash_mcp-0.2.15}/README.md +0 -0
- {quash_mcp-0.2.13 → quash_mcp-0.2.15}/SETUP_CLAUDE_CODE.md +0 -0
- {quash_mcp-0.2.13 → quash_mcp-0.2.15}/quash_mcp/__init__.py +0 -0
- {quash_mcp-0.2.13 → quash_mcp-0.2.15}/quash_mcp/__main__.py +0 -0
- {quash_mcp-0.2.13 → quash_mcp-0.2.15}/quash_mcp/device/__init__.py +0 -0
- {quash_mcp-0.2.13 → quash_mcp-0.2.15}/quash_mcp/device/adb_tools.py +0 -0
- {quash_mcp-0.2.13 → quash_mcp-0.2.15}/quash_mcp/device/portal.py +0 -0
- {quash_mcp-0.2.13 → quash_mcp-0.2.15}/quash_mcp/device/state_capture.py +0 -0
- {quash_mcp-0.2.13 → quash_mcp-0.2.15}/quash_mcp/models.py +0 -0
- {quash_mcp-0.2.13 → quash_mcp-0.2.15}/quash_mcp/server.py +0 -0
- {quash_mcp-0.2.13 → quash_mcp-0.2.15}/quash_mcp/state.py +0 -0
- {quash_mcp-0.2.13 → quash_mcp-0.2.15}/quash_mcp/tools/__init__.py +0 -0
- {quash_mcp-0.2.13 → quash_mcp-0.2.15}/quash_mcp/tools/build.py +0 -0
- {quash_mcp-0.2.13 → quash_mcp-0.2.15}/quash_mcp/tools/build_old.py +0 -0
- {quash_mcp-0.2.13 → quash_mcp-0.2.15}/quash_mcp/tools/configure.py +0 -0
- {quash_mcp-0.2.13 → quash_mcp-0.2.15}/quash_mcp/tools/connect.py +0 -0
- {quash_mcp-0.2.13 → quash_mcp-0.2.15}/quash_mcp/tools/execute.py +0 -0
- {quash_mcp-0.2.13 → quash_mcp-0.2.15}/quash_mcp/tools/execute_v2_backup.py +0 -0
- {quash_mcp-0.2.13 → quash_mcp-0.2.15}/quash_mcp/tools/runsuite.py +0 -0
- {quash_mcp-0.2.13 → quash_mcp-0.2.15}/quash_mcp/tools/usage.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: quash-mcp
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.15
|
|
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
|
|
@@ -13,13 +13,15 @@ from quash_mcp.models import SessionDTO, ConfigInfo, UIStateInfo
|
|
|
13
13
|
async def main():
|
|
14
14
|
# Initialize the session state
|
|
15
15
|
state = get_state()
|
|
16
|
-
state.config["api_key"] = "
|
|
16
|
+
state.config["api_key"] = "mhg_AMncG3jkTtEntd8vTqJk3nHl6rnxceRr"
|
|
17
17
|
state.device_serial = "emulator-5554"
|
|
18
18
|
state.portal_ready = True # Assume portal is ready for local testing
|
|
19
19
|
|
|
20
20
|
# Define the task
|
|
21
|
-
task = "Open
|
|
22
|
-
|
|
21
|
+
# task = "Open the google search app and search for bakeries."
|
|
22
|
+
task = "Open settings and scroll 3 times."
|
|
23
|
+
# task = "Go home"
|
|
24
|
+
# task = "Open chrome, search for quashbugs.com and scroll down."
|
|
23
25
|
|
|
24
26
|
# Define a progress callback
|
|
25
27
|
def progress_callback(message):
|
|
@@ -20,7 +20,7 @@ class BackendClient:
|
|
|
20
20
|
|
|
21
21
|
def __init__(self):
|
|
22
22
|
# Get backend URL from environment variable, default to production backend
|
|
23
|
-
self.base_url = os.getenv("MAHORAGA_BACKEND_URL", "
|
|
23
|
+
self.base_url = os.getenv("MAHORAGA_BACKEND_URL", "http://localhost:8000")
|
|
24
24
|
self.timeout = 300.0 # 5 minutes for long-running LLM calls
|
|
25
25
|
logger.info(f"🔧 Backend client initialized: URL={self.base_url}")
|
|
26
26
|
|
|
@@ -35,7 +35,8 @@ class BackendClient:
|
|
|
35
35
|
Dict with validation result:
|
|
36
36
|
{
|
|
37
37
|
"valid": bool,
|
|
38
|
-
"user": {"email": str, "name": str
|
|
38
|
+
"user": {"email": str, "name": str},
|
|
39
|
+
"organization_credits": float,
|
|
39
40
|
"openrouter_api_key": str,
|
|
40
41
|
"error": str (if invalid)
|
|
41
42
|
}
|
|
@@ -17,6 +17,7 @@ from ..state import get_state
|
|
|
17
17
|
from ..backend_client import get_backend_client
|
|
18
18
|
from ..device.state_capture import get_device_state
|
|
19
19
|
from ..device.adb_tools import AdbTools
|
|
20
|
+
import logging
|
|
20
21
|
|
|
21
22
|
# Import mahoraga components for tool functions
|
|
22
23
|
try:
|
|
@@ -236,12 +237,12 @@ async def execute_v3(
|
|
|
236
237
|
|
|
237
238
|
# Check credits
|
|
238
239
|
user_info = validation_result.get("user", {})
|
|
239
|
-
|
|
240
|
+
organization_credits = validation_result.get("organization_credits", 0)
|
|
240
241
|
|
|
241
|
-
if
|
|
242
|
+
if organization_credits <= 0:
|
|
242
243
|
return {
|
|
243
244
|
"status": "error",
|
|
244
|
-
"message": f"❌ Insufficient credits. Current balance: ${
|
|
245
|
+
"message": f"❌ Insufficient credits. Current balance: ${organization_credits:.2f}",
|
|
245
246
|
"user": user_info
|
|
246
247
|
}
|
|
247
248
|
|
|
@@ -250,7 +251,7 @@ async def execute_v3(
|
|
|
250
251
|
if progress_callback:
|
|
251
252
|
progress_callback(message)
|
|
252
253
|
|
|
253
|
-
log_progress(f"✅ API Key validated - Credits: ${
|
|
254
|
+
log_progress(f"✅ API Key validated - Credits: ${organization_credits:.2f}")
|
|
254
255
|
log_progress(f"👤 User: {user_info.get('name', 'Unknown')}")
|
|
255
256
|
log_progress(f"🚀 Starting task: {task}")
|
|
256
257
|
log_progress(f"📱 Device: {state.device_serial}")
|
|
@@ -315,6 +316,15 @@ async def execute_v3(
|
|
|
315
316
|
# Add wrapped function to globals so code can call it directly
|
|
316
317
|
executor_globals[tool_name] = make_printing_wrapper(tool_function)
|
|
317
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
|
+
|
|
318
328
|
log_progress(f"🔧 Loaded {len(filtered_tools)} tool functions: {list(filtered_tools.keys())}")
|
|
319
329
|
except Exception as e:
|
|
320
330
|
log_progress(f"⚠️ Warning: Could not load tool functions: {e}")
|
|
@@ -402,50 +412,8 @@ async def execute_v3(
|
|
|
402
412
|
log_progress(f"🤔 Reasoning: {reasoning}")
|
|
403
413
|
|
|
404
414
|
|
|
405
|
-
# 3.
|
|
406
|
-
|
|
407
|
-
success = step_result.get("success", False)
|
|
408
|
-
final_message = step_result.get("final_message", "Task completed")
|
|
409
|
-
|
|
410
|
-
duration = time.time() - start_time
|
|
411
|
-
|
|
412
|
-
if success:
|
|
413
|
-
log_progress(f"✅ Task completed successfully!")
|
|
414
|
-
else:
|
|
415
|
-
log_progress(f"❌ Task marked as failed")
|
|
416
|
-
|
|
417
|
-
# Finalize session on backend
|
|
418
|
-
finalize_result = await backend.finalize_session(session=session)
|
|
419
|
-
|
|
420
|
-
if success:
|
|
421
|
-
log_progress(f"✅ Task completed successfully in {len(session.steps)} steps")
|
|
422
|
-
log_progress(f"💰 Usage: {finalize_result.get('total_tokens', {}).get('total')} tokens, ${finalize_result.get('total_cost', 0):.4f}")
|
|
423
|
-
|
|
424
|
-
return {
|
|
425
|
-
"status": "success",
|
|
426
|
-
"steps_taken": len(session.steps),
|
|
427
|
-
"final_message": final_message,
|
|
428
|
-
"message": f"✅ Success: {final_message}",
|
|
429
|
-
"tokens": finalize_result.get("total_tokens"),
|
|
430
|
-
"cost": finalize_result.get("total_cost"),
|
|
431
|
-
"duration_seconds": duration
|
|
432
|
-
}
|
|
433
|
-
else:
|
|
434
|
-
log_progress(f"❌ Task failed: {final_message}")
|
|
435
|
-
log_progress(f"💰 Usage: {finalize_result.get('total_tokens', {}).get('total')} tokens, ${finalize_result.get('total_cost', 0):.4f}")
|
|
436
|
-
|
|
437
|
-
return {
|
|
438
|
-
"status": "failed",
|
|
439
|
-
"steps_taken": len(session.steps),
|
|
440
|
-
"final_message": final_message,
|
|
441
|
-
"message": f"❌ Failed: {final_message}",
|
|
442
|
-
"tokens": finalize_result.get("total_tokens"),
|
|
443
|
-
"cost": finalize_result.get("total_cost"),
|
|
444
|
-
"duration_seconds": duration
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
# 4. Execute action locally (only if task is not complete)
|
|
415
|
+
# 3. Execute action locally FIRST (if provided)
|
|
416
|
+
# NOTE: Backend should have already removed complete() from the code
|
|
449
417
|
if code and action_type == "execute_code":
|
|
450
418
|
log_progress(f"⚡ Executing action...")
|
|
451
419
|
|
|
@@ -514,6 +482,8 @@ async def execute_v3(
|
|
|
514
482
|
|
|
515
483
|
session.chat_history.append(ChatHistoryMessage(role="user", content=f"Execution Result:\n```\n{feedback}\n```"))
|
|
516
484
|
|
|
485
|
+
# Introduce a small delay to allow UI effects to settle before checking completion
|
|
486
|
+
time.sleep(1.0) # Added delay
|
|
517
487
|
|
|
518
488
|
except Exception as e:
|
|
519
489
|
error_msg = f"Error during execution: {str(e)}"
|
|
@@ -526,6 +496,48 @@ async def execute_v3(
|
|
|
526
496
|
session.chat_history.append(ChatHistoryMessage(role="user", content="No code was provided. Please provide code to execute."))
|
|
527
497
|
|
|
528
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
|
+
|
|
504
|
+
duration = time.time() - start_time
|
|
505
|
+
|
|
506
|
+
if success:
|
|
507
|
+
log_progress(f"✅ Task completed successfully!")
|
|
508
|
+
else:
|
|
509
|
+
log_progress(f"❌ Task marked as failed")
|
|
510
|
+
|
|
511
|
+
# Finalize session on backend
|
|
512
|
+
finalize_result = await backend.finalize_session(session=session)
|
|
513
|
+
|
|
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
|
+
}
|
|
540
|
+
|
|
529
541
|
|
|
530
542
|
# Max steps reached
|
|
531
543
|
log_progress(f"⚠️ Reached maximum steps ({max_steps})")
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
Test script to verify MCP -> Backend integration.
|
|
4
|
-
Tests API key validation and execution logging with real backend.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
import asyncio
|
|
8
|
-
import os
|
|
9
|
-
import sys
|
|
10
|
-
from pathlib import Path
|
|
11
|
-
from dotenv import load_dotenv
|
|
12
|
-
|
|
13
|
-
# Load .env file from project root
|
|
14
|
-
project_root = Path(__file__).parent.parent
|
|
15
|
-
env_file = project_root / ".env"
|
|
16
|
-
load_dotenv(env_file)
|
|
17
|
-
|
|
18
|
-
# Add src to path
|
|
19
|
-
sys.path.insert(0, str(Path(__file__).parent / "src"))
|
|
20
|
-
|
|
21
|
-
from backend_client import get_backend_client
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
async def test_integration():
|
|
25
|
-
"""Test MCP backend integration."""
|
|
26
|
-
|
|
27
|
-
print("=" * 60)
|
|
28
|
-
print("🧪 Testing Mahoraga MCP -> Backend Integration")
|
|
29
|
-
print("=" * 60)
|
|
30
|
-
|
|
31
|
-
# Check environment
|
|
32
|
-
mock_mode = os.getenv("MAHORAGA_MOCK_BACKEND", "true")
|
|
33
|
-
backend_url = os.getenv("MAHORAGA_BACKEND_URL", "http://localhost:8000")
|
|
34
|
-
|
|
35
|
-
print(f"\n📋 Configuration:")
|
|
36
|
-
print(f" Mock Mode: {mock_mode}")
|
|
37
|
-
print(f" Backend URL: {backend_url}")
|
|
38
|
-
|
|
39
|
-
# Get backend client
|
|
40
|
-
client = get_backend_client()
|
|
41
|
-
|
|
42
|
-
# Test API key from database
|
|
43
|
-
test_api_key = "mah_test1234567890abcdef"
|
|
44
|
-
|
|
45
|
-
print(f"\n🔑 Testing API Key Validation...")
|
|
46
|
-
print(f" API Key: {test_api_key}")
|
|
47
|
-
|
|
48
|
-
# Test validation
|
|
49
|
-
result = await client.validate_api_key(test_api_key)
|
|
50
|
-
|
|
51
|
-
if result.get("valid"):
|
|
52
|
-
print(" ✅ API Key Valid!")
|
|
53
|
-
user = result.get("user", {})
|
|
54
|
-
print(f" 👤 User: {user.get('name')} ({user.get('email')})")
|
|
55
|
-
print(f" 💰 Credits: ${user.get('credits', 0):.2f}")
|
|
56
|
-
else:
|
|
57
|
-
print(f" ❌ API Key Invalid: {result.get('error')}")
|
|
58
|
-
return False
|
|
59
|
-
|
|
60
|
-
# Test execution logging
|
|
61
|
-
print(f"\n📊 Testing Execution Logging...")
|
|
62
|
-
|
|
63
|
-
log_result = await client.log_execution(
|
|
64
|
-
api_key=test_api_key,
|
|
65
|
-
execution_id="test_exec_integration_001",
|
|
66
|
-
status="completed",
|
|
67
|
-
tokens={"prompt": 150, "completion": 75, "total": 225},
|
|
68
|
-
cost=0.05
|
|
69
|
-
)
|
|
70
|
-
|
|
71
|
-
if log_result.get("logged"):
|
|
72
|
-
print(" ✅ Execution Logged!")
|
|
73
|
-
print(f" 💸 Credits Deducted: ${log_result.get('credits_deducted', 0):.2f}")
|
|
74
|
-
print(f" 💰 New Balance: ${log_result.get('new_balance', 0):.2f}")
|
|
75
|
-
else:
|
|
76
|
-
print(f" ❌ Logging Failed: {log_result.get('error')}")
|
|
77
|
-
return False
|
|
78
|
-
|
|
79
|
-
# Verify credits were deducted
|
|
80
|
-
print(f"\n🔍 Verifying Credit Deduction...")
|
|
81
|
-
|
|
82
|
-
verify_result = await client.validate_api_key(test_api_key)
|
|
83
|
-
if verify_result.get("valid"):
|
|
84
|
-
new_credits = verify_result.get("user", {}).get("credits", 0)
|
|
85
|
-
print(f" ✅ Credits Updated: ${new_credits:.2f}")
|
|
86
|
-
|
|
87
|
-
print(f"\n" + "=" * 60)
|
|
88
|
-
print("✅ All Integration Tests Passed!")
|
|
89
|
-
print("=" * 60)
|
|
90
|
-
print("\n💡 Next Steps:")
|
|
91
|
-
print(" 1. MCP is now connected to real backend")
|
|
92
|
-
print(" 2. Test with actual MCP execute command")
|
|
93
|
-
print(" 3. Build web portal for user management")
|
|
94
|
-
|
|
95
|
-
return True
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
if __name__ == "__main__":
|
|
99
|
-
success = asyncio.run(test_integration())
|
|
100
|
-
sys.exit(0 if success else 1)
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Test script to verify tool functions are loaded correctly.
|
|
3
|
-
"""
|
|
4
|
-
|
|
5
|
-
import sys
|
|
6
|
-
sys.path.insert(0, '/Users/abhinavsai/POC/mahoraga-mac/quash-mcp')
|
|
7
|
-
sys.path.insert(0, '/Users/abhinavsai/POC/mahoraga-mac/mahoraga')
|
|
8
|
-
|
|
9
|
-
def test_tool_loading():
|
|
10
|
-
print("Testing tool loading...")
|
|
11
|
-
|
|
12
|
-
try:
|
|
13
|
-
# Import mahoraga components
|
|
14
|
-
from mahoraga.tools import Tools, describe_tools
|
|
15
|
-
from mahoraga.tools.adb import AdbTools as MahoragaAdbTools
|
|
16
|
-
from mahoraga.agent.context.personas import DEFAULT
|
|
17
|
-
from mahoraga.agent.utils.async_utils import async_to_sync
|
|
18
|
-
|
|
19
|
-
print("✅ All imports successful")
|
|
20
|
-
|
|
21
|
-
# Create a mahoraga AdbTools instance
|
|
22
|
-
print("\nCreating mahoraga AdbTools instance...")
|
|
23
|
-
mahoraga_tools = MahoragaAdbTools(
|
|
24
|
-
serial="emulator-5554", # Use your device serial
|
|
25
|
-
use_tcp=True,
|
|
26
|
-
remote_tcp_port=8080
|
|
27
|
-
)
|
|
28
|
-
print(f"✅ Created mahoraga AdbTools instance")
|
|
29
|
-
print(f" - Serial: {mahoraga_tools.device.serial}")
|
|
30
|
-
print(f" - TCP forwarded: {mahoraga_tools.tcp_forwarded}")
|
|
31
|
-
|
|
32
|
-
# Get tool list
|
|
33
|
-
print("\nGetting tool list...")
|
|
34
|
-
tool_list = describe_tools(mahoraga_tools, exclude_tools=None)
|
|
35
|
-
print(f"✅ Got {len(tool_list)} tools:")
|
|
36
|
-
for tool_name, tool_func in tool_list.items():
|
|
37
|
-
print(f" - {tool_name}: {tool_func}")
|
|
38
|
-
|
|
39
|
-
# Filter by allowed tools
|
|
40
|
-
print(f"\nFiltering by DEFAULT persona allowed tools...")
|
|
41
|
-
allowed_tool_names = DEFAULT.allowed_tools
|
|
42
|
-
print(f" Allowed tools: {allowed_tool_names}")
|
|
43
|
-
|
|
44
|
-
filtered_tools = {name: func for name, func in tool_list.items() if name in allowed_tool_names}
|
|
45
|
-
print(f"✅ Filtered to {len(filtered_tools)} tools:")
|
|
46
|
-
for tool_name in filtered_tools.keys():
|
|
47
|
-
print(f" - {tool_name}")
|
|
48
|
-
|
|
49
|
-
# Test executor globals setup
|
|
50
|
-
print("\nSetting up executor globals...")
|
|
51
|
-
executor_globals = {"__builtins__": __builtins__}
|
|
52
|
-
|
|
53
|
-
for tool_name, tool_function in filtered_tools.items():
|
|
54
|
-
import asyncio
|
|
55
|
-
if asyncio.iscoroutinefunction(tool_function):
|
|
56
|
-
tool_function = async_to_sync(tool_function)
|
|
57
|
-
executor_globals[tool_name] = tool_function
|
|
58
|
-
|
|
59
|
-
print(f"✅ Executor globals set up with {len(executor_globals)} items")
|
|
60
|
-
|
|
61
|
-
# Test that functions are callable
|
|
62
|
-
print("\nTesting function availability...")
|
|
63
|
-
test_functions = ['start_app', 'swipe', 'press_key', 'tap_by_index']
|
|
64
|
-
for func_name in test_functions:
|
|
65
|
-
if func_name in executor_globals:
|
|
66
|
-
print(f" ✅ {func_name} is available")
|
|
67
|
-
else:
|
|
68
|
-
print(f" ❌ {func_name} is NOT available")
|
|
69
|
-
|
|
70
|
-
print("\n✅ All tests passed!")
|
|
71
|
-
return True
|
|
72
|
-
|
|
73
|
-
except Exception as e:
|
|
74
|
-
print(f"\n❌ Test failed: {e}")
|
|
75
|
-
import traceback
|
|
76
|
-
traceback.print_exc()
|
|
77
|
-
return False
|
|
78
|
-
|
|
79
|
-
if __name__ == "__main__":
|
|
80
|
-
success = test_tool_loading()
|
|
81
|
-
sys.exit(0 if success else 1)
|
|
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
|