add-app
GitHub用于向AltGallery仓库添加新应用或IPA源。自动提取元数据、生成配置文件、下载资源并更新索引,实现应用入库的端到端自动化流程。
Trigger Scenarios
Install
npx skills add bebound/AltGallery --skill add-app -g -y
SKILL.md
Frontmatter
{
"name": "add-app",
"description": "Add a new app to the AltGallery repo. When the project already ships its own AltStore source (apps.json), extracts the fields it needs from that source to pre-fill config.toml; otherwise reads the bundle identifier and version metadata out of a release .ipa with tools\/ipa_bundle_id.py, verifies the version altgen would derive from the release tag or filename against the ipa and switches to version_source = ipa when they differ, and extracts the icon from that ipa with tools\/ipa_icon.py when the repo has no icon. Creates apps\/<AppName>\/, writes config.toml and news.toml, downloads icon + screenshots, samples a tint color from the icon, renders images\/news.png, generates apps.json with altgen (local verification only), and adds the README \"Available Apps\" entry. Both apps.json and all-apps.json are generated and committed by the CI workflow — never update or commit them locally. Use whenever the user wants to add a new app \/ a new IPA source to the gallery."
}
Adding a New App to AltGallery
End-to-end procedure for adding one IPA project to the gallery. Shared reference details (icon color sampling, news rendering) live in AGENTS.md — the key gotchas are inlined here, and links point at the full sections.
When the project already ships its own AltStore source, extract its metadata
first (step 2) so config.toml is pre-filled from the source instead of being
re-derived from the README by hand.
Every config.toml in this repo follows one template — apps/PiliPlus/config.toml.
Read it and copy it when writing a new one (step 4): header comment, section
order, shared wording, and raw-URL style all come from that file, so the skill
never needs updating when the template changes. Only the values
change between apps.
Procedure
-
Create the folder
mkdir -p apps/<AppName>/imagesUse the project's display name (match the GitHub repo's casing).
-
Extract fields from the project's AltStore source, or from a release ipa
Many projects maintain their own AltStore source — an
apps.json— in the repo (repo root, analtstore/AltStorefolder, or a dedicated branch) or hosted and linked from the README (often an "AltStore" / "Add to AltStore" badge). When one exists, prefer it as the source of truth for the fields below over hand-deriving them. altgen regenerates the version list from GitHub Releases, so only the app-level metadata and the latest version are needed — not the full version history.a. Locate it — grep the README for a source URL or
apps.jsonpath, or list the repo tree:# any JSON links in the README (source URL, altstore badge) curl -sL https://raw.githubusercontent.com/<owner>/<repo>/<default-branch>/README.md \ | grep -oE 'https?://[^ )">]+\.json' | sort -ub. Fetch and inspect it (pick the
.apps[]entry matching the app):curl -sL <source-url> \ | jq '.apps[0] | {name, bundleIdentifier, developerName, subtitle, localizedDescription, iconURL, tintColor, minOSVersion: .versions[0].minOSVersion, downloadURL: .versions[0].downloadURL}'c. Map to
config.toml(AltStore JSON key → TOML key):AltStore source field config.tomlapps[0].name[app] name(and the folder name)apps[0].bundleIdentifier[app] bundle_identifierapps[0].developerName[app] developer_nameapps[0].subtitle[app] subtitleapps[0].localizedDescription[app] description(trim long release-note blobs)apps[0].tintColor[app] tint_color— ⚠️ AltStore stores it without#(e.g.ff375f); write it as#ff375fapps[0].screenshots(objects'imageURL, or strings)download to images/*(step 3)apps[0].versions[0].minOSVersion[app] min_os_versionapps[0].versions[0].downloadURLipa filename → [versions] asset_patternapps[0].versions[0].versionleading v→[versions] strip_v_prefixroot name/subtitle/description/website[source] *d. Caveats
- A missing
bundleIdentifier— no source at all, or one that omits it — comes from the release ipa (step e); never invent one.com.example.*in an existingconfig.tomlmeans someone guessed. - Still download the icon/screenshots into
apps/<AppName>/and pointicon_url/screenshotsat theraw.githubusercontent.com/bebound/AltGallery/...URLs — never copy the source's remote asset URLs intoconfig.toml; AltGallery hosts its own copies. localizedDescriptionis often a release-note-style blob; shorten it for[app] description(a one-to-two sentence summary reads better).- When the source has no
tintColor, sample one from the icon instead (step 5). - No source (or stale/broken)? Derive the fields from the README as
before, but take
bundle_identifier/min_os_versionfrom the ipa (step e).
e. When no source provides the identifier — read it from a release ipa
AltStore installs fail when the identifier does not match the app, so never write a placeholder or copy one from a README:
# 1. latest ipa asset — also the exact filename for asset_pattern curl -sL "https://api.github.com/repos/<owner>/<repo>/releases/latest" \ | jq -r '.assets[] | select(.name|endswith(".ipa")) | .browser_download_url' | head -1 # 2. download it (often tens of MB) and read it — keep the file for # step 2f (version check) and step 3 (icon fallback) curl -sL -o /tmp/<AppName>.ipa "<url-from-1>" tools/ipa_bundle_id.py /tmp/<AppName>.ipa --jsonUse
bundle_idandmin_os_version;asset_patternmust match the asset name from step 1. Stdlib only (barepython3); exit 1 when unreadable. Delete the ipa only after steps 2f and 3 are done. AGENTS.md → Reading an IPA's Bundle ID.f. Confirm the version altgen will generate matches the ipa — always
Do this for every new app, even when a source supplied the metadata: a source's
versions[0].versionis only what its generator read, not what the ipa itself reports. Download the latest release ipa if step 2e did not already (any release with a newer tag works too — the check is about the tag-vs-ipa relationship, not a specific build), then compare:tools/ipa_bundle_id.py /tmp/<AppName>.ipa --json | jq -r '.version'- What altgen would put in
apps.json: the release tag with a leadingvstripped (strip_v_prefix = true, the template default) — or, whenversion_patternis configured, the version matched in the release name (default) or the asset filename (version_source = "filename"). - What the ipa reports:
CFBundleShortVersionStringfromtools/ipa_bundle_id.py. The two can disagree even on the newest release (AltStore: tagv2.3, ipa2.3.1, build 63).
When they differ, the tag/filename-derived version is wrong — set
version_source = "ipa"in[versions](step 4). Never fake a match by rewriting the tag,strip_v_prefix, orversion_pattern. When they agree, leave the template's default and add nothing: an unnecessaryversion_source = "ipa"makes every CI run download each ipa. - A missing
-
Download icon and screenshots Fetch from the project's GitHub repo (e.g.
raw.githubusercontent.com, or the repo'sassets/folder) intoapps/<AppName>/icon.pngandapps/<AppName>/images/*.png(~3 portrait shots, e.g.home.png,detail.png,comment.png). When a source was found in step 2, itsiconURL/screenshotstell you exactly which files to grab.⚠️ If no screenshots can be found (no
screenshotsfield in the source, noimages/assetsfolder in the repo, README links broken, etc.), still download the icon and finish the rest of the flow — just remember that the screenshots are missing, and output the warning at the end (step 10).⚠️ If no icon exists in the repo either, take it from the release ipa (step 2e) — never leave
icon.pngmissing, never grab a random web image:tools/ipa_icon.py /tmp/<AppName>.ipa --out apps/<AppName>/icon.pngIt prints the size on stderr: most ipas ship only 120x120/152x152 (the 1024px artwork is locked in
Assets.car), which looks soft at promo size — say so in the final summary. Don't run it over a repo icon (--forceis required, on purpose). AGENTS.md → Extracting the App Icon from an IPA. -
Write
config.toml—apps/PiliPlus/config.tomlis the repo's reference config. Read it and copy it, don't retype a remembered skeleton:cat apps/PiliPlus/config.tomlCopy the file into
apps/<AppName>/config.tomland replace only the app-specific values. Everything the template sets — the header comment, the section order, the shared[source]wording,[news],strip_v_prefix/include_prereleases, the[output]path, the raw-URL form — stays exactly as PiliPlus has it, so a future change to the template reaches new apps on its own. Never freeze a copy of those values into this skill: re-read the file every time.What is app-specific:
- every value naming the app or its repo:
[github] repo,[source]name/website,[app] name/developer_name/subtitle/description, and the<AppName>segment in eachicon_url/screenshots/image_urlpath - the header comment's description line (one line, English — see AGENTS.md → Comments)
[app] bundle_identifier— from the source (2c) or the ipa (2e); never a made-upcom.example.*— andmin_os_versionfrom the same places- the tint colors —
[app] tint_colorfrom the source'stintColor(2c) or, when the source has none, the icon sample (step 5);[source] tint_colornormally mirrors it, and diverges only for a project with a distinct secondary brand color [versions]matching rules: ⚠️asset_patternis a regex, not a glob — to match any ipa use".*\\.ipa$";"*.ipa"fails withinvalid regex. Narrow it to a specific filename when each release ships exactly one ipa (e.g."EhPanda\\.ipa$") — the latestdownloadURLfrom step 2 gives the exact name. Addversion_pattern/build_version_patternonly when the release tags need them (the template carries PiliPlus's own — replace or drop them as appropriate). Addversion_source = "ipa"only when step 2f found the ipa's version differs from the tag/filename-derived one (e.g. AltStore's tagv2.3vs. ipa2.3.1), with a one-line English comment naming both values so nobody "fixes" it back:# Read the version from the ipa's Info.plist: the release tag is v2.3 # while the ipa inside it reports 2.3.1 (build 63). version_source = "ipa"
- every value naming the app or its repo:
-
Sample a tint color (when the project has no official brand color in its AltStore source — otherwise reuse the source's
tintColordirectly): call the existing sampler intools/render_news.py→extract_icon_color()instead of re-implementing the PIL sampling (see AGENTS.md → Icon Color Sampling):PYTHONPATH=tools .venv/bin/python3 -c "from render_news import extract_icon_color; from pathlib import Path; print(extract_icon_color(Path('apps/<AppName>/icon.png')))"⚠️ Eyeball the result — human confirmation required. Multi-color or pale icons may not have an obvious single brand color.
-
Write
news.toml— copyapps/PiliPlus/news.tomlthe same way (it documents every optional[colors]key as a comment), then change onlynameandtagline. All keys are optional; leave the commented defaults in place. Then render the shared promo image (AGENTS.md → [Generating News Images]):./.venv/bin/python tools/render_news.py --out apps/<AppName>Renders
apps/<AppName>/images/news.png(1600x1200, 4:3) into the working tree (do not auto-commit). The image is drawn entirely with Pillow — no external rasterizer needed. ⚠️ Use the venv interpreter (same asupdate_news.sh): barepython3has no Pillow, sorender_news.pyfails with an import error. -
Generate
apps.json— never hand-edit it:cd apps/<AppName> && uvx altgen -c config.toml⚠️ After ANY
config.tomlchange, regenerate — never leave config andapps.jsonout of sync. If rate-limited or fetching from a private repo, useGITHUB_TOKEN=$(gh auth token) uvx altgen -c config.toml. This local run only verifiesconfig.toml— the resultingapps.jsonis gitignored (it won't show ingit status). The workflow (.github/workflows/update.yml) regenerates and commits the realapps.json— same asall-apps.json— after your change merges. -
Do NOT commit
all-apps.json— likeapps.json, the repo-root JSON is generated and committed by the CI workflow (.github/workflows/update.yml) after your change merges; never commit either file locally. To verify the new app's source after itsconfig.tomlis written, the single-app form is fine:./update.sh <AppName>only regenerates this app'sapps.json(no other app is touched). It still rewrites the trackedall-apps.json, so an unstagedMshows ingit status— leave it for the workflow. Your change ships onlyapps/<AppName>/plus the README entry. -
Update README — add the app to Available Apps, icon inline before the name:
### <a href="https://github.com/owner/name"><img src="https://raw.githubusercontent.com/bebound/AltGallery/master/apps/<AppName>/icon.png" alt="<AppName> icon" width="24" align="top"> <AppName></a> Short one-line description.⚠️ Use
align="top"on the icon —align="center"renders ~7px low on GitHub.Then add the same entry to
README_CN.md(the Chinese README linked from the language switcher under the title): identical heading and icon URL, one-line description written in Chinese. Both READMEs share the same app order, so the entry goes at the same position. -
Warn if no screenshots were found — this is the LAST step. If step 3 could not obtain any screenshots (empty
apps/<AppName>/images/, noscreenshotsfield in the source, noimages/assetsfolder in the repo, broken URLs, …), output an explicit warning to the user before finishing. Example:⚠️ Warning: no screenshots found for <AppName> — apps/<AppName>/images/ is empty. The app will show without screenshots in the gallery. When you have screenshots, drop them into apps/<AppName>/images/ and update the `[app] screenshots` list in config.toml (then regenerate apps.json).This warning is required — never end the add-app flow silently while screenshots are missing.
Checklist
-
config.tomlcopied fromapps/PiliPlus/config.toml(read at run time, not from memory) — shared header/section order/wording/URLs left untouched, only app-specific values replaced - fields extracted from the project's own AltStore source (when one exists);
tintColornormalized to#RRGGBB -
bundle_identifierandmin_os_versionfrom the source or the ipa (tools/ipa_bundle_id.py) — nocom.example.*placeholder - version confirmed against the ipa (step 2f):
version_source = "ipa"(with a comment) when the tag/filename differs, left out when it matches - ipa deleted afterwards
-
apps/<AppName>/{config.toml, news.toml, icon.png, images/*}all present -
icon.pngfrom the repo, or from the ipa viatools/ipa_icon.pywhen the repo has none — size checked, small size reported to the user - icon/screenshots hosted locally and referenced via
bebound/AltGalleryraw URLs (never the source's remote URLs) -
apps.jsonregenerated after the last config change; never hand-edited; gitignored (not committed) -
all-apps.jsonNOT touched — CI workflow regenerates and commits it -
images/news.pngrendered; not auto-committed - README entry added to both
README.mdandREADME_CN.md, icon withalign="top" -
README_CN.mddescription written in Chinese - if no screenshots could be found, the user was explicitly warned (never proceed silently)
Version History
- 7e916c6 Current 2026-11-13 18:24


