/** * Cleo Nexus account engine: device-code login, logout and session status. * * The Nexus API is a better-auth app (cleo-nexus `apps/api/src/auth.ts`): * * - `POST /api/auth/device/code` and `POST /api/auth/device/token` run the * RFC 8628 grant for the single allowed client id `cleo-cli`, with RFC form * bodies (cleo-nexus #11). The access token it returns is a better-auth * session token. * - The bearer plugin accepts that token as `Authorization: Bearer ` on * every `/v1` route and on `POST /api/auth/sign-out`, which deletes the * session server-side. * - `GET /v1/account/me` names the user and their organizations; a dead * session answers 401 `E_UNAUTHENTICATED`. * * The device-code protocol itself is NOT implemented here: it is the shared * runner in `../llm/oauth/device-code.ts` (also used by the kimi-code LLM * login). This module adds the Nexus endpoints, token storage and account * lookup. The CLI (`cleo login nexus`, `cleo logout`, `cleo status`) and the * `nexus-account` setup section are thin callers. * * No function here logs, and no result carries the token. * * @task T12712 * @epic T12322 */ import type { NexusAccountErrorCode, NexusAccountMe, NexusAccountOrganization, NexusAccountStatus, NexusLoginResult, NexusLogoutResult } from '@cleocode/contracts'; import { type DeviceCodeConfig, type DeviceCodeStartResponse, pollForToken } from '../llm/oauth/device-code.js'; import { type FetchLike } from './http.js'; import { type NexusTokenStore, type SealedNexusSession } from './nexus-credentials.js'; import { NexusDeviceStore } from './nexus-device.js'; /** Environment override for the default API origin (e.g. staging). */ export declare const NEXUS_API_URL_ENV = "CLEO_NEXUS_API_URL"; /** Default time budget for the server-side revocation at logout. */ export declare const NEXUS_REVOKE_TIMEOUT_MS = 5000; /** Default time budget for a live status check. */ export declare const NEXUS_STATUS_TIMEOUT_MS = 1500; /** * What a team member does when only the project owner role can create a * project's key: cleo-nexus #35 honours `initialKey`, and a first key, only * for an organization owner or admin (T13101). */ export declare const NEXUS_PROJECT_KEY_OWNER_REMEDY = "an org owner or admin must create the project key from a signed-in session"; /** A Nexus account flow failure with a stable code. Never carries a token. */ export declare class NexusAccountError extends Error { readonly code: NexusAccountErrorCode; readonly fix?: string | undefined; /** * @param code - Stable error code. * @param message - Human message. * @param fix - Optional remedy for the user. */ constructor(code: NexusAccountErrorCode, message: string, fix?: string | undefined); } /** Dependencies shared by every flow; all optional (tests inject them). */ export interface NexusFlowOptions { /** API URL; defaults to `$CLEO_NEXUS_API_URL`, then {@link NEXUS_DEFAULT_API_URL}. */ apiUrl?: string; /** Token store; defaults to the 0600 file store. */ store?: NexusTokenStore; /** `fetch` override. */ fetch?: FetchLike; } /** Options for {@link logoutFromNexus}. */ export interface NexusLogoutOptions extends NexusFlowOptions { /** Budget for the revocation call; default {@link NEXUS_REVOKE_TIMEOUT_MS}. */ revokeTimeoutMs?: number; } /** Options for {@link loginToNexus}. */ export interface NexusLoginOptions extends NexusFlowOptions { /** Called once with the user code and verification URL to show the user. */ onCode?: (code: DeviceCodeStartResponse) => void; /** Called on every pending poll with elapsed and total seconds. */ onPending?: (elapsed: number, expiresIn: number) => void; /** Cancels polling. */ signal?: AbortSignal; /** Wait override for polling (tests). */ sleep?: (ms: number) => Promise; } /** Options for {@link getNexusAccountStatus}. */ export interface NexusStatusOptions { /** Report only this origin. Default: every stored origin, else the default origin. */ apiUrl?: string; /** Token store. */ store?: NexusTokenStore; /** `fetch` override. */ fetch?: FetchLike; /** Check each session against the API (default `true`). */ live?: boolean; /** Time budget per live check. */ timeoutMs?: number; /** * Report device credentials (`nexus-device.json`) too; default `true`, * whatever `CLEO_NEXUS_DEVICE` says. A device credential replaces the 9.24 * session row of the same user on its origin. */ devices?: boolean; /** Device store; defaults to `/nexus-device.json`. */ deviceStore?: NexusDeviceStore; } /** * Resolve and validate the API URL to its origin. * * @param raw - `--api-url`, or `undefined` for `$CLEO_NEXUS_API_URL` / the default. * @returns The origin, e.g. `https://api.cleocode.dev`. * @throws {NexusAccountError} `E_NEXUS_INVALID_API_URL` for anything but an * https URL (http only on loopback) without credentials in it. */ export declare function resolveNexusApiUrl(raw?: string): string; /** * The shared device-code runner's config for a Nexus origin. * * @param apiUrl - Resolved API origin. * @param fetchImpl - Optional `fetch` override. * @returns A {@link DeviceCodeConfig} for the better-auth device endpoints. */ export declare function nexusDeviceCodeConfig(apiUrl: string, fetchImpl?: FetchLike): DeviceCodeConfig; /** * Refuse a server-supplied verification URL unless it is safe to print and to * hand to the OS browser opener: * * - no control characters or whitespace (terminal injection: ESC, CR, LF, CSI); * - no leading `-` (option injection into `open` / `xdg-open`); * - `https:` (plain `http:` only when the API itself is a loopback http origin); * - no `user:pass@`; * - the host is the API's web origin or a subdomain of it: the API host minus a * leading `api.` label (`api.cleocode.dev` → `cleocode.dev`, * `api.staging.cleocode.dev` → `staging.cleocode.dev`). A phishing host, a * `file:` URL or a custom scheme never reaches the screen or the opener. * * @param uri - `verification_uri` or `verification_uri_complete` as received. * @param apiUrl - Resolved API origin. * @returns The normalised URL (`new URL(uri).href`): the exact form that was * validated, and the only one to print or open. * @throws {NexusAccountError} `E_NEXUS_UNTRUSTED_VERIFICATION_URI`. */ export declare function assertTrustedVerificationUri(uri: string, apiUrl: string): string; /** * The organization to show for an account: the personal one, else the first. * * @param me - Parsed `/v1/account/me`. * @returns The primary organization, or `null`. */ export declare function primaryNexusOrganization(me: NexusAccountMe): NexusAccountOrganization | null; /** * Sign in to a Cleo Nexus account with the device-code grant and store the * session token (0600, keyed by API origin). * * @param opts - API URL, store, UI hooks and test overrides. * @returns The secret-free login result. * @throws {NexusAccountError} On a start, poll, denial or expiry failure. */ export declare function loginToNexus(opts?: NexusLoginOptions): Promise; /** * The device-code grant against a Nexus origin (contract §3.3 steps 1 to 3): * start, validate and show the verification URL, then poll until the user * approves. The session token it returns is held in memory only; the caller * decides whether to store it (9.24 behaviour) or enrol a device with it. * * @param apiUrl - Resolved API origin. * @param opts - UI hooks and test overrides. * @param scope - Optional OAuth `scope` for `/device/code` (`cleo:device` or `cleo:read-only`). * @returns The token response (the session token is its only secret). * @throws {NexusAccountError} On a start, poll, denial or expiry failure. */ export declare function runNexusDeviceCode(apiUrl: string, opts: NexusLoginOptions, scope?: string): Promise>>; /** * Sign out: delete the session locally FIRST, then revoke it server-side * (`POST /api/auth/sign-out` with the bearer token) within * {@link NEXUS_REVOKE_TIMEOUT_MS}. A slow or hung server can delay logout by * at most that budget and can never keep the token on disk; a failed * revocation is reported. The delete only removes the session that was read, * so a login that finished concurrently for the same origin survives. * * @param opts - API URL, store, revoke budget and test overrides. * @returns The logout result. */ export declare function logoutFromNexus(opts?: NexusLogoutOptions): Promise; /** * `POST /api/auth/sign-out` with a bearer session token, within `timeoutMs`, * which deletes the session server-side. Never follows redirects. * * @param apiUrl - Resolved API origin. * @param bearer - The session token (never logged). * @param timeoutMs - Time budget for the call. * @param fetchImpl - Optional `fetch` override. * @returns `revoked`, or `already-invalid` on a 401. * @throws On a network error, a timeout or a non-2xx, non-401 answer. */ export declare function signOutNexusSessionToken(apiUrl: string, bearer: string, timeoutMs: number, fetchImpl?: FetchLike): Promise<'revoked' | 'already-invalid'>; /** * Secret-free status rows for `cleo status` and `cleo auth list`: one per * device credential (when device credentials are on) and one per stored 9.24 * session on an origin no device credential covers, or one "not signed in" * row for the default origin. Makes a network call only when a credential is * stored. A device that is signed out locally (no current credential) is not * signed in. * * @param opts - Origin filter, stores, liveness and test overrides. * @returns Status rows, sorted by origin. */ export declare function getNexusAccountStatus(opts?: NexusStatusOptions): Promise; /** * The stored session for an origin, or a `E_NEXUS_NOT_SIGNED_IN` error. * * @param apiUrl - Resolved API origin. * @param store - Token store. * @returns The sealed session. * @throws {NexusAccountError} When no session is stored. */ export declare function requireNexusSession(apiUrl: string, store: NexusTokenStore): Promise; //# sourceMappingURL=nexus-auth.d.ts.map