/** * Cloud device-code pairing client. * * Talks to crewly-auth's `/api/cloud/device/*` endpoints so this machine can * sign in to the owner's Crewly account without anyone at it: * * 1. {@link startCloudDevicePairing} — get a secret `deviceCode`, a short * `userCode` (`ABCD-2345`) and the link the owner opens * (`crewlyai.com/cloud/pair?code=ABCD-2345`). * 2. {@link waitForCloudDeviceApproval} — poll at the Cloud's interval * (backing off on `slow_down` and rate limits) until the owner approves, * denies, or the pairing expires. On approval the Cloud hands over the * token pair exactly once. * * Shared by `crewly cloud login` (CLI) and {@link CloudDevicePairingService} * (backend, for `/setup` and Settings → Cloud & devices). No state; the device code * never leaves the caller's memory. * * @module services/cloud/cloud-device-pairing.client */ /** `fetch` signature (injectable for tests). */ export type FetchLike = (input: string, init?: RequestInit) => Promise; /** What `start` returns. */ export interface DevicePairingStartResult { /** Secret the machine polls with — never show or log it */ deviceCode: string; /** Short code the owner checks on the approve page */ userCode: string; /** Approve page with the code filled in (show / encode as QR) */ verificationUrl: string; /** Approve page without the code (for typing it by hand) */ verificationUri?: string; /** Pairing lifetime (seconds) */ expiresIn: number; /** Minimum seconds between polls */ interval: number; } /** The token pair handed over on approval. */ export interface DevicePairingCredentials { token: string; refreshToken: string; tier: string; email?: string; } /** One poll's answer. */ export type DevicePairingPollResult = { status: 'authorization_pending' | 'slow_down'; interval?: number; } | { status: 'expired' | 'denied'; } | { status: 'approved'; credentials: DevicePairingCredentials; }; /** How waiting ended. */ export type DevicePairingOutcome = { status: 'approved'; credentials: DevicePairingCredentials; } | { status: 'expired' | 'denied' | 'cancelled'; }; /** A request the Cloud refused (or could not answer). */ export declare class DevicePairingError extends Error { readonly status: number; readonly retryable: boolean; /** * @param message - Human-readable reason (never contains a secret) * @param status - HTTP status, 0 when the Cloud was unreachable * @param retryable - True for network errors, 5xx and 429 */ constructor(message: string, status: number, retryable: boolean); } /** * Ask the Cloud for a pairing. * * @param cloudUrl - Cloud API base URL (e.g. https://api.crewlyai.com) * @param input - How the device introduces itself on the approve page * @param fetchImpl - fetch (tests) * @returns Device code, user code, link, lifetime and interval * @throws DevicePairingError when the Cloud refuses or is unreachable */ export declare function startCloudDevicePairing(cloudUrl: string, input: { deviceName: string; deviceId?: string; purpose?: string; }, fetchImpl?: FetchLike): Promise; /** * Poll a pairing once. * * @param cloudUrl - Cloud API base URL * @param deviceCode - Secret from {@link startCloudDevicePairing} * @param fetchImpl - fetch (tests) * @returns The Cloud's answer * @throws DevicePairingError when the request fails (see `retryable`) */ export declare function pollCloudDevicePairing(cloudUrl: string, deviceCode: string, fetchImpl?: FetchLike): Promise; /** * Wait `ms`, resolving early when `signal` aborts. * * @param ms - Milliseconds * @param signal - Optional abort signal * @returns Resolves after the delay or on abort */ export declare function abortableSleep(ms: number, signal?: AbortSignal): Promise; /** Options for {@link waitForCloudDeviceApproval}. */ export interface WaitForApprovalOptions { cloudUrl: string; start: DevicePairingStartResult; fetchImpl?: FetchLike; /** Sleep (tests pass an instant one) */ sleep?: (ms: number, signal?: AbortSignal) => Promise; /** Clock (tests) */ now?: () => number; /** Stop waiting (returns `cancelled`) */ signal?: AbortSignal; /** Called after every poll with the status seen (progress display) */ onPoll?: (status: DevicePairingPollResult['status'], intervalS: number) => void; } /** * Poll until the owner approves or denies, or the pairing expires. * * Honours the Cloud's interval, adds {@link CLOUD_DEVICE_PAIRING_CONSTANTS.SLOW_DOWN_INCREMENT_S} * on `slow_down` / HTTP 429 (capped), and rides out up to * {@link CLOUD_DEVICE_PAIRING_CONSTANTS.MAX_CONSECUTIVE_POLL_ERRORS} network * or server errors in a row (a laptop changing Wi-Fi mid-login). * * @param options - Cloud URL, the started pairing and hooks * @returns How it ended; `approved` carries the token pair * @throws DevicePairingError on a non-retryable refusal or too many errors in a row */ export declare function waitForCloudDeviceApproval(options: WaitForApprovalOptions): Promise; //# sourceMappingURL=cloud-device-pairing.client.d.ts.map