@capgo/cli
Version:
A CLI to upload to capgo servers
161 lines (160 loc) • 8.66 kB
TypeScript
import type { CiSecretSetupAdvice, CiSecretTarget } from '../ci-secrets.js';
import type { IosEffectDeps } from '../ios/flow.js';
import type { TailEffectDeps } from '../tail/flow.js';
import type { OnboardingStep } from '../types.js';
import type { Platform } from './contract.js';
/**
* The iOS driver-held transient state — the exact `IosEffectDeps['carried']`
* shape, plus the MCP-only `parkedImportStep` (S12): the interactive import
* sub-flow prompt the driver parked between tool calls — the headless mirror
* of the TUI's React `step` state for the EPHEMERAL import prompts, which
* resume routing can never reproduce (see engine.ts iosParkedStep). NON-SECRET
* (a step name). Wiped with the session; a restart self-heals via a fresh
* import-scanning that re-derives the inventory and re-renders the picker.
*/
export type IosCarried = NonNullable<IosEffectDeps['carried']> & {
parkedImportStep?: OnboardingStep;
/**
* The chosen identity's Apple cert resource id (import-checking-apple-cert's
* transient — typed on IosStepCtx, not on the engine's carried shape). The
* registry really holds it after the transient merge; typing it here lets
* the driver DROP it when the user re-picks a different identity (the TUI
* clears its appleCertId mirror the same way). NON-SECRET (an Apple id).
*/
_appleCertIdForChosen?: string;
};
/**
* The tail driver-held transient state — the exact `TailEffectDeps['carried']`
* shape, plus the NON-SECRET tail OUTCOME facts the outcome-aware terminal
* summary harvests (engine.ts harvestTailOutcomes → tailCompleteResult): the
* exact upload summary line (counts/labels only), the written workflow path
* and the exported .env path. These three are non-secret BY CONSTRUCTION and
* are the one carried subset allowed to surface verbatim in a tool result —
* secret VALUES (savedCredentials / ciSecretEntries) must still never leave
* this registry.
*/
export type TailCarried = NonNullable<TailEffectDeps['carried']> & {
ciSecretUploadSummary?: string;
workflowFilePath?: string;
envExportPath?: string;
};
/**
* S9-S11: the MCP's parked interactive TAIL step + the NON-SECRET view context
* it was rendered with (option inventories / labels — never credential values).
* The TUI holds the current tail step in React state; the MCP mirrors it here so
* (a) the strict tail gate validates an answer against the step that actually
* asked, and (b) a re-render (corrective message, plain re-check) re-asks the
* SAME parked question instead of drifting forward through the resume router —
* which would collapse past consent gates like preview-workflow-file. A server
* restart loses the park; resume routing then takes over (the frozen
* tailResumeStep contract). EXPLICITLY NO SECRETS: ciSecretEntries (values)
* stay in tailCarried; only derived key NAMES may surface in tool results.
*/
export interface TailParkedState {
step: string;
ciSecretTargets?: CiSecretTarget[];
ciSecretSetupAdvice?: CiSecretSetupAdvice[];
ciSecretRepoLabel?: string | null;
ciSecretError?: string;
availableScripts?: Record<string, string>;
recommendedScript?: string | null;
}
export interface OnboardingSessionState {
iosCarried: IosCarried;
tailCarried: TailCarried;
tailParked?: TailParkedState;
/**
* The platform this session is setting up, as CHOSEN by the user (platform
* picker answer / single-platform auto-route / explicit `{ platform }`), NOT
* inferred from on-disk progress. This is what lets a bare `next_step({})`
* resume the right platform AND lets two concurrent server processes onboard
* the same app for different platforms without reading each other's progress
* files. Process-local: a restart loses it and the flow re-asks the picker.
*/
activePlatform?: Platform;
/**
* The platform whose continue-vs-restart resume prompt has already been
* resolved THIS session — shown and answered, or skipped as unnecessary (no
* resumable on-disk progress). While it differs from the committed platform, a
* fresh platform entry first shows the resume prompt (mirroring the TUI's
* `resume-prompt` fork) instead of silently resuming. Process-local: a restart
* loses it and the prompt is re-offered. Reset by runStart so a fresh "start
* onboarding" always re-asks.
*/
resumeResolvedFor?: Platform;
}
/**
* Get the session state for `appId`, creating an empty one on demand.
* The returned object is the current SNAPSHOT: merges replace the carried
* objects rather than mutating them, so re-read after merging.
*/
export declare function getSession(appId: string): OnboardingSessionState;
/**
* Read the platform this session committed to (via setSessionPlatform), or
* undefined when none has been chosen yet (fresh session, or after a restart).
* Deliberately does NOT consult disk progress — the platform is a session
* decision, not a property of what happens to be saved on disk.
*/
export declare function getSessionPlatform(appId: string): Platform | undefined;
/**
* Record (or, with `undefined`, clear) the platform this session is setting up.
* Called when the user picks at the platform gate, when a single-platform
* project auto-routes, or on any explicit `{ platform }`. Cleared by runStart so
* a fresh "start onboarding" always re-asks instead of silently resuming.
*/
export declare function setSessionPlatform(appId: string, platform: Platform | undefined): void;
/**
* Read which platform's resume prompt has already been resolved this session
* (shown+answered, or determined unnecessary), or undefined when none has.
* Process-local; deliberately ignores disk — the resume decision is a session
* decision, exactly like the platform itself.
*/
export declare function getResumeResolvedFor(appId: string): Platform | undefined;
/**
* Record (or, with `undefined`, clear) which platform's continue-vs-restart
* resume prompt has been resolved this session. Set once the prompt is answered
* or skipped as unnecessary; cleared by runStart so a fresh start re-asks.
*/
export declare function setResumeResolvedFor(appId: string, platform: Platform | undefined): void;
/**
* Drop the per-flow carried transients (iOS carried, tail carried, tail park)
* for `appId` while PRESERVING the session-level decisions (activePlatform,
* resumeResolvedFor). Used by the resume prompt's "restart" arm: the on-disk
* progress is wiped, so the in-memory carried state from the old run must go
* too — but the session stays committed to the same platform and must not
* re-ask the resume prompt it just answered. Immutable: builds a NEW entry.
*/
export declare function clearSessionCarried(appId: string): void;
/**
* Merge `partial` into the iOS carried state for `appId` and return the new
* merged carried object. `undefined` values leave the prior value intact.
*/
export declare function mergeIosCarried(appId: string, partial: Partial<IosCarried>): IosCarried;
/**
* Merge `partial` into the tail carried state for `appId` and return the new
* merged carried object. `undefined` values leave the prior value intact.
*/
export declare function mergeTailCarried(appId: string, partial: Partial<TailCarried>): TailCarried;
/**
* Park the current interactive tail step (+ its non-secret view context) for
* `appId`. REPLACES any prior park — each render re-parks the step it shows,
* so the park always mirrors the question currently in front of the user.
* Immutable: builds a NEW session entry; prior snapshots are untouched.
*/
export declare function setTailParked(appId: string, parked: TailParkedState): void;
/**
* Drop `keys` from the iOS carried state for `appId` and return the new
* carried object. The complement of mergeIosCarried for one-shot consumable
* fields (e.g. the verify-app `verifyAction` pick, which the driver MUST clear
* after the resolver effect ran so a later re-entry runs the initial fetch —
* merge semantics skip `undefined`, so a merge can never clear). Immutable:
* builds a NEW carried object; prior snapshots are untouched.
*/
export declare function dropIosCarried(appId: string, keys: (keyof IosCarried)[]): IosCarried;
/**
* Drop the session entry for `appId`. Idempotent: safe on an absent appId and
* safe to call twice. The next getSession() recreates a fresh empty session.
*/
export declare function clearSession(appId: string): void;
/** Drop every session (test isolation only — production code clears per appId). */
export declare function clearAllSessions(): void;