- `storebound console plan <form> [--json] [--lookup]`: read-only plan for an AI agent that drives the browser itself: start URL (deep links), ordered steps, answer sheet with the console's exact labels (certain / ask owner, curly quotes noted), never-press list, verified UI facts, driving guidance (in-page async loop + window.__log), verify instructions. All six forms. - `storebound console verify <form> [--from-text <file|->]`: App Privacy per-type summaries from page text, Play Create app from input states, content rating / target audience summaries, App Store record and age rating from the API; exit 1 on a mismatch. - MCP: console_plan, console_verify (read-only); console fill stays off MCP. - App Privacy (verified 2026-10-12): two information steps, unlabeled purpose checkboxes (nearest parent text), "Definitions and Examples" never pressed; fixture and engine follow. - Play Create app (verified 2026-10-12): /developers/<id>/create-new-app, App package name + read-only Check availability, material-radio inner [role=radio] click, app dashboard URL and play.appId, service account "Add app" as an owner step. - console-autofill skill rewritten agent-first; agents-core rule 10; README; pitfalls 12, 18 (confirmed sign-out), 19; CHANGELOG; play-first-release.
47 lines
2.9 KiB
TypeScript
47 lines
2.9 KiB
TypeScript
// Checks a console form against the config after the agent (or the owner) filled it. Read-only.
|
|
// storebound console verify <form> [--from-text <file|->] [--json]
|
|
// apple-app-privacy: the page text of the App Privacy page (the Data Types section's per-type summaries; the App Store
|
|
// Connect API has no App Privacy endpoint). play-app-record: the JSON of the plan's capture snippet (input states) or
|
|
// the page text. play-content-rating, play-target-audience: the summary page text. apple-app-record: the App Store
|
|
// Connect API (the app by bundle id; a GET) or, with --from-text, the page text. apple-age-rating: the API.
|
|
// Exit 1 on any mismatch, and when nothing could be matched. Pure logic in cli/lib/console-verify.ts. MCP: console_verify.
|
|
import { readFileSync } from 'node:fs';
|
|
import type { Ctx } from '../lib/ctx';
|
|
import { FORM_IDS, isForm } from '../lib/console-answers';
|
|
import { verifyAgeRatingApi, verifyAppRecordApi, type VerifyResult, verifyTable, verifyText } from '../lib/console-verify';
|
|
import { printJson } from '../lib/log';
|
|
|
|
async function readText(src: string): Promise<string> {
|
|
if (src === '-' || src === 'true') return Bun.stdin.text();
|
|
return readFileSync(src, 'utf8');
|
|
}
|
|
|
|
export async function run(ctx: Ctx): Promise<void> {
|
|
const { config, root } = ctx.loaded;
|
|
const args = ctx.args, json = args.has('json');
|
|
const form = args._[0];
|
|
if (!isForm(form)) throw new Error(`console verify <form> [--from-text <file|->]: one of ${FORM_IDS.join(', ')}`);
|
|
const from = args.get('from-text'), inline = args.get('text');
|
|
let r: VerifyResult;
|
|
if (from !== undefined || inline !== undefined) {
|
|
const text = inline ?? await readText(from!);
|
|
if (!text.trim()) throw new Error('console verify: the page text is empty');
|
|
r = verifyText(form, config, text);
|
|
} else if (form === 'apple-app-record' || form === 'apple-age-rating') {
|
|
const bundleId = config.apple?.bundleId;
|
|
if (!bundleId) throw new Error('console verify: apple.bundleId is not set');
|
|
const { ascClient } = await import('../apple/asc');
|
|
const c = await ascClient(root, bundleId, config.apple?.appId);
|
|
if (form === 'apple-app-record') {
|
|
const found = await c.asc('GET', `/v1/apps?filter[bundleId]=${encodeURIComponent(bundleId)}`);
|
|
const hit = (found.data as { attributes: { bundleId: string; name?: string; sku?: string; primaryLocale?: string } }[]).find(a => a.attributes.bundleId === bundleId);
|
|
r = verifyAppRecordApi(config, hit?.attributes ?? null);
|
|
} else {
|
|
const info = await c.editableAppInfo();
|
|
const age = await c.maybe(`/v1/appInfos/${info.id}/ageRatingDeclaration`);
|
|
r = verifyAgeRatingApi(config, (age?.data?.attributes ?? null) as Record<string, unknown> | null);
|
|
}
|
|
} else throw new Error(`console verify ${form}: pass the page text with --from-text <file|-> (run \`storebound console plan ${form}\` for what to capture)`);
|
|
if (json) printJson(r); else console.log(verifyTable(r));
|
|
if (!r.ok) process.exitCode = 1;
|
|
}
|