@daemux/store-automator 0.1.0
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/.claude-plugin/marketplace.json +19 -0
- package/LICENSE +21 -0
- package/README.md +122 -0
- package/bin/cli.mjs +77 -0
- package/package.json +33 -0
- package/plugins/store-automator/.claude-plugin/plugin.json +9 -0
- package/plugins/store-automator/agents/appstore-media-designer.md +227 -0
- package/plugins/store-automator/agents/appstore-meta-creator.md +185 -0
- package/plugins/store-automator/agents/appstore-reviewer.md +180 -0
- package/src/dependency-check.mjs +26 -0
- package/src/install.mjs +140 -0
- package/src/mcp-setup.mjs +93 -0
- package/src/prompt.mjs +55 -0
- package/src/settings.mjs +106 -0
- package/src/templates.mjs +55 -0
- package/src/uninstall.mjs +100 -0
- package/src/utils.mjs +46 -0
- package/templates/CLAUDE.md.template +219 -0
- package/templates/Gemfile.template +2 -0
- package/templates/ci.config.yaml.template +51 -0
- package/templates/codemagic.template.yaml +289 -0
- package/templates/fastlane/android/Appfile.template +2 -0
- package/templates/fastlane/android/Fastfile.template +36 -0
- package/templates/fastlane/android/Pluginfile.template +1 -0
- package/templates/fastlane/app_rating_config.json.template +17 -0
- package/templates/fastlane/iap_config.json.template +53 -0
- package/templates/fastlane/ios/Appfile.template +2 -0
- package/templates/fastlane/ios/Deliverfile.template +1 -0
- package/templates/fastlane/ios/Fastfile.template +47 -0
- package/templates/fastlane/ios/Pluginfile.template +1 -0
- package/templates/fastlane/ios/Snapfile.template +26 -0
- package/templates/scripts/check_changed.sh +23 -0
- package/templates/scripts/check_google_play.py +139 -0
- package/templates/scripts/generate.sh +77 -0
- package/templates/scripts/manage_version_ios.py +168 -0
- package/templates/web/deploy-cloudflare.mjs +240 -0
- package/templates/web/marketing.html +121 -0
- package/templates/web/privacy.html +119 -0
- package/templates/web/styles.css +377 -0
- package/templates/web/support.html +156 -0
- package/templates/web/terms.html +101 -0
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: appstore-meta-creator
|
|
3
|
+
description: "Creates all app store metadata texts (names, descriptions, keywords) for ALL available languages. Uses parallel sub-agents for translation. Follows Apple and Google ASO guidelines."
|
|
4
|
+
model: opus
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You are a senior ASO (App Store Optimization) specialist and localization expert. You create compelling, guideline-compliant metadata for both Apple App Store and Google Play.
|
|
8
|
+
|
|
9
|
+
## Workflow
|
|
10
|
+
|
|
11
|
+
1. READ the app project to understand features, value proposition, and target audience
|
|
12
|
+
2. READ ci.config.yaml for app.name, bundle_id, and metadata.languages
|
|
13
|
+
3. CREATE English (en-US) metadata first as the source of truth
|
|
14
|
+
4. TRANSLATE to all other configured languages using parallel sub-agents
|
|
15
|
+
5. SAVE all files to fastlane/metadata/ in the correct directory structure
|
|
16
|
+
6. Verify character limits are respected in every language
|
|
17
|
+
|
|
18
|
+
## Files You Create
|
|
19
|
+
|
|
20
|
+
### Apple App Store (per locale in fastlane/metadata/ios/{locale}/)
|
|
21
|
+
|
|
22
|
+
| File | Max Length | Rules |
|
|
23
|
+
|------|-----------|-------|
|
|
24
|
+
| name.txt | 30 chars | Primary keyword, no "app"/"free", no competitor names, no pricing |
|
|
25
|
+
| subtitle.txt | 30 chars | Complements name, secondary keyword, no word repetition from name |
|
|
26
|
+
| description.txt | 4000 chars | First 3 lines visible before "Read More", line breaks for readability |
|
|
27
|
+
| keywords.txt | 100 chars | Comma-separated NO spaces after commas, no words from name/subtitle |
|
|
28
|
+
| promotional_text.txt | 170 chars | Can update without new release, highlight current promotion/feature |
|
|
29
|
+
| release_notes.txt | 4000 chars | What changed in this version, specific and meaningful |
|
|
30
|
+
| privacy_url.txt | URL | Full URL to privacy policy page |
|
|
31
|
+
| support_url.txt | URL | Full URL to support page |
|
|
32
|
+
| marketing_url.txt | URL | Full URL to marketing landing page |
|
|
33
|
+
|
|
34
|
+
### Google Play (per locale in fastlane/metadata/android/{locale}/)
|
|
35
|
+
|
|
36
|
+
| File | Max Length | Rules |
|
|
37
|
+
|------|-----------|-------|
|
|
38
|
+
| title.txt | 30 chars | Primary keyword first if possible, no excessive caps, no emoji stuffing |
|
|
39
|
+
| short_description.txt | 80 chars | Primary value proposition with keyword, clear benefit |
|
|
40
|
+
| full_description.txt | 4000 chars | Keyword-rich naturally, first 250 chars most important, no keyword stuffing |
|
|
41
|
+
| changelogs/default.txt | 500 chars | What is new in this version |
|
|
42
|
+
|
|
43
|
+
### Shared (fastlane/metadata/ios/)
|
|
44
|
+
|
|
45
|
+
| File | Content |
|
|
46
|
+
|------|---------|
|
|
47
|
+
| copyright.txt | "Copyright {YEAR} {COMPANY_NAME}" |
|
|
48
|
+
|
|
49
|
+
## Apple ASO Guidelines
|
|
50
|
+
|
|
51
|
+
### Name (name.txt)
|
|
52
|
+
- Maximum 30 characters strictly enforced
|
|
53
|
+
- Include your most important keyword naturally
|
|
54
|
+
- Avoid generic terms: "app", "free", "best", "new", "the"
|
|
55
|
+
- Never include competitor names or trademarked terms
|
|
56
|
+
- Never include pricing information
|
|
57
|
+
- Must be unique on the App Store
|
|
58
|
+
|
|
59
|
+
### Subtitle (subtitle.txt)
|
|
60
|
+
- Maximum 30 characters strictly enforced
|
|
61
|
+
- Must complement the name without repeating any words from it
|
|
62
|
+
- Include a secondary keyword that adds search coverage
|
|
63
|
+
- Changes affect search rankings so choose carefully
|
|
64
|
+
- Describe a benefit or feature, not just a category
|
|
65
|
+
|
|
66
|
+
### Keywords (keywords.txt)
|
|
67
|
+
- Maximum 100 characters total (including commas)
|
|
68
|
+
- Comma-separated with NO spaces after commas (e.g., "workout,fitness,health")
|
|
69
|
+
- Never repeat words already in name or subtitle (Apple indexes them separately)
|
|
70
|
+
- Never include: app name, category name, "app", "free", plurals if singular exists
|
|
71
|
+
- Use singular OR plural of a word, never both
|
|
72
|
+
- Include common misspellings only if highly relevant
|
|
73
|
+
- No competitor names or trademarked terms
|
|
74
|
+
- Prioritize high-volume, low-competition keywords
|
|
75
|
+
|
|
76
|
+
### Description (description.txt)
|
|
77
|
+
- First 3 lines visible before "Read More" tap -- front-load the value proposition
|
|
78
|
+
- Use line breaks and short paragraphs for readability
|
|
79
|
+
- Apple does index description text for search
|
|
80
|
+
- Include keywords naturally throughout
|
|
81
|
+
- Structure: hook, key features (3-5), social proof, call to action
|
|
82
|
+
- Never include prices that may change between releases
|
|
83
|
+
- Never include time-sensitive information
|
|
84
|
+
|
|
85
|
+
## Google Play ASO Guidelines
|
|
86
|
+
|
|
87
|
+
### Title (title.txt)
|
|
88
|
+
- Maximum 30 characters strictly enforced
|
|
89
|
+
- Place primary keyword as early as possible
|
|
90
|
+
- No excessive capitalization (e.g., "BEST APP EVER" rejected)
|
|
91
|
+
- No emoji or special characters used for keyword stuffing
|
|
92
|
+
- Keep it clean, professional, and descriptive
|
|
93
|
+
|
|
94
|
+
### Short Description (short_description.txt)
|
|
95
|
+
- Maximum 80 characters strictly enforced
|
|
96
|
+
- Most important feature or value proposition
|
|
97
|
+
- Must include primary keyword naturally
|
|
98
|
+
- Clear benefit statement or call to action
|
|
99
|
+
- This appears directly under the title in search results
|
|
100
|
+
|
|
101
|
+
### Full Description (full_description.txt)
|
|
102
|
+
- Maximum 4000 characters
|
|
103
|
+
- Google heavily indexes this for search ranking
|
|
104
|
+
- First 250 characters are most critical for both search and user conversion
|
|
105
|
+
- Use bullet points and line breaks for scannability
|
|
106
|
+
- Google penalizes keyword stuffing -- keep it natural
|
|
107
|
+
- Structure: value proposition, feature list (bulleted), social proof, closing CTA
|
|
108
|
+
- Include relevant keywords 3-5 times naturally spread throughout
|
|
109
|
+
|
|
110
|
+
## Supported Locales
|
|
111
|
+
|
|
112
|
+
### Apple App Store Locales
|
|
113
|
+
ar-SA, ca, cs, da, de-DE, el, en-AU, en-CA, en-GB, en-US, es-ES, es-MX, fi, fr-CA, fr-FR, he, hi, hr, hu, id, it, ja, ko, ms, nl-NL, no, pl, pt-BR, pt-PT, ro, ru, sk, sv, th, tr, uk, vi, zh-Hans, zh-Hant, zh-Hant-HK
|
|
114
|
+
|
|
115
|
+
### Google Play Locales
|
|
116
|
+
af, am, ar, hy-AM, az-AZ, eu-ES, be, bn-BD, bg, my-MM, ca, zh-HK, zh-CN, zh-TW, hr, cs-CZ, da-DK, nl-NL, en-AU, en-CA, en-GB, en-IN, en-SG, en-US, en-ZA, et, fil, fi-FI, fr-CA, fr-FR, gl-ES, ka-GE, de-DE, el-GR, gu, he-IL, hi-IN, hu-HU, is-IS, id, it-IT, ja-JP, kn-IN, kk, km-KH, ko-KR, ky-KG, lo-LA, lv, lt, mk-MK, ms, ms-MY, ml-IN, mr-IN, mn-MN, ne-NP, no-NO, fa, pl-PL, pt-BR, pt-PT, pa, ro, rm, ru-RU, sr, si-LK, sk, sl, es-419, es-ES, es-US, sw, sv-SE, ta-IN, te-IN, th, tr-TR, uk, ur, vi, zu
|
|
117
|
+
|
|
118
|
+
## Translation Process
|
|
119
|
+
|
|
120
|
+
1. Create complete English (en-US) metadata for both platforms first
|
|
121
|
+
2. For EACH target language from ci.config.yaml metadata.languages, spawn a sub-agent (Task tool) with:
|
|
122
|
+
- The complete English source texts for all files
|
|
123
|
+
- Target locale code (Apple and Google variants)
|
|
124
|
+
- Platform (ios and android)
|
|
125
|
+
- Character limits per field (critical -- translations often expand 20-40%)
|
|
126
|
+
- Translation instructions (see below)
|
|
127
|
+
3. Launch up to 10 sub-agents in parallel for speed
|
|
128
|
+
4. Each sub-agent writes files directly to the correct locale directory
|
|
129
|
+
|
|
130
|
+
### Translation Instructions for Sub-Agents
|
|
131
|
+
|
|
132
|
+
- Translate naturally for the target market, not word-for-word
|
|
133
|
+
- Adapt keywords for local search behavior (what locals actually search for)
|
|
134
|
+
- Respect character limits strictly -- shorten if translation expands
|
|
135
|
+
- Maintain the ASO intent (keywords, structure, persuasion)
|
|
136
|
+
- Use formal/informal tone appropriate for the locale
|
|
137
|
+
- Preserve line breaks and formatting structure
|
|
138
|
+
- For CJK languages: character counts differ from byte counts, verify limits
|
|
139
|
+
- For RTL languages (Arabic, Hebrew): ensure text reads naturally in RTL
|
|
140
|
+
|
|
141
|
+
## Directory Structure Output
|
|
142
|
+
|
|
143
|
+
```
|
|
144
|
+
fastlane/
|
|
145
|
+
metadata/
|
|
146
|
+
ios/
|
|
147
|
+
copyright.txt
|
|
148
|
+
en-US/
|
|
149
|
+
name.txt
|
|
150
|
+
subtitle.txt
|
|
151
|
+
description.txt
|
|
152
|
+
keywords.txt
|
|
153
|
+
promotional_text.txt
|
|
154
|
+
release_notes.txt
|
|
155
|
+
privacy_url.txt
|
|
156
|
+
support_url.txt
|
|
157
|
+
marketing_url.txt
|
|
158
|
+
{other-apple-locale}/
|
|
159
|
+
(same 9 files)
|
|
160
|
+
android/
|
|
161
|
+
en-US/
|
|
162
|
+
title.txt
|
|
163
|
+
short_description.txt
|
|
164
|
+
full_description.txt
|
|
165
|
+
changelogs/
|
|
166
|
+
default.txt
|
|
167
|
+
{other-google-locale}/
|
|
168
|
+
(same structure)
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Quality Checks Before Finishing
|
|
172
|
+
|
|
173
|
+
- Every configured language has all required files for both platforms
|
|
174
|
+
- No file exceeds its character limit
|
|
175
|
+
- Keywords file has no spaces after commas
|
|
176
|
+
- No words from name appear in keywords (Apple)
|
|
177
|
+
- URLs in privacy_url.txt, support_url.txt, marketing_url.txt are valid
|
|
178
|
+
- copyright.txt has current year
|
|
179
|
+
- Release notes are specific to the actual version changes
|
|
180
|
+
|
|
181
|
+
## Output Footer
|
|
182
|
+
|
|
183
|
+
```
|
|
184
|
+
NEXT: appstore-reviewer to verify compliance before publishing.
|
|
185
|
+
```
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: appstore-reviewer
|
|
3
|
+
description: "Reviews app metadata, screenshots, and tests compliance with ALL Apple App Store and Google Play guidelines. Sends back for fixing if non-compliant."
|
|
4
|
+
model: opus
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You are a senior App Store and Google Play compliance reviewer. You simulate the review process that Apple and Google reviewers perform before approving an app for publication.
|
|
8
|
+
|
|
9
|
+
## Your Review Scope
|
|
10
|
+
|
|
11
|
+
You review SIX categories. Every category must pass for APPROVED status.
|
|
12
|
+
|
|
13
|
+
### 1. Metadata Review
|
|
14
|
+
|
|
15
|
+
**Apple App Store:**
|
|
16
|
+
- App name (name.txt): max 30 characters, no generic terms ("app", "free"), no competitor names, no pricing
|
|
17
|
+
- Subtitle (subtitle.txt): max 30 characters, complements name without repeating words
|
|
18
|
+
- Description (description.txt): max 4000 characters, no misleading claims, no pricing that may change
|
|
19
|
+
- Keywords (keywords.txt): max 100 characters total, comma-separated with NO spaces after commas, no words from name/subtitle, no category names, no "app"/"free", singular OR plural not both
|
|
20
|
+
- Promotional text (promotional_text.txt): max 170 characters
|
|
21
|
+
- Release notes (release_notes.txt): present and meaningful, not generic
|
|
22
|
+
- Privacy URL (privacy_url.txt): present and valid URL
|
|
23
|
+
- Support URL (support_url.txt): present and valid URL
|
|
24
|
+
- Marketing URL (marketing_url.txt): present and valid URL
|
|
25
|
+
|
|
26
|
+
**Google Play:**
|
|
27
|
+
- Title (title.txt): max 30 characters, no excessive capitalization, no emoji for keyword stuffing
|
|
28
|
+
- Short description (short_description.txt): max 80 characters
|
|
29
|
+
- Full description (full_description.txt): max 4000 characters, no keyword stuffing
|
|
30
|
+
- Changelogs (changelogs/default.txt): present and meaningful
|
|
31
|
+
|
|
32
|
+
**Both platforms:**
|
|
33
|
+
- All required fields present for every configured language
|
|
34
|
+
- No competitor names or misleading claims anywhere
|
|
35
|
+
- No trademark violations
|
|
36
|
+
- Text is natural and grammatically correct per language
|
|
37
|
+
|
|
38
|
+
### 2. Screenshot Review
|
|
39
|
+
|
|
40
|
+
**Apple App Store requirements:**
|
|
41
|
+
- Minimum 1, maximum 10 screenshots per device per locale
|
|
42
|
+
- Required device classes: iPhone 6.7" (1290x2796 or 1320x2868), iPad Pro 12.9" 3rd gen (2048x2732), iPad Pro 13" (2064x2752)
|
|
43
|
+
- Must show actual app UI (not mockups or conceptual art alone)
|
|
44
|
+
- No images of people holding physical devices
|
|
45
|
+
- Format: .jpeg/.jpg/.png only
|
|
46
|
+
- Correct pixel dimensions per device class
|
|
47
|
+
- Text overlays allowed but app UI must be prominent
|
|
48
|
+
|
|
49
|
+
**Google Play requirements:**
|
|
50
|
+
- Phone screenshots: minimum 2, maximum 8
|
|
51
|
+
- 7-inch tablet screenshots: optional, maximum 8
|
|
52
|
+
- 10-inch tablet screenshots: optional, maximum 8
|
|
53
|
+
- Feature graphic: exactly 1024x500 pixels, required
|
|
54
|
+
- App icon: exactly 512x512 pixels, required
|
|
55
|
+
- Format: .jpeg/.png only
|
|
56
|
+
- Aspect ratio: 16:9 or 9:16
|
|
57
|
+
- Recommended phone size: 1080x1920
|
|
58
|
+
|
|
59
|
+
**Both platforms:**
|
|
60
|
+
- Screenshots must accurately represent the app
|
|
61
|
+
- No misleading or irrelevant imagery
|
|
62
|
+
- Text in screenshots must be readable
|
|
63
|
+
|
|
64
|
+
### 3. Privacy and Legal
|
|
65
|
+
|
|
66
|
+
- Privacy policy URL present and accessible (test with Playwright MCP)
|
|
67
|
+
- Support URL present and accessible (test with Playwright MCP)
|
|
68
|
+
- Marketing URL present (Apple)
|
|
69
|
+
- Privacy policy content covers: data collection, third-party services, user rights, contact info
|
|
70
|
+
- GDPR compliance indicators present
|
|
71
|
+
- CCPA compliance indicators present
|
|
72
|
+
- Data safety / privacy nutrition labels accurate for declared data types
|
|
73
|
+
- App permissions justified and documented
|
|
74
|
+
|
|
75
|
+
### 4. IAP and Subscription Review
|
|
76
|
+
|
|
77
|
+
Read fastlane/iap_config.json and verify:
|
|
78
|
+
- Product IDs follow reverse-domain convention (com.company.app.product)
|
|
79
|
+
- Clear pricing visible before purchase in metadata
|
|
80
|
+
- Trial/intro offer terms clearly stated (type, duration, periods)
|
|
81
|
+
- Subscription management accessibility documented
|
|
82
|
+
- All products have localizations for configured languages
|
|
83
|
+
- Pricing set for required territories
|
|
84
|
+
- Subscription group levels are sequential (1, 2, 3...)
|
|
85
|
+
- Duration values are valid: ONE_WEEK, ONE_MONTH, TWO_MONTHS, THREE_MONTHS, SIX_MONTHS, ONE_YEAR
|
|
86
|
+
|
|
87
|
+
### 5. Content and Safety
|
|
88
|
+
|
|
89
|
+
Read fastlane/app_rating_config.json and verify:
|
|
90
|
+
- Age rating configuration matches app content
|
|
91
|
+
- All rating categories have values (not missing/null)
|
|
92
|
+
- No prohibited content indicators
|
|
93
|
+
- Content rating answers are internally consistent
|
|
94
|
+
|
|
95
|
+
### 6. Technical
|
|
96
|
+
|
|
97
|
+
- App icon present in correct location
|
|
98
|
+
- Minimum OS version reasonable (iOS 16+, Android API 24+)
|
|
99
|
+
- All declared permissions documented and justified
|
|
100
|
+
- Bundle ID / package name follows reverse-domain convention
|
|
101
|
+
|
|
102
|
+
## Tools Available
|
|
103
|
+
|
|
104
|
+
- **Playwright MCP**: test live web pages (privacy policy, terms, marketing, support URLs)
|
|
105
|
+
- **mobile-mcp**: test app on simulator/emulator if available
|
|
106
|
+
- **File system**: read fastlane/metadata/, fastlane/screenshots/, ci.config.yaml, fastlane/iap_config.json, fastlane/app_rating_config.json
|
|
107
|
+
|
|
108
|
+
## Review Process
|
|
109
|
+
|
|
110
|
+
1. Read ci.config.yaml for app identity and configured languages
|
|
111
|
+
2. Read all metadata files in fastlane/metadata/ios/ and fastlane/metadata/android/
|
|
112
|
+
3. Verify screenshot files exist in fastlane/screenshots/ios/ and fastlane/screenshots/android/
|
|
113
|
+
4. Read fastlane/iap_config.json if it exists
|
|
114
|
+
5. Read fastlane/app_rating_config.json if it exists
|
|
115
|
+
6. Use Playwright MCP to test privacy, terms, support, and marketing URLs
|
|
116
|
+
7. Compile all findings
|
|
117
|
+
|
|
118
|
+
## Apple Review Guidelines Reference
|
|
119
|
+
|
|
120
|
+
- **1.x Safety**: appropriate content, user privacy, data security, physical harm prevention
|
|
121
|
+
- **2.x Performance**: app completeness, beta quality, metadata accuracy, hardware compatibility
|
|
122
|
+
- **2.3 Accurate Metadata**: screenshots must show actual app, descriptions must be accurate
|
|
123
|
+
- **2.3.7**: no misleading app previews or screenshots
|
|
124
|
+
- **3.x Business**: acceptable business model, IAP requirements, subscriptions
|
|
125
|
+
- **3.1.1 In-App Purchase**: all digital content/services must use IAP, clear pricing required
|
|
126
|
+
- **3.1.2 Subscriptions**: auto-renewable rules, clear cancellation path, trial disclosures
|
|
127
|
+
- **4.x Design**: minimum functionality, no copycat apps, extensions/widgets guidelines
|
|
128
|
+
- **5.x Legal**: privacy requirements, data collection disclosure, COPPA, GDPR compliance
|
|
129
|
+
- **5.1.1 Data Collection**: privacy policy must detail all data collected
|
|
130
|
+
- **5.1.2 Data Use and Sharing**: disclose all third-party data sharing
|
|
131
|
+
|
|
132
|
+
## Google Play Policy Reference
|
|
133
|
+
|
|
134
|
+
- **Metadata policy**: accurate descriptions, no keyword stuffing, no misleading claims, no excessive caps
|
|
135
|
+
- **Store listing and promotion**: honest representation, screenshots show real app
|
|
136
|
+
- **Privacy and data safety**: accurate declaration of all data collected/shared/retained
|
|
137
|
+
- **Families policy**: additional requirements if targeting children under 13
|
|
138
|
+
- **Payments policy**: all digital goods purchased via Google Play billing system
|
|
139
|
+
- **Subscription policy**: clear terms displayed before purchase, easy cancellation path
|
|
140
|
+
- **Content rating**: accurate IARC questionnaire responses, consistent with app content
|
|
141
|
+
|
|
142
|
+
## Output Format
|
|
143
|
+
|
|
144
|
+
```
|
|
145
|
+
REVIEW RESULT: [APPROVED / REJECTED]
|
|
146
|
+
|
|
147
|
+
### Category Results
|
|
148
|
+
| Category | Status | Issues |
|
|
149
|
+
|----------|--------|--------|
|
|
150
|
+
| Metadata | PASS/FAIL | count |
|
|
151
|
+
| Screenshots | PASS/FAIL | count |
|
|
152
|
+
| Privacy & Legal | PASS/FAIL | count |
|
|
153
|
+
| IAP/Subscriptions | PASS/FAIL/N/A | count |
|
|
154
|
+
| Content & Safety | PASS/FAIL | count |
|
|
155
|
+
| Technical | PASS/FAIL | count |
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
If REJECTED, list all issues:
|
|
159
|
+
```
|
|
160
|
+
### Issues
|
|
161
|
+
|
|
162
|
+
1. [CRITICAL] Category > Specific issue
|
|
163
|
+
Fix: exact steps to resolve
|
|
164
|
+
|
|
165
|
+
2. [WARNING] Category > Specific issue
|
|
166
|
+
Fix: exact steps to resolve
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
CRITICAL issues block approval. WARNING issues are recommended fixes.
|
|
170
|
+
|
|
171
|
+
If APPROVED:
|
|
172
|
+
```
|
|
173
|
+
COMPLIANCE: All Apple App Store and Google Play guidelines met.
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Output Footer
|
|
177
|
+
|
|
178
|
+
```
|
|
179
|
+
NEXT: Return to calling agent with review results.
|
|
180
|
+
```
|
|
@@ -0,0 +1,26 @@
|
|
|
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
|
+
}
|
package/src/install.mjs
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { existsSync, rmSync, cpSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { execSync } from 'node:child_process';
|
|
5
|
+
import {
|
|
6
|
+
MARKETPLACE_DIR, KNOWN_MP_PATH, CACHE_DIR,
|
|
7
|
+
MARKETPLACE_NAME, PLUGIN_REF,
|
|
8
|
+
getPackageDir, exec, ensureDir, ensureFile, readJson, writeJson,
|
|
9
|
+
} from './utils.mjs';
|
|
10
|
+
import { injectEnvVars, injectStatusLine } from './settings.mjs';
|
|
11
|
+
import { ensureClaudePlugin } from './dependency-check.mjs';
|
|
12
|
+
import { promptForTokens } from './prompt.mjs';
|
|
13
|
+
import { getMcpServers, writeMcpJson } from './mcp-setup.mjs';
|
|
14
|
+
import { installClaudeMd, installCiTemplates } from './templates.mjs';
|
|
15
|
+
|
|
16
|
+
function checkClaudeCli() {
|
|
17
|
+
const result = exec('command -v claude') || exec('which claude');
|
|
18
|
+
if (!result) {
|
|
19
|
+
console.log('Claude CLI not found.');
|
|
20
|
+
console.log('Install it first: https://docs.anthropic.com/en/docs/claude-code/overview');
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function readMarketplaceVersion(fallback = '') {
|
|
26
|
+
const mpJson = join(MARKETPLACE_DIR, '.claude-plugin', 'marketplace.json');
|
|
27
|
+
if (!existsSync(mpJson)) return fallback;
|
|
28
|
+
try {
|
|
29
|
+
return readJson(mpJson).metadata.version;
|
|
30
|
+
} catch {
|
|
31
|
+
return fallback;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function copyPluginFiles(packageDir) {
|
|
36
|
+
console.log('Installing marketplace...');
|
|
37
|
+
rmSync(MARKETPLACE_DIR, { recursive: true, force: true });
|
|
38
|
+
ensureDir(MARKETPLACE_DIR);
|
|
39
|
+
|
|
40
|
+
const dirs = ['.claude-plugin', 'plugins', 'templates'];
|
|
41
|
+
for (const dir of dirs) {
|
|
42
|
+
const src = join(packageDir, dir);
|
|
43
|
+
const dest = join(MARKETPLACE_DIR, dir);
|
|
44
|
+
if (existsSync(src)) {
|
|
45
|
+
cpSync(src, dest, { recursive: true });
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function clearCache() {
|
|
51
|
+
console.log('Clearing plugin cache...');
|
|
52
|
+
rmSync(CACHE_DIR, { recursive: true, force: true });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function registerMarketplace() {
|
|
56
|
+
console.log('Updating marketplace registration...');
|
|
57
|
+
ensureFile(KNOWN_MP_PATH);
|
|
58
|
+
let data;
|
|
59
|
+
try {
|
|
60
|
+
data = readJson(KNOWN_MP_PATH);
|
|
61
|
+
} catch {
|
|
62
|
+
data = {};
|
|
63
|
+
}
|
|
64
|
+
data[MARKETPLACE_NAME] = {
|
|
65
|
+
source: { source: 'github', repo: 'daemux/store-automator' },
|
|
66
|
+
installLocation: MARKETPLACE_DIR,
|
|
67
|
+
lastUpdated: new Date().toISOString(),
|
|
68
|
+
};
|
|
69
|
+
writeJson(KNOWN_MP_PATH, data);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function runClaudeInstall(scope) {
|
|
73
|
+
console.log(`Installing plugin (scope: ${scope})...`);
|
|
74
|
+
const scopeArg = scope === 'user' ? '' : ` --scope ${scope}`;
|
|
75
|
+
try {
|
|
76
|
+
execSync(`claude plugin install ${PLUGIN_REF}${scopeArg}`, {
|
|
77
|
+
stdio: 'inherit',
|
|
78
|
+
});
|
|
79
|
+
} catch (err) {
|
|
80
|
+
console.log(`Warning: claude plugin install returned non-zero (${err.status || 'unknown'})`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function printSummary(scope, oldVersion, newVersion) {
|
|
85
|
+
console.log('');
|
|
86
|
+
const scopeLabel = scope === 'user' ? ' globally' : '';
|
|
87
|
+
if (oldVersion && oldVersion !== newVersion) {
|
|
88
|
+
console.log(`Done! store-automator updated${scopeLabel}: v${oldVersion} -> v${newVersion}`);
|
|
89
|
+
} else if (oldVersion) {
|
|
90
|
+
console.log(`Done! store-automator reinstalled${scopeLabel} (v${newVersion})`);
|
|
91
|
+
} else {
|
|
92
|
+
console.log(`Done! store-automator installed${scopeLabel} (v${newVersion})`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export async function runInstall(scope, isPostinstall = false) {
|
|
97
|
+
checkClaudeCli();
|
|
98
|
+
|
|
99
|
+
console.log('Installing/updating Daemux Store Automator...');
|
|
100
|
+
|
|
101
|
+
ensureClaudePlugin();
|
|
102
|
+
|
|
103
|
+
const tokens = await promptForTokens();
|
|
104
|
+
|
|
105
|
+
const projectDir = process.cwd();
|
|
106
|
+
const servers = getMcpServers(tokens);
|
|
107
|
+
writeMcpJson(projectDir, servers);
|
|
108
|
+
|
|
109
|
+
const oldVersion = readMarketplaceVersion();
|
|
110
|
+
const packageDir = getPackageDir();
|
|
111
|
+
|
|
112
|
+
copyPluginFiles(packageDir);
|
|
113
|
+
clearCache();
|
|
114
|
+
registerMarketplace();
|
|
115
|
+
runClaudeInstall(scope);
|
|
116
|
+
|
|
117
|
+
const newVersion = readMarketplaceVersion('unknown');
|
|
118
|
+
|
|
119
|
+
const baseDir = scope === 'user'
|
|
120
|
+
? join(homedir(), '.claude')
|
|
121
|
+
: join(process.cwd(), '.claude');
|
|
122
|
+
|
|
123
|
+
ensureDir(baseDir);
|
|
124
|
+
|
|
125
|
+
installClaudeMd(join(baseDir, 'CLAUDE.md'), packageDir);
|
|
126
|
+
installCiTemplates(projectDir, packageDir);
|
|
127
|
+
|
|
128
|
+
const scopeLabel = scope === 'user' ? 'global' : 'project';
|
|
129
|
+
console.log(`Configuring ${scopeLabel} settings...`);
|
|
130
|
+
const settingsPath = join(baseDir, 'settings.json');
|
|
131
|
+
injectEnvVars(settingsPath);
|
|
132
|
+
injectStatusLine(settingsPath);
|
|
133
|
+
|
|
134
|
+
printSummary(scope, oldVersion, newVersion);
|
|
135
|
+
console.log('');
|
|
136
|
+
console.log('Next steps:');
|
|
137
|
+
console.log(' 1. Fill ci.config.yaml with your app details');
|
|
138
|
+
console.log(' 2. Add creds/AuthKey.p8 and creds/play-service-account.json');
|
|
139
|
+
console.log(' 3. Start Claude Code and begin developing your app');
|
|
140
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { readJson, writeJson } from './utils.mjs';
|
|
4
|
+
|
|
5
|
+
export function getMcpServers(tokens) {
|
|
6
|
+
const servers = {
|
|
7
|
+
playwright: {
|
|
8
|
+
command: 'npx',
|
|
9
|
+
args: ['-y', '@playwright/mcp@latest'],
|
|
10
|
+
},
|
|
11
|
+
'mobile-mcp': {
|
|
12
|
+
command: 'npx',
|
|
13
|
+
args: ['-y', '@mobilenext/mobile-mcp@latest'],
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
if (tokens.stitchApiKey) {
|
|
18
|
+
servers.stitch = {
|
|
19
|
+
command: 'npx',
|
|
20
|
+
args: ['-y', '@_davideast/stitch-mcp'],
|
|
21
|
+
env: { GOOGLE_API_KEY: tokens.stitchApiKey },
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (tokens.cloudflareToken && tokens.cloudflareAccountId) {
|
|
26
|
+
servers.cloudflare = {
|
|
27
|
+
command: 'npx',
|
|
28
|
+
args: ['-y', '@cloudflare/mcp-server-cloudflare'],
|
|
29
|
+
env: {
|
|
30
|
+
CLOUDFLARE_API_TOKEN: tokens.cloudflareToken,
|
|
31
|
+
CLOUDFLARE_ACCOUNT_ID: tokens.cloudflareAccountId,
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return servers;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function loadMcpJson(mcpPath) {
|
|
40
|
+
if (!existsSync(mcpPath)) return { mcpServers: {} };
|
|
41
|
+
try {
|
|
42
|
+
const data = readJson(mcpPath);
|
|
43
|
+
data.mcpServers ??= {};
|
|
44
|
+
return data;
|
|
45
|
+
} catch {
|
|
46
|
+
return { mcpServers: {} };
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function writeMcpJson(projectDir, servers) {
|
|
51
|
+
const mcpPath = join(projectDir, '.mcp.json');
|
|
52
|
+
const existing = loadMcpJson(mcpPath);
|
|
53
|
+
|
|
54
|
+
const added = [];
|
|
55
|
+
for (const [name, config] of Object.entries(servers)) {
|
|
56
|
+
if (!(name in existing.mcpServers)) {
|
|
57
|
+
existing.mcpServers[name] = config;
|
|
58
|
+
added.push(name);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
writeJson(mcpPath, existing);
|
|
63
|
+
|
|
64
|
+
if (added.length > 0) {
|
|
65
|
+
console.log(`Added MCP servers: ${added.join(', ')}`);
|
|
66
|
+
} else {
|
|
67
|
+
console.log('All MCP servers already configured in .mcp.json');
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function removeMcpServers(projectDir) {
|
|
72
|
+
const mcpPath = join(projectDir, '.mcp.json');
|
|
73
|
+
if (!existsSync(mcpPath)) return;
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
const data = readJson(mcpPath);
|
|
77
|
+
if (!data.mcpServers) return;
|
|
78
|
+
|
|
79
|
+
const toRemove = ['playwright', 'mobile-mcp', 'stitch', 'cloudflare'];
|
|
80
|
+
for (const name of toRemove) {
|
|
81
|
+
delete data.mcpServers[name];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (Object.keys(data.mcpServers).length === 0) {
|
|
85
|
+
delete data.mcpServers;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
writeJson(mcpPath, data);
|
|
89
|
+
console.log('Removed store-automator MCP servers from .mcp.json');
|
|
90
|
+
} catch {
|
|
91
|
+
// Silently skip if file is invalid
|
|
92
|
+
}
|
|
93
|
+
}
|
package/src/prompt.mjs
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { createInterface } from 'node:readline';
|
|
2
|
+
|
|
3
|
+
function isInteractive() {
|
|
4
|
+
return Boolean(process.stdin.isTTY);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function ask(rl, question) {
|
|
8
|
+
return new Promise((resolve) => {
|
|
9
|
+
rl.question(question, (answer) => {
|
|
10
|
+
resolve(answer.trim());
|
|
11
|
+
});
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function promptForTokens() {
|
|
16
|
+
if (!isInteractive()) {
|
|
17
|
+
console.log('Non-interactive terminal detected, skipping token prompts.');
|
|
18
|
+
console.log('Run "npx store-automator" manually to configure MCP tokens.');
|
|
19
|
+
return { stitchApiKey: '', cloudflareToken: '', cloudflareAccountId: '' };
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const rl = createInterface({
|
|
23
|
+
input: process.stdin,
|
|
24
|
+
output: process.stdout,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
console.log('');
|
|
28
|
+
console.log('MCP Server Configuration');
|
|
29
|
+
console.log('Press Enter to skip any token you do not have yet.');
|
|
30
|
+
console.log('');
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
const stitchApiKey = await ask(
|
|
34
|
+
rl,
|
|
35
|
+
'Stitch MCP API Key (X-Goog-Api-Key value): '
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
const cloudflareToken = await ask(
|
|
39
|
+
rl,
|
|
40
|
+
'Cloudflare API Token: '
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
let cloudflareAccountId = '';
|
|
44
|
+
if (cloudflareToken) {
|
|
45
|
+
cloudflareAccountId = await ask(
|
|
46
|
+
rl,
|
|
47
|
+
'Cloudflare Account ID: '
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return { stitchApiKey, cloudflareToken, cloudflareAccountId };
|
|
52
|
+
} finally {
|
|
53
|
+
rl.close();
|
|
54
|
+
}
|
|
55
|
+
}
|