@capgo/cli 8.3.0 → 8.4.0

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.
Files changed (31) hide show
  1. package/dist/index.js +608 -608
  2. package/dist/package.json +26 -2
  3. package/dist/src/build/mobileprovision-parser.d.ts +9 -0
  4. package/dist/src/build/onboarding/android/flow.d.ts +505 -0
  5. package/dist/src/build/onboarding/android/keystore.d.ts +18 -0
  6. package/dist/src/build/onboarding/android/oauth-google.d.ts +31 -0
  7. package/dist/src/build/onboarding/android/oauth-scopes.d.ts +4 -0
  8. package/dist/src/build/onboarding/android/types.d.ts +42 -1
  9. package/dist/src/build/onboarding/apple-api.d.ts +5 -1
  10. package/dist/src/build/onboarding/env-export.d.ts +12 -1
  11. package/dist/src/build/onboarding/flow/android-flow.d.ts +3 -0
  12. package/dist/src/build/onboarding/flow/contract.d.ts +24 -0
  13. package/dist/src/build/onboarding/flow/ios-flow.d.ts +3 -0
  14. package/dist/src/build/onboarding/ios/flow.d.ts +650 -0
  15. package/dist/src/build/onboarding/ios/progress.d.ts +2 -0
  16. package/dist/src/build/onboarding/mcp/app-id-validation.d.ts +8 -0
  17. package/dist/src/build/onboarding/mcp/contract.d.ts +51 -0
  18. package/dist/src/build/onboarding/mcp/engine.d.ts +131 -0
  19. package/dist/src/build/onboarding/mcp/explanations.d.ts +4 -0
  20. package/dist/src/build/onboarding/mcp/oauth-session.d.ts +29 -0
  21. package/dist/src/build/onboarding/mcp/onboarding-tools.d.ts +17 -0
  22. package/dist/src/build/onboarding/mcp/step-input.d.ts +45 -0
  23. package/dist/src/build/onboarding/mcp/terminal-launch.d.ts +22 -0
  24. package/dist/src/build/onboarding/tail/flow.d.ts +283 -0
  25. package/dist/src/build/onboarding/tail-types.d.ts +29 -0
  26. package/dist/src/build/onboarding/types.d.ts +82 -1
  27. package/dist/src/build/onboarding/ui/p8-error.d.ts +13 -0
  28. package/dist/src/build/output-record.d.ts +17 -0
  29. package/dist/src/schemas/onboarding.d.ts +41 -0
  30. package/dist/src/sdk.js +9 -9
  31. package/package.json +26 -2
@@ -0,0 +1,650 @@
1
+ import type { Buffer } from 'node:buffer';
2
+ import type { AscApp, AscDistributionCert, AscProfileSummary } from '../apple-api.js';
3
+ import type { ApiKeyData, CertificateData, EnrichedIdentityAvailability, OnboardingProgress, OnboardingStep, ProfileData } from '../types.js';
4
+ import type { AsyncCommandRunner, CiSecretDiscovery, CiSecretEntry, CiSecretTarget, CommandRunner } from '../ci-secrets.js';
5
+ import type { DiscoveredProfile, ExportedP12, IdentityProfileMatch, SigningIdentity } from '../macos-signing.js';
6
+ import type { MobileprovisionDetail } from '../../mobileprovision-parser.js';
7
+ import type { BuildCredentials } from '../../../schemas/build.js';
8
+ import type { BuildLogger, BuildRequestOptions, BuildRequestResult } from '../../request.js';
9
+ import type { EnvExportOpts, EnvExportResult } from '../env-export.js';
10
+ import type { GeneratedWorkflow, WorkflowGeneratorOpts } from '../workflow-generator.js';
11
+ import type { WorkflowWriteOptions, WorkflowWriteResult } from '../workflow-writer.js';
12
+ import type { TailTransient } from '../tail/flow.js';
13
+ import type { AppVerifyResult, AscAppLike, GatePath } from '../app-verification.js';
14
+ import type { DetectedBundleIds } from '../bundle-id-detector.js';
15
+ /**
16
+ * Stable reason an identity has no usable matching profile. Drives the
17
+ * `import-no-match-recovery` menu variant. (Mirrors the `noMatchReason` enum.)
18
+ */
19
+ export type IosNoMatchReason = 'apple-no-cert-match' | 'apple-no-profiles-linked' | 'apple-bundle-mismatch' | 'apple-distribution-mismatch' | 'apple-other' | 'no-profile-on-disk';
20
+ /**
21
+ * A duplicate Capgo provisioning profile (creating-profile / import-create).
22
+ * Matches the `{ id, name, profileType }` triple returned by apple-api's
23
+ * findCapgoProfiles() and carried on DuplicateProfileError.profiles. Derived
24
+ * from the real AscProfileSummary so it tracks any future field additions.
25
+ */
26
+ export type IosDuplicateProfile = Pick<AscProfileSummary, 'id' | 'name' | 'profileType'>;
27
+ export type IosStepKind = 'auto' | 'input' | 'choice' | 'done' | 'error';
28
+ export interface IosStepOption {
29
+ value: string;
30
+ label?: string;
31
+ note?: string;
32
+ }
33
+ export interface IosStepView {
34
+ step: OnboardingStep;
35
+ kind: IosStepKind;
36
+ title?: string;
37
+ prompt?: string;
38
+ collect?: string[];
39
+ options?: IosStepOption[];
40
+ message?: string;
41
+ }
42
+ /**
43
+ * Per-step runtime context the driver supplies to the view builder AND threads
44
+ * back through `IosEffectResult.transient` between effects. EVERY field is
45
+ * OPTIONAL so a caller that only passes `{ appId }` still gets a usable view.
46
+ *
47
+ * This is the iOS "ephemeral inventory" — driver-held transient state that is
48
+ * NEVER persisted to progress.json (it carries Apple-side selections + raw
49
+ * cert/profile/keychain payloads). The total resume function (getIosResumeStep)
50
+ * NEVER produces a step that depends on these — on resume the driver re-runs the
51
+ * silent inventory (import-scanning) and re-renders the picker. See the audit's
52
+ * "Ephemeral inventory" section for the producer/consumer map.
53
+ */
54
+ export interface IosStepCtx {
55
+ appId?: string;
56
+ /** Selected signing identity (import-pick-identity). REQUIRED by import-exporting. */
57
+ chosenIdentity?: SigningIdentity;
58
+ /** Selected provisioning profile (import-pick-profile). REQUIRED by import-exporting. */
59
+ chosenProfile?: DiscoveredProfile;
60
+ /** Discovery result list from import-scanning (identities + on-disk profiles). */
61
+ importMatches?: IdentityProfileMatch[];
62
+ /** Scanned on-disk profiles (paired with importMatches). */
63
+ importProfiles?: DiscoveredProfile[];
64
+ /** Per-identity Apple-side availability (import-validating-all-certs). */
65
+ identityAvailability?: Record<string, EnrichedIdentityAvailability>;
66
+ /** Per-identity prefetched Apple profiles (parallel prefetch after validation). */
67
+ profilePrefetch?: Record<string, DiscoveredProfile[]>;
68
+ /** Apple cert resource id for the chosen identity (import-checking-apple-cert). */
69
+ _appleCertIdForChosen?: string;
70
+ /** Why the chosen identity has no usable profile — drives the recovery menu. */
71
+ noMatchReason?: IosNoMatchReason;
72
+ /** Duplicate Capgo profiles (creating-profile / import-create-profile-only). */
73
+ duplicateProfiles?: IosDuplicateProfile[];
74
+ /** Existing Apple certs offered for revocation when the cert limit is hit. */
75
+ existingCerts?: AscDistributionCert[];
76
+ /** The user's revoke selection (cert-limit-prompt → revoking-certificate). */
77
+ certToRevoke?: AscDistributionCert;
78
+ /**
79
+ * Whether the host can show a native file picker — gates the
80
+ * import-no-match-recovery / import-portal-explanation "use a .mobileprovision
81
+ * from disk" option (the TUI's canUseFilePicker(), app.tsx:3570/3733). The
82
+ * DRIVER threads the host capability here; the view defaults it to true (the
83
+ * macOS-first onboarding target) so a caller that only passes { appId } still
84
+ * gets the file-picker recovery option.
85
+ */
86
+ canUseFilePicker?: boolean;
87
+ /** Resolved certificate data (creating-profile create-new / import-exporting). */
88
+ certData?: CertificateData;
89
+ /** Resolved profile data (creating-profile create-new / import-exporting). */
90
+ profileData?: ProfileData;
91
+ /** Apple team id resolved alongside certData/profileData. */
92
+ teamId?: string;
93
+ /** Keychain export password (import-exporting). Transient only. */
94
+ importedP12Password?: string;
95
+ /** Buffer of .p8 file content during validation (only the PATH is persisted). */
96
+ p8Content?: Buffer;
97
+ /**
98
+ * True once the p8-method-select file-picker effect has opened the native
99
+ * dialog this drive — the engine returns it in transient and the driver
100
+ * threads it back as `deps.carried.pickerOpened` so a re-render does NOT
101
+ * re-open the picker. Mirrors the TUI's `pickerOpenedRef` guard.
102
+ */
103
+ pickerOpened?: boolean;
104
+ /**
105
+ * True once the import-provide-profile-path .mobileprovision picker has opened
106
+ * the native dialog this drive — returned in transient and threaded back as
107
+ * `deps.carried.profilePickerOpened` so a re-render does NOT re-open the
108
+ * picker. SEPARATE from `pickerOpened` (the .p8 picker). Mirrors the TUI's
109
+ * `mobileprovisionPickerOpenedRef` guard (app.tsx:1689).
110
+ */
111
+ profilePickerOpened?: boolean;
112
+ /** Verified key id + issuer id (mirror of completedSteps.apiKeyVerified). */
113
+ apiKey?: ApiKeyData;
114
+ /** ASC apps fetched by the verify-app effect (picker source + Path B re-poll). */
115
+ verifyApps?: AscApp[];
116
+ /** Registered Developer-portal bundle ids (diagnostic — sharpens Path B wording). */
117
+ verifyRegisteredIds?: string[];
118
+ /** The authoritative Release build id, re-detected FRESH from disk ('' = unresolved). */
119
+ verifyReleaseBundleId?: string;
120
+ /** The Debug-config bundle id when it differs from Release (else ''). */
121
+ verifyDebugBundleId?: string;
122
+ /** True when Debug + Release literal ids both exist AND differ (awareness note + telemetry). */
123
+ verifyDebugReleaseDiffer?: boolean;
124
+ /**
125
+ * The verify-app classification (the pure classifyAppVerification result),
126
+ * widened with the two pass-through outcomes ('fetch-failed' /
127
+ * 'no-release-config') so the driver's Result telemetry mirrors the TUI's.
128
+ * Present once the initial fetch has run — its absence is what makes
129
+ * iosViewForStep render verify-app as an AUTO effect instead of the gate.
130
+ */
131
+ verifyResult?: AppVerifyResult | 'fetch-failed' | 'no-release-config';
132
+ /** Which gate path the user is on (null = the picker). */
133
+ verifyPath?: GatePath | null;
134
+ /** The existing app picked in Path A (its bundleId is the target to match). */
135
+ verifyChosenApp?: AscAppLike | null;
136
+ /** 1-based count of blocked Continue attempts (drives the escalating warning). */
137
+ verifyAttempt?: number;
138
+ /** Path B: ask before re-opening the browser after a blocked re-poll. */
139
+ verifyAskReopen?: boolean;
140
+ /** Where verify-app routes on pass/pass-through (set by verifying-key on import). */
141
+ pendingVerifyNext?: OnboardingStep;
142
+ /** Human-readable error message the error view renders (failing step's message). */
143
+ error?: string;
144
+ /** The step to re-run when the user picks "Try again" (absent = no retry offered). */
145
+ retryStep?: OnboardingStep;
146
+ ciSecretEntries?: TailTransient['ciSecretEntries'];
147
+ savedCredentials?: TailTransient['savedCredentials'];
148
+ ciSecretTargets?: TailTransient['ciSecretTargets'];
149
+ ciSecretSetupAdvice?: TailTransient['ciSecretSetupAdvice'];
150
+ ciSecretRepoLabel?: TailTransient['ciSecretRepoLabel'];
151
+ ciSecretExistingKeys?: TailTransient['ciSecretExistingKeys'];
152
+ ciSecretUploadSummary?: TailTransient['ciSecretUploadSummary'];
153
+ envExportPath?: TailTransient['envExportPath'];
154
+ workflowFilePath?: TailTransient['workflowFilePath'];
155
+ buildUrl?: TailTransient['buildUrl'];
156
+ buildOutput?: TailTransient['buildOutput'];
157
+ aiJobId?: TailTransient['aiJobId'];
158
+ availableScripts?: TailTransient['availableScripts'];
159
+ recommendedScript?: TailTransient['recommendedScript'];
160
+ envExportError?: TailTransient['envExportError'];
161
+ ciSecretError?: TailTransient['ciSecretError'];
162
+ }
163
+ /**
164
+ * Async dependencies the iOS effects need (Apple API client, CSR + keychain
165
+ * export, mobileprovision parsing, persistence, the shared tail helpers, and
166
+ * status/log callbacks). EVERY helper is OPTIONAL and ADDITIVE so a driver can
167
+ * inject only what the path it drives needs and the skeleton's stubs keep
168
+ * type-checking. Data types are the REAL exports from apple-api / macos-signing /
169
+ * mobileprovision-parser / csr; only the call-shape envelopes are engine-local.
170
+ */
171
+ export interface IosEffectDeps {
172
+ appId?: string;
173
+ /** Verify an ASC API key (keyId + issuerId via the .p8). */
174
+ verifyApiKey?: (args: {
175
+ keyId: string;
176
+ issuerId: string;
177
+ p8Content: Buffer;
178
+ }) => Promise<{
179
+ teamId?: string;
180
+ }>;
181
+ /**
182
+ * Create a distribution certificate from a CSR. Returns the RAW Apple cert
183
+ * response (mirrors the real apple-api `createCertificate` helper): the cert
184
+ * resource id, the base64 DER `certificateContent`, the expiry, and the team
185
+ * id. The engine pairs `certificateContent` + the CSR private key via
186
+ * `createP12` to produce the final .p12 — keeping the IO-free engine in charge
187
+ * of assembling the CertificateData credential. Throws CertificateLimitError
188
+ * (carrying the existing certs) when Apple's per-team cert limit is hit.
189
+ */
190
+ createCertificate?: (args: {
191
+ csr: string;
192
+ accessToken?: string;
193
+ }) => Promise<{
194
+ certificateId: string;
195
+ certificateContent: string;
196
+ expirationDate: string;
197
+ teamId: string;
198
+ }>;
199
+ /** Revoke an existing certificate (cert-limit recovery). */
200
+ revokeCertificate?: (certificateId: string) => Promise<void>;
201
+ /** Create a provisioning profile for a bundle id + cert. */
202
+ createProfile?: (args: {
203
+ bundleId: string;
204
+ certificateId: string;
205
+ distribution?: string;
206
+ }) => Promise<ProfileData>;
207
+ /** Delete a provisioning profile (duplicate-profile recovery). */
208
+ deleteProfile?: (profileId: string) => Promise<void>;
209
+ /** Resolve the Apple cert resource id from a local cert SHA-1. */
210
+ findCertIdBySha1?: (sha1: string) => Promise<string | null>;
211
+ /** Classify a cert's Apple-side availability (import-validating-all-certs). */
212
+ classifyCertAvailability?: (identity: SigningIdentity) => Promise<EnrichedIdentityAvailability>;
213
+ /** List the team's distribution certificates (cert-limit prompt). */
214
+ listCertificates?: () => Promise<AscDistributionCert[]>;
215
+ /** Check for duplicate Capgo profiles for a bundle id. */
216
+ checkDuplicateProfiles?: (bundleId: string) => Promise<IosDuplicateProfile[]>;
217
+ /** Ensure the bundle id exists on Apple (import-create-profile-only). */
218
+ ensureBundleId?: (bundleId: string) => Promise<void>;
219
+ /**
220
+ * List the Apple profiles linked to a cert (import-checking-apple-cert).
221
+ *
222
+ * Returns the RAW Apple shape (AscProfileSummary[]) exactly as the real
223
+ * apple-api `listProfilesForCert` helper does — id / name / profileType /
224
+ * profileContent / expirationDate / bundleIdentifier. The engine itself
225
+ * synthesizes each summary into a DiscoveredProfile (populating profileBase64
226
+ * + certificateSha1s=[identity.sha1]) via `synthesizeProfileFromAscSummary`,
227
+ * byte-for-byte mirroring the TUI's inline mapping at app.tsx:1556 / :1460.
228
+ * Keeping the dep at the raw Apple shape means the driver pre-binds nothing
229
+ * more than the real helper.
230
+ */
231
+ listProfilesForCert?: (certificateId: string) => Promise<AscProfileSummary[]>;
232
+ /** List every ASC app visible to the API key (verify-app fetch + Path B re-poll). */
233
+ listApps?: () => Promise<AscApp[]>;
234
+ /** List every registered bundle-id identifier (verify-app diagnostics). */
235
+ listBundleIds?: () => Promise<string[]>;
236
+ /**
237
+ * FRESH bundle-id detection from disk (verify-app + the Path-A re-check). The
238
+ * driver pre-binds the real `detectIosBundleIds({ cwd, iosDir, capacitorAppId })`
239
+ * — the engine reads `releaseResolved`/`pbxproj` for the authoritative Release
240
+ * id, `debug`/`debugReleaseDiffer` for the awareness note, and `capacitor` for
241
+ * the persisted iosBundleIdContextAppId snapshot. Called PER CHECK so an edit
242
+ * the user made since the wizard started is picked up (the TUI bypasses its
243
+ * memo the same way, app.tsx:1522/3088).
244
+ */
245
+ detectBundleIds?: () => DetectedBundleIds;
246
+ /**
247
+ * Rewrite the Release PRODUCT_BUNDLE_IDENTIFIER assignments equal to `fromId`
248
+ * to `toId` in the Xcode project (the Path-A auto-fix). The driver pre-binds
249
+ * the real `writeReleaseBundleId(cwd, iosDir, …)`; returns the number of
250
+ * replaced assignments (0 = nothing matched). Throws only on an FS error.
251
+ */
252
+ writeReleaseBundleId?: (fromId: string, toId: string) => {
253
+ changed: number;
254
+ };
255
+ /** Generate a CSR + private key PEM. */
256
+ generateCsr?: (args?: {
257
+ commonName?: string;
258
+ }) => {
259
+ csr: string;
260
+ privateKeyPem: string;
261
+ };
262
+ /** Build a .p12 from a cert + private key. Returns base64. */
263
+ createP12?: (args: {
264
+ certificatePem: string;
265
+ privateKeyPem: string;
266
+ password: string;
267
+ }) => string;
268
+ /** List the Mac's code-signing identities (import-scanning). */
269
+ listSigningIdentities?: () => Promise<SigningIdentity[]>;
270
+ /** Scan the Mac's on-disk provisioning profiles (import-scanning). */
271
+ scanProvisioningProfiles?: () => Promise<DiscoveredProfile[]>;
272
+ /**
273
+ * Export a .p12 (cert + key) from the Keychain for the chosen identity
274
+ * (import-exporting). Signature mirrors the REAL macos-signing helper VERBATIM:
275
+ * takes the identity's SHA-1 and resolves to { base64, passphrase } (the
276
+ * auto-generated wrap passphrase becomes the transient importedP12Password the
277
+ * saving-credentials handoff reads — NEVER persisted, risk #2 / D-iOS-3).
278
+ */
279
+ exportP12FromKeychain?: (targetSha1: string) => Promise<ExportedP12>;
280
+ /** Parse a `.mobileprovision` file in detail (import-provide-profile-path). */
281
+ parseMobileprovisionDetailed?: (bytes: Buffer) => MobileprovisionDetail;
282
+ loadProgress?: (appId: string) => Promise<OnboardingProgress | null>;
283
+ saveProgress?: (appId: string, progress: OnboardingProgress) => Promise<void>;
284
+ deleteProgress?: (appId: string) => Promise<void>;
285
+ /** Persist the saved build-credential map (saving-credentials). */
286
+ updateSavedCredentials?: (appId: string, platform: 'ios' | 'android', credentials: Record<string, string>) => Promise<void>;
287
+ loadSavedCredentials?: (appId: string) => Promise<unknown>;
288
+ readFile?: (path: string) => Promise<Buffer>;
289
+ copyFile?: (src: string, dest: string) => Promise<void>;
290
+ /**
291
+ * Open the native .p8 file picker (p8-method-select). Resolves to the chosen
292
+ * absolute path, or null when the user cancels. The driver pre-binds the real
293
+ * `openFilePicker` here; tests inject a canned path/null. Mirrors the TUI's
294
+ * `openFilePicker()` call inside the p8-method-select effect.
295
+ */
296
+ openP8FilePicker?: () => Promise<string | null>;
297
+ /**
298
+ * Open the native .mobileprovision file picker (import-provide-profile-path).
299
+ * Resolves to the chosen absolute path, or null when the user cancels. The
300
+ * driver pre-binds the real `openMobileprovisionPicker` here; tests inject a
301
+ * canned path/null. Mirrors the TUI's `openMobileprovisionPicker()` call
302
+ * inside the import-provide-profile-path effect (app.tsx:1696). The bytes are
303
+ * then read via `deps.readFile` and parsed via `deps.parseMobileprovisionDetailed`.
304
+ */
305
+ openProfilePicker?: () => Promise<string | null>;
306
+ /**
307
+ * Whether the host can show a native file picker. Gates the
308
+ * import-no-match-recovery / import-portal-explanation "use a .mobileprovision
309
+ * from disk" option exactly as the TUI's `canUseFilePicker()` does
310
+ * (app.tsx:3570/3733). Defaults to true when omitted (the macOS-first target).
311
+ */
312
+ canUseFilePicker?: () => boolean;
313
+ /**
314
+ * Open a URL in the host's default browser (import-portal-explanation's
315
+ * "open the portal anyway" branch). Best-effort — the driver pre-binds the
316
+ * real `open` helper; tests inject a recorder/no-op. Mirrors the TUI's
317
+ * `open(...)` call at app.tsx:3749. A failure must NOT abort recovery.
318
+ */
319
+ openExternal?: (url: string) => Promise<void> | void;
320
+ /**
321
+ * Whether the host is macOS. Gates the post-backup fork: on macOS the user is
322
+ * offered import-vs-create at `setup-method-select`; off-macOS the import
323
+ * sub-flow is unavailable so backing-up routes straight to the create-new
324
+ * `api-key-instructions`. Mirrors the TUI's `isMacOS()` branch. Defaults to
325
+ * true when omitted (the macOS-first onboarding target).
326
+ */
327
+ isMacOS?: () => boolean;
328
+ createCiSecretEntries?: (credentials: Partial<BuildCredentials>, apiKey?: string) => CiSecretEntry[];
329
+ detectCiSecretTargets?: (runner?: CommandRunner) => CiSecretDiscovery;
330
+ getCiSecretRepoLabelAsync?: (target: CiSecretTarget, runner?: AsyncCommandRunner) => Promise<string | null>;
331
+ listExistingCiSecretKeysAsync?: (target: CiSecretTarget, keys: string[], runner?: AsyncCommandRunner) => Promise<string[]>;
332
+ uploadCiSecretsAsync?: (target: CiSecretTarget, entries: CiSecretEntry[], existingKeys?: string[], runner?: AsyncCommandRunner, onProgress?: (current: number, total: number, keyName: string) => void) => Promise<void>;
333
+ exportCredentialsToEnv?: (opts: EnvExportOpts) => EnvExportResult;
334
+ defaultExportPath?: (appId: string, platform: 'ios' | 'android') => string;
335
+ generateWorkflow?: (opts: WorkflowGeneratorOpts) => GeneratedWorkflow;
336
+ writeWorkflowFile?: (opts: WorkflowGeneratorOpts, writeOptions?: WorkflowWriteOptions) => WorkflowWriteResult;
337
+ requestBuildInternal?: (appId: string, options: BuildRequestOptions, silent?: boolean, logger?: BuildLogger) => Promise<BuildRequestResult>;
338
+ /** The streaming BuildLogger threaded into requestBuildInternal (4th arg). */
339
+ logger?: BuildLogger;
340
+ /** The build VIEWER sink (FullscreenBuildOutput), distinct from onLog. */
341
+ onBuildOutput?: (line: string) => void;
342
+ /** Resolves the Capgo API key for the build request (CLI-flag-over-saved). */
343
+ resolveApikey?: () => string | undefined;
344
+ /** Per-key CI-secret upload progress (uploadCiSecretsAsync 5th arg). */
345
+ onCiSecretUploadProgress?: (current: number, total: number, keyName: string) => void;
346
+ /** The 2-phase checking-ci-secrets status text. */
347
+ onCiSecretCheckPhase?: (phase: string) => void;
348
+ /** The ci-secrets-failed reason. */
349
+ onCiSecretError?: (message: string) => void;
350
+ /** Reads the project's package.json scripts map (with-workflow preload). */
351
+ getPackageScripts?: () => Record<string, string>;
352
+ /** Detects the web-framework project type (best-effort; may resolve null). */
353
+ findProjectType?: (options?: {
354
+ quiet?: boolean;
355
+ }) => Promise<string | null>;
356
+ /** Maps a detected project type to its recommended build script name. */
357
+ findBuildCommandForProjectType?: (projectType: string) => Promise<string | null>;
358
+ /** Workflow-file telemetry hook (e.g. 'workflow-file-written'). */
359
+ trackWorkflowEvent?: (event: string, options?: {
360
+ decision?: string;
361
+ }) => void;
362
+ /**
363
+ * DRIVER-HELD transient tail state threaded back into each post-save effect.
364
+ * The TUI resolves these ONCE (at saving-credentials) and keeps them in React
365
+ * state; a headless driver mirrors that by capturing the matching
366
+ * IosEffectResult.transient and passing it back here on the NEXT effect.
367
+ * NEVER persisted to progress.json. When absent (crash-recovery resume) the
368
+ * effect falls back to a single lossy re-derivation from progress.
369
+ */
370
+ carried?: {
371
+ savedCredentials?: Record<string, string>;
372
+ ciSecretEntries?: CiSecretEntry[];
373
+ ciSecretExistingKeys?: string[];
374
+ /**
375
+ * Whether the workflow file did NOT exist when previewed (the TUI's
376
+ * `previewIsNew`, resolved at preview-workflow-file via existsSync). The
377
+ * writing-workflow-file effect logs '✔ Wrote' vs '✔ Overwrote' from it.
378
+ * Absent defaults to NEW ('Wrote'). EPHEMERAL — never persisted.
379
+ */
380
+ workflowIsNew?: boolean;
381
+ /** The chosen signing identity (lossy re-scan source on resume). */
382
+ chosenIdentity?: SigningIdentity;
383
+ /** The chosen provisioning profile (lossy re-scan source on resume). */
384
+ chosenProfile?: DiscoveredProfile;
385
+ /**
386
+ * The import-scanning discovery inventory (identity↔on-disk-profile matches +
387
+ * the raw scanned profiles), threaded forward so the NEXT import effect can
388
+ * read it without a re-scan. Produced by import-scanning into transient; the
389
+ * driver mirrors it back here for import-validating-all-certs (which batches
390
+ * classifyCertAvailability over importMatches) and the pickers. EPHEMERAL —
391
+ * never persisted; on a crash-recovery resume the engine re-lands on
392
+ * import-scanning and re-populates it.
393
+ */
394
+ importMatches?: IdentityProfileMatch[];
395
+ importProfiles?: DiscoveredProfile[];
396
+ /** Resolved cert/profile/team export payloads carried into saving-credentials. */
397
+ certData?: CertificateData;
398
+ profileData?: ProfileData;
399
+ teamId?: string;
400
+ /**
401
+ * The validated .p8 file content (ASC private key) the driver carries
402
+ * between the .p8 input chain and `verifying-key`. ONLY the p8Path is
403
+ * persisted to progress.json — the raw key bytes ride this transient
404
+ * channel, mirroring the TUI's `p8ContentRef`. The verifying-key effect
405
+ * reads it from here; when absent (crash-recovery resume) it falls back to
406
+ * re-reading the file at `progress.p8Path` via `deps.readFile`.
407
+ */
408
+ p8Content?: Buffer;
409
+ /**
410
+ * Tracks that the p8-method-select file-picker effect already ran, so a
411
+ * re-render does NOT re-open the native picker. Mirrors the TUI's
412
+ * `pickerOpenedRef`. The driver threads the returned `pickerOpened: true`
413
+ * transient back here on the next call.
414
+ */
415
+ pickerOpened?: boolean;
416
+ /**
417
+ * Tracks that the import-provide-profile-path .mobileprovision file-picker
418
+ * effect already ran this attempt, so a re-render / re-drive does NOT re-open
419
+ * the native picker. SEPARATE from `pickerOpened` (the .p8 picker guard) so
420
+ * the two file pickers never cross-suppress each other — mirrors the TUI's
421
+ * distinct `mobileprovisionPickerOpenedRef` (app.tsx:1689). The driver threads
422
+ * the returned `profilePickerOpened: true` transient back here; it RESETS the
423
+ * flag (to false) before routing into import-provide-profile-path from the
424
+ * recovery menu, exactly as the TUI clears the ref at app.tsx:3593.
425
+ */
426
+ profilePickerOpened?: boolean;
427
+ /**
428
+ * Keychain export passphrase for the IMPORT path's .p12 (import-exporting).
429
+ * Transient only — the import-exporting effect never persists it, so the
430
+ * saving-credentials handoff reads it from carried. Absent on the create-new
431
+ * path (which uses the well-known DEFAULT_P12_PASSWORD) and on a crash-recovery
432
+ * resume that lost the in-memory state.
433
+ */
434
+ importedP12Password?: string;
435
+ /**
436
+ * The cert the user picked at `cert-limit-prompt` (cert-limit recovery). The
437
+ * choice is EPHEMERAL — `applyIosInput` persists nothing; the driver records
438
+ * the picked AscDistributionCert here and re-drives the prompt as a resolver
439
+ * effect, exactly as the TUI stashes `certToRevoke` in React state before
440
+ * advancing to `revoking-certificate` (app.tsx:3923). The resolver returns
441
+ * `revoking-certificate` when present, `error` when absent (the user exited).
442
+ * Mirrors the BATCH 2 ephemeral-branching mechanism (`pickerOpened` /
443
+ * `chosenIdentity`): the selection lives in carried, never in progress.json.
444
+ */
445
+ certToRevoke?: AscDistributionCert;
446
+ /**
447
+ * The duplicate Capgo profiles surfaced at `duplicate-profile-prompt`
448
+ * (duplicate-profile recovery). Produced by `creating-profile` /
449
+ * `import-create-profile-only` into transient; the driver threads the list
450
+ * back here so `deleting-duplicate-profiles` knows which profiles to delete.
451
+ * NEVER persisted (only `duplicateProfileOrigin` is — see types.ts).
452
+ */
453
+ duplicateProfiles?: IosDuplicateProfile[];
454
+ /**
455
+ * The user's confirm/exit decision at `duplicate-profile-prompt`. EPHEMERAL —
456
+ * `applyIosInput` persists nothing (per the audit's sequencing model); the
457
+ * driver records the choice here and re-drives the prompt as a resolver
458
+ * effect. `true` → `deleting-duplicate-profiles`; falsy (the user exited) →
459
+ * `error` (mirroring app.tsx:3942's delete-vs-exitOnboarding branch).
460
+ */
461
+ confirmDeleteDuplicates?: boolean;
462
+ /**
463
+ * The user's pick at `import-no-match-recovery` (the 5-way HUB). EPHEMERAL —
464
+ * `applyIosInput` persists nothing; the driver records the choice here and
465
+ * re-drives the prompt as a resolver effect. 'create' →
466
+ * import-create-profile-only (with an ASC key) or api-key-instructions
467
+ * (without); 'provide-profile-path' → import-provide-profile-path; 'browser'
468
+ * → import-portal-explanation; 'back' → import-pick-identity. Mirrors the
469
+ * TUI's recovery-menu onChange (app.tsx:3579).
470
+ */
471
+ recoveryAction?: 'create' | 'provide-profile-path' | 'browser' | 'back';
472
+ /**
473
+ * The user's pick at `import-portal-explanation` (the manual-portal
474
+ * walkthrough). EPHEMERAL — the driver records the choice here and re-drives
475
+ * the step as a resolver. 'use-create' → import-create-profile-only;
476
+ * 'use-file' → import-provide-profile-path; 'open-anyway' / 'back' →
477
+ * import-no-match-recovery. Mirrors app.tsx:3738.
478
+ */
479
+ portalAction?: 'use-create' | 'open-anyway' | 'use-file' | 'back';
480
+ /**
481
+ * The user's pick at `import-export-warning` (the heads-up before the one
482
+ * Keychain dialog). EPHEMERAL — `applyIosInput` persists nothing; the driver
483
+ * records the choice here and re-drives the step as a resolver. 'go' →
484
+ * import-exporting (the precompiled signed helper is resolved + verified in
485
+ * the export step itself — PR #2458 removed the swiftc compile step);
486
+ * 'back' → import-pick-profile; 'exit'/absent → exit onboarding. Mirrors
487
+ * app.tsx:3769 onChange.
488
+ */
489
+ exportWarningAction?: 'go' | 'back' | 'exit';
490
+ /**
491
+ * The STICKY no-match reason set by the step that ROUTED into recovery
492
+ * (import-pick-identity / import-checking-apple-cert). The recovery resolver
493
+ * + the import-provide-profile-path cancel branch thread it back so a
494
+ * re-entry from a file-picker cancel / portal "open anyway" does NOT
495
+ * recompute or overwrite it (risk #8) — the menu keeps showing the SAME
496
+ * variant. Mirrors the TUI leaving `noMatchReason` untouched on back-nav.
497
+ */
498
+ noMatchReason?: IosNoMatchReason;
499
+ /**
500
+ * The step to re-run when the user picks "Try again" on the error screen
501
+ * (BATCH 8). EPHEMERAL — set by the failing effect into transient.retryStep,
502
+ * threaded back here by the driver, and read by the error RESOLVER
503
+ * (runIosEffect('error')) to route a retry. NEVER persisted: an error is
504
+ * transient runtime state, so a crash-recovery resume re-enters the failing
505
+ * phase fresh (getIosResumeStep never returns 'error'). Mirrors the TUI's
506
+ * setRetryStep + the ErrorStep 'retry' branch (app.tsx:1116 / 4468).
507
+ */
508
+ retryStep?: OnboardingStep;
509
+ /**
510
+ * The user's pick on the error screen (BATCH 8). EPHEMERAL — `applyIosInput`
511
+ * persists nothing; the driver records the choice here and re-drives the step
512
+ * as a resolver. 'retry' → re-run carried.retryStep (the failing step);
513
+ * 'restart' → welcome (a fresh reset); 'exit'/absent → stay on 'error' (the
514
+ * terminal exit sink — the driver leaves onboarding, mirroring the TUI's
515
+ * exitOnboarding at app.tsx:4482). NEVER persisted.
516
+ */
517
+ errorAction?: 'retry' | 'restart' | 'exit';
518
+ /**
519
+ * Where verify-app routes once the invariant holds (or on a pass-through
520
+ * exit): the import continuation (import-validating-all-certs /
521
+ * import-pick-identity) on the import app_store path, absent on create-new
522
+ * (verify-app falls back to 'creating-certificate'). Produced by
523
+ * verifying-key into transient.pendingVerifyNext; the driver threads it back
524
+ * here. NEVER persisted — a fresh mount has none, so a resume re-entering
525
+ * verify-app always falls back to creating-certificate (matching the TUI's
526
+ * pendingVerifyNext React state + getResumeStep's verify-app comment).
527
+ */
528
+ pendingVerifyNext?: OnboardingStep;
529
+ /**
530
+ * The user's pick on the PARKED verify-app step (the picker or one of the
531
+ * two gates). EPHEMERAL — `applyIosInput` persists nothing; the driver
532
+ * records the pick here and re-drives verify-app as a resolver effect:
533
+ * 'pick' (+ verifyChosenApp) / 'create-new' route the picker; 'autofix' /
534
+ * 'continue' drive the Path-A fix-build-id gate; 'recheck' / 'open' /
535
+ * 'reopen' drive the Path-B create-app gate; 'back' resets to the picker;
536
+ * 'cancel' exits via the error sink. Mirrors the TUI Select onChange values
537
+ * (app.tsx:3246/3283/3323/3360). The driver MUST clear it after each
538
+ * resolver run so a later re-entry runs the initial fetch.
539
+ */
540
+ verifyAction?: 'pick' | 'create-new' | 'autofix' | 'continue' | 'recheck' | 'open' | 'reopen' | 'back' | 'cancel';
541
+ /** The existing ASC app picked in the verify-app picker (Path A target). */
542
+ verifyChosenApp?: AscAppLike | null;
543
+ /** The ASC apps fetched by the initial verify-app effect (picker source + re-poll). */
544
+ verifyApps?: AscApp[];
545
+ /** Registered Developer-portal bundle ids (Path B wording sharpener). */
546
+ verifyRegisteredIds?: string[];
547
+ /** The authoritative Release build id resolved by the verify-app fresh detect. */
548
+ verifyReleaseBundleId?: string;
549
+ /** The Debug-config bundle id when it differs from Release (else ''). */
550
+ verifyDebugBundleId?: string;
551
+ /** Which gate path the user is on (null = the picker). */
552
+ verifyPath?: GatePath | null;
553
+ /** 1-based count of blocked Continue attempts (the escalation driver). */
554
+ verifyAttempt?: number;
555
+ /** Path B: ask before re-opening the browser after a blocked re-poll. */
556
+ verifyAskReopen?: boolean;
557
+ };
558
+ onStatus?: (message: string) => void;
559
+ onLog?: (message: string, color?: string) => void;
560
+ /** Internal-only diagnostic line → the support internal log (main PR #2406). Optional; no-op when absent. */
561
+ onInternalLog?: (line: string) => void;
562
+ signal?: AbortSignal;
563
+ }
564
+ export interface IosEffectResult {
565
+ /** Updated progress after the effect ran (matches what was persisted). */
566
+ progress: OnboardingProgress;
567
+ /** Explicit next step when not derivable from progress alone (★ transitions). */
568
+ next?: OnboardingStep;
569
+ /** Transient runtime data that lives in the driver but is NOT persisted. */
570
+ transient?: Partial<IosStepCtx>;
571
+ }
572
+ /**
573
+ * The create-new choice/input vocabulary. Mirrors android's `AndroidInput`:
574
+ * one variant per choice/input step that records (or routes) state. The iOS
575
+ * `applyIosInput` signature still accepts `unknown`, so callers cast to this.
576
+ * Navigation-only choices (api-key-instructions) are included for completeness
577
+ * but return progress unchanged.
578
+ */
579
+ export type IosInput = {
580
+ step: 'setup-method-select';
581
+ value: 'create' | 'import';
582
+ } | {
583
+ step: 'api-key-instructions';
584
+ value: 'picker' | 'manual';
585
+ } | {
586
+ step: 'input-p8-path';
587
+ value: string;
588
+ } | {
589
+ step: 'input-key-id';
590
+ value: string;
591
+ } | {
592
+ step: 'input-issuer-id';
593
+ value: string;
594
+ } | {
595
+ step: 'cert-limit-prompt';
596
+ value: string;
597
+ } | {
598
+ step: 'duplicate-profile-prompt';
599
+ value: 'delete' | 'exit';
600
+ } | {
601
+ step: 'verify-app';
602
+ value: string;
603
+ } | {
604
+ step: 'import-distribution-mode';
605
+ value: 'app_store' | 'ad_hoc' | '__cancel__';
606
+ } | {
607
+ step: 'import-pick-identity';
608
+ value: string;
609
+ } | {
610
+ step: 'import-pick-profile';
611
+ value: string;
612
+ } | {
613
+ step: 'import-no-match-recovery';
614
+ value: 'create' | 'provide-profile-path' | 'browser' | 'back';
615
+ } | {
616
+ step: 'import-portal-explanation';
617
+ value: 'use-create' | 'open-anyway' | 'use-file' | 'back';
618
+ } | {
619
+ step: 'import-export-warning';
620
+ value: 'go' | 'back' | 'exit';
621
+ };
622
+ /**
623
+ * Build the view-model for a given step. Post-save tail steps delegate to the
624
+ * shared neutral view (adapted back to IosStepView). The create-new choice/input
625
+ * steps (setup-method fork + .p8 chain) return real per-step views mirroring the
626
+ * TUI prompts/options (ui/steps/ios-credentials.tsx). All other steps return a
627
+ * minimal placeholder 'auto' view echoing the step (real per-step views land in
628
+ * later batches).
629
+ */
630
+ export declare function iosViewForStep(step: OnboardingStep, progress: OnboardingProgress, ctx?: IosStepCtx): IosStepView;
631
+ /**
632
+ * Apply a user input to progress. Post-save tail choice/input steps delegate
633
+ * the reducer to the shared neutral module. The create-new choice/input steps
634
+ * persist their field(s) exactly as the TUI's onSubmit/onChange handlers do
635
+ * (ui/app.tsx). All other steps return progress unchanged (real per-step
636
+ * mutations land in later batches).
637
+ *
638
+ * PURE — no IO. The .p8 file read + keyId extraction + Apple verification are
639
+ * effect-boundary concerns (p8-method-select / verifying-key); the reducers here
640
+ * only record the raw user input into progress.
641
+ */
642
+ export declare function applyIosInput(step: OnboardingStep, progress: OnboardingProgress, input: unknown): OnboardingProgress;
643
+ /**
644
+ * Run the async side-effect for a step. Post-save tail steps (incl.
645
+ * saving-credentials) delegate to the shared neutral module via toTailDeps; the
646
+ * neutral result maps 1:1 onto IosEffectResult (next is a wider OnboardingStep;
647
+ * transient is a subset of IosStepCtx). All other steps are not implemented yet
648
+ * — the real Apple-API / keychain / build effects land in later batches.
649
+ */
650
+ export declare function runIosEffect(step: OnboardingStep, progress: OnboardingProgress, deps: IosEffectDeps): Promise<IosEffectResult>;
@@ -0,0 +1,2 @@
1
+ import type { OnboardingProgress, OnboardingStep } from '../types.js';
2
+ export declare function getIosResumeStep(progress: OnboardingProgress | null): OnboardingStep;
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Returns true only when `appId` is a safe reverse-domain package identifier
3
+ * that can be embedded in a shell command string without risk of injection.
4
+ *
5
+ * Valid examples: com.example.app io.capgo.app_1 com.acme.my-app
6
+ * Invalid examples: com.x; rm -rf ~ com.x$(cmd) nodots ""
7
+ */
8
+ export declare function isSafeAppIdForCommand(appId: string): boolean;