Lisa Pocket is a native SwiftUI app (XcodeGen), so it can't use Markup's
eas build/eas submit (that's Expo). The equivalent here is xcodebuild archive + -exportArchive (destination: upload) driven by an App Store
Connect API key, which handles automatic provisioning and the TestFlight
upload. testflight.sh runs it locally; the
release-ios-testflight.yml
workflow runs the same thing in CI — modeled on the secret-gated pattern of
release-mac-apps.yml (itself lifted from Markup).
App identity: bundle id ai.meetlisa.main (+ the widget extension
ai.meetlisa.main.widgets), team 9LH9NBX7P4.
Reused from Telloria (same Apple Developer account, wangharp@gmail.com):
the Apple team 9LH9NBX7P4 and the App Store Connect API key are
account-level, so the very key that uploads Telloria uploads Lisa Pocket too —
no new credentials. The privacy manifest (Sources/PrivacyInfo.xcprivacy)
mirrors Telloria's required-reason API set (UserDefaults / file-timestamp /
boot-time / disk-space). It differs only on data collection: Telloria declares
email / user-id / purchase / analytics because it collects them; Lisa Pocket is
a thin client to your own backend and collects nothing to a Lisa server, so
its collected-data + tracking are empty. iOS only — there is no Android
target (Lisa Pocket is native SwiftUI, not the Expo app Telloria ships to both
stores).
- Register the app in App Store Connect. App Store Connect → Apps → ➕ →
New App → iOS, bundle id
ai.meetlisa.main, pick an SKU. (If the bundle id isn't in the list, register it first under Certificates, IDs & Profiles → Identifiers, with App Groups + Push Notifications capabilities; also create the app groupgroup.ai.meetlisa.main.) - Create an App Store Connect API key. Users and Access → Integrations →
App Store Connect API → ➕, role App Manager. Note the Key ID and
Issuer ID, and download
AuthKey_<KEYID>.p8(downloadable once). - First push permission only: APNs alerts / Live-Activity refresh stay
inert until you also set
LISA_APNS_*on the Mac runninglisa serve(see ../../packaging/ios-companion/README.md). TestFlight itself doesn't need that — only live push delivery does.
cd packaging/ios-companion
brew install xcodegen # one-time
ASC_KEY_ID=ABC123XYZ9 \
ASC_ISSUER_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx \
ASC_KEY_PATH=~/Downloads/AuthKey_ABC123XYZ9.p8 \
./testflight.shThe Mac already has the Apple Distribution cert in its keychain, so locally
you only need the API key. The build number defaults to a timestamp; override
with BUILD_NUMBER=… / MARKETING_VERSION=… if needed. On Xcode < 16 pass
EXPORT_METHOD=app-store.
Then: App Store Connect → your app → TestFlight → add yourself / a tester group once Apple finishes processing (a few minutes).
Status: configured & live (2026-07-23). All six secrets below are populated on
oratis/LISA, so any collaborator with push access can ship a TestFlight build from CI alone — no Mac, no local certificates.
Either of:
# 1. Tag-triggered (preferred — leaves a versioned marker)
git tag pocket-v1.2.0 && git push origin pocket-v1.2.0# 2. Manual: Actions → "Release — iOS TestFlight (Lisa Pocket)" → Run workflow
gh workflow run release-ios-testflight.yml --repo oratis/LISAWatch the run under Actions; on success the build appears in App Store
Connect → TestFlight after Apple finishes processing (a few minutes). The build
number is the Unix timestamp of the build (set automatically); the marketing
version comes from project.yml unless overridden.
Repo secrets (Settings → Secrets and variables → Actions):
| Secret | What |
|---|---|
ASC_KEY_ID |
API Key ID |
ASC_ISSUER_ID |
API Issuer ID |
ASC_API_KEY_BASE64 |
base64 -i AuthKey_<id>.p8 | pbcopy |
IOS_DIST_CERT_BASE64 |
an Apple Distribution .p12 (cert + key), base64 |
IOS_DIST_CERT_PASSWORD |
the .p12 passphrase |
APPLE_TEAM_ID |
9LH9NBX7P4 (reused from the mac workflow) |
With the secrets absent the workflow is a no-op, so forks stay green. To rotate:
re-export the cert / re-download an API key and gh secret set the new values —
nothing sensitive lives in the repo itself.
TestFlight (above) gets the build to Apple. Going public on the App Store adds these App Store Connect console steps (account actions — only you):
- App Privacy (App → App Privacy) — answer "Data Not Collected" to match the privacy manifest (Lisa Pocket is a thin client to your own backend; nothing reaches a Lisa-operated server). No tracking.
- Reviewability — the make-or-break item. A reviewer has no Mac running
lisa serve, so a pairing-only build looks non-functional (Guideline 2.1). Ship the LISA Cloud M0 demo (see docs/PLAN_CLOUD_v1.0.md): a hosted instance + a seeded demo account, and put its credentials in App Review → App Review Information → Sign-in required → demo user/pass, with a note explaining the Mac-pairing vs cloud modes. - Encryption / export compliance — answered by
ITSAppUsesNonExemptEncryption=falseinproject.yml(standard HTTPS only), so no per-build prompt. - Metadata — name "Lisa Pocket", subtitle, description, keywords, support URL
- privacy-policy URL (required; host on meetlisa.ai), category, age rating,
and screenshots (6.7"/6.5" iPhone at minimum; iPad if
supportsTablet). Copy-ready text is drafted inAPPSTORE_METADATA.md.
- privacy-policy URL (required; host on meetlisa.ai), category, age rating,
and screenshots (6.7"/6.5" iPhone at minimum; iPad if
- Submit for Review (the build from TestFlight → "Add Build" on the app version).
The cloud onboarding path offers Sign in with Apple in addition to pasting a
?token= URL. It's off by default and single-tenant — on success the server
hands back the shared LISA_WEB_TOKEN, matching the M0/C2 demo (one shared soul;
per-user isolation is deferred C3 work). Enable it on the cloud instance with:
| Env | Meaning |
|---|---|
LISA_CLOUD_APPLE_SIGNIN |
1/true to turn the POST /api/auth/apple endpoint on (else 404). |
LISA_CLOUD_APPLE_AUD |
Expected token audience; defaults to the app bundle id ai.meetlisa.main. |
LISA_CLOUD_APPLE_SUBS |
Optional comma-separated allowlist of Apple subs. Empty ⇒ any verified Apple ID may sign in (fine for a rate-limited demo); set it to restrict access. |
The endpoint verifies the Apple identity token (issuer / audience / expiry /
signature against appleid.apple.com/auth/keys) before returning the token — see
src/web/cloudAuth.ts. The iOS app needs the Sign in with Apple capability
(declared in project.yml; enable it on the App ID in the Developer portal before
a signed build). For App Review you can leave it off and keep the token-paste demo
flow in the review notes above, or enable it and allowlist the reviewer.
- The actual upload authenticates to your Apple account, so it can't run without the API key + the app record above — those are account actions.
- TestFlight builds use the production APNs environment;
testflight.shflips the generatedaps-environmententitlement toproductionfor the archive (project.yml staysdevelopmentfor normal dev builds). - Live push behavior is still only verifiable on a real device with
LISA_APNS_*configured; everything up to the upload is scripted here.
Set Sign-in required: YES. The ASC form insists on a user/pass pair, but this build signs in by pasting ONE full URL — so fill the fields in a way that can't mislead the reviewer, and put the real instructions in the notes:
- User name:
see-review-notes(a literal hint, not a credential) - Password:
<DEMO_TOKEN>(the raw token, as a fallback reference)
Fill the two placeholders from the live Cloud Run service
(gcloud run services describe lisa-cloud …) — keep the real token OUT of this
repo. Before every submission: curl -s -o /dev/null -w '%{http_code}' \ "<DEMO_URL>/?token=<DEMO_TOKEN>" must print 200 — a 401 here is exactly the
"unable to sign in when we entered the code" rejection (Guideline 2.1,
2026-07-13). The 401 body is now structured JSON (token_mismatch /
token_missing) if you need to diagnose.
Lisa Pocket is a companion client for "Lisa", a personal AI. It connects to
EITHER (a) the user's own Mac running the open-source Lisa server (local +
private — the default), OR (b) a hosted LISA Cloud instance.
IMPORTANT — sign-in model: this build has NO username/password form. Access
is granted by pasting one full URL that embeds the access token. The
username/password fields above are placeholders; please follow these steps:
1. Open the app → Settings tab.
2. At the top, set "Connect to" → "LISA Cloud".
3. Paste this ENTIRE line (one line, nothing trimmed) into the URL field:
<DEMO_URL>/?token=<DEMO_TOKEN>
4. Tap "Connect". The app performs a live check and shows
"Connected to LISA Cloud." If you instead see a message about a
rejected token (401), the paste was truncated — please re-copy the
whole line including everything after "?token=".
5. Open the Chat tab and talk to Lisa. (First reply may take a few
seconds; responses stream in.)
IN-APP PURCHASES: Settings tab -> "Add credits…" opens the sheet listing the
three consumable credit packs. If a session's free allowance runs out mid-chat,
the chat bubble offers the same "Add credits…" button directly.
Lisa collects no personal data; the cloud demo is a shared, rate-limited
instance provided for review.
- ASC app record created (
ai.meetlisa.main) + App Store Connect API key - LISA Cloud demo live + token filled into the review notes above
- App Privacy → Data Not Collected (matches
PrivacyInfo.xcprivacy) - Export compliance → covered by
ITSAppUsesNonExemptEncryption=false - Privacy-policy URL (required) hosted on meetlisa.ai
- Support URL
- Screenshots — 6.7" + 6.5" iPhone (iPad if
supportsTablet) - Metadata — name/subtitle/description/keywords/category/age rating (drafted in
APPSTORE_METADATA.md) -
testflight.shupload succeeded → build shows in TestFlight - IAP reachable — sign in with the review account on a device and confirm
Settings → "Add credits…" lists all three packs. Then kill the network,
relaunch, and confirm the button is still there: it must not depend on
/api/billing/quota(that dependency is what got 1.1 rejected under Guideline 2.1(b) — seeREVIEW_RESPONSE_2.1b.md) - Sandbox purchases credit the review account — the review-facing service
runs a build that includes
sandboxCreditAllowed()(it allowlists theLISA_REVIEWER_SEEDaccount automatically); on an older revision App Review's sandbox buy fails withsandbox_rejected - 社交媒体年龄分级 questions answered in ASC → App 信息 (mandatory once you submit again)
- Build attached to the version → Submit for Review