App-store-connect-cli-skills asc-shots-pipeline
Orchestrate iOS screenshot automation with xcodebuild/simctl for build-run, AXe for UI actions, JSON settings and plan files, Koubou-based framing (`asc screenshots frame`), and screenshot upload (`asc screenshots upload`). Use when users ask for automated screenshot capture, AXe-driven simulator flows, frame composition, or screenshot-to-upload pipelines.
git clone https://github.com/rorkai/app-store-connect-cli-skills
T=$(mktemp -d) && git clone --depth=1 https://github.com/rorkai/app-store-connect-cli-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/asc-shots-pipeline" ~/.claude/skills/rorkai-app-store-connect-cli-skills-asc-shots-pipeline && rm -rf "$T"
skills/asc-shots-pipeline/SKILL.mdasc screenshots pipeline (xcodebuild -> AXe -> frame -> asc)
Use this skill for agent-driven screenshot workflows where the app is built and launched with Xcode CLI tools, UI is driven with AXe, and screenshots are uploaded with
asc.
Current scope
- Implemented now: build/run, AXe plan capture, frame composition, and upload.
- Device discovery is built-in via
.asc screenshots list-frame-devices - Local screenshot automation commands are experimental in asc cli.
- Framing is pinned to Koubou
for deterministic output.0.18.1 - Feedback/issues: https://github.com/rorkai/App-Store-Connect-CLI/issues/new/choose
Defaults
- Settings file:
.asc/shots.settings.json - Capture plan:
.asc/screenshots.json - Raw screenshots dir:
./screenshots/raw - Framed screenshots dir:
./screenshots/framed - Default frame device:
iphone-air
1) Create settings JSON first
Create or update
.asc/shots.settings.json:
{ "version": 1, "app": { "bundle_id": "com.example.app", "project": "MyApp.xcodeproj", "scheme": "MyApp", "simulator_udid": "booted" }, "paths": { "plan": ".asc/screenshots.json", "raw_dir": "./screenshots/raw", "framed_dir": "./screenshots/framed" }, "pipeline": { "frame_enabled": true, "upload_enabled": false }, "upload": { "version_localization_id": "", "device_type": "IPHONE_65", "source_dir": "./screenshots/framed" } }
If you intentionally skip framing, set:
"frame_enabled": false"upload.source_dir": "./screenshots/raw"
2) Build and run app on simulator
Use Xcode CLI for build/install/launch:
xcrun simctl boot "$UDID" || true xcodebuild \ -project "MyApp.xcodeproj" \ -scheme "MyApp" \ -configuration Debug \ -destination "platform=iOS Simulator,id=$UDID" \ -derivedDataPath ".build/DerivedData" \ build xcrun simctl install "$UDID" ".build/DerivedData/Build/Products/Debug-iphonesimulator/MyApp.app" xcrun simctl launch "$UDID" "com.example.app"
Use
xcodebuild -showBuildSettings if the app bundle path differs from the default location.
3) Capture screenshots with AXe (or asc screenshots run
)
asc screenshots runPrefer plan-driven capture:
asc screenshots run --plan ".asc/screenshots.json" --udid "$UDID" --output json
Useful AXe primitives during plan authoring:
axe describe-ui --udid "$UDID" axe tap --id "search_field" --udid "$UDID" axe type "wwdc" --udid "$UDID" axe screenshot --output "./screenshots/raw/home.png" --udid "$UDID"
Minimal
.asc/screenshots.json example:
{ "version": 1, "app": { "bundle_id": "com.example.app", "udid": "booted", "output_dir": "./screenshots/raw" }, "steps": [ { "action": "launch" }, { "action": "wait", "duration_ms": 800 }, { "action": "screenshot", "name": "home" } ] }
4) Frame screenshots with asc screenshots frame
asc screenshots frameasc cli pins framing to Koubou
0.18.1.
Install and verify before running framing steps:
pip install koubou==0.18.1 kou --version # expect 0.18.1 # If Koubou reports missing device frames, run once with network access: kou setup-frames
List supported frame device values first:
asc screenshots list-frame-devices --output json
Frame one screenshot (defaults to
iphone-air):
asc screenshots frame \ --input "./screenshots/raw/home.png" \ --output-dir "./screenshots/framed" \ --device "iphone-air" \ --output json
Supported
--device values:
(default)iphone-airiphone-17-proiphone-17-pro-maxiphone-16eiphone-17mac
5) Upload screenshots with asc
Generate and review artifacts before upload:
asc screenshots review-generate --framed-dir "./screenshots/framed" --output-dir "./screenshots/review" asc screenshots review-open --output-dir "./screenshots/review" asc screenshots review-approve --all-ready --output-dir "./screenshots/review"
Upload from the configured source directory (default
./screenshots/framed when framing is enabled):
asc screenshots upload \ --version-localization "LOC_ID" \ --path "./screenshots/framed" \ --device-type "IPHONE_65" \ --output json
List or validate before upload when needed:
asc screenshots sizes --output table asc screenshots list --version-localization "LOC_ID" --output table
Agent behavior
- Always confirm exact flags with
before running commands.--help - Re-check command paths with
because screenshot commands are evolving quickly.asc screenshots --help - Keep outputs deterministic: default to JSON for machine steps.
- Prefer
before selecting a frame device.asc screenshots list-frame-devices --output json - Ensure screenshot files exist before upload.
- Use explicit long flags (
,--app
,--output
, etc.).--version-localization - Treat screenshot-local automation as experimental and call it out in user-facing handoff notes.
- If framing fails with a version error, re-install pinned Koubou:
.pip install koubou==0.18.1 - If framing fails because device frames are missing, run
once with network access.kou setup-frames
6) Multi-locale capture (optional)
Do not use
xcrun simctl launch ... -e AppleLanguages for localization.
-e is an environment variable pattern and does not reliably switch app language.
For this pipeline, use simulator-wide locale defaults per UDID. This works with
asc screenshots capture, which relaunches the app internally.
# Map each locale to a dedicated simulator UDID. # (Create these simulators once with `xcrun simctl create`.) declare -A LOCALE_UDID=( ["en-US"]="UDID_EN_US" ["de-DE"]="UDID_DE_DE" ["fr-FR"]="UDID_FR_FR" ["ja-JP"]="UDID_JA_JP" ) set_simulator_locale() { local UDID="$1" local LOCALE="$2" # e.g. de-DE local LANG="${LOCALE%%-*}" # de local APPLE_LOCALE="${LOCALE/-/_}" # de_DE xcrun simctl boot "$UDID" || true xcrun simctl spawn "$UDID" defaults write NSGlobalDomain AppleLanguages -array "$LANG" xcrun simctl spawn "$UDID" defaults write NSGlobalDomain AppleLocale -string "$APPLE_LOCALE" } for LOCALE in "${!LOCALE_UDID[@]}"; do UDID="${LOCALE_UDID[$LOCALE]}" echo "Capturing $LOCALE on $UDID..." set_simulator_locale "$UDID" "$LOCALE" xcrun simctl terminate "$UDID" "com.example.app" || true asc screenshots capture \ --bundle-id "com.example.app" \ --name "home" \ --udid "$UDID" \ --output-dir "./screenshots/raw/$LOCALE" \ --output json done
If you launch manually (outside
asc screenshots capture), use app launch arguments:
xcrun simctl launch "$UDID" "com.example.app" -AppleLanguages "(de)" -AppleLocale "de_DE"
7) Parallel execution for speed
Run one locale per simulator UDID in parallel:
#!/bin/bash # parallel-capture.sh declare -A LOCALE_UDID=( ["en-US"]="UDID_EN_US" ["de-DE"]="UDID_DE_DE" ["fr-FR"]="UDID_FR_FR" ["ja-JP"]="UDID_JA_JP" ) capture_locale() { local LOCALE="$1" local UDID="$2" local LANG="${LOCALE%%-*}" local APPLE_LOCALE="${LOCALE/-/_}" echo "Starting $LOCALE on $UDID" xcrun simctl boot "$UDID" || true xcrun simctl spawn "$UDID" defaults write NSGlobalDomain AppleLanguages -array "$LANG" xcrun simctl spawn "$UDID" defaults write NSGlobalDomain AppleLocale -string "$APPLE_LOCALE" xcrun simctl terminate "$UDID" "com.example.app" || true asc screenshots capture \ --bundle-id "com.example.app" \ --name "home" \ --udid "$UDID" \ --output-dir "./screenshots/raw/$LOCALE" \ --output json echo "Completed $LOCALE" } for LOCALE in "${!LOCALE_UDID[@]}"; do capture_locale "$LOCALE" "${LOCALE_UDID[$LOCALE]}" & done wait echo "All captures done. Now framing..."
Or use
xargs with locale:udid pairs:
printf "%s\n" \ "en-US:UDID_EN_US" \ "de-DE:UDID_DE_DE" \ "fr-FR:UDID_FR_FR" \ "ja-JP:UDID_JA_JP" | xargs -P 4 -I {} bash -c ' PAIR="{}" LOCALE="${PAIR%%:*}" UDID="${PAIR##*:}" LANG="${LOCALE%%-*}" APPLE_LOCALE="${LOCALE/-/_}" xcrun simctl boot "$UDID" || true xcrun simctl spawn "$UDID" defaults write NSGlobalDomain AppleLanguages -array "$LANG" xcrun simctl spawn "$UDID" defaults write NSGlobalDomain AppleLocale -string "$APPLE_LOCALE" xcrun simctl terminate "$UDID" "com.example.app" || true asc screenshots capture --bundle-id "com.example.app" --name "home" --udid "$UDID" --output-dir "./screenshots/raw/$LOCALE" --output json '
8) Full multi-locale pipeline example
#!/bin/bash # full-pipeline-multi-locale.sh declare -A LOCALE_UDID=( ["en-US"]="UDID_EN_US" ["de-DE"]="UDID_DE_DE" ["fr-FR"]="UDID_FR_FR" ["es-ES"]="UDID_ES_ES" ["ja-JP"]="UDID_JA_JP" ) DEVICE="iphone-air" RAW_DIR="./screenshots/raw" FRAMED_DIR="./screenshots/framed" # Step 1: Parallel capture with per-simulator locale defaults for LOCALE in "${!LOCALE_UDID[@]}"; do ( UDID="${LOCALE_UDID[$LOCALE]}" LANG="${LOCALE%%-*}" APPLE_LOCALE="${LOCALE/-/_}" xcrun simctl boot "$UDID" || true xcrun simctl spawn "$UDID" defaults write NSGlobalDomain AppleLanguages -array "$LANG" xcrun simctl spawn "$UDID" defaults write NSGlobalDomain AppleLocale -string "$APPLE_LOCALE" xcrun simctl terminate "$UDID" "com.example.app" || true asc screenshots capture \ --bundle-id "com.example.app" \ --name "home" \ --udid "$UDID" \ --output-dir "$RAW_DIR/$LOCALE" \ --output json echo "Captured $LOCALE" ) & done wait # Step 2: Parallel framing for LOCALE in "${!LOCALE_UDID[@]}"; do ( asc screenshots frame \ --input "$RAW_DIR/$LOCALE/home.png" \ --output-dir "$FRAMED_DIR/$LOCALE" \ --device "$DEVICE" \ --output json echo "Framed $LOCALE" ) & done wait # Step 3: Generate review (single run, aggregates all locales) asc screenshots review-generate \ --framed-dir "$FRAMED_DIR" \ --output-dir "./screenshots/review" # Step 4: Upload (run per locale if needed) for LOCALE in "${!LOCALE_UDID[@]}"; do asc screenshots upload \ --version-localization "LOC_ID_FOR_$LOCALE" \ --path "$FRAMED_DIR/$LOCALE" \ --device-type "IPHONE_65" \ --output json done