UNPKG

@capgo/cli

Version:
249 lines (248 loc) 13.6 kB
import type { AndroidOnboardingProgress, AndroidOnboardingStep } from '../android/types.js'; import type { AndroidEffectDeps } from '../android/flow.js'; import type { IosEffectDeps, IosStepCtx, IosStepView } from '../ios/flow.js'; import type { OnboardingProgress, OnboardingStep } from '../types.js'; import type { NextStepResult, Platform } from './contract.js'; import type { BuildOutputRecord } from '../../output-record.js'; import { androidViewForStep } from '../android/flow.js'; import { brokerBegin, brokerClear, brokerPoll } from './broker-session.js'; /** Facts gathered during preflight; the pure deciders branch only on these. */ export interface PreflightFacts { capacitorProject: boolean; appId?: string; platformsDetected: Platform[]; authenticated: boolean; appRegistered: boolean; androidProgress: AndroidOnboardingProgress | null; iosProgress: OnboardingProgress | null; } /** User input carried into the flow via next_step. */ interface OnboardingInput { platform?: string; serviceAccountJsonPath?: string; runBuild?: boolean; checkBuild?: boolean; keyId?: string; issuerId?: string; p8Path?: string; serviceAccountMethod?: 'generate' | 'existing'; playDeveloperId?: string; gcpProjectId?: string; gcpProjectName?: string; androidPackage?: string; saMethodChoice?: 'retry' | 'save-anyway' | 'oauth'; /** Set true at google-sign-in to (re)open the browser for a fresh OAuth — recovery when the browser didn't open, was closed, or the sign-in stalled. */ reopenSignIn?: boolean; /** At google-sign-in: open the broker sign-in link in the user's browser (true) or let them open it (false/omit). */ openSignInBrowser?: boolean; /** The confirmation code the user reads off the broker success page — releases the token on poll. */ confirmCode?: string; /** Answer to the resume prompt: 'continue' resumes the saved step, 'restart' wipes this platform's saved progress and begins again. */ resumeChoice?: 'continue' | 'restart'; credentialsExistChoice?: 'backup' | 'cancel'; keystoreMethod?: 'existing' | 'generate'; keystorePath?: string; keystoreStorePassword?: string; keystoreAlias?: string; keystoreKeyPassword?: string; keystoreNewAlias?: string; keystorePasswordMethod?: 'random' | 'manual'; keystoreCommonName?: string; /** Answer to the parked iOS verify-app gate (the TUI Select vocabulary). */ verifyAction?: 'pick' | 'create-new' | 'autofix' | 'continue' | 'recheck' | 'open' | 'reopen' | 'back' | 'cancel'; /** The picked App Store app's bundle id — only with verifyAction 'pick'. */ verifyAppId?: string; /** * Answer to the parked iOS cert-limit-prompt (S6b): the Apple resource id of * the Distribution certificate to revoke, or '__exit__' (the engine's * OPTION_CERT_LIMIT_EXIT sentinel) to stop. */ certToRevoke?: string; /** Answer to the parked iOS duplicate-profile-prompt (S6b). */ duplicateProfileAction?: 'delete' | 'exit'; /** * Answer to the parked iOS error recovery screen (S6b): 'retry' re-runs the * failing step (carried.retryStep), 'restart' wipes progress + session and * starts over, 'exit' stops, 'email-support' surfaces support instructions * (MCP-only arm — no host-side opens). */ errorAction?: 'retry' | 'restart' | 'exit' | 'email-support'; /** Answer to the CI-secrets choice steps (target-select / ask / overwrite / push-confirm / setup / failed). */ ciSecretAction?: 'github' | 'gitlab' | 'skip' | 'yes' | 'no' | 'replace' | 'retry' | 'continue' | 'confirm' | 'cancel'; /** Answer to ask-github-actions-setup ('no' maps to the persisted setupMode 'declined'). */ githubActionsSetup?: 'with-workflow' | 'secrets-only' | 'no'; /** Answer to ask-export-env (yes/no) and confirm-env-export-overwrite (replace/skip). */ exportEnvAction?: 'yes' | 'no' | 'replace' | 'skip'; /** Custom .env target path — only together with exportEnvAction 'yes'. */ envExportPath?: string; /** Answer to pick-package-manager. */ packageManager?: 'bun' | 'npm' | 'pnpm' | 'yarn'; /** Answer to pick-build-script: a script name, '__custom__', or '__skip__'. */ buildScript?: string; /** Answer to pick-build-script-custom: the exact custom build command. */ buildScriptCustom?: string; /** Answer to preview-workflow-file: write / view (returns the file text, re-asks) / cancel. */ workflowFileAction?: 'write' | 'view' | 'cancel'; /** Answer to the iOS setup-method fork: create fresh credentials via Apple, or import from this Mac's Keychain. */ setupMethod?: 'create-new' | 'import-existing'; /** Answer to import-distribution-mode ('__cancel__' switches to the create-new path). */ importDistribution?: 'app_store' | 'ad_hoc' | '__cancel__'; /** Answer to import-pick-identity: the chosen identity's SHA-1 (an option value), or '__cancel__' for create-new. */ identityChoice?: string; /** Answer to import-pick-profile: the chosen profile's UUID (an option value), or '__back__' to re-pick the identity. */ profileChoice?: string; /** Answer to the import-no-match-recovery hub. */ importRecoveryAction?: 'create' | 'provide-profile-path' | 'browser' | 'back'; /** Answer to import-portal-explanation (the manual Apple-portal walkthrough). */ portalAction?: 'use-create' | 'open-anyway' | 'use-file' | 'back'; /** Absolute path to a .mobileprovision file — answers import-provide-profile-path (the MCP's manual-path arm of the TUI's native picker). */ profilePath?: string; /** Answer to import-export-warning: 'go' exports from the Keychain (the one macOS permission dialog), 'back' re-picks the profile, 'exit' stops. */ exportConfirm?: 'go' | 'back' | 'exit'; } /** Decide the first/again step for a fresh or resumed session. */ export declare function decideStart(facts: PreflightFacts, progress: OnboardingProgress | null, deps: EngineDeps): Promise<NextStepResult>; /** * Map an interactive IosStepView into a NextStepResult — the iOS mirror of * mapAndroidView. State names reuse the engine step ids; option values mirror * the TUI Select values. Only NON-SECRET, view-derived data may appear here. */ export declare function mapIosView(view: IosStepView, facts: PreflightFacts, ctx?: IosStepCtx): NextStepResult; export declare function decideIos(facts: PreflightFacts, deps: EngineDeps, opts?: { verifyAction?: OnboardingInput['verifyAction']; verifyAppId?: string; certToRevoke?: string; duplicateProfileAction?: 'delete' | 'exit'; errorAction?: 'retry' | 'restart' | 'exit' | 'email-support'; /** import-pick-identity answer: an identity SHA-1 or '__cancel__'. */ identityChoice?: string; /** import-pick-profile answer: a profile UUID or '__back__'. */ profileChoice?: string; /** import-no-match-recovery answer. */ importRecoveryAction?: 'create' | 'provide-profile-path' | 'browser' | 'back'; /** import-portal-explanation answer. */ portalAction?: 'use-create' | 'open-anyway' | 'use-file' | 'back'; /** import-provide-profile-path answer: the .mobileprovision path. */ profilePath?: string; /** import-export-warning answer. */ exportConfirm?: 'go' | 'back' | 'exit'; /** * S9-S11: the explicit tail step a validated tail answer routed to * (drive() → applyMcpTailAnswer). Honored only while the slim tail * progress carries credentialsSaved — the same guard as the tail park. */ tailNext?: OnboardingStep; }): Promise<NextStepResult>; export declare function mapAndroidView(view: ReturnType<typeof androidViewForStep>, facts: PreflightFacts, opts?: { keystorePath?: string; keystorePassword?: string; }): NextStepResult; export declare function decideAndroid(facts: PreflightFacts, deps: EngineDeps, opts?: { signInProceed?: boolean; /** Drop any in-flight Google OAuth session and (re)open the browser for a fresh * sign-in — recovery for "still waiting" when the browser never opened / was closed. */ reopenSignIn?: boolean; /** At google-sign-in: open the broker sign-in link in the user's browser (vs. letting them open it). */ openSignInBrowser?: boolean; /** The confirmation code the user reads off the broker success page — released the token on the next poll. */ confirmCode?: string; /** * S9-S11: the explicit tail step a validated tail answer routed to * (drive() → applyMcpTailAnswer). Honored only while the slim tail * progress carries credentialsSaved — the same guard as the tail park. */ tailNext?: AndroidOnboardingStep; }): Promise<NextStepResult>; export declare function decideAdvance(facts: PreflightFacts, progress: OnboardingProgress | null, input: OnboardingInput | undefined, deps: EngineDeps): Promise<NextStepResult>; export interface EngineDeps { cwd: string; hasSavedKey: () => boolean; getAppId: () => Promise<string | undefined>; detectPlatforms: () => Promise<Platform[]>; isAppRegistered: (appId: string) => Promise<boolean>; loadProgress: (appId: string) => Promise<OnboardingProgress | null>; registerApp: (appId: string) => Promise<{ ok: true; } | { ok: false; alreadyExists: boolean; error: string; }>; loadAndroidProgress: (appId: string) => Promise<AndroidOnboardingProgress | null>; readBuildRecord: (path: string) => Promise<BuildOutputRecord | null>; buildRecordPath: (appId: string, platform: Platform) => string; /** * Remove a build record (and its QR png) left behind by an earlier build. * Called by runBuild BEFORE the hand-off so checkBuild can never read a * stale record as the new build's result (hostile-review 2026-06-12). * Optional so legacy fixtures keep working; production wires * removeBuildOutputRecord. */ clearBuildRecord?: (recordPath: string) => Promise<void>; /** * The shared iOS flow engine's IO deps (Apple API / CSR / fs / persistence), * pre-bound by the driver (buildIosEffectDeps in onboarding-tools.ts for * production; canned fakes in tests). decideIos threads the per-app carried * session state in on every effect run. Optional so legacy fixtures that * never enter the iOS path keep working — a missing helper inside surfaces * as a caught effect error, never a crash. */ iosEffectDeps?: IosEffectDeps; androidEffectDeps: AndroidEffectDeps; /** * Optional injectable broker OAuth session for testing. When provided, the engine uses these instead of the * disk-persisted broker-session.ts functions. Production omits this and relies on the broker session. */ oauthSession?: { begin: typeof brokerBegin; poll: typeof brokerPoll; clear: typeof brokerClear; }; /** * Write the generated/loaded Android keystore (.p12) to a file on disk so the * user has a durable copy after onboarding. Called once when the keystore phase * completes. Returns the absolute path of the written file. * * Optional — when omitted the keystore is kept in progress only (no file written). * Omitting does not break the flow; the keystore data is always in _keystoreBase64. */ writeKeystoreFile?: (appId: string, base64: string, alias: string) => Promise<string>; } export declare function gatherFacts(deps: EngineDeps): Promise<PreflightFacts>; export declare function runStart(deps: EngineDeps, platform?: Platform): Promise<NextStepResult>; export declare function runAdvance(deps: EngineDeps, input?: OnboardingInput): Promise<NextStepResult>; /** * Read-only: determine the onboarding state the user is currently on, WITHOUT * running any side effect. Mirrors the branch selection of decideStart/ * decideAndroid/decideIos (preflight → platform → resume step, with the same * ask-build → build-ready / .p8-chain → ios-api-key name mapping) but never * calls effects. */ export declare function resolveCurrentState(facts: PreflightFacts): string; /** State names the MCP constructs directly that are NOT engine step ids. */ export declare const MCP_ONLY_STATES: readonly string[]; /** * Engine step ids (present in the type tables) the MCP can NEVER emit as a * state name: * - TUI bootstrap / TUI-only screens: welcome, adding-platform, * the AI build-debug sub-flow (decideIos reroutes its entry to * 'build-failed'), the contact-support sub-flow (the MCP's error screen has * the email-support arm instead), the native file pickers (the MCP collects * paths as text), the google-sign-in-running spinner (the MCP parks on * 'google-sign-in' via its OAuth session), and view-workflow-diff (the MCP * folds the diff into preview-workflow-file's context — 'view' re-parks); * - the .p8 input chain, collapsed into the single 'ios-api-key' gate; * - 'ask-build', mapped to the shared 'build-ready' choice (decideBuildPhase); * - 'requesting-build', never run over MCP (the C2/D2 handoff + checkBuild * polling replace it). */ export declare const MCP_UNREACHABLE_STEPS: ReadonlySet<string>; /** * Read-only "explain the current step" entry point backing the * capgo_builder_onboarding_explain tool. Gathers facts (read-only) and returns a * plain-language explanation string. Never advances the flow or runs effects. */ export declare function explainOnboarding(deps: EngineDeps, input?: { state?: string; }): Promise<string>; export {};