diff --git a/.cursor/rules/storebound.mdc b/.cursor/rules/storebound.mdc index d83db9a..9b268ea 100644 --- a/.cursor/rules/storebound.mdc +++ b/.cursor/rules/storebound.mdc @@ -33,6 +33,13 @@ These rules apply to every AI tool that uses storebound (skills, MCP server or t 9. **Personal data stays local.** Tester lists, review reply drafts, health reports and analytics snapshots live in `.storebound/` (gitignored). Never put tester emails, reviewer names or crash logs into issues, docs or commits, and never ask for tester emails in chat (storebound masks them in its output). +10. **Console forms without an API: drive them yourself, agent-first.** `storebound console plan
--json` (MCP + `console_plan`) gives the start URL, steps, the answer sheet, the buttons you must never press and the verified UI + facts. Drive the page with your own browser tool, adapt to what it shows, run long flows as an in-page async loop + that writes `window.__log` (one evaluate call times out around 45 s), stop before the final button (Publish, + Submit, Create, a final Save) and tell the owner exactly what to press, then `storebound console verify `. + Never sign in, accept an agreement or grant a permission (a service account's app access) for the owner. + `storebound console fill` is the optional scripted fast path; it never blocks you. ## Tools @@ -54,7 +61,7 @@ These rules apply to every AI tool that uses storebound (skills, MCP server or t - `skills/appstore-first-release/SKILL.md`: Step-by-step first (and later) App Store release of a mobile app or game - `skills/aso-basics/SKILL.md`: App store optimization basics for a mobile app's or game's text - `skills/build-and-sign/SKILL.md`: Sign, build and upload a mobile app or game from the terminal -- `skills/console-autofill/SKILL.md`: 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 with storebound's own profile that the owner signs in to once (never a copy of the owner's Google cookies unless they opt in); 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 +- `skills/console-autofill/SKILL.md`: Fill App Store Connect and Google Play Console forms that have no API (New App, App Privacy, Play Create app, the IARC content rating, Target audience and content plus Ads; the App Store age rating is guidance because the API sets it). Agent-first by default - `skills/export-compliance/SKILL.md`: Answer App Store export compliance (encryption) for a mobile app or game correctly - `skills/listing-localization/SKILL.md`: Translate the store listing of a mobile app or game into more languages - `skills/migrate-from-fastlane/SKILL.md`: Move a mobile app or game from fastlane (deliver, supply, snapshot / screengrab, Appfile, Deliverfile) to storebound with `storebound import fastlane` diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index bbeb108..12f475b 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -33,6 +33,13 @@ These rules apply to every AI tool that uses storebound (skills, MCP server or t 9. **Personal data stays local.** Tester lists, review reply drafts, health reports and analytics snapshots live in `.storebound/` (gitignored). Never put tester emails, reviewer names or crash logs into issues, docs or commits, and never ask for tester emails in chat (storebound masks them in its output). +10. **Console forms without an API: drive them yourself, agent-first.** `storebound console plan --json` (MCP + `console_plan`) gives the start URL, steps, the answer sheet, the buttons you must never press and the verified UI + facts. Drive the page with your own browser tool, adapt to what it shows, run long flows as an in-page async loop + that writes `window.__log` (one evaluate call times out around 45 s), stop before the final button (Publish, + Submit, Create, a final Save) and tell the owner exactly what to press, then `storebound console verify `. + Never sign in, accept an agreement or grant a permission (a service account's app access) for the owner. + `storebound console fill` is the optional scripted fast path; it never blocks you. ## Tools @@ -56,7 +63,7 @@ These rules apply to every AI tool that uses storebound (skills, MCP server or t | `appstore-first-release` | Step-by-step first (and later) App Store release of a mobile app or game | | `aso-basics` | App store optimization basics for a mobile app's or game's text | | `build-and-sign` | Sign, build and upload a mobile app or game from the terminal | -| `console-autofill` | 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 with storebound's own profile that the owner signs in to once (never a copy of the owner's Google cookies unless they opt in); 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 | +| `console-autofill` | Fill App Store Connect and Google Play Console forms that have no API (New App, App Privacy, Play Create app, the IARC content rating, Target audience and content plus Ads; the App Store age rating is guidance because the API sets it). Agent-first by default | | `export-compliance` | Answer App Store export compliance (encryption) for a mobile app or game correctly | | `listing-localization` | Translate the store listing of a mobile app or game into more languages | | `migrate-from-fastlane` | Move a mobile app or game from fastlane (deliver, supply, snapshot / screengrab, Appfile, Deliverfile) to storebound with `storebound import fastlane` | diff --git a/AGENTS.md b/AGENTS.md index 68f7c2e..8498f20 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -34,6 +34,13 @@ These rules apply to every AI tool that uses storebound (skills, MCP server or t 9. **Personal data stays local.** Tester lists, review reply drafts, health reports and analytics snapshots live in `.storebound/` (gitignored). Never put tester emails, reviewer names or crash logs into issues, docs or commits, and never ask for tester emails in chat (storebound masks them in its output). +10. **Console forms without an API: drive them yourself, agent-first.** `storebound console plan --json` (MCP + `console_plan`) gives the start URL, steps, the answer sheet, the buttons you must never press and the verified UI + facts. Drive the page with your own browser tool, adapt to what it shows, run long flows as an in-page async loop + that writes `window.__log` (one evaluate call times out around 45 s), stop before the final button (Publish, + Submit, Create, a final Save) and tell the owner exactly what to press, then `storebound console verify `. + Never sign in, accept an agreement or grant a permission (a service account's app access) for the owner. + `storebound console fill` is the optional scripted fast path; it never blocks you. ## Tools @@ -55,7 +62,7 @@ These rules apply to every AI tool that uses storebound (skills, MCP server or t - `skills/appstore-first-release/SKILL.md`: Step-by-step first (and later) App Store release of a mobile app or game - `skills/aso-basics/SKILL.md`: App store optimization basics for a mobile app's or game's text - `skills/build-and-sign/SKILL.md`: Sign, build and upload a mobile app or game from the terminal -- `skills/console-autofill/SKILL.md`: 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 with storebound's own profile that the owner signs in to once (never a copy of the owner's Google cookies unless they opt in); 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 +- `skills/console-autofill/SKILL.md`: Fill App Store Connect and Google Play Console forms that have no API (New App, App Privacy, Play Create app, the IARC content rating, Target audience and content plus Ads; the App Store age rating is guidance because the API sets it). Agent-first by default - `skills/export-compliance/SKILL.md`: Answer App Store export compliance (encryption) for a mobile app or game correctly - `skills/listing-localization/SKILL.md`: Translate the store listing of a mobile app or game into more languages - `skills/migrate-from-fastlane/SKILL.md`: Move a mobile app or game from fastlane (deliver, supply, snapshot / screengrab, Appfile, Deliverfile) to storebound with `storebound import fastlane` diff --git a/CHANGELOG.md b/CHANGELOG.md index 91966cb..d54e6d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,30 @@ ## Unreleased +- Agent-first console mode. `storebound console plan [--json]` (read-only, offline; `--lookup` asks the App + Store Connect API for the app id) gives an AI agent everything to fill a console form with its own browser tool: + the start URL (deep links from apple.appId, play.developerId, the new play.appId and the remembered ids), ordered + steps (what to click by visible text), the answer sheet (each answer certain or ask-owner, with the console's exact + labels, curly quotes noted), the never-press list (Publish, Submit, Create, agreements, help panels), the known UI + facts per form and how to verify. `storebound console verify [--from-text ]` (read-only, exit 1 on a + mismatch) checks the result: App Privacy from the page text (per-type "Used for …", linked, tracking), Play Create + app from the input states, content rating and target audience from their summaries, the App Store record and age + rating from the API. MCP: `console_plan`, `console_verify` (read-only; `console fill` stays off MCP). The + console-autofill skill now puts the agent-driven mode first; `console fill` is the optional scripted fast path. + Pitfall 20. +- App Privacy, verified on a real console (2026-10-12): two information steps in each type dialog ("Let’s define two + important terms…", "To help put tracking into context…"), purpose checkboxes without an accessible name (matched by + the nearest parent text), "Definitions and Examples" never pressed. The scripted engine handles all of it. +- Play Create app, verified through Claude in Chrome (2026-10-12): the form's own URL + `/console/u/0/developers//create-new-app` (the engine opens it directly with a developer id); the App package + name field (from `play.packageName`, max 150) and its read-only "Check availability" ("Package name available"), + which storebound presses; the `` gotcha (the native input does not update the form's model: the + engine clicks the inner `[role=radio]`); the app dashboard `/developers//app//app-dashboard` (the app id + is remembered and deep-links the App content forms); after Create app the owner adds the new app to the service + account (Users and permissions > the service account > Add app), never an agent. The old note "the first AAB + upload sets the package name" is gone. +- Confirmed (2026-10-12): the earlier default copy of the owner's Google cookies DID sign the owner out of Play + Console in their real Chrome (pitfall 18). The copy has been opt-in since the change below. - Fix (`play images`): a phone-only project no longer aborts with "no sevenInchScreenshots ... (render first)". Play requires the 512 icon, the 1024 x 500 feature graphic and at least 2 screenshots (Play Console Help answer 9866151); 7" / 10" tablet screenshots are optional. `play images` (dry and real) uploads what exists (icon, feature graphic, diff --git a/GEMINI.md b/GEMINI.md index 389107d..14bb7d1 100644 --- a/GEMINI.md +++ b/GEMINI.md @@ -32,6 +32,13 @@ These rules apply to every AI tool that uses storebound (skills, MCP server or t 9. **Personal data stays local.** Tester lists, review reply drafts, health reports and analytics snapshots live in `.storebound/` (gitignored). Never put tester emails, reviewer names or crash logs into issues, docs or commits, and never ask for tester emails in chat (storebound masks them in its output). +10. **Console forms without an API: drive them yourself, agent-first.** `storebound console plan --json` (MCP + `console_plan`) gives the start URL, steps, the answer sheet, the buttons you must never press and the verified UI + facts. Drive the page with your own browser tool, adapt to what it shows, run long flows as an in-page async loop + that writes `window.__log` (one evaluate call times out around 45 s), stop before the final button (Publish, + Submit, Create, a final Save) and tell the owner exactly what to press, then `storebound console verify `. + Never sign in, accept an agreement or grant a permission (a service account's app access) for the owner. + `storebound console fill` is the optional scripted fast path; it never blocks you. ## Tools @@ -55,7 +62,7 @@ These rules apply to every AI tool that uses storebound (skills, MCP server or t | `appstore-first-release` | Step-by-step first (and later) App Store release of a mobile app or game | | `aso-basics` | App store optimization basics for a mobile app's or game's text | | `build-and-sign` | Sign, build and upload a mobile app or game from the terminal | -| `console-autofill` | 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 with storebound's own profile that the owner signs in to once (never a copy of the owner's Google cookies unless they opt in); 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 | +| `console-autofill` | Fill App Store Connect and Google Play Console forms that have no API (New App, App Privacy, Play Create app, the IARC content rating, Target audience and content plus Ads; the App Store age rating is guidance because the API sets it). Agent-first by default | | `export-compliance` | Answer App Store export compliance (encryption) for a mobile app or game correctly | | `listing-localization` | Translate the store listing of a mobile app or game into more languages | | `migrate-from-fastlane` | Move a mobile app or game from fastlane (deliver, supply, snapshot / screengrab, Appfile, Deliverfile) to storebound with `storebound import fastlane` | diff --git a/README.md b/README.md index 487fe76..9b0f83e 100644 --- a/README.md +++ b/README.md @@ -485,7 +485,14 @@ a terminal first and are never offered over MCP. - **RevenueCat (optional):** `storebound revenuecat diff | sync` mirrors products, subscriptions, entitlements and a default offering into a RevenueCat project (REST API v2, secret key from env / `.storebound/`). sync only creates and attaches, never deletes. Runtime entitlements, receipts and paywalls stay RevenueCat's job. Skill: `revenuecat-sync`. -- **Console autofill for forms without an API:** `storebound console fill ` (`apple-app-record`, +- **Console forms without an API, agent-first:** `storebound console plan [--json]` gives your AI agent the + start URL (deep links when the ids are known), the steps, the answer sheet from the config (each answer certain or + "ask owner", with the console's exact labels), the buttons it must never press and the UI facts verified on the real + consoles; the agent drives the page with its own browser tool (Claude in Chrome, a built-in browser, Playwright), + stops before the final button and tells you what to press. `storebound console verify [--from-text -]` then + checks the page against the config (App Privacy per-type summaries, Play Create app input states, summaries, or the + App Store Connect API) and exits 1 on a mismatch. Both are read-only MCP tools (`console_plan`, `console_verify`). +- **Scripted fast path:** `storebound console fill ` (`apple-app-record`, `apple-age-rating`, `apple-app-privacy`, `play-app-record`, `play-content-rating`, `play-target-audience`) opens a visible browser, goes to the form by itself (Apps > + > New App, the app's App Privacy, Play All apps > Create app, Play app list > App content > Start / Manage; @@ -508,8 +515,11 @@ a terminal first and are never offered over MCP. with several accounts and none set it stops and lists them. It fills App name (`play.name` or `app.name`, 30 characters), Default language, App or game (the profile) and Free or paid (`play.free`, else Free for IAP / ads: an owner check, a free app can never become paid). It never ticks the Developer Program Policies or US export laws - declarations and never presses the form's Create app: you do. The package name is set by the first AAB upload - (`storebound play upload --track internal` or `storebound flow run release`). + declarations and never presses the form's Create app: you do. With `play.developerId` it opens the form's own URL + (`/console/u/0/developers//create-new-app`); it fills App package name from `play.packageName` and presses the + read-only "Check availability", and clicks the inner `[role=radio]` of Play's material radios (the native input + does not update the form). After Create app, add the new app to your service account yourself (Users and + permissions > the service account > Add app): app-level permissions do not cover apps created later. When a page has changed it falls back to step-by-step guidance with copyable answers. **Console pages change; this is AI-assisted: review every field and save it yourself.** Desktop: "Fill in console" on the App info tab. Never over MCP. Skill: `console-autofill`. diff --git a/cli/api/wrapped.gen.ts b/cli/api/wrapped.gen.ts index cca8965..b92370c 100644 --- a/cli/api/wrapped.gen.ts +++ b/cli/api/wrapped.gen.ts @@ -28,7 +28,7 @@ export const WRAPPED: { apple: Record; play: Record; play: Record Promise, string]> = { 'ads apple': [() => import('./ads/apple'), 'Apple Ads (Search Ads): status | campaigns | report (spend, taps, installs, CPA per campaign / keyword, --csv); bid | budget | pause (dry run, asks first; never via MCP)'], 'web': [() => import('./web'), 'init (apple-app-site-association, assetlinks.json, Smart App Banner tag, optional landing page) | check (live files: content type, no redirects, ids match)'], 'revenuecat': [() => import('./revenuecat'), 'diff (catalog vs RevenueCat products, entitlements, default offering) | sync (dry run, asks first; never via MCP)'], - 'console fill': [() => import('./console/fill'), ' [--store apple|play]: AI-assisted autofill of console forms without an API in a visible Chromium you sign in to yourself; stops before Save / Submit'], + 'console plan': [() => import('./console/plan'), ' [--json]: agent-first console mode: start URL, steps, answer sheet (certain / ask owner, console labels), never-press list, UI facts and how to verify, for an AI agent that drives the browser itself; read-only'], + 'console verify': [() => import('./console/verify'), ' [--from-text ] [--json]: check a filled console form against the config (page text, input states or the App Store Connect API); read-only, exit 1 on a mismatch'], + 'console fill': [() => import('./console/fill'), ' [--store apple|play]: optional scripted fast path: autofill in a visible Chromium you sign in to yourself; stops before Save / Submit'], 'reviews': [() => import('./reviews'), 'list | draft (local reply drafts in .storebound/replies) | send (approved drafts only, asks first)'], 'originality': [() => import('./originality'), 'Guideline 4.3 check vs your other apps (--against ): listing text, name, icon and screenshot perceptual hashes, project structure; scores, flags, suggestions; offline, exit 1 on a near-duplicate'], 'listing': [() => import('./listing'), 'translate --to de-DE,ja [--from en-US] [--translate ""]: listing translation drafts per language (App Store name, subtitle, promo text, keywords, description; Play title, short, full) with store limits and keyword rules, marked until reviewed | check | reviewed --lang ; local files only'], diff --git a/cli/console/accounts.ts b/cli/console/accounts.ts index 7cb5636..b9cba98 100644 --- a/cli/console/accounts.ts +++ b/cli/console/accounts.ts @@ -24,12 +24,12 @@ import { homedir } from 'node:os'; import { dirname, join } from 'node:path'; import type { ShipConfig } from '../lib/config'; import type { FormId } from '../lib/console-answers'; -import { hasToken, type Name, NAV_BASES, type NavBases, type NavStep, navPlan, playDeveloperId, switchAllowed } from '../lib/console-forms'; +import { hasToken, type Name, NAV_BASES, type NavBases, type NavStep, navPlan, playAppIdOf, playDeveloperId, switchAllowed } from '../lib/console-forms'; import { ACTION_MS, accName, findAny, type Locator, navClick, navigate, type NavResult, type Page, poll, realRole, safeClick, signInPage, SignInSeen, type Stop, vis } from './fill'; // ---- the cache ---- -export interface PlayEntry { developerId: string; account?: string; at: string } +export interface PlayEntry { developerId: string; account?: string; /** The Play Console app id (/app//), read from the URL once the app is open. */ appId?: string; at: string } export interface AppleEntry { team: string; teamId?: string; at: string } export interface AccountsCache { version: 1; play: Record; apple: Record } @@ -44,7 +44,7 @@ export function readAccounts(file = accountsFile()): AccountsCache { let j: any; try { j = existsSync(file) ? JSON.parse(readFileSync(file, 'utf8')) : null; } catch { return out; } for (const [k, v] of Object.entries((j?.play ?? {}) as Record)) { - if (KEY.test(k) && typeof v?.developerId === 'string' && /^\d{1,25}$/.test(v.developerId)) out.play[k] = { developerId: v.developerId, ...(str(v.account) ? { account: v.account } : {}), at: str(v.at, 40) ? v.at : '' }; + if (KEY.test(k) && typeof v?.developerId === 'string' && /^\d{1,25}$/.test(v.developerId)) out.play[k] = { developerId: v.developerId, ...(str(v.account) ? { account: v.account } : {}), ...(typeof v.appId === 'string' && /^\d{1,25}$/.test(v.appId) ? { appId: v.appId } : {}), at: str(v.at, 40) ? v.at : '' }; } for (const [k, v] of Object.entries((j?.apple ?? {}) as Record)) { if (KEY.test(k) && str(v?.team)) out.apple[k] = { team: v.team, ...(typeof v.teamId === 'string' && /^[A-Z0-9]{10}$/.test(v.teamId) ? { teamId: v.teamId } : {}), at: str(v.at, 40) ? v.at : '' }; @@ -55,7 +55,9 @@ export function readAccounts(file = accountsFile()): AccountsCache { /** Remembers which account has the app (written atomically: the desktop app may read it at the same time). */ export function rememberPlay(pkg: string, e: Omit, file = accountsFile()): void { const c = readAccounts(file); - c.play[pkg] = { ...e, at: new Date().toISOString() }; + // the app id stays when the same account is remembered again without one + const keep = c.play[pkg]?.developerId === e.developerId && c.play[pkg]?.appId && !e.appId ? { appId: c.play[pkg]!.appId! } : {}; + c.play[pkg] = { ...e, ...keep, at: new Date().toISOString() }; write(c, file); } export function rememberApple(bundleId: string, e: Omit, file = accountsFile()): void { @@ -281,7 +283,18 @@ function notHere(r: NavResult, plan: NavStep[]): boolean { * `signIn` (the caller waits and calls again). */ export async function openAcross(page: Page, o: AcrossOpts): Promise<{ nav: NavResult; plan: NavStep[] }> { - try { return await across(page, o); } catch (e) { + try { + const r = await across(page, o); + // a Play form of an existing app is open: its URL carries the Play Console app id (/app//), remembered + // next to the developer id so `console plan` can deep-link the App content forms + const pkg = o.config.play?.packageName; + if (r.nav.ok && pkg && o.form.startsWith('play-') && o.form !== 'play-app-record') { + const dev = playDeveloperId(page.url()), app = playAppIdOf(page.url()); + const f = o.file ?? accountsFile(), account = readAccounts(f).play[pkg]?.account; + if (dev && app) rememberPlay(pkg, { developerId: dev, ...(account ? { account } : {}), appId: app }, f); + } + return r; + } catch (e) { if (e instanceof SignInSeen) return { nav: { ok: false, steps: [], failed: 'the account list', why: 'a sign-in page appeared', signIn: true }, plan: [] }; throw e; } diff --git a/cli/console/fill.ts b/cli/console/fill.ts index 22bd3fa..04407d9 100644 --- a/cli/console/fill.ts +++ b/cli/console/fill.ts @@ -72,6 +72,37 @@ async function find(root: Locator, role: string, names: Name[], label = false): const f = await first(tries); if (f.state !== 'none') return f; } + return role === 'checkbox' || role === 'radio' ? byParentText(root, role, names) : { state: 'none' }; +} + +/** + * The nearest non-empty texts around controls that has no accessible name of its own (no aria-label, aria-labelledby + * or associated