store art from the live stores: storebound art pull (App Store screenshot sets and previews per locale and display type from imageAsset.templateUrl at the original size, the App Store icon from the newest build or the public artwork; per Play language icon, feature graphic, phone / 7" / 10" / TV / Wear screenshots as the =s0 originals checked against Play's sha256) into store/renders with pulled.json (sha256, store, URL host, store checksum); upsert (a re-run changes nothing, differing local files kept unless --force, the upload's file of a slot compared: Play .jpg, App Store .png, the App Store icon as a picture); onboard runs it; apple screenshots / previews and play images see unchanged pulled files as the store's own; check: render sources are notes with pulled art; local renders check: pulled files pass, store-empty sets not demanded; art live / thumb / replace; desktop Store art tab: live thumbnails per store, language and set (same / differs / not pulled), Pull from stores, store-only apps read-only, Replace with a file (image_pick, art.replace) in the lightbox and the art editor; boot check store art case; tests/art-pull.test.ts; console parity 76 rows (57 complete, 19 with the reason); store-screenshots skill (pull first for live apps), pitfall 59
This commit is contained in:
parent
49a73c90e4
commit
00be838b76
40 changed files with 1669 additions and 58 deletions
22
cli/serve.ts
22
cli/serve.ts
|
|
@ -41,6 +41,11 @@ const opt = (flag: string, v: unknown): string[] => (v === undefined || v === nu
|
|||
const lang = { type: 'string', pattern: '^[A-Za-z0-9_-]{1,20}(,[A-Za-z0-9_-]{1,20}){0,99}$', description: 'Comma-separated language ids from the config (default: all)' };
|
||||
/** A price sheet: any .csv file path the owner picked (read only), never an option. */
|
||||
export const PRICE_SHEET_RE = /^(?!-)[^\x00-\x1f]{1,1000}\.[cC][sS][vV]$/;
|
||||
/** A live art thumbnail: https on Apple's or Google's image CDN only. */
|
||||
export const ART_THUMB_RE = /^https:\/\/([a-z0-9-]+\.)*(mzstatic\.com|googleusercontent\.com|ggpht\.com)\/[^\s"'<>]{1,1500}$/;
|
||||
/** The picked image of art.replace (absolute, PNG / JPEG) and the app folder file it replaces (relative, no ..). */
|
||||
export const ART_FROM_RE = /^\/[^\x00-\x1f]{1,1000}\.([pP][nN][gG]|[jJ][pP][eE]?[gG])$/;
|
||||
export const ART_TO_RE = /^(?![/-])(?!.*\.\.)[A-Za-z0-9 _./-]{1,500}\.(png|jpe?g|PNG|JPE?G)$/;
|
||||
|
||||
/** MCP tools the desktop profile leaves out (sending builds to testers on Play, release state changes: the window has
|
||||
* the owner actions for those; workspace and release-train reads: the window has portfolio.status for those; the raw API reader api_get). */
|
||||
|
|
@ -51,12 +56,12 @@ export const FORBIDDEN_METHOD_WORDS = /submit|release|publish|rollout|resume|com
|
|||
/** Read tools whose command prints a structured result with --json. */
|
||||
export const JSON_TOOLS = new Set(['check', 'check_aso', 'aso_audit', 'aso_suggest', 'aso_competitors', 'scan', 'validate', 'health', 'health_issue', 'reviews_list', 'prices_diff', 'analytics_compare', 'apple_status',
|
||||
'apple_next_build', 'play_status', 'play_next_version_code', 'shots_check', 'apple_testflight_status', 'apple_ppo_status', 'catalog_diff',
|
||||
'project_icon', 'project_listings', 'reviews_drafts', 'project_declarations', 'project_listing_text', 'project_renders', 'project_render', 'prices_current', 'project_editable', 'listing_live']);
|
||||
'project_icon', 'project_listings', 'reviews_drafts', 'project_declarations', 'project_listing_text', 'project_renders', 'project_render', 'prices_current', 'project_editable', 'listing_live', 'art_live', 'art_thumb']);
|
||||
|
||||
/** Console forms `storebound console fill` knows (cli/console/fill.ts). */
|
||||
export const CONSOLE_FORMS = ['apple-app-record', 'apple-age-rating', 'apple-app-privacy', 'play-app-record', 'play-content-rating', 'play-target-audience'];
|
||||
/** Write tools that print a structured result with --json in the dry run and the real run (console.fill: the answers). */
|
||||
export const JSON_WRITES = new Set(['console_fill', 'owner_apple_submit', 'owner_apple_resubmit', 'project_edit', 'listing_set']);
|
||||
export const JSON_WRITES = new Set(['console_fill', 'owner_apple_submit', 'owner_apple_resubmit', 'project_edit', 'listing_set', 'art_replace']);
|
||||
/** Write tools whose command may ask its own question (confirm()): a verified host token answers it, as for owner actions. */
|
||||
const TOKEN_ANSWERS = new Set(['console_fill']);
|
||||
|
||||
|
|
@ -116,6 +121,17 @@ export const DESKTOP_ONLY: ToolDef[] = [
|
|||
props: { form: { type: 'string', enum: CONSOLE_FORMS, description: 'The console form' }, store: { type: 'string', enum: ['apple', 'play'], description: 'Default: from the form' },
|
||||
fresh_profile: { type: 'boolean', default: false, description: "Skip attaching to a Chrome that runs with remote debugging. storebound's own browser profile (the owner signs in there once) is always used otherwise; the owner's Chrome cookies are never copied here" } },
|
||||
required: ['form'], args: i => [String(i.form), ...opt('store', i.store), ...(i.fresh_profile === true ? ['--fresh-profile'] : [])] },
|
||||
{ name: 'art_live', title: 'Live store art', command: ['art', 'live'], write: false, network: true,
|
||||
description: 'Read-only: the store art that is live per store, language and set (App Store screenshot sets and previews of the version being prepared, else the live one, and the icon; Play icon, feature graphic and screenshots per language) with thumbnail URLs and whether the app folder has the same file (pulled.json).' },
|
||||
{ name: 'art_thumb', title: 'One live thumbnail', command: ['art', 'thumb'], write: false, network: true,
|
||||
description: 'One thumbnail of the live store art as a data: URL (at most 2 MB), from the App Store or Google Play image CDN only.',
|
||||
props: { url: { type: 'string', pattern: ART_THUMB_RE.source, description: 'A thumb URL from art.live' } },
|
||||
required: ['url'], args: i => ['--url', String(i.url)] },
|
||||
{ name: 'art_replace', title: 'Replace one store image', command: ['art', 'replace'], write: true, network: false, idempotent: true,
|
||||
description: 'Replaces one image of the app folder (a screenshot in the renders folder, or a frame a scene shows) with a PNG / JPEG file the owner picked. The dry run shows old -> new size and the store checks (size, alpha, bytes); an error stops it. Nothing goes to a store: upload with the Play / App Store apply after its dry run.',
|
||||
props: { from: { type: 'string', pattern: ART_FROM_RE.source, description: 'The picked file (absolute path)' },
|
||||
to: { type: 'string', pattern: ART_TO_RE.source, description: 'The image to replace, relative to the app folder' } },
|
||||
required: ['from', 'to'], args: i => ['--from', String(i.from), '--to', String(i.to)] },
|
||||
{ name: 'prices_current', title: 'Current prices', command: ['prices', 'export'], write: false, network: true,
|
||||
description: 'Read-only: the current price of every product on both stores per region (Apple manual prices, with with_automatic also the equalized ones). Writes nothing.',
|
||||
props: { store: { type: 'string', enum: ['apple', 'play'] }, with_automatic: { type: 'boolean', default: false } },
|
||||
|
|
@ -246,7 +262,7 @@ const SYSTEM: MethodDescriptor[] = [
|
|||
];
|
||||
|
||||
/** Read methods a store-only app (params.virtual, no app folder) may use: store reads that need only the store ids. */
|
||||
export const VIRTUAL_READS = new Set(['apple_status', 'play_status', 'health', 'health_issue', 'reviews_list', 'apple_testflight_status', 'apple_ppo_status', 'listing_live']);
|
||||
export const VIRTUAL_READS = new Set(['apple_status', 'play_status', 'health', 'health_issue', 'reviews_list', 'apple_testflight_status', 'apple_ppo_status', 'listing_live', 'art_live', 'art_thumb']);
|
||||
/** Writes a store-only app may run (no app folder: they send only what the call carries, never the generated config's
|
||||
* placeholder links). The confirm token binds them to the store ids (`virtual`) instead of a folder. */
|
||||
export const VIRTUAL_WRITES = new Set(['listing_set']);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue