// The Play Console app id of this app (play.appId), found by package name. Read-only on Play Console. // storebound play app-id [--json] the id when known, else the agent's steps // storebound play app-id --from-text [--write] [--json] find it in captured app list pages // Known: play.appId, or the id storebound remembered when a Play form was open (~/.storebound/console-accounts.json). // Otherwise it is an agent step: the agent opens the developer account's app list (10 apps per page, a new app is not // on page 1; verified 2026-10-13), searches the package name or walks the pages, runs the capture snippet and pipes // the captures here; storebound finds the row with the package name and reads the id from its /app// link. The // play.appId change is shown as a dry diff; --write saves it into storebound.config.ts (an upsert). Right after // Create app, `storebound console verify play-app-record --url --write` takes it from the redirect instead. // Pure logic in cli/lib/play-app-id.ts. import { readFileSync } from 'node:fs'; import { basename } from 'node:path'; import type { Ctx } from '../lib/ctx'; import { log, printJson } from '../lib/log'; import { APP_LIST_CAPTURE, appIdChange, appIdDiff, appIdSteps, findPlayAppId, parseAppListPages, writePlayAppId } from '../lib/play-app-id'; import * as rt from '../lib/rt'; export async function run(ctx: Ctx): Promise { const { config, path } = ctx.loaded; const args = ctx.args, json = args.has('json'); const pkg = config.play?.packageName; if (!pkg) throw new Error('play app-id: play.packageName is not set'); const from = args.get('from-text'); const { readAccounts, withAccountIds } = await import('../console/accounts'); const cfg = withAccountIds(config); const cached = readAccounts().play[pkg]; const dev = cfg.play?.developerId ?? cached?.developerId ?? null; const file = basename(path || 'storebound.config.ts'); if (from === undefined) { const known = config.play?.appId ?? cached?.appId ?? null; const source = config.play?.appId ? 'play.appId' : cached?.appId ? 'remembered from an open Play form (~/.storebound/console-accounts.json)' : null; const change = known && !config.play?.appId ? appIdChange(config, known) : null; if (json) return printJson({ packageName: pkg, appId: known, source, steps: known ? [] : appIdSteps(cfg, dev), capture: APP_LIST_CAPTURE, ...(change ? { change } : {}) }); if (known) { console.log(`${pkg}: Play Console app id ${known} (${source})`); if (change) console.log(`${appIdDiff(change, file)} (dry: add it with \`storebound play app-id --from-text\` and --write, or put appId: '${known}' into play)`); return; } console.log(`${pkg}: the Play Console app id is not known yet. Find it in the console (read-only), then pipe the captures here:\n`); appIdSteps(cfg, dev).forEach((s, i) => console.log(` ${i + 1}. ${s}`)); console.log(`\nCapture snippet (one evaluate call in the app list page):\n${APP_LIST_CAPTURE}`); return; } const raw = from === '-' || from === 'true' ? await rt.stdinText() : readFileSync(from, 'utf8'); const pages = parseAppListPages(raw); const r = findPlayAppId(pages, pkg); if (!r.found) { const why = r.ambiguous ? `several rows hold ${pkg} (app ids ${r.ambiguous.join(', ')}): search the exact package name and capture again` : r.hasNext ? `not on the ${r.pages} captured page(s) and the list has a next page: press "Next page", capture again and pipe every capture (a JSON array or one per line)` : `not on the ${r.pages} captured page(s), and the list has no next page: is this the developer account that has the app? (search ${pkg} in the list's search box)`; if (json) printJson({ packageName: pkg, appId: null, ...r, next: why }); else console.log(`${pkg}: ${why}`); process.exitCode = 1; return; } const change = appIdChange(config, r.appId); const write = args.has('write') && !args.has('dry'); const wrote = write && path ? writePlayAppId(path, config, r.appId) : false; if (json) return printJson({ packageName: pkg, appId: r.appId, page: r.page, change, written: wrote }); console.log(`${pkg}: Play Console app id ${r.appId} (page ${r.page} of the captures)`); console.log(appIdDiff(change, file)); if (!change.changed) return; if (wrote) log('play', `wrote play.appId '${r.appId}' into ${file}`); else console.log('dry: run the same command with --write to save it'); }