@capgo/cli 8.3.1 → 8.4.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.
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 +226 -226
  31. package/package.json +26 -2
@@ -0,0 +1,51 @@
1
+ export type StepKind = 'auto' | 'human_gate' | 'choice' | 'done' | 'error' | 'info';
2
+ export type OnboardingPhase = 'preflight' | 'app' | 'credentials' | 'build' | 'done';
3
+ export type Platform = 'ios' | 'android';
4
+ export interface ChoiceOption {
5
+ value: string;
6
+ label?: string;
7
+ note?: string;
8
+ }
9
+ export interface CollectField {
10
+ field: string;
11
+ desc: string;
12
+ }
13
+ export interface NextAction {
14
+ /** The exact tool to call next. */
15
+ tool: string;
16
+ /** Argument hint for the model. */
17
+ with?: Record<string, unknown>;
18
+ /** A literal, copy-pasteable example call the model can pattern-match. */
19
+ call?: string;
20
+ /** Plain-English directive. */
21
+ instruction: string;
22
+ }
23
+ export interface NextStepResult {
24
+ onboarding: 'capgo-builder';
25
+ phase: OnboardingPhase;
26
+ /** Granular step name (reuses OnboardingStep vocabulary where applicable). */
27
+ state: string;
28
+ platform?: Platform;
29
+ /** 0–100. */
30
+ progress: number;
31
+ kind: StepKind;
32
+ summary: string;
33
+ /** Human-facing journey overview; informational, not an execution list. */
34
+ roadmap?: string[];
35
+ context?: Record<string, unknown>;
36
+ /** Present when kind === 'choice'. */
37
+ options?: ChoiceOption[];
38
+ /** Present when kind === 'human_gate'. */
39
+ human?: {
40
+ instruction: string;
41
+ resourceUri?: string;
42
+ };
43
+ /** Present when kind === 'human_gate': what to bring back. */
44
+ collect?: CollectField[];
45
+ next?: NextAction;
46
+ /** Rules of engagement; included on the first result of a session. */
47
+ rules?: string[];
48
+ }
49
+ export declare const ONBOARDING_RULES: string[];
50
+ /** Render a result into MCP text content: imperative directive first, structured data last. */
51
+ export declare function renderResult(result: NextStepResult): string;
@@ -0,0 +1,131 @@
1
+ import type { AndroidOnboardingProgress } from '../android/types.js';
2
+ import type { AndroidEffectDeps } from '../android/flow.js';
3
+ import type { OnboardingProgress } from '../types.js';
4
+ import type { NextStepResult, Platform } from './contract.js';
5
+ import type { BuildOutputRecord } from '../../output-record.js';
6
+ import { androidViewForStep } from '../android/flow.js';
7
+ import { beginOAuthSession, clearOAuthSession, pollOAuthSession } from './oauth-session.js';
8
+ /** Facts gathered during preflight; the pure deciders branch only on these. */
9
+ export interface PreflightFacts {
10
+ capacitorProject: boolean;
11
+ appId?: string;
12
+ platformsDetected: Platform[];
13
+ authenticated: boolean;
14
+ appRegistered: boolean;
15
+ androidProgress: AndroidOnboardingProgress | null;
16
+ iosProgress: OnboardingProgress | null;
17
+ }
18
+ /** User input carried into the flow via next_step. */
19
+ interface OnboardingInput {
20
+ platform?: string;
21
+ serviceAccountJsonPath?: string;
22
+ runBuild?: boolean;
23
+ checkBuild?: boolean;
24
+ keyId?: string;
25
+ issuerId?: string;
26
+ p8Path?: string;
27
+ serviceAccountMethod?: 'generate' | 'existing';
28
+ playDeveloperId?: string;
29
+ gcpProjectId?: string;
30
+ gcpProjectName?: string;
31
+ androidPackage?: string;
32
+ saMethodChoice?: 'retry' | 'save-anyway' | 'oauth';
33
+ credentialsExistChoice?: 'backup' | 'cancel';
34
+ keystoreMethod?: 'existing' | 'generate';
35
+ keystorePath?: string;
36
+ keystoreStorePassword?: string;
37
+ keystoreAlias?: string;
38
+ keystoreKeyPassword?: string;
39
+ keystoreNewAlias?: string;
40
+ keystorePasswordMethod?: 'random' | 'manual';
41
+ keystoreCommonName?: string;
42
+ }
43
+ /** Decide the first/again step for a fresh or resumed session. */
44
+ export declare function decideStart(facts: PreflightFacts, progress: OnboardingProgress | null, deps: EngineDeps): Promise<NextStepResult>;
45
+ export declare function decideIos(facts: PreflightFacts): NextStepResult;
46
+ export declare function mapAndroidView(view: ReturnType<typeof androidViewForStep>, facts: PreflightFacts, opts?: {
47
+ keystorePath?: string;
48
+ keystorePassword?: string;
49
+ }): NextStepResult;
50
+ export declare function decideAndroid(facts: PreflightFacts, deps: EngineDeps, opts?: {
51
+ signInProceed?: boolean;
52
+ }): Promise<NextStepResult>;
53
+ export declare function decideAdvance(facts: PreflightFacts, progress: OnboardingProgress | null, input: OnboardingInput | undefined, deps: EngineDeps): Promise<NextStepResult>;
54
+ export interface EngineDeps {
55
+ cwd: string;
56
+ hasSavedKey: () => boolean;
57
+ getAppId: () => Promise<string | undefined>;
58
+ detectPlatforms: () => Promise<Platform[]>;
59
+ isAppRegistered: (appId: string) => Promise<boolean>;
60
+ loadProgress: (appId: string) => Promise<OnboardingProgress | null>;
61
+ registerApp: (appId: string) => Promise<{
62
+ ok: true;
63
+ } | {
64
+ ok: false;
65
+ alreadyExists: boolean;
66
+ error: string;
67
+ }>;
68
+ loadAndroidProgress: (appId: string) => Promise<AndroidOnboardingProgress | null>;
69
+ finalizeAndroidCredentials: (appId: string) => Promise<{
70
+ ok: true;
71
+ } | {
72
+ ok: false;
73
+ error: string;
74
+ }>;
75
+ readBuildRecord: (path: string) => Promise<BuildOutputRecord | null>;
76
+ buildRecordPath: (appId: string, platform: Platform) => string;
77
+ setIosApiKey: (appId: string, keyId: string, issuerId: string, p8Path: string) => Promise<void>;
78
+ finalizeIosCredentials: (appId: string) => Promise<{
79
+ ok: true;
80
+ } | {
81
+ ok: false;
82
+ error: string;
83
+ }>;
84
+ androidEffectDeps: AndroidEffectDeps;
85
+ /** Returns true when the current host can launch Terminal.app (macOS). Injectable for tests. */
86
+ canLaunchTerminal: () => boolean;
87
+ /** Launch `command` in a new macOS Terminal.app window. Injectable for tests. */
88
+ launchBuildInTerminal: (command: string) => Promise<{
89
+ ok: true;
90
+ } | {
91
+ ok: false;
92
+ error: string;
93
+ }>;
94
+ /**
95
+ * Optional injectable OAuth session registry for testing. When provided, the
96
+ * engine uses these instead of the module-level functions in oauth-session.ts.
97
+ * Production builds omit this and rely on the module-level registry.
98
+ */
99
+ oauthSession?: {
100
+ begin: typeof beginOAuthSession;
101
+ poll: typeof pollOAuthSession;
102
+ clear: typeof clearOAuthSession;
103
+ };
104
+ /**
105
+ * Write the generated/loaded Android keystore (.p12) to a file on disk so the
106
+ * user has a durable copy after onboarding. Called once when the keystore phase
107
+ * completes. Returns the absolute path of the written file.
108
+ *
109
+ * Optional — when omitted the keystore is kept in progress only (no file written).
110
+ * Omitting does not break the flow; the keystore data is always in _keystoreBase64.
111
+ */
112
+ writeKeystoreFile?: (appId: string, base64: string, alias: string) => Promise<string>;
113
+ }
114
+ export declare function gatherFacts(deps: EngineDeps): Promise<PreflightFacts>;
115
+ export declare function runStart(deps: EngineDeps): Promise<NextStepResult>;
116
+ export declare function runAdvance(deps: EngineDeps, input?: OnboardingInput): Promise<NextStepResult>;
117
+ /**
118
+ * Read-only: determine the onboarding state the user is currently on, WITHOUT
119
+ * running any side effect. Mirrors the branch selection of decideStart/decideAndroid
120
+ * (preflight → platform → android resume step) but never calls effects.
121
+ */
122
+ export declare function resolveCurrentState(facts: PreflightFacts): string;
123
+ /**
124
+ * Read-only "explain the current step" entry point backing the
125
+ * capgo_builder_onboarding_explain tool. Gathers facts (read-only) and returns a
126
+ * plain-language explanation string. Never advances the flow or runs effects.
127
+ */
128
+ export declare function explainOnboarding(deps: EngineDeps, input?: {
129
+ state?: string;
130
+ }): Promise<string>;
131
+ export {};
@@ -0,0 +1,4 @@
1
+ /** state → multi-line plain-language explanation (WHAT · WHY · OPTIONS · WHAT TO DO). */
2
+ export declare const EXPLANATIONS: Record<string, string>;
3
+ /** Return the explanation for a state, or a generic fallback for unknown states. */
4
+ export declare function explainForState(state: string | undefined | null): string;
@@ -0,0 +1,29 @@
1
+ import type { GoogleOAuthTokens } from '../android/oauth-google.js';
2
+ import type { PendingOAuthSession } from '../android/oauth-google.js';
3
+ export type { PendingOAuthSession };
4
+ /**
5
+ * Start a new OAuth session for `appId`, replacing any existing one.
6
+ *
7
+ * `start` is a factory that returns a `PendingOAuthSession` (e.g.
8
+ * `() => startOAuthFlow(config, options)`). Passing a factory rather than the
9
+ * session directly lets the caller defer construction until after the prior
10
+ * session has been cleaned up.
11
+ */
12
+ export declare function beginOAuthSession(appId: string, start: () => Promise<PendingOAuthSession>): Promise<void>;
13
+ /**
14
+ * Poll the current status for `appId`.
15
+ *
16
+ * Returns `{ status: 'absent' }` when no session has been started or the
17
+ * entry has been cleared. Otherwise returns the entry's current
18
+ * `status`/`tokens`/`error`.
19
+ */
20
+ export declare function pollOAuthSession(appId: string): {
21
+ status: 'pending' | 'done' | 'error' | 'absent';
22
+ tokens?: GoogleOAuthTokens;
23
+ error?: Error;
24
+ };
25
+ /**
26
+ * Close and remove the session entry for `appId`. Safe to call on an absent
27
+ * appId or after the result has already settled.
28
+ */
29
+ export declare function clearOAuthSession(appId: string): void;
@@ -0,0 +1,17 @@
1
+ import type { CapgoSDK } from '../../../sdk.js';
2
+ import type { EngineDeps } from './engine.js';
3
+ /** Minimal shape of the MCP server's tool registrar (matches McpServer.tool). */
4
+ interface McpLike {
5
+ tool: (name: string, description: string, schema: Record<string, unknown>, handler: (args: any) => Promise<{
6
+ content: Array<{
7
+ type: 'text';
8
+ text: string;
9
+ }>;
10
+ }>) => unknown;
11
+ }
12
+ /**
13
+ * Register the 2-tool onboarding spine onto an MCP server.
14
+ * `depsOverride` is for tests; production passes only `server` + `sdk`.
15
+ */
16
+ export declare function registerOnboardingTools(server: McpLike, sdk: CapgoSDK, depsOverride?: EngineDeps): void;
17
+ export {};
@@ -0,0 +1,45 @@
1
+ import type { AndroidOnboardingStep } from '../android/types.js';
2
+ /** state → the set of input fields that legitimately answer it (in order). */
3
+ export declare const STEP_ALLOWED_FIELDS: Partial<Record<AndroidOnboardingStep, string[]>>;
4
+ /** The set of all android input keys we govern (for the extras check). */
5
+ export declare const ANDROID_INPUT_KEYS: string[];
6
+ /**
7
+ * Validate an incoming next_step input against the step it answers.
8
+ *
9
+ * Returns { ok:true } when the input carries EXACTLY ONE of the step's allowed
10
+ * fields and no other governed android key. Otherwise { ok:false } with the
11
+ * allowed fields + the offending extra keys for a corrective message.
12
+ *
13
+ * Steps with no allowed-field entry (auto/sign-in/no-field) are not governed and
14
+ * always pass — the strict gate only constrains interactive input steps.
15
+ *
16
+ * @param currentStep the resume step the user is currently on
17
+ * @param input the next_step input object
18
+ */
19
+ export declare function validateStepInput(currentStep: AndroidOnboardingStep, input: Record<string, unknown>): {
20
+ ok: boolean;
21
+ allowedFields?: string[];
22
+ extras: string[];
23
+ };
24
+ /**
25
+ * Content validation for the keystore store-password steps, mirroring the ink
26
+ * TUI onSubmit guards in app.tsx so the stateless MCP path enforces the SAME
27
+ * rule before a value is persisted (and before it can reach keystore-generating):
28
+ *
29
+ * - keystore-new-store-password → reject < 6 chars (app.tsx:2575,
30
+ * 'Password must be at least 6 characters')
31
+ * - keystore-existing-store-password → reject empty (app.tsx:2455,
32
+ * 'Store password cannot be empty')
33
+ *
34
+ * Returns { ok:true } when the value passes (or the step is not a store-password
35
+ * step). On failure returns { ok:false, message } with the exact main wording so
36
+ * the gate can re-render the current step with a corrective summary and persist
37
+ * nothing.
38
+ *
39
+ * @param currentStep the resume step the user is currently on
40
+ * @param storePassword the supplied keystoreStorePassword (or undefined/null)
41
+ */
42
+ export declare function validateStorePassword(currentStep: AndroidOnboardingStep, storePassword: string | undefined | null): {
43
+ ok: boolean;
44
+ message?: string;
45
+ };
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Build the arguments array for invoking osascript to open a Terminal window
3
+ * running `command`. Escapes backslashes first, then double-quotes, so that
4
+ * neither character can break out of the AppleScript string literal.
5
+ */
6
+ export declare function buildOsascriptArgs(command: string): string[];
7
+ /**
8
+ * Returns true when the current platform is macOS (darwin).
9
+ * Accepts an explicit platform string for testability.
10
+ */
11
+ export declare function canLaunchTerminal(platform?: string): boolean;
12
+ /**
13
+ * Launch `command` in a new macOS Terminal.app window via osascript.
14
+ * The optional `exec` parameter is injectable so tests never spawn a real
15
+ * process — pass a fake to avoid side effects.
16
+ */
17
+ export declare function launchBuildInTerminal(command: string, exec?: (cmd: string, args: string[]) => Promise<unknown>): Promise<{
18
+ ok: true;
19
+ } | {
20
+ ok: false;
21
+ error: string;
22
+ }>;
@@ -0,0 +1,283 @@
1
+ import type { BuildCredentials } from '../../../schemas/build.js';
2
+ import type { BuildLogger, BuildRequestOptions, BuildRequestResult } from '../../request.js';
3
+ import type { AsyncCommandRunner, CiSecretDiscovery, CiSecretEntry, CiSecretSetupAdvice, CiSecretTarget, CommandRunner } from '../ci-secrets.js';
4
+ import type { EnvExportOpts, EnvExportResult } from '../env-export.js';
5
+ import type { BuildScriptChoice, GeneratedWorkflow, PackageManager, WorkflowGeneratorOpts } from '../workflow-generator.js';
6
+ import type { WorkflowWriteOptions, WorkflowWriteResult } from '../workflow-writer.js';
7
+ export type TailStep = 'saving-credentials' | 'detecting-ci-secrets' | 'ci-secrets-setup' | 'ci-secrets-target-select' | 'ask-ci-secrets' | 'checking-ci-secrets' | 'confirm-ci-secret-overwrite' | 'uploading-ci-secrets' | 'ci-secrets-failed' | 'ask-github-actions-setup' | 'confirm-secrets-push' | 'ask-export-env' | 'exporting-env' | 'confirm-env-export-overwrite' | 'overwrite-and-export-env' | 'pick-package-manager' | 'pick-build-script' | 'pick-build-script-custom' | 'preview-workflow-file' | 'view-workflow-diff' | 'writing-workflow-file' | 'ask-build' | 'requesting-build' | 'build-complete';
8
+ export type TailStepKind = 'auto' | 'input' | 'choice' | 'done' | 'error';
9
+ export interface TailStepOption {
10
+ value: string;
11
+ label?: string;
12
+ note?: string;
13
+ }
14
+ export interface TailStepView {
15
+ step: string;
16
+ kind: TailStepKind;
17
+ title?: string;
18
+ prompt?: string;
19
+ collect?: string[];
20
+ options?: TailStepOption[];
21
+ message?: string;
22
+ }
23
+ /**
24
+ * Runtime context for the tail views — the OPTIONAL transient data a driver
25
+ * surfaces from a prior effect. Mirrors the tail subset of `AndroidStepCtx` so
26
+ * the android engine can pass its ctx straight through.
27
+ */
28
+ export interface TailStepCtx {
29
+ ciSecretEntries?: CiSecretEntry[];
30
+ ciSecretTargets?: CiSecretTarget[];
31
+ ciSecretSetupAdvice?: CiSecretSetupAdvice[];
32
+ ciSecretRepoLabel?: string | null;
33
+ detectedPackageManager?: string;
34
+ availableScripts?: Record<string, string>;
35
+ recommendedScript?: string | null;
36
+ defaultEnvExportPath?: string;
37
+ }
38
+ export interface TailEffectProgress {
39
+ appId: string;
40
+ setupMode?: 'undecided' | 'with-workflow' | 'secrets-only' | 'declined';
41
+ ciSecretTarget?: CiSecretTarget | null;
42
+ selectedPackageManager?: PackageManager | null;
43
+ buildScriptChoice?: BuildScriptChoice | null;
44
+ envExportTargetPath?: string;
45
+ /**
46
+ * Android-only marker that gates the random-password backup hint at
47
+ * saving-credentials. DRIVER REQUIREMENT: the driver MUST set this on `progress`
48
+ * when its keystore step auto-generated the store password (the bespoke android
49
+ * tail only held this in React `randomPasswordGenerated` state, never persisted);
50
+ * the engine reads it from `progress` and has no other source. Never set on iOS.
51
+ */
52
+ keystorePasswordGenerated?: boolean;
53
+ }
54
+ export interface TailEffectDeps<P extends TailEffectProgress = TailEffectProgress> {
55
+ /** Tags the saved-cred store, env-export filename and build/workflow platform. */
56
+ platform: 'ios' | 'android';
57
+ /**
58
+ * Build the platform credential SHAPE written at `saving-credentials` (e.g.
59
+ * ANDROID_KEYSTORE_FILE… on android). Throws on missing inputs — same guards
60
+ * the android engine used inline.
61
+ */
62
+ buildSavedCredentials: (progress: P) => Record<string, string> | Promise<Record<string, string>>;
63
+ /**
64
+ * Lossy fallback used when the driver did not thread the saved-credential map
65
+ * through `carried` (crash-recovery resume). Returns {} when not rebuildable.
66
+ */
67
+ rebuildTailCredentials: (progress: P) => Record<string, string>;
68
+ /**
69
+ * The platform's resume resolver — used by the `saving-credentials` self-heal
70
+ * guard to detect a progress that should resume elsewhere.
71
+ */
72
+ resumeStep: (progress: P) => string;
73
+ updateSavedCredentials: (appId: string, platform: 'ios' | 'android', credentials: Record<string, string>) => Promise<void>;
74
+ loadProgress: (appId: string) => Promise<P | null>;
75
+ /**
76
+ * Persist progress. NOTE: the POST-SAVE tail never calls this — saving-credentials
77
+ * deletes progress.json and every later tail step runs purely from transient/
78
+ * carried (the bespoke android tail is in-memory-only), so persisting would
79
+ * re-create the deleted file. Kept on the surface so drivers can still supply it
80
+ * (and for symmetry with the pre-save engine), but unused by runTailEffect.
81
+ */
82
+ saveProgress: (appId: string, progress: P) => Promise<void>;
83
+ deleteProgress: (appId: string) => Promise<void>;
84
+ createCiSecretEntries?: (credentials: Partial<BuildCredentials>, apiKey?: string) => CiSecretEntry[];
85
+ detectCiSecretTargets?: (runner?: CommandRunner) => CiSecretDiscovery;
86
+ getCiSecretRepoLabelAsync?: (target: CiSecretTarget, runner?: AsyncCommandRunner) => Promise<string | null>;
87
+ listExistingCiSecretKeysAsync?: (target: CiSecretTarget, keys: string[], runner?: AsyncCommandRunner) => Promise<string[]>;
88
+ uploadCiSecretsAsync?: (target: CiSecretTarget, entries: CiSecretEntry[], existingKeys?: string[], runner?: AsyncCommandRunner, onProgress?: (current: number, total: number, keyName: string) => void) => Promise<void>;
89
+ exportCredentialsToEnv?: (opts: EnvExportOpts) => EnvExportResult;
90
+ defaultExportPath?: (appId: string, platform: 'ios' | 'android') => string;
91
+ generateWorkflow?: (opts: WorkflowGeneratorOpts) => GeneratedWorkflow;
92
+ writeWorkflowFile?: (opts: WorkflowGeneratorOpts, writeOptions?: WorkflowWriteOptions) => WorkflowWriteResult;
93
+ requestBuildInternal?: (appId: string, options: BuildRequestOptions, silent?: boolean, logger?: BuildLogger) => Promise<BuildRequestResult>;
94
+ /**
95
+ * The streaming BuildLogger the TUI threads into requestBuildInternal (the 4th
96
+ * arg). On android it streams every line into `setBuildOutput`; the engine just
97
+ * forwards it. When absent, requestBuildInternal is called without a logger.
98
+ */
99
+ logger?: BuildLogger;
100
+ /**
101
+ * The build VIEWER sink — DISTINCT from `onLog` (the side-log). The bespoke
102
+ * android tail (app.tsx ~L1654-1740) writes the build header / blank+queued /
103
+ * ⚠ failure / no-key UX / catch lines via `setBuildOutput` (a dedicated build
104
+ * output pane), NOT via the side-log `addLog`. The shared engine forwards
105
+ * those build-viewer lines here so the driver can route them to the right
106
+ * sink. OPTIONAL — absent on iOS (and legacy callers), where the lines are
107
+ * simply dropped and routing is unaffected.
108
+ */
109
+ onBuildOutput?: (line: string) => void;
110
+ /**
111
+ * Resolves the Capgo API key the build request should use, mirroring the
112
+ * android tail's CLI-flag-over-saved precedence (`apikey ?? findSavedKeySilent()`).
113
+ * Returns undefined when no key is resolvable — in which case requesting-build
114
+ * skips the build attempt and finishes at build-complete (the android no-key UX).
115
+ * When this dep is ABSENT the engine falls back to the legacy empty-string apikey
116
+ * so existing callers/tests that never resolved a key keep working.
117
+ */
118
+ resolveApikey?: () => string | undefined;
119
+ /**
120
+ * Per-key upload progress, forwarded as the 5th arg of uploadCiSecretsAsync.
121
+ * The android tail feeds this into `setCiSecretUploadProgress`. No-op when absent.
122
+ */
123
+ onCiSecretUploadProgress?: (current: number, total: number, keyName: string) => void;
124
+ /**
125
+ * The 2-phase checking-ci-secrets status text ('Resolving GitHub repository…'
126
+ * then 'Checking existing env vars in <repo>…'). The android tail feeds this into
127
+ * `setCiSecretCheckPhase`. This is the ONLY sink for the check phases — they are
128
+ * intentionally NOT surfaced via `onStatus`, which the driver routes to the
129
+ * oauth/gcp panes (sending the check phases there would corrupt those). No-op
130
+ * when absent.
131
+ */
132
+ onCiSecretCheckPhase?: (phase: string) => void;
133
+ /**
134
+ * The ci-secrets-failed reason (repo-null / catch in checking-ci-secrets). The
135
+ * android tail feeds this into `setCiSecretError`, which the CiSecretsFailedStep
136
+ * renders. Also surfaced via `transient.ciSecretError`. OPTIONAL — no-op when
137
+ * absent (the failed-step view falls back to its generic message).
138
+ */
139
+ onCiSecretError?: (message: string) => void;
140
+ /** Reads the project's package.json scripts map. */
141
+ getPackageScripts?: () => Record<string, string>;
142
+ /** Detects the web-framework project type (best-effort; may resolve null). */
143
+ findProjectType?: (options?: {
144
+ quiet?: boolean;
145
+ }) => Promise<string | null>;
146
+ /** Maps a detected project type to its recommended build script name. */
147
+ findBuildCommandForProjectType?: (projectType: string) => Promise<string | null>;
148
+ /**
149
+ * Workflow-file telemetry hook (e.g. 'workflow-file-written'). The android tail
150
+ * calls `trackWorkflowEvent`. No-op when absent.
151
+ */
152
+ trackWorkflowEvent?: (event: string, options?: {
153
+ decision?: string;
154
+ }) => void;
155
+ /**
156
+ * DRIVER-HELD transient tail state, threaded back into each effect. The TUI
157
+ * resolves these ONCE (at `saving-credentials`) and keeps them in React state;
158
+ * a headless driver mirrors that by capturing `TailEffectResult.transient` and
159
+ * passing it back here on the NEXT effect. NEVER persisted to progress.json.
160
+ */
161
+ carried?: {
162
+ savedCredentials?: Record<string, string>;
163
+ ciSecretEntries?: CiSecretEntry[];
164
+ ciSecretExistingKeys?: string[];
165
+ /**
166
+ * Whether the workflow file did NOT exist when previewed (app.tsx's
167
+ * `previewIsNew`, resolved at `preview-workflow-file` via existsSync — driver
168
+ * state, never persisted). `writing-workflow-file` logs '✔ Wrote' vs
169
+ * '✔ Overwrote' from it. Absent/undefined defaults to NEW ('Wrote'), matching
170
+ * the bespoke React `useState(true)` default.
171
+ */
172
+ workflowIsNew?: boolean;
173
+ };
174
+ onStatus?: (message: string) => void;
175
+ onLog?: (message: string, color?: string) => void;
176
+ /** Internal-only diagnostic line → the support internal log (main PR #2406). Optional; no-op when absent. */
177
+ onInternalLog?: (line: string) => void;
178
+ signal?: AbortSignal;
179
+ }
180
+ export interface TailEffectResult<P extends TailEffectProgress = TailEffectProgress> {
181
+ /** Updated progress after the effect ran (matches what was persisted). */
182
+ progress: P;
183
+ /** Explicit next step (a platform step id — string so each platform widens it). */
184
+ next?: string;
185
+ /** Transient runtime data that lives in the driver but is NOT persisted. */
186
+ transient?: TailTransient;
187
+ }
188
+ /** The tail subset of a platform's transient ctx. Every field is optional. */
189
+ export interface TailTransient {
190
+ ciSecretEntries?: CiSecretEntry[];
191
+ savedCredentials?: Record<string, string>;
192
+ ciSecretTargets?: CiSecretTarget[];
193
+ ciSecretSetupAdvice?: CiSecretSetupAdvice[];
194
+ ciSecretRepoLabel?: string | null;
195
+ ciSecretExistingKeys?: string[];
196
+ ciSecretUploadSummary?: string;
197
+ envExportPath?: string;
198
+ workflowFilePath?: string;
199
+ buildUrl?: string;
200
+ buildOutput?: string[];
201
+ aiJobId?: string;
202
+ /** Workflow-builder script preload (resolved at uploading-ci-secrets, with-workflow). */
203
+ availableScripts?: Record<string, string>;
204
+ recommendedScript?: string | null;
205
+ /** Set when env-export found nothing to write or threw — routed to build-complete, never thrown. */
206
+ envExportError?: string;
207
+ /** Set when requesting-build THREW — routed to build-complete, never thrown (app.tsx ~L1733). */
208
+ error?: string;
209
+ /** Set on the ci-secrets-failed routes (repo-null / catch) so the failed-step view can render the reason. */
210
+ ciSecretError?: string;
211
+ }
212
+ export type TailInput = {
213
+ step: 'ci-secrets-setup';
214
+ value: 'retry' | 'skip';
215
+ } | {
216
+ step: 'ci-secrets-target-select';
217
+ ciSecretTarget: CiSecretTarget | null;
218
+ } | {
219
+ step: 'ask-ci-secrets';
220
+ value: 'yes' | 'no';
221
+ } | {
222
+ step: 'confirm-ci-secret-overwrite';
223
+ value: 'replace' | 'skip';
224
+ } | {
225
+ step: 'ci-secrets-failed';
226
+ value: 'retry' | 'continue';
227
+ } | {
228
+ step: 'ask-github-actions-setup';
229
+ value: 'with-workflow' | 'secrets-only' | 'no';
230
+ } | {
231
+ step: 'confirm-secrets-push';
232
+ value: 'confirm' | 'cancel';
233
+ } | {
234
+ step: 'ask-export-env';
235
+ value: 'no';
236
+ } | {
237
+ step: 'ask-export-env';
238
+ value: 'yes';
239
+ envExportTargetPath: string;
240
+ } | {
241
+ step: 'confirm-env-export-overwrite';
242
+ value: 'replace' | 'skip';
243
+ } | {
244
+ step: 'pick-package-manager';
245
+ selectedPackageManager: PackageManager;
246
+ } | {
247
+ step: 'pick-build-script';
248
+ value: '__custom__';
249
+ } | {
250
+ step: 'pick-build-script';
251
+ buildScriptChoice: BuildScriptChoice;
252
+ } | {
253
+ step: 'pick-build-script-custom';
254
+ command: string;
255
+ } | {
256
+ step: 'preview-workflow-file';
257
+ value: 'write' | 'view' | 'cancel';
258
+ } | {
259
+ step: 'view-workflow-diff';
260
+ value: 'close';
261
+ } | {
262
+ step: 'ask-build';
263
+ value: 'yes' | 'no';
264
+ };
265
+ /**
266
+ * Pure: a UI-framework-neutral description of a tail step. Mirrors the matching
267
+ * <Select>/prompt the TUI renders. Moved verbatim from `androidViewForStep`'s
268
+ * tail cases — the android engine adapts the returned view to `AndroidStepView`.
269
+ */
270
+ export declare function tailViewForStep(step: TailStep, progress: TailEffectProgress | null, ctx: TailStepCtx): TailStepView;
271
+ /**
272
+ * Pure state write for each tail choice/input step — returns a NEW progress
273
+ * object (spread — never mutate). Navigation-only / spinner-gate inputs return
274
+ * progress unchanged. Moved verbatim from `applyAndroidInput`'s tail reducers.
275
+ */
276
+ export declare function applyTailInput<P extends TailEffectProgress>(step: TailStep, progress: P, input: TailInput): P;
277
+ /**
278
+ * Dispatches to the right tail effect handler. Moved verbatim from
279
+ * `runAndroidEffect`'s tail cases. Platform-specific calls are parameterised via
280
+ * `deps.platform` / `deps.buildSavedCredentials` / `deps.rebuildTailCredentials`
281
+ * / `deps.resumeStep`; everything else is unchanged.
282
+ */
283
+ export declare function runTailEffect<P extends TailEffectProgress>(step: TailStep, progress: P, deps: TailEffectDeps<P>): Promise<TailEffectResult<P>>;
@@ -0,0 +1,29 @@
1
+ import type { CiSecretTarget } from './ci-secrets.js';
2
+ import type { BuildScriptChoice, PackageManager } from './workflow-generator.js';
3
+ export interface TailProgress {
4
+ /**
5
+ * The 3-way GitHub Actions setup choice made at `ask-github-actions-setup`.
6
+ * Mirrors `const [setupMode] = useState<…>('undecided')` in both app.tsx.
7
+ */
8
+ setupMode?: 'undecided' | 'with-workflow' | 'secrets-only' | 'declined';
9
+ /**
10
+ * The CI-secrets destination picked at `ci-secrets-target-select`.
11
+ * Mirrors `const [ciSecretTarget] = useState<CiSecretTarget | null>(null)`.
12
+ */
13
+ ciSecretTarget?: CiSecretTarget | null;
14
+ /**
15
+ * The package manager chosen at `pick-package-manager`.
16
+ * Mirrors `const [selectedPackageManager] = useState<PackageManager | null>(null)`.
17
+ */
18
+ selectedPackageManager?: PackageManager | null;
19
+ /**
20
+ * The build-script choice made at `pick-build-script`.
21
+ * Mirrors `const [buildScriptChoice] = useState<BuildScriptChoice | null>(null)`.
22
+ */
23
+ buildScriptChoice?: BuildScriptChoice | null;
24
+ /**
25
+ * The user-supplied `.env` export path entered at `ask-export-env`.
26
+ * Mirrors `const [envExportTargetPath] = useState<string>('')`.
27
+ */
28
+ envExportTargetPath?: string;
29
+ }