@leejungkiin/awkit 1.7.0 → 1.7.4
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.
- package/bin/awk.js +576 -84
- package/core/CLAUDE.md +1 -1
- package/core/GEMINI.md +148 -167
- package/core/GEMINI.md.bak +149 -116
- package/core/skill-runtime-manifest.json +3 -0
- package/docs/Claude Fable 5.md +3826 -0
- package/docs/android_kotlin_system_instruction.md +210 -0
- package/docs/brainstorm_ponytail_integration.md +146 -0
- package/docs/brainstorm_smart_setup.md +113 -0
- package/docs/deep-research-report (1).md +293 -0
- package/docs/history/GEMINI.v1.md +135 -0
- package/docs/history/brainstorm_antigravity_unified_architecture.v1.md +105 -0
- package/docs/history/implementation_plan.v1.md +58 -0
- package/package.json +4 -1
- package/scripts/artifact-storage.js +130 -0
- package/scripts/automation-gate.js +40 -7
- package/scripts/claude-plan.js +76 -0
- package/scripts/dependency-manager.js +210 -0
- package/scripts/exec-rtk.js +11 -5
- package/scripts/i18n-helper.js +381 -0
- package/scripts/multi-model-pipeline.js +144 -0
- package/skill-packs/mobile-ios/pack.json +4 -2
- package/skill-packs/reverse-engineering/pack.json +1 -0
- package/skills/CATALOG.md +20 -0
- package/skills/GEMINI.md +9 -1
- package/skills/TRIGGER_INDEX.md +10 -0
- package/skills/ai-music/SKILL.md +275 -0
- package/skills/android-re-analyzer/SKILL.md +238 -0
- package/skills/android-re-analyzer/references/api-extraction-patterns.md +119 -0
- package/skills/android-re-analyzer/references/call-flow-analysis.md +176 -0
- package/skills/android-re-analyzer/references/fernflower-usage.md +115 -0
- package/skills/android-re-analyzer/references/jadx-usage.md +116 -0
- package/skills/android-re-analyzer/references/setup-guide.md +221 -0
- package/skills/android-re-analyzer/scripts/check-deps.sh +129 -0
- package/skills/android-re-analyzer/scripts/decompile.sh +375 -0
- package/skills/android-re-analyzer/scripts/find-api-calls.sh +118 -0
- package/skills/android-re-analyzer/scripts/install-dep.sh +448 -0
- package/skills/animal-island-ui-style/SKILL.md +1450 -0
- package/skills/app-store-review-agent/SKILL.md +164 -0
- package/skills/app-store-review-agent/references/guidelines/README.md +154 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/ai_apps.md +37 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/all_apps.md +50 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/crypto_finance.md +31 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/games.md +31 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/health_fitness.md +31 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/kids.md +27 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/macos.md +38 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/social_ugc.md +32 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/subscription_iap.md +34 -0
- package/skills/app-store-review-agent/references/guidelines/by-app-type/vpn.md +18 -0
- package/skills/app-store-review-agent/references/rules/design/minimum_functionality.md +96 -0
- package/skills/app-store-review-agent/references/rules/design/sign_in_with_apple.md +54 -0
- package/skills/app-store-review-agent/references/rules/entitlements/unused_entitlements.md +83 -0
- package/skills/app-store-review-agent/references/rules/metadata/accurate_metadata.md +54 -0
- package/skills/app-store-review-agent/references/rules/metadata/apple_trademark.md +99 -0
- package/skills/app-store-review-agent/references/rules/metadata/china_storefront.md +72 -0
- package/skills/app-store-review-agent/references/rules/metadata/competitor_terms.md +56 -0
- package/skills/app-store-review-agent/references/rules/metadata/subscription_metadata.md +81 -0
- package/skills/app-store-review-agent/references/rules/privacy/privacy_manifest.md +84 -0
- package/skills/app-store-review-agent/references/rules/privacy/unnecessary_data.md +60 -0
- package/skills/app-store-review-agent/references/rules/subscription/misleading_pricing.md +63 -0
- package/skills/app-store-review-agent/references/rules/subscription/missing_tos_pp.md +54 -0
- package/skills/awf-ponytail/SKILL.md +91 -0
- package/skills/awf-ponytail-review/SKILL.md +67 -0
- package/skills/awf-session-restore/SKILL.md +3 -3
- package/skills/brainstorm-agent/SKILL.md +11 -2
- package/skills/brainstorm-agent/templates/brief-template.md +8 -0
- package/skills/claude-planner/SKILL.md +47 -0
- package/skills/code-review/SKILL.md +87 -0
- package/skills/expo-game-development/SKILL.md +163 -0
- package/skills/flutter/LICENSE.txt +202 -0
- package/skills/flutter/SKILL.md +127 -0
- package/skills/flutter-project-creater/LICENSE.txt +202 -0
- package/skills/flutter-project-creater/SKILL.md +106 -0
- package/skills/game-developer/SKILL.md +163 -0
- package/skills/game-developer/references/ecs-patterns.md +501 -0
- package/skills/game-developer/references/multiplayer-networking.md +475 -0
- package/skills/game-developer/references/performance-optimization.md +422 -0
- package/skills/game-developer/references/unity-patterns.md +271 -0
- package/skills/game-developer/references/unreal-cpp.md +352 -0
- package/skills/generate-gui-assets/SKILL.md +305 -0
- package/skills/generate-gui-assets/agents/openai.yaml +4 -0
- package/skills/generate-gui-assets/references/catalog-schema.md +58 -0
- package/skills/generate-gui-assets/references/extraction-techniques.md +21 -0
- package/skills/generate-gui-assets/references/prompt-patterns.md +58 -0
- package/skills/generate-gui-assets/scripts/__pycache__/clean_chroma_edges.cpython-311.pyc +0 -0
- package/skills/generate-gui-assets/scripts/build_gui_contact_sheet.py +51 -0
- package/skills/generate-gui-assets/scripts/clean_chroma_edges.py +262 -0
- package/skills/generate-gui-assets/scripts/copy_approved_icons.py +64 -0
- package/skills/generate-gui-assets/scripts/prepare_gui_asset_run.py +91 -0
- package/skills/generate-gui-assets/scripts/suggest_grid_options.py +63 -0
- package/skills/generate-gui-assets/scripts/validate_gui_catalog.py +50 -0
- package/skills/godot-game-development/SKILL.md +142 -0
- package/skills/hatch-pet/LICENSE.txt +201 -0
- package/skills/hatch-pet/SKILL.md +420 -0
- package/skills/hatch-pet/agents/openai.yaml +4 -0
- package/skills/hatch-pet/references/animation-rows.md +29 -0
- package/skills/hatch-pet/references/codex-pet-contract.md +35 -0
- package/skills/hatch-pet/references/qa-rubric.md +60 -0
- package/skills/hatch-pet/scripts/__pycache__/clean_chroma_edges.cpython-311.pyc +0 -0
- package/skills/hatch-pet/scripts/clean_chroma_edges.py +262 -0
- package/skills/hatch-pet/scripts/compose_atlas.py +150 -0
- package/skills/hatch-pet/scripts/derive_running_left_from_running_right.py +143 -0
- package/skills/hatch-pet/scripts/extract_strip_frames.py +323 -0
- package/skills/hatch-pet/scripts/finalize_pet_run.py +382 -0
- package/skills/hatch-pet/scripts/generate_pet_images.py +287 -0
- package/skills/hatch-pet/scripts/inspect_frames.py +246 -0
- package/skills/hatch-pet/scripts/make_contact_sheet.py +96 -0
- package/skills/hatch-pet/scripts/package_custom_pet.py +108 -0
- package/skills/hatch-pet/scripts/pet_job_status.py +117 -0
- package/skills/hatch-pet/scripts/prepare_pet_run.py +673 -0
- package/skills/hatch-pet/scripts/queue_pet_repairs.py +172 -0
- package/skills/hatch-pet/scripts/record_imagegen_result.py +250 -0
- package/skills/hatch-pet/scripts/render_animation_videos.py +134 -0
- package/skills/hatch-pet/scripts/render_animation_videos.sh +5 -0
- package/skills/hatch-pet/scripts/validate_atlas.py +139 -0
- package/skills/i18n-orchestrator/SKILL.md +37 -0
- package/skills/ios-simulator-skill/SKILL.md +390 -0
- package/skills/ios-simulator-skill/scripts/accessibility_audit.py +300 -0
- package/skills/ios-simulator-skill/scripts/app_launcher.py +326 -0
- package/skills/ios-simulator-skill/scripts/app_state_capture.py +400 -0
- package/skills/ios-simulator-skill/scripts/appearance.py +385 -0
- package/skills/ios-simulator-skill/scripts/build_and_test.py +348 -0
- package/skills/ios-simulator-skill/scripts/clipboard.py +103 -0
- package/skills/ios-simulator-skill/scripts/common/__init__.py +61 -0
- package/skills/ios-simulator-skill/scripts/common/cache_utils.py +289 -0
- package/skills/ios-simulator-skill/scripts/common/device_utils.py +462 -0
- package/skills/ios-simulator-skill/scripts/common/env_config.py +35 -0
- package/skills/ios-simulator-skill/scripts/common/hang_pipeline.py +862 -0
- package/skills/ios-simulator-skill/scripts/common/hang_sessions.py +490 -0
- package/skills/ios-simulator-skill/scripts/common/idb_utils.py +180 -0
- package/skills/ios-simulator-skill/scripts/common/screenshot_utils.py +338 -0
- package/skills/ios-simulator-skill/scripts/container.py +668 -0
- package/skills/ios-simulator-skill/scripts/gesture.py +394 -0
- package/skills/ios-simulator-skill/scripts/hang_watcher.py +1533 -0
- package/skills/ios-simulator-skill/scripts/keyboard.py +391 -0
- package/skills/ios-simulator-skill/scripts/localization_audit.py +483 -0
- package/skills/ios-simulator-skill/scripts/location.py +467 -0
- package/skills/ios-simulator-skill/scripts/log_monitor.py +493 -0
- package/skills/ios-simulator-skill/scripts/model_inspector.py +645 -0
- package/skills/ios-simulator-skill/scripts/navigator.py +461 -0
- package/skills/ios-simulator-skill/scripts/privacy_manager.py +310 -0
- package/skills/ios-simulator-skill/scripts/push_notification.py +240 -0
- package/skills/ios-simulator-skill/scripts/screen_mapper.py +296 -0
- package/skills/ios-simulator-skill/scripts/sim_health_check.sh +245 -0
- package/skills/ios-simulator-skill/scripts/sim_list.py +299 -0
- package/skills/ios-simulator-skill/scripts/simctl_boot.py +312 -0
- package/skills/ios-simulator-skill/scripts/simctl_create.py +316 -0
- package/skills/ios-simulator-skill/scripts/simctl_delete.py +357 -0
- package/skills/ios-simulator-skill/scripts/simctl_erase.py +351 -0
- package/skills/ios-simulator-skill/scripts/simctl_shutdown.py +290 -0
- package/skills/ios-simulator-skill/scripts/simulator_selector.py +375 -0
- package/skills/ios-simulator-skill/scripts/status_bar.py +250 -0
- package/skills/ios-simulator-skill/scripts/test_recorder.py +323 -0
- package/skills/ios-simulator-skill/scripts/visual_diff.py +235 -0
- package/skills/ios-simulator-skill/scripts/xcode/__init__.py +13 -0
- package/skills/ios-simulator-skill/scripts/xcode/builder.py +397 -0
- package/skills/ios-simulator-skill/scripts/xcode/cache.py +204 -0
- package/skills/ios-simulator-skill/scripts/xcode/config.py +178 -0
- package/skills/ios-simulator-skill/scripts/xcode/reporter.py +343 -0
- package/skills/ios-simulator-skill/scripts/xcode/xcresult.py +451 -0
- package/skills/ios-visual-qa-strategist/SKILL.md +111 -0
- package/skills/ios-visual-qa-strategist/agents/openai.yaml +4 -0
- package/skills/ios-visual-qa-strategist/references/ios-tool-selection.md +61 -0
- package/skills/ios-visual-qa-strategist/references/minimal-capture-policy.md +56 -0
- package/skills/ios-visual-qa-strategist/references/visual-reasoning-heuristics.md +53 -0
- package/skills/orchestrator/SKILL.md +0 -20
- package/skills/persistent-storage/SKILL.md +55 -0
- package/skills/short-maker/SKILL.md +23 -0
- package/skills/short-maker/scripts/effects.js +56 -0
- package/skills/short-maker/scripts/shortmaker-bridge.js +332 -0
- package/skills/short-maker/scripts/videomix.js +601 -0
- package/skills/short-maker/templates/hyperframes/cinematic-character.template.html +172 -0
- package/skills/short-maker/templates/hyperframes/index.template.html +194 -0
- package/skills/smali-to-kotlin/SKILL.md +128 -0
- package/skills/smali-to-kotlin/examples/getting-started/tech-stack.md +58 -0
- package/skills/smali-to-kotlin/examples/pipeline/data-ui-parity.md +118 -0
- package/skills/smali-to-kotlin/examples/pipeline/scanner-and-bootstrap.md +106 -0
- package/skills/smali-to-kotlin/library-patterns.md +189 -0
- package/skills/smali-to-kotlin/phase-0-discovery.md +128 -0
- package/skills/smali-to-kotlin/phase-1-architecture.md +166 -0
- package/skills/smali-to-kotlin/phase-2-blueprint-ui.md +347 -0
- package/skills/smali-to-kotlin/phase-2-blueprint.md +228 -0
- package/skills/smali-to-kotlin/phase-3-build.md +248 -0
- package/skills/smali-to-kotlin/phase-3-logic-build.md +268 -0
- package/skills/smali-to-kotlin/smali-reading-guide.md +310 -0
- package/skills/smali-to-kotlin/templates/app-map.md +101 -0
- package/skills/smali-to-kotlin/templates/architecture.md +142 -0
- package/skills/smali-to-kotlin/templates/blueprint.md +145 -0
- package/skills/spec-gate/SKILL.md +6 -2
- package/skills/symphony-enforcer/SKILL.md +8 -0
- package/skills/symphony-enforcer/examples/mindful-stop.md +2 -0
- package/skills/symphony-enforcer/examples/three-phase.md +16 -0
- package/skills/symphony-enforcer/examples/trigger-points.md +7 -1
- package/skills/unity-game-development/SKILL.md +231 -0
- package/skills/verification-gate/SKILL.md +4 -2
- package/skills/video-edit/SKILL.md +36 -0
- package/skills/video-edit/scripts/video_edit.py +324 -0
- package/templates/setup-mapping.json +48 -0
- package/templates/specs/design-template.md +161 -71
- package/templates/specs/requirements-template.md +65 -133
- package/templates/specs/task-spec-template.xml +3 -0
- package/workflows/_uncategorized/critic.md +40 -0
- package/workflows/_uncategorized/git-rebase-flow.md +81 -0
- package/workflows/_uncategorized/image-gen.md +118 -0
- package/workflows/_uncategorized/multi-model-pipeline.md +60 -0
- package/workflows/_uncategorized/pixel-gen.md +86 -0
- package/workflows/_uncategorized/pixel-setup.md +90 -0
- package/workflows/_uncategorized/ponytail-review.md +59 -0
- package/workflows/_uncategorized/reverse-android-build.md +222 -0
- package/workflows/_uncategorized/reverse-android-design.md +139 -0
- package/workflows/_uncategorized/reverse-android-discover.md +150 -0
- package/workflows/_uncategorized/reverse-android-scan.md +158 -0
- package/workflows/_uncategorized/reverse-android.md +143 -0
- package/workflows/_uncategorized/reverse-ios-build.md +240 -0
- package/workflows/_uncategorized/reverse-ios-design.md +112 -0
- package/workflows/_uncategorized/reverse-ios-discover.md +120 -0
- package/workflows/_uncategorized/reverse-ios-scan.md +155 -0
- package/workflows/_uncategorized/reverse-ios.md +152 -0
- package/workflows/_uncategorized/safety-router.md +34 -0
- package/workflows/_uncategorized/teach.md +89 -0
- package/workflows/_uncategorized/verify-ui.md +53 -0
- package/workflows/_uncategorized/visualize-screenshots.md +34 -0
- package/workflows/ads/ads-analyst.md +201 -0
- package/workflows/ads/ads-audit.md +106 -0
- package/workflows/ads/ads-optimize.md +97 -0
- package/workflows/ads/ads-targeting.md +241 -0
- package/workflows/ads/adsExpert.md +160 -0
- package/workflows/ads/smali-ads-config.md +400 -0
- package/workflows/ads/smali-ads-flow.md +331 -0
- package/workflows/ads/smali-ads-interstitial.md +377 -0
- package/workflows/ads/smali-ads-native.md +382 -0
- package/workflows/context/teach.md +89 -0
- package/workflows/gitnexus.md +8 -8
- package/workflows/lifecycle/brainstorm.md +43 -0
- package/workflows/lifecycle/code.md +5 -0
- package/workflows/lifecycle/init.md +23 -5
- package/workflows/lifecycle/multi-model-pipeline.md +60 -0
- package/workflows/quality/ponytail-review.md +59 -0
- package/workflows/roles/critic.md +40 -0
- package/workflows/roles/safety-router.md +34 -0
|
@@ -0,0 +1,467 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
GPS location simulation for iOS simulators.
|
|
4
|
+
|
|
5
|
+
Set a fixed coordinate, use a named city preset, play back a GPX route,
|
|
6
|
+
walk a multi-waypoint path, or clear any active location override.
|
|
7
|
+
|
|
8
|
+
Wraps `xcrun simctl location <udid> set/clear/start/run`.
|
|
9
|
+
|
|
10
|
+
Key features:
|
|
11
|
+
- Set precise lat/lng coordinates
|
|
12
|
+
- 15 built-in city presets
|
|
13
|
+
- GPX scenario playback (built-in scenarios only — simctl limitation)
|
|
14
|
+
- Multi-waypoint path with configurable speed and update interval
|
|
15
|
+
- Clear active location override
|
|
16
|
+
- Standard --json, --verbose, and --udid flags
|
|
17
|
+
|
|
18
|
+
Usage examples:
|
|
19
|
+
python scripts/location.py --lat 53.3498 --lng -6.2603
|
|
20
|
+
python scripts/location.py --city Dublin
|
|
21
|
+
python scripts/location.py --gpx FreewayDrive
|
|
22
|
+
python scripts/location.py --waypoints "37.33,-122.03 37.78,-122.41" --speed 10
|
|
23
|
+
python scripts/location.py --clear
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
import argparse
|
|
27
|
+
import json
|
|
28
|
+
import subprocess
|
|
29
|
+
import sys
|
|
30
|
+
|
|
31
|
+
from common import resolve_udid
|
|
32
|
+
|
|
33
|
+
# === PRESETS ===
|
|
34
|
+
|
|
35
|
+
_CITY_ALIASES: set[str] = {"nyc", "sf", "la"}
|
|
36
|
+
|
|
37
|
+
CITY_PRESETS: dict[str, tuple[float, float]] = {
|
|
38
|
+
"dublin": (53.3498, -6.2603),
|
|
39
|
+
"london": (51.5074, -0.1278),
|
|
40
|
+
"newyork": (40.7128, -74.0060),
|
|
41
|
+
"nyc": (40.7128, -74.0060),
|
|
42
|
+
"sanfrancisco": (37.7749, -122.4194),
|
|
43
|
+
"sf": (37.7749, -122.4194),
|
|
44
|
+
"tokyo": (35.6762, 139.6503),
|
|
45
|
+
"sydney": (-33.8688, 151.2093),
|
|
46
|
+
"paris": (48.8566, 2.3522),
|
|
47
|
+
"berlin": (52.5200, 13.4050),
|
|
48
|
+
"beijing": (39.9042, 116.4074),
|
|
49
|
+
"mumbai": (19.0760, 72.8777),
|
|
50
|
+
"cairo": (30.0444, 31.2357),
|
|
51
|
+
"saopaulo": (-23.5505, -46.6333),
|
|
52
|
+
"losangeles": (34.0522, -118.2437),
|
|
53
|
+
"la": (34.0522, -118.2437),
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
# === MAIN CLASS ===
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class LocationManager:
|
|
61
|
+
"""Manage simulated GPS location on an iOS simulator."""
|
|
62
|
+
|
|
63
|
+
def __init__(self, udid: str | None = None):
|
|
64
|
+
"""Initialize with optional device UDID.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
udid: Simulator UDID — auto-detects booted device if None.
|
|
68
|
+
"""
|
|
69
|
+
self.udid = udid
|
|
70
|
+
|
|
71
|
+
def set_coordinate(self, lat: float, lng: float, verbose: bool = False) -> tuple[bool, str]:
|
|
72
|
+
"""
|
|
73
|
+
Set simulator GPS to a fixed coordinate.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
lat: Latitude in decimal degrees (-90 to 90).
|
|
77
|
+
lng: Longitude in decimal degrees (-180 to 180).
|
|
78
|
+
verbose: Include extra context in the returned message.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
(success, message) tuple.
|
|
82
|
+
"""
|
|
83
|
+
if not (-90 <= lat <= 90):
|
|
84
|
+
return False, f"Invalid latitude {lat}: must be between -90 and 90"
|
|
85
|
+
if not (-180 <= lng <= 180):
|
|
86
|
+
return False, f"Invalid longitude {lng}: must be between -180 and 180"
|
|
87
|
+
|
|
88
|
+
coord = f"{lat},{lng}"
|
|
89
|
+
cmd = ["xcrun", "simctl", "location", self.udid, "set", coord]
|
|
90
|
+
|
|
91
|
+
try:
|
|
92
|
+
result = subprocess.run(cmd, check=False, capture_output=True, text=True, timeout=15)
|
|
93
|
+
except subprocess.TimeoutExpired:
|
|
94
|
+
return False, "Error: location set timed out"
|
|
95
|
+
except Exception as e:
|
|
96
|
+
return False, f"Error: {e}"
|
|
97
|
+
|
|
98
|
+
if result.returncode != 0:
|
|
99
|
+
error = result.stderr.strip() or "unknown error"
|
|
100
|
+
return False, f"location set failed: {error}"
|
|
101
|
+
|
|
102
|
+
if verbose:
|
|
103
|
+
return True, (
|
|
104
|
+
f"Location set\n"
|
|
105
|
+
f" Latitude: {lat}\n"
|
|
106
|
+
f" Longitude: {lng}\n"
|
|
107
|
+
f" Device: {self.udid}"
|
|
108
|
+
)
|
|
109
|
+
return True, f"Location set: {lat}, {lng}"
|
|
110
|
+
|
|
111
|
+
def set_city(self, city_name: str, verbose: bool = False) -> tuple[bool, str]:
|
|
112
|
+
"""
|
|
113
|
+
Set location to a named city preset.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
city_name: Case-insensitive city name from CITY_PRESETS.
|
|
117
|
+
verbose: Include coordinate detail in the returned message.
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
(success, message) tuple.
|
|
121
|
+
"""
|
|
122
|
+
key = city_name.lower().replace(" ", "")
|
|
123
|
+
if key not in CITY_PRESETS:
|
|
124
|
+
available = ", ".join(sorted({k for k in CITY_PRESETS if k not in _CITY_ALIASES}))
|
|
125
|
+
return False, f"Unknown city '{city_name}'. Available: {available}"
|
|
126
|
+
|
|
127
|
+
lat, lng = CITY_PRESETS[key]
|
|
128
|
+
success, message = self.set_coordinate(lat, lng, verbose=verbose)
|
|
129
|
+
|
|
130
|
+
if success and not verbose:
|
|
131
|
+
return True, f"Location set: {city_name.title()} ({lat}, {lng})"
|
|
132
|
+
return success, message
|
|
133
|
+
|
|
134
|
+
def run_gpx_scenario(self, scenario: str, verbose: bool = False) -> tuple[bool, str]:
|
|
135
|
+
"""
|
|
136
|
+
Run a named GPX location scenario built into the simulator.
|
|
137
|
+
|
|
138
|
+
Use `xcrun simctl location <udid> list` to see available scenarios.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
scenario: Scenario name (e.g. "FreewayDrive", "ApplePark").
|
|
142
|
+
verbose: Include extra context in the returned message.
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
(success, message) tuple.
|
|
146
|
+
"""
|
|
147
|
+
cmd = ["xcrun", "simctl", "location", self.udid, "run", scenario]
|
|
148
|
+
|
|
149
|
+
try:
|
|
150
|
+
result = subprocess.run(cmd, check=False, capture_output=True, text=True, timeout=15)
|
|
151
|
+
except subprocess.TimeoutExpired:
|
|
152
|
+
return False, "Error: location run timed out"
|
|
153
|
+
except Exception as e:
|
|
154
|
+
return False, f"Error: {e}"
|
|
155
|
+
|
|
156
|
+
if result.returncode != 0:
|
|
157
|
+
error = result.stderr.strip() or "unknown error"
|
|
158
|
+
return False, f"GPX scenario failed: {error}"
|
|
159
|
+
|
|
160
|
+
if verbose:
|
|
161
|
+
return True, f"GPX scenario running\n Scenario: {scenario}\n Device: {self.udid}"
|
|
162
|
+
return True, f"GPX scenario running: {scenario}"
|
|
163
|
+
|
|
164
|
+
def list_scenarios(self) -> tuple[bool, list[str], str]:
|
|
165
|
+
"""
|
|
166
|
+
List built-in location scenarios available on this simulator.
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
(success, scenarios, error) tuple where scenarios is a list of names
|
|
170
|
+
and error is an empty string on success or a description of the failure.
|
|
171
|
+
"""
|
|
172
|
+
cmd = ["xcrun", "simctl", "location", self.udid, "list"]
|
|
173
|
+
|
|
174
|
+
try:
|
|
175
|
+
result = subprocess.run(cmd, check=False, capture_output=True, text=True, timeout=15)
|
|
176
|
+
except subprocess.TimeoutExpired:
|
|
177
|
+
return False, [], "Error: list scenarios timed out"
|
|
178
|
+
except Exception as e:
|
|
179
|
+
return False, [], f"Error: {e}"
|
|
180
|
+
|
|
181
|
+
if result.returncode != 0:
|
|
182
|
+
error = result.stderr.strip() or "unknown error"
|
|
183
|
+
return False, [], f"list scenarios failed: {error}"
|
|
184
|
+
|
|
185
|
+
lines = [ln.strip() for ln in result.stdout.splitlines() if ln.strip()]
|
|
186
|
+
return True, lines, ""
|
|
187
|
+
|
|
188
|
+
def start_waypoints(
|
|
189
|
+
self,
|
|
190
|
+
waypoints: list[tuple[float, float]],
|
|
191
|
+
speed_mps: float = 20.0,
|
|
192
|
+
interval_seconds: float | None = None,
|
|
193
|
+
distance_meters: float | None = None,
|
|
194
|
+
verbose: bool = False,
|
|
195
|
+
) -> tuple[bool, str]:
|
|
196
|
+
"""
|
|
197
|
+
Animate the location along a series of waypoints.
|
|
198
|
+
|
|
199
|
+
Requires at least two waypoints.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
waypoints: List of (lat, lng) pairs.
|
|
203
|
+
speed_mps: Movement speed in metres per second (default 20 m/s).
|
|
204
|
+
interval_seconds: Location update interval in seconds; mutually exclusive with distance_meters.
|
|
205
|
+
distance_meters: Location update distance in metres; mutually exclusive with interval_seconds.
|
|
206
|
+
verbose: Include waypoint list in the returned message.
|
|
207
|
+
|
|
208
|
+
Returns:
|
|
209
|
+
(success, message) tuple.
|
|
210
|
+
"""
|
|
211
|
+
if len(waypoints) < 2:
|
|
212
|
+
return False, "At least two waypoints are required for --waypoints"
|
|
213
|
+
|
|
214
|
+
coord_args = [f"{lat},{lng}" for lat, lng in waypoints]
|
|
215
|
+
cmd = [
|
|
216
|
+
"xcrun",
|
|
217
|
+
"simctl",
|
|
218
|
+
"location",
|
|
219
|
+
self.udid,
|
|
220
|
+
"start",
|
|
221
|
+
f"--speed={speed_mps}",
|
|
222
|
+
]
|
|
223
|
+
if interval_seconds is not None:
|
|
224
|
+
cmd.append(f"--interval={interval_seconds}")
|
|
225
|
+
if distance_meters is not None:
|
|
226
|
+
cmd.append(f"--distance={distance_meters}")
|
|
227
|
+
cmd.extend(coord_args)
|
|
228
|
+
|
|
229
|
+
try:
|
|
230
|
+
result = subprocess.run(cmd, check=False, capture_output=True, text=True, timeout=15)
|
|
231
|
+
except subprocess.TimeoutExpired:
|
|
232
|
+
return False, "Error: location start timed out"
|
|
233
|
+
except Exception as e:
|
|
234
|
+
return False, f"Error: {e}"
|
|
235
|
+
|
|
236
|
+
if result.returncode != 0:
|
|
237
|
+
error = result.stderr.strip() or "unknown error"
|
|
238
|
+
return False, f"Waypoint route failed: {error}"
|
|
239
|
+
|
|
240
|
+
if verbose:
|
|
241
|
+
pts = "\n".join(f" {i + 1}. {lat}, {lng}" for i, (lat, lng) in enumerate(waypoints))
|
|
242
|
+
return True, (
|
|
243
|
+
f"Waypoint route started\n"
|
|
244
|
+
f" Speed: {speed_mps} m/s\n"
|
|
245
|
+
f" Waypoints:\n{pts}\n"
|
|
246
|
+
f" Device: {self.udid}"
|
|
247
|
+
)
|
|
248
|
+
return True, f"Waypoint route started: {len(waypoints)} points at {speed_mps} m/s"
|
|
249
|
+
|
|
250
|
+
def clear(self, verbose: bool = False) -> tuple[bool, str]:
|
|
251
|
+
"""
|
|
252
|
+
Clear any active location simulation and restore real GPS.
|
|
253
|
+
|
|
254
|
+
Args:
|
|
255
|
+
verbose: Include extra context in the returned message.
|
|
256
|
+
|
|
257
|
+
Returns:
|
|
258
|
+
(success, message) tuple.
|
|
259
|
+
"""
|
|
260
|
+
cmd = ["xcrun", "simctl", "location", self.udid, "clear"]
|
|
261
|
+
|
|
262
|
+
try:
|
|
263
|
+
result = subprocess.run(cmd, check=False, capture_output=True, text=True, timeout=15)
|
|
264
|
+
except subprocess.TimeoutExpired:
|
|
265
|
+
return False, "Error: location clear timed out"
|
|
266
|
+
except Exception as e:
|
|
267
|
+
return False, f"Error: {e}"
|
|
268
|
+
|
|
269
|
+
if result.returncode != 0:
|
|
270
|
+
error = result.stderr.strip() or "unknown error"
|
|
271
|
+
return False, f"location clear failed: {error}"
|
|
272
|
+
|
|
273
|
+
if verbose:
|
|
274
|
+
return True, f"Location cleared\n Device: {self.udid}"
|
|
275
|
+
return True, "Location cleared"
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
# === CLI ===
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def _parse_waypoints(raw: str) -> list[tuple[float, float]]:
|
|
282
|
+
"""
|
|
283
|
+
Parse a whitespace-separated string of 'lat,lng' pairs.
|
|
284
|
+
|
|
285
|
+
Args:
|
|
286
|
+
raw: e.g. "53.34,-6.26 51.50,-0.12"
|
|
287
|
+
|
|
288
|
+
Returns:
|
|
289
|
+
List of (lat, lng) float tuples.
|
|
290
|
+
|
|
291
|
+
Raises:
|
|
292
|
+
ValueError: If any pair is malformed.
|
|
293
|
+
"""
|
|
294
|
+
pairs = []
|
|
295
|
+
for token in raw.split():
|
|
296
|
+
parts = token.split(",")
|
|
297
|
+
if len(parts) != 2:
|
|
298
|
+
raise ValueError(f"Expected 'lat,lng', got: {token!r}")
|
|
299
|
+
try:
|
|
300
|
+
pairs.append((float(parts[0]), float(parts[1])))
|
|
301
|
+
except ValueError:
|
|
302
|
+
raise ValueError(f"Non-numeric coordinate in: {token!r}") from None
|
|
303
|
+
return pairs
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def main() -> None:
|
|
307
|
+
"""CLI entry point."""
|
|
308
|
+
parser = argparse.ArgumentParser(
|
|
309
|
+
description="Simulate GPS location on an iOS simulator",
|
|
310
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
311
|
+
epilog="""
|
|
312
|
+
Examples:
|
|
313
|
+
python scripts/location.py --lat 53.3498 --lng -6.2603
|
|
314
|
+
python scripts/location.py --city Dublin
|
|
315
|
+
python scripts/location.py --gpx FreewayDrive
|
|
316
|
+
python scripts/location.py --waypoints "37.33,-122.03 37.78,-122.41" --speed 10
|
|
317
|
+
python scripts/location.py --clear
|
|
318
|
+
python scripts/location.py --list-scenarios
|
|
319
|
+
""",
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
# Device selection
|
|
323
|
+
parser.add_argument("--udid", help="Target device UDID (auto-detects booted simulator)")
|
|
324
|
+
|
|
325
|
+
# Location actions (mutually exclusive)
|
|
326
|
+
action_group = parser.add_mutually_exclusive_group(required=True)
|
|
327
|
+
action_group.add_argument("--lat", type=float, help="Latitude (requires --lng)")
|
|
328
|
+
action_group.add_argument("--city", metavar="NAME", help="Named city preset")
|
|
329
|
+
action_group.add_argument("--gpx", metavar="SCENARIO", help="Run a built-in GPX scenario name")
|
|
330
|
+
action_group.add_argument(
|
|
331
|
+
"--waypoints",
|
|
332
|
+
metavar="'lat,lng ...'",
|
|
333
|
+
help="Whitespace-separated lat,lng pairs for animated route",
|
|
334
|
+
)
|
|
335
|
+
action_group.add_argument("--clear", action="store_true", help="Clear location override")
|
|
336
|
+
action_group.add_argument(
|
|
337
|
+
"--list-scenarios", action="store_true", help="List available built-in GPX scenarios"
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
# Coordinate companion
|
|
341
|
+
parser.add_argument("--lng", type=float, help="Longitude (used with --lat)")
|
|
342
|
+
|
|
343
|
+
# Waypoint options
|
|
344
|
+
parser.add_argument(
|
|
345
|
+
"--speed",
|
|
346
|
+
type=float,
|
|
347
|
+
default=20.0,
|
|
348
|
+
metavar="M_PER_SEC",
|
|
349
|
+
help="Movement speed in m/s for --waypoints (default: 20)",
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
waypoint_pace = parser.add_mutually_exclusive_group()
|
|
353
|
+
waypoint_pace.add_argument(
|
|
354
|
+
"--interval",
|
|
355
|
+
type=float,
|
|
356
|
+
metavar="SECONDS",
|
|
357
|
+
help="Location update interval in seconds for --waypoints (mutually exclusive with --distance)",
|
|
358
|
+
)
|
|
359
|
+
waypoint_pace.add_argument(
|
|
360
|
+
"--distance",
|
|
361
|
+
type=float,
|
|
362
|
+
metavar="METERS",
|
|
363
|
+
help="Location update distance in metres for --waypoints (mutually exclusive with --interval)",
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
# Output flags
|
|
367
|
+
parser.add_argument("--json", action="store_true", help="Output as JSON")
|
|
368
|
+
parser.add_argument("--verbose", action="store_true", help="Verbose output")
|
|
369
|
+
|
|
370
|
+
args = parser.parse_args()
|
|
371
|
+
|
|
372
|
+
# Validate --lat and --lng must be provided together
|
|
373
|
+
if (args.lat is None) != (args.lng is None):
|
|
374
|
+
parser.error("--lat and --lng must be provided together")
|
|
375
|
+
|
|
376
|
+
# Resolve device UDID
|
|
377
|
+
try:
|
|
378
|
+
udid = resolve_udid(args.udid)
|
|
379
|
+
except RuntimeError as e:
|
|
380
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
381
|
+
sys.exit(1)
|
|
382
|
+
|
|
383
|
+
manager = LocationManager(udid=udid)
|
|
384
|
+
|
|
385
|
+
# === Dispatch ===
|
|
386
|
+
|
|
387
|
+
if args.lat is not None:
|
|
388
|
+
success, message = manager.set_coordinate(args.lat, args.lng, verbose=args.verbose)
|
|
389
|
+
action = "set_coordinate"
|
|
390
|
+
extra: dict = {"lat": args.lat, "lng": args.lng}
|
|
391
|
+
|
|
392
|
+
elif args.city:
|
|
393
|
+
success, message = manager.set_city(args.city, verbose=args.verbose)
|
|
394
|
+
action = "set_city"
|
|
395
|
+
key = args.city.lower().replace(" ", "")
|
|
396
|
+
extra = {
|
|
397
|
+
"city": args.city,
|
|
398
|
+
"lat": CITY_PRESETS[key][0] if key in CITY_PRESETS else None,
|
|
399
|
+
"lng": CITY_PRESETS[key][1] if key in CITY_PRESETS else None,
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
elif args.gpx:
|
|
403
|
+
success, message = manager.run_gpx_scenario(args.gpx, verbose=args.verbose)
|
|
404
|
+
action = "run_gpx"
|
|
405
|
+
extra = {"scenario": args.gpx}
|
|
406
|
+
|
|
407
|
+
elif args.waypoints:
|
|
408
|
+
try:
|
|
409
|
+
waypoints = _parse_waypoints(args.waypoints)
|
|
410
|
+
except ValueError as e:
|
|
411
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
412
|
+
sys.exit(1)
|
|
413
|
+
success, message = manager.start_waypoints(
|
|
414
|
+
waypoints,
|
|
415
|
+
speed_mps=args.speed,
|
|
416
|
+
interval_seconds=args.interval,
|
|
417
|
+
distance_meters=args.distance,
|
|
418
|
+
verbose=args.verbose,
|
|
419
|
+
)
|
|
420
|
+
action = "start_waypoints"
|
|
421
|
+
extra = {
|
|
422
|
+
"waypoints": [{"lat": lat, "lng": lng} for lat, lng in waypoints],
|
|
423
|
+
"speed_mps": args.speed,
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
elif args.list_scenarios:
|
|
427
|
+
ok, scenarios, list_error = manager.list_scenarios()
|
|
428
|
+
if args.json:
|
|
429
|
+
payload: dict = {"action": "list_scenarios", "udid": udid, "scenarios": scenarios}
|
|
430
|
+
if not ok:
|
|
431
|
+
payload["error"] = list_error
|
|
432
|
+
print(json.dumps(payload))
|
|
433
|
+
elif ok and scenarios:
|
|
434
|
+
print("\n".join(f" {s}" for s in scenarios))
|
|
435
|
+
elif ok:
|
|
436
|
+
print("No scenarios available")
|
|
437
|
+
else:
|
|
438
|
+
print(list_error, file=sys.stderr)
|
|
439
|
+
sys.exit(0 if ok else 1)
|
|
440
|
+
|
|
441
|
+
else: # --clear
|
|
442
|
+
success, message = manager.clear(verbose=args.verbose)
|
|
443
|
+
action = "clear"
|
|
444
|
+
extra = {}
|
|
445
|
+
|
|
446
|
+
# === Output ===
|
|
447
|
+
|
|
448
|
+
if args.json:
|
|
449
|
+
print(
|
|
450
|
+
json.dumps(
|
|
451
|
+
{
|
|
452
|
+
"action": action,
|
|
453
|
+
"udid": udid,
|
|
454
|
+
"success": success,
|
|
455
|
+
"message": message,
|
|
456
|
+
**extra,
|
|
457
|
+
}
|
|
458
|
+
)
|
|
459
|
+
)
|
|
460
|
+
else:
|
|
461
|
+
print(message)
|
|
462
|
+
|
|
463
|
+
sys.exit(0 if success else 1)
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
if __name__ == "__main__":
|
|
467
|
+
main()
|