@leejungkiin/awkit 1.7.1 → 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 +35 -2
- 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/video-edit/SKILL.md +36 -0
- package/skills/video-edit/scripts/video_edit.py +324 -0
- package/templates/project-identity/android.json +2 -2
- package/templates/project-identity/backend-nestjs.json +2 -2
- package/templates/project-identity/expo.json +2 -2
- package/templates/project-identity/ios.json +2 -2
- package/templates/project-identity/web-nextjs.json +2 -2
- 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,400 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
App State Capture for iOS Simulator
|
|
4
|
+
|
|
5
|
+
Captures complete app state including screenshot, accessibility tree, and logs.
|
|
6
|
+
Optimized for minimal token output.
|
|
7
|
+
|
|
8
|
+
Usage: python scripts/app_state_capture.py [options]
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import argparse
|
|
12
|
+
import json
|
|
13
|
+
import subprocess
|
|
14
|
+
import sys
|
|
15
|
+
from datetime import datetime
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
|
|
18
|
+
from common import (
|
|
19
|
+
capture_screenshot,
|
|
20
|
+
count_elements,
|
|
21
|
+
get_accessibility_tree,
|
|
22
|
+
resolve_udid,
|
|
23
|
+
)
|
|
24
|
+
from common.env_config import env_int
|
|
25
|
+
|
|
26
|
+
STATE_SUBPROCESS_TIMEOUT = env_int("IOS_SIM_STATE_SUBPROCESS_TIMEOUT", 15)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class AppStateCapture:
|
|
30
|
+
"""Captures comprehensive app state for debugging."""
|
|
31
|
+
|
|
32
|
+
def __init__(
|
|
33
|
+
self,
|
|
34
|
+
app_bundle_id: str | None = None,
|
|
35
|
+
udid: str | None = None,
|
|
36
|
+
inline: bool = False,
|
|
37
|
+
screenshot_size: str = "half",
|
|
38
|
+
):
|
|
39
|
+
"""
|
|
40
|
+
Initialize state capture.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
app_bundle_id: Optional app bundle ID for log filtering
|
|
44
|
+
udid: Optional device UDID (uses booted if not specified)
|
|
45
|
+
inline: If True, return screenshots as base64 (for vision-based automation)
|
|
46
|
+
screenshot_size: 'full', 'half', 'quarter', 'thumb' (default: 'half')
|
|
47
|
+
"""
|
|
48
|
+
self.app_bundle_id = app_bundle_id
|
|
49
|
+
self.udid = udid
|
|
50
|
+
self.inline = inline
|
|
51
|
+
self.screenshot_size = screenshot_size
|
|
52
|
+
|
|
53
|
+
def capture_screenshot(self, output_path: Path) -> bool:
|
|
54
|
+
"""Capture screenshot of current screen."""
|
|
55
|
+
cmd = ["xcrun", "simctl", "io"]
|
|
56
|
+
|
|
57
|
+
if self.udid:
|
|
58
|
+
cmd.append(self.udid)
|
|
59
|
+
else:
|
|
60
|
+
cmd.append("booted")
|
|
61
|
+
|
|
62
|
+
cmd.extend(["screenshot", str(output_path)])
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
subprocess.run(cmd, capture_output=True, check=True)
|
|
66
|
+
return True
|
|
67
|
+
except subprocess.CalledProcessError:
|
|
68
|
+
return False
|
|
69
|
+
|
|
70
|
+
def capture_accessibility_tree(self, output_path: Path) -> dict:
|
|
71
|
+
"""Capture accessibility tree using shared utility."""
|
|
72
|
+
try:
|
|
73
|
+
# Use shared utility to fetch tree
|
|
74
|
+
tree = get_accessibility_tree(self.udid, nested=True)
|
|
75
|
+
|
|
76
|
+
# Save tree
|
|
77
|
+
with open(output_path, "w") as f:
|
|
78
|
+
json.dump(tree, f, indent=2)
|
|
79
|
+
|
|
80
|
+
# Return summary using shared utility
|
|
81
|
+
return {"captured": True, "element_count": count_elements(tree)}
|
|
82
|
+
except Exception as e:
|
|
83
|
+
return {"captured": False, "error": str(e)}
|
|
84
|
+
|
|
85
|
+
def capture_logs(self, output_path: Path, line_limit: int = 100) -> dict:
|
|
86
|
+
"""Capture recent app logs."""
|
|
87
|
+
if not self.app_bundle_id:
|
|
88
|
+
# Can't capture logs without app ID
|
|
89
|
+
return {"captured": False, "reason": "No app bundle ID specified"}
|
|
90
|
+
|
|
91
|
+
# Get app name from bundle ID (simplified)
|
|
92
|
+
app_name = self.app_bundle_id.split(".")[-1]
|
|
93
|
+
|
|
94
|
+
cmd = ["xcrun", "simctl", "spawn"]
|
|
95
|
+
|
|
96
|
+
if self.udid:
|
|
97
|
+
cmd.append(self.udid)
|
|
98
|
+
else:
|
|
99
|
+
cmd.append("booted")
|
|
100
|
+
|
|
101
|
+
cmd.extend(
|
|
102
|
+
[
|
|
103
|
+
"log",
|
|
104
|
+
"show",
|
|
105
|
+
"--predicate",
|
|
106
|
+
f'process == "{app_name}"',
|
|
107
|
+
"--last",
|
|
108
|
+
"1m", # Last 1 minute
|
|
109
|
+
"--style",
|
|
110
|
+
"compact",
|
|
111
|
+
]
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
try:
|
|
115
|
+
result = subprocess.run(
|
|
116
|
+
cmd,
|
|
117
|
+
check=False,
|
|
118
|
+
capture_output=True,
|
|
119
|
+
text=True,
|
|
120
|
+
timeout=STATE_SUBPROCESS_TIMEOUT,
|
|
121
|
+
)
|
|
122
|
+
logs = result.stdout
|
|
123
|
+
|
|
124
|
+
# Limit lines for token efficiency
|
|
125
|
+
lines = logs.split("\n")
|
|
126
|
+
if len(lines) > line_limit:
|
|
127
|
+
lines = lines[-line_limit:]
|
|
128
|
+
|
|
129
|
+
# Save logs
|
|
130
|
+
with open(output_path, "w") as f:
|
|
131
|
+
f.write("\n".join(lines))
|
|
132
|
+
|
|
133
|
+
# Analyze for issues
|
|
134
|
+
warning_count = sum(1 for line in lines if "warning" in line.lower())
|
|
135
|
+
error_count = sum(1 for line in lines if "error" in line.lower())
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
"captured": True,
|
|
139
|
+
"lines": len(lines),
|
|
140
|
+
"warnings": warning_count,
|
|
141
|
+
"errors": error_count,
|
|
142
|
+
}
|
|
143
|
+
except (subprocess.CalledProcessError, subprocess.TimeoutExpired) as e:
|
|
144
|
+
return {"captured": False, "error": str(e)}
|
|
145
|
+
|
|
146
|
+
def capture_device_info(self) -> dict:
|
|
147
|
+
"""Get device information."""
|
|
148
|
+
cmd = ["xcrun", "simctl", "list", "devices", "booted"]
|
|
149
|
+
|
|
150
|
+
if self.udid:
|
|
151
|
+
# Specific device info
|
|
152
|
+
cmd = ["xcrun", "simctl", "list", "devices"]
|
|
153
|
+
|
|
154
|
+
try:
|
|
155
|
+
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
|
156
|
+
|
|
157
|
+
# Parse output for device info (simplified)
|
|
158
|
+
lines = result.stdout.split("\n")
|
|
159
|
+
device_info = {}
|
|
160
|
+
|
|
161
|
+
for line in lines:
|
|
162
|
+
if "iPhone" in line or "iPad" in line:
|
|
163
|
+
# Extract device name and state
|
|
164
|
+
parts = line.strip().split("(")
|
|
165
|
+
if parts:
|
|
166
|
+
device_info["name"] = parts[0].strip()
|
|
167
|
+
if len(parts) > 2:
|
|
168
|
+
device_info["udid"] = parts[1].replace(")", "").strip()
|
|
169
|
+
device_info["state"] = parts[2].replace(")", "").strip()
|
|
170
|
+
break
|
|
171
|
+
|
|
172
|
+
return device_info
|
|
173
|
+
except subprocess.CalledProcessError:
|
|
174
|
+
return {}
|
|
175
|
+
|
|
176
|
+
def capture_all(
|
|
177
|
+
self, output_dir: str, log_lines: int = 100, app_name: str | None = None
|
|
178
|
+
) -> dict:
|
|
179
|
+
"""
|
|
180
|
+
Capture complete app state.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
output_dir: Directory to save artifacts
|
|
184
|
+
log_lines: Number of log lines to capture
|
|
185
|
+
app_name: App name for semantic naming (for inline mode)
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
Summary of captured state
|
|
189
|
+
"""
|
|
190
|
+
# Create output directory (only if not in inline mode)
|
|
191
|
+
output_path = Path(output_dir)
|
|
192
|
+
timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
|
|
193
|
+
if not self.inline:
|
|
194
|
+
capture_dir = output_path / f"app-state-{timestamp}"
|
|
195
|
+
capture_dir.mkdir(parents=True, exist_ok=True)
|
|
196
|
+
else:
|
|
197
|
+
capture_dir = None
|
|
198
|
+
|
|
199
|
+
summary = {
|
|
200
|
+
"timestamp": datetime.now().isoformat(),
|
|
201
|
+
"screenshot_mode": "inline" if self.inline else "file",
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if capture_dir:
|
|
205
|
+
summary["output_dir"] = str(capture_dir)
|
|
206
|
+
|
|
207
|
+
# Capture screenshot using new unified utility
|
|
208
|
+
screenshot_result = capture_screenshot(
|
|
209
|
+
self.udid,
|
|
210
|
+
size=self.screenshot_size,
|
|
211
|
+
inline=self.inline,
|
|
212
|
+
app_name=app_name,
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
if self.inline:
|
|
216
|
+
# Inline mode: store base64
|
|
217
|
+
summary["screenshot"] = {
|
|
218
|
+
"mode": "inline",
|
|
219
|
+
"base64": screenshot_result["base64_data"],
|
|
220
|
+
"width": screenshot_result["width"],
|
|
221
|
+
"height": screenshot_result["height"],
|
|
222
|
+
"size_preset": self.screenshot_size,
|
|
223
|
+
}
|
|
224
|
+
else:
|
|
225
|
+
# File mode: save to disk
|
|
226
|
+
screenshot_path = capture_dir / "screenshot.png"
|
|
227
|
+
# Move temp file to target location
|
|
228
|
+
import shutil
|
|
229
|
+
|
|
230
|
+
shutil.move(screenshot_result["file_path"], screenshot_path)
|
|
231
|
+
summary["screenshot"] = {
|
|
232
|
+
"mode": "file",
|
|
233
|
+
"file": "screenshot.png",
|
|
234
|
+
"size_bytes": screenshot_result["size_bytes"],
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
# Capture accessibility tree
|
|
238
|
+
if not self.inline or capture_dir:
|
|
239
|
+
accessibility_path = (capture_dir or output_path) / "accessibility-tree.json"
|
|
240
|
+
else:
|
|
241
|
+
accessibility_path = None
|
|
242
|
+
|
|
243
|
+
if accessibility_path:
|
|
244
|
+
tree_info = self.capture_accessibility_tree(accessibility_path)
|
|
245
|
+
summary["accessibility"] = tree_info
|
|
246
|
+
|
|
247
|
+
# Capture logs (if app ID provided)
|
|
248
|
+
if self.app_bundle_id:
|
|
249
|
+
if not self.inline or capture_dir:
|
|
250
|
+
logs_path = (capture_dir or output_path) / "app-logs.txt"
|
|
251
|
+
else:
|
|
252
|
+
logs_path = None
|
|
253
|
+
|
|
254
|
+
if logs_path:
|
|
255
|
+
log_info = self.capture_logs(logs_path, log_lines)
|
|
256
|
+
summary["logs"] = log_info
|
|
257
|
+
|
|
258
|
+
# Get device info
|
|
259
|
+
device_info = self.capture_device_info()
|
|
260
|
+
if device_info:
|
|
261
|
+
summary["device"] = device_info
|
|
262
|
+
# Save device info (file mode only)
|
|
263
|
+
if capture_dir:
|
|
264
|
+
with open(capture_dir / "device-info.json", "w") as f:
|
|
265
|
+
json.dump(device_info, f, indent=2)
|
|
266
|
+
|
|
267
|
+
# Save summary (file mode only)
|
|
268
|
+
if capture_dir:
|
|
269
|
+
with open(capture_dir / "summary.json", "w") as f:
|
|
270
|
+
json.dump(summary, f, indent=2)
|
|
271
|
+
|
|
272
|
+
# Create markdown summary
|
|
273
|
+
self._create_summary_md(capture_dir, summary)
|
|
274
|
+
|
|
275
|
+
return summary
|
|
276
|
+
|
|
277
|
+
def _create_summary_md(self, capture_dir: Path, summary: dict) -> None:
|
|
278
|
+
"""Create markdown summary file."""
|
|
279
|
+
md_path = capture_dir / "summary.md"
|
|
280
|
+
|
|
281
|
+
with open(md_path, "w") as f:
|
|
282
|
+
f.write("# App State Capture\n\n")
|
|
283
|
+
f.write(f"**Timestamp:** {summary['timestamp']}\n\n")
|
|
284
|
+
|
|
285
|
+
if "device" in summary:
|
|
286
|
+
f.write("## Device\n")
|
|
287
|
+
device = summary["device"]
|
|
288
|
+
f.write(f"- Name: {device.get('name', 'Unknown')}\n")
|
|
289
|
+
f.write(f"- UDID: {device.get('udid', 'N/A')}\n")
|
|
290
|
+
f.write(f"- State: {device.get('state', 'Unknown')}\n\n")
|
|
291
|
+
|
|
292
|
+
f.write("## Screenshot\n")
|
|
293
|
+
f.write("\n\n")
|
|
294
|
+
|
|
295
|
+
if "accessibility" in summary:
|
|
296
|
+
acc = summary["accessibility"]
|
|
297
|
+
f.write("## Accessibility\n")
|
|
298
|
+
if acc.get("captured"):
|
|
299
|
+
f.write(f"- Elements: {acc.get('element_count', 0)}\n")
|
|
300
|
+
else:
|
|
301
|
+
f.write(f"- Error: {acc.get('error', 'Unknown')}\n")
|
|
302
|
+
f.write("\n")
|
|
303
|
+
|
|
304
|
+
if "logs" in summary:
|
|
305
|
+
logs = summary["logs"]
|
|
306
|
+
f.write("## Logs\n")
|
|
307
|
+
if logs.get("captured"):
|
|
308
|
+
f.write(f"- Lines: {logs.get('lines', 0)}\n")
|
|
309
|
+
f.write(f"- Warnings: {logs.get('warnings', 0)}\n")
|
|
310
|
+
f.write(f"- Errors: {logs.get('errors', 0)}\n")
|
|
311
|
+
else:
|
|
312
|
+
f.write(f"- {logs.get('reason', logs.get('error', 'Not captured'))}\n")
|
|
313
|
+
f.write("\n")
|
|
314
|
+
|
|
315
|
+
f.write("## Files\n")
|
|
316
|
+
f.write("- `screenshot.png` - Current screen\n")
|
|
317
|
+
f.write("- `accessibility-tree.json` - Full UI hierarchy\n")
|
|
318
|
+
if self.app_bundle_id:
|
|
319
|
+
f.write("- `app-logs.txt` - Recent app logs\n")
|
|
320
|
+
f.write("- `device-info.json` - Device details\n")
|
|
321
|
+
f.write("- `summary.json` - Complete capture metadata\n")
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def main():
|
|
325
|
+
"""Main entry point."""
|
|
326
|
+
parser = argparse.ArgumentParser(description="Capture complete app state for debugging")
|
|
327
|
+
parser.add_argument(
|
|
328
|
+
"--app-bundle-id", help="App bundle ID for log filtering (e.g., com.example.app)"
|
|
329
|
+
)
|
|
330
|
+
parser.add_argument(
|
|
331
|
+
"--output", default=".", help="Output directory (default: current directory)"
|
|
332
|
+
)
|
|
333
|
+
parser.add_argument(
|
|
334
|
+
"--log-lines", type=int, default=100, help="Number of log lines to capture (default: 100)"
|
|
335
|
+
)
|
|
336
|
+
parser.add_argument(
|
|
337
|
+
"--udid",
|
|
338
|
+
help="Device UDID (auto-detects booted simulator if not provided)",
|
|
339
|
+
)
|
|
340
|
+
parser.add_argument(
|
|
341
|
+
"--inline",
|
|
342
|
+
action="store_true",
|
|
343
|
+
help="Return screenshots as base64 (inline mode for vision-based automation)",
|
|
344
|
+
)
|
|
345
|
+
parser.add_argument(
|
|
346
|
+
"--size",
|
|
347
|
+
choices=["full", "half", "quarter", "thumb"],
|
|
348
|
+
default="half",
|
|
349
|
+
help="Screenshot size for token optimization (default: half)",
|
|
350
|
+
)
|
|
351
|
+
parser.add_argument("--app-name", help="App name for semantic screenshot naming")
|
|
352
|
+
|
|
353
|
+
args = parser.parse_args()
|
|
354
|
+
|
|
355
|
+
# Resolve UDID with auto-detection
|
|
356
|
+
try:
|
|
357
|
+
udid = resolve_udid(args.udid)
|
|
358
|
+
except RuntimeError as e:
|
|
359
|
+
print(f"Error: {e}")
|
|
360
|
+
sys.exit(1)
|
|
361
|
+
|
|
362
|
+
# Create capturer
|
|
363
|
+
capturer = AppStateCapture(
|
|
364
|
+
app_bundle_id=args.app_bundle_id,
|
|
365
|
+
udid=udid,
|
|
366
|
+
inline=args.inline,
|
|
367
|
+
screenshot_size=args.size,
|
|
368
|
+
)
|
|
369
|
+
|
|
370
|
+
# Capture state
|
|
371
|
+
try:
|
|
372
|
+
summary = capturer.capture_all(
|
|
373
|
+
output_dir=args.output, log_lines=args.log_lines, app_name=args.app_name
|
|
374
|
+
)
|
|
375
|
+
|
|
376
|
+
# Token-efficient output
|
|
377
|
+
if "output_dir" in summary:
|
|
378
|
+
print(f"State captured: {summary['output_dir']}/")
|
|
379
|
+
else:
|
|
380
|
+
# Inline mode
|
|
381
|
+
print(
|
|
382
|
+
f"State captured (inline mode): {summary['screenshot']['width']}x{summary['screenshot']['height']}"
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
# Report any issues found
|
|
386
|
+
if "logs" in summary and summary["logs"].get("captured"):
|
|
387
|
+
logs = summary["logs"]
|
|
388
|
+
if logs["errors"] > 0 or logs["warnings"] > 0:
|
|
389
|
+
print(f"Issues found: {logs['errors']} errors, {logs['warnings']} warnings")
|
|
390
|
+
|
|
391
|
+
if "accessibility" in summary and summary["accessibility"].get("captured"):
|
|
392
|
+
print(f"Elements: {summary['accessibility']['element_count']}")
|
|
393
|
+
|
|
394
|
+
except Exception as e:
|
|
395
|
+
print(f"Error: {e}")
|
|
396
|
+
sys.exit(1)
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
if __name__ == "__main__":
|
|
400
|
+
main()
|