@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
package/dist/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@capgo/cli",
3
3
  "type": "module",
4
- "version": "8.3.0",
4
+ "version": "8.4.0",
5
5
  "description": "A CLI to upload to capgo servers",
6
6
  "author": "Martin martin@capgo.app",
7
7
  "license": "Apache 2.0",
@@ -109,12 +109,36 @@
109
109
  "test:apple-api-import-helpers": "bun test/test-apple-api-import-helpers.mjs",
110
110
  "test:bundle-id-detector": "bun test/test-bundle-id-detector.mjs",
111
111
  "test:apple-api-app-list": "bun test/test-apple-api-app-list.mjs",
112
+ "test:apple-api-cert-create": "bun test/test-apple-api-cert-create.mjs",
112
113
  "test:app-verification": "bun test/test-app-verification.mjs",
113
114
  "test:pbxproj-parser": "bun test/test-pbxproj-parser.mjs",
114
115
  "test:manifest-path-encoding": "bun test/test-manifest-path-encoding.mjs",
115
116
  "test:self-update": "bun test/test-self-update.mjs",
116
117
  "test:update-prompt": "bun test/test-update-prompt.mjs",
117
- "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: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:mcp-analytics && 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:prompt-preferences && bun run test:esm-sdk && bun run test:mcp && bun run test:version-detection && bun run test:platform-paths && bun run test:payload-split && bun run test:manifest-path-encoding && bun run test:macos-signing && bun run test:apple-api-import-helpers && 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: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-bundle-files && bun run test:self-update && bun run test:update-prompt",
118
+ "test:android-tail-engine": "bun test/test-android-tail-engine.mjs",
119
+ "test:android-tail-render": "bun test/test-android-tail-render.mjs",
120
+ "test:android-tail-routing": "bun test/test-android-tail-routing.mjs",
121
+ "test:dev-gate-stripped": "bun test/test-dev-gate-stripped.mjs",
122
+ "test:frame-fit-ios-shared": "bun test/test-frame-fit-ios-shared.mjs",
123
+ "test:ios-confirm-app-id": "bun test/test-ios-confirm-app-id.mjs",
124
+ "test:ios-create-new": "bun test/test-ios-create-new.mjs",
125
+ "test:ios-e2e": "bun test/test-ios-e2e.mjs",
126
+ "test:ios-flow-contract": "bun test/test-ios-flow-contract.mjs",
127
+ "test:ios-import-discovery": "bun test/test-ios-import-discovery.mjs",
128
+ "test:ios-import-export": "bun test/test-ios-import-export.mjs",
129
+ "test:ios-import-pickers": "bun test/test-ios-import-pickers.mjs",
130
+ "test:ios-import-recovery": "bun test/test-ios-import-recovery.mjs",
131
+ "test:ios-recovery": "bun test/test-ios-recovery.mjs",
132
+ "test:ios-resume": "bun test/test-ios-resume.mjs",
133
+ "test:ios-tail-handoff": "bun test/test-ios-tail-handoff.mjs",
134
+ "test:ios-tui-render": "bun test/test-ios-tui-render.mjs",
135
+ "test:p8-error": "bun test/test-p8-error.mjs",
136
+ "test:ios-tui-routing": "bun test/test-ios-tui-routing.mjs",
137
+ "test:ios-updater-sync-validation": "bun test/test-ios-updater-sync-validation.mjs",
138
+ "test:ios-verify-app": "bun test/test-ios-verify-app.mjs",
139
+ "test:platform-flow-contract": "bun test/test-platform-flow-contract.mjs",
140
+ "test:tail-engine-shared": "bun test/test-tail-engine-shared.mjs",
141
+ "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: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:mcp-analytics && 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:prompt-preferences && bun run test:esm-sdk && bun run test:mcp && bun run test:version-detection && bun run test:platform-paths && bun run test:payload-split && bun run test:manifest-path-encoding && bun run test:macos-signing && bun run test:apple-api-import-helpers && 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: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-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",
118
142
  "test:build-platform-selection": "bun test/test-build-platform-selection.mjs",
119
143
  "test:ai-log-capture": "bun test/test-ai-log-capture.mjs",
120
144
  "test:ai-analyze-flow": "bun test/test-ai-analyze-flow.mjs",
@@ -1,3 +1,4 @@
1
+ import { Buffer } from 'node:buffer';
1
2
  export interface MobileprovisionInfo {
2
3
  name: string;
3
4
  uuid: string;
@@ -33,3 +34,11 @@ export declare function parseMobileprovisionFromBase64(base64Content: string): M
33
34
  * - SHA1 of each embedded developer certificate (used for cert↔profile matching)
34
35
  */
35
36
  export declare function parseMobileprovisionDetailed(filePath: string): MobileprovisionDetail;
37
+ /**
38
+ * Buffer-based variant of {@link parseMobileprovisionDetailed} — parses the raw
39
+ * .mobileprovision bytes directly instead of reading a path. Used by the iOS
40
+ * onboarding engine's import-provide-profile-path effect, which reads the file
41
+ * via its injected `readFile` dep (so the IO stays at the boundary) and parses
42
+ * the returned Buffer here. `source` is a label used only in error messages.
43
+ */
44
+ export declare function parseMobileprovisionBufferDetailed(data: Buffer, source?: string): MobileprovisionDetail;
@@ -0,0 +1,505 @@
1
+ import type { Buffer } from 'node:buffer';
2
+ import type { AndroidOnboardingProgress, AndroidOnboardingStep } from './types.js';
3
+ import type { KeystoreOptions, KeystoreResult, ListAliasesResult, ProbeKeyPasswordResult } from './keystore.js';
4
+ import type { ValidateOptions, ValidationResult } from './service-account-validation.js';
5
+ import type { GcpProject, GcpServiceAccount, GcpServiceAccountKey } from './gcp-api.js';
6
+ import type { GoogleOAuthTokens, GoogleUserInfo, PendingOAuthSession, RunOAuthFlowOptions } from './oauth-google.js';
7
+ import type { BuildCredentials } from '../../../schemas/build.js';
8
+ import type { BuildLogger, BuildRequestOptions, BuildRequestResult } from '../../request.js';
9
+ import type { AsyncCommandRunner, CiSecretDiscovery, CiSecretEntry, CiSecretSetupAdvice, CiSecretTarget, CommandRunner } from '../ci-secrets.js';
10
+ import type { EnvExportOpts, EnvExportResult } from '../env-export.js';
11
+ import type { BuildScriptChoice, GeneratedWorkflow, PackageManager, WorkflowGeneratorOpts } from '../workflow-generator.js';
12
+ import type { WorkflowWriteOptions, WorkflowWriteResult } from '../workflow-writer.js';
13
+ /**
14
+ * Pure helper: given persisted progress, validated OAuth tokens and the user's
15
+ * profile, return a NEW progress object (immutable spread) with:
16
+ * - `_oauthRefreshToken` set to `tokens.refreshToken`
17
+ * - `completedSteps.googleSignInComplete` set to `{ email, googleSubject, scope }`
18
+ *
19
+ * This is the single canonical place where Google sign-in state is written to
20
+ * progress — shared by the Ink core effect (`google-sign-in-running`) and the
21
+ * MCP bridge so both produce identical progress objects.
22
+ */
23
+ export declare function applyGoogleSignIn(progress: AndroidOnboardingProgress, tokens: GoogleOAuthTokens, info: GoogleUserInfo): AndroidOnboardingProgress;
24
+ export type AndroidStepKind = 'auto' | 'input' | 'choice' | 'done' | 'error';
25
+ export interface AndroidStepOption {
26
+ value: string;
27
+ label?: string;
28
+ note?: string;
29
+ }
30
+ export interface AndroidStepView {
31
+ step: AndroidOnboardingStep;
32
+ kind: AndroidStepKind;
33
+ title?: string;
34
+ prompt?: string;
35
+ collect?: string[];
36
+ options?: AndroidStepOption[];
37
+ message?: string;
38
+ }
39
+ export interface AndroidStepCtx {
40
+ appId: string;
41
+ detectedPackageIds?: string[];
42
+ gcpProjects?: {
43
+ projectId: string;
44
+ name: string;
45
+ projectNumber?: string;
46
+ }[];
47
+ detectedAliases?: string[];
48
+ saValidation?: {
49
+ ok: false;
50
+ kind: string;
51
+ message: string;
52
+ } | {
53
+ ok: true;
54
+ };
55
+ /**
56
+ * Task 3 — keystore-existing-key-password prompt boundary.
57
+ * Set to true in AndroidEffectResult.transient when the auto-probe could not
58
+ * resolve the key password and the driver should show the manual input.
59
+ * After the user submits, applyAndroidInput records keystoreKeyPassword and
60
+ * re-running the effect finds it set and completes the phase.
61
+ */
62
+ needsKeyPasswordPrompt?: boolean;
63
+ /**
64
+ * Task 4 — fresh access token returned from google-sign-in-running so the
65
+ * driver can seed its token cache and avoid an immediate refresh on the next
66
+ * step. If the driver ignores it, deps.getAccessToken() will mint one —
67
+ * behaviorally identical.
68
+ */
69
+ accessToken?: string;
70
+ /**
71
+ * keystore-existing-detecting-alias wrong-password signal.
72
+ * Set to true in AndroidEffectResult.transient when listKeystoreAliases returns
73
+ * { ok: false, reason: 'wrong-password' }. The driver (app.tsx) maps this to
74
+ * the original UX: setError + setRetryStep('keystore-existing-store-password')
75
+ * + setStep('error') WITHOUT calling handleError (no retryCount bump).
76
+ */
77
+ wrongPassword?: boolean;
78
+ /** CI-secret entries built at saving-credentials (key/value/masked). */
79
+ ciSecretEntries?: CiSecretEntry[];
80
+ /**
81
+ * Full saved-credential map written at saving-credentials (the 5 build-cred
82
+ * fields, NO CAPGO_TOKEN). The Ink TUI holds the same in its `savedCredentials`
83
+ * React state and the env-export effects write it verbatim. Transient only —
84
+ * never persisted to progress.json (it carries the raw keystore/SA secrets).
85
+ */
86
+ savedCredentials?: Record<string, string>;
87
+ /** CI-secret destinations discovered at detecting-ci-secrets. */
88
+ ciSecretTargets?: CiSecretTarget[];
89
+ /** Per-destination setup advice surfaced when no target is reachable. */
90
+ ciSecretSetupAdvice?: CiSecretSetupAdvice[];
91
+ /** Resolved owner/repo (GitHub) the CLI will push secrets to. */
92
+ ciSecretRepoLabel?: string | null;
93
+ /** Which secret keys already exist on the remote (checking-ci-secrets). */
94
+ ciSecretExistingKeys?: string[];
95
+ /** Human summary of the upload (uploading-ci-secrets). */
96
+ ciSecretUploadSummary?: string;
97
+ /** Absolute path of the written .env file (exporting-env). */
98
+ envExportPath?: string;
99
+ /** Set when env-export found nothing to write or threw — routed to build-complete, never thrown (exporting-env / overwrite-and-export-env). */
100
+ envExportError?: string;
101
+ /** Absolute path of the written workflow file (writing-workflow-file). */
102
+ workflowFilePath?: string;
103
+ /** The queued build URL (requesting-build). */
104
+ buildUrl?: string;
105
+ /** Streamed build-request log lines (requesting-build). */
106
+ buildOutput?: string[];
107
+ /** Captured AI-analysis job id surfaced on a failed build (requesting-build). */
108
+ aiJobId?: string;
109
+ /** Detected package manager from the project's lockfile (pick-package-manager). */
110
+ detectedPackageManager?: string;
111
+ /** All scripts from package.json (pick-build-script picker). */
112
+ availableScripts?: Record<string, string>;
113
+ /** Project-type recommendation surfaced at the top of pick-build-script. */
114
+ recommendedScript?: string | null;
115
+ /** Default `.env` export path shown at ask-export-env (defaultExportPath). */
116
+ defaultEnvExportPath?: string;
117
+ }
118
+ export declare const KIND_TABLE: Record<AndroidOnboardingStep, AndroidStepKind>;
119
+ /**
120
+ * Pure function: given an explicit step name, persisted progress (or null),
121
+ * and the current runtime context, return a UI-framework-neutral description
122
+ * of the step.
123
+ *
124
+ * This is the primary entry-point for drivers that already know the step
125
+ * (e.g. the MCP bridge). `androidStepView` is a thin wrapper that resolves
126
+ * the step from progress first.
127
+ *
128
+ * Dynamic kind for `android-package-select`:
129
+ * - ctx.detectedPackageIds === undefined → 'auto' (preload not yet done)
130
+ * - ctx.detectedPackageIds.length > 0 → 'choice' (options = the ids)
131
+ * - ctx.detectedPackageIds.length === 0 → 'input' (user must type it)
132
+ *
133
+ * All other steps use KIND_TABLE[step] unchanged.
134
+ *
135
+ * No I/O; no mutation of progress.
136
+ */
137
+ export declare function androidViewForStep(step: AndroidOnboardingStep, progress: AndroidOnboardingProgress | null, ctx: AndroidStepCtx): AndroidStepView;
138
+ export type AndroidInput = {
139
+ step: 'credentials-exist';
140
+ value: 'backup' | 'cancel';
141
+ } | {
142
+ step: 'keystore-method-select';
143
+ value: 'existing' | 'generate' | 'learn';
144
+ } | {
145
+ step: 'keystore-existing-path';
146
+ path: string;
147
+ } | {
148
+ step: 'keystore-existing-store-password';
149
+ password: string;
150
+ } | {
151
+ step: 'keystore-existing-alias-select';
152
+ alias: string;
153
+ } | {
154
+ step: 'keystore-existing-alias';
155
+ alias: string;
156
+ } | {
157
+ step: 'keystore-existing-key-password';
158
+ password: string;
159
+ } | {
160
+ step: 'keystore-new-alias';
161
+ alias: string;
162
+ } | {
163
+ step: 'keystore-new-password-method';
164
+ value: 'random' | 'manual';
165
+ } | {
166
+ step: 'keystore-new-store-password';
167
+ password: string;
168
+ } | {
169
+ step: 'keystore-new-key-password';
170
+ password: string;
171
+ } | {
172
+ step: 'keystore-new-cn';
173
+ cn: string;
174
+ } | {
175
+ step: 'service-account-method-select';
176
+ value: 'generate' | 'existing';
177
+ } | {
178
+ step: 'sa-json-existing-path';
179
+ path: string;
180
+ } | {
181
+ step: 'sa-json-validation-failed';
182
+ value: 'retry' | 'oauth';
183
+ } | {
184
+ step: 'sa-json-validation-failed';
185
+ value: 'save-anyway';
186
+ serviceAccountKeyBase64: string;
187
+ } | {
188
+ step: 'play-developer-id-input';
189
+ rawDeveloperIdOrUrl: string;
190
+ } | {
191
+ step: 'gcp-projects-select';
192
+ gcpProject: {
193
+ projectId: string;
194
+ name: string;
195
+ projectNumber?: string;
196
+ };
197
+ } | {
198
+ step: 'gcp-project-create-name';
199
+ displayName: string;
200
+ } | {
201
+ step: 'android-package-select';
202
+ packageName: string;
203
+ source: 'gradle' | 'capacitor-config' | 'user-input';
204
+ serviceAccountMethod: 'generate' | 'existing';
205
+ } | {
206
+ step: 'ci-secrets-setup';
207
+ value: 'retry' | 'skip';
208
+ } | {
209
+ step: 'ci-secrets-target-select';
210
+ ciSecretTarget: CiSecretTarget | null;
211
+ } | {
212
+ step: 'ask-ci-secrets';
213
+ value: 'yes' | 'no';
214
+ } | {
215
+ step: 'confirm-ci-secret-overwrite';
216
+ value: 'replace' | 'skip';
217
+ } | {
218
+ step: 'ci-secrets-failed';
219
+ value: 'retry' | 'continue';
220
+ } | {
221
+ step: 'ask-github-actions-setup';
222
+ value: 'with-workflow' | 'secrets-only' | 'declined';
223
+ } | {
224
+ step: 'confirm-secrets-push';
225
+ value: 'confirm' | 'cancel';
226
+ } | {
227
+ step: 'ask-export-env';
228
+ value: 'no';
229
+ } | {
230
+ step: 'ask-export-env';
231
+ value: 'yes';
232
+ envExportTargetPath: string;
233
+ } | {
234
+ step: 'confirm-env-export-overwrite';
235
+ value: 'replace' | 'skip';
236
+ } | {
237
+ step: 'pick-package-manager';
238
+ selectedPackageManager: PackageManager;
239
+ } | {
240
+ step: 'pick-build-script';
241
+ value: '__custom__';
242
+ } | {
243
+ step: 'pick-build-script';
244
+ buildScriptChoice: BuildScriptChoice;
245
+ } | {
246
+ step: 'pick-build-script-custom';
247
+ command: string;
248
+ } | {
249
+ step: 'preview-workflow-file';
250
+ value: 'write' | 'view' | 'cancel';
251
+ } | {
252
+ step: 'view-workflow-diff';
253
+ value: 'close';
254
+ } | {
255
+ step: 'ask-build';
256
+ value: 'yes' | 'no';
257
+ };
258
+ export declare function applyAndroidInput(step: AndroidOnboardingStep, progress: AndroidOnboardingProgress, input: AndroidInput): AndroidOnboardingProgress;
259
+ export interface AndroidEffectDeps {
260
+ generateKeystore: (opts: KeystoreOptions) => KeystoreResult;
261
+ listKeystoreAliases: (bytes: Uint8Array, password: string) => ListAliasesResult;
262
+ tryUnlockPrivateKey: (bytes: Uint8Array, password: string) => ProbeKeyPasswordResult;
263
+ validateServiceAccountJson: (opts: ValidateOptions) => Promise<ValidationResult>;
264
+ updateSavedCredentials: (appId: string, platform: 'ios' | 'android', credentials: Record<string, string>) => Promise<void>;
265
+ loadSavedCredentials: (appId: string) => Promise<unknown>;
266
+ saveAndroidProgress: (appId: string, progress: AndroidOnboardingProgress) => Promise<void>;
267
+ loadAndroidProgress: (appId: string) => Promise<AndroidOnboardingProgress | null>;
268
+ deleteAndroidProgress: (appId: string) => Promise<void>;
269
+ readFile: (path: string) => Promise<Buffer>;
270
+ copyFile: (src: string, dest: string) => Promise<void>;
271
+ /**
272
+ * Run the full browser OAuth flow. The driver pre-binds OAuth client config
273
+ * (clientId, clientSecret, scopes) so the core never sees credentials —
274
+ * config/scope policy lives in the driver.
275
+ *
276
+ * Signature mirrors RunOAuthFlowOptions from oauth-google.ts (minus
277
+ * timeoutMs/signal which the driver controls internally).
278
+ */
279
+ runOAuthFlow: (callbacks: Pick<RunOAuthFlowOptions, 'onAuthUrl' | 'onStatus'>) => Promise<GoogleOAuthTokens>;
280
+ /**
281
+ * Non-blocking OAuth starter (MCP fire-and-poll). Opens the browser and
282
+ * starts the loopback listener, then returns a PendingOAuthSession immediately
283
+ * without waiting for sign-in to complete. The MCP bridge uses this to
284
+ * avoid blocking a single tool call on the full OAuth round-trip.
285
+ *
286
+ * Optional — only provided by the MCP driver. The Ink driver uses the
287
+ * blocking `runOAuthFlow` instead and does not need this dep.
288
+ *
289
+ * The driver pre-binds OAuth client config (clientId, clientSecret, scopes).
290
+ */
291
+ startOAuthFlow?: (callbacks?: Pick<RunOAuthFlowOptions, 'onAuthUrl' | 'onStatus'>) => Promise<PendingOAuthSession>;
292
+ /** Fetch the signed-in user's profile (email, sub). */
293
+ fetchUserInfo: (accessToken: string) => Promise<GoogleUserInfo>;
294
+ /**
295
+ * Mint a fresh access token from the stored refresh token. Called before
296
+ * each cloud step that needs one. The driver owns the token cache and
297
+ * handles expiry.
298
+ */
299
+ getAccessToken: () => Promise<string>;
300
+ /**
301
+ * Revoke a Google OAuth refresh token. Best-effort — the core swallows
302
+ * failures (non-fatal; the token expires on its own).
303
+ */
304
+ revokeToken: (refreshToken: string) => Promise<void>;
305
+ /**
306
+ * List GCP projects the user has access to.
307
+ * Mirrors gcp-api.ts: listProjects(accessToken).
308
+ */
309
+ listProjects: (accessToken: string) => Promise<GcpProject[]>;
310
+ /**
311
+ * Create a GCP project and wait for the operation to finish.
312
+ * Mirrors gcp-api.ts: createProject(accessToken, projectId, displayName).
313
+ */
314
+ createProject: (accessToken: string, projectId: string, displayName: string) => Promise<GcpProject>;
315
+ /**
316
+ * Enable an API on a project (idempotent).
317
+ * Mirrors gcp-api.ts: enableService(accessToken, projectId, serviceName).
318
+ */
319
+ enableService: (accessToken: string, projectId: string, serviceName: string) => Promise<void>;
320
+ /**
321
+ * Find or create the Capgo service account in a project.
322
+ * Mirrors gcp-api.ts: ensureServiceAccount(args).
323
+ */
324
+ ensureServiceAccount: (args: {
325
+ accessToken: string;
326
+ projectId: string;
327
+ accountId: string;
328
+ displayName?: string;
329
+ description?: string;
330
+ }) => Promise<{
331
+ account: GcpServiceAccount;
332
+ created: boolean;
333
+ }>;
334
+ /**
335
+ * Create a JSON key for a service account.
336
+ * Mirrors gcp-api.ts: createServiceAccountKey(args).
337
+ */
338
+ createServiceAccountKey: (args: {
339
+ accessToken: string;
340
+ projectId: string;
341
+ serviceAccountEmail: string;
342
+ }) => Promise<GcpServiceAccountKey>;
343
+ /**
344
+ * Invite the service account into the Play Console developer account.
345
+ * Mirrors play-api.ts: inviteServiceAccount(args).
346
+ */
347
+ inviteServiceAccount: (args: {
348
+ accessToken: string;
349
+ developerId: string;
350
+ serviceAccountEmail: string;
351
+ developerAccountPermissions?: readonly string[];
352
+ grants?: ReadonlyArray<{
353
+ packageName: string;
354
+ permissions: readonly string[];
355
+ }>;
356
+ }) => Promise<void>;
357
+ /**
358
+ * Find applicationId values in the Android Gradle build files.
359
+ * The driver pre-binds `androidDir` so this dep is argless from the core's
360
+ * perspective. The driver calls findAndroidApplicationIds(androidDir) under
361
+ * the hood.
362
+ */
363
+ findAndroidApplicationIds: () => Promise<string[]>;
364
+ /**
365
+ * Build the CI-secret entries (key/value/masked) from the saved credentials.
366
+ * Mirrors ci-secrets.ts: createCiSecretEntries(credentials, apiKey?).
367
+ */
368
+ createCiSecretEntries?: (credentials: Partial<BuildCredentials>, apiKey?: string) => CiSecretEntry[];
369
+ /**
370
+ * Detect which CI-secret destinations (GitHub/GitLab) are reachable.
371
+ * Mirrors ci-secrets.ts: detectCiSecretTargets(runner?). The driver pre-binds
372
+ * the command runner, so the core calls this with no args.
373
+ */
374
+ detectCiSecretTargets?: (runner?: CommandRunner) => CiSecretDiscovery;
375
+ /**
376
+ * Resolve the concrete `owner/repo` (GitHub) or `group/project` (GitLab) the
377
+ * CLI will target, so the user can confirm before any secret is overwritten.
378
+ * Mirrors ci-secrets.ts: getCiSecretRepoLabelAsync(target, runner?).
379
+ */
380
+ getCiSecretRepoLabelAsync?: (target: CiSecretTarget, runner?: AsyncCommandRunner) => Promise<string | null>;
381
+ /**
382
+ * List which of `keys` already exist as secrets/variables on the remote.
383
+ * Mirrors ci-secrets.ts: listExistingCiSecretKeysAsync(target, keys, runner?).
384
+ */
385
+ listExistingCiSecretKeysAsync?: (target: CiSecretTarget, keys: string[], runner?: AsyncCommandRunner) => Promise<string[]>;
386
+ /**
387
+ * Push the CI-secret entries to the target, reporting per-key progress.
388
+ * Mirrors ci-secrets.ts: uploadCiSecretsAsync(target, entries, existingKeys?, runner?, onProgress?).
389
+ */
390
+ uploadCiSecretsAsync?: (target: CiSecretTarget, entries: CiSecretEntry[], existingKeys?: string[], runner?: AsyncCommandRunner, onProgress?: (current: number, total: number, keyName: string) => void) => Promise<void>;
391
+ /**
392
+ * Write the credentials to a local 0o600 `.env` file (no git operation).
393
+ * Mirrors env-export.ts: exportCredentialsToEnv(opts).
394
+ */
395
+ exportCredentialsToEnv?: (opts: EnvExportOpts) => EnvExportResult;
396
+ /**
397
+ * Resolve the default `.env` export path for an app + platform (pure).
398
+ * Mirrors env-export.ts: defaultExportPath(appId, platform).
399
+ */
400
+ defaultExportPath?: (appId: string, platform: 'ios' | 'android') => string;
401
+ /**
402
+ * Generate the GitHub Actions workflow YAML (pure).
403
+ * Mirrors workflow-generator.ts: generateWorkflow(opts).
404
+ */
405
+ generateWorkflow?: (opts: WorkflowGeneratorOpts) => GeneratedWorkflow;
406
+ /**
407
+ * Generate + write the workflow file to `.github/workflows/capgo-build.yml`.
408
+ * Mirrors workflow-writer.ts: writeWorkflowFile(opts, writeOptions?).
409
+ */
410
+ writeWorkflowFile?: (opts: WorkflowGeneratorOpts, writeOptions?: WorkflowWriteOptions) => WorkflowWriteResult;
411
+ /**
412
+ * Fire the actual `capgo build request`. The driver pre-binds the logger /
413
+ * silent flag it owns; the core supplies appId + options.
414
+ * Mirrors request.ts: requestBuildInternal(appId, options, silent?, logger?).
415
+ */
416
+ requestBuildInternal?: (appId: string, options: BuildRequestOptions, silent?: boolean, logger?: BuildLogger) => Promise<BuildRequestResult>;
417
+ /**
418
+ * The streaming BuildLogger the TUI threads into requestBuildInternal (the 4th
419
+ * arg) so every build line streams into `setBuildOutput`. Forwarded verbatim.
420
+ */
421
+ logger?: BuildLogger;
422
+ /**
423
+ * The build VIEWER sink — the android TUI feeds this into `setBuildOutput` (the
424
+ * dedicated build output pane, DISTINCT from the `onLog` side-log). The shared
425
+ * tail writes the build header / blank+queued / ⚠ failure / no-key UX / catch
426
+ * lines here. Forwarded verbatim through `toTailDeps`. No-op when absent.
427
+ */
428
+ onBuildOutput?: (line: string) => void;
429
+ /**
430
+ * Resolves the Capgo API key the build request should use, mirroring the
431
+ * android tail's CLI-flag-over-saved precedence. Returns undefined when no key
432
+ * is resolvable (the no-key UX finishes at build-complete). Forwarded verbatim.
433
+ */
434
+ resolveApikey?: () => string | undefined;
435
+ /**
436
+ * Per-key CI-secret upload progress, forwarded as the 5th arg of
437
+ * uploadCiSecretsAsync. The android tail feeds this into
438
+ * `setCiSecretUploadProgress`. No-op when absent.
439
+ */
440
+ onCiSecretUploadProgress?: (current: number, total: number, keyName: string) => void;
441
+ /**
442
+ * The 2-phase checking-ci-secrets status text. The android tail feeds this into
443
+ * `setCiSecretCheckPhase`. No-op when absent.
444
+ */
445
+ onCiSecretCheckPhase?: (phase: string) => void;
446
+ /**
447
+ * The ci-secrets-failed reason (repo-null / catch in checking-ci-secrets). The
448
+ * android tail feeds this into `setCiSecretError` (rendered by the
449
+ * CiSecretsFailedStep). Forwarded verbatim through `toTailDeps`. No-op when absent.
450
+ */
451
+ onCiSecretError?: (message: string) => void;
452
+ /** Reads the project's package.json scripts map (workflow-builder preload). */
453
+ getPackageScripts?: () => Record<string, string>;
454
+ /** Detects the web-framework project type (best-effort; may resolve null). */
455
+ findProjectType?: (options?: {
456
+ quiet?: boolean;
457
+ }) => Promise<string | null>;
458
+ /** Maps a detected project type to its recommended build script name. */
459
+ findBuildCommandForProjectType?: (projectType: string) => Promise<string | null>;
460
+ /**
461
+ * Workflow-file telemetry hook (e.g. 'workflow-file-written'). The android tail
462
+ * calls `trackWorkflowEvent`. No-op when absent.
463
+ */
464
+ trackWorkflowEvent?: (event: string, options?: {
465
+ decision?: string;
466
+ }) => void;
467
+ /**
468
+ * DRIVER-HELD transient tail state, threaded back into each post-save tail
469
+ * effect. The Ink TUI resolves these ONCE (at `saving-credentials`) and keeps
470
+ * them in React state (`savedCredentials` / `ciSecretEntries` /
471
+ * `ciSecretExistingKeys`); a headless driver mirrors that by capturing the
472
+ * matching `AndroidEffectResult.transient` from each effect and passing it
473
+ * back here on the NEXT effect. The engine NEVER persists these to
474
+ * progress.json — they are secrets/credentials/entries that must stay in
475
+ * memory only. When a field is absent (e.g. a crash-recovery resume where the
476
+ * driver lost its in-memory state) the effect falls back to a SINGLE lossy
477
+ * re-derivation from progress (rebuildTailCredentials / createCiSecretEntries)
478
+ * rather than resolving the Capgo API key a second time.
479
+ */
480
+ carried?: {
481
+ /** Full saved credentials map written at saving-credentials (5 fields, no CAPGO_TOKEN). */
482
+ savedCredentials?: Record<string, string>;
483
+ /** CI-secret entries resolved ONCE at saving-credentials (creds + Capgo API key → CAPGO_TOKEN). */
484
+ ciSecretEntries?: CiSecretEntry[];
485
+ /** Which secret keys already exist on the remote, resolved at checking-ci-secrets. */
486
+ ciSecretExistingKeys?: string[];
487
+ /** Whether the workflow file did NOT exist at preview (app.tsx's `previewIsNew`); drives 'Wrote' vs 'Overwrote'. Defaults to NEW when absent. */
488
+ workflowIsNew?: boolean;
489
+ };
490
+ onStatus?: (message: string) => void;
491
+ onLog?: (message: string, color?: string) => void;
492
+ /** Internal-only diagnostic line → the support internal log (main PR #2406). Optional; no-op when absent. */
493
+ onInternalLog?: (line: string) => void;
494
+ onAuthUrl?: (url: string) => void;
495
+ signal?: AbortSignal;
496
+ }
497
+ export interface AndroidEffectResult {
498
+ /** Updated progress after the effect ran (matches what was persisted). */
499
+ progress: AndroidOnboardingProgress;
500
+ /** Explicit next step when not derivable from progress alone (★ transitions). */
501
+ next?: AndroidOnboardingStep;
502
+ /** Transient runtime data that lives in the driver but is NOT persisted. */
503
+ transient?: Partial<AndroidStepCtx>;
504
+ }
505
+ export declare function runAndroidEffect(step: AndroidOnboardingStep, progress: AndroidOnboardingProgress, deps: AndroidEffectDeps): Promise<AndroidEffectResult>;
@@ -20,6 +20,24 @@ export interface KeystoreResult {
20
20
  alias: string;
21
21
  notAfter: Date;
22
22
  }
23
+ /**
24
+ * Sanitize a keystore alias for use as an on-disk filename component.
25
+ *
26
+ * The alias originates from user input (e.g. `keystoreNewAlias`). It is used
27
+ * verbatim for the keystore crypto and the saved `KEYSTORE_KEY_ALIAS`, but the
28
+ * value used to build the `<alias>.p12` filename must be sanitized so a value
29
+ * like `../../evil` or `/etc/x` cannot escape the target directory.
30
+ *
31
+ * Rules:
32
+ * - strip any directory components (keep only the basename),
33
+ * - allow only `[A-Za-z0-9._-]`, replacing any other char with `_`,
34
+ * - normalize empty or dot-only results (``, `.`, `..`) to a safe default,
35
+ * - the return value never contains `/`, `\`, or `..`.
36
+ *
37
+ * IMPORTANT: this is ONLY for the filename. Do NOT use it for the crypto alias
38
+ * or the saved key alias — those must stay exactly what the user chose.
39
+ */
40
+ export declare function sanitizeKeystoreAlias(alias: string): string;
23
41
  /**
24
42
  * Generate a URL-safe random password suitable for Android keystore use.
25
43
  * 24 bytes → 32-char base64url string. Collision-resistant, never written in logs.
@@ -123,12 +123,43 @@ export interface LoopbackCallbackResult {
123
123
  */
124
124
  finishResponse: (html: string, statusCode?: number) => void;
125
125
  }
126
+ /**
127
+ * The shape returned by `startOAuthFlow`. The loopback server is already
128
+ * running and the browser has been opened; the caller awaits `result` at a
129
+ * later point (fire-and-poll pattern for MCP).
130
+ */
131
+ export interface PendingOAuthSession {
132
+ /** Full Google authorization URL that was opened in the browser. */
133
+ authUrl: string;
134
+ /** The loopback redirect URI embedded in the authUrl. */
135
+ redirectUri: string;
136
+ /**
137
+ * Resolves with validated tokens once the browser callback lands,
138
+ * code is exchanged and scopes pass. Rejects on error/timeout/missing-scopes.
139
+ */
140
+ result: Promise<GoogleOAuthTokens>;
141
+ /** Force-close the loopback server (safe after result settles). */
142
+ close: () => void;
143
+ }
144
+ /**
145
+ * Non-blocking OAuth starter: opens the browser and starts the loopback
146
+ * listener, then returns IMMEDIATELY without waiting for the sign-in to
147
+ * complete. The caller can `await session.result` later to collect tokens.
148
+ *
149
+ * This is the foundation for the MCP fire-and-poll sign-in model. The Ink
150
+ * wizard uses `runOAuthFlow` (which internally calls this and then awaits
151
+ * `result`) to preserve its blocking behavior.
152
+ */
153
+ export declare function startOAuthFlow(config: GoogleOAuthConfig, options?: RunOAuthFlowOptions): Promise<PendingOAuthSession>;
126
154
  /**
127
155
  * Run the full browser-based OAuth flow and return tokens.
128
156
  *
129
157
  * Side effects:
130
158
  * - Opens a browser window at Google's consent screen.
131
159
  * - Starts (and later stops) a loopback HTTP server on 127.0.0.1.
160
+ *
161
+ * Delegates to `startOAuthFlow` and awaits the result — preserving the
162
+ * original blocking behavior Ink depends on.
132
163
  */
133
164
  export declare function runOAuthFlow(config: GoogleOAuthConfig, options?: RunOAuthFlowOptions): Promise<GoogleOAuthTokens>;
134
165
  export {};
@@ -0,0 +1,4 @@
1
+ /** OAuth scopes for Capgo Android onboarding — androidpublisher (Play) plus
2
+ * cloud-platform (create GCP projects / service accounts / keys). Shared by the
3
+ * Ink wizard and the MCP bridge so the two drivers can never drift. */
4
+ export declare const OAUTH_SCOPES_FOR_ONBOARDING: readonly ["openid", "https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/androidpublisher", "https://www.googleapis.com/auth/cloud-platform"];