@capgo/cli
Version:
A CLI to upload to capgo servers
147 lines (146 loc) • 7.39 kB
TypeScript
import type { AscCredentials, AscEventLine, AscLogLine } from './protocol';
import type { CodesignRunner } from '../macos-signing';
/**
* Where a resolved helper binary came from. `package` is the signed npm bundle —
* the ONLY source we spawn-time signature-verify. `override`/`cache`/`local` are
* dev/CI paths (unsigned or ad-hoc) that skip verification.
*/
export type AscHelperSource = 'override' | 'package' | 'cache' | 'local';
export interface ResolvedAscHelper {
/** Inner executable to spawn. */
binary: string;
/** Where it came from (drives whether we verify the signature at spawn). */
source: AscHelperSource;
/**
* The enclosing `.app` bundle path — present only for the signed `package`
* source, where it is the target of the designated-requirement check.
*/
bundlePath?: string;
}
/** Thrown when the helper is requested on a non-macOS host. */
export declare class NotMacOSError extends Error {
constructor();
}
export declare function isMacOS(): boolean;
/**
* Locate the precompiled Swift helper, in priority order:
* 1. `CAPGO_ASC_KEY_HELPER_PATH` — explicit override (dev / CI / tests). Only
* honored in dev builds (DCE'd from release bundles, like the keychain
* helper's CAPGO_KEYCHAIN_HELPER_PATH); skips signature verification.
* 2. The signed `CapgoAscKeyHelper.app` in the arch-matching
* `@capgo/cli-helper-darwin-*` package — verified at spawn time.
* 3. `~/.capgo/asc-key-helper/<binary>` — the legacy cached download location.
* 4. A local `swift build` of the vendored package (dev, running from src).
* Returns `null` (caller shows install / manual guidance) when none exists OR on
* macOS < 14 — the packaged SwiftUI/WKWebView app requires macOS 14, so the
* guided path must never be offered where it can't launch.
*
* Synchronous on purpose: it gates the guided path in several call sites that
* cannot await. Signature verification of the package source is deferred to
* {@link runAscKeyHelper} (just before spawn), which can await.
*/
export declare function resolveAscHelper(): ResolvedAscHelper | null;
/**
* Path-only resolver kept for the many synchronous gating call sites. Returns
* the inner executable to spawn, or `null`. See {@link resolveAscHelper} for the
* source metadata that drives spawn-time signature verification.
*/
export declare function resolveHelperBinary(): string | null;
/** Why the guided helper can't be offered. See {@link probeGuidedHelper}. */
export type GuidedHelperUnusableReason = 'not-macos' /** Not a Mac — the helper is a macOS app (Linux/Windows). */ | 'unsupported-os' /** macOS < 14 — the SwiftUI/WKWebView app can't launch. */ | 'not-installed' /** No helper resolved (optional package absent, no cache/build). */ | 'untrusted'; /** Helper present but its Developer-ID signature/team didn't verify. */
export type GuidedHelperProbe = {
usable: true;
source: AscHelperSource;
} | {
usable: false;
reason: GuidedHelperUnusableReason;
detail?: string;
};
export interface ProbeGuidedHelperOptions {
/**
* Inject a pre-resolved helper instead of calling {@link resolveAscHelper}
* (tests). `null` models "nothing resolved". Omit in production.
*/
resolved?: ResolvedAscHelper | null;
/** Forward an injected codesign runner to signature verification (tests). */
codesignRunner?: CodesignRunner;
}
/**
* Decide — once, up front — whether guided ASC-key creation can ACTUALLY run on
* this machine, so the onboarding flow never offers the guided path it would
* then have to reject. Unlike the sync {@link resolveHelperBinary} existence
* check, this also verifies the Developer-ID signature + Capgo team of a
* packaged bundle, so a helper that is installed but wrongly signed / wrong-team /
* tampered is reported `untrusted` (treated exactly like not-installed: the
* guided option is withheld and the user goes straight to manual instructions,
* the same as on Linux).
*
* Async because the signature check spawns `codesign`. Mirrors the spawn-time
* verification in {@link runAscKeyHelper} (same bundle id, same verifier), so a
* `usable` verdict here means the later spawn won't be refused for trust.
*/
export declare function probeGuidedHelper(options?: ProbeGuidedHelperOptions): Promise<GuidedHelperProbe>;
/**
* Dismiss the helper window. The outcome resolves as soon as the helper delivers
* its terminal `result` line — BEFORE the process exits — so the helper can keep
* its window open (showing a success screen) while the CLI advances. The caller
* invokes `close()` once the flow has moved on (e.g. the key verified) to close
* the window; until then it stays open. Safe to call more than once / after the
* window already closed.
*/
export type CloseHelper = () => void;
export interface AscHelperSuccess {
ok: true;
credentials: AscCredentials;
runId: string;
/** Number of stats events the helper emitted during the run. */
eventCount: number;
/** Number of diagnostic `log` lines routed to the internal support log. */
logCount: number;
/** Dismiss the still-open helper window. See {@link CloseHelper}. */
close: CloseHelper;
}
export interface AscHelperFailure {
ok: false;
errorCode: string;
message: string;
runId: string;
/** Number of diagnostic `log` lines routed to the internal support log. */
logCount: number;
/** Dismiss the helper window (no-op once it has exited). See {@link CloseHelper}. */
close: CloseHelper;
}
export type AscHelperOutcome = AscHelperSuccess | AscHelperFailure;
export interface RunAscKeyHelperOptions {
/** Pre-resolved helper binary path (tests inject a fake; prod auto-resolves). */
helperPathOverride?: string;
/** API key for analytics attribution; falls back to the saved key. */
apikey?: string;
/** Optional observer for every event line (UI progress / tests). */
onEvent?: (event: AscEventLine) => void;
/** Optional observer for every diagnostic log line (UI / tests). */
onLog?: (line: AscLogLine) => void;
/** Forward events to PostHog via trackEvent. Defaults to true. */
forwardToAnalytics?: boolean;
/**
* Append diagnostic `log` lines (and a per-run summary) to the CLI's internal
* support log via {@link appendInternalLog}. Defaults to true. The append is
* best-effort and no-ops when no internal log has been started for this run.
*/
forwardToInternalLog?: boolean;
/**
* Abort signal. When it fires — e.g. the onboarding TUI unmounts because the
* user quit — the helper child is terminated (SIGTERM, then SIGKILL) so its
* stdio pipes stop keeping the CLI process alive. Without this the CLI hangs
* after exit while the helper window is still open.
*/
signal?: AbortSignal;
}
/**
* Launch the precompiled helper, stream its NDJSON stats protocol, forward each
* `event` line to PostHog, and resolve with the credentials from the terminal
* `result` line. The private key is returned to the caller but NEVER forwarded
* to analytics. Never rejects on a helper-side failure — returns a structured
* failure outcome instead (it throws only for {@link NotMacOSError}).
*/
export declare function runAscKeyHelper(options?: RunAscKeyHelperOptions): Promise<AscHelperOutcome>;