aiblueprint-cli 1.4.85 → 1.4.87

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 (20) hide show
  1. package/agents-config/skills/app-icon/SKILL.md +76 -97
  2. package/agents-config/skills/app-icon/references/example-character-mascot.webp +0 -0
  3. package/agents-config/skills/app-icon/references/example-symbol-object.webp +0 -0
  4. package/agents-config/skills/app-icon/references/inspiration-premium-icons.webp +0 -0
  5. package/agents-config/skills/appstore-connect/SKILL.md +7 -1
  6. package/agents-config/skills/appstore-connect/references/testflight.md +212 -0
  7. package/package.json +1 -1
  8. package/agents-config/skills/codex-environment/SKILL.md +0 -184
  9. package/agents-config/skills/ios-testflight/SKILL.md +0 -280
  10. /package/agents-config/skills/{prompt-creator → prompt-manager}/SKILL.md +0 -0
  11. /package/agents-config/skills/{prompt-creator → prompt-manager}/references/anthropic-best-practices.md +0 -0
  12. /package/agents-config/skills/{prompt-creator → prompt-manager}/references/anti-patterns.md +0 -0
  13. /package/agents-config/skills/{prompt-creator → prompt-manager}/references/clarity-principles.md +0 -0
  14. /package/agents-config/skills/{prompt-creator → prompt-manager}/references/context-management.md +0 -0
  15. /package/agents-config/skills/{prompt-creator → prompt-manager}/references/few-shot-patterns.md +0 -0
  16. /package/agents-config/skills/{prompt-creator → prompt-manager}/references/openai-best-practices.md +0 -0
  17. /package/agents-config/skills/{prompt-creator → prompt-manager}/references/prompt-templates.md +0 -0
  18. /package/agents-config/skills/{prompt-creator → prompt-manager}/references/reasoning-techniques.md +0 -0
  19. /package/agents-config/skills/{prompt-creator → prompt-manager}/references/system-prompt-patterns.md +0 -0
  20. /package/agents-config/skills/{prompt-creator → prompt-manager}/references/xml-structure.md +0 -0
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: app-icon
3
- description: Generate a polished iOS + Android app icon for an Expo / React Native app with AI image generation, then post-process it to App Store / Play specs. Use for "create my app icon", "generate the iOS icon", "redo the app logo", "make the launcher icon". Requires an image-generation tool (Codex imagegen / gpt-image); without one it builds the prompt for you to run, then resumes at post-processing.
3
+ description: Generate a premium, vibrant, dimensional iOS + Android app icon for an Expo / React Native app with AI image generation, then post-process it to App Store / Play specs. Use for "create my app icon", "generate the iOS icon", "redo the app logo", "make the launcher icon". Works with ANY image-generation tool (Codex `image_gen`, `gemini-cli` Nano Banana, gpt-image, …).
4
4
  argument-hint: "[app concept or brand]"
5
5
  ---
6
6
 
@@ -8,11 +8,25 @@ argument-hint: "[app concept or brand]"
8
8
 
9
9
  Generate a store-quality launcher icon (iOS App Store icon + Android adaptive icon + web favicon) for an Expo / React Native app, then post-process it to meet store requirements.
10
10
 
11
+ <aesthetic_target>
12
+ The target is a **premium, vibrant, dimensional** icon like top App Store featured apps — NOT a flat pictogram. See `references/` for the north star:
13
+
14
+ - `references/example-character-mascot.webp` — what this recipe produces for a character app (glossy 3D mascot + heart, full-bleed warm gradient).
15
+ - `references/example-symbol-object.webp` — what it produces for a symbol / monochrome brand (glossy 3D spark on a deep ink background — proof a one-color brand still gets a dimensional icon, not a flat glyph).
16
+ - `references/inspiration-premium-icons.webp` — external inspiration (soft glossy blob mascot on a vivid gradient tile).
17
+
18
+ The look every time: **one dimensional hero** (a soft glossy 3D character/mascot, or a bold symbolic object/abstract form) with smooth gradients, soft studio lighting, gentle rim light and a subtle glow, **centered on a full-bleed vivid branded background** that reaches all four edges. Saturated, high-contrast, polished, delightful.
19
+
20
+ **The #1 failure to avoid:** a flat monochrome pictogram / black-on-white glyph / line icon on a plain white background. If the output looks like an SVG symbol, it is wrong — regenerate.
21
+ </aesthetic_target>
22
+
11
23
  <capability_gate>
12
- **This workflow requires an image-generation tool** (Codex's `imagegen` skill backed by gpt-image, or any model/agent that can generate raster images).
24
+ **Use ANY image-generation tool you have.** Generate the icon yourself; do not just hand the prompt back if you can run a tool.
13
25
 
14
- - **Codex**: invoke the `imagegen` skill with the prompt below. Generated files land in `~/.codex/generated_images/<session-id>/ig_*.png` — copy them into the repo.
15
- - **Agent WITHOUT image generation (e.g. Claude Code without an image tool)**: do NOT fake it (no SVG-to-PNG pipelines that produces flat, amateur results). Instead: build the final prompt from the recipe below, give it to the user to run in their image tool of choice, then resume at the Post-Processing phase with the file they provide.
26
+ - **Codex** the built-in **`image_gen`** tool. Generate, then copy the file into the repo.
27
+ - **`gemini-cli` available** (the api2cli CLI `gemini-cli image generate`) use it directly; it is excellent for icons (exact command in `<generation_loop>`). One-time auth: `gemini-cli auth set <GOOGLE_API_KEY>`. (Install: `npx api2cli bundle gemini && npx api2cli link gemini`.)
28
+ - **Any other raster image tool** (gpt-image, an MCP image tool, Imagen, etc.) → use it with the prompt from `<icon_recipe>`.
29
+ - **Truly no image tool reachable** → do NOT fake it (no SVG-to-PNG pipelines — that produces flat, amateur results). Build the final prompt from `<icon_recipe>`, give it to the user to run, then resume at `<post_processing>` with the file they provide.
16
30
  </capability_gate>
17
31
 
18
32
  <locate_assets>
@@ -23,9 +37,7 @@ Find where this app's icon actually lives before writing anything. Read the Expo
23
37
  - Android adaptive background color → `expo.android.adaptiveIcon.backgroundColor`
24
38
  - Web favicon → `expo.web.favicon` (commonly `./assets/favicon.png`)
25
39
 
26
- In a monorepo the app may live under `mobile-app/` or `apps/<name>/` — resolve paths relative to the Expo project root, not the repo root. Below, `<ASSETS>` means that app's resolved assets directory.
27
-
28
- Pull the **brand colors** and **app concept/name** from the project (a brand/site config, the README, or just ask the user). Use color *names* in the prompt, never hex.
40
+ In a monorepo the app may live under `mobile-app/` or `apps/<name>/` — resolve paths relative to the Expo project root. Below, `<ASSETS>` means that app's resolved assets directory. Pull **brand colors** and **app concept/name** from the project (a brand/site config, the README, or ask the user). Use color *names* in the prompt, never hex.
29
41
  </locate_assets>
30
42
 
31
43
  <objective>
@@ -37,125 +49,92 @@ Pull the **brand colors** and **app concept/name** from the project (a brand/sit
37
49
  </objective>
38
50
 
39
51
  <icon_recipe>
40
- A layered prompt that avoids the common app-icon failure modes (baked rounded tiles, logo plates, accidental text, drop-shadow "floating card" look, unwanted mascots). Fill the two project blanks — **Subject** (the product's core concept/mascot/symbol) and the **Color palette** (named brand colors, never hex) — then send the entire block as one prompt to the image model.
41
-
42
- Counter-intuitive but critical: tell the model it is **NOT** making an app-icon tile. Models bake in rounded-square plates, outer margins, and drop shadows the moment they hear "app icon". You want a full-bleed square subject filling 92-98% of the canvas; iOS applies its own mask afterward.
52
+ Fill the two project blanks — **Subject** (the product's concept/mascot/symbol) and the **Color palette** (named brand colors, never hex) — then send the whole block as ONE prompt to your image tool.
43
53
 
44
54
  ```
45
- Create a 1024x1024 square symbol illustration.
46
-
47
- Subject: <single centered mark for <AppName> — the product's core concept/mascot/symbol, highly readable at small sizes>
48
-
49
- Context: standalone symbol/illustration for general use, not an app launcher icon or UI mockup.
50
- Do not design or imply an app icon, logo plate, badge, or rounded-square container, even if the words "app icon" or "logo" appear.
51
- Do not draw an icon inside a larger canvas. No outer margins, padding, or separate card background.
52
- Do not draw any rounded-square tile, card, or container behind the subject.
53
- The canvas itself is a perfect square with sharp 90° corners; do not simulate rounded-corner app icon masks or device-rounded corners.
54
- No global drop shadows, long cast shadows, outer glows, or halos around the subject or canvas.
55
- No UI mockups. No borders, frames, stickers, app plates, or device chrome.
56
- No text/typography (letters, numbers, monograms). No watermark.
57
- Not a full photo/portrait/real-world scene. No realistic human faces as the main subject.
58
- Do not copy or imitate real brand logos, trademarked shapes, or recognizable brand marks.
59
-
60
- Archetype (internal decision, do not mention in the output):
61
- Choose exactly ONE archetype: object_icon, abstract_form_icon, hybrid_icon, or character_icon. Characters are optional and must only be used when clearly appropriate; never the default.
62
- - object_icon: a single physical/symbolic object without a face (finance, productivity, utilities, dev tools, dashboards, system apps).
63
- - abstract_form_icon: pure form/metaphor without literal objects or faces (AI tools, design tools, analytics, experimental products).
64
- - hybrid_icon: an object with subtle life cues (no face), friendly but restrained (health, lifestyle).
65
- - character_icon: a friendly expressive character with a face (kids, games, beginner education, wellness, fun social).
66
-
67
- Concept:
68
- Design a single, intentional visual element that represents the app. Avoid generic logos and the most literal/obvious metaphor; choose a clear but slightly unexpected metaphor.
69
- Creativity means unusual material choices, unexpected-but-clear metaphors, expressive lighting, playful proportions, premium texture decisions. It does NOT mean always adding eyes/faces or always making it cute.
70
-
71
- Material:
72
- Default to an illustration-friendly matte finish (painted polymer, ceramic, paper, or flat vector). Avoid glass/chrome/neon unless explicitly requested. Material choice should communicate the product category.
73
-
74
- Composition:
75
- Main subject fills 92-98% of the canvas. Strong silhouette. No unnecessary elements.
76
-
77
- Lighting:
78
- Soft, controlled lighting. Minimal specular highlights. No bloom/glow/lens flares. No "3D glass icon" look.
79
-
80
- Overall feel:
81
- Modern, bold, subject-first illustration (not an app icon layout). Creative without being childish. Readable at small sizes. Clean illustration / 2D or 2.5D, matte finish, subtle shading only.
82
-
83
- Color palette: <brand colors as names — e.g. "warm peach background, deep ink subject">. Clean contrast on both light and dark backgrounds.
84
-
85
- Technical constraints:
86
- Square 1:1 aspect ratio.
87
- Main subject fills 92-98% of the canvas (zoom in; avoid excessive empty space).
88
- Center/balance the silhouette. Keep critical details within ~5-8% safe area.
89
- Android-safe: keep critical details within the central ~70% (silhouette may extend).
90
- Background extends to all four edges of the square canvas with straight (non-rounded) corners; keep it clean (low-detail, low-noise).
91
- Default-look guardrail: avoid inflated glass/chrome/neon/glow/sparkles/lens flare/exaggerated shine unless explicitly requested.
92
-
93
- Quality filters (internal):
94
- Reject if: it reads like a photo/portrait/full scene; it becomes a mascot by default; too many elements hurt clarity; a face appears without choosing character_icon; any rounded-square/card/tile background or app-icon container appears behind the subject.
95
- Accept if: instant read at small size; strong silhouette; intentional material; clean contrast on both light and dark backgrounds.
96
-
97
- Icon QA (internal): blur test (~64px), small-size readability, wallpaper contrast, one focal point.
98
- ```
55
+ Create a 1024x1024 square app icon — premium, vibrant and dimensional, in the style of top App Store featured apps.
99
56
 
100
- **Optional style preset.** Lock a look as a HARD constraint (it wins any conflict) by appending a dominant style block before the technical constraints. Useful named looks: `minimalism`, `glassy`, `geometric`, `gradient`, `flat`, `material`, `clay`, `kawaii`. Example for `minimalism`:
57
+ Subject: <single hero for <AppName> a friendly soft 3D character/mascot with simple dot eyes, OR a bold symbolic object/abstract form; instantly readable at small sizes>
101
58
 
102
- ```
103
- STYLE SYSTEM: MINIMALISM (dominant — if anything conflicts, the style wins)
104
- Cultural DNA: Swiss design, Apple, Braun, Dieter Rams, Functionalism.
105
- Visual traits: max 3 colors; simple primary silhouettes; large negative space; no textures; no effects.
106
- Mandatory: readable at very small sizes; works in monochrome; single dominant symbol.
107
- Forbidden: gradients, shadows, 3D effects, decorative details, textures.
59
+ The look (north star): one dimensional hero rendered in soft glossy 3D (or rich 2.5D) — smooth color gradients, soft studio lighting, gentle rim light, a subtle soft glow, rounded volumes and real depth — centered on a FULL-BLEED vivid branded background (solid color or smooth gradient) that reaches all four edges. Saturated, high-contrast, polished, delightful. Like a soft glossy jelly/clay form with tasteful highlights.
60
+
61
+ Background: fill the ENTIRE square edge-to-edge with a bold branded color or smooth gradient (e.g. a vivid blue gradient, or a deep dark backdrop for high contrast). Never plain white, never empty. The background is part of the icon.
62
+
63
+ Subject treatment: dimensional and glossy soft rounded 3D forms, smooth gradients, gentle highlights and rim light, a subtle inner glow, clear depth and volume. Rich saturated color. One strong, simple silhouette.
64
+
65
+ Composition: the hero centered, filling ~60-80% of the canvas with comfortable breathing room; the branded background fills the rest to the edges. One focal point, no clutter.
66
+
67
+ If the brand is monochrome (one ink + one accent), STILL make it fully dimensional and premium — glossy 3D form, tonal gradients within the brand color, soft lighting, a bold full-bleed background. Never a flat single-color glyph.
68
+
69
+ Do NOT: flat monochrome pictogram, black-on-white glyph, single-color line/silhouette icon, plain SVG/vector symbol, sticker or clip-art; plain white or empty background; a smaller rounded card/icon floating inside the canvas (no icon-in-icon, no outer margins); baked rounded app-icon corners or device-rounded corners (keep a full square with sharp 90 degree corners — iOS applies its own mask); any text, letters, numbers, monograms or watermark; realistic human faces or photos as the main subject; real or trademarked brand logos; mirror chrome, garish neon, or lens flares.
70
+
71
+ Color palette: <brand colors as names — e.g. "vivid blue gradient background, soft white-to-sky-blue glossy character with subtle highlights">. High saturation, strong contrast on the home screen.
72
+
73
+ Technical: square 1:1; background bleeds to all four edges with sharp corners; keep critical details within the central ~70% so an Android circular/rounded mask never clips them; punchy and readable at 60px (blur test).
108
74
  ```
109
75
 
110
- Non-negotiable for iOS (enforced in Post-Processing, not the prompt): the saved `icon.png` must have **no baked rounded corners** (iOS applies the mask), **no transparency** (App Store rejects alpha in the marketing icon), and stay **readable at 60px**.
76
+ **Archetype** (pick one, internal): a friendly **character/mascot** (simple dot eyes) is a strong default for consumer / social / wellness / AI apps; a **symbolic object or abstract form** fits finance, productivity, dev tools, utilities. Don't force a face onto a utility, and don't flatten a playful app into a glyph.
111
77
  </icon_recipe>
112
78
 
113
79
  <generation_loop>
114
- 1. Generate the icon (1-3 candidates if the concept is open).
115
- 2. **Visually inspect every output** (open/read the images): strong silhouette? readable at small size? no baked rounded-square tile / no drop-shadow card? no accidental text or watermark? no transparency artifacts?
116
- 3. Reject and regenerate what fails — refine the prompt, don't accept "almost".
117
- 4. **Safety-filter gotcha**: a prompt can be rejected for ambiguous wording with harmless content. Reword to simple, neutral, single-paragraph phrasing and retry.
118
- 5. **Blur test:** shrink to ~64px — if the mark turns to mush, the concept is too detailed; simplify the silhouette.
80
+ 1. **Generate** with your tool. With `gemini-cli` the validated command (Nano Banana Pro) is:
81
+ ```bash
82
+ PROMPT="$(cat <<'EOF'
83
+ <paste the filled icon_recipe prompt here>
84
+ EOF
85
+ )"
86
+ gemini-cli image generate --prompt "$PROMPT" \
87
+ --model gemini-3-pro-image-preview --aspect-ratio 1:1 --image-size 2K \
88
+ --out ./icon-raw.png --images-only --json
89
+ ```
90
+ (`gemini-3-pro-image-preview` = Nano Banana Pro, best for icons; `gemini-2.5-flash-image` is the cheaper fallback. Codex: use the `image_gen` tool with the same prompt.)
91
+ 2. **Visually inspect** every output: premium and dimensional (glossy 3D, depth)? full-bleed branded background (NOT white)? strong simple silhouette? readable at small size? NO flat-glyph look, NO accidental text/watermark?
92
+ 3. **Reject and regenerate** anything that drifts flat/monochrome/SVG-like or lands on a white background — tighten the prompt, don't accept "almost".
93
+ 4. **Safety-filter gotcha:** reword ambiguous prompts to a plain neutral paragraph and retry.
94
+ 5. **Blur test:** shrink to ~64px — if the mark turns to mush, simplify the silhouette.
119
95
  </generation_loop>
120
96
 
121
97
  <post_processing>
122
- macOS uses `sips` (built in) + ImageMagick (`magick`, `brew install imagemagick`). Replace `<ASSETS>` and `<brand-bg-color>` with the values from `<locate_assets>`.
98
+ Models often **bake rounded corners or a light border** around the tile. Scale up slightly and center-crop so the rounded ring falls off-canvas, leaving a true full-bleed square. `sips` is built into macOS; ImageMagick (`magick`, `brew install imagemagick`) is only needed for Android padding. Replace `<ASSETS>` and `<brand-bg-color>` with the values from `<locate_assets>`.
123
99
 
124
100
  ```bash
125
- # 1. iOS icon: enforce 1024x1024 and strip alpha (App Store requirement)
126
- sips -z 1024 1024 icon-raw.png --out /tmp/icon-1024.png
127
- magick /tmp/icon-1024.png -background "<brand-bg-color>" -alpha remove -alpha off <ASSETS>/icon.png
128
- # (no ImageMagick? `sips -s format jpeg /tmp/icon-1024.png --out /tmp/icon.jpg` then back to png also strips alpha)
101
+ # 1. Full-bleed fix: push any baked rounded corners / border off-canvas, then normalize to 1024.
102
+ sips -z 1126 1126 icon-raw.png --out /tmp/icon-up.png # scale up ~10%
103
+ sips -c 1024 1024 /tmp/icon-up.png --out /tmp/icon-fullbleed.png # center-crop to 1024 square
104
+ # (skip step 1 if the generated background already bleeds to sharp corners.)
105
+
106
+ # 2. iOS icon: enforce 1024 and guarantee NO transparency (App Store rejects alpha).
107
+ sips -s format png /tmp/icon-fullbleed.png --out <ASSETS>/icon.png
108
+ sips -g hasAlpha <ASSETS>/icon.png # must say: hasAlpha no
109
+ # If it reports alpha, flatten: magick in.png -background "<brand-bg-color>" -alpha remove -alpha off <ASSETS>/icon.png
129
110
 
130
- # 2. Android adaptive icon: pad the subject into the central ~66% safe circle on a brand background.
131
- # Scaling the full-bleed icon to ~66% places the SUBJECT inside the safe zone; the brand-bg extent fills the rest.
111
+ # 3. Android adaptive icon: pad the subject into the central ~66% safe circle on a brand background.
132
112
  magick <ASSETS>/icon.png -resize 66% -background "<brand-bg-color>" -gravity center -extent 1024x1024 <ASSETS>/adaptive-icon.png
133
- # Then make sure app config matches: expo.android.adaptiveIcon.backgroundColor == <brand-bg-color>.
134
- # (For a cleaner result you can instead re-run the recipe with "leave ~20% padding for Android adaptive safe zone".)
113
+ # Make app config match: expo.android.adaptiveIcon.backgroundColor == <brand-bg-color>.
114
+ # No magick? Re-run the recipe adding "leave ~20% padding for the Android adaptive safe zone".
135
115
 
136
- # 3. Favicon + verification
116
+ # 4. Favicon + final verification
137
117
  sips -z 192 192 <ASSETS>/icon.png --out <ASSETS>/favicon.png
138
118
  sips -g pixelWidth -g pixelHeight -g hasAlpha <ASSETS>/icon.png <ASSETS>/adaptive-icon.png <ASSETS>/favicon.png
139
- # icon.png MUST report hasAlpha: no
140
119
  ```
141
120
 
142
121
  Remember: **icon and adaptive-icon changes require a native rebuild** (`npx expo run:ios` / `npx expo run:android`, or a new EAS build) — they will NOT appear on hot reload. The favicon is a web asset and reloads normally.
143
122
  </post_processing>
144
123
 
145
124
  <render_verification>
146
- File inspection is not enough for the launcher icon — confirm it on a device/simulator after a native rebuild.
125
+ File inspection is not enough confirm the icon on a device/simulator after a native rebuild.
147
126
 
148
- 1. Rebuild the dev client so the new icon is bundled (`npx expo run:ios` or `npx expo run:android`).
149
- 2. On the home screen, confirm: the icon shows the new art (not the old/default), iOS applies a clean rounded mask with no double-rounding or visible alpha edge, and the mark is readable at home-screen size.
150
- 3. Fail the run if the icon still shows the previous art or a transparent/white corner halo. Fix before reporting completion.
127
+ 1. Rebuild the dev client (`npx expo run:ios` or `npx expo run:android`).
128
+ 2. On the home screen confirm: the new art shows (not the old/default), iOS applies a clean rounded mask with **no double-rounding and no white/transparent corner halo**, and the mark is readable at home-screen size.
129
+ 3. Fail the run if the icon still shows the previous art, a flat glyph, or a corner halo. Fix before reporting completion.
151
130
  </render_verification>
152
131
 
153
132
  <failure_modes>
154
- - Generated icon has transparency or baked rounded cornersApp Store rejection; strip alpha (`-alpha remove`), regenerate without any tile/mask.
155
- - iOS shows a white/transparent corner halo → alpha was not stripped; re-run the `magick ... -alpha remove -alpha off` step and rebuild.
156
- - Android adaptive icon looks cropped at the edges subject sat outside the central 66% safe circle; increase padding (lower the `-resize` percentage) or regenerate with explicit padding.
133
+ - **Flat monochrome glyph / black-on-white pictogram** (the most common, worst failure) the prompt leaned minimal/matte; re-emphasize "premium vibrant dimensional, glossy 3D, full-bleed branded background, NOT a flat SVG symbol", and regenerate.
134
+ - White/transparent corner halo after the iOS mask baked rounded corners; run the step-1 scale-up + center-crop, verify `hasAlpha no`, rebuild.
135
+ - Generated icon has transparency App Store rejection; flatten onto the brand bg (`magick ... -alpha remove -alpha off`).
136
+ - Android adaptive icon looks cropped at the edges → subject sat outside the central 66% safe circle; increase padding or regenerate with explicit padding.
157
137
  - Icon unchanged after editing the PNG → no native rebuild; hot reload never updates the launcher icon.
158
- - Flat/amateur output prompt missed the "subject-first illustration, not an app icon layout" + matte/material anchors, or fell back to a generic logo.
159
- - Prompt rejected by safety filter → reword neutrally, simplify to one plain paragraph; content was almost certainly fine.
138
+ - Prompt rejected by safety filter reword neutrally, simplify to one plain paragraph.
160
139
  - Mark turns to mush at small size → too detailed; simplify to a single strong silhouette (blur/64px test).
161
140
  </failure_modes>
@@ -1,12 +1,17 @@
1
1
  ---
2
2
  name: appstore-connect
3
- description: Interact with App Store Connect via the asc CLI - apps, builds, TestFlight, beta testers, reviews, sales/analytics, metadata, IAP, signing, submissions. Use for "check my app", "App Store Connect", "TestFlight status", "app review", "my app sales", or "asc".
3
+ description: Interact with App Store Connect via the asc CLI - apps, builds, TestFlight, beta testers, reviews, sales/analytics, metadata, IAP, signing, submissions. Also builds an Expo/React Native iOS app and ships it to TestFlight via "appstore-connect testflight". Use for "check my app", "App Store Connect", "TestFlight status", "ship to TestFlight", "app review", "my app sales", or "asc".
4
+ argument-hint: "[testflight [--expo] [setup]] | <natural-language ASC request>"
4
5
  ---
5
6
 
6
7
  # App Store Connect (asc)
7
8
 
8
9
  Read, manage, and ship any of the user's App Store apps through the **`asc`** CLI (App Store Connect CLI by Rork). `asc` covers nearly the entire ASC surface; reach for the raw API only for the rare gap.
9
10
 
11
+ <testflight_mode>
12
+ **If invoked as `appstore-connect testflight` (or "build and ship to TestFlight"):** this is a full build-and-upload workflow, not a single `asc` call. Read [references/testflight.md](references/testflight.md) and follow it end to end — it takes an Expo / React Native iOS app from local dev to an installable TestFlight build (ASC API-key signing to avoid Apple ID 2FA, `eas build --local` by default, `--expo` for a cloud build, then `asc publish testflight`). Pass `--expo` for a cloud build, or `testflight setup` to stop after credentials are ready. Everything below is the general `asc` surface for everyday read/manage tasks.
13
+ </testflight_mode>
14
+
10
15
  <auth>
11
16
  `asc` is usually already authenticated on this machine (a default keychain profile). Verify before doing real work:
12
17
 
@@ -40,6 +45,7 @@ asc docs list # embedded guides; `asc docs <topic>` to read one
40
45
  | List/inspect apps, app id | `asc apps`, `asc status --app <id>` |
41
46
  | Builds (processing, ids) | `asc builds` |
42
47
  | TestFlight: beta groups, testers, distribute a build | `asc testflight` |
48
+ | **Build an iOS app + ship to TestFlight (end to end)** | `appstore-connect testflight` → [references/testflight.md](references/testflight.md) |
43
49
  | Versions / release state | `asc versions`, `asc release`, `asc status` |
44
50
  | Metadata, localizations, keywords | `asc metadata`, `asc localizations` |
45
51
  | Screenshots / previews | `asc screenshots`, `asc video-previews` |
@@ -0,0 +1,212 @@
1
+ # TestFlight: build an iOS app and ship it to TestFlight
2
+
3
+ Loaded for `appstore-connect testflight`. Takes an Expo / React Native iOS app from local dev to an installable TestFlight build, fully non-interactively.
4
+
5
+ **Two key insights that make this automatable:**
6
+
7
+ 1. **ASC API key signing avoids Apple ID 2FA.** Interactive `eas build` credential setup needs Apple ID 2FA and cannot be scripted. Creating the distribution certificate + App Store provisioning profile through the App Store Connect API (`$SKILL_DIR/scripts/asc-api.mjs`) avoids 2FA entirely.
8
+ 2. **`eas build --local` avoids Expo cloud build credits.** Default to the local Mac build with `credentialsSource: "local"`; use `--expo` only when the user explicitly wants a cloud build.
9
+
10
+ `$SKILL_DIR` is the `appstore-connect` skill directory; `<APP_DIR>` is the Expo project root (often the repo root, or `mobile-app/` / `apps/<name>/` in a monorepo).
11
+
12
+ ## Arguments
13
+
14
+ - **default** (`appstore-connect testflight`): run setup → local build → upload → verify.
15
+ - **`--expo`**: use the EAS **cloud** build instead of local (consumes EAS build quota — confirm once).
16
+ - **setup-only** (`testflight setup`, "prepare credentials"): run phases A–D, then stop and report readiness.
17
+
18
+ ## Prerequisites (ask once, as a group — the agent cannot create these)
19
+
20
+ 1. **Apple Developer Program** membership (paid, enrolled).
21
+ 2. **App Store Connect API key**: the `AuthKey_<KEY_ID>.p8` file, its key ID, and the team issuer ID (App Store Connect → Users and Access → Integrations; needs Admin or App Manager). If unknown, follow [setup.md](setup.md).
22
+ 3. **Expo account** logged in: `npx eas-cli@latest whoami` (else `login`).
23
+ 4. **App record** in App Store Connect for the bundle ID. The public API **cannot** create app records — if missing, the user creates it once at appstoreconnect.apple.com (My Apps → + → New App). Verified in Phase D.
24
+ 5. **`asc` CLI** (`brew install asc`) — used only for the final upload.
25
+ 6. For the default local build: macOS with Xcode CLT + CocoaPods (`xcodebuild -version`, `command -v pod`). If missing, stop and ask whether to use `--expo`.
26
+
27
+ ## Critical safety
28
+
29
+ - Never commit or print `.p8` keys, `.p12` files, p12 passwords, provisioning profiles, or `credentials.json`. Verify `.gitignore` excludes them before any commit.
30
+ - Export ASC credentials as env vars for `asc-api.mjs`; never write them into repo files.
31
+ - Get explicit confirmation before: creating Apple certificates (low per-account limit), `--expo` cloud builds (consumes credits), and the TestFlight upload.
32
+ - Env vars are **baked in at build time** — confirm the production config points at prod backends before building, or testers ship with a dev backend.
33
+
34
+ ## Phase A — Preflight
35
+
36
+ ```bash
37
+ cd <APP_DIR>
38
+ npx tsc --noEmit && npm run lint # whatever checks the project has
39
+ npx expo-doctor # surface config problems early
40
+ npx eas-cli@latest whoami # Expo account logged in?
41
+ command -v asc && asc version
42
+ xcodebuild -version && command -v pod # required for default local builds
43
+ ```
44
+
45
+ Confirm the permanent values with the user: app `title`, `bundleId`, Apple `teamId`. The bundle ID is permanent once the app record exists.
46
+
47
+ Export ASC credentials for the rest of the session:
48
+
49
+ ```bash
50
+ export ASC_KEY_ID="<10-char key id>"
51
+ export ASC_ISSUER_ID="<issuer uuid>"
52
+ export ASC_P8_PATH="/path/to/AuthKey_<KEY_ID>.p8"
53
+ ```
54
+
55
+ ## Phase B — EAS project init
56
+
57
+ Skip if the project already has a real `eas.projectId` (a UUID).
58
+
59
+ ```bash
60
+ cd <APP_DIR>
61
+ npx eas-cli@latest project:init --non-interactive --force
62
+ npx eas-cli@latest project:info
63
+ ```
64
+
65
+ Write the printed project ID into the Expo config (`app.json`/`app.config.ts` → `extra.eas.projectId`, or your project config).
66
+
67
+ ## Phase C — Production env + eas.json
68
+
69
+ Point the build at production. Set every required production backend env var (auth secrets, API keys, URLs), then wire the prod values into `<APP_DIR>/eas.json → build.production`:
70
+
71
+ ```json
72
+ "production": {
73
+ "autoIncrement": true,
74
+ "credentialsSource": "local",
75
+ "env": {
76
+ "EXPO_PUBLIC_API_URL": "https://<prod-backend>"
77
+ }
78
+ }
79
+ ```
80
+
81
+ `credentialsSource: "local"` is what makes Phase E fully non-interactive.
82
+
83
+ ## Phase D — Apple signing credentials via the ASC API
84
+
85
+ All calls: `node "$SKILL_DIR/scripts/asc-api.mjs" <METHOD> <path> [body.json]` with the Phase A env vars. Work in a directory **outside** the repo (e.g. `~/ios-credentials/<slug>/`) so nothing secret can be committed.
86
+
87
+ **1. Resolve the app record (hard gate):**
88
+
89
+ ```bash
90
+ node "$SKILL_DIR/scripts/asc-api.mjs" GET "/v1/apps?filter[bundleId]=<bundle_id>"
91
+ ```
92
+
93
+ Empty `data` → STOP: tell the user to create the app record (My Apps → + → New App) with this exact bundle ID, then resume. Otherwise save `app_id = data[0].id`.
94
+
95
+ **2. Bundle ID registration + capabilities:**
96
+
97
+ ```bash
98
+ node "$SKILL_DIR/scripts/asc-api.mjs" GET "/v1/bundleIds?filter[identifier]=<bundle_id>"
99
+ # if missing: POST /v1/bundleIds {"data":{"type":"bundleIds","attributes":{"identifier":"<bundle_id>","name":"<title>","platform":"IOS"}}}
100
+ # enable capabilities the app uses (IN_APP_PURCHASE, APPLE_ID_AUTH, PUSH_NOTIFICATIONS) via POST /v1/bundleIdCapabilities
101
+ ```
102
+
103
+ **3. Distribution certificate** (Apple caps these at ~2–3 per account — check first):
104
+
105
+ ```bash
106
+ node "$SKILL_DIR/scripts/asc-api.mjs" GET "/v1/certificates?filter[certificateType]=DISTRIBUTION"
107
+ ```
108
+
109
+ Reuse only if the user has its private key/.p12 locally. Otherwise create one (with confirmation):
110
+
111
+ ```bash
112
+ openssl req -new -newkey rsa:2048 -nodes -keyout dist.key -out dist.csr \
113
+ -subj "/emailAddress=<user-email>/CN=<title> Distribution/C=US"
114
+ node -e "const fs=require('fs');fs.writeFileSync('cert-req.json',JSON.stringify({data:{type:'certificates',attributes:{certificateType:'DISTRIBUTION',csrContent:fs.readFileSync('dist.csr','utf8')}}}))"
115
+ node "$SKILL_DIR/scripts/asc-api.mjs" POST /v1/certificates cert-req.json
116
+ # save body.data.id (cert id) and body.data.attributes.certificateContent (base64) -> dist.cer
117
+ ```
118
+
119
+ **4. App Store provisioning profile:**
120
+
121
+ ```bash
122
+ node -e "const fs=require('fs');fs.writeFileSync('profile-req.json',JSON.stringify({data:{type:'profiles',attributes:{name:'<title> AppStore',profileType:'IOS_APP_STORE'},relationships:{bundleId:{data:{type:'bundleIds',id:'<BUNDLE_DB_ID>'}},certificates:{data:[{type:'certificates',id:'<CERT_ID>'}]},devices:{data:[]}}}}))"
123
+ node "$SKILL_DIR/scripts/asc-api.mjs" POST /v1/profiles profile-req.json
124
+ # save body.data.attributes.profileContent (base64) -> appstore.mobileprovision
125
+ ```
126
+
127
+ **5. Build the `.p12` (the `-legacy` flag is mandatory — Apple tooling rejects OpenSSL 3.x default ciphers):**
128
+
129
+ ```bash
130
+ echo "<certificateContent-b64>" | base64 -d > dist.cer
131
+ P12PASS=$(openssl rand -hex 12)
132
+ openssl x509 -inform der -in dist.cer -out dist.pem 2>/dev/null || cp dist.cer dist.pem
133
+ openssl pkcs12 -export -in dist.pem -inkey dist.key -out dist.p12 \
134
+ -name "<title> Distribution" -passout pass:"$P12PASS" -legacy
135
+ echo "<profileContent-b64>" | base64 -d > appstore.mobileprovision
136
+ ```
137
+
138
+ **6. Wire into the project (gitignored paths only):**
139
+
140
+ ```bash
141
+ mkdir -p <APP_DIR>/credentials
142
+ cp dist.p12 appstore.mobileprovision <APP_DIR>/credentials/
143
+ node -e "const fs=require('fs');fs.writeFileSync('<APP_DIR>/credentials.json',JSON.stringify({ios:{provisioningProfilePath:'credentials/appstore.mobileprovision',distributionCertificate:{path:'credentials/dist.p12',password:process.argv[1]}}},null,2))" "$P12PASS"
144
+ git check-ignore <APP_DIR>/credentials.json <APP_DIR>/credentials/dist.p12 # MUST print both
145
+ ```
146
+
147
+ **Setup-only mode STOPS here** — report readiness (app record, cert, profile, credentials.json, eas.json) and the next command.
148
+
149
+ ## Phase E — Local iOS build (default)
150
+
151
+ Uses the local Mac/Xcode toolchain; does **not** consume Expo cloud credits.
152
+
153
+ ```bash
154
+ cd <APP_DIR>
155
+ npx eas-cli@latest build --platform ios --profile production \
156
+ --local --non-interactive --output /tmp/<slug>.ipa
157
+ ```
158
+
159
+ If `expo-doctor` blocks on dependency patch mismatches, run `npx expo install --check`, apply the minimal patch updates, re-run `npx tsc --noEmit && npm run lint`, then rebuild. Don't blindly upgrade deps mid-release.
160
+
161
+ ## Phase E (--expo) — EAS cloud build
162
+
163
+ Only when the user passes `--expo` (confirm once — consumes EAS credits):
164
+
165
+ ```bash
166
+ cd <APP_DIR>
167
+ npx eas-cli@latest build --platform ios --profile production --non-interactive --no-wait
168
+ npx eas-cli@latest build:list --platform ios --limit 1 --json --non-interactive # poll to FINISHED/ERRORED
169
+ curl -sL -o /tmp/<slug>.ipa "<artifacts.buildUrl>" # download on FINISHED
170
+ ```
171
+
172
+ ## Phase F — TestFlight upload
173
+
174
+ **1. Internal beta group** (`asc publish testflight` fails without `--group`):
175
+
176
+ ```bash
177
+ node -e "const fs=require('fs');fs.writeFileSync('group-req.json',JSON.stringify({data:{type:'betaGroups',attributes:{name:'Internal',isInternalGroup:true,hasAccessToAllBuilds:true},relationships:{app:{data:{type:'apps',id:'<app_id>'}}}}}))"
178
+ node "$SKILL_DIR/scripts/asc-api.mjs" POST /v1/betaGroups group-req.json
179
+ # 409 "already exists" is fine — fetch the id: GET "/v1/apps/<app_id>/betaGroups"
180
+ ```
181
+
182
+ **2. Upload and wait** (`asc auth status --validate`, else `asc auth login` with the same ASC key):
183
+
184
+ ```bash
185
+ asc publish testflight --app "<app_id>" --ipa /tmp/<slug>.ipa --group "<BETA_GROUP_ID>" --wait --timeout 45m
186
+ ```
187
+
188
+ **3. Add testers** (internal testers must be ASC team members; external emails work via groups):
189
+
190
+ ```bash
191
+ node -e "const fs=require('fs');fs.writeFileSync('tester-req.json',JSON.stringify({data:{type:'betaTesters',attributes:{email:'<tester-email>'},relationships:{betaGroups:{data:[{type:'betaGroups',id:'<BETA_GROUP_ID>'}]}}}}))"
192
+ node "$SKILL_DIR/scripts/asc-api.mjs" POST /v1/betaTesters tester-req.json
193
+ ```
194
+
195
+ **4. Verify:** `asc status --app "<app_id>" --output table` shows the build processed and attached to the group. Report build number, group, and testers.
196
+
197
+ ## Failure modes
198
+
199
+ - **`.p12` rejected during signing** → exported without `-legacy`. Re-export with `-legacy`.
200
+ - **Certificate creation 409 / limit reached** → list existing DISTRIBUTION certs; ask which to revoke (`DELETE /v1/certificates/<id>`) or whether the user has its `.p12` to reuse. Never revoke without confirmation — it breaks other apps.
201
+ - **Local build fails before Xcode archive** → check `xcodebuild -version`, `xcode-select -p`, `command -v pod`. If unavailable and the user accepts cloud quota, rerun with `--expo`.
202
+ - **Build asks for credentials** → `credentialsSource: "local"` missing in `eas.json`, or `credentials.json` paths wrong (relative to `<APP_DIR>`).
203
+ - **`asc publish testflight` "--group is required"** → create the beta group first (Phase F step 1).
204
+ - **Upload rejected: missing export compliance** → answer the encryption question once in App Store Connect, or add `"ITSAppUsesNonExemptEncryption": false` to `infoPlist` in the Expo config.
205
+ - **App opens but can't reach backend** → built before `eas.json` had prod URLs, or prod env vars missing. Env is baked at build time — rebuild after fixing.
206
+ - **User insists on EAS-managed credentials (Apple ID login)** → that needs interactive 2FA; run `npx eas-cli@latest credentials` in a real terminal with the user present. Do not script the 2FA prompt.
207
+
208
+ ## Success metrics
209
+
210
+ - Default local build produces `/tmp/<slug>.ipa` with no interactive prompt; `--expo` cloud builds reach FINISHED.
211
+ - The build shows as processed in TestFlight, attached to a beta group with at least one tester.
212
+ - `git status` shows no credential files staged; nothing secret printed in the transcript.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aiblueprint-cli",
3
- "version": "1.4.85",
3
+ "version": "1.4.87",
4
4
  "description": "AIBlueprint CLI for setting up AI coding configurations",
5
5
  "author": "AIBlueprint",
6
6
  "license": "MIT",
@@ -1,184 +0,0 @@
1
- ---
2
- name: codex-environment
3
- description: Create, inspect, or repair Codex app Local Environment configuration for worktrees, including .codex/environments/environment.toml, setup and cleanup scripts, worktree up/down scripts, copied env files, isolated databases, and reusable app actions.
4
- disable-model-invocation: true
5
- allow_implicit_invocation: false
6
- ---
7
-
8
- # Codex Environment
9
-
10
- Use this skill when the user asks about Codex Local Environments, worktree setup scripts, cleanup scripts, Codex actions, `.codex/environments/environment.toml`, `CODEX_WORKTREE_PATH`, `CODEX_SOURCE_TREE_PATH`, or making a worktree ready to run.
11
-
12
- ## What We Learned
13
-
14
- - Codex app Local Environments are project config, not one-off terminal commands.
15
- - The shareable config lives at `.codex/environments/environment.toml` at the project root.
16
- - Setup scripts run when Codex creates a new worktree for a new thread.
17
- - Cleanup scripts run before Codex cleans up the worktree.
18
- - Actions appear in the Codex app top bar and run in the integrated terminal.
19
- - Worktrees only contain checked-in files, so ignored files like `.env` must be copied or recreated by setup.
20
- - Prefer short setup/cleanup entries that call versioned project scripts instead of long inline shell.
21
- - Keep executable entrypoints in `scripts/`, for example `scripts/worktree-up` and `scripts/worktree-down`.
22
- - Keep `.codex` focused on Codex config; keep operational logic in `scripts/`.
23
- - Do not commit secrets. It is fine for setup to copy ignored env files locally.
24
- - For projects with local databases, setup should create a worktree-specific DB name and rewrite the copied `.env` to point to it.
25
- - Cleanup should drop only the worktree-specific databases and remove copied local env files.
26
- - Use `CODEX_SOURCE_TREE_PATH` for the original project checkout and `CODEX_WORKTREE_PATH` for the new worktree when available.
27
-
28
- ## Preferred Shape
29
-
30
- Create or update:
31
-
32
- ```text
33
- .codex/
34
- └── environments/
35
- └── environment.toml
36
- scripts/
37
- ├── worktree-up
38
- ├── worktree-down
39
- ├── codex-dev.sh
40
- └── project-specific helpers...
41
- ```
42
-
43
- Use this `environment.toml` pattern:
44
-
45
- ```toml
46
- # THIS IS AUTOGENERATED. DO NOT EDIT MANUALLY
47
- version = 1
48
- name = "Project Worktree"
49
-
50
- [setup]
51
- script = "cd \"$CODEX_WORKTREE_PATH\"\nscripts/worktree-up"
52
-
53
- [cleanup]
54
- script = "cd \"$CODEX_WORKTREE_PATH\"\nscripts/worktree-down"
55
-
56
- [[actions]]
57
- name = "Dev"
58
- icon = "tool"
59
- command = "scripts/codex-dev.sh"
60
-
61
- [[actions]]
62
- name = "Typecheck"
63
- icon = "tool"
64
- command = "pnpm ts"
65
-
66
- [[actions]]
67
- name = "Unit tests"
68
- icon = "tool"
69
- command = "pnpm test:ci"
70
-
71
- [[actions]]
72
- name = "Lint"
73
- icon = "tool"
74
- command = "pnpm lint:ci"
75
- ```
76
-
77
- Keep setup script content small:
78
-
79
- ```bash
80
- #!/usr/bin/env bash
81
- set -euo pipefail
82
-
83
- cd "${CODEX_WORKTREE_PATH:-$(pwd)}"
84
- scripts/make-worktree-ready.sh
85
- ```
86
-
87
- Keep cleanup script content small:
88
-
89
- ```bash
90
- #!/usr/bin/env bash
91
- set -euo pipefail
92
-
93
- cd "${CODEX_WORKTREE_PATH:-$(pwd)}"
94
- scripts/worktree-db-down.sh
95
- ```
96
-
97
- ## Setup Script Rules
98
-
99
- When writing `scripts/worktree-up` or its helper:
100
-
101
- - Start with `set -euo pipefail`.
102
- - `cd "${CODEX_WORKTREE_PATH:-$(pwd)}"` before touching files.
103
- - Locate the source checkout with `CODEX_SOURCE_TREE_PATH`, then a project-specific fallback like `$HOME/Developer/saas/<repo>`.
104
- - Copy ignored env files from source to worktree, usually `.env`, `.env.local`, and `.env.development.local`.
105
- - Prefer project package manager conventions, for example `pnpm install`, never `npm install` in a pnpm repo.
106
- - Create a deterministic worktree DB name from the branch or target worktree, replacing `/` with `-`.
107
- - Rewrite only DB-related env values in the copied `.env`.
108
- - Run project generation steps needed after env setup, for example `pnpm prisma:generate`.
109
- - Make reruns idempotent. If the DB already exists, skip import unless a reset env var is set.
110
- - Add an explicit reset escape hatch such as `LUMAIL_RESET_DB=1`.
111
-
112
- ## Cleanup Script Rules
113
-
114
- When writing `scripts/worktree-down` or its helper:
115
-
116
- - Read DB names from the worktree `.env`, not from guessed branch names.
117
- - Drop only the DBs referenced by the worktree env.
118
- - Use `dropdb --if-exists` and a maintenance database.
119
- - Remove copied local env files from the worktree after DB cleanup.
120
- - If env files or PostgreSQL tools are missing, exit cleanly with a short message.
121
-
122
- ## Actions
123
-
124
- Use actions for commands the user will run often:
125
-
126
- - `Dev`: start the app, ideally via a script that finds a free port.
127
- - `Typecheck`: `pnpm ts`, `npm run typecheck`, or equivalent.
128
- - `Unit tests`: the project unit test command.
129
- - `Lint`: the CI lint command.
130
- - `E2E`: optional, only if it is not too expensive/noisy for a quick action.
131
-
132
- For dev servers, prefer a script like:
133
-
134
- ```bash
135
- #!/usr/bin/env bash
136
- set -euo pipefail
137
-
138
- cd "${CODEX_WORKTREE_PATH:-$(pwd)}"
139
-
140
- port="${CODEX_DEV_PORT_START:-3910}"
141
- while lsof -nP -iTCP:"$port" -sTCP:LISTEN >/dev/null 2>&1; do
142
- port=$((port + 1))
143
- done
144
-
145
- echo "Starting app on http://localhost:$port"
146
- exec pnpm dev -p "$port"
147
- ```
148
-
149
- ## Verification
150
-
151
- After editing a Codex environment:
152
-
153
- ```bash
154
- bash -n scripts/worktree-up scripts/worktree-down
155
- sed -n '1,220p' .codex/environments/environment.toml
156
- git status --short
157
- ```
158
-
159
- If Python 3.11+ is available, validate TOML:
160
-
161
- ```bash
162
- python3 - <<'PY'
163
- import tomllib
164
- with open(".codex/environments/environment.toml", "rb") as f:
165
- tomllib.load(f)
166
- PY
167
- ```
168
-
169
- ## Guardrails
170
-
171
- - Do not read or write `.conductor` directories.
172
- - Do not print full env values or secrets in logs; print only DB names or redacted hosts.
173
- - Do not create duplicate environments when the user already has one; update the existing `.codex/environments/environment.toml`.
174
- - Do not put large fragile shell programs directly in TOML strings.
175
- - Do not hardcode a dev port unless the user asks; pick a free port or allow `PORT`.
176
- - Do not drop databases unless their names come from the worktree env or from an explicit user-provided target.
177
- - If the environment is meant for future worktrees, ensure the `.codex` config and scripts are present in the source checkout/main branch, not only the current Codex worktree.
178
-
179
- ## Online References
180
-
181
- - OpenAI Codex Local Environments: https://developers.openai.com/codex/app/local-environments
182
- - OpenAI Codex Worktrees: https://developers.openai.com/codex/app/worktrees
183
- - `CODEX_SOURCE_TREE_PATH` / `CODEX_WORKTREE_PATH` community note: https://zenn.dev/route06/articles/c1f686b4479957
184
- - Codex app environment TOML practice note: https://zenn.dev/devneko/articles/8a5f6f44adf606
@@ -1,280 +0,0 @@
1
- ---
2
- name: ios-testflight
3
- description: Build a NowStack Mobile iOS app and upload it to TestFlight. Defaults to local `eas build --local`; use `--expo` only when the user explicitly wants an EAS cloud build.
4
- ---
5
-
6
- # iOS TestFlight - NowStack Mobile
7
-
8
- Take a working NowStack Mobile app from local dev to an installable TestFlight build. Run phases A-D for setup only (stop before the build, report readiness); run all phases end to end to build and upload. This is the iOS beta surface; for App Store review use `ns-ios-distribute`.
9
-
10
- Default build mode is **local Mac build** with `eas build --local`, so it does not consume Expo/EAS cloud build credits. If the user passes `--expo`, opt into the EAS cloud build path instead.
11
-
12
- <objective>
13
- Go from "the app works in the simulator" to "a build is installable from TestFlight" with maximum automation. Default to the local Mac build path to preserve Expo cloud credits; use `--expo` only when the user asks for a cloud build. Everything used here ships in this repo or is a public CLI (`eas-cli`, `asc`, `openssl`, `node`).
14
-
15
- The proven flow (battle-tested on a real app built from this boilerplate):
16
-
17
- 1. Create the EAS project and wire `easProjectId` into `site-config.ts`.
18
- 2. Deploy Convex to production and set prod env vars.
19
- 3. Point `eas.json` production env at the prod Convex URLs.
20
- 4. Create Apple signing credentials (distribution cert + App Store provisioning profile) through the **App Store Connect API** using `mobile-app/scripts/asc-api.mjs` — no browser, no Apple ID 2FA.
21
- 5. Run a local signed App Store IPA build with `eas build --local`, `credentialsSource: "local"`, and an explicit `--output /tmp/{slug}.ipa`.
22
- 6. Upload the `.ipa` to TestFlight with `asc publish testflight` into an internal beta group.
23
-
24
- The key insight: interactive `eas build` credential setup requires Apple ID 2FA and cannot be automated reliably. The ASC API key path avoids 2FA entirely, and local `eas build --local` avoids Expo cloud build credits while still using the same local signing files.
25
- </objective>
26
-
27
- <arguments>
28
- - Default / no flag: run setup, local build, upload, and verify.
29
- - `--expo`: use the EAS cloud build phase instead of local build. This consumes Expo/EAS build quota and requires explicit user confirmation.
30
- - Setup-only requests (`ios setup`, "prepare TestFlight", "credentials only"): run phases A-D, then stop before any build/upload.
31
- </arguments>
32
-
33
- <prerequisites>
34
- The user must have (ask once, as a group — these cannot be created by the agent):
35
-
36
- 1. **Apple Developer Program membership** (paid, enrolled).
37
- 2. **App Store Connect API key**: the `AuthKey_<KEY_ID>.p8` file, its key ID, and the team issuer ID. If unknown, follow the `appstore-connect` skill's setup workflow (`references/setup.md`), or point the user to App Store Connect > Users and Access > Integrations > App Store Connect API (key must have Admin or App Manager role).
38
- 3. **Expo account** logged in: `npx eas-cli@latest whoami` (else `npx eas-cli@latest login`).
39
- 4. **App record in App Store Connect** for the bundle ID. The public API cannot create app records — if missing, the user creates it once at appstoreconnect.apple.com (My Apps > + > New App, selecting the bundle ID). Verify with Phase D step 1 and stop with clear instructions if absent.
40
- 5. `asc` CLI installed (`brew install asc`) — used only for the final upload; everything else goes through `scripts/asc-api.mjs`.
41
- 6. For the default local build: macOS with Xcode command line tools and CocoaPods (`xcodebuild -version`, `command -v pod`). If those are unavailable, stop and ask whether to use `--expo`.
42
- </prerequisites>
43
-
44
- <state_variables>
45
- | Variable | Source |
46
- | --- | --- |
47
- | `{bundle_id}` | `SiteConfig.bundleId` |
48
- | `{apple_team_id}` | `SiteConfig.appleTeamId` |
49
- | `{eas_project_id}` | output of `eas project:init`, then written to `site-config.ts` |
50
- | `{asc_key_id}` / `{asc_issuer_id}` / `{asc_p8_path}` | from the user / the `appstore-connect` skill's `references/setup.md`. Never commit, never print. |
51
- | `{convex_prod_url}` / `{convex_prod_site_url}` | from `npx convex deploy` output / Convex dashboard |
52
- | `{asc_app_id}` | numeric app ID resolved from the bundle ID (Phase D) |
53
- | `{build_mode}` | default `local`; `expo` only when the user passes `--expo` |
54
- | `{ipa_path}` | `/tmp/{slug}.ipa` from local build, or downloaded cloud artifact |
55
- </state_variables>
56
-
57
- <critical_safety>
58
- - Never commit or print `.p8` keys, `.p12` files, p12 passwords, provisioning profiles, or `credentials.json`. The repo `.gitignore` already excludes them — verify before any commit.
59
- - Export ASC credentials as env vars for `scripts/asc-api.mjs`; do not write them into files inside the repo.
60
- - Get explicit user confirmation before: creating Apple certificates (accounts have a low cert limit), using `--expo` cloud EAS builds (consumes build credits), and the TestFlight upload.
61
- - Local builds do not consume Expo cloud credits, but they still sign a production App Store IPA and may increment the remote EAS build number when `appVersionSource` is `remote`.
62
- - Builds bake env vars in permanently: confirm `eas.json` points at the PROD Convex deployment before building, or testers ship with a dev backend.
63
- </critical_safety>
64
-
65
- <phase n="A" title="Preflight">
66
- From the repo root:
67
-
68
- ```bash
69
- npm run check-setup # must be free of errors (easProjectId warning is expected pre-init)
70
- cd mobile-app && npx tsc --noEmit && npm run lint
71
- npx eas-cli@latest whoami # Expo account logged in?
72
- command -v asc && asc version
73
- xcodebuild -version && command -v pod # required for default local builds
74
- ```
75
-
76
- Read `site-config.ts` and confirm with the user: `title`, `bundleId`, `appleTeamId`, payment product IDs. The bundle ID is permanent once the app record exists — it must be final now.
77
-
78
- Export the ASC credentials for the rest of the session:
79
-
80
- ```bash
81
- export ASC_KEY_ID="<10-char key id>"
82
- export ASC_ISSUER_ID="<issuer uuid>"
83
- export ASC_P8_PATH="/path/to/AuthKey_<KEY_ID>.p8"
84
- ```
85
- </phase>
86
-
87
- <phase n="B" title="EAS project init">
88
- Skip if `easProjectId` in `site-config.ts` is already a real UUID.
89
-
90
- ```bash
91
- cd mobile-app
92
- npx eas-cli@latest project:init --non-interactive --force
93
- npx eas-cli@latest project:info
94
- ```
95
-
96
- Write the printed project ID into `site-config.ts > easProjectId`. Re-run `npm run check-setup` — the easProjectId warning must be gone.
97
- </phase>
98
-
99
- <phase n="C" title="Convex production + eas.json">
100
- Follow `docs/production-checklist.md` stage 1. Summary:
101
-
102
- ```bash
103
- npx convex deploy -y
104
- # prod env starts EMPTY — set every required var:
105
- npx convex env set --prod SITE_URL "https://<the-app-domain>"
106
- npx convex env set --prod BETTER_AUTH_SECRET "$(openssl rand -hex 32)" # fresh, never reuse dev
107
- npx convex env set --prod RESEND_API_KEY "$(npx convex env get RESEND_API_KEY)"
108
- npx convex env set --prod EMAIL_FROM "$(npx convex env get EMAIL_FROM)"
109
- # if Apple Sign In is enabled: APPLE_CLIENT_ID = {bundle_id}, APPLE_CLIENT_SECRET copied from dev
110
- # if Google Sign In is enabled: GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET copied from dev
111
- npx convex env list --prod
112
- ```
113
-
114
- Then put the prod URLs into `mobile-app/eas.json > build.production`:
115
-
116
- ```json
117
- "production": {
118
- "autoIncrement": true,
119
- "credentialsSource": "local",
120
- "env": {
121
- "EXPO_PUBLIC_CONVEX_URL": "https://<prod-deployment>.convex.cloud",
122
- "EXPO_PUBLIC_CONVEX_SITE_URL": "https://<prod-deployment>.convex.site"
123
- }
124
- }
125
- ```
126
-
127
- `credentialsSource: "local"` is what makes the build fully non-interactive (Phase E).
128
- </phase>
129
-
130
- <phase n="D" title="Apple signing credentials via ASC API">
131
- All API calls use `node mobile-app/scripts/asc-api.mjs <METHOD> <path> [body.json]` with the env vars from Phase A. Work in a directory OUTSIDE the repo (e.g. `~/ios-credentials/<slug>/`) so nothing secret can be committed.
132
-
133
- **1. Resolve the app record (hard gate):**
134
-
135
- ```bash
136
- node mobile-app/scripts/asc-api.mjs GET "/v1/apps?filter[bundleId]={bundle_id}"
137
- ```
138
-
139
- Empty `data` → STOP. Tell the user to create the app record at appstoreconnect.apple.com (My Apps > + > New App) with this exact bundle ID, then resume. Otherwise save `{asc_app_id}` = `data[0].id`.
140
-
141
- **2. Bundle ID registration + capabilities:**
142
-
143
- ```bash
144
- node mobile-app/scripts/asc-api.mjs GET "/v1/bundleIds?filter[identifier]={bundle_id}"
145
- # if missing, register it:
146
- # POST /v1/bundleIds {"data":{"type":"bundleIds","attributes":{"identifier":"{bundle_id}","name":"{title}","platform":"IOS"}}}
147
- node mobile-app/scripts/asc-api.mjs GET "/v1/bundleIds/<BUNDLE_DB_ID>/bundleIdCapabilities"
148
- # enable missing capabilities the app uses (IN_APP_PURCHASE, APPLE_ID_AUTH, PUSH_NOTIFICATIONS):
149
- # POST /v1/bundleIdCapabilities {"data":{"type":"bundleIdCapabilities","attributes":{"capabilityType":"APPLE_ID_AUTH"},"relationships":{"bundleId":{"data":{"type":"bundleIds","id":"<BUNDLE_DB_ID>"}}}}}
150
- ```
151
-
152
- **3. Distribution certificate.** First check for an existing one (Apple caps distribution certs at ~2-3 per account):
153
-
154
- ```bash
155
- node mobile-app/scripts/asc-api.mjs GET "/v1/certificates?filter[certificateType]=DISTRIBUTION"
156
- ```
157
-
158
- Reuse only if the user has its private key/.p12 locally. Otherwise create a new one (with user confirmation):
159
-
160
- ```bash
161
- openssl req -new -newkey rsa:2048 -nodes -keyout dist.key -out dist.csr \
162
- -subj "/emailAddress=<user-email>/CN={title} Distribution/C=US"
163
- node -e "const fs=require('fs');fs.writeFileSync('cert-req.json',JSON.stringify({data:{type:'certificates',attributes:{certificateType:'DISTRIBUTION',csrContent:fs.readFileSync('dist.csr','utf8')}}}))"
164
- node mobile-app/scripts/asc-api.mjs POST /v1/certificates cert-req.json
165
- # save body.data.id (cert id) and body.data.attributes.certificateContent (base64) -> dist.cer
166
- ```
167
-
168
- **4. App Store provisioning profile:**
169
-
170
- ```bash
171
- node -e "const fs=require('fs');fs.writeFileSync('profile-req.json',JSON.stringify({data:{type:'profiles',attributes:{name:'{title} AppStore',profileType:'IOS_APP_STORE'},relationships:{bundleId:{data:{type:'bundleIds',id:'<BUNDLE_DB_ID>'}},certificates:{data:[{type:'certificates',id:'<CERT_ID>'}]},devices:{data:[]}}}}))"
172
- node mobile-app/scripts/asc-api.mjs POST /v1/profiles profile-req.json
173
- # save body.data.attributes.profileContent (base64) -> profile.mobileprovision
174
- ```
175
-
176
- **5. Build the .p12 (the `-legacy` flag is mandatory — Apple tooling rejects OpenSSL 3.x default ciphers):**
177
-
178
- ```bash
179
- echo "<certificateContent-b64>" | base64 -d > dist.cer
180
- P12PASS=$(openssl rand -hex 12)
181
- openssl x509 -inform der -in dist.cer -out dist.pem 2>/dev/null || cp dist.cer dist.pem
182
- openssl pkcs12 -export -in dist.pem -inkey dist.key -out dist.p12 \
183
- -name "{title} Distribution" -passout pass:"$P12PASS" -legacy
184
- echo "<profileContent-b64>" | base64 -d > appstore.mobileprovision
185
- ```
186
-
187
- **6. Wire into the project (gitignored paths only):**
188
-
189
- ```bash
190
- mkdir -p mobile-app/credentials
191
- cp dist.p12 mobile-app/credentials/dist.p12
192
- cp appstore.mobileprovision mobile-app/credentials/
193
- node -e "const fs=require('fs');fs.writeFileSync('mobile-app/credentials.json',JSON.stringify({ios:{provisioningProfilePath:'credentials/appstore.mobileprovision',distributionCertificate:{path:'credentials/dist.p12',password:process.argv[1]}}},null,2))" "$P12PASS"
194
- git check-ignore mobile-app/credentials.json mobile-app/credentials/dist.p12 # MUST print both paths
195
- ```
196
-
197
- Setup-only mode STOPS here — report readiness (app record, cert, profile, credentials.json, eas.json) and the next command.
198
- </phase>
199
-
200
- <phase n="E" title="Local iOS build (default)">
201
- Default path. This uses the local Mac/Xcode toolchain and does **not** consume Expo cloud build credits. It still uses EAS CLI for config/versioning and local credentials for signing.
202
-
203
- ```bash
204
- cd mobile-app
205
- npx eas-cli@latest build --platform ios --profile production \
206
- --local --non-interactive --output /tmp/{slug}.ipa
207
- ```
208
-
209
- If the build fails in `expo doctor` due to patch-version mismatches, inspect the exact error. Do not blindly upgrade dependencies during a release unless the build is blocked; if blocked, run `npx expo install --check`, apply the minimal Expo SDK patch updates, rerun `npx tsc --noEmit && npm run lint`, then rebuild.
210
-
211
- Local `autoIncrement` can skip build numbers if a canceled cloud build already reserved one. That is acceptable for App Store Connect.
212
- </phase>
213
-
214
- <phase n="E-expo" title="EAS cloud build (--expo only)">
215
- Run this phase only when the user passes `--expo`, and confirm once because it consumes Expo/EAS build credits:
216
-
217
- ```bash
218
- cd mobile-app
219
- npx eas-cli@latest build --platform ios --profile production --non-interactive --no-wait
220
- ```
221
-
222
- Poll until terminal state (FINISHED / ERRORED):
223
-
224
- ```bash
225
- npx eas-cli@latest build:list --platform ios --limit 1 --json --non-interactive
226
- # or: npx eas-cli@latest build:view <BUILD_ID> --json
227
- ```
228
-
229
- On FINISHED, download the artifact:
230
-
231
- ```bash
232
- curl -sL -o /tmp/{slug}.ipa "<artifacts.buildUrl>"
233
- ```
234
- </phase>
235
-
236
- <phase n="F" title="TestFlight upload">
237
- **1. Internal beta group** (TestFlight needs a group; `asc publish testflight` fails without `--group`):
238
-
239
- ```bash
240
- node -e "const fs=require('fs');fs.writeFileSync('group-req.json',JSON.stringify({data:{type:'betaGroups',attributes:{name:'Internal',isInternalGroup:true,hasAccessToAllBuilds:true},relationships:{app:{data:{type:'apps',id:'{asc_app_id}'}}}}}))"
241
- node mobile-app/scripts/asc-api.mjs POST /v1/betaGroups group-req.json
242
- # 409 "already exists" is fine — fetch the id: GET "/v1/apps/{asc_app_id}/betaGroups"
243
- ```
244
-
245
- **2. Upload and wait for processing** (asc must be authenticated — `asc auth status --validate`, else `asc auth login` with the same ASC key):
246
-
247
- ```bash
248
- asc publish testflight --app "{asc_app_id}" --ipa /tmp/{slug}.ipa \
249
- --group "<BETA_GROUP_ID>" --wait --timeout 45m
250
- ```
251
-
252
- **3. Add testers** (internal testers must be App Store Connect team members; external emails work via groups):
253
-
254
- ```bash
255
- node -e "const fs=require('fs');fs.writeFileSync('tester-req.json',JSON.stringify({data:{type:'betaTesters',attributes:{email:'<tester-email>'},relationships:{betaGroups:{data:[{type:'betaGroups',id:'<BETA_GROUP_ID>'}]}}}}))"
256
- node mobile-app/scripts/asc-api.mjs POST /v1/betaTesters tester-req.json
257
- ```
258
-
259
- **4. Verify**: `asc status --app "{asc_app_id}" --output table` shows the build processed and attached to the group. Report build number, group, and tester list to the user.
260
- </phase>
261
-
262
- <failure_modes>
263
- - **`.p12` rejected during signing** → it was exported without `-legacy`. Re-export: `openssl pkcs12 -in dist.p12 -nodes -out tmp.pem -passin pass:"$P12PASS" && openssl pkcs12 -export -in tmp.pem -out dist.p12 -passout pass:"$P12PASS" -legacy`.
264
- - **Certificate creation returns 409 / limit reached** → list existing DISTRIBUTION certs; ask the user which to revoke (`DELETE /v1/certificates/<id>`) or whether they have its .p12 to reuse. Never revoke without confirmation — it breaks other apps signed with it.
265
- - **Local build fails before Xcode archive** → verify macOS/Xcode/CocoaPods: `xcodebuild -version`, `xcode-select -p`, `command -v pod`. If unavailable and the user accepts cloud quota usage, rerun with `--expo`.
266
- - **Local build fails asking for credentials** → `credentialsSource: "local"` is missing in `eas.json`, or `credentials.json` paths are wrong (they are relative to `mobile-app/`).
267
- - **`--expo` cloud build fails asking for credentials** → same credential checks as local, then rerun cloud build.
268
- - **`asc publish testflight` fails with "--group is required"** → create the beta group first (Phase F step 1).
269
- - **Upload rejected: missing export compliance** → answer the encryption question in App Store Connect once, or add `"ITSAppUsesNonExemptEncryption": false` to `infoPlist` in `mobile-app/app.config.ts` so future builds skip it.
270
- - **App opens but can't reach backend** → build was made before `eas.json` had prod URLs, or prod Convex env vars are missing (`npx convex env list --prod`). Rebuild after fixing — env is baked at build time.
271
- - **User insists on EAS-managed credentials (Apple ID login)** → that flow requires interactive 2FA and cannot run with `--non-interactive`. Run `npx eas-cli@latest credentials` in a real terminal with the user present, then build. Do not attempt to script the 2FA prompt.
272
- </failure_modes>
273
-
274
- <success_metrics>
275
- - `npm run check-setup` passes with a real `easProjectId`.
276
- - Default local build produces `/tmp/{slug}.ipa` without any interactive prompt; `--expo` cloud builds reach FINISHED.
277
- - The build shows as processed in TestFlight, attached to a beta group with at least one tester.
278
- - The TestFlight app signs in and talks to the PROD Convex deployment.
279
- - `git status` shows no credential files staged; nothing secret printed in the transcript.
280
- </success_metrics>