With the copied Chrome profile, `console fill play-app-record` stopped with "no developer account named "<name>" (play.developerAccount) on the chooser" although the name was there: the real "Choose developer account" rows are not plain /developers/<id> text links (nested spans, icon-font glyphs, role=button rows with the id in a data attribute, an avatar row whose id only its click reveals). - readChooser: every visible clickable row in the page content (header and navigation left out); the name is the first visible line (icon fonts, images and aria-hidden parts skipped); the id comes from /developers/<id> in any attribute, else a data-*id attribute, else '' (the row is then opened through the account-switch gate and the id is read from the URL). "Create a new developer account" is not an account. - matchAccount: exact spelling, then spaces and case aside (Turkish I/ı folded), then a unique prefix. A case pair (ACME / Acme) needs the exact spelling; otherwise it stops and lists both with their ids. - One developer account: picked (a different play.developerAccount is said). Not found: every visible account is listed with its id. - The resolved developerId is remembered as before. Fixtures play-chooser-real.html (the owner's chooser shape, placeholder names) and play-chooser-one.html; tests for exact, case pair, prefix, single account and not found.
176 lines
16 KiB
Markdown
176 lines
16 KiB
Markdown
---
|
|
name: console-autofill
|
|
description: AI-assisted autofill of App Store Connect and Google Play Console forms that have no API (the New App dialog, App Privacy, Play's Create app form, the Play IARC content rating questionnaire, Target audience and content plus the Ads declaration; the App Store age rating is shown as guidance because the API sets it) in a visible browser that reuses the owner's Chrome sign-in (or one they sign in to); storebound opens the form itself. Answers come only from storebound.config.ts; anything the config does not prove stays empty and highlighted. storebound moves through the pages itself (Next; App Privacy: Get Started, Edit, each data type and the draft Save; content rating: Save draft) and never signs in, submits or publishes: the owner presses the final button. Use when the user wants help filling one of these console forms, asks what to answer in them, or wants the answers as a checklist.
|
|
---
|
|
|
|
# Console autofill (forms without an API)
|
|
|
|
CLI: `storebound` (or `bunx storebound` in a project that has it, or `bun <toolchain>/cli/index.ts`).
|
|
|
|
**Read this to the owner first.** Console pages change without notice. This is AI-assisted autofill and it may put an
|
|
answer in the wrong place. The owner reviews every field and presses the final button. storebound never types a
|
|
password, never touches a sign-in page, never prints or sends a cookie, and never clicks Submit, Publish, Create, a
|
|
final Save or anything that sends a form for review (English or Turkish console). It clicks only what moves through
|
|
the form (see "What storebound clicks" below). It reuses the owner's
|
|
Chrome sign-in through a temporary copy of only the console cookies (deleted at the end; `--fresh-profile` opts out).
|
|
It is not available over MCP.
|
|
|
|
## Forms
|
|
|
|
| form | where | filled from |
|
|
|---|---|---|
|
|
| `apple-app-record` | App Store Connect > Apps > + > New App | apple.name (else app.name), apple.primaryLocale, apple.bundleId, console.appRecord (SKU, user access) |
|
|
| `apple-age-rating` | App Information > Age Ratings | guide only: `storebound apple appinfo` sets every question through the API |
|
|
| `apple-app-privacy` | the app > App Privacy | appPrivacy.types (the owner's answers per Apple type id: collected, purposes, linked, tracking; certain), else dataSafety (types and purposes; Play advertising = Third-Party Advertising only); unset linked / tracking stay the owner's |
|
|
| `play-app-record` | Play Console > Home (All apps) > Create app | play.name or app.name (30 chars), play.defaultLanguage or the first language's play code, app.profile (App / Game), play.free or monetization (Free, owner check); the declarations are never ticked |
|
|
| `play-content-rating` | Policy > App content > Content rating | app.contactEmail, app.profile, products, console.contentRating |
|
|
| `play-target-audience` | Policy > App content > Target audience and content (+ Ads) | console.targetAudience, ads.appAdsTxt |
|
|
|
|
## Steps
|
|
|
|
1. `storebound console fill` lists the forms. `storebound console fill <form> --dry` prints every answer with its
|
|
source; `[check]` lines are not proven by the config. Show them to the owner.
|
|
2. For each `[check]` line, ask the owner (AGENTS.md rule 5: never guess a compliance answer). If they want it filled
|
|
next time, add it to the config, for example:
|
|
```ts
|
|
console: {
|
|
appRecord: { sku: 'com.example.game', userAccess: 'full' },
|
|
contentRating: { category: 'game', answers: { violence: false, lootBoxes: false, userInteraction: false } },
|
|
targetAudience: { ages: ['13-15', '16-17', '18-over'], appealsToChildren: false, containsAds: true },
|
|
},
|
|
```
|
|
Age rating answers live in `apple.info.ageRating` (also `socialMedia`, `socialMediaAgeRestricted`).
|
|
3. The owner runs `storebound console fill <form>` in a terminal (or presses the button in the desktop app). The
|
|
first log line says which browser it uses, in this order:
|
|
- **attach**: Chrome already runs with remote debugging (a `DevToolsActivePort` file in Chrome's user data folder,
|
|
or `--cdp http://127.0.0.1:<port>`): storebound opens one new tab there and closes only that tab.
|
|
- **copy** (default on macOS with Google Chrome installed): the owner's existing Chrome sign-in, no second sign-in.
|
|
storebound copies ONLY `Local State` and the profile's `Cookies` (+ `Cookies-journal`) into a temporary folder,
|
|
deletes every cookie there except the ones App Store Connect and Play Console get (apple.com and google.com
|
|
domains), and opens real Google Chrome on that copy. Never copied: passwords (`Login Data`), history, extensions,
|
|
bookmarks or anything else. The temporary folder is deleted when the run ends (window closed, error, Ctrl+C, or
|
|
Cancel in the desktop app). Chrome decrypts the cookies with its own Keychain key ("Chrome Safe Storage"); macOS
|
|
may ask once to allow that. `--chrome-profile "Profile 1"` picks another Chrome profile (default: the last used).
|
|
Cookies are never printed, logged or sent anywhere.
|
|
- **profile** (`--fresh-profile`, `--profile <dir>`, `STOREBOUND_CONSOLE_PROFILE`, or when the other two do not
|
|
work): storebound's own profile (`~/.storebound/console-profile/<store>`, one per store, not per account: every
|
|
studio shares the one login). The owner signs in there once; it is kept for the next run.
|
|
Keeping the sign-in: Apple's and Google's sign-in cookies are partly session cookies. storebound launches the copy
|
|
and the own profile with `--restore-last-session` (so Chromium loads them again) and always closes the browser
|
|
gracefully (window closed or Ctrl+C), so they are written to disk. Apple keeps a long sign-in only with "Keep me
|
|
signed in" ticked and "Trust" chosen for the browser; Google with "Stay signed in"; storebound reminds the owner on
|
|
the sign-in page. In copy mode the owner's Chrome Cookies file holds the session cookies only while that Chrome
|
|
runs, or when Chrome's "On startup: Continue where you left off" is on; otherwise one sign-in in the window is
|
|
needed.
|
|
Sign-in is detected by URL and by frame (App Store Connect `/login`, a frame from idmsa.apple.com /
|
|
appleid.apple.com, accounts.google.com, Google `/signin`), by a visible password field in any frame, and by the
|
|
form's own texts ("Sign in with Passkey", "Email or Phone Number", "Keep me signed in" and the Turkish ones):
|
|
Apple's form starts with no password field. storebound checks before every navigation step, after each click or
|
|
load, and while a step waits. Then it prints "sign in in the browser window; storebound waits" and waits up to
|
|
10 minutes, never typing or reading anything there; when the console is back (no sign-in page, the console URL)
|
|
it opens the form again from step 1.
|
|
4. After sign-in storebound opens the form itself and logs every step ("opening the form, step 2/4: the add button
|
|
(+)"): New App: Apps > + > New App; App Privacy: `/apps/<apple.appId>/distribution/privacy` (without `apple.appId`
|
|
but with App Store Connect API credentials, storebound looks the id up from `apple.bundleId` and caches it in
|
|
`.storebound/console-app-ids.json`), else the app list > the app (by name / bundle id; one app shown twice on its
|
|
card, title and icon, is one app) > App Privacy; Play forms: the app list > the app (by `play.packageName`) > Policy and
|
|
programs > App content > the form's Start or Manage link. These navigation clicks pass the same click gate: never
|
|
Save, Submit, Publish, Create, Send, Next or the Turkish equivalents; only a LINK named exactly Start / Manage /
|
|
Başla / Yönet is let through. If a step is not found in time, or two apps match (they are listed: set
|
|
`apple.appId` / `play.packageName`), storebound says which step failed, prints what to click, and keeps watching:
|
|
a sign-in that finishes restarts navigation at step 1; when the owner moves on by hand (the + button, the New App
|
|
item, the app in the list appear) it continues from that step; when the owner opens the form it fills it. Watching
|
|
ends when the window is closed. When the form appears storebound fills the certain answers, outlines every
|
|
unproven or unfillable field with a "storebound: check this" label, and lists them in the terminal.
|
|
5. **One login, many developer accounts or teams.** storebound never asks for a second sign-in; it finds the
|
|
account that has the app and remembers it in `~/.storebound/console-accounts.json` (ids only, never cookies;
|
|
shared with the desktop app):
|
|
- Play: the app list of one account is `/console/u/0/developers/<id>/app-list`. The id is `play.developerId` in
|
|
the config, else the workspace account's `developerId` (`storebound.workspace.json`, passed as
|
|
`STOREBOUND_PLAY_DEVELOPER_ID`), else the remembered one; else storebound reads every developer account on the
|
|
"Choose a developer account" page and looks for `play.packageName` in each app list ("developer account 4/5:
|
|
..."). A package in none of them is said plainly (create the app, or ask to be invited).
|
|
- App Store Connect: the team is switched in the account menu at the top right. Order: the remembered team, the
|
|
menu entry that names `apple.teamId` (or the workspace account's `teamId`), then every other team. The app is
|
|
recognized by its entry in the app list (App Privacy) or, for a new app, by the bundle id in the New App dialog's
|
|
Bundle ID list (Cancel closes the dialog in a team without it). App Store Connect has no documented URL
|
|
parameter that picks a team, so storebound does not use one.
|
|
- Play Create app (`play-app-record`) is a first create: no account has the package yet, so storebound does not
|
|
search, it picks: `play.developerId` (or the workspace account's id, or the remembered one), else the only
|
|
account the login sees (picked even when `play.developerAccount` names another; the log says so), else the
|
|
chooser entry named `play.developerAccount`: the exact spelling first, then spaces and case aside, then a unique
|
|
prefix ("ACME" and "Acme" are two accounts: only the exact spelling picks one). Several accounts and none of
|
|
these set, or a name that matches none or two: it stops and lists every account with its developer id. Ask the
|
|
owner which one; never guess. The choice is remembered under
|
|
`play.packageName`.
|
|
- Switching only changes what the console shows. Each switch click must be an account name storebound just read
|
|
from the chooser or the menu, and never Sign Out or a deny-listed name (a studio called "Next Games" is left
|
|
for the owner to pick).
|
|
6. Multi-page forms: storebound fills a page and clicks its Next itself (App Privacy: Get Started or Edit next to
|
|
Data Types, Next, the data types' Save, then each data type in the Data Types section: purposes > Next > linked >
|
|
Next > tracking > Save; content rating: Save draft, then Next). It stops on a page only when an answer there is uncertain: it logs
|
|
`answer needed on "<page>": <the answers>`, leaves them outlined, and goes on by itself once the owner has set
|
|
each one (a box that should stay empty: click it twice). It never clicks Next past an open question.
|
|
7. On the last page storebound prints "review every field and press <Save | Submit | Publish | Create> yourself"
|
|
(App Privacy: "review the summary, then press Publish yourself"). The
|
|
command ends when the owner closes the window (`--json` then prints the result with `filled`, `highlighted`,
|
|
`fallback`, `stoppedAt`).
|
|
8. Nothing waits silently. While storebound waits it logs `still waiting for <what>, <time>` once a minute. When the
|
|
page after a click (or the form) is not one it knows within `--step-timeout` seconds (default 30), it says so with
|
|
the page's buttons, writes a debug dump to `.storebound/console-debug/<form>-<time>/` (page.json: headings, dialogs,
|
|
every visible control with role and name; page.html: the sanitized structure; screen.jpg) and prints the manual
|
|
steps. After 20 minutes without progress it closes the window and exits 1. `--debug-dump` writes a dump at every
|
|
step: attach that folder to a bug report after looking at screen.jpg (labels only; no values, cookies or tokens;
|
|
emails, phone numbers and the account area are replaced).
|
|
9. If a field cannot be found or matches twice (for more than a moment: a page that is still loading does not count), the console UI changed: storebound stops filling and prints the
|
|
step-by-step list ("label: answer") for the owner to copy. That is the expected safe behavior, not an error.
|
|
|
|
## Play Create app (`play-app-record`)
|
|
|
|
- Navigation: the developer account's app list (Home / All apps) > the page-level **Create app** button. That button
|
|
only opens the form, but the form's final submit has the same words. storebound clicks it only as that one
|
|
navigation step: whole name exactly "Create app" / "Uygulama oluştur", a button or link, on the app list (All apps
|
|
heading or an /app-list URL), with no Create app form open and not inside a form or dialog. Inside the form it is
|
|
refused like every other submit.
|
|
- Fills App name (`play.name`, else `app.name`; over 30 characters it is left to the owner), Default language
|
|
(`play.defaultLanguage`, else the first language's play code, shown as "English (United States) – en-US"), App or
|
|
game (`app.profile`: game -> Game, app / subscription-app -> App) and Free or paid (`play.free`, else Free when the
|
|
config earns from IAP / subscriptions / ads only).
|
|
- **Owner check:** Free or paid is filled but outlined: a free app can never become paid on Google Play. Tell the
|
|
owner before they press Create app.
|
|
- **Declarations** (Developer Program Policies, US export laws) are agreements: storebound never ticks them, it
|
|
outlines them. The owner reads and ticks them and presses Create app.
|
|
- The package name is not in this form: the first AAB upload sets it for good (`storebound play upload --track
|
|
internal`, or `storebound flow run release`).
|
|
|
|
## What storebound clicks, and what stays with the owner
|
|
|
|
Every button of each form is classified in `cli/lib/console-forms.ts` (`FORM_BUTTONS`), from Apple's and Google's
|
|
help pages ("Manage app privacy", "Add a new app", "Set an app age rating"; "Content ratings", "Target audience and
|
|
content", "Create and set up your app"):
|
|
|
|
| form | storebound clicks (navigation) | stays with the owner, and why |
|
|
|---|---|---|
|
|
| App Privacy | Get Started; Edit next to Data Types (never the Privacy Policy Edit); each data type or "Set Up <type>"; Next; **Save inside the questionnaire dialogs** (a draft: Apple publishes the answers only after Publish) | **Publish** and its confirmation: it puts the answers on the App Store product page |
|
|
| New App (App Store) | + and New App (Cancel only to leave a team without the bundle id) | **Create**: creates the app record for good |
|
|
| Age rating (guide only) | nothing (`storebound apple appinfo` sets it by API) | Set Up / Next / Done, if the owner wants to look |
|
|
| Create app (Play) | Create app on the app list (opens the form) | the two **declarations** (agreements, never ticked) and **Create app** in the form: creates the app |
|
|
| Content rating (Play) | Start; **Save draft** then Next on each page (a draft: IARC gets nothing before Submit; the owner agreed) | **Submit** on the Summary: sends the questionnaire to IARC |
|
|
| Target audience (Play) | Start / Manage, Next between the pages | **Save** on the last page: stores the declaration, which Google then reviews |
|
|
|
|
Mid-way storebound stops only for an uncertain answer (see 6). It never asks the owner to click a navigation button
|
|
unless the console page changed so that storebound cannot find it (then it says so).
|
|
|
|
## The owner decides
|
|
|
|
Every `[check]` answer, linked-to-identity and tracking for each App Privacy data type (unless `appPrivacy.types`
|
|
answers them), Developer's Advertising or Marketing, the IARC answers, the target
|
|
ages, Play's Free or paid and its declarations, and pressing the final button (App Privacy Publish, IARC Submit,
|
|
Target audience Save, New App Create, Play Create app).
|
|
|
|
## Out of scope
|
|
|
|
- Signing in, two-factor codes, accepting agreements, creating developer accounts or teams.
|
|
- Forms with an API: Data safety (`storebound play data-safety`), age rating, categories and content rights
|
|
(`storebound apple appinfo`), export compliance (`storebound apple encryption`).
|
|
- Recording or reading console data: storebound only fills the visible form.
|