serve: desktop methods for play build / upload / share / app-id, apple build / upload / signing ensure / adhoc register + export, shots fonts / jpeg, install (dry run first; the host token answers their question)

This commit is contained in:
CRTkafa 2026-10-21 04:55:50 +07:00
parent 335a4124ab
commit ce1b1edcb8

View file

@ -63,7 +63,7 @@ export const CONSOLE_FORMS = ['apple-app-record', 'apple-age-rating', 'apple-app
/** 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', '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']);
const TOKEN_ANSWERS = new Set(['console_fill', 'play_upload', 'play_share', 'apple_upload', 'apple_signing_ensure', 'apple_adhoc_register']);
/** The one desktop exception to FORBIDDEN_ARGS: `scan --write-only` (scan.write, the Fix button of a privacy-labels
* finding) writes the detected declarations into the config, only after the native dialog showed its diff and the
@ -143,6 +143,52 @@ export const DESKTOP_ONLY: ToolDef[] = [
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 } },
args: i => [...opt('store', i.store), ...(i.with_automatic === true ? ['--with-automatic'] : [])] },
// Build, sign and test-distribution commands (docs/desktop-coverage.md: the More tools tab). Their own question is
// answered by the host token (TOKEN_ANSWERS) after the native dialog showed the dry run.
{ name: 'play_build', title: 'Build the Android app bundle', command: ['play', 'build'], write: true, network: false,
description: 'gradle bundleRelease (Flutter: flutter build appbundle) with the next free versionCode and the upload key from env / .storebound. The dry run shows the plan; nothing goes to a store.',
props: { flavor: { type: 'string', pattern: '^[A-Za-z0-9_]{1,60}$', description: 'Gradle product flavor' }, module: { type: 'string', pattern: '^[A-Za-z0-9_:-]{1,60}$', description: 'Gradle module (default app)' },
version_name: { type: 'string', pattern: '^[0-9A-Za-z.+-]{1,40}$', description: 'versionName (default: from the project)' } },
args: i => [...opt('flavor', i.flavor), ...opt('module', i.module), ...opt('version-name', i.version_name)] },
{ name: 'play_upload', title: 'Upload an AAB to a testing track', command: ['play', 'upload'], write: true, network: true,
description: 'Uploads the newest AAB of the build output to a Google Play testing track (internal, closed or open testing), versionCode checked first. Testers of that track get it; production stays an owner action.',
props: { track: { type: 'string', enum: ['internal', 'alpha', 'beta'], default: 'internal' }, status: { type: 'string', enum: ['completed', 'draft'], default: 'completed' },
name: { type: 'string', pattern: '^[^\\x00-\\x1f]{1,50}$', description: 'Release name (default: the version)' } },
args: i => [...opt('track', i.track), ...opt('status', i.status), ...opt('name', i.name)] },
{ name: 'play_share', title: 'Internal app sharing link', command: ['play', 'share'], write: true, network: true,
description: 'Uploads one AAB or APK to Google Play internal app sharing and prints the share link (anyone allowed to use the link can install it for 60 days).',
props: { aab: { type: 'string', pattern: '^(?![/-])(?!.*\\.\\.)[A-Za-z0-9 _./-]{1,500}\\.aab$', description: 'AAB path relative to the app folder' },
apk: { type: 'string', pattern: '^(?![/-])(?!.*\\.\\.)[A-Za-z0-9 _./-]{1,500}\\.apk$', description: 'APK path relative to the app folder' } },
args: i => [...opt('aab', i.aab), ...opt('apk', i.apk)] },
{ name: 'play_app_id', title: 'Play Console app id', command: ['play', 'app-id'], write: false, network: true,
description: 'Read-only: the Play Console app id (play.appId) for the package name, from the known ids, else the agent steps to find it.' },
{ name: 'apple_build', title: 'Build the iOS app', command: ['apple', 'build'], write: true, network: true,
description: 'xcodebuild archive + exportArchive with a generated ExportOptions.plist, the next build number and API key signing (Capacitor, React Native, Flutter, Unity). The dry run shows the plan; nothing goes to a store.',
props: { scheme: { type: 'string', pattern: '^[^\\x00-\\x1f]{1,100}$', description: 'Xcode scheme (default: from the config or the project)' }, configuration: { type: 'string', pattern: '^[A-Za-z0-9_ -]{1,40}$', description: 'Build configuration (default Release)' } },
args: i => [...opt('scheme', i.scheme), ...opt('configuration', i.configuration)] },
{ name: 'apple_upload', title: 'Upload an IPA', command: ['apple', 'upload'], write: true, network: true,
description: 'Uploads the newest exported IPA to App Store Connect (API build upload, or altool), build number checked first, and waits for processing. It shows up in TestFlight; submitting stays an owner action.',
props: { via: { type: 'string', enum: ['api', 'altool'], default: 'api' }, no_wait: { type: 'boolean', default: false, description: 'Do not wait for processing' } },
args: i => [...opt('via', i.via), ...(i.no_wait === true ? ['--no-wait'] : [])] },
{ name: 'apple_signing_ensure', title: 'Set up signing', command: ['apple', 'signing', 'ensure'], write: true, network: true, idempotent: true,
description: 'Creates what signing is missing on the Apple Developer team: bundle id capabilities, a distribution certificate (key kept in .storebound/signing) and the App Store profiles, installed for Xcode. The dry run lists the items.' },
{ name: 'apple_adhoc_register', title: 'Register test devices', command: ['apple', 'adhoc', 'register'], write: true, network: true, idempotent: true,
description: 'Registers the devices of apple.adhoc.devices (or the given UDID) on the Apple Developer team. Each device uses one of the yearly slots of its product family.',
props: { udid: { type: 'string', pattern: '^[A-Fa-f0-9-]{24,40}$', description: 'One device UDID' }, name: { type: 'string', pattern: '^[^\\x00-\\x1f]{1,50}$', description: 'Its device name' } },
args: i => [...opt('udid', i.udid), ...opt('name', i.name)] },
{ name: 'apple_adhoc_export', title: 'Export an ad-hoc IPA', command: ['apple', 'adhoc', 'export'], write: true, network: false,
description: 'xcodebuild -exportArchive of an archive with the ad-hoc / release-testing ExportOptions.plist; writes the IPA locally.',
props: { archive: { type: 'string', pattern: '^(?!-)(?!.*\\.\\.)[^\\x00-\\x1f]{1,1000}\\.xcarchive$', description: 'The .xcarchive path' } }, required: ['archive'], args: i => opt('archive', i.archive) },
{ name: 'shots_fonts', title: 'Download font subsets', command: ['shots', 'fonts'], write: true, network: true, idempotent: true,
description: 'Downloads the font subsets the screenshot copy needs (CJK, Arabic, Cyrillic ...) into the app folder.' },
{ name: 'shots_jpeg', title: 'JPEG copies of the Play renders', command: ['shots', 'jpeg'], write: true, network: false, idempotent: true,
description: 'Writes JPEG copies (quality 92) of the Play renders next to them, for faster Play uploads.',
props: { variant: { type: 'string', pattern: '^[a-z0-9][a-z0-9-]*$', description: 'A/B variant id from shots.variants (default: the base renders)' }, all_variants: { type: 'boolean', default: false, description: 'The base renders and every variant' } },
args: i => [...opt('variant', i.variant), ...(i.all_variants === true ? ['--variants'] : [])] },
{ name: 'agent_install', title: 'Install for an AI tool', command: ['install'], write: true, network: false, idempotent: true,
description: 'Installs the storebound skills and the MCP server for one AI tool (Claude, Codex, Cursor, Copilot, Windsurf, Gemini, OpenCode) for this app folder.',
props: { agent: { type: 'string', enum: ['claude', 'codex', 'cursor', 'copilot', 'windsurf', 'gemini', 'opencode'] }, no_mcp: { type: 'boolean', default: false }, no_skills: { type: 'boolean', default: false } },
required: ['agent'], args: i => [...opt('agent', i.agent), ...(i.no_mcp === true ? ['--no-mcp'] : []), ...(i.no_skills === true ? ['--no-skills'] : [])] },
];
const BUILD_NO = { type: 'string', pattern: '^\\d{1,9}(\\.\\d{1,9}){0,3}$', description: 'The build number (CFBundleVersion) the owner picked from the uploaded builds; never "the latest"' };
const PLATFORM = { type: 'string', enum: ['ios', 'macos', 'tvos', 'visionos'], description: 'App Store platform of the version (default: the one that has it; when several do, the command asks)' };