AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Ios Store Listing

skill-jakeintech-claude-ios-skills-ios-store-listing · by Jakeintech

Generate and optimize App Store listing metadata — app name, subtitle, description, keywords, promotional text, What's New, categories. Uses 3 parallel subagents for marketing copy, brand voice, and ASO keyword research grounded in live App Store competitor data.

— No reviews yet
0 installs
27 views
0.0% view→install

Install

$ agentstack add skill-jakeintech-claude-ios-skills-ios-store-listing

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • ✓ Prompt-injection patterns
  • ✓ Secret / credential exfiltration
  • ✓ Dangerous shell & filesystem operations
  • ✓ Untrusted network calls
  • ✓ Known-malicious package signatures

What it can access

  • ✓ Network access No
  • ✓ Filesystem access No
  • ✓ Shell / process execution No
  • ✓ Environment & secrets No
  • ✓ Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-jakeintech-claude-ios-skills-ios-store-listing)

Reliability & compatibility

✓ Security review passed
0 installs to date
— no reviews yet
○ 5mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Ios Store Listing? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

App Store Listing

Generates optimized App Store listing metadata using parallel subagents:

  • marketing-copywriter — hook, description, promo text, what's-new
  • brand-voice — tone-checks and adjusts copywriter output against brand book
  • aso-specialist — live WebSearch competitor scan + keywords + title/subtitle variants

Synthesizes their outputs into appstore/listing.json.

Fields produced

| Field | Constraint | Source in synthesis | |-------|-----------|---------------------| | name | 30 chars | app-info.json.name (preserved; aso suggestions are advisory) | | subtitle | 30 chars | aso.subtitle_variants[0] or app-info.subtitle | | promotionalText | 170 chars | brand-voice.adjusted_promotionalText | | description | 4000 chars | brand-voice.adjusted_description | | keywords | 100 chars | aso.keywords | | whatsNew | 4000 chars | brand-voice.adjusted_whatsNew | | supportUrl | reachable URL | app-info.support_url | | marketingUrl | reachable URL | app-info.marketing_url | | copyright | auto | {year} {developer_name} | | primaryCategory | Apple ID | app-info.categories[0] | | secondaryCategory | Apple ID | app-info.categories[1] |

Process

1. Validate inputs

# Required files
for f in "docs/product-vision/00-product-bounds.md" "appstore/app-info.json" "CLAUDE.md"; do
  test -f "$f" || { echo "MISSING: $f"; exit 1; }
done
import json
info = json.load(open("appstore/app-info.json"))
assert info.get("app_id"), "app_id missing"
assert info.get("name"), "name missing"
# Support both flat keys (primaryCategory/secondaryCategory) and legacy categories[] array
primary_cat = info.get("primaryCategory") or (info.get("categories") or [None])[0]
assert primary_cat, "primaryCategory (or categories[0]) missing"

If app-info.json lacks marketing_url or support_url, warn the user: those fields in listing.json will be empty until /ios-site init runs.

2. Gather sources

brand_book=$(cat docs/product-vision/00-product-bounds.md)
claude_md=$(cat CLAUDE.md)
app_info=$(cat appstore/app-info.json)
existing_listing=$(cat appstore/listing.json 2>/dev/null || echo "{}")

# Git log since last tag (fallback: last 30 commits)
git_log=$(git log --oneline "$(git describe --tags --abbrev=0 2>/dev/null || echo HEAD~30)..HEAD")

3. Dispatch subagents in parallel

Make ONE message with two Agent tool calls (the marketing-copywriter → brand-voice is a serial chain; aso is independent).

Chain 1 (sequential): marketing-copywriter → brand-voice. Prompt the Agent tool: "You are running marketing-copywriter per skills/ios-store-listing/agents/marketing-copywriter.md. Here are inputs... Then take your output and run brand-voice per skills/ios-store-listing/agents/brand-voice.md with the brand book. Return final brand-voice JSON."

Chain 2 (independent): aso-specialist with WebSearch access per skills/ios-store-listing/agents/aso-specialist.md.

Both chains run in parallel (one message, two Agent calls). Each chain is one Agent dispatch.

Agent inputs should include the file contents literally (not paths) so subagents don't need filesystem access.

4. Wait and parse

  • If any chain fails to return valid JSON, retry that chain ONCE with a stricter schema prompt.
  • Second failure → abort, print raw output, exit non-zero. Never fall back to heuristic synthesis.

5. Synthesis (pure logic)

import json, datetime

def truncate_at_sentence(s, limit):
    if len(s) <= limit:
        return s, False
    cut = s[:limit].rsplit(".", 1)[0] + "."
    if len(cut) < limit * 0.5:  # sentence boundary too far back
        cut = s[:limit-1].rstrip() + "…"
    return cut, True

def synthesize(copy, aso, app_info, year=2026, developer="Jake Williams"):
    warnings = []
    truncations = []

    # Pull adjusted copy from brand-voice
    desc, t = truncate_at_sentence(copy["adjusted_description"], 4000)
    if t: truncations.append(("description", len(copy["adjusted_description"]), 4000))
    promo, t = truncate_at_sentence(copy["adjusted_promotionalText"], 170)
    if t: truncations.append(("promotionalText", len(copy["adjusted_promotionalText"]), 170))
    whats_new, t = truncate_at_sentence(copy["adjusted_whatsNew"], 4000)
    if t: truncations.append(("whatsNew", len(copy["adjusted_whatsNew"]), 4000))

    # ASO outputs
    keywords = aso["keywords"][:100]  # hard clip
    if aso["competitor_scan"] == "failed":
        warnings.append(f"⚠ ASO scan failed ({aso['competitor_scan_reason']}) — keywords from brand book only")

    # Subtitle: aso variant if valid, else fall back
    subtitle = aso["subtitle_variants"][0] if aso.get("subtitle_variants") and len(aso["subtitle_variants"][0]) <= 30 else app_info.get("subtitle", "")

    # Support both flat keys and legacy array; flat keys are canonical going forward
    primary_cat = app_info.get("primaryCategory") or (app_info.get("categories") or [None])[0]
    secondary_cat = app_info.get("secondaryCategory") or (app_info.get("categories") or [None, None])[1]

    listing = {
        "name": app_info["name"],  # never auto-rename
        "subtitle": subtitle,
        "promotionalText": promo,
        "description": desc,
        "keywords": keywords,
        "whatsNew": whats_new,
        "supportUrl": app_info.get("support_url", ""),
        "marketingUrl": app_info.get("marketing_url", ""),
        "copyright": f"{year} {developer}",
        "primaryCategory": primary_cat,
        "secondaryCategory": secondary_cat,
    }
    return listing, warnings, truncations, copy.get("tone_violations", []), aso.get("title_variants", [])

6. Validation

assert len(listing["name"]) <= 30
assert len(listing["subtitle"]) <= 30
assert len(listing["promotionalText"]) <= 170
assert len(listing["description"]) <= 4000
assert len(listing["keywords"]) <= 100
assert len(listing["whatsNew"]) <= 4000
assert listing["primaryCategory"], "primaryCategory required"
# JSON round-trip
json.loads(json.dumps(listing))

7. Present for review

Print each field with character count. Highlight truncations with ⚠. Show:

  • ASO warnings (scan failed, etc.)
  • Title-variant suggestions from ASO (advisory): "Consider renaming in app-info.json: {variants}"
  • Tone violations from brand-voice: "Copywriter missed these tone notes: {violations}"

Ask: "Write this to appstore/listing.json? [y/N]"

8. Write

On approval:

with open("appstore/listing.json", "w") as f:
    json.dump(listing, f, indent=2)

If existing file's contents are byte-identical to new output, skip write and report "no changes."

9. Next step

Print:

Listing written to appstore/listing.json. Next:
  /ios-site init        — scaffold the marketing/legal site (if not done yet)
  /appstore-iac plan    — preview the full App Store sync
  /appstore-iac apply   — push everything live

User override

Edit appstore/listing.json directly. Re-running the skill regenerates all fields — if the user wants to lock a field, they can revert after regeneration. (v1 simplification: the skill always presents a diff for user approval before writing, so user edits are never silently overwritten.)

Localization

For additional locales, run /ios-localize (separate skill). That produces appstore/listing-{locale}.json files that appstore-iac uploads.

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.