Generate AltStoreapps.json source files from GitHub Releases IPA assets.
Static app metadata (name, bundle identifier, icon and screenshot URLs, descriptions…) comes from a TOML config; everything dynamic (version, build version, release date, download URL, file size, release notes) is read live from the GitHub Releases API.
GitHub Releases API ──┐
├──► altgen ──► apps.json
app config TOML ──────┘
pip install altgen
altgen --versionRequires Python ≥ 3.10.
One TOML config = one app = one apps.json:
altgen -c piliplus.toml # writes apps.json next to the config
altgen -c piliplus.toml -o out/piliplus.json # override output pathOr skip the config file entirely for a quick single-app source (any CLI flag overrides its TOML counterpart):
altgen --repo owner/App --app-name App --bundle-id com.owner.app -o apps.jsonHosting many sources is just many configs — loop over them or use a CI
matrix, one altgen -c <config> per app.
Combine several apps.json files into one source (e.g. host many apps under a single source while keeping one config per app):
altgen merge a.json b.json -o merged.json
altgen merge -c merge.toml a.json b.json # root values + output from TOML
altgen merge --name MySource --tint-color "#00AEEF" a.json b.jsonappsare taken from the inputs and sorted by name (case-insensitive);newsentries from all inputs (rootnewsplus each app'snews) are combined and sorted newest-first.- Root values (
name,subtitle,description,icon_url,website,tint_color) come from the CLI flags or the config's[source]table — same rules as build mode, and--name(or[source] name) is required. - Merge configs only support
[source]and[output]tables; build-mode tables ([github],[app], …) are rejected. - A duplicate
bundleIdentifieror duplicate newsidentifieracross inputs is an error (exit 2).
See examples/merge.toml for a full merge config.
Unauthenticated requests are limited to 60/hour; a token raises that to
5,000/hour. Precedence: --token > GITHUB_TOKEN env var > [github] token.
Only [github] repo, [app] name, and [app] bundle_identifier are
required. Keys are snake_case in TOML and become the AltStore camelCase
JSON keys (bundle_identifier → bundleIdentifier, icon_url →
iconURL, min_os_version → minOSVersion, …). Unknown keys are
rejected with an error.
See examples/piliplus.toml for a full example.
[github]
repo = "owner/App"# REQUIRED: GitHub repo with releasestoken = ""# optional (see above)
[source] # the source this apps.json describesname = "App"# defaults to the repo namesubtitle = ""description = ""icon_url = ""# omitted from JSON when unsetwebsite = ""tint_color = "#00AEEF"# must be #RRGGBB
[app] # the app inside the sourcename = "App"# REQUIREDbundle_identifier = "com.x.y"# REQUIREDdeveloper_name = ""# defaults to the repo owner (CLI mode)subtitle = ""description = ""# fallback when a release body is emptyicon_url = ""# falls back to [source].icon_urlscreenshots = ["https://…"]
tint_color = ""# falls back to [source].tint_colormin_os_version = "14.0"# omitted from versions when unset
[versions]
strip_v_prefix = true# tag "v1.2.3" → version "1.2.3"include_prereleases = false# drafts are always skippedasset_pattern = "\\.ipa$"# regex, case-insensitive search on asset namebuild_version_pattern = "\\+(\\d+)\\.ipa$"# group 1 = buildVersion; no match → key omitted# version_pattern = "…" # optional: extract version with this regex instead of the tag# (for projects whose tags lack a version); no match → tag-derived version# version_source = "release" # where version_pattern runs: "release" (default, release name) or "filename" (IPA asset name)max_versions = 1# default: newest version only; 0 = all versions
[news]
enabled = truetitle_template = "{name} {version} - {date}"# default; placeholders: {name} {version} {tag} {date} (e.g. 07 Aug 2026)caption_template = ""# optional; default: "{name} {version} is available."image_url = ""# optional; omitted from JSON when unset# news appID = [app] bundle_identifiermax_entries = 0# 0 = unlimited; caps after sorting; news already limited to releases kept by max_versions
[output]
path = "apps.json"# resolved against THIS file's directory- Versions are sorted newest-first by
(date, version); one version entry per matching release asset. A release with several IPAs produces several entries — sharing one version by default, or one version each whenversion_source = "filename". - The version is the release tag by default (
strip_v_prefixstrips a leadingv). For projects whose tags carry no version (e.g.youproextra-ipa2), setversion_patternto extract it from the release name or IPA filename instead; on no match the version falls back to the tag-derived value. - By default only the newest version is emitted (
max_versions = 1); setmax_versions = 0(or--max-versions 0) to include all versions. - News follows the same convention: one news entry per release, kept only
when that release still has a version entry after the
max_versionscap. Matching is by release, not by version string, so several releases that resolve to the same version (e.g. a date-bearing tag likerelease-v1.2.3-2026-08-21vs...-2026-08-14) each get their own news entry only while their version entry survives the cap.[news] max_entriescan further cap the list. - News entries follow the AltStore spec:
appIDfirst (the app'sbundle_identifier), a full ISOdatetimestamp,identifierderived from the release tag (release-<tag>), and an optionalimageURL;title_template/caption_templatesupport{name},{version},{tag},{date}placeholders. - A release with no matching asset contributes nothing — not even a news entry. One news entry is emitted per release that has assets.
- Empty output (no releases, only drafts, …) is a valid source: altgen warns on stderr and exits 0.
altgen [-c PATH] [--repo OWNER/REPO] [--token TOKEN]
[--name] [--subtitle] [--description] [--icon-url] [--website] [--tint-color]
[--app-name] [--bundle-id] [--developer-name] [--app-subtitle]
[--app-description] [--app-icon-url] [--app-tint-color] [--min-os-version]
[--screenshots URL …] [--include-prereleases] [--max-versions N]
[-o PATH] [-q] [-v] [--version]
altgen merge APPS_JSON… [-c PATH] [--name] [--subtitle] [--description]
[--icon-url] [--website] [--tint-color] [-o PATH] [-q]
- Without
-c,--repo,--app-name, and--bundle-idare required (build mode). --max-versions Ncaps the output after sorting (newest first); it defaults to1(latest version only) and0means all versions.- CLI flags override TOML values;
-oresolves against the current directory while[output] pathresolves against the config file's directory (so a config next to its sources works from any CWD). altgen mergecombines apps.json files; see Merge sources.-vlogs skipped releases (draft / prerelease / no matching assets) to stderr;-qsilences the success message.- Exit codes:
0success,1GitHub, IO, or write error,2usage or configuration error.
pip install -e ".[dev]"
pytest # fully offline — fixtures captured from the GitHub API