From 26f0723641aa37f78e6dc2322dcfc4b475f6a545 Mon Sep 17 00:00:00 2001 From: CRT Date: Wed, 21 Oct 2026 05:07:58 +0700 Subject: [PATCH] games status: both stores' game services in one read-only report (CLI, MCP games_status, desktop read method); --json status output stays pure JSON --- cli/apple/game-center.ts | 2 +- cli/commands.ts | 1 + cli/data/flags.json | 6 ++++++ cli/games-status.ts | 9 +++++++++ cli/mcp-tools.ts | 3 +++ cli/play/games.ts | 4 ++-- cli/serve.ts | 2 +- 7 files changed, 23 insertions(+), 4 deletions(-) diff --git a/cli/apple/game-center.ts b/cli/apple/game-center.ts index fa5649e..35fb43a 100644 --- a/cli/apple/game-center.ts +++ b/cli/apple/game-center.ts @@ -65,7 +65,7 @@ export async function run(ctx: Ctx): Promise { const plan = planGameCenter(g, config.languages, s); const project = gcFindings(config, readProjectEntitlements(root)); - if (a.has('json')) { console.log(JSON.stringify({ ...stateSummary(s, plan), project, plan }, null, 2)); if (sub === 'status') return; } + if (a.has('json')) { console.log(JSON.stringify({ ...stateSummary(s, plan), project, plan }, null, 2)); return; } if (s.capability === false) warn(TAG, `the App ID ${bundleId} has no GAME_CENTER capability: storebound apple signing ensure --dry, then without --dry (games.gameCenter adds it)`); if (s.capability === null) warn(TAG, `bundle id ${bundleId} not found in Certificates, Identifiers & Profiles: storebound apple bundle`); for (const f of project) (f.level === 'ok' ? log : warn)(TAG, `${f.msg}${f.fix ? ` (${f.fix})` : ''}`); diff --git a/cli/commands.ts b/cli/commands.ts index b7a8f6a..d851e4c 100644 --- a/cli/commands.ts +++ b/cli/commands.ts @@ -61,6 +61,7 @@ export const COMMANDS: Record Promise, string]> = { 'play rollout': [() => import('./play/rollout'), 'staged rollout: --fraction x | --halt | --resume | --complete (asks first)'], 'play share': [() => import('./play/share'), 'internal app sharing: upload an AAB / APK, print the share link (asks first; --dry)'], 'play app-id': [() => import('./play/app-id'), '[--from-text ] [--write]: the Play Console app id (play.appId) by package name: known ids, else the agent steps (app list search / pages, capture) and the dry diff; --write saves it; read-only on Play'], + 'games status': [() => import('./games-status'), 'Game Center + Play Games Services on both stores, read-only: App ID capability, entitlement, games_APP_ID resource, detail / version switch, leaderboard and achievement counts vs the config, console links; --json'], 'play games': [() => import('./play/games'), '[status] Play Games Services leaderboards + achievements from games (Games Configuration API upsert, matched by playId / name; drafts only, the owner publishes); --dry, asks first'], 'play next-version-code': [() => import('./play/next-version-code'), 'next free versionCode (prints the number)'], 'apple bundle': [() => import('./apple/bundle'), 'register the bundle id; finds the app record'], diff --git a/cli/data/flags.json b/cli/data/flags.json index d9d9fe4..8c967b1 100644 --- a/cli/data/flags.json +++ b/cli/data/flags.json @@ -460,6 +460,12 @@ "from", "resume" ], + "games status": [ + "achievements", + "keychain", + "leaderboards", + "no-install" + ], "health": [ "apple", "days", diff --git a/cli/games-status.ts b/cli/games-status.ts index 2362309..0cedfe3 100644 --- a/cli/games-status.ts +++ b/cli/games-status.ts @@ -86,3 +86,12 @@ export function gameServicesFindings(s: GameServicesStatus): VFinding[] { } return out; } + +/** `storebound games status [--json]`: both stores' game services in one read-only report (desktop card, MCP). */ +export async function run(ctx: import('./lib/ctx').Ctx): Promise { + const s = await gameServicesStatus(ctx.loaded); + if (ctx.args.has('json')) { console.log(JSON.stringify(s, null, 2)); return; } + const { formatFindings } = await import('./lib/validate-rules'); + console.log(formatFindings(gameServicesFindings(s))); + for (const l of s.links) console.log(`${l.label}: ${l.url}`); +} diff --git a/cli/mcp-tools.ts b/cli/mcp-tools.ts index eddb107..bd3a0ba 100644 --- a/cli/mcp-tools.ts +++ b/cli/mcp-tools.ts @@ -217,6 +217,9 @@ export const TOOLS: ToolDef[] = [ { name: 'apple_encryption', title: 'Export compliance answer', command: ['apple', 'encryption'], write: true, network: true, idempotent: true, description: 'Answer export compliance for builds from apple.usesNonExemptEncryption in the config (never guesses).', props: { build: { type: 'string', description: 'Build number (default: every build without an answer)' } }, args: i => opt('build', i.build) }, + { name: 'games_status', title: 'Game services status (both stores)', command: ['games', 'status'], write: false, network: true, + description: 'Read-only Game Center and Play Games Services status: App ID GAME_CENTER capability, the entitlement, Game Center detail and version switch, the games_APP_ID resource vs games.playGames.appId, leaderboard / achievement counts on each store vs the config with pending changes, findings and absolute console links.', + args: () => ['--json'] }, { name: 'apple_game_center', title: 'Game Center sync', command: ['apple', 'game-center'], write: true, network: true, idempotent: true, description: 'Game Center from the config\'s games section: switch it on for the app (gameCenterDetails) and the editable version (gameCenterAppVersions), upsert leaderboards and achievements with their localizations by vendor id (create / PATCH only what differs; never deletes or archives), then read back. Nothing is submitted: they go to review with the next version. The App ID capability is signing ensure\'s (terminal).', args: () => [] }, diff --git a/cli/play/games.ts b/cli/play/games.ts index 83239a7..2e5e906 100644 --- a/cli/play/games.ts +++ b/cli/play/games.ts @@ -72,13 +72,13 @@ export async function run(ctx: Ctx): Promise { const pgs = hasAndroidProject(root) ? readPgsProject(root) : null; const project = pgs ? pgsFindings(config, pgs) : []; const appId = config.games!.playGames!.appId ?? pgs?.appId?.value; - for (const f of project) (f.level === 'ok' ? log : warn)(TAG, `${f.msg}${f.fix ? ` (${f.fix})` : ''}`); + if (!a.has('json')) for (const f of project) (f.level === 'ok' ? log : warn)(TAG, `${f.msg}${f.fix ? ` (${f.fix})` : ''}`); if (!appId) throw new Error('games.playGames.appId is not set and the Android project has no games_APP_ID: create the games project first (storebound console plan play-games-services), then set the id'); const c = gamesConfigClient(root); const have = await readPlayGames(c, appId); const locale = playDefaultLocale(config); const plan = planPlayGames(config.games!, config.languages, locale, have); - if (a.has('json')) console.log(JSON.stringify({ appId, leaderboards: have.leaderboards.length, achievements: have.achievements.length, project, plan }, null, 2)); + if (a.has('json')) { console.log(JSON.stringify({ appId, leaderboards: have.leaderboards.length, achievements: have.achievements.length, project, plan }, null, 2)); return; } if (sub === 'status' || a.has('dry')) { if (!a.has('json')) console.log(dryPlan(`play games (project ${appId})`, planRows(plan))); return; diff --git a/cli/serve.ts b/cli/serve.ts index e08378c..9b81220 100644 --- a/cli/serve.ts +++ b/cli/serve.ts @@ -54,7 +54,7 @@ export const DESKTOP_EXCLUDED = new Set(['play_release_testing', 'apple_phased_r export const FORBIDDEN_METHOD_WORDS = /submit|release|publish|rollout|resume|complete|send|start|stop|cancel|delete|remove|invite/i; /** Read tools whose command prints a structured result with --json. */ -export const JSON_TOOLS = new Set(['check', 'report', 'check_aso', 'aso_audit', 'aso_suggest', 'aso_competitors', 'scan', 'validate', 'health', 'health_issue', 'reviews_list', 'prices_diff', 'analytics_compare', 'apple_status', +export const JSON_TOOLS = new Set(['games_status', 'apple_game_center_status', 'play_games_status', 'check', 'report', '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', 'art_live', 'art_thumb']);