@capgo/cli 8.23.4 → 8.24.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@capgo/cli",
3
3
  "type": "module",
4
- "version": "8.23.4",
4
+ "version": "8.24.1",
5
5
  "description": "A CLI to upload to capgo servers",
6
6
  "author": "Martin martin@capgo.app",
7
7
  "license": "Apache 2.0",
@@ -157,7 +157,7 @@
157
157
  "test:ios-verify-app": "bun test/test-ios-verify-app.mjs",
158
158
  "test:platform-flow-contract": "bun test/test-platform-flow-contract.mjs",
159
159
  "test:tail-engine-shared": "bun test/test-tail-engine-shared.mjs",
160
- "test": "bun run build && bun run test:helper-dce && bun run test:version-detection:setup && bun run test:bundle && bun run test:functional && bun run test:semver && bun run test:version-edge-cases && bun run test:regex && bun run test:upload && bun run test:fail-on-incompatible && bun run test:native-dependencies && bun run test:credentials && bun run test:credentials-validation && bun run test:android-service-account-validation && bun run test:build-zip-filter && bun run test:checksum && bun run test:build-needed && bun run test:ci-prompts && bun run test:ci-secrets && bun run test:android-onboarding-progress && bun run test:onboarding-telemetry && bun run test:v2-event-migration && bun run test:analytics && bun run test:analytics-error-category && bun run test:analytics-org-resolver && bun run test:supabase-perf && bun run test:preview-qr && bun run test:app-set-options && bun run test:mcp-analytics && bun run test:mcp-instructions && bun run test:mcp-live-update-onboarding && bun run test:mcp-stdout-guard && bun run test:mcp-platform-select && bun run test:mcp-explain-scopes && bun run test:mcp-oauth-reopen && bun run test:mcp-broker-oauth && bun run test:mcp-broker-session && bun run test:mcp-credentials-manage && bun run test:mcp-resume-prompt && bun run test:mcp-build-job && bun run test:mcp-build-tools && bun run test:app-created-source && bun run test:doctor-analytics && bun run test:posthog-exception && bun run test:build-platform-selection && bun run test:onboarding-recovery && bun run test:onboarding-progress && bun run test:onboarding-run-targets && bun run test:run-device-command && bun run test:init-app-conflict && bun run test:init-guardrails && bun run test:init-replay && bun run test:prompt-preferences && bun run test:esm-sdk && bun run test:mcp && bun run test:mcp-no-key-handshake && bun run test:auth-session && bun run test:version-detection && bun run test:platform-paths && bun run test:project-type-detection && bun run test:payload-split && bun run test:manifest-path-encoding && bun run test:macos-signing && bun run test:asc-key-protocol && bun run test:apple-api-import-helpers && bun run test:apple-api-verify-key && bun run test:bundle-id-detector && bun run test:apple-api-app-list && bun run test:app-verification && bun run test:pbxproj-parser && bun run test:ai-log-capture && bun run test:ai-analyze-flow && bun run test:cicd-failure-help && bun run test:ai-sse-parser && bun run test:ai-render-markdown && bun run test:ai-stream-markdown && bun run test:ai-onboarding-mode && bun run test:ai-fit && bun run test:platform-layout && bun run test:frame-fit && bun run test:onboarding-min-size && bun run test:min-size-gate && bun run test:shell-size-gate && bun run test:build-log-sanitize && bun run test:build-output-viewport && bun run test:diff-viewer-viewport && bun run test:build-complete-exit && bun run test:ai-analyze-stream && bun run test:support-mailto && bun run test:support-redact && bun run test:support-internal-log && bun run test:support-help-menu && bun run test:support-contact && bun run test:support-upload-prompt && bun run test:support-bundle-files && bun run test:self-update && bun run test:update-prompt && bun run test:apple-api-cert-create && bun run test:android-tail-engine && bun run test:android-tail-render && bun run test:android-tail-routing && bun run test:dev-gate-stripped && bun run test:frame-fit-ios-shared && bun run test:ios-confirm-app-id && bun run test:ios-create-new && bun run test:ios-e2e && bun run test:ios-flow-contract && bun run test:ios-import-discovery && bun run test:ios-import-export && bun run test:ios-import-pickers && bun run test:ios-import-recovery && bun run test:ios-recovery && bun run test:ios-resume && bun run test:ios-tail-handoff && bun run test:ios-tui-render && bun run test:p8-error && bun run test:ios-tui-routing && bun run test:ios-updater-sync-validation && bun run test:ios-verify-app && bun run test:platform-flow-contract && bun run test:tail-engine-shared && bun run test:prescan && bun run test:android-reporting-api && bun run test:android-app-verification && bun run test:android-rename",
160
+ "test": "bun run build && bun run test:helper-dce && bun run test:version-detection:setup && bun run test:bundle && bun run test:functional && bun run test:semver && bun run test:version-edge-cases && bun run test:regex && bun run test:upload && bun run test:fail-on-incompatible && bun run test:native-dependencies && bun run test:credentials && bun run test:credentials-validation && bun run test:android-service-account-validation && bun run test:build-zip-filter && bun run test:checksum && bun run test:build-needed && bun run test:ci-prompts && bun run test:ci-secrets && bun run test:android-onboarding-progress && bun run test:onboarding-telemetry && bun run test:v2-event-migration && bun run test:analytics && bun run test:analytics-error-category && bun run test:analytics-org-resolver && bun run test:supabase-perf && bun run test:preview-qr && bun run test:app-set-options && bun run test:mcp-analytics && bun run test:mcp-instructions && bun run test:mcp-live-update-onboarding && bun run test:mcp-stdout-guard && bun run test:mcp-platform-select && bun run test:mcp-explain-scopes && bun run test:mcp-oauth-reopen && bun run test:mcp-broker-oauth && bun run test:mcp-broker-session && bun run test:mcp-credentials-manage && bun run test:mcp-resume-prompt && bun run test:mcp-build-job && bun run test:mcp-build-tools && bun run test:app-created-source && bun run test:doctor-analytics && bun run test:posthog-exception && bun run test:build-platform-selection && bun run test:onboarding-recovery && bun run test:onboarding-progress && bun run test:onboarding-run-targets && bun run test:run-device-command && bun run test:init-app-conflict && bun run test:init-guardrails && bun run test:init-replay && bun run test:prompt-preferences && bun run test:esm-sdk && bun run test:mcp && bun run test:mcp-no-key-handshake && bun run test:auth-session && bun run test:version-detection && bun run test:platform-paths && bun run test:project-type-detection && bun run test:payload-split && bun run test:manifest-path-encoding && bun run test:macos-signing && bun run test:asc-key-protocol && bun run test:apple-api-import-helpers && bun run test:apple-api-verify-key && bun run test:bundle-id-detector && bun run test:apple-api-app-list && bun run test:app-verification && bun run test:pbxproj-parser && bun run test:ai-log-capture && bun run test:ai-analyze-flow && bun run test:cicd-failure-help && bun run test:ai-sse-parser && bun run test:ai-render-markdown && bun run test:ai-stream-markdown && bun run test:ai-onboarding-mode && bun run test:ai-fit && bun run test:platform-layout && bun run test:frame-fit && bun run test:onboarding-min-size && bun run test:min-size-gate && bun run test:shell-size-gate && bun run test:build-log-sanitize && bun run test:build-output-viewport && bun run test:diff-viewer-viewport && bun run test:build-complete-exit && bun run test:ai-analyze-stream && bun run test:support-mailto && bun run test:support-redact && bun run test:support-internal-log && bun run test:support-help-menu && bun run test:support-contact && bun run test:support-upload-prompt && bun run test:support-bundle-files && bun run test:self-update && bun run test:update-prompt && bun run test:apple-api-cert-create && bun run test:android-tail-engine && bun run test:android-tail-render && bun run test:android-tail-routing && bun run test:dev-gate-stripped && bun run test:frame-fit-ios-shared && bun run test:ios-confirm-app-id && bun run test:ios-create-new && bun run test:ios-e2e && bun run test:ios-flow-contract && bun run test:ios-import-discovery && bun run test:ios-import-export && bun run test:ios-import-pickers && bun run test:ios-import-recovery && bun run test:ios-recovery && bun run test:ios-resume && bun run test:ios-tail-handoff && bun run test:ios-tui-render && bun run test:p8-error && bun run test:ios-tui-routing && bun run test:ios-updater-sync-validation && bun run test:ios-verify-app && bun run test:platform-flow-contract && bun run test:tail-engine-shared && bun run test:prescan && bun run test:android-reporting-api && bun run test:android-app-verification && bun run test:android-rename && bun run test:appflow-auth && bun run test:appflow-api-map && bun run test:appflow-validate && bun run test:appflow-flow && bun run test:appflow-gapfill && bun run test:appflow-engine && bun run test:appflow-tail && bun run test:appflow-fetch && bun run test:appflow-sa-decode",
161
161
  "test:build-platform-selection": "bun test/test-build-platform-selection.mjs",
162
162
  "test:ai-log-capture": "bun test/test-ai-log-capture.mjs",
163
163
  "test:ai-analyze-flow": "bun test/test-ai-analyze-flow.mjs",
@@ -185,10 +185,19 @@
185
185
  "test:support-upload-prompt": "bun test/test-support-upload-prompt.mjs",
186
186
  "test:support-bundle-files": "bun test/test-support-bundle-files.mjs",
187
187
  "test:mcp-live-update-onboarding": "bun test/test-mcp-live-update-onboarding.mjs",
188
+ "test:app-set-options": "bun test/test-app-set-options.mjs",
188
189
  "test:android-reporting-api": "bun test/test-android-reporting-api.mjs",
189
190
  "test:android-app-verification": "bun test/test-android-app-verification.mjs",
190
191
  "test:android-rename": "bun test/test-android-rename.mjs",
191
- "test:app-set-options": "bun test/test-app-set-options.mjs"
192
+ "test:appflow-auth": "bun test/test-appflow-auth.mjs",
193
+ "test:appflow-api-map": "bun test/test-appflow-api-map.mjs",
194
+ "test:appflow-validate": "bun test/test-appflow-validate.mjs",
195
+ "test:appflow-flow": "bun test/test-appflow-flow.mjs",
196
+ "test:appflow-gapfill": "bun test/test-appflow-gapfill.mjs",
197
+ "test:appflow-engine": "bun test/test-appflow-engine.mjs",
198
+ "test:appflow-tail": "bun test/test-appflow-tail.mjs",
199
+ "test:appflow-fetch": "bun test/test-appflow-fetch.mjs",
200
+ "test:appflow-sa-decode": "bun test/test-appflow-sa-decode.mjs"
192
201
  },
193
202
  "dependencies": {
194
203
  "@inkjs/ui": "^2.0.0",
@@ -0,0 +1,59 @@
1
+ export declare const stripDataUri: (s: string) => string;
2
+ export declare const bundleIdFromAppIdentifier: (ai: string) => string;
3
+ /** Thrown by rest()/gql() on any non-2xx, GraphQL error payload, or parse failure. */
4
+ export declare class AppflowApiError extends Error {
5
+ readonly status: number | string;
6
+ constructor(message: string, status: number | string);
7
+ }
8
+ interface RawIosProvisioningProfile {
9
+ application_identifier?: string;
10
+ name?: string;
11
+ provisioning_profile_file?: string;
12
+ }
13
+ interface RawIosSigning {
14
+ cert_file?: string;
15
+ cert_password?: string;
16
+ provisioning_profiles?: RawIosProvisioningProfile[];
17
+ }
18
+ interface RawIosDistribution {
19
+ user_name?: string;
20
+ app_specific_password?: string;
21
+ apple_app_id?: string | number;
22
+ team_id?: string;
23
+ }
24
+ interface RawAndroidSigning {
25
+ keystore_file?: string;
26
+ keystore_password?: string;
27
+ key_password?: string;
28
+ key_alias?: string;
29
+ }
30
+ interface RawAndroidDistribution {
31
+ json_key_file?: string;
32
+ }
33
+ export declare function mapIosSigning(raw: RawIosSigning | null | undefined): Record<string, string>;
34
+ export declare function mapIosDistribution(raw: RawIosDistribution | null | undefined): Record<string, string>;
35
+ export declare function mapAndroidSigning(raw: RawAndroidSigning | null | undefined): Record<string, string>;
36
+ export declare function mapAndroidDistribution(raw: RawAndroidDistribution | null | undefined): Record<string, string>;
37
+ export declare const redactTrace: (method: string, url: string, status: number | string, shapeKeys: string[]) => string;
38
+ export interface AppflowOrg {
39
+ id?: string;
40
+ name?: string;
41
+ slug: string;
42
+ }
43
+ export interface AppflowApp {
44
+ id: string;
45
+ name?: string;
46
+ slug?: string;
47
+ nativeType?: string;
48
+ }
49
+ export declare function createAppflowApi(token: string, log?: (s: string) => void): {
50
+ listOrgs(): Promise<AppflowOrg[]>;
51
+ listApps(slug: string): Promise<AppflowApp[]>;
52
+ listCertificates(appId: string): Promise<any[]>;
53
+ listDistribution(appId: string): Promise<any[]>;
54
+ fetchIosSigning(appId: string, tag: string): Promise<Record<string, string>>;
55
+ fetchAndroidSigning(appId: string, tag: string): Promise<Record<string, string>>;
56
+ fetchIosDistribution(appId: string, id: number | string): Promise<Record<string, string>>;
57
+ fetchAndroidDistribution(appId: string, id: number | string): Promise<Record<string, string>>;
58
+ };
59
+ export {};
@@ -0,0 +1,19 @@
1
+ export interface AppflowToken {
2
+ access_token: string;
3
+ refresh_token?: string;
4
+ expires_in: number;
5
+ id_token?: string;
6
+ capturedAtMs: number;
7
+ }
8
+ export declare function pkce(): {
9
+ verifier: string;
10
+ challenge: string;
11
+ };
12
+ export declare function buildAuthorizeUrl(challenge: string, state: string, nonce: string): string;
13
+ export declare function isExpired(t: Pick<AppflowToken, 'expires_in' | 'capturedAtMs'>, marginMs?: number): boolean;
14
+ export declare function sanitizeOauthError(raw: string | null | undefined): string;
15
+ export declare function loginWithBrowser(opts?: {
16
+ openBrowser?: (url: string) => void;
17
+ signal?: AbortSignal;
18
+ }): Promise<AppflowToken>;
19
+ export declare function refresh(token: AppflowToken): Promise<AppflowToken>;
@@ -0,0 +1,43 @@
1
+ import { Buffer } from 'node:buffer';
2
+ import type { AppflowEffectDeps } from './flow.js';
3
+ import type { AppflowProgress } from './types.js';
4
+ import type { AppflowToken } from './auth.js';
5
+ /** Load a cached Appflow token, or null when absent/unreadable. Never throws. */
6
+ export declare function loadAppflowToken(): AppflowToken | null;
7
+ /** Persist an Appflow token to the credentials dir (0700 dir / best-effort). Never throws. */
8
+ /** Persist an Appflow token to the credentials dir atomically (0700 dir / 0600
9
+ * file, temp+rename). Best-effort and never throws: a failure just means the next
10
+ * run re-auths. Uses the shared safe-write helpers (symlink-guarded, atomic). */
11
+ export declare function saveAppflowToken(token: AppflowToken): void;
12
+ /**
13
+ * PLAY_CONFIG_JSON is stored base64-encoded (the same convention the native
14
+ * android flow uses — see android/types.ts). Decode it to the raw service-account
15
+ * JSON bytes for validation; if the value is already raw JSON (starts with '{'),
16
+ * pass it through as utf8. Exported for unit testing the encoding contract.
17
+ */
18
+ export declare function serviceAccountJsonBytes(value: string): Buffer;
19
+ /**
20
+ * Reader: load a user-provided App Store Connect API key (.p8) file and return
21
+ * its base64-encoded bytes, ready to store as APPLE_KEY_CONTENT (the same
22
+ * encoding the native iOS flow and the generate path use). Reads the raw bytes
23
+ * (a .p8 is PEM text, but we base64 the file as-is to match the generate path).
24
+ * Advisory: returns null on any error (missing / unreadable file), never throws.
25
+ */
26
+ export declare function readP8File(path: string): Promise<string | null>;
27
+ /**
28
+ * Build the production AppflowEffectDeps for a given app id. The `packageName`
29
+ * (the Play package, when known) sharpens the service-account probe; pass the
30
+ * appId as a fallback. Token cache, browser, logger, and validators are all
31
+ * wired here so the engine and the TUI share one source of truth.
32
+ */
33
+ export declare function buildAppflowEffectDeps(opts?: {
34
+ appId?: string;
35
+ packageName?: string;
36
+ }): AppflowEffectDeps;
37
+ /**
38
+ * Persist the collected per-platform Capgo creds into the REAL credential store
39
+ * (the same path the native flows use), so the build/validate/CI tail cannot
40
+ * tell migrated creds from natively-set-up ones. Writes only the platforms that
41
+ * actually collected creds. Returns the platforms written.
42
+ */
43
+ export declare function persistAppflowCredentials(appId: string, progress: AppflowProgress, local?: boolean): Promise<('ios' | 'android')[]>;
@@ -0,0 +1,94 @@
1
+ import type { PlatformFlow, StepView } from '../flow/contract';
2
+ import type { AppflowInput, AppflowProgress, AppflowStep } from './types';
3
+ import type { AppflowToken } from './auth';
4
+ import type { TailStep } from '../tail/flow.js';
5
+ import type { AppflowTailDepsOptions } from './tail.js';
6
+ export interface AppflowEffectResult {
7
+ progress: AppflowProgress;
8
+ next?: AppflowStep;
9
+ transient?: Record<string, unknown>;
10
+ }
11
+ export interface AppflowValidationResult {
12
+ id: 'sa' | 'keystore' | 'app-password' | 'p12';
13
+ status: 'pass' | 'warn' | 'skipped';
14
+ message: string;
15
+ }
16
+ export interface AppflowGenerateResult {
17
+ ok: boolean;
18
+ creds?: Record<string, string>;
19
+ message?: string;
20
+ }
21
+ export interface AppflowEffectDeps {
22
+ appId?: string;
23
+ log?: (s: string) => void;
24
+ loadToken?: () => AppflowToken | null;
25
+ saveToken?: (t: AppflowToken) => void;
26
+ openBrowser?: (url: string) => void;
27
+ validateServiceAccountJson?: (json: string, packageName?: string) => Promise<{
28
+ ok: boolean;
29
+ reason?: string;
30
+ }>;
31
+ tryUnlockPrivateKey?: (keystoreB64: string, storePass: string, alias: string) => Promise<boolean>;
32
+ validateAppleAppPassword?: (user: string, pw: string) => Promise<{
33
+ valid: boolean;
34
+ message?: string;
35
+ kind?: 'authenticated' | 'rejected' | 'unreachable';
36
+ }>;
37
+ validateP12?: (p12B64: string, password: string) => Promise<boolean>;
38
+ generateIosP8Key?: () => Promise<AppflowGenerateResult>;
39
+ generateAndroidServiceAccount?: (opts: {
40
+ packageName?: string;
41
+ }) => Promise<AppflowGenerateResult>;
42
+ readP8File?: (path: string) => Promise<string | null>;
43
+ carried?: Record<string, unknown>;
44
+ tailOptions?: AppflowTailDepsOptions;
45
+ }
46
+ export declare function isAppflowTailStep(step: AppflowStep): step is TailStep;
47
+ export declare function autoSelect<T>(items: T[]): T | 'prompt' | null;
48
+ export interface AppflowCert {
49
+ tag?: string;
50
+ name?: string;
51
+ type?: string;
52
+ credentials?: {
53
+ ios?: unknown;
54
+ android?: unknown;
55
+ };
56
+ }
57
+ export interface AppflowDistCred {
58
+ id?: number | string;
59
+ type?: string;
60
+ name?: string;
61
+ }
62
+ export declare function decideAfterFetchSigning(progress: Pick<AppflowProgress, 'scope' | 'migratable'>): AppflowStep;
63
+ export declare function platformsToBuild(progress: Pick<AppflowProgress, 'scope' | 'ios' | 'android'>): ('ios' | 'android')[];
64
+ export declare function runValidations(progress: AppflowProgress, deps?: AppflowEffectDeps): Promise<AppflowValidationResult[]>;
65
+ export declare function appflowViewForStep(step: AppflowStep, progress: AppflowProgress, ctx?: Record<string, unknown>): StepView;
66
+ /**
67
+ * Decode the `email` claim from an Appflow token's id_token JWT WITHOUT verifying
68
+ * the signature (jsonwebtoken.decode) — we only need the human-readable account
69
+ * identity to surface which Appflow account is being migrated, not to trust it.
70
+ * Returns undefined when there is no id_token or no string email claim.
71
+ */
72
+ export declare function appflowAccountEmail(token: {
73
+ id_token?: string;
74
+ } | null | undefined): string | undefined;
75
+ export declare function applyAppflowInput(step: AppflowStep, progress: AppflowProgress, input: AppflowInput): AppflowProgress;
76
+ export declare function getAppflowResumeStep(progress: AppflowProgress | null): AppflowStep;
77
+ /**
78
+ * Resume the inline build phase after the user chose 'build'. The migration is
79
+ * single-platform, so the build enters the shared tail at saving-credentials for
80
+ * the migrated platform and finishes ('done') once that build run completes.
81
+ */
82
+ export declare function getAppflowBuildResumeStep(progress: AppflowProgress): AppflowStep;
83
+ export declare function nextTailStep(step: TailStep, value: string | undefined, progress: AppflowProgress): TailStep;
84
+ /**
85
+ * Record the migration's single inline tail run as complete (reached at
86
+ * build-complete via any path — built, skipped, or failed). Returns the next
87
+ * overall appflow step, which is always 'done' for a single-platform migration.
88
+ */
89
+ export declare function markTailRunComplete(progress: AppflowProgress): {
90
+ progress: AppflowProgress;
91
+ next: AppflowStep;
92
+ };
93
+ export declare function runAppflowEffect(step: AppflowStep, progress: AppflowProgress, deps: AppflowEffectDeps): Promise<AppflowEffectResult>;
94
+ export declare const appflowFlow: PlatformFlow<AppflowStep, AppflowProgress, AppflowInput>;
@@ -0,0 +1,44 @@
1
+ import type { TailEffectDeps } from '../tail/flow.js';
2
+ import type { AppflowProgress } from './types.js';
3
+ /**
4
+ * The tail-facing progress shape. The shared tail reads `appId` (required),
5
+ * plus the tail-input fields the appflow driver records on `AppflowProgress`
6
+ * (setupMode / ciSecretTarget / selectedPackageManager / buildScriptChoice /
7
+ * envExportTargetPath). `AppflowProgress` carries every one of those as an
8
+ * optional field, so it satisfies `TailEffectProgress` once `appId` is present.
9
+ */
10
+ export type AppflowTailProgress = AppflowProgress & {
11
+ appId: string;
12
+ };
13
+ /**
14
+ * Build the saved-credential SHAPE for the chosen platform from the migrated
15
+ * Capgo creds. The migration already mapped Appflow's signing/distribution into
16
+ * the Capgo field names (progress.ios / progress.android are Record<string,string>),
17
+ * so the "build" here is just selecting the right platform map. Throws on an empty
18
+ * map — the same fail-fast guard the native builders use.
19
+ */
20
+ export declare function buildAppflowSavedCredentials(progress: AppflowTailProgress, platform: 'ios' | 'android'): Record<string, string>;
21
+ export interface AppflowTailDepsOptions {
22
+ /** Capgo API key flag (overrides the saved key, like the native drivers). */
23
+ apikey?: string;
24
+ /** Gateway override threaded into the build request options. */
25
+ supaHost?: string;
26
+ /** Builder journey id threaded into the build request options. */
27
+ journeyId?: string;
28
+ /** Build VIEWER sink — every requesting-build line streams here (fullscreen pane). */
29
+ onBuildOutput?: (line: string) => void;
30
+ /** Side-log sink (✔ Credentials saved, ✔ Uploaded …). */
31
+ onLog?: (message: string, color?: string) => void;
32
+ /** The driver-held transient threaded back into each effect (NEVER persisted). */
33
+ carried?: TailEffectDeps<AppflowTailProgress>['carried'];
34
+ signal?: AbortSignal;
35
+ }
36
+ /**
37
+ * Adapt the appflow flow's collected state into a platform-neutral
38
+ * `TailEffectDeps` for `platform`, reusing the SAME shared CLI building blocks
39
+ * the iOS/Android drivers inject. Mirrors ios/flow.ts's `toTailDeps` 1:1; the
40
+ * only appflow-specific pieces are the credential SHAPE (selected straight from
41
+ * the migrated maps) and the thin no-disk persistence (the migration keeps no
42
+ * progress.json).
43
+ */
44
+ export declare function toAppflowTailDeps(platform: 'ios' | 'android', options?: AppflowTailDepsOptions): TailEffectDeps<AppflowTailProgress>;
@@ -0,0 +1,50 @@
1
+ import type { AppflowToken } from './auth';
2
+ import type { TailStep } from '../tail/flow.js';
3
+ import type { BuildScriptChoice, PackageManager } from '../workflow-generator.js';
4
+ import type { CiSecretTarget } from '../ci-secrets.js';
5
+ export type AppflowOwnStep = 'explain' | 'authenticating' | 'fetch-orgs' | 'select-org' | 'fetch-apps' | 'select-app' | 'fetch-signing' | 'select-ios-cert' | 'select-android-cert' | 'no-signing-submenu' | 'fetch-distribution' | 'select-ios-dist' | 'select-android-dist' | 'ios-dist-gapfill' | 'android-dist-gapfill' | 'p8-source-select' | 'input-p8-path' | 'input-p8-key-id' | 'input-p8-issuer-id' | 'load-provided-p8' | 'ios-p8-generate' | 'android-sa-generate' | 'validate' | 'validate-results' | 'p8-upgrade-prompt' | 'handoff-build' | 'done' | 'error';
6
+ export type AppflowStep = AppflowOwnStep | TailStep;
7
+ export type MigrationScope = 'ios' | 'android';
8
+ export type NoSigningScope = 'ios' | 'android';
9
+ export interface AppflowProgress {
10
+ scope: MigrationScope;
11
+ token?: AppflowToken;
12
+ appflowAccount?: string;
13
+ orgSlug?: string;
14
+ appId?: string;
15
+ capgoAppId?: string;
16
+ appSlug?: string;
17
+ ios?: Record<string, string>;
18
+ android?: Record<string, string>;
19
+ migratable: {
20
+ ios: boolean;
21
+ android: boolean;
22
+ };
23
+ noSigningScope?: NoSigningScope;
24
+ iosCertTag?: string;
25
+ androidCertTag?: string;
26
+ iosDistId?: string;
27
+ androidDistId?: string;
28
+ iosDistGapfill?: 'generate' | 'skip';
29
+ androidDistGapfill?: 'generate' | 'skip';
30
+ p8Upgrade?: 'convert' | 'skip';
31
+ p8Source?: 'generate' | 'provide';
32
+ p8Path?: string;
33
+ p8KeyId?: string;
34
+ p8IssuerId?: string;
35
+ notes?: string[];
36
+ completedSteps: AppflowStep[];
37
+ handoffChoice?: 'build' | 'skip';
38
+ built?: boolean;
39
+ setupMode?: 'undecided' | 'with-workflow' | 'secrets-only' | 'declined';
40
+ ciSecretTarget?: CiSecretTarget | null;
41
+ selectedPackageManager?: PackageManager | null;
42
+ buildScriptChoice?: BuildScriptChoice | null;
43
+ envExportTargetPath?: string;
44
+ }
45
+ export type AppflowInput = {
46
+ value?: string;
47
+ field?: string;
48
+ text?: string;
49
+ ciSecretTargets?: CiSecretTarget[];
50
+ };
@@ -0,0 +1,2 @@
1
+ export { appflowFlow } from '../appflow/flow.js';
2
+ export type { AppflowEffectDeps, AppflowEffectResult, AppflowValidationResult } from '../appflow/flow.js';
@@ -0,0 +1,8 @@
1
+ export type AppPasswordResultKind = 'authenticated' | 'rejected' | 'unreachable';
2
+ export interface AppPasswordResult {
3
+ valid: boolean;
4
+ kind: AppPasswordResultKind;
5
+ code?: unknown;
6
+ message?: string;
7
+ }
8
+ export declare function validateAppleAppPassword(username: string, appPassword: string, fetchImpl?: typeof fetch): Promise<AppPasswordResult>;
@@ -100,6 +100,10 @@ interface OnboardingInput {
100
100
  profilePath?: string;
101
101
  /** Answer to import-export-warning: 'go' exports from the Keychain (the one macOS permission dialog), 'back' re-picks the profile, 'exit' stops. */
102
102
  exportConfirm?: 'go' | 'back' | 'exit';
103
+ /** Generic choice value for the Appflow migration flow's neutral steps (org/app/cert pick, submenu, gap-fill, p8-upgrade, handoff-build). */
104
+ value?: string;
105
+ /** Answer to the one-time single-platform "Are you migrating from Ionic Appflow?" gate. */
106
+ migratingFromAppflow?: 'yes' | 'no';
103
107
  }
104
108
  /** Decide the first/again step for a fresh or resumed session. */
105
109
  export declare function decideStart(facts: PreflightFacts, progress: OnboardingProgress | null, deps: EngineDeps): Promise<NextStepResult>;
@@ -138,6 +142,16 @@ export declare function mapAndroidView(view: ReturnType<typeof androidViewForSte
138
142
  keystorePath?: string;
139
143
  keystorePassword?: string;
140
144
  }): NextStepResult;
145
+ /**
146
+ * Drive the Appflow migration flow.
147
+ *
148
+ * `scope` is the migration scope ('ios' | 'android'), from the single-platform
149
+ * "migrating from Appflow?" gate.
150
+ * `input` carries the user's last answer (a choice value, or {} to advance an
151
+ * info / human_gate). The bounded loop runs auto effects until it lands on an
152
+ * interactive step or the build hand-off.
153
+ */
154
+ export declare function decideAppflow(facts: PreflightFacts, deps: EngineDeps, scope: 'ios' | 'android', input?: OnboardingInput): Promise<NextStepResult>;
141
155
  export declare function decideAndroid(facts: PreflightFacts, deps: EngineDeps, opts?: {
142
156
  signInProceed?: boolean;
143
157
  /** Drop any in-flight Google OAuth session and (re)open the browser for a fresh
@@ -3,6 +3,7 @@ import type { IosEffectDeps } from '../ios/flow.js';
3
3
  import type { TailEffectDeps } from '../tail/flow.js';
4
4
  import type { OnboardingStep } from '../types.js';
5
5
  import type { Platform } from './contract.js';
6
+ import type { AppflowProgress } from '../appflow/types.js';
6
7
  /**
7
8
  * The iOS driver-held transient state — the exact `IosEffectDeps['carried']`
8
9
  * shape, plus the MCP-only `parkedImportStep` (S12): the interactive import
@@ -82,6 +83,25 @@ export interface OnboardingSessionState {
82
83
  * onboarding" always re-asks.
83
84
  */
84
85
  resumeResolvedFor?: Platform;
86
+ /**
87
+ * The Appflow migration's in-flight progress (token, org/app selection, the
88
+ * per-platform mapped Capgo creds collected so far, completed steps, and the
89
+ * migration-entry gate answer). The appflow flow is driven through the neutral
90
+ * PlatformFlow contract and — unlike ios/android — has no on-disk progress
91
+ * loader, so its cross-call state lives here, process-local, exactly like the
92
+ * iOS/tail carried transients. HOLDS SECRETS (token, cert/keystore bytes,
93
+ * service-account JSON, app-specific password): it must NEVER serialize into a
94
+ * tool result / progress.json / log. A server restart loses it and the
95
+ * migration re-runs from the explain step (the flow self-heals via the token
96
+ * cache). NON-PERSISTED by design.
97
+ */
98
+ appflowProgress?: AppflowProgress;
99
+ /**
100
+ * The one-time "Are you migrating from Ionic Appflow?" gate answer for a
101
+ * single-platform (ios/android) entry. Set once the user answers so a resume /
102
+ * bare next_step never re-prompts. NON-SECRET (a yes/no decision).
103
+ */
104
+ appflowGate?: 'asked-yes' | 'asked-no';
85
105
  }
86
106
  /**
87
107
  * Get the session state for `appId`, creating an empty one on demand.
@@ -116,6 +136,14 @@ export declare function getResumeResolvedFor(appId: string): Platform | undefine
116
136
  * or skipped as unnecessary; cleared by runStart so a fresh start re-asks.
117
137
  */
118
138
  export declare function setResumeResolvedFor(appId: string, platform: Platform | undefined): void;
139
+ /** Read the in-flight Appflow migration progress for `appId`, or undefined when none. */
140
+ export declare function getAppflowProgress(appId: string): AppflowProgress | undefined;
141
+ /** Store (or, with `undefined`, clear) the in-flight Appflow migration progress for `appId`. */
142
+ export declare function setAppflowProgress(appId: string, progress: AppflowProgress | undefined): void;
143
+ /** Read the one-time "migrating from Appflow?" gate answer for `appId`, or undefined when unasked. */
144
+ export declare function getAppflowGate(appId: string): 'asked-yes' | 'asked-no' | undefined;
145
+ /** Record the one-time "migrating from Appflow?" gate answer for `appId`. */
146
+ export declare function setAppflowGate(appId: string, gate: 'asked-yes' | 'asked-no' | undefined): void;
119
147
  /**
120
148
  * Drop the per-flow carried transients (iOS carried, tail carried, tail park)
121
149
  * for `appId` while PRESERVING the session-level decisions (activePlatform,
@@ -1,5 +1,5 @@
1
1
  import type { TailProgress } from './tail-types.js';
2
- export type Platform = 'ios' | 'android';
2
+ export type Platform = 'ios' | 'android' | 'appflow';
3
3
  export interface OnboardingCompletionSummary {
4
4
  /** The Capgo dashboard build URL, when a build was kicked off. */
5
5
  buildUrl?: string;
@@ -0,0 +1,17 @@
1
+ import type { FC } from 'react';
2
+ import type { OnboardingResult } from '../types.js';
3
+ import type { MigrationScope } from '../appflow/types.js';
4
+ import type { OnboardingBeforeExit } from './exit.js';
5
+ export interface AppflowAppProps {
6
+ appId: string;
7
+ /** Migration scope ('ios' | 'android') from the single-platform "migrating from Appflow?" gate. */
8
+ scope: MigrationScope;
9
+ apikey?: string;
10
+ supaHost?: string;
11
+ journeyId: string;
12
+ onStep?: (step: string) => void;
13
+ onResult?: (result: OnboardingResult) => void;
14
+ onBeforeExit?: OnboardingBeforeExit;
15
+ }
16
+ declare const AppflowApp: FC<AppflowAppProps>;
17
+ export default AppflowApp;
@@ -1,8 +1,11 @@
1
- import type { FC } from 'react';
1
+ import type { FC, ReactNode } from 'react';
2
2
  import type { Platform } from '../types.js';
3
3
  import type { PlatformPickerLayout } from './frame-fit.js';
4
4
  export type PlatformKeyAction = {
5
- type: 'select';
5
+ type: 'move';
6
+ delta: number;
7
+ } | {
8
+ type: 'jump';
6
9
  platform: Platform;
7
10
  } | {
8
11
  type: 'confirm';
@@ -12,8 +15,52 @@ export declare function platformKeyAction(input: string, key: {
12
15
  rightArrow?: boolean;
13
16
  return?: boolean;
14
17
  }): PlatformKeyAction;
18
+ interface PlatformCardProps {
19
+ emoji: string;
20
+ name: string;
21
+ hint: string;
22
+ selected: boolean;
23
+ }
24
+ export declare const PlatformCard: FC<PlatformCardProps>;
15
25
  export interface PlatformPickerProps {
16
26
  layout: PlatformPickerLayout;
17
27
  onSelect: (platform: Platform) => void;
28
+ /** Rendered directly below the legend (flush, no gap). */
29
+ footer?: ReactNode;
18
30
  }
19
31
  export declare const PlatformPicker: FC<PlatformPickerProps>;
32
+ export interface CardChoice {
33
+ value: string;
34
+ emoji: string;
35
+ name: string;
36
+ hint: string;
37
+ }
38
+ export type CardKeyAction = {
39
+ type: 'move';
40
+ delta: number;
41
+ } | {
42
+ type: 'jump';
43
+ index: number;
44
+ } | {
45
+ type: 'confirm';
46
+ } | null;
47
+ export declare function cardKeyAction(input: string, key: {
48
+ leftArrow?: boolean;
49
+ rightArrow?: boolean;
50
+ return?: boolean;
51
+ }, count: number): CardKeyAction;
52
+ export interface CardChooserProps {
53
+ layout: PlatformPickerLayout;
54
+ question: string;
55
+ subtitle?: string;
56
+ options: CardChoice[];
57
+ /** Index of the card highlighted on first render (default 0 = leftmost). */
58
+ defaultIndex?: number;
59
+ /** Rendered directly below the legend (flush, no gap). */
60
+ footer?: ReactNode;
61
+ onSelect: (value: string) => void;
62
+ }
63
+ /** A heading + bordered cards (←/→/Enter) — or a Select on narrow terminals —
64
+ * for any small choice. Reuses PlatformCard so the boxes match the platform picker. */
65
+ export declare const CardChooser: FC<CardChooserProps>;
66
+ export {};
@@ -1,4 +1,4 @@
1
- import type { FC } from 'react';
1
+ import type { FC, ReactNode } from 'react';
2
2
  import type { PlatformPickerLayout } from './frame-fit.js';
3
3
  export type UpdateChoice = 'update' | 'skip';
4
4
  export type UpdateKeyAction = {
@@ -17,5 +17,7 @@ export interface UpdatePromptProps {
17
17
  currentVersion: string;
18
18
  latestVersion: string;
19
19
  onDecide: (choice: UpdateChoice) => void;
20
+ /** Rendered directly below the legend (flush, no gap). */
21
+ footer?: ReactNode;
20
22
  }
21
23
  export declare const UpdatePrompt: FC<UpdatePromptProps>;