@daemux/store-automator 0.3.0 → 0.5.1

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.
Files changed (38) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/README.md +11 -13
  3. package/bin/cli.mjs +69 -10
  4. package/package.json +7 -8
  5. package/plugins/store-automator/.claude-plugin/plugin.json +1 -1
  6. package/plugins/store-automator/agents/app-designer.md +320 -0
  7. package/plugins/store-automator/agents/appstore-meta-creator.md +37 -1
  8. package/plugins/store-automator/agents/appstore-reviewer.md +66 -5
  9. package/plugins/store-automator/agents/architect.md +144 -0
  10. package/plugins/store-automator/agents/developer.md +249 -0
  11. package/plugins/store-automator/agents/devops.md +396 -0
  12. package/plugins/store-automator/agents/product-manager.md +258 -0
  13. package/plugins/store-automator/agents/reviewer.md +386 -0
  14. package/plugins/store-automator/agents/simplifier.md +192 -0
  15. package/plugins/store-automator/agents/tester.md +284 -0
  16. package/scripts/check_changed.sh +23 -0
  17. package/scripts/check_google_play.py +139 -0
  18. package/scripts/codemagic-setup.mjs +44 -0
  19. package/scripts/generate.sh +107 -0
  20. package/scripts/manage_version_ios.py +168 -0
  21. package/src/codemagic-api.mjs +73 -0
  22. package/src/codemagic-setup.mjs +164 -0
  23. package/src/github-setup.mjs +52 -0
  24. package/src/install.mjs +32 -7
  25. package/src/prompt.mjs +7 -2
  26. package/src/templates.mjs +15 -5
  27. package/src/uninstall.mjs +37 -21
  28. package/templates/CLAUDE.md.template +293 -223
  29. package/templates/ci.config.yaml.template +14 -1
  30. package/templates/codemagic.template.yaml +15 -6
  31. package/templates/fastlane/android/Fastfile.template +11 -4
  32. package/templates/fastlane/ios/Fastfile.template +27 -11
  33. package/templates/fastlane/ios/Snapfile.template +3 -1
  34. package/templates/github/workflows/codemagic-trigger.yml +68 -0
  35. package/templates/scripts/create_app_record.py +172 -0
  36. package/templates/scripts/generate.sh +6 -0
  37. package/plugins/store-automator/agents/appstore-media-designer.md +0 -195
  38. package/src/dependency-check.mjs +0 -26
@@ -1,5 +1,10 @@
1
1
  default_platform(:android)
2
2
 
3
+ # Resolve paths absolutely to avoid symlink-relative breakage.
4
+ # CM_BUILD_DIR is set by Codemagic CI; locally we derive from __FILE__.
5
+ ROOT_DIR = ENV.fetch("CM_BUILD_DIR", File.expand_path("../..", __FILE__))
6
+ APP_ROOT = ENV.fetch("APP_ROOT", "app")
7
+
3
8
  def metadata_changed?(path)
4
9
  !sh("git diff --name-only HEAD~1 -- #{path}").strip.empty?
5
10
  rescue StandardError
@@ -9,14 +14,14 @@ end
9
14
  platform :android do
10
15
  lane :deploy_android do
11
16
  upload_to_play_store(
12
- aab: "../build/app/outputs/bundle/release/app-release.aab",
17
+ aab: "#{ROOT_DIR}/#{APP_ROOT}/build/app/outputs/bundle/release/app-release.aab",
13
18
  track: ENV.fetch("TRACK", "internal"),
14
19
  json_key: ENV["GOOGLE_PLAY_SERVICE_ACCOUNT_JSON_PATH"],
15
20
  skip_upload_metadata: !metadata_changed?("fastlane/metadata/android/"),
16
21
  skip_upload_screenshots: !metadata_changed?("fastlane/screenshots/android/"),
17
22
  skip_upload_images: !metadata_changed?("fastlane/screenshots/android/"),
18
23
  skip_upload_changelogs: false,
19
- metadata_path: "../fastlane/metadata/android",
24
+ metadata_path: "#{ROOT_DIR}/fastlane/metadata/android",
20
25
  rollout: ENV.fetch("ROLLOUT_FRACTION", "").empty? ? nil : ENV["ROLLOUT_FRACTION"].to_f,
21
26
  in_app_update_priority: ENV.fetch("IN_APP_UPDATE_PRIORITY", "3").to_i
22
27
  )
@@ -26,11 +31,13 @@ platform :android do
26
31
  manage_google_iap(
27
32
  json_key: ENV["GOOGLE_PLAY_SERVICE_ACCOUNT_JSON_PATH"],
28
33
  package_name: ENV["PACKAGE_NAME"],
29
- config_path: "../fastlane/iap_config.json"
34
+ config_path: "#{ROOT_DIR}/fastlane/iap_config.json"
30
35
  )
31
36
  end
32
37
 
33
38
  lane :update_data_safety do
34
- sh("python3", "../scripts/update_data_safety.py") if File.exist?("../fastlane/data_safety.csv")
39
+ script = "#{ROOT_DIR}/scripts/update_data_safety.py"
40
+ csv = "#{ROOT_DIR}/fastlane/data_safety.csv"
41
+ sh("python3", script) if File.exist?(csv)
35
42
  end
36
43
  end
@@ -1,5 +1,10 @@
1
1
  default_platform(:ios)
2
2
 
3
+ # Resolve paths absolutely to avoid symlink-relative breakage.
4
+ # CM_BUILD_DIR is set by Codemagic CI; locally we derive from __FILE__.
5
+ ROOT_DIR = ENV.fetch("CM_BUILD_DIR", File.expand_path("../..", __FILE__))
6
+ APP_ROOT = ENV.fetch("APP_ROOT", "app")
7
+
3
8
  def metadata_changed?(path)
4
9
  !sh("git diff --name-only HEAD~1 -- #{path}").strip.empty?
5
10
  rescue StandardError
@@ -7,28 +12,39 @@ rescue StandardError
7
12
  end
8
13
 
9
14
  def asc_api_key
10
- app_store_connect_api_key(
11
- key_id: ENV["APP_STORE_CONNECT_KEY_IDENTIFIER"],
12
- issuer_id: ENV["APP_STORE_CONNECT_ISSUER_ID"],
13
- key_content: ENV["APP_STORE_CONNECT_PRIVATE_KEY"],
14
- is_key_content_base64: false
15
- )
15
+ key_path = ENV["FASTLANE_API_KEY_PATH"]
16
+ if key_path && File.exist?(key_path)
17
+ app_store_connect_api_key(
18
+ key_id: ENV["APP_STORE_CONNECT_KEY_IDENTIFIER"],
19
+ issuer_id: ENV["APP_STORE_CONNECT_ISSUER_ID"],
20
+ key_filepath: key_path,
21
+ is_key_content_base64: false
22
+ )
23
+ else
24
+ app_store_connect_api_key(
25
+ key_id: ENV["APP_STORE_CONNECT_KEY_IDENTIFIER"],
26
+ issuer_id: ENV["APP_STORE_CONNECT_ISSUER_ID"],
27
+ key_content: ENV["APP_STORE_CONNECT_PRIVATE_KEY"],
28
+ is_key_content_base64: false
29
+ )
30
+ end
16
31
  end
17
32
 
18
33
  platform :ios do
19
34
  lane :deploy_ios do
20
35
  deliver(
21
36
  api_key: asc_api_key,
22
- ipa: "../build/ios/ipa/*.ipa",
37
+ app_identifier: ENV["BUNDLE_ID"],
38
+ ipa: Dir.glob("#{ROOT_DIR}/#{APP_ROOT}/build/ios/ipa/*.ipa").first,
23
39
  skip_metadata: !metadata_changed?("fastlane/metadata/ios/"),
24
40
  skip_screenshots: !metadata_changed?("fastlane/screenshots/ios/"),
25
41
  sync_screenshots: true,
26
- metadata_path: "../fastlane/metadata/ios",
27
- screenshots_path: "../fastlane/screenshots/ios",
42
+ metadata_path: "#{ROOT_DIR}/fastlane/metadata/ios",
43
+ screenshots_path: "#{ROOT_DIR}/fastlane/screenshots/ios",
28
44
  submit_for_review: ENV.fetch("SUBMIT_FOR_REVIEW", "true") == "true",
29
45
  automatic_release: ENV.fetch("AUTOMATIC_RELEASE", "true") == "true",
30
46
  force: true,
31
- app_rating_config_path: "../fastlane/app_rating_config.json",
47
+ app_rating_config_path: "#{ROOT_DIR}/fastlane/app_rating_config.json",
32
48
  price_tier: ENV.fetch("PRICE_TIER", "0").to_i,
33
49
  run_precheck_before_submit: true,
34
50
  precheck_include_in_app_purchases: false,
@@ -41,7 +57,7 @@ platform :ios do
41
57
  manage_iap(
42
58
  api_key: asc_api_key,
43
59
  app_id: ENV["BUNDLE_ID"],
44
- config_path: "../fastlane/iap_config.json"
60
+ config_path: "#{ROOT_DIR}/fastlane/iap_config.json"
45
61
  )
46
62
  end
47
63
  end
@@ -1,3 +1,5 @@
1
+ root_dir = ENV.fetch("CM_BUILD_DIR", File.expand_path("../..", __FILE__))
2
+
1
3
  devices([
2
4
  "iPhone 16 Pro Max",
3
5
  "iPhone 16 Pro",
@@ -21,6 +23,6 @@ languages([
21
23
  ])
22
24
 
23
25
  scheme(ENV.fetch("APP_SCHEME", "Runner"))
24
- output_directory("../fastlane/screenshots/ios")
26
+ output_directory("#{root_dir}/fastlane/screenshots/ios")
25
27
  clear_previous_screenshots(false)
26
28
  concurrent_simulators(true)
@@ -0,0 +1,68 @@
1
+ name: Trigger Codemagic Builds
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+
7
+ jobs:
8
+ trigger:
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - name: Checkout config
12
+ uses: actions/checkout@v4
13
+ with:
14
+ sparse-checkout: ci.config.yaml
15
+ sparse-checkout-cone-mode: false
16
+
17
+ - name: Install yq
18
+ run: |
19
+ if ! command -v yq &> /dev/null; then
20
+ sudo wget -qO /usr/local/bin/yq https://github.com/mikefarah/yq/releases/latest/download/yq_linux_amd64
21
+ sudo chmod +x /usr/local/bin/yq
22
+ fi
23
+
24
+ - name: Read Codemagic config
25
+ id: config
26
+ run: |
27
+ if [ ! -f ci.config.yaml ]; then
28
+ echo "skip=true" >> "$GITHUB_OUTPUT"
29
+ echo "ci.config.yaml not found - skipping trigger."
30
+ exit 0
31
+ fi
32
+
33
+ APP_ID=$(yq -r '.codemagic.app_id // ""' ci.config.yaml)
34
+ if [ -z "$APP_ID" ] || [ "$APP_ID" = "null" ]; then
35
+ echo "skip=true" >> "$GITHUB_OUTPUT"
36
+ echo "app_id not configured - skipping trigger."
37
+ exit 0
38
+ fi
39
+
40
+ echo "app_id=$APP_ID" >> "$GITHUB_OUTPUT"
41
+
42
+ WORKFLOWS=$(yq -r '.codemagic.workflows[]' ci.config.yaml)
43
+ DELIMITER="EOF_$(date +%s%N)"
44
+ echo "workflows<<$DELIMITER" >> "$GITHUB_OUTPUT"
45
+ echo "$WORKFLOWS" >> "$GITHUB_OUTPUT"
46
+ echo "$DELIMITER" >> "$GITHUB_OUTPUT"
47
+
48
+ - name: Trigger Codemagic builds
49
+ if: steps.config.outputs.skip != 'true'
50
+ env:
51
+ CM_TOKEN: ${{ secrets.CM_API_TOKEN }}
52
+ APP_ID: ${{ steps.config.outputs.app_id }}
53
+ BRANCH: ${{ github.ref_name }}
54
+ run: |
55
+ echo "${{ steps.config.outputs.workflows }}" | while IFS= read -r workflow; do
56
+ [ -z "$workflow" ] && continue
57
+ if ! echo "$workflow" | grep -Eq '^[a-zA-Z0-9_-]+$'; then
58
+ echo "ERROR: Invalid workflow name: $workflow"
59
+ continue
60
+ fi
61
+ echo "Triggering: $workflow"
62
+ curl --max-time 30 --retry 2 -sf -X POST https://api.codemagic.io/builds \
63
+ -H "Content-Type: application/json" \
64
+ -H "x-auth-token: $CM_TOKEN" \
65
+ -d "{\"appId\": \"$APP_ID\", \"workflowId\": \"$workflow\", \"branch\": \"$BRANCH\"}" \
66
+ && echo " -> success" \
67
+ || echo " -> failed"
68
+ done
@@ -0,0 +1,172 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Create an App Store Connect app record via the direct API.
4
+
5
+ Looks up the Bundle ID resource, then creates a new app record
6
+ with the specified name, SKU, and primary locale.
7
+
8
+ Required env vars:
9
+ APP_STORE_CONNECT_KEY_IDENTIFIER - Key ID from App Store Connect
10
+ APP_STORE_CONNECT_ISSUER_ID - Issuer ID from App Store Connect
11
+ APP_STORE_CONNECT_PRIVATE_KEY - Contents of the P8 key file
12
+ BUNDLE_ID - App bundle identifier (e.g. com.daemux.gigachat)
13
+ APP_NAME - Display name for the app
14
+ SKU - Unique SKU string for the app
15
+
16
+ Exit codes:
17
+ 0 - App record created successfully
18
+ 1 - Any failure (missing env vars, API error, etc.)
19
+ """
20
+ import json
21
+ import os
22
+ import sys
23
+ import time
24
+
25
+ try:
26
+ import jwt
27
+ import requests
28
+ except ImportError:
29
+ print("Installing dependencies...", file=sys.stderr)
30
+ import subprocess
31
+ subprocess.check_call(
32
+ [sys.executable, "-m", "pip", "install", "PyJWT", "cryptography", "requests"],
33
+ stdout=subprocess.DEVNULL,
34
+ )
35
+ import jwt
36
+ import requests
37
+
38
+ BASE_URL = "https://api.appstoreconnect.apple.com/v1"
39
+ TIMEOUT = (10, 30)
40
+
41
+
42
+ def get_jwt_token(key_id: str, issuer_id: str, private_key: str) -> str:
43
+ """Generate a signed JWT for App Store Connect API authentication."""
44
+ now = int(time.time())
45
+ payload = {
46
+ "iss": issuer_id,
47
+ "iat": now,
48
+ "exp": now + 1200,
49
+ "aud": "appstoreconnect-v1",
50
+ }
51
+ return jwt.encode(
52
+ payload, private_key, algorithm="ES256", headers={"kid": key_id}
53
+ )
54
+
55
+
56
+ def lookup_bundle_id_resource(headers: dict, bundle_id: str) -> str:
57
+ """Look up the Bundle ID resource ID for the given identifier."""
58
+ resp = requests.get(
59
+ f"{BASE_URL}/bundleIds",
60
+ params={"filter[identifier]": bundle_id},
61
+ headers=headers,
62
+ timeout=TIMEOUT,
63
+ )
64
+ resp.raise_for_status()
65
+ data = resp.json().get("data", [])
66
+ if not data:
67
+ print(
68
+ f"ERROR: No Bundle ID found for identifier '{bundle_id}'. "
69
+ "Register it in App Store Connect > Certificates, Identifiers & Profiles first.",
70
+ file=sys.stderr,
71
+ )
72
+ sys.exit(1)
73
+ return data[0]["id"]
74
+
75
+
76
+ def create_app_record(
77
+ headers: dict,
78
+ bundle_id: str,
79
+ bundle_id_resource_id: str,
80
+ app_name: str,
81
+ sku: str,
82
+ ) -> dict:
83
+ """Create a new app record in App Store Connect."""
84
+ payload = {
85
+ "data": {
86
+ "type": "apps",
87
+ "attributes": {
88
+ "bundleId": bundle_id,
89
+ "name": app_name,
90
+ "sku": sku,
91
+ "primaryLocale": "en-US",
92
+ },
93
+ "relationships": {
94
+ "bundleId": {
95
+ "data": {
96
+ "type": "bundleIds",
97
+ "id": bundle_id_resource_id,
98
+ }
99
+ }
100
+ },
101
+ }
102
+ }
103
+ resp = requests.post(
104
+ f"{BASE_URL}/apps",
105
+ json=payload,
106
+ headers=headers,
107
+ timeout=TIMEOUT,
108
+ )
109
+ if resp.status_code == 409:
110
+ print(
111
+ f"ERROR: An app with bundle ID '{bundle_id}' already exists.",
112
+ file=sys.stderr,
113
+ )
114
+ sys.exit(1)
115
+ if not resp.ok:
116
+ errors = resp.json().get("errors", [])
117
+ for err in errors:
118
+ print(f"ERROR: {err.get('detail', err.get('title', 'Unknown'))}", file=sys.stderr)
119
+ sys.exit(1)
120
+ return resp.json()["data"]
121
+
122
+
123
+ def main() -> None:
124
+ key_id = os.environ.get("APP_STORE_CONNECT_KEY_IDENTIFIER", "")
125
+ issuer_id = os.environ.get("APP_STORE_CONNECT_ISSUER_ID", "")
126
+ private_key = os.environ.get("APP_STORE_CONNECT_PRIVATE_KEY", "")
127
+ bundle_id = os.environ.get("BUNDLE_ID", "")
128
+ app_name = os.environ.get("APP_NAME", "")
129
+ sku = os.environ.get("SKU", "")
130
+
131
+ missing = []
132
+ if not key_id:
133
+ missing.append("APP_STORE_CONNECT_KEY_IDENTIFIER")
134
+ if not issuer_id:
135
+ missing.append("APP_STORE_CONNECT_ISSUER_ID")
136
+ if not private_key:
137
+ missing.append("APP_STORE_CONNECT_PRIVATE_KEY")
138
+ if not bundle_id:
139
+ missing.append("BUNDLE_ID")
140
+ if not app_name:
141
+ missing.append("APP_NAME")
142
+ if not sku:
143
+ missing.append("SKU")
144
+
145
+ if missing:
146
+ print(f"ERROR: Missing required environment variables: {', '.join(missing)}", file=sys.stderr)
147
+ sys.exit(1)
148
+
149
+ token = get_jwt_token(key_id, issuer_id, private_key)
150
+ headers = {
151
+ "Authorization": f"Bearer {token}",
152
+ "Content-Type": "application/json",
153
+ }
154
+
155
+ print(f"Looking up Bundle ID resource for '{bundle_id}'...")
156
+ bundle_id_resource_id = lookup_bundle_id_resource(headers, bundle_id)
157
+ print(f"Found Bundle ID resource: {bundle_id_resource_id}")
158
+
159
+ print(f"Creating app record '{app_name}' (SKU: {sku})...")
160
+ app_data = create_app_record(headers, bundle_id, bundle_id_resource_id, app_name, sku)
161
+
162
+ result = {
163
+ "app_id": app_data["id"],
164
+ "name": app_data["attributes"]["name"],
165
+ "bundle_id": app_data["attributes"]["bundleId"],
166
+ "sku": app_data["attributes"]["sku"],
167
+ }
168
+ print(f"App record created successfully: {json.dumps(result)}")
169
+
170
+
171
+ if __name__ == "__main__":
172
+ main()
@@ -54,6 +54,9 @@ PRICE_TIER=$(yq -r '.ios.price_tier' "$CONFIG")
54
54
  SUBMIT_REVIEW=$(yq -r '.ios.submit_for_review' "$CONFIG")
55
55
  AUTO_RELEASE=$(yq -r '.ios.automatic_release' "$CONFIG")
56
56
 
57
+ # Read flutter_root (defaults to "." if not set)
58
+ APP_ROOT=$(yq -r '.flutter_root // "."' "$CONFIG")
59
+
57
60
  # Read credentials from ci.config.yaml
58
61
  P8_KEY_PATH=$(yq -r '.credentials.apple.p8_key_path' "$CONFIG")
59
62
  APPLE_KEY_ID=$(yq -r '.credentials.apple.key_id' "$CONFIG")
@@ -72,6 +75,7 @@ validate_value "ios.primary_category" "$PRIMARY_CAT"
72
75
  validate_value "ios.secondary_category" "$SECONDARY_CAT"
73
76
  validate_value "credentials.apple.key_id" "$APPLE_KEY_ID"
74
77
  validate_value "credentials.apple.issuer_id" "$APPLE_ISSUER_ID"
78
+ validate_value "flutter_root" "$APP_ROOT"
75
79
 
76
80
  # Generate codemagic.yaml from template
77
81
  sed \
@@ -93,9 +97,11 @@ sed \
93
97
  -e "s|\${APPLE_ISSUER_ID}|$APPLE_ISSUER_ID|g" \
94
98
  -e "s|\${GOOGLE_SA_JSON_PATH}|$GOOGLE_SA_JSON_PATH|g" \
95
99
  -e "s|\${KEYSTORE_PASSWORD}|$KEYSTORE_PASSWORD|g" \
100
+ -e "s|\${APP_ROOT}|$APP_ROOT|g" \
96
101
  "$TEMPLATE" > codemagic.yaml
97
102
 
98
103
  echo "Generated codemagic.yaml for $APP_NAME ($BUNDLE_ID)"
99
104
  echo " iOS: bundle_id=$BUNDLE_ID, category=$PRIMARY_CAT"
100
105
  echo " Android: package=$PACKAGE_NAME, track=$TRACK"
106
+ echo " Flutter: root=$APP_ROOT"
101
107
  echo " Credentials: P8=$P8_KEY_PATH, SA=$GOOGLE_SA_JSON_PATH"
@@ -1,195 +0,0 @@
1
- ---
2
- name: appstore-media-designer
3
- description: "Creates ASO-optimized app store screenshots for Apple App Store and Google Play. All designs created in Stitch MCP. Researches competitors for inspiration. 5 screenshots per device, all sizes."
4
- model: opus
5
- ---
6
-
7
- You are a senior app store creative designer and ASO (App Store Optimization) specialist. You create high-converting, guideline-compliant screenshots that maximize organic downloads from store search results.
8
-
9
- ## Critical ASO Context
10
-
11
- Screenshots are the #1 conversion factor in app store search results. Users see screenshots as **small thumbnails** in search results, NOT full-size. Your designs MUST convert at thumbnail size:
12
-
13
- - **Headlines must be BIG** — max 2 lines, large bold text readable at thumbnail size
14
- - **Short, selling copy** — benefit-focused, not feature descriptions
15
- - **Visual clarity** — clean layouts that communicate instantly, no clutter
16
- - **First screenshot is everything** — 80% of users decide from the first screenshot alone
17
-
18
- ## Workflow
19
-
20
- 1. READ the app source code (lib/ directory) to understand all screens and features
21
- 2. READ any existing Stitch designs in the same project (the app design was created here first)
22
- 3. READ ci.config.yaml for app identity and branding info
23
- 4. **RESEARCH competitors** — search for the biggest competitors in the same app category, study their screenshot strategies, note what works (headlines, layouts, colors)
24
- 5. PLAN 5 screenshot scenes optimized for ASO conversion
25
- 6. USE Stitch MCP to create ALL screenshots in the **same Stitch project** as the app design
26
- 7. EXPORT and SAVE screenshots to fastlane/screenshots/ in the correct directory structure
27
- 8. Verify all required sizes, formats, and file names are present
28
-
29
- ## Competitor Research (MANDATORY)
30
-
31
- Before designing screenshots, research the top 5-10 competitors in your app's category:
32
-
33
- 1. Use web search to find the top apps in the category on both App Store and Google Play
34
- 2. Study their screenshot strategies: headline styles, colors, layouts, number of screenshots
35
- 3. Note common patterns that successful apps use
36
- 4. Identify opportunities to differentiate while following proven patterns
37
- 5. Document findings briefly before starting design
38
-
39
- ## Screenshot Strategy: 5 Scenes
40
-
41
- For every app, create exactly 5 screenshot scenes:
42
-
43
- | Scene | Purpose | Headline Strategy |
44
- |-------|---------|-------------------|
45
- | 01_hero | Most impressive feature/screen — this is the MONEY SHOT | Bold value proposition, max 5 words, answers "what does this app do?" |
46
- | 02_feature1 | Primary feature in action | Benefit headline: what the user GETS |
47
- | 03_feature2 | Secondary differentiating feature | What makes this app DIFFERENT |
48
- | 04_social | Social proof, results, or key metric | Trust/credibility headline |
49
- | 05_settings | Customization, extras, or final CTA | "And more..." or urgency headline |
50
-
51
- ### Headline Rules (CRITICAL for ASO)
52
-
53
- - **MAX 2 lines of text** — never more
54
- - **BIG font size** — must be readable when the screenshot is thumbnail-sized in search results
55
- - **Short selling text** — 3-6 words per headline, not feature descriptions
56
- - **Action/benefit words** — "Unlock", "Transform", "Discover", "Create", "Save", "Get"
57
- - **No filler words** — every word must earn its place
58
- - Examples of GOOD headlines: "Chat Smarter, Not Harder", "Your AI Assistant", "Unlimited Creativity"
59
- - Examples of BAD headlines: "Advanced AI-powered conversational interface with real-time responses"
60
-
61
- ### Scene Design Rules
62
-
63
- - Headlines placed at TOP of the screenshot — big, bold, high contrast
64
- - Background: solid color or gradient from the app's color palette
65
- - App screen mockup placed centrally, occupying 55-65% of the image area
66
- - Device frame is OPTIONAL — frameless looks more modern and gives more screen space
67
- - Consistent typography and color scheme across all 5 scenes
68
- - The app UI shown must represent realistic app content
69
- - Clean, modern, minimal style — Apple/Google design quality
70
-
71
- ## All Screenshots Created in Stitch MCP
72
-
73
- **MANDATORY: ALL screenshots are designed entirely in Stitch MCP. No simulator screenshots, no mobile-mcp, no external tools.**
74
-
75
- ### Design Process
76
-
77
- 1. **Use the existing Stitch project** — screenshots go in the SAME project where the app design was created
78
- 2. For each of the 5 scenes, create a design in Stitch MCP with a detailed prompt
79
- 3. Generate at EVERY required device dimension (see sizes below)
80
- 4. Export each design as PNG and save to the correct directory path
81
-
82
- ### Stitch Design Prompt Template
83
-
84
- For each scene, use a detailed prompt like:
85
-
86
- ```
87
- App store screenshot for a [app category] app called "[App Name]".
88
-
89
- LAYOUT:
90
- - Top 30%: Large headline "[HEADLINE TEXT]" in bold [font], [color] text, left-aligned or centered
91
- - Optional small subheadline below in lighter weight
92
- - Center/bottom 65%: [Device type] showing the app's [specific screen] with [describe UI content in detail]
93
- - Background: [gradient/solid color matching app theme]
94
-
95
- STYLE:
96
- - Clean, modern, minimal — premium App Store quality
97
- - No device frame / thin device frame (choose one)
98
- - High contrast between text and background
99
- - [App name]'s color palette: primary [#hex], accent [#hex], background [#hex]
100
-
101
- DIMENSIONS: [width] x [height] pixels
102
- FORMAT: PNG, RGB color space
103
- ```
104
-
105
- ### Device Sizes to Generate
106
-
107
- For EACH of the 5 scenes, generate at ALL these sizes:
108
-
109
- **Apple App Store (required):**
110
- - iPhone 6.7": 1290 x 2796 px
111
- - iPad Pro 12.9": 2048 x 2732 px
112
- - iPad Pro 13": 2064 x 2752 px
113
-
114
- **Google Play (required):**
115
- - Phone: 1080 x 1920 px
116
- - 7" Tablet: 1200 x 1920 px
117
- - 10" Tablet: 1920 x 1200 px (landscape)
118
-
119
- **Google Play extras (required):**
120
- - Feature Graphic: 1024 x 500 px (landscape banner — app name + tagline + brand colors)
121
- - Icon: 512 x 512 px
122
-
123
- ## Directory Structure
124
-
125
- Save all exported screenshots to:
126
-
127
- ### iOS
128
- ```
129
- fastlane/screenshots/ios/
130
- en-US/
131
- iPhone 6.7/
132
- 01_hero.png, 02_feature1.png, 03_feature2.png, 04_social.png, 05_settings.png
133
- iPad Pro 12.9/
134
- 01_hero.png, 02_feature1.png, 03_feature2.png, 04_social.png, 05_settings.png
135
- iPad Pro 13/
136
- 01_hero.png, 02_feature1.png, 03_feature2.png, 04_social.png, 05_settings.png
137
- ```
138
-
139
- ### Android
140
- ```
141
- fastlane/screenshots/android/
142
- en-US/
143
- phoneScreenshots/
144
- 01_hero.png, 02_feature1.png, 03_feature2.png, 04_social.png, 05_settings.png
145
- sevenInchScreenshots/
146
- 01_hero.png, 02_feature1.png, 03_feature2.png, 04_social.png, 05_settings.png
147
- tenInchScreenshots/
148
- 01_hero.png, 02_feature1.png, 03_feature2.png, 04_social.png, 05_settings.png
149
- featureGraphic.png
150
- icon.png
151
- ```
152
-
153
- ## Apple App Store Rules
154
- - Must show app UI (Stitch-designed screens representing real app features)
155
- - No photographs of people holding physical devices
156
- - Format: .png only
157
- - Max 10 per device class per locale
158
- - Text overlays allowed, app UI must be prominent
159
- - No misleading content
160
- - Portrait orientation
161
-
162
- ## Google Play Rules
163
- - Screenshots must accurately depict the app experience
164
- - Device frames optional
165
- - Feature graphic: landscape 1024x500, displayed at top of store listing
166
- - Text must be readable
167
- - No excessive text overlaying the UI
168
-
169
- ## Fallback: If Stitch MCP is unavailable
170
-
171
- Only if Stitch MCP tools are not available, save a design specification to `fastlane/screenshots/design-spec.json` with brand colors, scene descriptions, and headline text for each scene. This spec can be used to create screenshots manually.
172
-
173
- ## Output Verification Checklist
174
-
175
- After creating all screenshots, verify:
176
-
177
- - [ ] iPhone 6.7" — 5 screenshots at 1290x2796, .png
178
- - [ ] iPad Pro 12.9" — 5 screenshots at 2048x2732, .png
179
- - [ ] iPad Pro 13" — 5 screenshots at 2064x2752, .png
180
- - [ ] Android phone — 5 screenshots at 1080x1920, .png
181
- - [ ] Android 7" tablet — 5 screenshots at 1200x1920, .png
182
- - [ ] Android 10" tablet — 5 screenshots at 1920x1200, .png
183
- - [ ] Feature graphic — 1024x500, .png
184
- - [ ] Icon — 512x512, .png
185
- - [ ] Headlines are BIG and readable at thumbnail size
186
- - [ ] Max 2 lines of headline text per screenshot
187
- - [ ] Consistent color scheme and typography across all scenes
188
- - [ ] App UI is prominent and represents the actual app
189
- - [ ] No photographs of people holding physical devices
190
-
191
- ## Output Footer
192
-
193
- ```
194
- NEXT: appstore-reviewer to verify screenshot compliance.
195
- ```
@@ -1,26 +0,0 @@
1
- import { exec } from './utils.mjs';
2
-
3
- export function isClaudePluginInstalled() {
4
- const result = exec('npm ls -g @daemux/claude-plugin --depth=0');
5
- return result !== null && result.includes('@daemux/claude-plugin');
6
- }
7
-
8
- export function installClaudePlugin() {
9
- console.log('Installing @daemux/claude-plugin globally...');
10
- const result = exec('npm install -g @daemux/claude-plugin');
11
- if (result === null) {
12
- console.log('Warning: could not install @daemux/claude-plugin globally.');
13
- console.log('Run manually: npm install -g @daemux/claude-plugin');
14
- return false;
15
- }
16
- console.log('@daemux/claude-plugin installed successfully.');
17
- return true;
18
- }
19
-
20
- export function ensureClaudePlugin() {
21
- if (isClaudePluginInstalled()) {
22
- console.log('@daemux/claude-plugin is already installed globally.');
23
- return true;
24
- }
25
- return installClaudePlugin();
26
- }