@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,338 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Screenshot utilities with dual-mode support.
|
|
4
|
+
|
|
5
|
+
Provides unified screenshot handling with:
|
|
6
|
+
- File-based mode: Persistent artifacts for test documentation
|
|
7
|
+
- Inline base64 mode: Vision-based automation for agent analysis
|
|
8
|
+
- Size presets: Token optimization (full/half/quarter/thumb)
|
|
9
|
+
- Semantic naming: {appName}_{screenName}_{state}_{timestamp}.png
|
|
10
|
+
|
|
11
|
+
Supports resize operations via PIL (optional dependency).
|
|
12
|
+
|
|
13
|
+
Used by:
|
|
14
|
+
- test_recorder.py - Step-based screenshot recording
|
|
15
|
+
- app_state_capture.py - State snapshot captures
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import base64
|
|
19
|
+
import os
|
|
20
|
+
import subprocess
|
|
21
|
+
import sys
|
|
22
|
+
from datetime import datetime
|
|
23
|
+
from pathlib import Path
|
|
24
|
+
from typing import Any
|
|
25
|
+
|
|
26
|
+
# Try to import PIL for resizing, but make it optional
|
|
27
|
+
try:
|
|
28
|
+
from PIL import Image
|
|
29
|
+
|
|
30
|
+
HAS_PIL = True
|
|
31
|
+
except ImportError:
|
|
32
|
+
HAS_PIL = False
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def generate_screenshot_name(
|
|
36
|
+
app_name: str | None = None,
|
|
37
|
+
screen_name: str | None = None,
|
|
38
|
+
state: str | None = None,
|
|
39
|
+
timestamp: str | None = None,
|
|
40
|
+
extension: str = "png",
|
|
41
|
+
) -> str:
|
|
42
|
+
"""Generate semantic screenshot filename.
|
|
43
|
+
|
|
44
|
+
Format: {appName}_{screenName}_{state}_{timestamp}.{ext}
|
|
45
|
+
Falls back to: screenshot_{timestamp}.{ext}
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
app_name: Application name (e.g., 'MyApp')
|
|
49
|
+
screen_name: Screen name (e.g., 'Login')
|
|
50
|
+
state: State description (e.g., 'Empty', 'Filled', 'Error')
|
|
51
|
+
timestamp: ISO timestamp (uses current time if None)
|
|
52
|
+
extension: File extension (default: 'png')
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
Semantic filename ready for safe file creation
|
|
56
|
+
|
|
57
|
+
Example:
|
|
58
|
+
name = generate_screenshot_name('MyApp', 'Login', 'Empty')
|
|
59
|
+
# Returns: 'MyApp_Login_Empty_20251028-143052.png'
|
|
60
|
+
|
|
61
|
+
name = generate_screenshot_name()
|
|
62
|
+
# Returns: 'screenshot_20251028-143052.png'
|
|
63
|
+
"""
|
|
64
|
+
if timestamp is None:
|
|
65
|
+
timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
|
|
66
|
+
|
|
67
|
+
# Build semantic name
|
|
68
|
+
if app_name or screen_name or state:
|
|
69
|
+
parts = [app_name, screen_name, state]
|
|
70
|
+
parts = [p for p in parts if p] # Filter None/empty
|
|
71
|
+
name = "_".join(parts) + f"_{timestamp}"
|
|
72
|
+
else:
|
|
73
|
+
name = f"screenshot_{timestamp}"
|
|
74
|
+
|
|
75
|
+
return f"{name}.{extension}"
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def get_size_preset(size: str = "half") -> tuple[float, float]:
|
|
79
|
+
"""Get scale factors for size preset.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
size: 'full', 'half', 'quarter', 'thumb'
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
Tuple of (scale_x, scale_y) for resizing
|
|
86
|
+
|
|
87
|
+
Example:
|
|
88
|
+
scale_x, scale_y = get_size_preset('half')
|
|
89
|
+
# Returns: (0.5, 0.5)
|
|
90
|
+
"""
|
|
91
|
+
presets = {
|
|
92
|
+
"full": (1.0, 1.0),
|
|
93
|
+
"half": (0.5, 0.5),
|
|
94
|
+
"quarter": (0.25, 0.25),
|
|
95
|
+
"thumb": (0.1, 0.1),
|
|
96
|
+
}
|
|
97
|
+
return presets.get(size, (0.5, 0.5))
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def resize_screenshot(
|
|
101
|
+
input_path: str,
|
|
102
|
+
output_path: str | None = None,
|
|
103
|
+
size: str = "half",
|
|
104
|
+
quality: int = 85,
|
|
105
|
+
) -> tuple[str, int, int]:
|
|
106
|
+
"""Resize screenshot for token optimization.
|
|
107
|
+
|
|
108
|
+
Requires PIL (Pillow). Falls back gracefully without it.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
input_path: Path to original screenshot
|
|
112
|
+
output_path: Output path (uses input_path if None)
|
|
113
|
+
size: 'full', 'half', 'quarter', 'thumb'
|
|
114
|
+
quality: JPEG quality (1-100, default: 85)
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
Tuple of (output_path, width, height) of resized image
|
|
118
|
+
|
|
119
|
+
Raises:
|
|
120
|
+
FileNotFoundError: If input file doesn't exist
|
|
121
|
+
ValueError: If PIL not installed and size != 'full'
|
|
122
|
+
|
|
123
|
+
Example:
|
|
124
|
+
output, w, h = resize_screenshot(
|
|
125
|
+
'screenshot.png',
|
|
126
|
+
'screenshot_half.png',
|
|
127
|
+
'half'
|
|
128
|
+
)
|
|
129
|
+
print(f"Resized to {w}x{h}")
|
|
130
|
+
"""
|
|
131
|
+
input_file = Path(input_path)
|
|
132
|
+
if not input_file.exists():
|
|
133
|
+
raise FileNotFoundError(f"Screenshot not found: {input_path}")
|
|
134
|
+
|
|
135
|
+
# If full size, just copy
|
|
136
|
+
if size == "full":
|
|
137
|
+
if output_path:
|
|
138
|
+
import shutil
|
|
139
|
+
|
|
140
|
+
shutil.copy(input_path, output_path)
|
|
141
|
+
output_file = Path(output_path)
|
|
142
|
+
else:
|
|
143
|
+
output_file = input_file
|
|
144
|
+
|
|
145
|
+
# Get original dimensions
|
|
146
|
+
if HAS_PIL:
|
|
147
|
+
img = Image.open(str(output_file))
|
|
148
|
+
return (str(output_file), img.width, img.height)
|
|
149
|
+
return (str(output_file), 0, 0) # Dimensions unknown without PIL
|
|
150
|
+
|
|
151
|
+
# Need PIL to resize
|
|
152
|
+
if not HAS_PIL:
|
|
153
|
+
raise ValueError(
|
|
154
|
+
f"Size preset '{size}' requires PIL (Pillow). " "Install with: pip3 install pillow"
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
# Open original image
|
|
158
|
+
img = Image.open(str(input_file))
|
|
159
|
+
orig_w, orig_h = img.size
|
|
160
|
+
|
|
161
|
+
# Calculate new size
|
|
162
|
+
scale_x, scale_y = get_size_preset(size)
|
|
163
|
+
new_w = int(orig_w * scale_x)
|
|
164
|
+
new_h = int(orig_h * scale_y)
|
|
165
|
+
|
|
166
|
+
# Resize with high-quality resampling
|
|
167
|
+
resized = img.resize((new_w, new_h), Image.Resampling.LANCZOS)
|
|
168
|
+
|
|
169
|
+
# Determine output path
|
|
170
|
+
if output_path is None:
|
|
171
|
+
# Insert size marker before extension
|
|
172
|
+
stem = input_file.stem
|
|
173
|
+
suffix = input_file.suffix
|
|
174
|
+
output_path = str(input_file.parent / f"{stem}_{size}{suffix}")
|
|
175
|
+
|
|
176
|
+
# Save resized image
|
|
177
|
+
resized.save(output_path, quality=quality, optimize=True)
|
|
178
|
+
|
|
179
|
+
return (output_path, new_w, new_h)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def capture_screenshot(
|
|
183
|
+
udid: str,
|
|
184
|
+
output_path: str | None = None,
|
|
185
|
+
size: str = "half",
|
|
186
|
+
inline: bool = False,
|
|
187
|
+
app_name: str | None = None,
|
|
188
|
+
screen_name: str | None = None,
|
|
189
|
+
state: str | None = None,
|
|
190
|
+
) -> dict[str, Any]:
|
|
191
|
+
"""Capture screenshot with flexible output modes.
|
|
192
|
+
|
|
193
|
+
Supports both file-based (persistent artifacts) and inline base64 modes
|
|
194
|
+
(for vision-based automation).
|
|
195
|
+
|
|
196
|
+
Args:
|
|
197
|
+
udid: Device UDID
|
|
198
|
+
output_path: File path for file mode (generates semantic name if None)
|
|
199
|
+
size: 'full', 'half', 'quarter', 'thumb' (default: 'half')
|
|
200
|
+
inline: If True, returns base64 data instead of saving to file
|
|
201
|
+
app_name: App name for semantic naming
|
|
202
|
+
screen_name: Screen name for semantic naming
|
|
203
|
+
state: State description for semantic naming
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
Dict with mode-specific fields:
|
|
207
|
+
|
|
208
|
+
File mode:
|
|
209
|
+
{
|
|
210
|
+
'mode': 'file',
|
|
211
|
+
'file_path': str,
|
|
212
|
+
'size_bytes': int,
|
|
213
|
+
'width': int,
|
|
214
|
+
'height': int,
|
|
215
|
+
'size_preset': str
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
Inline mode:
|
|
219
|
+
{
|
|
220
|
+
'mode': 'inline',
|
|
221
|
+
'base64_data': str,
|
|
222
|
+
'mime_type': 'image/png',
|
|
223
|
+
'width': int,
|
|
224
|
+
'height': int,
|
|
225
|
+
'size_preset': str
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
Example:
|
|
229
|
+
# File mode
|
|
230
|
+
result = capture_screenshot('ABC123', app_name='MyApp')
|
|
231
|
+
print(f"Saved to: {result['file_path']}")
|
|
232
|
+
|
|
233
|
+
# Inline mode
|
|
234
|
+
result = capture_screenshot('ABC123', inline=True, size='half')
|
|
235
|
+
print(f"Screenshot: {result['width']}x{result['height']}")
|
|
236
|
+
print(f"Base64: {result['base64_data'][:50]}...")
|
|
237
|
+
"""
|
|
238
|
+
try:
|
|
239
|
+
# Capture raw screenshot to temp file
|
|
240
|
+
temp_path = "/tmp/ios_simulator_screenshot.png"
|
|
241
|
+
cmd = ["xcrun", "simctl", "io", udid, "screenshot", temp_path]
|
|
242
|
+
|
|
243
|
+
subprocess.run(cmd, capture_output=True, text=True, check=True)
|
|
244
|
+
|
|
245
|
+
if inline:
|
|
246
|
+
# Inline mode: resize and convert to base64
|
|
247
|
+
# Resize if needed
|
|
248
|
+
if size != "full" and HAS_PIL:
|
|
249
|
+
resized_path, width, height = resize_screenshot(temp_path, size=size)
|
|
250
|
+
else:
|
|
251
|
+
resized_path = temp_path
|
|
252
|
+
# Get dimensions via PIL if available
|
|
253
|
+
if HAS_PIL:
|
|
254
|
+
img = Image.open(resized_path)
|
|
255
|
+
width, height = img.size
|
|
256
|
+
else:
|
|
257
|
+
width, height = 390, 844 # Fallback to common device size
|
|
258
|
+
|
|
259
|
+
# Read and encode as base64
|
|
260
|
+
with open(resized_path, "rb") as f:
|
|
261
|
+
base64_data = base64.b64encode(f.read()).decode("utf-8")
|
|
262
|
+
|
|
263
|
+
# Clean up temp files
|
|
264
|
+
Path(temp_path).unlink(missing_ok=True)
|
|
265
|
+
if resized_path != temp_path:
|
|
266
|
+
Path(resized_path).unlink(missing_ok=True)
|
|
267
|
+
|
|
268
|
+
return {
|
|
269
|
+
"mode": "inline",
|
|
270
|
+
"base64_data": base64_data,
|
|
271
|
+
"mime_type": "image/png",
|
|
272
|
+
"width": width,
|
|
273
|
+
"height": height,
|
|
274
|
+
"size_preset": size,
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
# File mode: save to output path with semantic naming
|
|
278
|
+
if output_path is None:
|
|
279
|
+
output_path = generate_screenshot_name(app_name, screen_name, state)
|
|
280
|
+
|
|
281
|
+
# Resize if needed
|
|
282
|
+
if size != "full" and HAS_PIL:
|
|
283
|
+
final_path, width, height = resize_screenshot(temp_path, output_path, size)
|
|
284
|
+
else:
|
|
285
|
+
# Just move temp to output
|
|
286
|
+
import shutil
|
|
287
|
+
|
|
288
|
+
shutil.move(temp_path, output_path)
|
|
289
|
+
final_path = output_path
|
|
290
|
+
|
|
291
|
+
# Get dimensions via PIL if available
|
|
292
|
+
if HAS_PIL:
|
|
293
|
+
img = Image.open(final_path)
|
|
294
|
+
width, height = img.size
|
|
295
|
+
else:
|
|
296
|
+
width, height = 390, 844 # Fallback
|
|
297
|
+
|
|
298
|
+
# Get file size
|
|
299
|
+
size_bytes = Path(final_path).stat().st_size
|
|
300
|
+
|
|
301
|
+
return {
|
|
302
|
+
"mode": "file",
|
|
303
|
+
"file_path": final_path,
|
|
304
|
+
"size_bytes": size_bytes,
|
|
305
|
+
"width": width,
|
|
306
|
+
"height": height,
|
|
307
|
+
"size_preset": size,
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
except subprocess.CalledProcessError as e:
|
|
311
|
+
raise RuntimeError(f"Failed to capture screenshot: {e.stderr}") from e
|
|
312
|
+
except Exception as e:
|
|
313
|
+
raise RuntimeError(f"Screenshot capture error: {e!s}") from e
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def format_screenshot_result(result: dict[str, Any]) -> str:
|
|
317
|
+
"""Format screenshot result for human-readable output.
|
|
318
|
+
|
|
319
|
+
Args:
|
|
320
|
+
result: Result dictionary from capture_screenshot()
|
|
321
|
+
|
|
322
|
+
Returns:
|
|
323
|
+
Formatted string for printing
|
|
324
|
+
|
|
325
|
+
Example:
|
|
326
|
+
result = capture_screenshot('ABC123', inline=True)
|
|
327
|
+
print(format_screenshot_result(result))
|
|
328
|
+
"""
|
|
329
|
+
if result["mode"] == "file":
|
|
330
|
+
return (
|
|
331
|
+
f"Screenshot: {result['file_path']}\n"
|
|
332
|
+
f"Dimensions: {result['width']}x{result['height']}\n"
|
|
333
|
+
f"Size: {result['size_bytes']} bytes"
|
|
334
|
+
)
|
|
335
|
+
return (
|
|
336
|
+
f"Screenshot (inline): {result['width']}x{result['height']}\n"
|
|
337
|
+
f"Base64 length: {len(result['base64_data'])} chars"
|
|
338
|
+
)
|