One installed program that ships one product on iOS and Android. Store screenshots in every language, the listing text, the version bump and the binary — from the command line, with the same words on every app.
A release is this, and nothing else:
appkit make screenshots # capture every scene, in every language
appkit review screenshots # look at all of them at once
appkit upload metadata 1.3.0 # the listing text
appkit upload screenshots 1.3.0 # the cards
appkit ship --submit # archive, upload, attach, send for reviewNo more Organizer, no more Distribute App wizard, no more dragging thirty screenshots into App Store Connect one locale at a time, no more refreshing TestFlight in a browser tab, no more Play Console upload form. You create the store record once, by hand, and never open either web console again.
Xcode and Android Studio are the SDK, not the workflow. Write the app in
whatever you write code in — appkit run builds it, installs it and launches
it, and nothing above this line needs an IDE window open.
appkit make screenshots launches the app once per scene per language, waits
for each screen to hold still, and composes the captures into store cards.
appkit review screenshots then draws you the whole set at once — one row per
locale, one column per card, a red frame where git says a card changed and a
green one where it is new:
That picture is the review step. git status can tell you whether a card
changed; it cannot tell you which, because a PNG diff in a terminal is one
line about bytes. A hole in the grid is a locale missing a scene — the most
common way a set goes wrong, and the hardest to catch one file at a time.
The cards are deterministic: same captures and same storyboard in, same bytes out, on every machine. That is what makes them reviewable at all — a pipeline whose pixels drift every run produces noise you learn to ignore, and the day you ignore a real change is the day a stale screen ships.
brew tap walkccc/appkit
brew trust walkccc/appkit # Homebrew 6 loads no third-party formula untrusted
brew install --HEAD appkit
appkit skills # once per machine — links the /appkit-… skills into ~/.claude/skillsThe formula sits in walkccc/homebrew-appkit rather than beside the code,
because that repo is the only thing the tap name walkccc/appkit resolves to.
Held here it still worked, but every install line had to carry the clone URL.
Needs macOS and Homebrew, plus Xcode for iOS or the Android SDK for Android.
Everything else (bash, python3, swiftc, curl, openssl) already ships
with the Xcode command line tools. Publishing to the App Store also needs
asc (brew install asc); appkit says so
when it needs it.
macOS is not optional: the composer is CoreText, which is the only thing on the machine that lays out Japanese and Korean beside Latin from an explicit font cascade instead of a guess.
appkit does not create the app — Xcode and Android Studio do. It creates the pipeline around it.
- Create the project. Xcode ▸ File ▸ New ▸ Project (name
MyApp, bundlecom.example.MyApp), or Android Studio ▸ New Project. This makes the repo folder; naming itmyapp-ios/myapp-androidkeeps it apart from a web repo for the same product. - Scaffold the pipeline, from inside it:
Or run
appkit new ios --name MyApp --bundle com.example.MyApp --locales "en zh-Hant ja" appkit new macos --name MyApp --bundle com.example.MyApp --locales "en" appkit new android --name MyApp --package com.example.myapp --locales "en ja"
/appkit-scaffold, which is the same call with the judgement attached. Nothing here touches screenshots yet — there is no app to photograph. - Build the app. This is the product; appkit has nothing to do here.
- Wire the screenshot mode with
/appkit-add-screenshots-mode:-ScreenshotSceneon iOS,-e screenshotSceneon Android, read off the launch, seeding each scene's data and freezing anything that animates forever. Thenappkit make screenshotsand look at what came out. - Create the store record in App Store Connect or the Play Console (appkit
cannot — Play has no API for it), then
appkit pull metadataand fill instore/metadatawith/appkit-metadata, through toappkit check metadata. - Release with
/appkit-release: the version bump, the real screenshot set, the listing upload, andappkit ship.
A second platform for a product already on appkit is the same appkit new call
in its own sibling repo — it never overwrites without --force.
Every command is verb noun, spelled out. There are no short forms and no flags
to memorise; appkit help VERB prints any one command's own page.
appkit help # what any of these do (appkit help VERB for one)
appkit new ios # scaffold a repo, from nothing (ios | macos | android)
appkit sync # write this repo's shared files
appkit doctor # check they stayed put (the pre-commit hook does)
appkit skills # link the skills into ~/.claude/skills, once
appkit setup signing # the one-time machine setup a platform needs
appkit path # where appkit is installed, for a repo's scripts
appkit run # build, install, launch
appkit capture # photograph the store scenes, one launch each
appkit render # compose them into store cards
appkit make screenshots # capture + render, and stop
appkit review screenshots # every card in one picture, changes framed
appkit check metadata # every store's limits, every language's coverage
appkit pull metadata # what the listing says right now, to compare
appkit upload metadata # the listing text, onto the store
appkit upload screenshots # the store cards, onto the store
appkit ship # the binary, onto the store
appkit version 1.3.0 # move the version everywhere it is written
appkit version build # the build number only, +1The device. On iOS appkit resolves and boots the simulators itself, one per
language, and runs them in parallel — you never open Simulator.app, and the
model is pinned in appkit so every repo moves off it at once. On Android an
emulator or a phone on a cable is something the machine already has, so appkit
uses whatever adb gives it and boots nothing. On macOS the device is the Mac
appkit is running on: nothing to boot, nothing to create, one pass, and a
capture photographs the app's front window rather than the screen — a screen
carries your desktop into the shot and comes out at whatever size this
particular Mac's panel is.
Building is yours — except on macOS. appkit only runs xcodebuild or
gradlew inside appkit run, appkit ship and appkit capture --build;
everything else reuses what the IDE already built, which is why capture takes a
flag to compile rather than doing it by default.
macOS is the exception, because SwiftPM emits a bare executable and there is no
IDE build to reuse: the .app around it — Info.plist, the icon, the SwiftPM
resource bundles, the signature — is assembled by appkit. That assembly is what
replaced a hand-written build.sh in each Mac repo, and the reason it is worth
sharing is the signature. macOS pins a TCC permission grant (Accessibility,
Screen Recording, the microphone) to the app's designated requirement;
ad-hoc signed, that requirement contains the binary's hash, so every rebuild is
a new app to TCC and the grant is silently dropped while System Settings still
shows the toggle on. Signed with a real certificate it survives. A Mac with no
Apple certificate gets one from appkit setup signing, once.
| A repo owns | appkit syncs in, and doctor checks |
|---|---|
appkit.json — platform, locales, scenes, ids |
AGENTS.core.md — the house style |
scripts/scenes.sh — what a screenshot IS |
.prettierrc, .swiftformat (iOS + macOS) |
store/ — the storyboard, the words, the cards |
.githooks/pre-commit, .gitignore's block |
Everything appkit hands a repo is a copy, never a symlink — a link survives
neither an upgrade nor another machine, and a repo should still read as itself on
a laptop where appkit was never installed. appkit sync writes it, appkit doctor fails on any difference, so an upgrade reaches a repo as a reviewable
diff rather than silently. A repo that genuinely extends a shared file declines
it instead of drifting: "sync": { "skip": [".prettierrc"] }.
A locale, a scene name or a store folder written into a script is drift — all
three are rows in appkit.json, and every command reads them from there.
appkit skills links these into ~/.claude/skills, once per machine — the
command is the mechanism, the skill is the judgement a script can't make:
| Skill | For |
|---|---|
/appkit-scaffold |
standing up a new repo, or a second platform for one |
/appkit-add-screenshots-mode |
wiring the debug-only screenshot mode into an app that doesn't have it yet |
/appkit-screenshots |
adding a scene, mixing in a widget or the watch, or debugging a set that will not reproduce |
/appkit-metadata |
writing the listing text — every field, every language |
/appkit-release |
shipping end to end, in order, knowing what's reversible |
- Design tokens. The shape is shared, in a paragraph of
AGENTS.core.md; the values never are — the same token name has meant three different values across the repos this came from, so shipping the file would have moved layout in shipped apps. .editorconfig. Ships inconfig/to copy by hand, not to sync: it carries a language's indent and lint rules, which are the repo's own.- An illustrated card set, or a site's own OG pipeline. A repo whose cards
are drawn rather than composed keeps its own renderer, declared as
render.commandinappkit.json.
appkit/
├── AGENTS.core.md the house style, copied into every app
├── bin/appkit the entry point: one verb per command, and appkit help
├── commands/ one file per subject — capture, render, review, upload, …
├── lib/ manifest, log + shutter, sharding, the picture gate
├── platform/ ios.sh, macos.sh, android.sh — the device contract
├── render/ compose.swift, same-picture.swift, contact-sheet.swift, fonts/
├── store/ asc.sh, play.sh, metadata.sh — the store adapters
├── skills/ agent instructions, linked into ~/.claude/skills
├── config/ .swiftformat, .prettierrc, .editorconfig, gitignore.base
├── githooks/ pre-commit
├── templates/ cards.json
├── docs/CARDS.md the card storyboard schema
└── VERSION what doctor checks a repo's floor against
CONTRIBUTING.md. The bar is "is this true of every app on
appkit" — if it's true of one platform it belongs in platform/, and if it's
true of one app it belongs in that app.
MIT — LICENSE. The vendored Poppins is under the SIL Open Font Licence
(render/fonts/OFL.txt).
