@fluid-app/fluid-cli 0.1.539 → 0.1.541

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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hook-BfNC1zgX.mjs","names":["commandPath","pkg.version","getErrorMessage","getAuthToken","getAuthToken"],"sources":["../src/catalog/annotations.ts","../src/catalog/walk.ts","../package.json","../src/auth/client-headers.ts","../src/utils/result.ts","../src/auth/fluid-api.ts","../src/config/types.ts","../../../platform/api-client-core/src/api-error-shape.ts","../../../platform/api-client-core/src/fetch-client.ts","../../../platform/api-client-core/src/hey-api-error-policy.ts","../../../api-clients/hey-api-fetch-runtime/src/generated/core/bodySerializer.gen.ts","../../../api-clients/hey-api-fetch-runtime/src/generated/core/params.gen.ts","../../../api-clients/hey-api-fetch-runtime/src/generated/core/serverSentEvents.gen.ts","../../../api-clients/hey-api-fetch-runtime/src/generated/core/pathSerializer.gen.ts","../../../api-clients/hey-api-fetch-runtime/src/generated/core/utils.gen.ts","../../../api-clients/hey-api-fetch-runtime/src/generated/core/auth.gen.ts","../../../api-clients/hey-api-fetch-runtime/src/generated/client/utils.gen.ts","../../../api-clients/hey-api-fetch-runtime/src/generated/client/client.gen.ts","../../../api-clients/identity/src/hey-api-client.ts","../../../api-clients/identity/src/hey-api/sdk.gen.ts","../../../platform/auth/src/constants.ts","../../../platform/auth/src/session/refresher.ts","../../../platform/auth/src/session/exchange.ts","../../../platform/auth/src/session/facts.ts","../../../platform/auth/src/session/types.ts","../../../platform/auth/src/native/exchange.ts","../../../platform/auth/src/session/pkce.ts","../../../platform/auth/src/native/secure-session.ts","../../../platform/auth/src/native/storage-adapters.ts","../src/auth/identity/browser.ts","../src/auth/identity/environment.ts","../src/auth/identity/keyring.ts","../src/auth/identity/loopback.ts","../src/auth/identity/session.ts","../src/config/paths.ts","../src/config/strict.ts","../src/config/config.ts","../src/config/project-config.ts","../src/auth/token.ts","../src/inputs/declare.ts","../src/inputs/scope.ts","../src/inputs/paths.ts","../src/inputs/tree.ts","../src/inputs/snapshot.ts","../src/inputs/reader.ts","../src/inputs/hook.ts"],"sourcesContent":["/**\n * What a command does to the API, for the command catalog.\n *\n * Commander has nowhere to keep this, so it rides on the command object\n * under a registered symbol. A plugin installed with its own copy of\n * @fluid-app/fluid-cli still writes to the same key the running CLI reads,\n * which a module-scoped WeakMap would not.\n */\n\nimport type { Command } from \"commander\";\n\nexport interface CommandEndpoint {\n readonly method: \"GET\" | \"POST\" | \"PUT\" | \"PATCH\" | \"DELETE\";\n /** Path as the API documents it, e.g. `/api/domains/{id}`. */\n readonly path: string;\n}\n\nexport interface CommandAnnotation {\n /** The endpoints the command calls. */\n readonly endpoints?: readonly CommandEndpoint[];\n /** Whether the command changes data. */\n readonly writes?: boolean;\n}\n\nconst ANNOTATION = Symbol.for(\"@fluid-app/fluid-cli.command-annotation\");\n\ntype Annotated = Command & { [ANNOTATION]?: CommandAnnotation };\n\n/**\n * Record the endpoints a command calls and whether it writes, so\n * `fluid commands --json` can list them. Returns the command for chaining.\n */\nexport function describeCommand<T extends Command>(\n command: T,\n annotation: CommandAnnotation,\n): T {\n const target = command as T & Annotated;\n target[ANNOTATION] = { ...target[ANNOTATION], ...annotation };\n return command;\n}\n\nexport function readAnnotation(command: Command): CommandAnnotation | null {\n return (command as Annotated)[ANNOTATION] ?? null;\n}\n","/**\n * Walk the loaded command tree into a plain catalog: every command, its\n * arguments and flags, and what it does to the API. `fluid commands` prints\n * it and shell completion reads the same tree, so neither can drift from\n * the commands a user actually has installed.\n */\n\nimport type { Argument, Command, Option } from \"commander\";\nimport { readAnnotation, type CommandEndpoint } from \"./annotations.js\";\n\nexport interface CatalogOption {\n readonly flags: string;\n readonly long: string | null;\n readonly short: string | null;\n readonly description: string;\n /** `<value>` must follow the flag. */\n readonly takesValue: boolean;\n /** `[value]` may follow the flag. */\n readonly optionalValue: boolean;\n /** The command refuses to run without it. */\n readonly required: boolean;\n readonly defaultValue: unknown;\n}\n\nexport interface CatalogArgument {\n readonly name: string;\n readonly description: string;\n readonly required: boolean;\n readonly variadic: boolean;\n}\n\nexport interface CatalogEntry {\n /** Full invocation, e.g. `fluid domains add`. */\n readonly command: string;\n readonly path: readonly string[];\n readonly description: string;\n readonly aliases: readonly string[];\n readonly arguments: readonly CatalogArgument[];\n readonly options: readonly CatalogOption[];\n /** Ancestors whose flags this command also accepts, e.g. `fluid admin`'s `--token`. */\n readonly inheritsOptionsFrom: readonly string[];\n readonly subcommands: readonly string[];\n /** False for a group that only holds subcommands. */\n readonly runnable: boolean;\n /** True or false when the plugin says so or the command takes `--yes`; null when unknown. */\n readonly writes: boolean | null;\n /** The endpoints the plugin declared; null when it declared none, which means unknown, not none. */\n readonly endpoints: readonly CommandEndpoint[] | null;\n}\n\n// Commander keeps these two private; they have been stable since v7.\ntype CommandInternals = { _hidden?: boolean; _actionHandler?: unknown };\n\nexport function isHidden(command: Command): boolean {\n return (command as unknown as CommandInternals)._hidden === true;\n}\n\nfunction isRunnable(command: Command): boolean {\n return (command as unknown as CommandInternals)._actionHandler != null;\n}\n\nexport function visibleSubcommands(command: Command): Command[] {\n return command.commands.filter((sub) => !isHidden(sub));\n}\n\nexport function visibleOptions(command: Command): Option[] {\n return command.options.filter((option) => !option.hidden);\n}\n\nfunction describeOption(option: Option): CatalogOption {\n return {\n flags: option.flags,\n long: option.long ?? null,\n short: option.short ?? null,\n description: option.description,\n takesValue: option.required,\n optionalValue: option.optional,\n required: option.mandatory,\n defaultValue: option.defaultValue,\n };\n}\n\nfunction describeArgument(argument: Argument): CatalogArgument {\n return {\n name: argument.name(),\n description: argument.description,\n required: argument.required,\n variadic: argument.variadic,\n };\n}\n\nfunction inferWrites(command: Command, declared: boolean | undefined) {\n if (declared !== undefined) return declared;\n // Commands that change live data confirm with --yes across the plugins.\n return command.options.some((option) => option.long === \"--yes\")\n ? true\n : null;\n}\n\nexport function walkCommands(root: Command): CatalogEntry[] {\n const entries: CatalogEntry[] = [];\n\n function visit(command: Command, path: string[], ancestors: Command[]) {\n const annotation = readAnnotation(command);\n const subcommands = visibleSubcommands(command);\n entries.push({\n command: path.join(\" \"),\n path,\n description: command.description(),\n aliases: command.aliases(),\n arguments: command.registeredArguments.map(describeArgument),\n options: visibleOptions(command).map(describeOption),\n inheritsOptionsFrom: ancestors\n .filter((ancestor) =>\n // The program's --version is not a flag a subcommand takes.\n visibleOptions(ancestor).some(\n (option) => option.long !== \"--version\",\n ),\n )\n .map((ancestor) => commandPath(ancestor).join(\" \")),\n subcommands: subcommands.map((sub) => sub.name()),\n runnable: isRunnable(command),\n writes: inferWrites(command, annotation?.writes),\n endpoints: annotation?.endpoints ?? null,\n });\n for (const sub of subcommands) {\n visit(sub, [...path, sub.name()], [...ancestors, command]);\n }\n }\n\n visit(root, [root.name()], []);\n return entries;\n}\n\nexport function rootCommand(command: Command): Command {\n let root = command;\n while (root.parent) root = root.parent;\n return root;\n}\n\nexport function commandPath(command: Command): string[] {\n const path: string[] = [];\n for (let c: Command | null = command; c; c = c.parent) {\n path.unshift(c.name());\n }\n return path;\n}\n\n/** Keep the entries at or under `prefix`, matching names or aliases. */\nexport function filterCatalog(\n entries: readonly CatalogEntry[],\n root: Command,\n prefix: readonly string[],\n): CatalogEntry[] {\n let target = root;\n for (const word of prefix) {\n const next = visibleSubcommands(target).find(\n (sub) => sub.name() === word || sub.aliases().includes(word),\n );\n if (!next) return [];\n target = next;\n }\n const base = commandPath(target);\n return entries.filter((entry) =>\n base.every((word, index) => entry.path[index] === word),\n );\n}\n","","import pkg from \"../../package.json\" with { type: \"json\" };\n\nexport const CLIENT_HEADERS: Readonly<Record<string, string>> = {\n \"X-Fluid-Client\": \"cli\",\n \"X-Fluid-Client-Version\": pkg.version,\n};\n","/**\n * Result type utilities for type-safe error handling\n *\n * The Result<T, E> pattern provides a discriminated union for fallible operations,\n * enabling exhaustive handling without try/catch blocks.\n */\n\n// Result type - discriminated union for success/failure\n\nexport interface Success<T> {\n readonly success: true;\n readonly value: T;\n}\n\nexport interface Failure<E> {\n readonly success: false;\n readonly error: E;\n}\n\nexport type Result<T, E = Error> = Success<T> | Failure<E>;\n\n// Constructor functions\n\nexport function success<T>(value: T): Success<T> {\n return { success: true, value };\n}\n\nexport function failure<E>(error: E): Failure<E> {\n return { success: false, error };\n}\n\n// Type guards\n\nexport function isSuccess<T, E>(result: Result<T, E>): result is Success<T> {\n return result.success === true;\n}\n\nexport function isFailure<T, E>(result: Result<T, E>): result is Failure<E> {\n return result.success === false;\n}\n\n// Utility functions\n\nexport function tryCatch<T>(fn: () => T): Result<T, Error> {\n try {\n return success(fn());\n } catch (error) {\n return failure(error instanceof Error ? error : new Error(String(error)));\n }\n}\n\nexport async function tryCatchAsync<T>(\n fn: () => Promise<T>,\n): Promise<Result<T, Error>> {\n try {\n return success(await fn());\n } catch (error) {\n return failure(error instanceof Error ? error : new Error(String(error)));\n }\n}\n\nexport function unwrap<T, E>(result: Result<T, E>): T {\n if (isSuccess(result)) return result.value;\n // Always throw an Error instance so V8 attaches a stack trace and\n // `instanceof Error` checks in catch blocks work as expected.\n if (result.error instanceof Error) throw result.error;\n throw new Error(\n typeof result.error === \"object\" && result.error !== null\n ? JSON.stringify(result.error)\n : String(result.error),\n );\n}\n\nexport function unwrapOr<T, E>(result: Result<T, E>, defaultValue: T): T {\n if (isSuccess(result)) return result.value;\n return defaultValue;\n}\n\nexport function mapResult<T, U, E>(\n result: Result<T, E>,\n fn: (value: T) => U,\n): Result<U, E> {\n if (isSuccess(result)) return success(fn(result.value));\n return result;\n}\n\nexport function mapError<T, E, F>(\n result: Result<T, E>,\n fn: (error: E) => F,\n): Result<T, F> {\n if (isFailure(result)) return failure(fn(result.error));\n return result;\n}\n\n// Error narrowing utilities\n\nexport function isError(value: unknown): value is Error {\n return value instanceof Error;\n}\n\nexport function isNodeError(value: unknown): value is NodeJS.ErrnoException {\n return value instanceof Error && \"code\" in value;\n}\n\nexport function getErrorMessage(error: unknown): string {\n if (error instanceof Error) return error.message;\n if (typeof error === \"string\") return error;\n return String(error);\n}\n","/**\n * Fluid API client for authentication operations, and the public signup\n * that comes before any of them\n */\n\nimport { CLIENT_HEADERS } from \"./client-headers.js\";\nimport type { CliError } from \"../utils/errors.js\";\nimport { type Result, success, failure } from \"../utils/result.js\";\n\nconst API_TIMEOUT_MS = 10_000;\n// A signup creates and sets up a company before it answers, so it gets\n// longer than a sign-in call.\nconst SIGNUP_TIMEOUT_MS = 60_000;\n\nexport function getFluidApiBase(): string {\n return process.env[\"FLUID_API_BASE\"] ?? \"https://api.fluid.app\";\n}\n\nfunction makeSignal(timeoutMs: number = API_TIMEOUT_MS): {\n signal: AbortSignal;\n cleanup: () => void;\n} {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n return { signal: controller.signal, cleanup: () => clearTimeout(timer) };\n}\n\nexport interface FluidApiError extends CliError {\n readonly code: string;\n readonly message: string;\n readonly details?: string;\n}\n\nexport const FLUID_API_ERROR = {\n INVALID_TOKEN: {\n code: \"INVALID_TOKEN\",\n message: \"Token is invalid or expired\",\n },\n API_UNREACHABLE: {\n code: \"API_UNREACHABLE\",\n message: \"Could not reach the Fluid API\",\n },\n // A 404 from confirm_mfa: the code confirmed but opens no company here; see\n // ConfirmMfaAction#confirm. Servers before ENG-2877 also answer an unknown\n // sign-in with it, so the message holds for both.\n SIGN_IN_UNFINISHED: {\n code: \"SIGN_IN_UNFINISHED\",\n message:\n \"This sign-in can't be finished. Check the shop and the address, then request a new code\",\n },\n // Servers before ENG-2877 answer 410 for an expired code, a used one, and\n // one locked after too many wrong attempts; current ones answer 422.\n CODE_EXPIRED: {\n code: \"CODE_EXPIRED\",\n message:\n \"Verification code has expired, was already used, or is locked after too many attempts — please request a new one\",\n },\n RATE_LIMITED: {\n code: \"RATE_LIMITED\",\n message: \"Too many sign-in requests — wait a minute and try again\",\n },\n // The server gives one answer to every code that does not confirm: an\n // unknown sign-in, a wrong code, an expired or used one, and one locked\n // after too many wrong attempts, so it never tells whether an address can\n // sign in (ENG-2877). It gives only the reasons; each login flow names its\n // own next step.\n INVALID_CODE: {\n code: \"INVALID_CODE\",\n message:\n \"Verification code is invalid, expired, already used, or locked after too many attempts\",\n },\n // A 422 naming the verification channel follows only a correct code: the\n // email or phone the code was sent to is no longer on the account, so it\n // is not a code problem and tells nothing about whether an address can\n // sign in. The code is not spent, so a caller that kept the challenge can\n // use it once the contact is back; each login flow names its own next step.\n CONTACT_MISSING: {\n code: \"CONTACT_MISSING\",\n message:\n \"The code is right, but the email or phone it was sent to is no longer on this account. Ask the company's admin to restore it\",\n },\n COMPANY_NOT_FOUND: {\n code: \"COMPANY_NOT_FOUND\",\n message: \"Company not found\",\n },\n SWITCH_FAILED: {\n code: \"SWITCH_FAILED\",\n message: \"Failed to switch company\",\n },\n LINK_GONE: {\n code: \"LINK_GONE\",\n message:\n \"This sign-in link has expired or was already used — run `fluid login` for a new one\",\n },\n} as const;\n\nfunction createApiError(\n template: { readonly code: string; readonly message: string },\n details?: string,\n): FluidApiError {\n return { code: template.code, message: template.message, details };\n}\n\nexport interface CompanyInfo {\n readonly name: string;\n}\n\nexport interface UserCompany {\n readonly id: number;\n readonly name: string;\n /** The company's `uuid_v7`, which an identity token exchange names. */\n readonly uuidV7?: string;\n}\n\nexport interface SwitchCompanyResult {\n readonly companyId: number;\n readonly companyName: string;\n readonly fluidShop: string;\n readonly jwt: string;\n}\n\nexport interface MfaResponse {\n readonly uuid: string;\n readonly expiresAt: string;\n}\n\nexport interface CompanyChoice {\n readonly id: number;\n readonly name: string;\n readonly shopName: string;\n readonly jwt: string;\n /** \"rep\" for an admin or rep session; \"member\" or \"customer\" for a shopper's. */\n readonly authType?: string;\n}\n\nexport interface ConfirmMfaResponse {\n readonly authType: string;\n readonly companies: CompanyChoice[];\n /**\n * `identity` when every company the code covered signs in through the\n * identity service, so Rails handed out no token for them.\n */\n readonly signInPath: \"identity\" | \"legacy\";\n}\n\n/**\n * Validate a token against the Fluid API and return company info.\n * `baseUrl` is the API the token belongs to; it defaults to the CLI's.\n */\nexport async function validateToken(\n token: string,\n options: { readonly baseUrl?: string } = {},\n): Promise<Result<CompanyInfo, FluidApiError>> {\n const { signal, cleanup } = makeSignal();\n try {\n const response = await fetch(\n `${options.baseUrl ?? getFluidApiBase()}/api/company/v1/companies/me`,\n {\n method: \"GET\",\n headers: {\n ...CLIENT_HEADERS,\n Authorization: `Bearer ${token}`,\n },\n signal,\n },\n );\n\n if (response.ok) {\n const data = (await response.json()) as {\n data?: { company?: { name?: string } };\n };\n const name = data?.data?.company?.name;\n if (!name) {\n return failure(\n createApiError(\n FLUID_API_ERROR.API_UNREACHABLE,\n \"Unexpected response shape from /companies/me\",\n ),\n );\n }\n return success({ name });\n }\n\n if (response.status === 401 || response.status === 403) {\n return failure(\n createApiError(\n FLUID_API_ERROR.INVALID_TOKEN,\n `HTTP ${response.status}: Check that your token is valid and non-expired.`,\n ),\n );\n }\n\n const body = await response.text();\n return failure(\n createApiError(\n FLUID_API_ERROR.API_UNREACHABLE,\n `HTTP ${response.status}: ${body}`,\n ),\n );\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return failure(createApiError(FLUID_API_ERROR.API_UNREACHABLE, message));\n } finally {\n cleanup();\n }\n}\n\n/**\n * Send a multi-factor authentication code to the given email address.\n * The API answers 201 whether or not the address can sign in\n * (anti-enumeration).\n *\n * With `fluidShop`, the server ties the code to the person's membership in\n * that company, so redeeming it signs in to that company alone and, for an\n * admin's emailed code, confirms a new signup.\n */\nexport async function sendMfa(\n email: string,\n options: { readonly fluidShop?: string } = {},\n): Promise<Result<MfaResponse, FluidApiError>> {\n const { signal, cleanup } = makeSignal();\n try {\n const response = await fetch(\n `${getFluidApiBase()}/api/authentication/send_mfa`,\n {\n method: \"POST\",\n headers: { ...CLIENT_HEADERS, \"Content-Type\": \"application/json\" },\n body: JSON.stringify(\n options.fluidShop\n ? { email, fluid_shop: options.fluidShop }\n : { email },\n ),\n signal,\n },\n );\n\n if (response.status === 201) {\n const data = (await response.json()) as {\n multi_factor_authentication?: {\n uuid?: string;\n verification_code_expires_at?: string;\n };\n };\n const uuid = data.multi_factor_authentication?.uuid;\n const expiresAt =\n data.multi_factor_authentication?.verification_code_expires_at;\n if (!uuid || !expiresAt) {\n return failure(\n createApiError(\n FLUID_API_ERROR.API_UNREACHABLE,\n \"Unexpected response shape from /send_mfa\",\n ),\n );\n }\n return success({ uuid, expiresAt });\n }\n\n const body = await response.text();\n if (response.status === 429) {\n return failure(\n createApiError(FLUID_API_ERROR.RATE_LIMITED, `HTTP 429: ${body}`),\n );\n }\n return failure(\n createApiError(\n FLUID_API_ERROR.API_UNREACHABLE,\n `HTTP ${response.status}: ${body}`,\n ),\n );\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return failure(createApiError(FLUID_API_ERROR.API_UNREACHABLE, message));\n } finally {\n cleanup();\n }\n}\n\n/**\n * Confirm a multi-factor authentication code and retrieve company JWTs.\n *\n * `baseUrl` is the API the code was sent from; a sign-in finished in a\n * later process passes the one it saved, not the current environment's.\n */\nexport async function confirmMfa(\n uuid: string,\n code: string,\n options: { readonly fluidShop?: string; readonly baseUrl?: string } = {},\n): Promise<Result<ConfirmMfaResponse, FluidApiError>> {\n const { signal, cleanup } = makeSignal();\n const apiBase = options.baseUrl ?? getFluidApiBase();\n try {\n const response = await fetch(`${apiBase}/api/authentication/confirm_mfa`, {\n method: \"POST\",\n headers: { ...CLIENT_HEADERS, \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n uuid,\n multi_factor_authentication: { verification_code: code },\n ...(options.fluidShop ? { fluid_shop: options.fluidShop } : {}),\n }),\n signal,\n });\n\n if (response.ok) {\n const data = (await response.json()) as {\n authenticated?: boolean;\n auth_type?: string;\n sign_in_path?: string;\n companies?:\n | {\n id: number;\n name: string;\n shop_name: string;\n jwt: string;\n auth_type?: string | null;\n }[]\n | null;\n };\n if (!data.authenticated) {\n return failure(\n createApiError(\n FLUID_API_ERROR.INVALID_CODE,\n \"Authentication was not confirmed by the server\",\n ),\n );\n }\n const companies = Array.isArray(data.companies) ? data.companies : [];\n return success({\n authType: data.auth_type ?? \"\",\n companies: companies.map((c) => ({\n id: c.id,\n name: c.name,\n shopName: c.shop_name,\n jwt: c.jwt,\n ...(c.auth_type ? { authType: c.auth_type } : {}),\n })),\n signInPath: data.sign_in_path === \"identity\" ? \"identity\" : \"legacy\",\n });\n }\n\n if (response.status === 404) {\n return failure(createApiError(FLUID_API_ERROR.SIGN_IN_UNFINISHED));\n }\n if (response.status === 410) {\n return failure(createApiError(FLUID_API_ERROR.CODE_EXPIRED));\n }\n const body = await response.text();\n if (response.status === 422) {\n return failure(\n createApiError(\n namesVerificationChannel(body)\n ? FLUID_API_ERROR.CONTACT_MISSING\n : FLUID_API_ERROR.INVALID_CODE,\n ),\n );\n }\n if (response.status === 429) {\n return failure(\n createApiError(FLUID_API_ERROR.RATE_LIMITED, `HTTP 429: ${body}`),\n );\n }\n return failure(\n createApiError(\n FLUID_API_ERROR.API_UNREACHABLE,\n `HTTP ${response.status}: ${body}`,\n ),\n );\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return failure(createApiError(FLUID_API_ERROR.API_UNREACHABLE, message));\n } finally {\n cleanup();\n }\n}\n\nexport interface IdentityLinkRedemption {\n /** Always `identity`: only a flagged company's sign-in emails a link. */\n readonly signInPath: \"identity\";\n readonly fluidShop: string;\n}\n\n/**\n * Redeem the single-use token in an identity sign-in email, the email a\n * person whose company signs in through the identity service receives\n * instead of a code. Rails answers 410 for a forged, expired, or used link.\n */\nexport async function redeemIdentityLink(\n token: string,\n options: { readonly baseUrl?: string } = {},\n): Promise<Result<IdentityLinkRedemption, FluidApiError>> {\n const { signal, cleanup } = makeSignal();\n try {\n const response = await fetch(\n `${options.baseUrl ?? getFluidApiBase()}/api/v2026-10/single-sign-ons/identity-link-redemptions`,\n {\n method: \"POST\",\n headers: { ...CLIENT_HEADERS, \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ token }),\n signal,\n },\n );\n\n if (response.ok) {\n const data = (await response.json()) as {\n sign_in_path?: string;\n fluid_shop?: string;\n };\n if (data.sign_in_path !== \"identity\" || !data.fluid_shop) {\n return failure(\n createApiError(\n FLUID_API_ERROR.API_UNREACHABLE,\n \"Unexpected response shape from /identity-link-redemptions\",\n ),\n );\n }\n return success({ signInPath: \"identity\", fluidShop: data.fluid_shop });\n }\n\n if (response.status === 410 || response.status === 404) {\n return failure(createApiError(FLUID_API_ERROR.LINK_GONE));\n }\n\n const body = await response.text();\n return failure(\n createApiError(\n FLUID_API_ERROR.API_UNREACHABLE,\n `HTTP ${response.status}: ${body}`,\n ),\n );\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return failure(createApiError(FLUID_API_ERROR.API_UNREACHABLE, message));\n } finally {\n cleanup();\n }\n}\n\n/**\n * Fetch all companies the authenticated user has access to.\n */\nexport async function fetchUserCompanies(\n token: string,\n baseUrl?: string,\n): Promise<Result<UserCompany[], FluidApiError>> {\n const { signal, cleanup } = makeSignal();\n const apiBase = baseUrl ?? getFluidApiBase();\n try {\n const response = await fetch(`${apiBase}/api/me`, {\n method: \"GET\",\n headers: { ...CLIENT_HEADERS, Authorization: `Bearer ${token}` },\n signal,\n });\n\n if (response.ok) {\n const data = (await response.json()) as {\n companies?: { id: number; name: string; uuid_v7?: string }[];\n };\n const companies = Array.isArray(data.companies) ? data.companies : [];\n return success(\n companies.map((c) => ({\n id: c.id,\n name: c.name,\n ...(typeof c.uuid_v7 === \"string\" ? { uuidV7: c.uuid_v7 } : {}),\n })),\n );\n }\n\n if (response.status === 401 || response.status === 403) {\n return failure(\n createApiError(\n FLUID_API_ERROR.INVALID_TOKEN,\n `HTTP ${response.status}`,\n ),\n );\n }\n\n const body = await response.text();\n return failure(\n createApiError(\n FLUID_API_ERROR.API_UNREACHABLE,\n `HTTP ${response.status}: ${body}`,\n ),\n );\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return failure(createApiError(FLUID_API_ERROR.API_UNREACHABLE, message));\n } finally {\n cleanup();\n }\n}\n\n/**\n * Switch to a different company and receive a new JWT.\n *\n * Response shape (from @fluid-app/auth SwitchCompanyResponse):\n * { company: { id, name, fluid_shop, jwt }, meta: { request_id, timestamp } }\n */\nexport async function switchCompany(\n token: string,\n companyId: number,\n baseUrl?: string,\n): Promise<Result<SwitchCompanyResult, FluidApiError>> {\n const { signal, cleanup } = makeSignal();\n const apiBase = baseUrl ?? getFluidApiBase();\n try {\n const response = await fetch(\n `${apiBase}/api/authentication/company/${companyId}/switch`,\n {\n method: \"PUT\",\n headers: {\n ...CLIENT_HEADERS,\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n },\n signal,\n },\n );\n\n if (response.ok) {\n const data = (await response.json()) as {\n company?: {\n id: number;\n name: string;\n fluid_shop: string;\n jwt: string;\n };\n };\n const company = data.company;\n if (!company?.jwt) {\n return failure(\n createApiError(\n FLUID_API_ERROR.SWITCH_FAILED,\n \"Unexpected response shape from /switch\",\n ),\n );\n }\n return success({\n companyId: company.id,\n companyName: company.name,\n fluidShop: company.fluid_shop,\n jwt: company.jwt,\n });\n }\n\n if (response.status === 404) {\n return failure(\n createApiError(\n FLUID_API_ERROR.COMPANY_NOT_FOUND,\n `Company ${companyId} not found`,\n ),\n );\n }\n\n if (response.status === 401 || response.status === 403) {\n return failure(\n createApiError(\n FLUID_API_ERROR.INVALID_TOKEN,\n `HTTP ${response.status}`,\n ),\n );\n }\n\n const body = await response.text();\n return failure(\n createApiError(\n FLUID_API_ERROR.SWITCH_FAILED,\n `HTTP ${response.status}: ${body}`,\n ),\n );\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return failure(createApiError(FLUID_API_ERROR.API_UNREACHABLE, message));\n } finally {\n cleanup();\n }\n}\n\nexport interface SignupRequest {\n readonly firstName: string;\n readonly lastName: string;\n readonly email: string;\n readonly companyName: string;\n /** Already in the server's form (see `normaliseSubdomain`). */\n readonly subdomain: string;\n readonly color?: string;\n}\n\nexport interface SignupResponse {\n readonly company: {\n readonly id: number;\n readonly name: string;\n readonly subdomain: string;\n readonly fluidShop: string;\n };\n readonly userCompany: { readonly id: number; readonly email: string };\n /** What the person still has to do, such as \"email_confirmation\". New values may appear. */\n readonly currentlyDue: readonly string[];\n}\n\n/** A signup that was refused or went unanswered. */\nexport interface SignupError extends CliError {\n /** The HTTP status, when the server answered. */\n readonly status?: number;\n /**\n * A 422 body's `errors`: keyed by record and then field (`company.subdomain`),\n * or by `base` alone for a request refused before any record was built.\n */\n readonly errors?: Record<string, unknown>;\n /** Seconds until the rate-limit window resets, on a 429. */\n readonly retryAfter?: number;\n}\n\nexport const SIGNUP_ERROR = {\n SUBDOMAIN_TAKEN: {\n code: \"SUBDOMAIN_TAKEN\",\n message:\n \"That subdomain belongs to another company. Pick another, unless an earlier signup of yours sent it: then the company exists, so sign in instead.\",\n },\n // The server answers `company.base` both for a setup failure that rolled\n // the company back and for an error raised after it was saved, and the\n // body does not say which, so this assumes the company may exist.\n SIGNUP_MAY_EXIST: {\n code: \"SIGNUP_MAY_EXIST\",\n message:\n \"The signup failed, and the company may still have been created. Run the same command once more with the same subdomain: if it then says the subdomain is taken, the company exists, so sign in instead.\",\n },\n SIGNUP_INVALID: {\n code: \"SIGNUP_INVALID\",\n message: \"Fluid rejected the signup. See errors for each field.\",\n },\n RATE_LIMITED: {\n code: \"RATE_LIMITED\",\n message:\n \"Too many signups from this address or for this email. Failed attempts count too, so wait out the retry time before trying again.\",\n },\n SIGNUP_FORBIDDEN: {\n code: \"SIGNUP_FORBIDDEN\",\n message: \"Fluid refused this signup. Don't retry; contact Fluid support.\",\n },\n SIGNUP_OUTCOME_UNKNOWN: {\n code: \"SIGNUP_OUTCOME_UNKNOWN\",\n message:\n \"No usable answer from Fluid, so the company may or may not have been created. Check before retrying: sign in, or run the same command once more and read a 'subdomain is taken' as created.\",\n },\n} as const;\n\nfunction signupError(\n template: { readonly code: string; readonly message: string },\n extra: Omit<SignupError, \"code\" | \"message\"> = {},\n): SignupError {\n return { code: template.code, message: template.message, ...extra };\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> | undefined {\n return value !== null && typeof value === \"object\" && !Array.isArray(value)\n ? (value as Record<string, unknown>)\n : undefined;\n}\n\n/** Whether a confirm 422 body names the verification channel. */\nfunction namesVerificationChannel(body: string): boolean {\n try {\n const errors = asRecord(asRecord(JSON.parse(body))?.errors);\n return (\n errors !== undefined &&\n fieldMessages(\n errors,\n \"multi_factor_authentication\",\n \"verification_channel\",\n ).length > 0\n );\n } catch {\n return false;\n }\n}\n\nfunction fieldMessages(\n errors: Record<string, unknown>,\n record: string,\n field: string,\n): string[] {\n const messages = asRecord(errors[record])?.[field];\n return Array.isArray(messages)\n ? messages.filter((m): m is string => typeof m === \"string\")\n : [];\n}\n\nfunction signupRejection(body: unknown): SignupError {\n const errors = asRecord(asRecord(body)?.[\"errors\"]) ?? {};\n // \"is taken\" from the pre-check, \"has already been taken\" from the\n // model's uniqueness validation.\n if (\n fieldMessages(errors, \"company\", \"subdomain\").some((m) =>\n m.includes(\"taken\"),\n )\n ) {\n return signupError(SIGNUP_ERROR.SUBDOMAIN_TAKEN, { status: 422, errors });\n }\n if (fieldMessages(errors, \"company\", \"base\").length > 0) {\n return signupError(SIGNUP_ERROR.SIGNUP_MAY_EXIST, { status: 422, errors });\n }\n return signupError(SIGNUP_ERROR.SIGNUP_INVALID, { status: 422, errors });\n}\n\nfunction readSignup(body: unknown): SignupResponse | undefined {\n const root = asRecord(body);\n const company = asRecord(root?.[\"company\"]);\n const userCompany = asRecord(root?.[\"user_company\"]);\n const due = asRecord(root?.[\"requirements\"])?.[\"currently_due\"];\n if (\n typeof company?.[\"id\"] !== \"number\" ||\n typeof company[\"fluid_shop\"] !== \"string\" ||\n typeof userCompany?.[\"id\"] !== \"number\"\n ) {\n return undefined;\n }\n return {\n company: {\n id: company[\"id\"],\n name: String(company[\"name\"] ?? \"\"),\n subdomain: String(company[\"subdomain\"] ?? \"\"),\n fluidShop: company[\"fluid_shop\"],\n },\n userCompany: {\n id: userCompany[\"id\"],\n email: String(userCompany[\"email\"] ?? \"\"),\n },\n currentlyDue: Array.isArray(due)\n ? due.filter((d): d is string => typeof d === \"string\")\n : [],\n };\n}\n\nasync function readJson(response: Response): Promise<unknown> {\n try {\n return await response.json();\n } catch {\n return undefined;\n }\n}\n\n/**\n * Sign up a new company with the public, unauthenticated `POST /api/company`.\n *\n * Sends exactly one request and never retries it: a 422 can follow a saved\n * company, and every attempt, failed or not, counts toward the per-IP and\n * per-email signup limits.\n */\nexport async function signup(\n request: SignupRequest,\n): Promise<Result<SignupResponse, SignupError>> {\n const { signal, cleanup } = makeSignal(SIGNUP_TIMEOUT_MS);\n try {\n const response = await fetch(`${getFluidApiBase()}/api/company`, {\n method: \"POST\",\n headers: { ...CLIENT_HEADERS, \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n user: {\n first_name: request.firstName,\n last_name: request.lastName,\n email: request.email,\n },\n company: {\n name: request.companyName,\n subdomain: request.subdomain,\n ...(request.color ? { color: request.color } : {}),\n },\n }),\n signal,\n });\n const body = await readJson(response);\n\n if (response.status === 200) {\n const created = readSignup(body);\n return created\n ? success(created)\n : failure(\n signupError(SIGNUP_ERROR.SIGNUP_OUTCOME_UNKNOWN, {\n status: 200,\n details: \"Unexpected response shape from POST /api/company\",\n }),\n );\n }\n if (response.status === 422) return failure(signupRejection(body));\n if (response.status === 429) {\n const retryAfter = asRecord(body)?.[\"retry_after\"];\n return failure(\n signupError(SIGNUP_ERROR.RATE_LIMITED, {\n status: 429,\n ...(typeof retryAfter === \"number\" ? { retryAfter } : {}),\n }),\n );\n }\n if (response.status === 403) {\n return failure(\n signupError(SIGNUP_ERROR.SIGNUP_FORBIDDEN, { status: 403 }),\n );\n }\n return failure(\n signupError(SIGNUP_ERROR.SIGNUP_OUTCOME_UNKNOWN, {\n status: response.status,\n details: `HTTP ${response.status}`,\n }),\n );\n } catch (err) {\n // A timeout or a dropped connection can come after the server saved the\n // company, so this is never reported as \"not created\".\n const message = err instanceof Error ? err.message : String(err);\n return failure(\n signupError(SIGNUP_ERROR.SIGNUP_OUTCOME_UNKNOWN, { details: message }),\n );\n } finally {\n cleanup();\n }\n}\n","/**\n * Configuration types for the Fluid CLI\n */\n\n/**\n * How an identity-mode profile finds its session. Nothing here is a secret:\n * the refresh token lives in the operating system's credential store under\n * `storageKey`, and the access token only in the CLI process's memory.\n */\nexport interface IdentityProfileAuth {\n readonly mode: \"identity\";\n /** Identity service origin the session was signed in with. */\n readonly identityUrl: string;\n /** Credential store account holding this session's refresh token. */\n readonly storageKey: string;\n /**\n * Company `uuid_v7` this profile moved to with `fluid switch`, by token\n * exchange. Absent while the profile uses the company it signed in to.\n */\n readonly org?: string;\n}\n\nexport interface FluidProfile {\n readonly name: string;\n /**\n * The legacy Rails token. Absent on an identity-mode profile, whose tokens\n * never touch this file (see `auth`).\n */\n readonly token?: string;\n readonly companyName: string;\n readonly storedAt: string; // ISO-8601\n readonly baseUrl?: string;\n /** Stable storefront identity written by Mist and newer auth endpoints. */\n readonly fluidShop?: string;\n /**\n * Cached commit identity for GitSync subjects, so a publish doesn't\n * pay for `/api/me` to learn a name that changes approximately never.\n * Scoped to the profile (token + company), and dropped whenever login\n * or a company switch rewrites it. See git-sync/actor-cache.ts.\n */\n readonly gitSyncActor?: {\n readonly name: string;\n readonly publicId: string | null;\n /** ISO-8601. */\n readonly cachedAt: string;\n };\n /** Present when the profile signs in through the Fluid identity service. */\n readonly auth?: IdentityProfileAuth;\n}\n\nexport type IdentityProfile = FluidProfile & {\n readonly auth: IdentityProfileAuth;\n};\n\n/** Whether a profile signs in through the identity service. */\nexport function isIdentityProfile(\n profile: FluidProfile | null | undefined,\n): profile is IdentityProfile {\n return profile?.auth?.mode === \"identity\";\n}\n\nexport interface FluidConfig {\n activeProfile: string | null;\n profiles: Record<string, FluidProfile>;\n plugins: Record<string, unknown>;\n /**\n * Allow-list of plugin package names.\n *\n * - `null` (default) — auto-discover and load all `@fluid-app/fluid-cli-*`\n * and `*-cli-commands` packages found in `node_modules` or the pnpm\n * workspace. Only `@fluid-app`-scoped packages matching the naming\n * convention are eligible; no third-party code is loaded.\n * - `string[]` — only load plugins whose names appear in the array.\n * Set to `[]` to disable all plugins.\n */\n enabledPlugins: string[] | null;\n /** Historical local workspace slugs, keyed by stable storefront slug.\n * Optional because older CLI versions and hand-written test configs do not\n * know about Mist's local workspace compatibility metadata. */\n workspaceAliasesByFluidShop?: Record<string, string[]>;\n}\n\nexport function createDefaultConfig(): FluidConfig {\n return {\n activeProfile: null,\n profiles: {},\n plugins: {},\n enabledPlugins: null, // auto-discover all plugins\n };\n}\n","/**\n * The shape contract for `ApiError.body` and `ApiError.data`.\n *\n * One decision, in one place: `body` is the response envelope exactly as the\n * server sent it, `data` is the unwrapped field-error bag. Rationale and the\n * alternatives considered live in `docs/api-error-data-contract.md`.\n */\n\n/**\n * The parsed JSON error response body, exactly as the server sent it.\n *\n * This is the envelope: `errors`, `error_message`, `error`, `status`, `meta`\n * and `request_id` all live here. `null` when the response carried no JSON\n * object — HTML, an empty body, a JSON primitive, or unparseable text.\n */\nexport type ApiErrorBody = Record<string, unknown> | null;\n\n/**\n * The field-level error bag: the envelope's `errors` value when the body has\n * one, else the field errors in its `error.details`, with its `error.message`\n * as `error.message`, when the body has that, otherwise the body itself.\n *\n * `errors` is declared `never` because unwrapping already consumed it — read\n * the envelope from `ApiError.body`, not from here. That makes the historical\n * mistake (`error.data.errors.some_field`, which silently resolved to\n * `undefined`) a typecheck failure instead of a degraded error message.\n */\nexport type ApiErrorFieldErrors = Record<string, unknown> & {\n errors?: never;\n};\n\n/**\n * Every value `ApiError.data` can hold. Fluid endpoints return an object bag,\n * but `errors` is occasionally an array or a string, and non-JSON responses\n * carry no data at all.\n */\nexport type ApiErrorData =\n | ApiErrorFieldErrors\n | readonly unknown[]\n | string\n | number\n | boolean\n | null;\n\n/**\n * Narrows a parsed JSON error payload to the envelope contract. Arrays and\n * primitives are not envelopes, so they resolve to `null` — their content is\n * still reachable through `ApiError.data`.\n */\nexport function toApiErrorBody(value: unknown): ApiErrorBody {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) {\n return null;\n }\n\n return value as Record<string, unknown>;\n}\n\n/**\n * The single unwrap rule, shared by every producer of `ApiError`.\n *\n * Rails sends two error envelopes. `ControllerAction#error_response` puts the\n * bag under `errors`; `Api::Response.error` puts it under `error.details`,\n * beside `error.message`. Both unwrap to the bag, `errors` first.\n *\n * `!= null` rather than a truthiness check so an explicitly empty `errors`\n * (`\"\"`, `0`, `false`) is preserved instead of silently falling back to the\n * whole envelope.\n * `details` is taken whatever its value, `null` included, so data from an\n * `Api::Response` error is never the envelope.\n */\nexport function toApiErrorData(body: ApiErrorBody): ApiErrorData {\n if (!body) {\n return null;\n }\n\n if (body.errors != null) {\n return body.errors as ApiErrorData;\n }\n\n const error = toApiErrorBody(body.error);\n if (error && error.details !== undefined) {\n return withErrorMessage(toFieldErrorDetails(error.details), error.message);\n }\n\n return body as ApiErrorData;\n}\n\n/**\n * `Api::Response` errors carry diagnostics such as `{ type: \"not_provisioned\" }`\n * or `{ violations: [{ type, description }] }` in `details` as readily as\n * field errors, and sometimes both. A field error is a list of message\n * strings, at any depth, so only those entries make up the bag; an `errors`\n * wrapper inside `details` is unwrapped as the envelope's own is. Details with\n * no field error unwrap to an empty bag. The diagnostics stay on\n * `ApiError.body`.\n */\nfunction toFieldErrorDetails(details: unknown): ApiErrorData {\n if (details === null) {\n return null;\n }\n\n const record = toApiErrorBody(details);\n const bag =\n record && record.errors != null ? toApiErrorBody(record.errors) : record;\n\n return ((bag && pickFieldErrors(bag)) ?? {}) as ApiErrorData;\n}\n\n/**\n * Telling a field error from a diagnostic list such as\n * `{ missing_keys: [\"coupons.create\"] }` by its content cannot be exact, so\n * the bag also carries `error.message` as `{ error: { message } }`, after the\n * field errors. `parseApiErrors` and the inlined `fluidToast` copies render\n * that nested form first and without a field prefix, so the server's message\n * is never hidden by whatever reaches the bag.\n */\nfunction withErrorMessage(\n fieldErrors: ApiErrorData,\n message: unknown,\n): ApiErrorData {\n if (typeof message !== \"string\" || message.trim().length === 0) {\n return fieldErrors;\n }\n\n const bag = toApiErrorBody(fieldErrors) ?? {};\n if (\"error\" in bag) {\n return fieldErrors;\n }\n\n return { ...bag, error: { message } };\n}\n\nfunction pickFieldErrors(\n record: Record<string, unknown>,\n): Record<string, unknown> | undefined {\n const picked: Record<string, unknown> = {};\n\n for (const [key, value] of Object.entries(record)) {\n const nested = toApiErrorBody(value);\n const fieldErrors = isMessageList(value)\n ? value\n : nested && pickFieldErrors(nested);\n\n if (fieldErrors) {\n picked[key] = fieldErrors;\n }\n }\n\n return Object.keys(picked).length > 0 ? picked : undefined;\n}\n\nfunction isMessageList(value: unknown): value is string[] {\n return (\n Array.isArray(value) &&\n value.length > 0 &&\n value.every((message) => typeof message === \"string\")\n );\n}\n","/**\n * Minimal, framework-agnostic fetch client for Fluid APIs\n * Compatible with fluid-admin patterns but usable standalone\n */\n\nimport type { ApiErrorBody, ApiErrorData } from \"./api-error-shape\";\nimport { toApiErrorBody, toApiErrorData } from \"./api-error-shape\";\n\nexport interface FetchClientConfig {\n /**\n * Base URL for all requests (e.g., \"https://api.fluid.app/api\")\n */\n baseUrl: string;\n\n /**\n * Optional function to get auth token\n * Return null/undefined if no token available\n */\n getAuthToken?: () => string | null | Promise<string | null>;\n\n /**\n * Optional callback when 401 auth error occurs\n */\n onAuthError?: () => void;\n\n /**\n * Default headers to include in all requests\n * Example: { \"x-fluid-client\": \"admin\" }\n */\n defaultHeaders?: Record<string, string>;\n\n /**\n * Credentials mode for fetch requests.\n * Set to `\"include\"` for cookie-based (same-origin BFF) authentication.\n * @default undefined (browser default: \"same-origin\")\n */\n credentials?: RequestCredentials;\n\n /**\n * Request cache mode for fetch requests.\n * @default undefined (browser default)\n */\n cache?: RequestCache;\n\n /**\n * Retry configuration for thrown network errors from fetch.\n * Does not retry HTTP error responses or aborted requests.\n * @default undefined (no retries)\n */\n networkRetry?: {\n maxRetries?: number;\n baseDelayMs?: number;\n };\n\n /**\n * Throw ApiError when a successful response declares JSON but cannot be parsed.\n * Defaults to false to preserve the legacy generated-client behavior.\n * @default false\n */\n throwOnInvalidJson?: boolean;\n}\n\nexport interface RequestOptions {\n method?: \"GET\" | \"POST\" | \"PUT\" | \"DELETE\" | \"PATCH\";\n headers?: Record<string, string>;\n params?: Record<string, unknown>;\n body?: unknown;\n signal?: AbortSignal;\n priority?: RequestInit[\"priority\"];\n}\n\n/**\n * API Error class compatible with fluid-admin's ApiError\n */\nexport class ApiError extends Error {\n public readonly status: number;\n\n /**\n * The unwrapped field-error bag. See `ApiErrorData`; the contract is\n * documented in `docs/api-error-data-contract.md`.\n */\n public readonly data: ApiErrorData;\n\n /**\n * The full error response envelope. Read `error_message`, `error`, `status`\n * and nested `errors` from here — `data` has already unwrapped one level.\n */\n public readonly body: ApiErrorBody;\n\n public readonly requestId?: string;\n\n constructor(\n message: string,\n status: number,\n data?: ApiErrorData,\n requestId?: string,\n body?: ApiErrorBody,\n ) {\n super(message);\n this.name = \"ApiError\";\n this.status = status;\n this.data = data ?? null;\n this.body = body ?? null;\n this.requestId = requestId;\n\n if (\"captureStackTrace\" in Error) {\n (\n Error as {\n captureStackTrace: (\n target: Error,\n constructor: NewableFunction,\n ) => void;\n }\n ).captureStackTrace(this, ApiError);\n }\n }\n\n toJSON(): {\n name: string;\n message: string;\n status: number;\n data: ApiErrorData;\n body: ApiErrorBody;\n requestId?: string;\n } {\n return {\n name: this.name,\n message: this.message,\n status: this.status,\n data: this.data,\n body: this.body,\n requestId: this.requestId,\n };\n }\n}\n\nfunction getStringRequestId(value: unknown): string | undefined {\n if (typeof value !== \"string\") {\n return undefined;\n }\n\n const trimmed = value.trim();\n return trimmed.length > 0 ? trimmed : undefined;\n}\n\nfunction getRequestIdFromHeaders(headers: Headers): string | undefined {\n return (\n getStringRequestId(headers.get(\"x-request-id\")) ??\n getStringRequestId(headers.get(\"request-id\")) ??\n getStringRequestId(headers.get(\"X-Request-ID\"))\n );\n}\n\nfunction getRequestIdFromJsonBody(body: unknown): string | undefined {\n if (!body || typeof body !== \"object\" || Array.isArray(body)) {\n return undefined;\n }\n\n const record = body as Record<string, unknown>;\n const meta = record.meta;\n\n return (\n getStringRequestId(record.request_id) ??\n getStringRequestId(record.requestId) ??\n (meta && typeof meta === \"object\" && !Array.isArray(meta)\n ? (getStringRequestId((meta as Record<string, unknown>).request_id) ??\n getStringRequestId((meta as Record<string, unknown>).requestId))\n : undefined)\n );\n}\n\n/**\n * Type guard for ApiError\n */\nexport function isApiError(error: unknown): error is ApiError {\n return error instanceof ApiError;\n}\n\nexport interface FetchClientInstance {\n request: <TResponse = unknown>(\n endpoint: string,\n options?: RequestOptions,\n ) => Promise<TResponse>;\n requestWithFormData: <TResponse = unknown>(\n endpoint: string,\n formData: FormData,\n options?: Omit<RequestOptions, \"body\" | \"params\"> & {\n method?: \"POST\" | \"PUT\" | \"PATCH\";\n },\n ) => Promise<TResponse>;\n get: <TResponse = unknown>(\n endpoint: string,\n params?: Record<string, unknown>,\n options?: Omit<RequestOptions, \"method\" | \"params\">,\n ) => Promise<TResponse>;\n post: <TResponse = unknown>(\n endpoint: string,\n body?: unknown,\n options?: Omit<RequestOptions, \"method\" | \"body\">,\n ) => Promise<TResponse>;\n put: <TResponse = unknown>(\n endpoint: string,\n body?: unknown,\n options?: Omit<RequestOptions, \"method\" | \"body\">,\n ) => Promise<TResponse>;\n patch: <TResponse = unknown>(\n endpoint: string,\n body?: unknown,\n options?: Omit<RequestOptions, \"method\" | \"body\">,\n ) => Promise<TResponse>;\n delete: <TResponse = unknown>(\n endpoint: string,\n options?: Omit<RequestOptions, \"method\">,\n ) => Promise<TResponse>;\n}\n\n/**\n * Creates a configured fetch client instance\n */\nexport function createFetchClient(\n config: FetchClientConfig,\n): FetchClientInstance {\n const {\n baseUrl,\n getAuthToken,\n onAuthError,\n defaultHeaders = {},\n credentials,\n cache,\n networkRetry,\n throwOnInvalidJson = false,\n } = config;\n const maxNetworkRetries = Math.max(0, networkRetry?.maxRetries ?? 0);\n const baseNetworkRetryDelayMs = Math.max(0, networkRetry?.baseDelayMs ?? 0);\n\n /**\n * Build headers for a request\n */\n async function buildHeaders(\n customHeaders?: Record<string, string>,\n ): Promise<Record<string, string>> {\n const headers: Record<string, string> = {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\",\n ...defaultHeaders,\n ...customHeaders,\n };\n\n // Add auth token if available\n if (getAuthToken) {\n const token = await getAuthToken();\n if (token) {\n headers.Authorization = `Bearer ${token}`;\n }\n }\n\n return headers;\n }\n\n /**\n * Join baseUrl + endpoint via string concatenation (matches fetchApi).\n * Using `new URL(endpoint, baseUrl)` would strip any path prefix from\n * baseUrl (e.g. \"/api\") when the endpoint starts with \"/\".\n */\n function joinUrl(endpoint: string): string {\n return `${baseUrl}${endpoint}`;\n }\n\n /**\n * Build URL with query parameters for GET requests\n * Compatible with fluid-admin's query param handling\n */\n function buildUrl(\n endpoint: string,\n params?: Record<string, unknown>,\n ): string {\n const fullUrl = joinUrl(endpoint);\n\n if (!params || Object.keys(params).length === 0) {\n return fullUrl;\n }\n\n const queryString = new URLSearchParams();\n\n Object.entries(params).forEach(([key, value]) => {\n if (value === undefined || value === null) {\n return; // Skip undefined/null values\n }\n\n if (Array.isArray(value)) {\n // Handle arrays like Rails expects: key[]. The OpenAPI-declared\n // param name is sometimes already bracketed (e.g. \"product_ids[]\",\n // \"filters[integration_class][]\") to match Rails' own convention;\n // appending another \"[]\" in that case would double-bracket the wire\n // format (key[][]=1), which Rails does not parse as the array.\n const arrayKey = key.endsWith(\"[]\") ? key : `${key}[]`;\n value.forEach((item) => queryString.append(arrayKey, String(item)));\n } else if (typeof value === \"object\") {\n // Handle nested objects: key[subkey]\n Object.entries(value).forEach(([subKey, subValue]) => {\n if (subValue === undefined || subValue === null) {\n return;\n }\n\n if (Array.isArray(subValue)) {\n subValue.forEach((item) =>\n queryString.append(`${key}[${subKey}][]`, String(item)),\n );\n } else {\n queryString.append(`${key}[${subKey}]`, String(subValue));\n }\n });\n } else {\n queryString.append(key, String(value));\n }\n });\n\n const qs = queryString.toString();\n return qs ? `${fullUrl}?${qs}` : fullUrl;\n }\n\n /**\n * Shared response handler for both JSON and FormData requests.\n * Handles auth errors, non-OK responses, 204 No Content, and JSON parsing.\n */\n async function handleResponse<TResponse>(\n response: Response,\n method: string,\n _url: string,\n ): Promise<TResponse> {\n const headerRequestId = getRequestIdFromHeaders(response.headers);\n\n if (response.status === 401 && onAuthError) {\n onAuthError();\n }\n\n if (!response.ok) {\n // Read body as text first to avoid SyntaxError from response.json()\n // when server returns non-JSON bodies with application/json content-type.\n const errorText = await response.text().catch(() => \"\");\n const contentType = response.headers.get(\"content-type\");\n\n if (contentType?.includes(\"application/json\")) {\n // Parsed as `unknown` and narrowed before any property read: a body of\n // `null` (or a bare string/number/array) is valid JSON, and reading\n // `.error` off it directly threw a TypeError instead of an ApiError.\n let parsed: unknown;\n try {\n parsed = JSON.parse(errorText);\n } catch {\n throw new ApiError(\n errorText.slice(0, 200) ||\n `${method} request failed with status ${response.status}`,\n response.status,\n null,\n headerRequestId,\n );\n }\n\n const body = toApiErrorBody(parsed);\n // Only an object envelope can carry a message. Some Rails BFF endpoints\n // return `{ error: { message, details } }` instead of `{ message }` or\n // `{ error_message }`, so that shape is tried last.\n const msg = body\n ? (() => {\n const nestedError =\n typeof body.error === \"object\" && body.error !== null\n ? (body.error as { message?: unknown }).message\n : undefined;\n const directError =\n typeof body.error === \"string\" ? body.error : undefined;\n const message =\n typeof body.message === \"string\" ? body.message : undefined;\n const errorMessage =\n typeof body.error_message === \"string\"\n ? body.error_message\n : undefined;\n return (\n message ||\n errorMessage ||\n directError ||\n (typeof nestedError === \"string\" ? nestedError : undefined)\n );\n })()\n : undefined;\n\n throw new ApiError(\n msg || `${method} request failed with status ${response.status}`,\n response.status,\n body ? toApiErrorData(body) : (parsed as ApiErrorData),\n headerRequestId ?? getRequestIdFromJsonBody(parsed),\n body,\n );\n } else {\n throw new ApiError(\n `${method} request failed with status ${response.status}`,\n response.status,\n null,\n headerRequestId,\n );\n }\n }\n\n if (\n response.status === 204 ||\n response.headers.get(\"content-length\") === \"0\"\n ) {\n return null as TResponse;\n }\n\n const contentType = response.headers.get(\"content-type\");\n\n if (contentType?.includes(\"application/json\")) {\n const responseText = await response.text();\n\n try {\n const data = JSON.parse(responseText);\n return data as TResponse;\n } catch {\n if (throwOnInvalidJson) {\n throw new ApiError(\n \"Failed to parse response as JSON\",\n response.status,\n null,\n headerRequestId,\n );\n }\n\n // API declared JSON content-type but body isn't valid JSON.\n // Return the raw payload to preserve the legacy non-strict path.\n return responseText ? (responseText as TResponse) : (null as TResponse);\n }\n }\n\n // Non-JSON response (text/plain, text/html, etc.)\n return null as TResponse;\n }\n\n function getNetworkRetryDelayMs(retryAttempt: number): number {\n return baseNetworkRetryDelayMs * 2 ** (retryAttempt - 1);\n }\n\n async function waitForNetworkRetry(retryAttempt: number): Promise<void> {\n const delayMs = getNetworkRetryDelayMs(retryAttempt);\n if (delayMs <= 0) {\n return;\n }\n\n await new Promise((resolve) => setTimeout(resolve, delayMs));\n }\n\n async function fetchWithNetworkRetry(\n url: string,\n fetchOptions: RequestInit,\n signal?: AbortSignal,\n ): Promise<Response> {\n let retryCount = 0;\n\n while (true) {\n try {\n return await fetch(url, fetchOptions);\n } catch (networkError) {\n if (signal?.aborted || retryCount >= maxNetworkRetries) {\n throw networkError;\n }\n\n retryCount += 1;\n await waitForNetworkRetry(retryCount);\n\n if (signal?.aborted) {\n throw networkError;\n }\n }\n }\n }\n\n /**\n * Main request function\n */\n async function request<TResponse = unknown>(\n endpoint: string,\n options: RequestOptions = {},\n ): Promise<TResponse> {\n const {\n method = \"GET\",\n headers: customHeaders,\n params,\n body,\n signal,\n priority,\n } = options;\n\n const url = params ? buildUrl(endpoint, params) : joinUrl(endpoint);\n\n const headers = await buildHeaders(customHeaders);\n\n let response: Response;\n\n try {\n const fetchOptions: RequestInit = { method, headers };\n if (credentials) fetchOptions.credentials = credentials;\n if (cache) fetchOptions.cache = cache;\n if (priority) fetchOptions.priority = priority;\n const serializedBody =\n body && method !== \"GET\" ? JSON.stringify(body) : null;\n if (serializedBody) fetchOptions.body = serializedBody;\n if (signal) fetchOptions.signal = signal;\n response = await fetchWithNetworkRetry(url, fetchOptions, signal);\n } catch (networkError) {\n throw new ApiError(\n `Network error: ${networkError instanceof Error ? networkError.message : \"Unknown network error\"}`,\n 0,\n null,\n );\n }\n\n return handleResponse<TResponse>(response, method, url);\n }\n\n /**\n * Request with FormData (for file uploads)\n */\n async function requestWithFormData<TResponse = unknown>(\n endpoint: string,\n formData: FormData,\n options: Omit<RequestOptions, \"body\" | \"params\"> & {\n method?: \"POST\" | \"PUT\" | \"PATCH\";\n } = {},\n ): Promise<TResponse> {\n const {\n method = \"POST\",\n headers: customHeaders,\n signal,\n priority,\n } = options;\n\n const url = joinUrl(endpoint);\n const headers = await buildHeaders(customHeaders);\n\n // Remove Content-Type to let browser set it with boundary\n delete headers[\"Content-Type\"];\n\n let response: Response;\n\n try {\n const fetchOptions: RequestInit = { method, headers, body: formData };\n if (credentials) fetchOptions.credentials = credentials;\n if (cache) fetchOptions.cache = cache;\n if (priority) fetchOptions.priority = priority;\n if (signal) fetchOptions.signal = signal;\n response = await fetchWithNetworkRetry(url, fetchOptions, signal);\n } catch (networkError) {\n throw new ApiError(\n `Network error: ${networkError instanceof Error ? networkError.message : \"Unknown network error\"}`,\n 0,\n null,\n );\n }\n\n return handleResponse<TResponse>(response, method, url);\n }\n\n // Return client with convenience methods\n return {\n request: request,\n requestWithFormData: requestWithFormData,\n\n // Convenience methods for common HTTP verbs\n get: <TResponse = unknown>(\n endpoint: string,\n params?: Record<string, unknown>,\n options?: Omit<RequestOptions, \"method\" | \"params\">,\n ): Promise<TResponse> =>\n request<TResponse>(endpoint, {\n ...options,\n method: \"GET\" as const,\n ...(params && { params }),\n }),\n\n post: <TResponse = unknown>(\n endpoint: string,\n body?: unknown,\n options?: Omit<RequestOptions, \"method\" | \"body\">,\n ): Promise<TResponse> =>\n request<TResponse>(endpoint, {\n ...options,\n method: \"POST\",\n body,\n }),\n\n put: <TResponse = unknown>(\n endpoint: string,\n body?: unknown,\n options?: Omit<RequestOptions, \"method\" | \"body\">,\n ): Promise<TResponse> =>\n request<TResponse>(endpoint, {\n ...options,\n method: \"PUT\",\n body,\n }),\n\n patch: <TResponse = unknown>(\n endpoint: string,\n body?: unknown,\n options?: Omit<RequestOptions, \"method\" | \"body\">,\n ): Promise<TResponse> =>\n request<TResponse>(endpoint, {\n ...options,\n method: \"PATCH\",\n body,\n }),\n\n delete: <TResponse = unknown>(\n endpoint: string,\n options?: Omit<RequestOptions, \"method\">,\n ): Promise<TResponse> =>\n request<TResponse>(endpoint, {\n ...options,\n method: \"DELETE\",\n }),\n };\n}\n\nexport type FetchClient = FetchClientInstance;\n","import type { ApiErrorBody, ApiErrorData } from \"./api-error-shape\";\nimport { toApiErrorBody, toApiErrorData } from \"./api-error-shape\";\nimport { ApiError } from \"./fetch-client\";\n\nexport interface HeyApiErrorRequestOptions {\n method?: string;\n}\n\nexport interface HeyApiErrorInterceptorOptions {\n onResponseError?: (response: Response) => void;\n shouldPassThroughTransportError?: (error: unknown) => boolean;\n}\n\nexport type HeyApiErrorInterceptor = (\n error: unknown,\n response: Response | undefined,\n request: Request | undefined,\n options: HeyApiErrorRequestOptions,\n) => unknown;\n\nfunction getNonEmptyString(value: unknown): string | undefined {\n if (typeof value !== \"string\") {\n return undefined;\n }\n\n const trimmed = value.trim();\n return trimmed.length > 0 ? trimmed : undefined;\n}\n\nfunction getRequestId(\n response: Response,\n error: ApiErrorBody,\n): string | undefined {\n const meta = toApiErrorBody(error?.meta);\n\n return (\n getNonEmptyString(response.headers.get(\"x-request-id\")) ??\n getNonEmptyString(response.headers.get(\"request-id\")) ??\n getNonEmptyString(error?.request_id) ??\n getNonEmptyString(error?.requestId) ??\n getNonEmptyString(meta?.request_id) ??\n getNonEmptyString(meta?.requestId)\n );\n}\n\nfunction getErrorMessage(\n error: Record<string, unknown>,\n fallback: string,\n): string {\n const nestedError = toApiErrorBody(error.error);\n\n return (\n getNonEmptyString(error.message) ??\n getNonEmptyString(error.error_message) ??\n getNonEmptyString(nestedError?.message) ??\n fallback\n );\n}\n\n/**\n * Creates an error interceptor compatible with generated Hey API fetch clients.\n * Consumer-specific auth and cancellation policy is supplied through callbacks,\n * keeping this package independent from generated client types.\n */\nexport function createHeyApiErrorInterceptor(\n options: HeyApiErrorInterceptorOptions = {},\n): HeyApiErrorInterceptor {\n return (error, response, request, requestOptions) => {\n if (!response) {\n if (error instanceof ApiError) {\n return error;\n }\n\n if (options.shouldPassThroughTransportError?.(error)) {\n return error;\n }\n\n // Request construction and auth callbacks run before dispatch. Preserve\n // those errors instead of reporting them as network failures.\n if (!request) {\n return error;\n }\n\n return new ApiError(\n `Network error: ${error instanceof Error ? error.message : \"Unknown network error\"}`,\n 0,\n null,\n );\n }\n\n // Generated clients surface malformed JSON bodies as SyntaxError. Map an\n // otherwise successful response to the shared strict parse contract.\n if (response.ok && error instanceof SyntaxError) {\n return new ApiError(\n \"Failed to parse response as JSON\",\n response.status,\n null,\n getRequestId(response, null),\n );\n }\n\n options.onResponseError?.(response);\n\n const method = request?.method ?? requestOptions.method ?? \"GET\";\n const errorRecord = toApiErrorBody(error);\n const requestId = getRequestId(response, errorRecord);\n\n if (errorRecord) {\n // `data` is the unwrapped field-error bag, `body` the whole envelope.\n // Both derive from one shared rule — see docs/api-error-data-contract.md.\n return new ApiError(\n getErrorMessage(errorRecord, `${method} request failed`),\n response.status,\n toApiErrorData(errorRecord),\n requestId,\n errorRecord,\n );\n }\n\n const isJson = response.headers\n .get(\"content-type\")\n ?.includes(\"application/json\");\n const text = getNonEmptyString(error);\n\n return new ApiError(\n isJson && text\n ? text.slice(0, 200)\n : `${method} request failed with status ${response.status}`,\n response.status,\n // A parsed JSON string is a legal `ApiErrorData` member, so it is kept\n // rather than dropped — the shared fetch client already preserves it, and\n // discarding it here made the two producers disagree.\n isJson ? (error as ApiErrorData) : null,\n requestId,\n // Unreachable as an envelope: this branch runs only when the payload is\n // not a plain object, which is exactly what `toApiErrorBody` rejects.\n null,\n );\n };\n}\n","// This file is auto-generated by @hey-api/openapi-ts\n\nimport type {\n ArrayStyle,\n ObjectStyle,\n SerializerOptions,\n} from \"./pathSerializer.gen\";\n\nexport type QuerySerializer = (query: Record<string, unknown>) => string;\n\nexport type BodySerializer = (body: unknown) => unknown;\n\ntype QuerySerializerOptionsObject = {\n allowReserved?: boolean;\n array?: Partial<SerializerOptions<ArrayStyle>>;\n object?: Partial<SerializerOptions<ObjectStyle>>;\n};\n\nexport type QuerySerializerOptions = QuerySerializerOptionsObject & {\n /**\n * Per-parameter serialization overrides. When provided, these settings\n * override the global array/object settings for specific parameter names.\n */\n parameters?: Record<string, QuerySerializerOptionsObject>;\n};\n\nconst serializeFormDataPair = (\n data: FormData,\n key: string,\n value: unknown,\n): void => {\n if (typeof value === \"string\" || value instanceof Blob) {\n data.append(key, value);\n } else if (value instanceof Date) {\n data.append(key, value.toISOString());\n } else {\n data.append(key, JSON.stringify(value));\n }\n};\n\nconst serializeUrlSearchParamsPair = (\n data: URLSearchParams,\n key: string,\n value: unknown,\n): void => {\n if (typeof value === \"string\") {\n data.append(key, value);\n } else {\n data.append(key, JSON.stringify(value));\n }\n};\n\nexport const formDataBodySerializer = {\n bodySerializer: (body: unknown): FormData => {\n const data = new FormData();\n\n Object.entries(body as Record<string, unknown>).forEach(([key, value]) => {\n if (value === undefined || value === null) {\n return;\n }\n if (Array.isArray(value)) {\n value.forEach((v) => serializeFormDataPair(data, key, v));\n } else {\n serializeFormDataPair(data, key, value);\n }\n });\n\n return data;\n },\n};\n\nexport const jsonBodySerializer = {\n bodySerializer: (body: unknown): string =>\n JSON.stringify(body, (_key, value) =>\n typeof value === \"bigint\" ? value.toString() : value,\n ),\n};\n\nexport const urlSearchParamsBodySerializer = {\n bodySerializer: (body: unknown): string => {\n const data = new URLSearchParams();\n\n Object.entries(body as Record<string, unknown>).forEach(([key, value]) => {\n if (value === undefined || value === null) {\n return;\n }\n if (Array.isArray(value)) {\n value.forEach((v) => serializeUrlSearchParamsPair(data, key, v));\n } else {\n serializeUrlSearchParamsPair(data, key, value);\n }\n });\n\n return data.toString();\n },\n};\n","// This file is auto-generated by @hey-api/openapi-ts\n\ntype Slot = \"body\" | \"headers\" | \"path\" | \"query\";\n\nexport type Field =\n | {\n in: Exclude<Slot, \"body\">;\n /**\n * Field name. This is the name we want the user to see and use.\n */\n key: string;\n /**\n * Field mapped name. This is the name we want to use in the request.\n * If omitted, we use the same value as `key`.\n */\n map?: string;\n }\n | {\n in: Extract<Slot, \"body\">;\n /**\n * Key isn't required for bodies.\n */\n key?: string;\n map?: string;\n }\n | {\n /**\n * Field name. This is the name we want the user to see and use.\n */\n key: string;\n /**\n * Field mapped name. This is the name we want to use in the request.\n * If `in` is omitted, `map` aliases `key` to the transport layer.\n */\n map: Slot;\n };\n\nexport interface Fields {\n allowExtra?: Partial<Record<Slot, boolean>>;\n args?: ReadonlyArray<Field>;\n}\n\nexport type FieldsConfig = ReadonlyArray<Field | Fields>;\n\nconst extraPrefixesMap: Record<string, Slot> = {\n $body_: \"body\",\n $headers_: \"headers\",\n $path_: \"path\",\n $query_: \"query\",\n};\nconst extraPrefixes = Object.entries(extraPrefixesMap);\n\ntype KeyMap = Map<\n string,\n | {\n in: Slot;\n map?: string;\n }\n | {\n in?: never;\n map: Slot;\n }\n>;\n\nfunction buildKeyMap(fields: FieldsConfig, map?: KeyMap): KeyMap {\n if (!map) {\n map = new Map();\n }\n\n for (const config of fields) {\n if (\"in\" in config) {\n if (config.key) {\n map.set(config.key, {\n in: config.in,\n map: config.map,\n });\n }\n } else if (\"key\" in config) {\n map.set(config.key, {\n map: config.map,\n });\n } else if (config.args) {\n buildKeyMap(config.args, map);\n }\n }\n\n return map;\n}\n\ninterface Params {\n body?: unknown;\n headers: Record<string, unknown>;\n path: Record<string, unknown>;\n query: Record<string, unknown>;\n}\n\nfunction stripEmptySlots(params: Params): void {\n for (const [slot, value] of Object.entries(params)) {\n if (slot === \"body\") continue;\n if (\n value &&\n typeof value === \"object\" &&\n !Array.isArray(value) &&\n !Object.keys(value).length\n ) {\n delete params[slot as Slot];\n }\n }\n}\n\nexport function buildClientParams(\n args: ReadonlyArray<unknown>,\n fields: FieldsConfig,\n): Params {\n const params: Params = {\n headers: Object.create(null),\n path: Object.create(null),\n query: Object.create(null),\n };\n\n const map = buildKeyMap(fields);\n\n function writeSlot(slot: Slot, key: string, value: unknown): void {\n let record = params[slot] as Record<string, unknown> | undefined;\n if (record === undefined) {\n record = Object.create(null) as Record<string, unknown>;\n params[slot] = record;\n }\n record[key] = value;\n }\n\n let config: FieldsConfig[number] | undefined;\n\n for (const [index, arg] of args.entries()) {\n if (fields[index]) {\n config = fields[index];\n }\n\n if (!config) {\n continue;\n }\n\n if (\"in\" in config) {\n if (config.key) {\n const field = map.get(config.key)!;\n const name = field.map || config.key;\n if (field.in) {\n writeSlot(field.in, name, arg);\n }\n } else {\n params.body = arg;\n }\n } else {\n for (const [key, value] of Object.entries(arg ?? {})) {\n const field = map.get(key);\n\n if (field) {\n if (field.in) {\n const name = field.map || key;\n writeSlot(field.in, name, value);\n } else {\n params[field.map] = value;\n }\n } else {\n const extra = extraPrefixes.find(([prefix]) =>\n key.startsWith(prefix),\n );\n\n if (extra) {\n const [prefix, slot] = extra;\n writeSlot(slot, key.slice(prefix.length), value);\n } else if (\"allowExtra\" in config && config.allowExtra) {\n for (const [slot, allowed] of Object.entries(config.allowExtra)) {\n if (allowed) {\n writeSlot(slot as Slot, key, value);\n break;\n }\n }\n }\n }\n }\n }\n }\n\n stripEmptySlots(params);\n\n return params;\n}\n","// This file is auto-generated by @hey-api/openapi-ts\n\nimport type { Config } from \"./types.gen\";\n\nexport type ServerSentEventsOptions<TData = unknown> = Omit<\n RequestInit,\n \"method\"\n> &\n Pick<Config, \"method\" | \"responseTransformer\" | \"responseValidator\"> & {\n /**\n * Fetch API implementation. You can use this option to provide a custom\n * fetch instance.\n *\n * @default globalThis.fetch\n */\n fetch?: typeof fetch;\n /**\n * Implementing clients can call request interceptors inside this hook.\n */\n onRequest?: (url: string, init: RequestInit) => Promise<Request>;\n /**\n * Callback invoked when a network or parsing error occurs during streaming.\n *\n * This option applies only if the endpoint returns a stream of events.\n *\n * @param error The error that occurred.\n */\n onSseError?: (error: unknown) => void;\n /**\n * Callback invoked when an event is streamed from the server.\n *\n * This option applies only if the endpoint returns a stream of events.\n *\n * @param event Event streamed from the server.\n * @returns Nothing (void).\n */\n onSseEvent?: (event: StreamEvent<TData>) => void;\n serializedBody?: RequestInit[\"body\"];\n /**\n * Default retry delay in milliseconds.\n *\n * This option applies only if the endpoint returns a stream of events.\n *\n * @default 3000\n */\n sseDefaultRetryDelay?: number;\n /**\n * Maximum number of retry attempts before giving up.\n */\n sseMaxRetryAttempts?: number;\n /**\n * Maximum retry delay in milliseconds.\n *\n * Applies only when exponential backoff is used.\n *\n * This option applies only if the endpoint returns a stream of events.\n *\n * @default 30000\n */\n sseMaxRetryDelay?: number;\n /**\n * Optional sleep function for retry backoff.\n *\n * Defaults to using `setTimeout`.\n */\n sseSleepFn?: (ms: number) => Promise<void>;\n url: string;\n };\n\nexport interface StreamEvent<TData = unknown> {\n data: TData;\n event?: string;\n id?: string;\n retry?: number;\n}\n\nexport type ServerSentEventsResult<\n TData = unknown,\n TReturn = void,\n TNext = unknown,\n> = {\n stream: AsyncGenerator<\n TData extends Record<string, unknown> ? TData[keyof TData] : TData,\n TReturn,\n TNext\n >;\n};\n\nexport function createSseClient<TData = unknown>({\n onRequest,\n onSseError,\n onSseEvent,\n responseTransformer,\n responseValidator,\n sseDefaultRetryDelay,\n sseMaxRetryAttempts,\n sseMaxRetryDelay,\n sseSleepFn,\n url,\n ...options\n}: ServerSentEventsOptions): ServerSentEventsResult<TData> {\n let lastEventId: string | undefined;\n\n const sleep =\n sseSleepFn ??\n ((ms: number) => new Promise((resolve) => setTimeout(resolve, ms)));\n\n const createStream = async function* () {\n let retryDelay: number = sseDefaultRetryDelay ?? 3000;\n let attempt = 0;\n const signal = options.signal ?? new AbortController().signal;\n\n while (true) {\n if (signal.aborted) break;\n\n attempt++;\n\n const headers =\n options.headers instanceof Headers\n ? options.headers\n : new Headers(options.headers as Record<string, string> | undefined);\n\n if (lastEventId !== undefined) {\n headers.set(\"Last-Event-ID\", lastEventId);\n }\n\n try {\n const requestInit: RequestInit = {\n redirect: \"follow\",\n ...options,\n body: options.serializedBody,\n headers,\n signal,\n };\n let request = new Request(url, requestInit);\n if (onRequest) {\n request = await onRequest(url, requestInit);\n }\n // fetch must be assigned here, otherwise it would throw the error:\n // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation\n const _fetch = options.fetch ?? globalThis.fetch;\n const response = await _fetch(request);\n\n if (!response.ok)\n throw new Error(\n `SSE failed: ${response.status} ${response.statusText}`,\n );\n\n if (!response.body) throw new Error(\"No body in SSE response\");\n\n const reader = response.body\n .pipeThrough(new TextDecoderStream())\n .getReader();\n\n let buffer = \"\";\n\n const abortHandler = () => {\n try {\n reader.cancel();\n } catch {\n // noop\n }\n };\n\n signal.addEventListener(\"abort\", abortHandler);\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += value;\n buffer = buffer.replace(/\\r\\n?/g, \"\\n\"); // normalize line endings\n\n const chunks = buffer.split(\"\\n\\n\");\n buffer = chunks.pop() ?? \"\";\n\n for (const chunk of chunks) {\n const lines = chunk.split(\"\\n\");\n const dataLines: Array<string> = [];\n let eventName: string | undefined;\n\n for (const line of lines) {\n if (line.startsWith(\"data:\")) {\n dataLines.push(line.replace(/^data:\\s*/, \"\"));\n } else if (line.startsWith(\"event:\")) {\n eventName = line.replace(/^event:\\s*/, \"\");\n } else if (line.startsWith(\"id:\")) {\n lastEventId = line.replace(/^id:\\s*/, \"\");\n } else if (line.startsWith(\"retry:\")) {\n const parsed = Number.parseInt(\n line.replace(/^retry:\\s*/, \"\"),\n 10,\n );\n if (!Number.isNaN(parsed)) {\n retryDelay = parsed;\n }\n }\n }\n\n let data: unknown;\n let parsedJson = false;\n\n if (dataLines.length) {\n const rawData = dataLines.join(\"\\n\");\n try {\n data = JSON.parse(rawData);\n parsedJson = true;\n } catch {\n data = rawData;\n }\n }\n\n if (parsedJson) {\n if (responseValidator) {\n await responseValidator(data);\n }\n\n if (responseTransformer) {\n data = await responseTransformer(data);\n }\n }\n\n onSseEvent?.({\n data,\n event: eventName,\n id: lastEventId,\n retry: retryDelay,\n });\n\n if (dataLines.length) {\n yield data as any;\n }\n }\n }\n } finally {\n signal.removeEventListener(\"abort\", abortHandler);\n reader.releaseLock();\n }\n\n break; // exit loop on normal completion\n } catch (error) {\n // connection failed or aborted; retry after delay\n onSseError?.(error);\n\n if (\n sseMaxRetryAttempts !== undefined &&\n attempt >= sseMaxRetryAttempts\n ) {\n break; // stop after firing error\n }\n\n // exponential backoff: double retry each attempt, cap at 30s\n const backoff = Math.min(\n retryDelay * 2 ** (attempt - 1),\n sseMaxRetryDelay ?? 30000,\n );\n await sleep(backoff);\n }\n }\n };\n\n const stream = createStream();\n\n return { stream };\n}\n","// This file is auto-generated by @hey-api/openapi-ts\n\ninterface SerializeOptions<T>\n extends SerializePrimitiveOptions, SerializerOptions<T> {}\n\ninterface SerializePrimitiveOptions {\n allowReserved?: boolean;\n name: string;\n}\n\nexport interface SerializerOptions<T> {\n /**\n * @default true\n */\n explode: boolean;\n style: T;\n}\n\nexport type ArrayStyle = \"form\" | \"spaceDelimited\" | \"pipeDelimited\";\nexport type ArraySeparatorStyle = ArrayStyle | MatrixStyle;\ntype MatrixStyle = \"label\" | \"matrix\" | \"simple\";\nexport type ObjectStyle = \"form\" | \"deepObject\";\ntype ObjectSeparatorStyle = ObjectStyle | MatrixStyle;\n\ninterface SerializePrimitiveParam extends SerializePrimitiveOptions {\n value: string;\n}\n\nexport const separatorArrayExplode = (\n style: ArraySeparatorStyle,\n): \".\" | \";\" | \",\" | \"&\" => {\n switch (style) {\n case \"label\":\n return \".\";\n case \"matrix\":\n return \";\";\n case \"simple\":\n return \",\";\n default:\n return \"&\";\n }\n};\n\nexport const separatorArrayNoExplode = (\n style: ArraySeparatorStyle,\n): \",\" | \"|\" | \"%20\" => {\n switch (style) {\n case \"form\":\n return \",\";\n case \"pipeDelimited\":\n return \"|\";\n case \"spaceDelimited\":\n return \"%20\";\n default:\n return \",\";\n }\n};\n\nexport const separatorObjectExplode = (\n style: ObjectSeparatorStyle,\n): \".\" | \";\" | \",\" | \"&\" => {\n switch (style) {\n case \"label\":\n return \".\";\n case \"matrix\":\n return \";\";\n case \"simple\":\n return \",\";\n default:\n return \"&\";\n }\n};\n\nexport const serializeArrayParam = ({\n allowReserved,\n explode,\n name,\n style,\n value,\n}: SerializeOptions<ArraySeparatorStyle> & {\n value: unknown[];\n}): string => {\n if (!explode) {\n const joinedValues = (\n allowReserved ? value : value.map((v) => encodeURIComponent(v as string))\n ).join(separatorArrayNoExplode(style));\n switch (style) {\n case \"label\":\n return `.${joinedValues}`;\n case \"matrix\":\n return `;${name}=${joinedValues}`;\n case \"simple\":\n return joinedValues;\n default:\n return `${name}=${joinedValues}`;\n }\n }\n\n const separator = separatorArrayExplode(style);\n const joinedValues = value\n .map((v) => {\n if (style === \"label\" || style === \"simple\") {\n return allowReserved ? v : encodeURIComponent(v as string);\n }\n\n return serializePrimitiveParam({\n allowReserved,\n name,\n value: v as string,\n });\n })\n .join(separator);\n return style === \"label\" || style === \"matrix\"\n ? separator + joinedValues\n : joinedValues;\n};\n\nexport const serializePrimitiveParam = ({\n allowReserved,\n name,\n value,\n}: SerializePrimitiveParam): string => {\n if (value === undefined || value === null) {\n return \"\";\n }\n\n if (typeof value === \"object\") {\n throw new Error(\n \"Deeply-nested arrays/objects aren’t supported. Provide your own `querySerializer()` to handle these.\",\n );\n }\n\n return `${name}=${allowReserved ? value : encodeURIComponent(value)}`;\n};\n\nexport const serializeObjectParam = ({\n allowReserved,\n explode,\n name,\n style,\n value,\n valueOnly,\n}: SerializeOptions<ObjectSeparatorStyle> & {\n value: Record<string, unknown> | Date;\n valueOnly?: boolean;\n}): string => {\n if (value instanceof Date) {\n return valueOnly ? value.toISOString() : `${name}=${value.toISOString()}`;\n }\n\n if (style !== \"deepObject\" && !explode) {\n let values: string[] = [];\n Object.entries(value).forEach(([key, v]) => {\n values = [\n ...values,\n key,\n allowReserved ? (v as string) : encodeURIComponent(v as string),\n ];\n });\n const joinedValues = values.join(\",\");\n switch (style) {\n case \"form\":\n return `${name}=${joinedValues}`;\n case \"label\":\n return `.${joinedValues}`;\n case \"matrix\":\n return `;${name}=${joinedValues}`;\n default:\n return joinedValues;\n }\n }\n\n const separator = separatorObjectExplode(style);\n const joinedValues = Object.entries(value)\n .map(([key, v]) =>\n serializePrimitiveParam({\n allowReserved,\n name: style === \"deepObject\" ? `${name}[${key}]` : key,\n value: v as string,\n }),\n )\n .join(separator);\n return style === \"label\" || style === \"matrix\"\n ? separator + joinedValues\n : joinedValues;\n};\n","// This file is auto-generated by @hey-api/openapi-ts\n\nimport type { BodySerializer, QuerySerializer } from \"./bodySerializer.gen\";\nimport {\n type ArraySeparatorStyle,\n serializeArrayParam,\n serializeObjectParam,\n serializePrimitiveParam,\n} from \"./pathSerializer.gen\";\n\nexport interface PathSerializer {\n path: Record<string, unknown>;\n url: string;\n}\n\nexport const PATH_PARAM_RE: RegExp = /\\{[^{}]+\\}/g;\n\nexport const defaultPathSerializer = ({\n path,\n url: _url,\n}: PathSerializer): string => {\n let url = _url;\n const matches = _url.match(PATH_PARAM_RE);\n if (matches) {\n for (const match of matches) {\n let explode = false;\n let name = match.substring(1, match.length - 1);\n let style: ArraySeparatorStyle = \"simple\";\n\n if (name.endsWith(\"*\")) {\n explode = true;\n name = name.substring(0, name.length - 1);\n }\n\n if (name.startsWith(\".\")) {\n name = name.substring(1);\n style = \"label\";\n } else if (name.startsWith(\";\")) {\n name = name.substring(1);\n style = \"matrix\";\n }\n\n const value = path[name];\n\n if (value === undefined || value === null) {\n continue;\n }\n\n if (Array.isArray(value)) {\n url = url.replace(\n match,\n serializeArrayParam({ explode, name, style, value }),\n );\n continue;\n }\n\n if (typeof value === \"object\") {\n url = url.replace(\n match,\n serializeObjectParam({\n explode,\n name,\n style,\n value: value as Record<string, unknown>,\n valueOnly: true,\n }),\n );\n continue;\n }\n\n if (style === \"matrix\") {\n url = url.replace(\n match,\n `;${serializePrimitiveParam({\n name,\n value: value as string,\n })}`,\n );\n continue;\n }\n\n const replaceValue = encodeURIComponent(\n style === \"label\" ? `.${value as string}` : (value as string),\n );\n url = url.replace(match, replaceValue);\n }\n }\n return url;\n};\n\nexport const getUrl = ({\n baseUrl,\n path,\n query,\n querySerializer,\n url: _url,\n}: {\n baseUrl?: string;\n path?: Record<string, unknown>;\n query?: Record<string, unknown>;\n querySerializer: QuerySerializer;\n url: string;\n}): string => {\n const pathUrl = _url.startsWith(\"/\") ? _url : `/${_url}`;\n let url = (baseUrl ?? \"\") + pathUrl;\n if (path) {\n url = defaultPathSerializer({ path, url });\n }\n let search = query ? querySerializer(query) : \"\";\n if (search.startsWith(\"?\")) {\n search = search.substring(1);\n }\n if (search) {\n url += `?${search}`;\n }\n return url;\n};\n\nexport function getValidRequestBody(options: {\n body?: unknown;\n bodySerializer?: BodySerializer | null;\n serializedBody?: unknown;\n}): unknown {\n const hasBody = options.body !== undefined;\n const isSerializedBody = hasBody && options.bodySerializer;\n\n if (isSerializedBody) {\n if (\"serializedBody\" in options) {\n const hasSerializedBody =\n options.serializedBody !== undefined && options.serializedBody !== \"\";\n\n return hasSerializedBody ? options.serializedBody : null;\n }\n\n // not all clients implement a serializedBody property (i.e., client-axios)\n return options.body !== \"\" ? options.body : null;\n }\n\n // plain/text body\n if (hasBody) {\n return options.body;\n }\n\n // no body was provided\n return undefined;\n}\n","// This file is auto-generated by @hey-api/openapi-ts\n\nexport type AuthToken = string | undefined;\n\nexport interface Auth {\n /**\n * Which part of the request do we use to send the auth?\n *\n * @default 'header'\n */\n in?: \"header\" | \"query\" | \"cookie\";\n /**\n * A unique identifier for the security scheme.\n *\n * Defined only when there are multiple security schemes whose `Auth`\n * shape would otherwise be identical.\n */\n key?: string;\n /**\n * Header or query parameter name.\n *\n * @default 'Authorization'\n */\n name?: string;\n scheme?: \"basic\" | \"bearer\";\n type: \"apiKey\" | \"http\";\n}\n\nexport const getAuthToken = async (\n auth: Auth,\n callback: ((auth: Auth) => Promise<AuthToken> | AuthToken) | AuthToken,\n): Promise<string | undefined> => {\n const token =\n typeof callback === \"function\" ? await callback(auth) : callback;\n\n if (!token) {\n return;\n }\n\n if (auth.scheme === \"bearer\") {\n return `Bearer ${token}`;\n }\n\n if (auth.scheme === \"basic\") {\n return `Basic ${btoa(token)}`;\n }\n\n return token;\n};\n","// This file is auto-generated by @hey-api/openapi-ts\n\nimport { getAuthToken } from \"../core/auth.gen\";\nimport type { QuerySerializerOptions } from \"../core/bodySerializer.gen\";\nimport { jsonBodySerializer } from \"../core/bodySerializer.gen\";\nimport {\n serializeArrayParam,\n serializeObjectParam,\n serializePrimitiveParam,\n} from \"../core/pathSerializer.gen\";\nimport { getUrl } from \"../core/utils.gen\";\nimport type {\n Client,\n ClientOptions,\n Config,\n RequestOptions,\n} from \"./types.gen\";\n\nexport const createQuerySerializer = <T = unknown>({\n parameters = {},\n ...args\n}: QuerySerializerOptions = {}): ((queryParams: T) => string) => {\n const querySerializer = (queryParams: T): string => {\n const search: string[] = [];\n if (queryParams && typeof queryParams === \"object\") {\n for (const name in queryParams) {\n const value = queryParams[name];\n\n if (value === undefined || value === null) {\n continue;\n }\n\n const options = parameters[name] || args;\n\n if (Array.isArray(value)) {\n const serializedArray = serializeArrayParam({\n allowReserved: options.allowReserved,\n explode: true,\n name,\n style: \"form\",\n value,\n ...options.array,\n });\n if (serializedArray) search.push(serializedArray);\n } else if (typeof value === \"object\") {\n const serializedObject = serializeObjectParam({\n allowReserved: options.allowReserved,\n explode: true,\n name,\n style: \"deepObject\",\n value: value as Record<string, unknown>,\n ...options.object,\n });\n if (serializedObject) search.push(serializedObject);\n } else {\n const serializedPrimitive = serializePrimitiveParam({\n allowReserved: options.allowReserved,\n name,\n value: value as string,\n });\n if (serializedPrimitive) search.push(serializedPrimitive);\n }\n }\n }\n return search.join(\"&\");\n };\n return querySerializer;\n};\n\n/**\n * Infers parseAs value from provided Content-Type header.\n */\nexport const getParseAs = (\n contentType: string | null,\n): Exclude<Config[\"parseAs\"], \"auto\"> => {\n if (!contentType) {\n // If no Content-Type header is provided, the best we can do is return the raw response body,\n // which is effectively the same as the 'stream' option.\n return \"stream\";\n }\n\n const cleanContent = contentType.split(\";\")[0]?.trim();\n\n if (!cleanContent) {\n return;\n }\n\n if (\n cleanContent.startsWith(\"application/json\") ||\n cleanContent.endsWith(\"+json\")\n ) {\n return \"json\";\n }\n\n if (cleanContent === \"multipart/form-data\") {\n return \"formData\";\n }\n\n if (\n [\"application/\", \"audio/\", \"image/\", \"video/\"].some((type) =>\n cleanContent.startsWith(type),\n )\n ) {\n return \"blob\";\n }\n\n if (cleanContent.startsWith(\"text/\")) {\n return \"text\";\n }\n\n return;\n};\n\nconst checkForExistence = (\n options: Pick<RequestOptions, \"auth\" | \"query\"> & {\n headers: Headers;\n },\n name?: string,\n): boolean => {\n if (!name) {\n return false;\n }\n if (\n options.headers.has(name) ||\n options.query?.[name] ||\n options.headers.get(\"Cookie\")?.includes(`${name}=`)\n ) {\n return true;\n }\n return false;\n};\n\nexport async function setAuthParams(\n options: Pick<RequestOptions, \"auth\" | \"query\" | \"security\"> & {\n headers: Headers;\n },\n): Promise<void> {\n for (const auth of options.security ?? []) {\n if (checkForExistence(options, auth.name)) {\n continue;\n }\n\n const token = await getAuthToken(auth, options.auth);\n\n if (!token) {\n continue;\n }\n\n const name = auth.name ?? \"Authorization\";\n\n switch (auth.in) {\n case \"query\":\n if (!options.query) {\n options.query = {};\n }\n options.query[name] = token;\n break;\n case \"cookie\":\n options.headers.append(\"Cookie\", `${name}=${token}`);\n break;\n case \"header\":\n default:\n options.headers.set(name, token);\n break;\n }\n }\n}\n\nexport const buildUrl: Client[\"buildUrl\"] = (options) =>\n getUrl({\n baseUrl: options.baseUrl as string,\n path: options.path,\n query: options.query,\n querySerializer:\n typeof options.querySerializer === \"function\"\n ? options.querySerializer\n : createQuerySerializer(options.querySerializer),\n url: options.url,\n });\n\nexport const mergeConfigs = (a: Config, b: Config): Config => {\n const config = { ...a, ...b };\n if (config.baseUrl?.endsWith(\"/\")) {\n config.baseUrl = config.baseUrl.substring(0, config.baseUrl.length - 1);\n }\n config.headers = mergeHeaders(a.headers, b.headers);\n return config;\n};\n\nconst headersEntries = (headers: Headers): Array<[string, string]> => {\n const entries: Array<[string, string]> = [];\n headers.forEach((value, key) => {\n entries.push([key, value]);\n });\n return entries;\n};\n\nexport const mergeHeaders = (\n ...headers: Array<Required<Config>[\"headers\"] | undefined>\n): Headers => {\n const mergedHeaders = new Headers();\n for (const header of headers) {\n if (!header) {\n continue;\n }\n\n const iterator =\n header instanceof Headers\n ? headersEntries(header)\n : Object.entries(header);\n\n for (const [key, value] of iterator) {\n if (value === null) {\n mergedHeaders.delete(key);\n } else if (Array.isArray(value)) {\n for (const v of value) {\n mergedHeaders.append(key, v as string);\n }\n } else if (value !== undefined) {\n // assume object headers are meant to be JSON stringified, i.e., their\n // content value in OpenAPI specification is 'application/json'\n mergedHeaders.set(\n key,\n typeof value === \"object\" ? JSON.stringify(value) : (value as string),\n );\n }\n }\n }\n return mergedHeaders;\n};\n\ntype ErrInterceptor<Err, Res, Req, Options> = (\n error: Err,\n /** response may be undefined due to a network error where no response object is produced */\n response: Res | undefined,\n /** request may be undefined, because error may be from building the request object itself */\n request: Req | undefined,\n options: Options,\n) => Err | Promise<Err>;\n\ntype ReqInterceptor<Req, Options> = (\n request: Req,\n options: Options,\n) => Req | Promise<Req>;\n\ntype ResInterceptor<Res, Req, Options> = (\n response: Res,\n request: Req,\n options: Options,\n) => Res | Promise<Res>;\n\nclass Interceptors<Interceptor> {\n fns: Array<Interceptor | null> = [];\n\n clear(): void {\n this.fns = [];\n }\n\n eject(id: number | Interceptor): void {\n const index = this.getInterceptorIndex(id);\n if (this.fns[index]) {\n this.fns[index] = null;\n }\n }\n\n exists(id: number | Interceptor): boolean {\n const index = this.getInterceptorIndex(id);\n return Boolean(this.fns[index]);\n }\n\n getInterceptorIndex(id: number | Interceptor): number {\n if (typeof id === \"number\") {\n return this.fns[id] ? id : -1;\n }\n return this.fns.indexOf(id);\n }\n\n update(\n id: number | Interceptor,\n fn: Interceptor,\n ): number | Interceptor | false {\n const index = this.getInterceptorIndex(id);\n if (this.fns[index]) {\n this.fns[index] = fn;\n return id;\n }\n return false;\n }\n\n use(fn: Interceptor): number {\n this.fns.push(fn);\n return this.fns.length - 1;\n }\n}\n\nexport interface Middleware<Req, Res, Err, Options> {\n error: Interceptors<ErrInterceptor<Err, Res, Req, Options>>;\n request: Interceptors<ReqInterceptor<Req, Options>>;\n response: Interceptors<ResInterceptor<Res, Req, Options>>;\n}\n\nexport const createInterceptors = <Req, Res, Err, Options>(): Middleware<\n Req,\n Res,\n Err,\n Options\n> => ({\n error: new Interceptors<ErrInterceptor<Err, Res, Req, Options>>(),\n request: new Interceptors<ReqInterceptor<Req, Options>>(),\n response: new Interceptors<ResInterceptor<Res, Req, Options>>(),\n});\n\nconst defaultQuerySerializer = createQuerySerializer({\n allowReserved: false,\n array: {\n explode: true,\n style: \"form\",\n },\n object: {\n explode: true,\n style: \"deepObject\",\n },\n});\n\nconst defaultHeaders = {\n \"Content-Type\": \"application/json\",\n};\n\nexport const createConfig = <T extends ClientOptions = ClientOptions>(\n override: Config<Omit<ClientOptions, keyof T> & T> = {},\n): Config<Omit<ClientOptions, keyof T> & T> => ({\n ...jsonBodySerializer,\n headers: defaultHeaders,\n parseAs: \"auto\",\n querySerializer: defaultQuerySerializer,\n ...override,\n});\n","// This file is auto-generated by @hey-api/openapi-ts\n\nimport { createSseClient } from \"../core/serverSentEvents.gen\";\nimport type { HttpMethod } from \"../core/types.gen\";\nimport { getValidRequestBody } from \"../core/utils.gen\";\nimport type {\n Client,\n Config,\n RequestOptions,\n ResolvedRequestOptions,\n} from \"./types.gen\";\nimport {\n buildUrl,\n createConfig,\n createInterceptors,\n getParseAs,\n mergeConfigs,\n mergeHeaders,\n setAuthParams,\n} from \"./utils.gen\";\n\ntype ReqInit = Omit<RequestInit, \"body\" | \"headers\"> & {\n body?: any;\n headers: ReturnType<typeof mergeHeaders>;\n};\n\nexport const createClient = (config: Config = {}): Client => {\n let _config = mergeConfigs(createConfig(), config);\n\n const getConfig = (): Config => ({ ..._config });\n\n const setConfig = (config: Config): Config => {\n _config = mergeConfigs(_config, config);\n return getConfig();\n };\n\n const interceptors = createInterceptors<\n Request,\n Response,\n unknown,\n ResolvedRequestOptions\n >();\n\n const beforeRequest = async <\n TData = unknown,\n TResponseStyle extends \"data\" | \"fields\" = \"fields\",\n ThrowOnError extends boolean = boolean,\n Url extends string = string,\n >(\n options: RequestOptions<TData, TResponseStyle, ThrowOnError, Url>,\n ) => {\n const opts = {\n ..._config,\n ...options,\n fetch: options.fetch ?? _config.fetch ?? globalThis.fetch,\n headers: mergeHeaders(_config.headers, options.headers),\n serializedBody: undefined as string | undefined,\n };\n\n if (opts.security) {\n await setAuthParams(opts);\n }\n\n if (opts.requestValidator) {\n await opts.requestValidator(opts);\n }\n\n if (opts.body !== undefined && opts.bodySerializer) {\n opts.serializedBody = opts.bodySerializer(opts.body) as\n | string\n | undefined;\n }\n\n // remove Content-Type header if body is empty to avoid sending invalid requests\n if (opts.body === undefined || opts.serializedBody === \"\") {\n opts.headers.delete(\"Content-Type\");\n }\n\n const resolvedOpts = opts as typeof opts &\n ResolvedRequestOptions<TResponseStyle, ThrowOnError, Url>;\n const url = buildUrl(resolvedOpts);\n\n return { opts: resolvedOpts, url };\n };\n\n const request: Client[\"request\"] = async (options) => {\n const throwOnError = options.throwOnError ?? _config.throwOnError;\n const responseStyle = options.responseStyle ?? _config.responseStyle;\n\n let request: Request | undefined;\n let response: Response | undefined;\n\n try {\n const { opts, url } = await beforeRequest(options);\n const requestInit: ReqInit = {\n redirect: \"follow\",\n ...opts,\n body: getValidRequestBody(opts),\n };\n\n request = new Request(url, requestInit);\n\n for (const fn of interceptors.request.fns) {\n if (fn) {\n request = await fn(request, opts);\n }\n }\n\n // fetch must be assigned here, otherwise it would throw the error:\n // TypeError: Failed to execute 'fetch' on 'Window': Illegal invocation\n const _fetch = opts.fetch!;\n\n response = await _fetch(request);\n\n for (const fn of interceptors.response.fns) {\n if (fn) {\n response = await fn(response, request, opts);\n }\n }\n\n const result = {\n request,\n response,\n };\n\n if (response.ok) {\n const parseAs =\n (opts.parseAs === \"auto\"\n ? getParseAs(response.headers.get(\"Content-Type\"))\n : opts.parseAs) ?? \"json\";\n\n if (\n response.status === 204 ||\n response.headers.get(\"Content-Length\") === \"0\"\n ) {\n let emptyData: any;\n switch (parseAs) {\n case \"arrayBuffer\":\n case \"blob\":\n case \"text\":\n emptyData = await response[parseAs]();\n break;\n case \"formData\":\n emptyData = new FormData();\n break;\n case \"stream\":\n emptyData = response.body;\n break;\n case \"json\":\n default:\n emptyData = {};\n break;\n }\n return opts.responseStyle === \"data\"\n ? emptyData\n : {\n data: emptyData,\n ...result,\n };\n }\n\n let data: any;\n switch (parseAs) {\n case \"arrayBuffer\":\n case \"blob\":\n case \"formData\":\n case \"text\":\n data = await response[parseAs]();\n break;\n case \"json\": {\n // Some servers return 200 with no Content-Length and empty body.\n // response.json() would throw; read as text and parse if non-empty.\n const text = await response.text();\n data = text ? JSON.parse(text) : {};\n break;\n }\n case \"stream\":\n return opts.responseStyle === \"data\"\n ? response.body\n : {\n data: response.body,\n ...result,\n };\n }\n\n if (parseAs === \"json\") {\n if (opts.responseValidator) {\n await opts.responseValidator(data);\n }\n\n if (opts.responseTransformer) {\n data = await opts.responseTransformer(data);\n }\n }\n\n return opts.responseStyle === \"data\"\n ? data\n : {\n data,\n ...result,\n };\n }\n\n const textError = await response.text();\n let jsonError: unknown;\n\n try {\n jsonError = JSON.parse(textError);\n } catch {\n // noop\n }\n\n throw jsonError ?? textError;\n } catch (error) {\n let finalError = error;\n\n for (const fn of interceptors.error.fns) {\n if (fn) {\n finalError = await fn(\n finalError,\n response,\n request,\n options as ResolvedRequestOptions,\n );\n }\n }\n\n finalError = finalError || {};\n\n if (throwOnError) {\n throw finalError;\n }\n\n // TODO: we probably want to return error and improve types\n return responseStyle === \"data\"\n ? undefined\n : {\n error: finalError,\n request,\n response,\n };\n }\n };\n\n const makeMethodFn =\n (method: Uppercase<HttpMethod>) => (options: RequestOptions) =>\n request({ ...options, method });\n\n const makeSseFn =\n (method: Uppercase<HttpMethod>) => async (options: RequestOptions) => {\n const { opts, url } = await beforeRequest(options);\n return createSseClient({\n ...opts,\n body: opts.body as BodyInit | null | undefined,\n method,\n onRequest: async (url, init) => {\n let request = new Request(url, init);\n for (const fn of interceptors.request.fns) {\n if (fn) {\n request = await fn(request, opts);\n }\n }\n return request;\n },\n serializedBody: getValidRequestBody(opts) as\n | BodyInit\n | null\n | undefined,\n url,\n });\n };\n\n const _buildUrl: Client[\"buildUrl\"] = (options) =>\n buildUrl({ ..._config, ...options });\n\n return {\n buildUrl: _buildUrl,\n connect: makeMethodFn(\"CONNECT\"),\n delete: makeMethodFn(\"DELETE\"),\n get: makeMethodFn(\"GET\"),\n getConfig,\n head: makeMethodFn(\"HEAD\"),\n interceptors,\n options: makeMethodFn(\"OPTIONS\"),\n patch: makeMethodFn(\"PATCH\"),\n post: makeMethodFn(\"POST\"),\n put: makeMethodFn(\"PUT\"),\n request,\n setConfig,\n sse: {\n connect: makeSseFn(\"CONNECT\"),\n delete: makeSseFn(\"DELETE\"),\n get: makeSseFn(\"GET\"),\n head: makeSseFn(\"HEAD\"),\n options: makeSseFn(\"OPTIONS\"),\n patch: makeSseFn(\"PATCH\"),\n post: makeSseFn(\"POST\"),\n put: makeSseFn(\"PUT\"),\n trace: makeSseFn(\"TRACE\"),\n },\n trace: makeMethodFn(\"TRACE\"),\n } as Client;\n};\n","import {\n ApiError,\n createHeyApiErrorInterceptor,\n} from \"@fluid-app/api-client-core\";\nimport { type Client, createClient } from \"@fluid-app/hey-api-fetch-runtime\";\n\nimport type { OAuthError } from \"./hey-api/types.gen\";\n\nexport type IdentityHeyApiClient = Client;\n\n/** An RFC 6749 error code the identity service can answer with. */\nexport type OAuthErrorCode = OAuthError[\"error\"];\n\nexport interface IdentityHeyApiClientConfig {\n /** The identity service origin, for example `https://id.fluid.app`. */\n baseUrl: string;\n fetch?: typeof fetch;\n /**\n * Resolves the principal access token for `/userinfo`, the one operation a\n * browser calls with `principalBearer`. It is consulted only for operations\n * that declare a bearer scheme, so the anonymous `/token` and `/revoke`\n * calls a public client makes never carry it.\n */\n getAccessToken?: () =>\n | string\n | null\n | undefined\n | Promise<string | null | undefined>;\n /**\n * Browser clients send `include` so `/token` and `/revoke` carry the\n * identity domain's HttpOnly refresh cookie. Defaults to `include`.\n */\n credentials?: RequestCredentials;\n defaultHeaders?: Record<string, string>;\n /** Invoked on a 401. */\n onAuthError?: () => void;\n}\n\nfunction isAbortError(error: unknown): boolean {\n return (\n (error instanceof DOMException && error.name === \"AbortError\") ||\n (error instanceof Error && error.name === \"AbortError\")\n );\n}\n\n/**\n * Creates an identity service Hey API client with the shared ApiError shape.\n *\n * Only bearer credentials are ever attached. Confidential clients that\n * authenticate with HTTP Basic are servers and do not use this package.\n */\nexport function createIdentityHeyApiClient(\n config: IdentityHeyApiClientConfig,\n): Client {\n const client = createClient({\n auth: async (auth) => {\n if (auth.scheme !== \"bearer\") return undefined;\n return (await config.getAccessToken?.()) ?? undefined;\n },\n baseUrl: config.baseUrl,\n credentials: config.credentials ?? \"include\",\n fetch: config.fetch,\n headers: {\n Accept: \"application/json\",\n ...config.defaultHeaders,\n },\n throwOnError: true,\n });\n\n client.interceptors.error.use(\n createHeyApiErrorInterceptor({\n onResponseError: (response) => {\n if (response.status === 401) {\n config.onAuthError?.();\n }\n },\n shouldPassThroughTransportError: isAbortError,\n }),\n );\n\n return client;\n}\n\nconst OAUTH_ERROR_CODES: ReadonlySet<string> = new Set<OAuthErrorCode>([\n \"invalid_request\",\n \"invalid_client\",\n \"invalid_grant\",\n \"unauthorized_client\",\n \"unsupported_grant_type\",\n \"invalid_scope\",\n \"invalid_target\",\n \"invalid_token\",\n \"login_required\",\n \"access_denied\",\n \"account_selection_required\",\n \"server_error\",\n \"temporarily_unavailable\",\n \"slow_down\",\n]);\n\n/** Narrows an unknown string to an RFC 6749 error code from the contract. */\nexport function isOAuthErrorCode(value: unknown): value is OAuthErrorCode {\n return typeof value === \"string\" && OAUTH_ERROR_CODES.has(value);\n}\n\n/**\n * Reads the RFC 6749 `error` code from a failed identity call, or null when the\n * failure carried none (a network error, or a body of another shape).\n */\nexport function oauthErrorCode(error: unknown): OAuthErrorCode | null {\n if (!(error instanceof ApiError)) return null;\n const code = error.body?.error;\n return isOAuthErrorCode(code) ? code : null;\n}\n","// This file is auto-generated by @hey-api/openapi-ts\n\nimport {\n type Client,\n type ClientMeta,\n type Options as Options2,\n type RequestResult,\n type TDataShape,\n urlSearchParamsBodySerializer,\n} from \"@fluid-app/hey-api-fetch-runtime\";\n\nimport type {\n AuthorizeData,\n AuthorizeErrors,\n CompleteDevelopmentAuthenticatorData,\n CompleteDevelopmentAuthenticatorErrors,\n CompleteWorkosSignInData,\n CompleteWorkosSignInErrors,\n CreateAnalyticsTokenData,\n CreateAnalyticsTokenErrors,\n CreateAnalyticsTokenResponses,\n CreateAuthorizationCodeData,\n CreateAuthorizationCodeErrors,\n CreateAuthorizationCodeResponses,\n CreateClientData,\n CreateClientErrors,\n CreateClientResponses,\n CreateMcpTokenData,\n CreateMcpTokenErrors,\n CreateMcpTokenResponses,\n CreateTokenData,\n CreateTokenErrors,\n CreateTokenResponses,\n GetAuthorizationServerMetadataData,\n GetAuthorizationServerMetadataResponses,\n GetClientData,\n GetClientErrors,\n GetClientResponses,\n GetCompanyDigestData,\n GetCompanyDigestErrors,\n GetCompanyDigestResponses,\n GetCompanyEligibilityData,\n GetCompanyEligibilityErrors,\n GetCompanyEligibilityResponses,\n GetDevelopmentAuthenticatorData,\n GetDevelopmentAuthenticatorErrors,\n GetDevelopmentAuthenticatorResponses,\n GetDevelopmentShopperCodeData,\n GetDevelopmentShopperCodeErrors,\n GetDevelopmentShopperCodeResponses,\n GetJwksData,\n GetJwksResponses,\n GetLivenessData,\n GetLivenessResponses,\n GetReadinessData,\n GetReadinessErrors,\n GetReadinessResponses,\n GetShopperSignInData,\n GetShopperSignInErrors,\n GetShopperSignInResponses,\n GetUserinfoData,\n GetUserinfoErrors,\n GetUserinfoResponses,\n IntrospectTokenData,\n IntrospectTokenErrors,\n IntrospectTokenResponses,\n ListClientsData,\n ListClientsErrors,\n ListClientsResponses,\n ListCompanyActiveMembershipsData,\n ListCompanyActiveMembershipsErrors,\n ListCompanyActiveMembershipsResponses,\n ListRevocationsData,\n ListRevocationsErrors,\n ListRevocationsResponses,\n MapCompanyToWorkosData,\n MapCompanyToWorkosErrors,\n MapCompanyToWorkosResponses,\n MapMembershipToWorkosData,\n MapMembershipToWorkosErrors,\n MapMembershipToWorkosResponses,\n MapPrincipalToWorkosData,\n MapPrincipalToWorkosErrors,\n MapPrincipalToWorkosResponses,\n ReceiveWorkosEventData,\n ReceiveWorkosEventErrors,\n ReceiveWorkosEventResponses,\n RevokeClientData,\n RevokeClientErrors,\n RevokeClientResponses,\n RevokeSessionData,\n RevokeSessionErrors,\n RevokeSessionResponses,\n RevokeTokenData,\n RevokeTokenErrors,\n RevokeTokenResponses,\n RotateClientSecretData,\n RotateClientSecretErrors,\n RotateClientSecretResponses,\n SendShopperCodeData,\n SendShopperCodeErrors,\n SendShopperCodeResponses,\n SignedOutData,\n SignedOutResponses,\n SignOutData,\n SignOutErrors,\n SignOutResponses,\n SignOutWithHintData,\n SignOutWithHintErrors,\n SignOutWithHintResponses,\n StartShopperSignInData,\n StartShopperSignInErrors,\n StartShopperSignInResponses,\n SyncCompanyData,\n SyncCompanyErrors,\n SyncCompanyResponses,\n SyncDropletInstallationData,\n SyncDropletInstallationErrors,\n SyncDropletInstallationResponses,\n SyncMembershipData,\n SyncMembershipErrors,\n SyncMembershipResponses,\n SyncOAuthAuthorizationData,\n SyncOAuthAuthorizationErrors,\n SyncOAuthAuthorizationResponses,\n SyncOAuthClientData,\n SyncOAuthClientErrors,\n SyncOAuthClientResponses,\n SyncOAuthConsentData,\n SyncOAuthConsentErrors,\n SyncOAuthConsentResponses,\n SyncPrincipalData,\n SyncPrincipalErrors,\n SyncPrincipalResponses,\n VerifyShopperCodeData,\n VerifyShopperCodeErrors,\n VerifyShopperInlineData,\n VerifyShopperInlineErrors,\n VerifyShopperInlineResponses,\n} from \"./types.gen\";\n\nexport type Options<\n TData extends TDataShape = TDataShape,\n ThrowOnError extends boolean = boolean,\n TResponse = unknown,\n> = Options2<TData, ThrowOnError, TResponse> & {\n /**\n * You can provide a client instance returned by `createClient()` instead of\n * individual options. This might be also useful if you want to implement a\n * custom client.\n */\n client: Client;\n /**\n * You can pass arbitrary values through the `meta` object. This can be\n * used to access values that aren't defined as part of the SDK function.\n */\n meta?: keyof ClientMeta extends never ? Record<string, unknown> : ClientMeta;\n};\n\n/**\n * Liveness\n */\nexport const getLiveness = <ThrowOnError extends boolean = true>(\n options: Options<GetLivenessData, ThrowOnError>,\n): RequestResult<GetLivenessResponses, unknown, ThrowOnError, \"data\"> =>\n options.client.get<GetLivenessResponses, unknown, ThrowOnError, \"data\">({\n responseStyle: \"data\",\n url: \"/healthz\",\n ...options,\n });\n\n/**\n * Readiness\n *\n * Unhealthy when the key store or the database is unreachable (FR-9).\n */\nexport const getReadiness = <ThrowOnError extends boolean = true>(\n options: Options<GetReadinessData, ThrowOnError>,\n): RequestResult<\n GetReadinessResponses,\n GetReadinessErrors,\n ThrowOnError,\n \"data\"\n> =>\n options.client.get<\n GetReadinessResponses,\n GetReadinessErrors,\n ThrowOnError,\n \"data\"\n >({\n responseStyle: \"data\",\n url: \"/readyz\",\n ...options,\n });\n\n/**\n * RFC 8414 authorization server metadata\n */\nexport const getAuthorizationServerMetadata = <\n ThrowOnError extends boolean = true,\n>(\n options: Options<GetAuthorizationServerMetadataData, ThrowOnError>,\n): RequestResult<\n GetAuthorizationServerMetadataResponses,\n unknown,\n ThrowOnError,\n \"data\"\n> =>\n options.client.get<\n GetAuthorizationServerMetadataResponses,\n unknown,\n ThrowOnError,\n \"data\"\n >({\n responseStyle: \"data\",\n url: \"/.well-known/oauth-authorization-server\",\n ...options,\n });\n\n/**\n * Public signing keys\n *\n * Lists every key in the published, signing, or retiring state. Carries\n * Cache-Control consistent with the JWKS cache bound in the shared spec.\n *\n */\nexport const getJwks = <ThrowOnError extends boolean = true>(\n options: Options<GetJwksData, ThrowOnError>,\n): RequestResult<GetJwksResponses, unknown, ThrowOnError, \"data\"> =>\n options.client.get<GetJwksResponses, unknown, ThrowOnError, \"data\">({\n responseStyle: \"data\",\n url: \"/.well-known/jwks.json\",\n ...options,\n });\n\n/**\n * Authorization endpoint with silent mode\n *\n * Code flow with mandatory PKCE S256 for registered clients. A request\n * that fails client or redirect URI validation is answered with 400 and\n * never redirects. Every other outcome is a 302 to the registered\n * redirect URI carrying either `code` and `state`, or `error` and\n * `state`. A root admin signed in through WorkOS AuthKit and raised to a\n * platform session also gets `picked_org`, the company they chose in\n * AuthKit's selector; it is a hint and scopes nothing. No response\n * carries an access or refresh token.\n *\n */\nexport const authorize = <ThrowOnError extends boolean = true>(\n options: Options<AuthorizeData, ThrowOnError>,\n): RequestResult<unknown, AuthorizeErrors, ThrowOnError, \"data\"> =>\n options.client.get<unknown, AuthorizeErrors, ThrowOnError, \"data\">({\n responseStyle: \"data\",\n url: \"/authorize\",\n ...options,\n });\n\n/**\n * Token endpoint\n *\n * One endpoint for every grant. Browser clients omit `refresh_token` and\n * send the per-client HttpOnly refresh cookie on a credentialed request.\n * Native and confidential clients send `refresh_token` in the body.\n *\n * Token exchange (RFC 8693) serves company switch, role narrowing, and\n * root-admin company entry (Phase 007 FR-3, FR-4). The caller presents\n * its own live principal access token as `subject_token`: the token\n * must have been issued by this service to the calling client, so an\n * exchange never moves a token between applications. What the new\n * token may name is read from the synced directory at that moment, and\n * it can never name a company or a role the subject does not hold\n * there:\n *\n * - Company switch: `org` names another company. The subject needs an\n * active, unlocked membership there, in a company that is active and\n * either carries the sign-in flag or is reported eligible; one that is\n * neither is refused `invalid_target` as not yet available on the new\n * sign-in. `roles` defaults to that membership's role.\n * - Role narrowing: `org` is omitted or names the token's own company,\n * and `roles` lists fewer roles. Each must be held in that company now\n * and rank no higher than the subject token's roles (`root_admin`\n * over `admin` over `rep`); nothing may add a role within that\n * company (`invalid_scope`). Narrowing is a view, not a boundary: a\n * switch answers with the target membership's role, so a narrowed\n * token that switches away and back holds what the directory holds.\n * `org` is compared case-insensitively, so the token's own company\n * in any case is a narrowing, and it must be a uuid\n * (`invalid_request`); the new token names it in lower case.\n * - Company entry: a root admin's token, platform-scoped or already in\n * another company, names any active company without a membership.\n * The new token keeps `root_admin`. Any other token asking the same\n * is refused `invalid_target`.\n *\n * A role is held only as Rails grants it, because Rails checks a\n * token's roles against its own records and refuses one it would not\n * grant: `admin` through an admin membership, `rep` through a rep\n * membership, and `root_admin` through the principal's flag. The\n * directory reads `rep` from the membership's role or its\n * `rep_standing`, so an admin whom Rails also holds as a rep, and a\n * root admin who is a rep in the company, can narrow to `rep`; an\n * admin membership alone does not hold it, so an admin with no rep\n * membership cannot.\n *\n * The new token keeps the subject token's `sub`, `sid`, `auth_time`,\n * and `amr`; the target company's session bound and flush apply, and\n * past them the exchange is refused `invalid_grant`. It never expires\n * later than the subject token, so it may carry less than the usual\n * lifetime, and the subject token stays valid until its own expiry.\n * The response carries no refresh token and the caller's refresh chain\n * does not move: a caller that stays in the target company exchanges\n * again after it refreshes. Revoking a token revokes every token\n * exchanged from it, on introspection and on the revocation list, and\n * an exchanged token introspects inactive once the directory no longer\n * entitles it to its company and roles. Every exchange, issued,\n * withheld, or refused, writes an audit record naming the subject, the\n * source and target organizations, and the grant type.\n *\n * A request authenticated by the refresh *cookie* must carry an `Origin`\n * that is registered for the client named in `client_id`, derived from\n * that client's redirect URIs. A missing, `null`, or non-matching\n * Origin is refused with `invalid_request`, the cookie is never\n * consulted, and nothing is rotated. The refresh cookies are\n * `SameSite=None` so that a first-party app can refresh from its own\n * origin, which means any page could otherwise make the browser attach\n * another client's cookie: a page on one registered client's origin\n * naming a second client would have read the second client's access\n * token out of the response, and a cross-site form post could have\n * rotated a chain from anywhere. `Access-Control-Allow-Origin` is\n * likewise echoed only for the origin of the client named in the\n * request. Body-token requests from native and confidential clients\n * consult no cookie and are unaffected.\n *\n * Client authentication depends on the grant, which an OpenAPI security\n * requirement cannot express because it applies to the whole\n * operation. `x-client-authentication` states the rule and\n * implementations must enforce it: `client_credentials` always requires\n * HTTP Basic client authentication and answers 401 `invalid_client`\n * without it. Token exchange takes HTTP Basic from a confidential client\n * or `client_id` from a public one, whose proof is that the subject\n * token was issued to it; a request that identifies no client is\n * answered 401 `invalid_client`. The anonymous alternative exists\n * otherwise only for public clients redeeming a code with PKCE or\n * refreshing.\n *\n *\n * One exception (Phase 008 FR-3): a publishable client, the successor\n * of a public token, has no secret. It asks for `client_credentials` by\n * naming itself with `client_id` in a simple form post, with no\n * Authorization header, from a page whose `Origin` matches its allowed\n * origins. Any other origin, or none, is answered 401\n * `invalid_client`. Its origin is echoed in\n * `Access-Control-Allow-Origin` without\n * `Access-Control-Allow-Credentials`, and is never on the preflight\n * allow-list of the credentialed endpoints. Its token carries\n * `allowed_origins`, so a resource server can refuse it from anywhere\n * else without asking this service.\n *\n * A grant type this endpoint does not serve answers 400\n * `unsupported_grant_type` before the client is looked at, whatever\n * credentials came with it.\n *\n * A client mirrored from Rails' OAuth::Client registrations (Phase 008\n * FR-6) is issued for with the v202610 rules it was registered under:\n * no `scope` receives its default scope, unknown scopes are dropped\n * rather than refused, and only a request that leaves nothing is\n * `invalid_scope`. Its `resource` is `urn:fluid:store:<uuid>`: a\n * droplet must send one and is issued for a store only while it is\n * installed there, with the scopes the installation grants; a platform\n * client may name any store; any other client may name none. A\n * resource that is sent but blank is `invalid_target`, never treated\n * as absent.\n *\n * A mirrored client's `authorization_code` grant redeems a code Rails'\n * consent screen minted through `POST /internal/oauth/codes`, with\n * OAuth::CodeRedeemer's checks in its order: the code, `client_id`, and\n * `redirect_uri` are required (`invalid_request`); the code must be\n * this client's, for exactly this `redirect_uri`, unused and unexpired\n * (`invalid_grant`); a code minted with a challenge needs its S256\n * `code_verifier` (`invalid_request` when missing, `invalid_grant`\n * when wrong). A refused redemption does not spend the code. A code\n * owned by an MCP authorization or an analytics consent is refused\n * `invalid_grant`, unspent: its resource redeems it. Any client\n * authentication failure is a 401 for a mirrored client. The answer is\n * a `typ: client` token with the code's scope and store, and, when the\n * client may refresh, an opaque `frt_` refresh token.\n *\n * A `frt_` refresh token is judged before its client, as Rails judges\n * it: unknown is `invalid_grant`; a confidential client's token needs\n * that client's Basic credentials (401 `invalid_client`); a public\n * client's token must name its client in `client_id`\n * (`invalid_request` when it names none, `invalid_grant` when it\n * names another). A token presented again after it rotated is a\n * replay: its whole family is revoked, with every unexpired access\n * token issued from it, and the answer is `invalid_grant`. Two\n * attempts of one relay operation that race, one finding the token\n * the other just spent, are one refresh: the loser is answered 503\n * `temporarily_unavailable` and the family stands.\n *\n * A client mirrored from Rails is answered only through Rails' relay:\n * every grant for one must carry Rails' own credential in\n * `Fluid-Relay-Authorization` (services/identity/README.md, \"Rate\n * limits\"), since Rails checks the client against its current\n * record first. Asked directly, the answer is 400\n * `unauthorized_client` and nothing is spent. A requested `scope` may narrow the token's\n * but never widen it (`invalid_scope`). A refresh request that names no\n * client and carries no `frt_` token answers `invalid_request` when the\n * token is missing and `invalid_grant` otherwise.\n *\n */\nexport const createToken = <ThrowOnError extends boolean = true>(\n options: Options<CreateTokenData, ThrowOnError>,\n): RequestResult<\n CreateTokenResponses,\n CreateTokenErrors,\n ThrowOnError,\n \"data\"\n> =>\n options.client.post<\n CreateTokenResponses,\n CreateTokenErrors,\n ThrowOnError,\n \"data\"\n >({\n ...urlSearchParamsBodySerializer,\n responseStyle: \"data\",\n security: [{ scheme: \"basic\", type: \"http\" }],\n url: \"/token\",\n ...options,\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n ...options.headers,\n },\n });\n\n/**\n * Session facts for the presented principal token\n *\n * Returns the facts a first-party app may show or branch on, so that no\n * app decodes a token (Phase 006 FR-1). Answers 401 for an expired or\n * malformed token, a client token, or a token whose session was revoked.\n * Browser callers on a registered client origin are allowed by CORS.\n *\n */\nexport const getUserinfo = <ThrowOnError extends boolean = true>(\n options: Options<GetUserinfoData, ThrowOnError>,\n): RequestResult<\n GetUserinfoResponses,\n GetUserinfoErrors,\n ThrowOnError,\n \"data\"\n> =>\n options.client.get<\n GetUserinfoResponses,\n GetUserinfoErrors,\n ThrowOnError,\n \"data\"\n >({\n responseStyle: \"data\",\n security: [{ scheme: \"bearer\", type: \"http\" }],\n url: \"/userinfo\",\n ...options,\n });\n\n/**\n * Rails' MCP token endpoint, served here\n *\n * `POST /api/mcp/v1/token` in Rails, which the Rails proxy forwards here\n * (Phase 008 FR-6), with Mcp::TokenMinter's and Mcp::Delegator's rules\n * and order. Every token is `typ: resource` with `aud` the configured\n * MCP audience (`IDENTITY_MCP_AUDIENCE`), never `fluid-api`.\n *\n * The client authenticates as Rails' OAuth::ClientAuthenticator does: a\n * confidential client presents its secret on the channel it registered\n * for, Basic (`client_secret_basic`) or the body (`client_id` and\n * `client_secret`, `client_secret_post`), and a public client sends\n * `client_id`; any failure is 401 `invalid_client`. Then:\n *\n * - `authorization_code` redeems a code Rails' MCP consent screen minted\n * through `POST /internal/oauth/codes` for a mirrored MCP\n * authorization. `resource` must be the MCP audience\n * (`invalid_target`); the code must carry an S256 challenge and the\n * verifier must match `[A-Za-z0-9-._~]{43,128}`; the authorization\n * must be mirrored, unrevoked, a person's, and that person active in\n * the directory; the code's scope must include `mcp:access` and be\n * within what the client may hold. Every refusal is `invalid_grant`.\n * The answer is an MCP token for the person, and a `frt_` refresh\n * token when the client may refresh.\n * - `refresh_token` rotates such a refresh token, under the same live\n * checks. A rotated one presented again ends its family. A wider\n * scope is `invalid_scope`.\n * - token exchange is the MCP gateway's\n * (`IDENTITY_MCP_GATEWAY_CLIENT_ID`, a confidential client): it\n * exchanges a person's MCP token for an analytics token for one\n * company (`resource` the analytics audience, `company_id`, `scope`\n * including `analytics:access`). The analytics token carries only\n * what the person's analytics and operator consents allow in that\n * company now, the person being active and, for the operator\n * consent, an operator (`root_admin`), lives at most five minutes and\n * never past the MCP token, and names the gateway in `act`.\n * Narrowing either consent revokes it. Every refusal is\n * `access_denied`.\n * - any other grant is `unsupported_grant_type`.\n *\n */\nexport const createMcpToken = <ThrowOnError extends boolean = true>(\n options: Options<CreateMcpTokenData, ThrowOnError>,\n): RequestResult<\n CreateMcpTokenResponses,\n CreateMcpTokenErrors,\n ThrowOnError,\n \"data\"\n> =>\n options.client.post<\n CreateMcpTokenResponses,\n CreateMcpTokenErrors,\n ThrowOnError,\n \"data\"\n >({\n responseStyle: \"data\",\n security: [{ scheme: \"basic\", type: \"http\" }],\n url: \"/resources/mcp/token\",\n ...options,\n headers: {\n \"Content-Type\": \"application/json\",\n ...options.headers,\n },\n });\n\n/**\n * Rails' analytics query access token endpoint, served here\n *\n * `POST /api/analytics/query-access/v1/token` in Rails, which the Rails\n * proxy forwards here (Phase 008 FR-6), with\n * Analytics::QueryAccess::TokenMinter's rules and order. Every token is\n * `typ: resource` with `aud` the configured analytics audience\n * (`IDENTITY_ANALYTICS_AUDIENCE`) and `org` the company.\n *\n * The client authenticates as at `/resources/mcp/token`, Basic or the\n * body on the channel it registered for. A failure is 401\n * `invalid_client` with `WWW-Authenticate: Basic realm=\"Analytics\"`. `resource` must be the\n * analytics audience (`invalid_target`). Then:\n *\n * - `authorization_code` redeems a code Rails' analytics consent screen\n * minted for a person's mirrored analytics consent, bound to the\n * consent's company. The consent must give access, the person must\n * be an active, unlocked member of the active company and not\n * flushed since the code was minted, and the code's scope must\n * include `analytics:access` and be within the client's scope and\n * the consent's. Every refusal is `invalid_grant`.\n * - `refresh_token` rotates its refresh token under the same live\n * checks; once the company withdraws the consent it is refused.\n * - `client_credentials` is a droplet's: a confidential client asking\n * for `company_id` where a mirrored analytics consent names one of\n * its active installations. The token lives fifteen minutes and has\n * no refresh token.\n * - Rails' installation bearer grant\n * (`urn:fluid:params:oauth:grant-type:installation`) presents a\n * Rails installation credential and stays in Rails; it is answered\n * `unsupported_grant_type` here.\n *\n */\nexport const createAnalyticsToken = <ThrowOnError extends boolean = true>(\n options: Options<CreateAnalyticsTokenData, ThrowOnError>,\n): RequestResult<\n CreateAnalyticsTokenResponses,\n CreateAnalyticsTokenErrors,\n ThrowOnError,\n \"data\"\n> =>\n options.client.post<\n CreateAnalyticsTokenResponses,\n CreateAnalyticsTokenErrors,\n ThrowOnError,\n \"data\"\n >({\n responseStyle: \"data\",\n security: [{ scheme: \"basic\", type: \"http\" }],\n url: \"/resources/analytics/token\",\n ...options,\n headers: {\n \"Content-Type\": \"application/json\",\n ...options.headers,\n },\n });\n\n/**\n * RFC 7662 introspection\n *\n * Two kinds of caller. This service's own registrations -- its resource\n * servers -- authenticate with HTTP Basic and may ask about any token.\n * Any other client -- one created through the lifecycle API, one\n * mirrored from Rails (whose v202610 introspection Rails relays here),\n * or a public client naming itself with `client_id` -- may ask only\n * about a token issued to it; every other token reads `active: false`,\n * so no client can probe another's tokens.\n *\n * A v202610 refresh token (`frt_`) is introspected from its family:\n * active while it is unspent, unrevoked, and unexpired. `token_type`\n * and `iss` are the members Rails' v202610 introspection answers with.\n *\n */\nexport const introspectToken = <ThrowOnError extends boolean = true>(\n options: Options<IntrospectTokenData, ThrowOnError>,\n): RequestResult<\n IntrospectTokenResponses,\n IntrospectTokenErrors,\n ThrowOnError,\n \"data\"\n> =>\n options.client.post<\n IntrospectTokenResponses,\n IntrospectTokenErrors,\n ThrowOnError,\n \"data\"\n >({\n ...urlSearchParamsBodySerializer,\n responseStyle: \"data\",\n security: [{ scheme: \"basic\", type: \"http\" }],\n url: \"/introspect\",\n ...options,\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n ...options.headers,\n },\n });\n\n/**\n * RFC 7009 revocation\n *\n * Revoking a refresh token revokes its session, which is how sign-out\n * ends every app on that session at once (INV-13). Revoking a v202610\n * refresh token (`frt_`) ends its family with every access token issued\n * from it, as Rails revokes one (`OAuth::TokenRevoker#revoke_family`).\n *\n * The token must have been issued to the calling client (RFC 7009\n * section 2.1). A caller that presents another client's token is\n * answered `200` with nothing revoked, exactly as an unknown token is:\n * telling the two apart would turn this endpoint into an oracle for\n * which token strings are live. The refusal is audited.\n *\n * Two ways to call it. A confidential client authenticates with HTTP\n * Basic. A public client -- a browser or native app signing out, which\n * has no secret to present -- names itself with `client_id` and proves\n * itself with the token: a public caller may revoke only a token\n * already issued to that same client. A browser client may omit `token`\n * and send its per-client refresh cookie instead.\n *\n * A request authenticated by that cookie must carry an `Origin`\n * registered for the client named in `client_id`, derived from its\n * redirect URIs. A missing, `null`, or non-matching Origin is refused\n * with `400 invalid_request`, the cookie is never consulted and nothing\n * is revoked. Without that rule the `SameSite=None` refresh cookie\n * would make sign-out forgeable: a cross-site form post, which needs no\n * CORS preflight, could end anyone's identity session. The refusal is\n * an error rather than the `200` a non-owner gets, because no token is\n * ever looked up, so it discloses nothing.\n *\n */\nexport const revokeToken = <ThrowOnError extends boolean = true>(\n options: Options<RevokeTokenData, ThrowOnError>,\n): RequestResult<\n RevokeTokenResponses,\n RevokeTokenErrors,\n ThrowOnError,\n \"data\"\n> =>\n options.client.post<\n RevokeTokenResponses,\n RevokeTokenErrors,\n ThrowOnError,\n \"data\"\n >({\n ...urlSearchParamsBodySerializer,\n responseStyle: \"data\",\n security: [{ scheme: \"basic\", type: \"http\" }],\n url: \"/revoke\",\n ...options,\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n ...options.headers,\n },\n });\n\n/**\n * Sign out of every app on the identity session\n *\n * Phase 007 FR-6. An app sends the browser here as a top-level\n * navigation; it is the one request that carries both the identity\n * session cookie and every app's refresh cookie. It revokes the session\n * the cookie names, which ends it for every client: each app's next\n * refresh is refused with `invalid_grant`, silent bootstrap answers\n * `login_required`, and introspection reports its tokens inactive. It\n * expires the identity cookie and every `__Host-fluid_refresh_*` cookie\n * the browser sent, and records a `session_revoked` audit row with\n * reason `sign_out`.\n *\n * Every session the browser's cookies name is ended: the identity\n * cookie's and each refresh cookie's, since a browser can keep a live\n * refresh cookie after its identity cookie has gone. When those sessions\n * carry upstream authenticator sessions -- AuthKit sessions, for a\n * WorkOS sign-in -- the browser is sent through WorkOS's logout for each\n * in turn, every one returning to `/signed-out`; otherwise the next\n * AuthKit visit would sign the person straight back in. The destination\n * and the logouts still to do cross those hops in the\n * `__Host-fluid_signout` cookie, and the destination is checked against\n * the registry again each time it comes back.\n *\n * The final destination is `post_logout_redirect_uri` with `state`\n * added, and it must equal one of the client's registered redirect URIs\n * exactly. Without it the signed-out page is shown. An unknown client,\n * an unregistered URI, or an overlong `state` is a `400` that never\n * redirects and ends nothing. No cookie, or a cookie naming no live\n * session, is still a sign-out: the browser goes where it asked to.\n *\n * Ending a session also lists every access token issued on it, and\n * every token exchanged from one, on `/revocations` until each would\n * have expired, so a resource server that polls the list rather than\n * introspecting stops accepting them at once.\n *\n * A session another path already ended still has its tokens listed.\n *\n * `id_token_hint` is accepted only in the body of `POST /signout`\n * (`signOutWithHint`), never in a URL, which is logged, kept in history,\n * and sent on in `Referer`. A `GET` that carries one is a `400` that\n * ends nothing.\n *\n * A failure to read or end the session is a `503` that clears nothing,\n * because answering \"signed out\" while every other app still refreshes\n * is the failure this endpoint exists to prevent.\n *\n */\nexport const signOut = <ThrowOnError extends boolean = true>(\n options: Options<SignOutData, ThrowOnError>,\n): RequestResult<SignOutResponses, SignOutErrors, ThrowOnError, \"data\"> =>\n options.client.get<SignOutResponses, SignOutErrors, ThrowOnError, \"data\">({\n responseStyle: \"data\",\n url: \"/signout\",\n ...options,\n });\n\n/**\n * Sign out, naming the session by id_token_hint if need be\n *\n * Phase 007 FR-6. The same sign-out as `GET /signout`, with its\n * parameters in a form body. An app posts it as a top-level form\n * submission. It is the one way to send `id_token_hint`, which ends a\n * session no cookie names: an app that keeps its refresh token in\n * memory, in a browser whose identity cookie has gone, posts the\n * `id_token` the token endpoint returned to it.\n *\n * The hint must be an ID token with this service's signature and issuer\n * whose audience is `client_id`, naming a session. The signature is\n * checked against every key this service has published, including one\n * since rotated out of the JWKS, because a session can refresh for long\n * after the key that signed its hint. It may have expired, so it works\n * however long ago it was issued. Anything else -- another client's ID token, an access\n * token, an `id_token_hint` in the URL on either method -- is a `400`\n * that ends nothing. The session it names is ended with any session the\n * cookies name.\n *\n * A cross-site form submission carries every app's refresh cookie\n * (SameSite=None) but not the identity session cookie (SameSite=Lax), so\n * an app on another site names its session with the hint.\n *\n */\nexport const signOutWithHint = <ThrowOnError extends boolean = true>(\n options: Options<SignOutWithHintData, ThrowOnError>,\n): RequestResult<\n SignOutWithHintResponses,\n SignOutWithHintErrors,\n ThrowOnError,\n \"data\"\n> =>\n options.client.post<\n SignOutWithHintResponses,\n SignOutWithHintErrors,\n ThrowOnError,\n \"data\"\n >({\n ...urlSearchParamsBodySerializer,\n responseStyle: \"data\",\n url: \"/signout\",\n ...options,\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n ...options.headers,\n },\n });\n\n/**\n * Where the upstream logout returns the browser\n *\n * The one sign-out URI each WorkOS manifest registers\n * (`config/workos*.yaml`, `redirects.sign_out_uris`). It reads the\n * `__Host-fluid_signout` cookie `/signout` set. While upstream logouts\n * remain it sends the browser through the next, with the rest in a new\n * cookie; then it checks the destination against the registry again and\n * redirects there. A missing, unreadable, or no longer registered\n * destination shows the signed-out page.\n *\n */\nexport const signedOut = <ThrowOnError extends boolean = true>(\n options: Options<SignedOutData, ThrowOnError>,\n): RequestResult<SignedOutResponses, unknown, ThrowOnError, \"data\"> =>\n options.client.get<SignedOutResponses, unknown, ThrowOnError, \"data\">({\n responseStyle: \"data\",\n url: \"/signed-out\",\n ...options,\n });\n\n/**\n * The shopper sign-in's email page\n *\n * Phase 007 FR-5. Where `/authorize` with `login_kind=shopper` sends the\n * browser, naming the parked request in `request`. Served only where a\n * code can be delivered. The pages are HTML for a person, are never\n * cached or framed, and carry no credential.\n *\n */\nexport const getShopperSignIn = <ThrowOnError extends boolean = true>(\n options: Options<GetShopperSignInData, ThrowOnError>,\n): RequestResult<\n GetShopperSignInResponses,\n GetShopperSignInErrors,\n ThrowOnError,\n \"data\"\n> =>\n options.client.get<\n GetShopperSignInResponses,\n GetShopperSignInErrors,\n ThrowOnError,\n \"data\"\n >({\n responseStyle: \"data\",\n url: \"/shopper/sign-in\",\n ...options,\n });\n\n/**\n * Send a one-time code to a shopper\n *\n * Sends a six-digit code to the address when it belongs to exactly one\n * active person holding an active, unlocked customer membership in the\n * request's company, and that company is active and carries the\n * identity sign-in flag. The address is matched ignoring case. Every\n * address is answered with the same code form, so the page cannot be\n * used to learn who shops where, and a refusal is audited without the\n * address. The page answers before the address is looked up: the\n * lookup, the code, and the email happen afterwards, so the page does\n * the same work and takes the same time for every address, and a send\n * that fails, or a code the issue limit refuses, shows the same page as\n * one that succeeds (each is audited). A code lives ten minutes; a person is sent at most five in\n * fifteen minutes, counted across every sign-in they start, and the\n * newest code for a request supersedes the older ones.\n *\n */\nexport const sendShopperCode = <ThrowOnError extends boolean = true>(\n options: Options<SendShopperCodeData, ThrowOnError>,\n): RequestResult<\n SendShopperCodeResponses,\n SendShopperCodeErrors,\n ThrowOnError,\n \"data\"\n> =>\n options.client.post<\n SendShopperCodeResponses,\n SendShopperCodeErrors,\n ThrowOnError,\n \"data\"\n >({\n ...urlSearchParamsBodySerializer,\n responseStyle: \"data\",\n url: \"/shopper/sign-in/code\",\n ...options,\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n ...options.headers,\n },\n });\n\n/**\n * Check a shopper's code and complete the sign-in\n *\n * A right code completes the sign-in the way every authenticator's\n * completion does: an identity session is opened, `auth_time` is\n * written, the session cookie is set, and the browser is redirected to\n * the app's registered redirect URI with a single-use `code` and its\n * `state`. The token that code redeems for names the person as\n * `urn:fluid:user:<users.uuid_v7>` with the company in `org`, `roles`\n * `[customer]`, and `amr` `[otp]`; Rails resolves it to the person's\n * member of that company. No WorkOS call is made at any step (INV-7.6).\n *\n * The code is spent and the parked request claimed in one transaction,\n * so a request that has expired is a `400` that spends nothing. A\n * wrong, expired, superseded, or already used code is answered with\n * the code form again and a `400`, the same for each. A request takes\n * five wrong codes before even the right one is refused. Whether the\n * person may still sign in is read again when the code is accepted: a\n * lock, a deactivation, or a cleared flag since the code was sent is a\n * `403` and issues no code.\n *\n */\nexport const verifyShopperCode = <ThrowOnError extends boolean = true>(\n options: Options<VerifyShopperCodeData, ThrowOnError>,\n): RequestResult<unknown, VerifyShopperCodeErrors, ThrowOnError, \"data\"> =>\n options.client.post<unknown, VerifyShopperCodeErrors, ThrowOnError, \"data\">({\n ...urlSearchParamsBodySerializer,\n responseStyle: \"data\",\n url: \"/shopper/sign-in/verify\",\n ...options,\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n ...options.headers,\n },\n });\n\n/**\n * Start a shopper's code sign-in from a browser app, as JSON\n *\n * The JSON twin of `/authorize` with `login_kind=shopper` followed by\n * `/shopper/sign-in/code`, for an app that shows the email and code\n * boxes itself instead of sending the browser through identity's pages.\n * The request is validated exactly as `/authorize` validates a shopper\n * request: the client is registered and may use the code grant, the\n * `redirect_uri` matches one of its registered URIs exactly, PKCE\n * `S256` is present, `state` is non-empty, `org` names the company, and\n * a code can be delivered. A uuid `org` is read in either case.\n *\n * The request is parked bound to this caller. The body carries the\n * parked request's handle in `request` and a secret in `binding`. The\n * pair replaces the `__Host-` binding cookie of the HTML pages, which a\n * cross-site page cannot rely on; `/shopper/v1/verify` refuses the\n * handle without the secret. Keep the secret in memory and send it\n * nowhere but to `/shopper/v1/verify`.\n *\n * The answer is `202` with the same shape for every address. Whether a\n * code is sent is decided after the response, as it is for\n * `/shopper/sign-in/code`: a code goes to an address that belongs to\n * exactly one active person holding an active, unlocked customer\n * membership in the company, which is active and carries the identity\n * sign-in flag. The same per-address send limit applies. The response\n * never says whether the address was found, a code was sent, or a limit\n * refused it.\n *\n * Credentialed CORS. The preflight is answered for the origins of\n * registered browser clients. The actual request must carry an `Origin`\n * that is an origin of the client named in `client_id`, derived from\n * that client's redirect URIs; a missing, `null`, or other `Origin` is a\n * `403` and nothing is parked. Publishable clients are excluded and are\n * refused the same way. `Access-Control-Allow-Origin` is echoed only for\n * that origin, with `Access-Control-Allow-Credentials: true`.\n *\n * Served only when `SHOPPER_INLINE_ENABLED` is on. Where it is off the\n * path is not registered and answers `404`.\n *\n */\nexport const startShopperSignIn = <ThrowOnError extends boolean = true>(\n options: Options<StartShopperSignInData, ThrowOnError>,\n): RequestResult<\n StartShopperSignInResponses,\n StartShopperSignInErrors,\n ThrowOnError,\n \"data\"\n> =>\n options.client.post<\n StartShopperSignInResponses,\n StartShopperSignInErrors,\n ThrowOnError,\n \"data\"\n >({\n responseStyle: \"data\",\n url: \"/shopper/v1/start\",\n ...options,\n headers: {\n \"Content-Type\": \"application/json\",\n ...options.headers,\n },\n });\n\n/**\n * Check a shopper's code and complete the sign-in, as JSON\n *\n * The JSON twin of `/shopper/sign-in/verify`. A right code completes the\n * sign-in the way every authenticator's completion does: an identity\n * session is opened and `auth_time` is written, and the identity session\n * cookie is set on this response. A browser may not store that cookie\n * for a cross-site caller (`SameSite=Lax`, and Safari's tracking\n * prevention), so a client must not depend on it: the returned `code`\n * is the sole result, and refresh-by-cookie is not promised cross-site.\n * Instead of a redirect, the body carries the\n * app's registered `redirect_uri`, the single-use authorization `code`\n * and the `state` of the request; the app redeems the code at `/token`\n * with its PKCE verifier. The token names the person as\n * `urn:fluid:user:<users.uuid_v7>` with the company in `org`, `roles`\n * `[customer]`, and `amr` `[otp]`. No WorkOS call is made at any step.\n *\n * The `request` and `binding` pair must be the one `/shopper/v1/start`\n * returned; a handle with a wrong or missing secret is answered as an\n * expired request. The code is spent and the parked request claimed in\n * one transaction. A wrong, expired, superseded, or already used code,\n * or one refused after exhausted attempts, is `invalid_code`; a new code\n * needs a new `/shopper/v1/start`; an unknown, expired, or already claimed request is\n * `expired`. A request takes five wrong codes before even the right one\n * is refused. Whether the person may still sign in is read again when\n * the code is accepted: a lock, a deactivation, or a cleared flag since\n * the code was sent is `403` `not_standing` and issues no code. The\n * refusal never says which check failed.\n *\n * Credentialed CORS, with the same origin rules as `/shopper/v1/start`,\n * applied to the client of the parked request. Served only when\n * `SHOPPER_INLINE_ENABLED` is on; otherwise `404`.\n *\n */\nexport const verifyShopperInline = <ThrowOnError extends boolean = true>(\n options: Options<VerifyShopperInlineData, ThrowOnError>,\n): RequestResult<\n VerifyShopperInlineResponses,\n VerifyShopperInlineErrors,\n ThrowOnError,\n \"data\"\n> =>\n options.client.post<\n VerifyShopperInlineResponses,\n VerifyShopperInlineErrors,\n ThrowOnError,\n \"data\"\n >({\n responseStyle: \"data\",\n url: \"/shopper/v1/verify\",\n ...options,\n headers: {\n \"Content-Type\": \"application/json\",\n ...options.headers,\n },\n });\n\n/**\n * Revocation list polled by resource servers\n *\n * Each entry expires once the longest token it could affect has expired (FR-7).\n *\n * The list names every tenant's clients, so only this service's own\n * registrations may read it. A client created through the lifecycle API\n * or mirrored from Rails is answered 401, exactly as an unknown client\n * is.\n *\n */\nexport const listRevocations = <ThrowOnError extends boolean = true>(\n options: Options<ListRevocationsData, ThrowOnError>,\n): RequestResult<\n ListRevocationsResponses,\n ListRevocationsErrors,\n ThrowOnError,\n \"data\"\n> =>\n options.client.get<\n ListRevocationsResponses,\n ListRevocationsErrors,\n ThrowOnError,\n \"data\"\n >({\n responseStyle: \"data\",\n security: [{ scheme: \"basic\", type: \"http\" }],\n url: \"/revocations\",\n ...options,\n });\n\n/**\n * End one session on behalf of the principal that owns it\n *\n * Ends the session through the path a sync message uses, so its tokens\n * are listed on `/revocations` and its refresh chains are consumed, with\n * the audit reason `site_sign_out`. A session already ended answers 204.\n * A session that does not exist and one that belongs to another\n * principal both answer 404 with no body.\n *\n */\nexport const revokeSession = <ThrowOnError extends boolean = true>(\n options: Options<RevokeSessionData, ThrowOnError>,\n): RequestResult<\n RevokeSessionResponses,\n RevokeSessionErrors,\n ThrowOnError,\n \"data\"\n> =>\n options.client.post<\n RevokeSessionResponses,\n RevokeSessionErrors,\n ThrowOnError,\n \"data\"\n >({\n responseStyle: \"data\",\n security: [{ scheme: \"basic\", type: \"http\" }],\n url: \"/internal/sessions/{sid}/revoke\",\n ...options,\n headers: {\n \"Content-Type\": \"application/json\",\n ...options.headers,\n },\n });\n\n/**\n * Upsert one company's full identity state\n *\n * Rails is the source of truth and pushes state, never a delta\n * (INV-5.1). The body carries the company's whole identity state and a\n * version that rises monotonically per record; the service applies a\n * message only when its version is above the stored one, so duplicates,\n * retries, and reordered deliveries converge on Rails' state\n * (INV-5.3). Every message, applied or ignored, is recorded.\n *\n * A message that makes the company inactive, or that carries a\n * `flushed_at` at or after a session's `auth_time`, revokes those\n * sessions in the same transaction as the upsert, before the response\n * (INV-5.7, AC-7.2). A session is in scope when the company is the one\n * it was opened in or any company its sign-in granted, since it can\n * mint for each of them. Independently of the revocation, every mint\n * scoped to a company refuses a session whose `auth_time` is at or\n * before that company's `flushed_at`.\n *\n */\nexport const syncCompany = <ThrowOnError extends boolean = true>(\n options: Options<SyncCompanyData, ThrowOnError>,\n): RequestResult<\n SyncCompanyResponses,\n SyncCompanyErrors,\n ThrowOnError,\n \"data\"\n> =>\n options.client.put<\n SyncCompanyResponses,\n SyncCompanyErrors,\n ThrowOnError,\n \"data\"\n >({\n responseStyle: \"data\",\n security: [{ scheme: \"basic\", type: \"http\" }],\n url: \"/internal/sync/companies\",\n ...options,\n headers: {\n \"Content-Type\": \"application/json\",\n ...options.headers,\n },\n });\n\n/**\n * Upsert one principal's full identity state\n *\n * Same versioning and recording rules as `/internal/sync/companies`. A\n * principal that arrives inactive revokes every session of that person\n * -- whichever company it is scoped to, and platform sessions with none\n * -- and their refresh chains, inside the upsert transaction and before\n * this response. On a company with `identity_sign_in`, an inactive\n * principal is also not entitled to it, whatever its memberships say.\n * `root_admin` is informational in this phase: the reconciliation\n * reports read it, and nothing that mints does.\n *\n */\nexport const syncPrincipal = <ThrowOnError extends boolean = true>(\n options: Options<SyncPrincipalData, ThrowOnError>,\n): RequestResult<\n SyncPrincipalResponses,\n SyncPrincipalErrors,\n ThrowOnError,\n \"data\"\n> =>\n options.client.put<\n SyncPrincipalResponses,\n SyncPrincipalErrors,\n ThrowOnError,\n \"data\"\n >({\n responseStyle: \"data\",\n security: [{ scheme: \"basic\", type: \"http\" }],\n url: \"/internal/sync/principals\",\n ...options,\n headers: {\n \"Content-Type\": \"application/json\",\n ...options.headers,\n },\n });\n\n/**\n * Upsert one membership's full identity state\n *\n * Same versioning and recording rules as `/internal/sync/companies`. A\n * membership that arrives locked, inactive, or with a `flushed_at` at\n * or after a session's `auth_time` revokes that principal's sessions\n * that were opened in or granted that company, and their refresh\n * chains, inside the upsert\n * transaction and before this response (INV-5.7, AC-7.1, AC-7.3). A\n * transaction that rolls back revokes nothing (AC-7.5).\n *\n */\nexport const syncMembership = <ThrowOnError extends boolean = true>(\n options: Options<SyncMembershipData, ThrowOnError>,\n): RequestResult<\n SyncMembershipResponses,\n SyncMembershipErrors,\n ThrowOnError,\n \"data\"\n> =>\n options.client.put<\n SyncMembershipResponses,\n SyncMembershipErrors,\n ThrowOnError,\n \"data\"\n >({\n responseStyle: \"data\",\n security: [{ scheme: \"basic\", type: \"http\" }],\n url: \"/internal/sync/memberships\",\n ...options,\n headers: {\n \"Content-Type\": \"application/json\",\n ...options.headers,\n },\n });\n\n/**\n * Set, replace, or clear a company's WorkOS organization\n *\n * The WorkOS half of the directory, kept apart from Rails' half on\n * purpose.\n *\n * Rails owns a company's name, active state, sign-in flag, session\n * setting, and flush, and it versions them. This service owns the WorkOS\n * organization id: Rails may not talk to WorkOS at all (Phase 003\n * INV-3.4), never learns the id, and never sends it. Two owners writing\n * through one version gate cannot both win -- a mapping written at a\n * higher version makes Rails' next message look stale, and one written\n * without advancing the version is itself discarded -- so the mapping\n * has its own operation and no version at all.\n *\n * It sets, replaces, or clears the id on a record that already exists,\n * and does nothing else: it does not move the Rails version, does not\n * touch `active`, `locked`, or any flush, and never revokes a session.\n * A record this service has not been told about answers 404, because a\n * mapping is a fact about a record and never the thing that creates one.\n * Setting the id a record already holds is a no-op that answers\n * `unchanged`.\n *\n * Clearing is allowed, and is how a mapping ends: WorkOS deletes an\n * organization or a user and the events handler says so. Every change is\n * recorded in the audit trail; a no-op is not.\n *\n */\nexport const mapCompanyToWorkos = <ThrowOnError extends boolean = true>(\n options: Options<MapCompanyToWorkosData, ThrowOnError>,\n): RequestResult<\n MapCompanyToWorkosResponses,\n MapCompanyToWorkosErrors,\n ThrowOnError,\n \"data\"\n> =>\n options.client.put<\n MapCompanyToWorkosResponses,\n MapCompanyToWorkosErrors,\n ThrowOnError,\n \"data\"\n >({\n responseStyle: \"data\",\n security: [{ scheme: \"basic\", type: \"http\" }],\n url: \"/internal/sync/companies/{company_id}/workos\",\n ...options,\n headers: {\n \"Content-Type\": \"application/json\",\n ...options.headers,\n },\n });\n\n/**\n * Set, replace, or clear a principal's WorkOS user\n *\n * Same rules as the company mapping; see that operation.\n */\nexport const mapPrincipalToWorkos = <ThrowOnError extends boolean = true>(\n options: Options<MapPrincipalToWorkosData, ThrowOnError>,\n): RequestResult<\n MapPrincipalToWorkosResponses,\n MapPrincipalToWorkosErrors,\n ThrowOnError,\n \"data\"\n> =>\n options.client.put<\n MapPrincipalToWorkosResponses,\n MapPrincipalToWorkosErrors,\n ThrowOnError,\n \"data\"\n >({\n responseStyle: \"data\",\n security: [{ scheme: \"basic\", type: \"http\" }],\n url: \"/internal/sync/principals/{principal_id}/workos\",\n ...options,\n headers: {\n \"Content-Type\": \"application/json\",\n ...options.headers,\n },\n });\n\n/**\n * Set, replace, or clear a membership's WorkOS organization membership\n *\n * Same rules as the company mapping; see that operation.\n */\nexport const mapMembershipToWorkos = <ThrowOnError extends boolean = true>(\n options: Options<MapMembershipToWorkosData, ThrowOnError>,\n): RequestResult<\n MapMembershipToWorkosResponses,\n MapMembershipToWorkosErrors,\n ThrowOnError,\n \"data\"\n> =>\n options.client.put<\n MapMembershipToWorkosResponses,\n MapMembershipToWorkosErrors,\n ThrowOnError,\n \"data\"\n >({\n responseStyle: \"data\",\n security: [{ scheme: \"basic\", type: \"http\" }],\n url: \"/internal/sync/memberships/{principal_id}/{company_id}/workos\",\n ...options,\n headers: {\n \"Content-Type\": \"application/json\",\n ...options.headers,\n },\n });\n\n/**\n * Counts and a version checksum for one company\n *\n * What reconciliation compares Rails against (FR-3). Two checksums,\n * built by one rule.\n *\n * **The rule.** Each checksum is the SHA-256 of a UTF-8 string, written\n * as 64 lower-case hex characters. The string is the checksum's lines\n * sorted by `principal_id` ascending (the lower-case canonical uuid,\n * compared as text), joined with a single line feed (`\\n`, U+000A)\n * between consecutive lines, with no line feed after the last line and\n * none before the first. There is no header and no trailing\n * whitespace. When there are no lines the string is empty, so the\n * checksum is\n * `e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855`.\n * Within a line, fields are separated by `|` (U+007C). Booleans are\n * written `true` or `false`, lower case. Integers are written in base\n * ten with no sign, no leading zeros, and no separators. A uuid is\n * written lower case with hyphens, 8-4-4-4-12.\n *\n * **`membership_checksum`** has one line per **active** membership of\n * the company:\n *\n * principal_id|role|rep_standing|shopper_standing|active|locked|flushed_at|version\n *\n * where `rep_standing` and `shopper_standing` are each `true` or\n * `false`, or the empty string when\n * the service has not been told it (Rails, which always sends it, never\n * writes that, so such a row reads as a mismatch and is re-sent),\n * `flushed_at` is its Unix second count, or the empty string when it is\n * null, and `version` is the membership's version.\n *\n * **`principal_checksum`** has one line per principal holding an\n * **active** membership of the company (the same memberships the first\n * checksum covers, so there are as many lines as there):\n *\n * principal_id|active|root_admin|version\n *\n * where `active`, `root_admin`, and `version` are the principal's own,\n * not the membership's. A principal named by an active membership that\n * no principal message has created yet renders its three fields empty,\n * `principal_id|||`; Rails, which holds every principal, never writes\n * that line, so it reads as a mismatch and the principal is re-sent.\n * `active_principal_count` is the number of lines. This checksum is what makes a lost principal\n * message -- a deactivation, a change of `root_admin` -- visible, which\n * the membership checksum cannot see.\n *\n * An unknown company answers 404 rather than an empty digest, so a\n * company Rails has never synced is not mistaken for one that matches.\n *\n * Inactive memberships are deliberately outside the checksum. Rails\n * sends a membership once as inactive and then stops counting it -- a\n * rep demoted to customer leaves the set Rails can enumerate -- while\n * this service keeps the row. A checksum over every row would differ\n * permanently after the first deactivation and reconciliation would\n * re-send that company on every pass without converging.\n *\n * Nothing the identity service owns is in the checksum either: the\n * WorkOS ids are absent from it, because Rails does not know them and a\n * checksum Rails cannot compute never matches.\n *\n * `active_membership_count` counts exactly what the checksum covers and\n * is the comparable number. `membership_count` is this service's own\n * total, inactive rows included; it is an operational figure that Rails\n * cannot reproduce and should not compare.\n *\n */\nexport const getCompanyDigest = <ThrowOnError extends boolean = true>(\n options: Options<GetCompanyDigestData, ThrowOnError>,\n): RequestResult<\n GetCompanyDigestResponses,\n GetCompanyDigestErrors,\n ThrowOnError,\n \"data\"\n> =>\n options.client.get<\n GetCompanyDigestResponses,\n GetCompanyDigestErrors,\n ThrowOnError,\n \"data\"\n >({\n responseStyle: \"data\",\n security: [{ scheme: \"basic\", type: \"http\" }],\n url: \"/internal/sync/companies/{company_id}/digest\",\n ...options,\n });\n\n/**\n * The principals this service holds an active membership for in one company\n *\n * What the reconciliation sweep reads (FR-3). Rails can enumerate every\n * membership it still claims, but not one it has stopped claiming -- a\n * rep demoted to customer leaves no row that says it was ever a rep. So\n * after the digest still disagrees with the claimed set re-sent, Rails\n * asks this service which memberships it holds as active, and sends as\n * inactive only those it no longer claims. Without this list the only\n * thing Rails could do is re-send every customer of the company, which\n * in a large company is hundreds of thousands of messages about people\n * this service has never held.\n *\n * Only memberships whose `active` is true are listed, in `principal_id`\n * ascending order, a page at a time. `next_after` is the last\n * `principal_id` of the page when more follow, and null on the last\n * page, including a last page that is exactly full.\n *\n * Paging is by key: each page starts strictly after the `after` it was\n * given, and each page is read from one snapshot. A sync that commits\n * between two pages cannot shift rows under the caller the way an\n * offset would, so a principal that stays an active member for the\n * whole walk is listed exactly once. One that joins or leaves during\n * the walk is listed or not according to where it sorts relative to\n * the cursor; the next reconciliation pass sees it either way.\n *\n * An unknown company answers 404, as the digest does, and so does a\n * `company_id` that is not a uuid, since it names no company. An\n * `after` that is not a uuid, or a `limit` outside 1 to 500, is a 400.\n *\n */\nexport const listCompanyActiveMemberships = <\n ThrowOnError extends boolean = true,\n>(\n options: Options<ListCompanyActiveMembershipsData, ThrowOnError>,\n): RequestResult<\n ListCompanyActiveMembershipsResponses,\n ListCompanyActiveMembershipsErrors,\n ThrowOnError,\n \"data\"\n> =>\n options.client.get<\n ListCompanyActiveMembershipsResponses,\n ListCompanyActiveMembershipsErrors,\n ThrowOnError,\n \"data\"\n >({\n responseStyle: \"data\",\n security: [{ scheme: \"basic\", type: \"http\" }],\n url: \"/internal/sync/companies/{company_id}/memberships\",\n ...options,\n });\n\n/**\n * Whether a company is fully provisioned enough to be flagged\n *\n * INV-5.5 and AC-8.2. A company is eligible only when every active\n * admin and rep in it has a principal carrying a WorkOS user id and an\n * active membership carrying a WorkOS membership id, and the company\n * itself carries a WorkOS organization id. AC-4.5 adds the second list:\n * members of this company who also belong to a company that is not yet\n * eligible. Phase 007 refuses the flag while either list is non-empty.\n *\n * Phase 007 INV-7.2 and AC-1.4 add the shopper half, because the flag\n * switches a company's shoppers with its staff. Rails sends its own\n * counts as `shopper_members` and `memberless_customers`, and\n * `shopper_checksum` over who the first count's shoppers are; the\n * report compares the count and the checksum with the active shopper\n * memberships this service holds whose principal has been synced --\n * those whose role is `customer` or that carry `shopper_standing` --\n * and is ineligible with `shopper_members_not_synced` when either\n * differs (equal counts alone could hide a stale shopper standing in\n * for a missing one), with `memberless_customers` while the second\n * count is above zero (those customers have no shopper path here yet),\n * and with `shoppers_not_reported` when any of the three is absent. A\n * provisioning reason, when there is one, is still the one named.\n *\n * The report reads the synced directory only; it never calls WorkOS, so\n * it is as current as the last sync message and no more.\n *\n */\nexport const getCompanyEligibility = <ThrowOnError extends boolean = true>(\n options: Options<GetCompanyEligibilityData, ThrowOnError>,\n): RequestResult<\n GetCompanyEligibilityResponses,\n GetCompanyEligibilityErrors,\n ThrowOnError,\n \"data\"\n> =>\n options.client.get<\n GetCompanyEligibilityResponses,\n GetCompanyEligibilityErrors,\n ThrowOnError,\n \"data\"\n >({\n responseStyle: \"data\",\n security: [{ scheme: \"basic\", type: \"http\" }],\n url: \"/internal/sync/companies/{company_id}/eligibility\",\n ...options,\n });\n\n/**\n * Upsert one OAuth::Client registration's whole state\n *\n * Rails' OAuth client registrations, mirrored so that this service can\n * issue for them (Phase 008 FR-6). The same rules as the directory:\n * the whole state, a version that rises per record, duplicates and\n * stale versions ignored, every delivery recorded.\n *\n * The registration becomes a client of this service with the digest\n * Rails holds, so the client keeps the secret it has. It is given the\n * v202610 grants Rails allows it -- `authorization_code`,\n * `refresh_token`, and, for a confidential client, `client_credentials`\n * -- but never this service's `/authorize`, which refuses every\n * mirrored client: Rails' consent screens decide, and mint each code\n * through `POST /internal/oauth/codes`. A `deactivated_at` stops it\n * authenticating, and its refresh tokens with it. A `client_id` this service\n * registered itself is never overwritten and answers 400.\n *\n * The message that first carries a `deactivated_at` also ends the\n * tokens this service already issued to the client, as Rails'\n * deactivation does: it writes a (client, every org) entry to the\n * revocation list in its own transaction, before the response.\n *\n */\nexport const syncOAuthClient = <ThrowOnError extends boolean = true>(\n options: Options<SyncOAuthClientData, ThrowOnError>,\n): RequestResult<\n SyncOAuthClientResponses,\n SyncOAuthClientErrors,\n ThrowOnError,\n \"data\"\n> =>\n options.client.put<\n SyncOAuthClientResponses,\n SyncOAuthClientErrors,\n ThrowOnError,\n \"data\"\n >({\n responseStyle: \"data\",\n security: [{ scheme: \"basic\", type: \"http\" }],\n url: \"/internal/sync/oauth_clients\",\n ...options,\n headers: {\n \"Content-Type\": \"application/json\",\n ...options.headers,\n },\n });\n\n/**\n * Upsert one MCP authorization's whole state\n *\n * Mcp::Authorization, which the MCP grants at `/resources/mcp/token`\n * read (Phase 008 FR-6). Exactly one of `principal_id` and\n * `installation_id` is set. The message that first carries\n * `revoked_at` ends everything issued under the authorization in its\n * own transaction: its refresh tokens, and by jti every unexpired MCP\n * token and every analytics token exchanged from one.\n *\n */\nexport const syncOAuthAuthorization = <ThrowOnError extends boolean = true>(\n options: Options<SyncOAuthAuthorizationData, ThrowOnError>,\n): RequestResult<\n SyncOAuthAuthorizationResponses,\n SyncOAuthAuthorizationErrors,\n ThrowOnError,\n \"data\"\n> =>\n options.client.put<\n SyncOAuthAuthorizationResponses,\n SyncOAuthAuthorizationErrors,\n ThrowOnError,\n \"data\"\n >({\n responseStyle: \"data\",\n security: [{ scheme: \"basic\", type: \"http\" }],\n url: \"/internal/sync/oauth_authorizations\",\n ...options,\n headers: {\n \"Content-Type\": \"application/json\",\n ...options.headers,\n },\n });\n\n/**\n * Upsert one analytics or MCP operator consent's whole state\n *\n * Analytics::QueryGrant and Mcp::OperatorGrant, which the analytics and\n * MCP grants read (Phase 008 FR-6). Oversight requires access, as it\n * does in Rails. A message that takes access or oversight away ends\n * everything issued under the consent in its own transaction: its\n * refresh tokens, and by jti every unexpired analytics token, delegated\n * ones included, so no token outlives a scope it was given.\n *\n */\nexport const syncOAuthConsent = <ThrowOnError extends boolean = true>(\n options: Options<SyncOAuthConsentData, ThrowOnError>,\n): RequestResult<\n SyncOAuthConsentResponses,\n SyncOAuthConsentErrors,\n ThrowOnError,\n \"data\"\n> =>\n options.client.put<\n SyncOAuthConsentResponses,\n SyncOAuthConsentErrors,\n ThrowOnError,\n \"data\"\n >({\n responseStyle: \"data\",\n security: [{ scheme: \"basic\", type: \"http\" }],\n url: \"/internal/sync/oauth_consents\",\n ...options,\n headers: {\n \"Content-Type\": \"application/json\",\n ...options.headers,\n },\n });\n\n/**\n * Upsert one droplet installation's whole state\n *\n * Droplet::Installation, mirrored so that droplet tokens are issued\n * here (Phase 008 FR-7). A droplet's `client_credentials` token for a\n * store is issued only while an active installation of its client\n * exists there, and carries only scopes the installation grants. A\n * newly granted scope is mintable as soon as its message is applied.\n *\n * A message that takes anything away -- the installation goes\n * inactive, or a scope it granted is gone -- writes a (client, store)\n * entry to the revocation list in its own transaction, before the\n * response, so every resource server rejects the droplet's outstanding\n * token for that store within one refresh of the list (AC-7.2). The\n * entry is listed for one access token lifetime. An uninstall that\n * arrives before its install still writes one.\n *\n */\nexport const syncDropletInstallation = <ThrowOnError extends boolean = true>(\n options: Options<SyncDropletInstallationData, ThrowOnError>,\n): RequestResult<\n SyncDropletInstallationResponses,\n SyncDropletInstallationErrors,\n ThrowOnError,\n \"data\"\n> =>\n options.client.put<\n SyncDropletInstallationResponses,\n SyncDropletInstallationErrors,\n ThrowOnError,\n \"data\"\n >({\n responseStyle: \"data\",\n security: [{ scheme: \"basic\", type: \"http\" }],\n url: \"/internal/sync/droplet_installations\",\n ...options,\n headers: {\n \"Content-Type\": \"application/json\",\n ...options.headers,\n },\n });\n\n/**\n * Mint a v202610 authorization code for a consent Rails decided\n *\n * Rails keeps its authorize and consent screens (Phase 008 FR-6). When\n * a person consents, Rails asks for the code here and redirects the\n * browser to the client with it; the client redeems it at `/token`.\n * Only Rails' own registration holding `identity.grants` may call this,\n * and the code is in this response alone; only its digest is stored.\n * It expires in five minutes, as Rails' own codes do.\n *\n * The client must be one mirrored from Rails, active, and allowed the\n * `authorization_code` grant. `redirect_uri` must be one it registered,\n * byte for byte; every `scope` must be one it is allowed; a public\n * client's code must carry an S256 `code_challenge`, the only method\n * accepted. The owner is the client itself, or, for a code its resource\n * redeems, a mirrored MCP authorization or analytics consent that is\n * still in force: Rails mirrors that record before minting, and a 409\n * means it has not, or that it was revoked.\n *\n */\nexport const createAuthorizationCode = <ThrowOnError extends boolean = true>(\n options: Options<CreateAuthorizationCodeData, ThrowOnError>,\n): RequestResult<\n CreateAuthorizationCodeResponses,\n CreateAuthorizationCodeErrors,\n ThrowOnError,\n \"data\"\n> =>\n options.client.post<\n CreateAuthorizationCodeResponses,\n CreateAuthorizationCodeErrors,\n ThrowOnError,\n \"data\"\n >({\n responseStyle: \"data\",\n security: [{ scheme: \"basic\", type: \"http\" }],\n url: \"/internal/oauth/codes\",\n ...options,\n headers: {\n \"Content-Type\": \"application/json\",\n ...options.headers,\n },\n });\n\n/**\n * List one company's scoped clients\n *\n * Oldest first, revoked ones included, at most 500. No secret or digest is ever listed.\n */\nexport const listClients = <ThrowOnError extends boolean = true>(\n options: Options<ListClientsData, ThrowOnError>,\n): RequestResult<\n ListClientsResponses,\n ListClientsErrors,\n ThrowOnError,\n \"data\"\n> =>\n options.client.get<\n ListClientsResponses,\n ListClientsErrors,\n ThrowOnError,\n \"data\"\n >({\n responseStyle: \"data\",\n security: [{ scheme: \"basic\", type: \"http\" }],\n url: \"/internal/clients\",\n ...options,\n });\n\n/**\n * Create a scoped client for one company\n *\n * Creates the credential that replaces a company, partner, or public\n * token (Phase 008 FR-3). Every client is bound to the one company in\n * `org` and uses `client_credentials` only.\n *\n * A confidential client's secret is in this response and nowhere else,\n * ever: it is stored as a digest and no other operation returns it\n * (AC-3.4). A publishable client has no secret; it is held to its\n * allowed origins instead, and may hold read scopes only, plus the\n * names a public token could carry (AC-3.3).\n *\n * Scopes are `resource:verb`, the vocabulary Rails' scope check asks\n * for: `view` is spelled `read`, every write action `write`, and any\n * other action is its own verb. `scopes` is required and may be empty,\n * which is what a company-token successor for a company with no role\n * receives (AC-3.5).\n *\n */\nexport const createClient = <ThrowOnError extends boolean = true>(\n options: Options<CreateClientData, ThrowOnError>,\n): RequestResult<\n CreateClientResponses,\n CreateClientErrors,\n ThrowOnError,\n \"data\"\n> =>\n options.client.post<\n CreateClientResponses,\n CreateClientErrors,\n ThrowOnError,\n \"data\"\n >({\n responseStyle: \"data\",\n security: [{ scheme: \"basic\", type: \"http\" }],\n url: \"/internal/clients\",\n ...options,\n headers: {\n \"Content-Type\": \"application/json\",\n ...options.headers,\n },\n });\n\n/**\n * Read one scoped client\n */\nexport const getClient = <ThrowOnError extends boolean = true>(\n options: Options<GetClientData, ThrowOnError>,\n): RequestResult<GetClientResponses, GetClientErrors, ThrowOnError, \"data\"> =>\n options.client.get<GetClientResponses, GetClientErrors, ThrowOnError, \"data\">(\n {\n responseStyle: \"data\",\n security: [{ scheme: \"basic\", type: \"http\" }],\n url: \"/internal/clients/{client_id}\",\n ...options,\n },\n );\n\n/**\n * Replace a confidential client's secret\n *\n * Issues a new secret, shown in this response only. The secret it\n * replaces keeps authenticating for the rotation window,\n * `previous_secret_ttl` seconds (default 86400, at most 604800, 0 to\n * end it at once), so a client running in several places can move to\n * the new one without an outage. A second rotation inside the window\n * ends the oldest secret at once: only the one immediately replaced is\n * kept.\n *\n */\nexport const rotateClientSecret = <ThrowOnError extends boolean = true>(\n options: Options<RotateClientSecretData, ThrowOnError>,\n): RequestResult<\n RotateClientSecretResponses,\n RotateClientSecretErrors,\n ThrowOnError,\n \"data\"\n> =>\n options.client.post<\n RotateClientSecretResponses,\n RotateClientSecretErrors,\n ThrowOnError,\n \"data\"\n >({\n responseStyle: \"data\",\n security: [{ scheme: \"basic\", type: \"http\" }],\n url: \"/internal/clients/{client_id}/rotate\",\n ...options,\n headers: {\n \"Content-Type\": \"application/json\",\n ...options.headers,\n },\n });\n\n/**\n * End a scoped client for good\n *\n * The client stops authenticating at once and any rotation window\n * closes. A (client, every org) entry is added to the revocation list\n * in the same transaction and listed for one access token lifetime, so\n * resource servers reject its outstanding tokens within one refresh of\n * the list, with this service unreachable (AC-5.1). Revoking a revoked\n * client changes nothing and answers it as it is.\n *\n */\nexport const revokeClient = <ThrowOnError extends boolean = true>(\n options: Options<RevokeClientData, ThrowOnError>,\n): RequestResult<\n RevokeClientResponses,\n RevokeClientErrors,\n ThrowOnError,\n \"data\"\n> =>\n options.client.post<\n RevokeClientResponses,\n RevokeClientErrors,\n ThrowOnError,\n \"data\"\n >({\n responseStyle: \"data\",\n security: [{ scheme: \"basic\", type: \"http\" }],\n url: \"/internal/clients/{client_id}/revoke\",\n ...options,\n headers: {\n \"Content-Type\": \"application/json\",\n ...options.headers,\n },\n });\n\n/**\n * Development sign-in form\n */\nexport const getDevelopmentAuthenticator = <\n ThrowOnError extends boolean = true,\n>(\n options: Options<GetDevelopmentAuthenticatorData, ThrowOnError>,\n): RequestResult<\n GetDevelopmentAuthenticatorResponses,\n GetDevelopmentAuthenticatorErrors,\n ThrowOnError,\n \"data\"\n> =>\n options.client.get<\n GetDevelopmentAuthenticatorResponses,\n GetDevelopmentAuthenticatorErrors,\n ThrowOnError,\n \"data\"\n >({\n responseStyle: \"data\",\n url: \"/dev/authenticate\",\n ...options,\n });\n\n/**\n * Complete a development sign-in\n *\n * Creates the identity session with `auth_time` set to now, sets the\n * session cookie, and redirects to the pending request's redirect URI\n * with a code. This is the only interactive moment in this phase, so it\n * is the only place `auth_time` is set (AC-6.3).\n *\n */\nexport const completeDevelopmentAuthenticator = <\n ThrowOnError extends boolean = true,\n>(\n options: Options<CompleteDevelopmentAuthenticatorData, ThrowOnError>,\n): RequestResult<\n unknown,\n CompleteDevelopmentAuthenticatorErrors,\n ThrowOnError,\n \"data\"\n> =>\n options.client.post<\n unknown,\n CompleteDevelopmentAuthenticatorErrors,\n ThrowOnError,\n \"data\"\n >({\n ...urlSearchParamsBodySerializer,\n responseStyle: \"data\",\n url: \"/dev/authenticate\",\n ...options,\n headers: {\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n ...options.headers,\n },\n });\n\n/**\n * Read back the last shopper code sent to an address\n *\n * Development only. Beside the development authenticator, shopper codes\n * go to an in-memory outbox instead of an email provider, and this reads\n * the last one sent to an address back, so the black-box collection can\n * complete a shopper sign-in. Not served anywhere else.\n *\n */\nexport const getDevelopmentShopperCode = <ThrowOnError extends boolean = true>(\n options: Options<GetDevelopmentShopperCodeData, ThrowOnError>,\n): RequestResult<\n GetDevelopmentShopperCodeResponses,\n GetDevelopmentShopperCodeErrors,\n ThrowOnError,\n \"data\"\n> =>\n options.client.get<\n GetDevelopmentShopperCodeResponses,\n GetDevelopmentShopperCodeErrors,\n ThrowOnError,\n \"data\"\n >({\n responseStyle: \"data\",\n url: \"/dev/shopper-codes\",\n ...options,\n });\n\n/**\n * Complete an AuthKit sign-in\n *\n * Where AuthKit returns the browser after a sign-in that `/authorize`\n * began. Served only when `IDENTITY_WORKOS_AUTHENTICATOR` is on.\n *\n * The callback is honoured only in the browser that started the\n * sign-in: `/authorize` set a `__Host-fluid_authkit_` cookie derived\n * from the pending request's handle, one per pending request and for as\n * long as it is parked, and a callback without the one for its state is\n * refused before any code is exchanged. The service then exchanges the code\n * with WorkOS and resolves the person from its synced directory, never\n * from WorkOS: the principal by the `urn:fluid:user_id` claim, or, for a\n * WorkOS user with no external_id, by a unique active email match that\n * is linked and audited; the role from the directory's membership. A\n * WorkOS role that disagrees is recorded and the directory's is minted.\n *\n * A resolved sign-in opens the identity session exactly as the\n * development authenticator does: `auth_time` is set, the session\n * cookie is set, and the browser is redirected to the pending request's\n * redirect URI with a code. The token minted from it carries `sub`\n * `urn:fluid:user:<users.uuid_v7>`, `org` the company's uuid_v7 (absent\n * for the platform organization), and the directory's role.\n *\n * A sign-in that resolves to nobody who may have a token -- unmapped\n * with no single email match, a locked or inactive membership, the\n * default `member` role -- gets the support recovery page and nothing\n * for the client (INV-5.6). A cancelled sign-in or a spent code is\n * `access_denied` to the client, and an outage is\n * `temporarily_unavailable`.\n *\n */\nexport const completeWorkosSignIn = <ThrowOnError extends boolean = true>(\n options: Options<CompleteWorkosSignInData, ThrowOnError>,\n): RequestResult<unknown, CompleteWorkosSignInErrors, ThrowOnError, \"data\"> =>\n options.client.get<unknown, CompleteWorkosSignInErrors, ThrowOnError, \"data\">(\n {\n responseStyle: \"data\",\n url: \"/workos/callback\",\n ...options,\n },\n );\n\n/**\n * Receive a WorkOS event\n *\n * The webhook endpoint (Phase 005 FR-9). Always served, so the path\n * answers from the first deploy; it accepts deliveries only once the\n * service holds both the endpoint's signing secret and its WorkOS API\n * key, and refuses every delivery with 503 until then.\n *\n * A delivery is verified against `WorkOS-Signature` -- `t=<ms>,\n * v1=<hex HMAC-SHA256 over \"<t>.<raw body>\">`, with a timestamp within\n * three minutes -- then recorded by event id and acknowledged before it\n * is processed. A redelivery or replay of a recorded id is acknowledged\n * as a duplicate and changes nothing. Processing compares the object\n * with the directory and, where WorkOS has drifted, writes Rails' state\n * back to WorkOS and records the divergence. No event creates or changes\n * a Fluid record (INV-5.1).\n *\n */\nexport const receiveWorkosEvent = <ThrowOnError extends boolean = true>(\n options: Options<ReceiveWorkosEventData, ThrowOnError>,\n): RequestResult<\n ReceiveWorkosEventResponses,\n ReceiveWorkosEventErrors,\n ThrowOnError,\n \"data\"\n> =>\n options.client.post<\n ReceiveWorkosEventResponses,\n ReceiveWorkosEventErrors,\n ThrowOnError,\n \"data\"\n >({\n responseStyle: \"data\",\n url: \"/workos/events\",\n ...options,\n headers: {\n \"Content-Type\": \"application/json\",\n ...options.headers,\n },\n });\n","/**\n * Auth Constants\n *\n * These constants define the default values for authentication\n * configuration and storage keys.\n */\n\n/**\n * Authentication-related constants with sensible defaults.\n */\nexport const AUTH_CONSTANTS = {\n /**\n * Grace period in milliseconds to account for clock skew\n * when checking token expiration. Tokens are considered valid\n * if they expire within this period.\n */\n TOKEN_GRACE_PERIOD_MS: 30_000, // 30 seconds\n\n /**\n * Default cookie max age in seconds (9 days).\n * This matches the typical JWT token lifetime from the Fluid API.\n */\n COOKIE_MAX_AGE: 777_600, // 9 days in seconds (9 * 24 * 60 * 60)\n} as const;\n\n/**\n * Storage keys for auth tokens.\n */\nexport const STORAGE_KEYS = {\n /** localStorage key for user token */\n USER_TOKEN: \"fluidUserToken\",\n /** localStorage key for company token (legacy) */\n COMPANY_TOKEN: \"fluidCompanyToken\",\n /** Cookie name for auth token */\n AUTH_COOKIE: \"auth_token\",\n} as const;\n\n/**\n * Default URL parameter names for token extraction.\n */\nexport const URL_PARAMS = {\n /** URL parameter name for user token */\n USER_TOKEN: \"fluidUserToken\",\n /** URL parameter name for company token (legacy) */\n COMPANY_TOKEN: \"fluidCompanyToken\",\n} as const;\n","/**\n * Single-flight refresh (INV-6.6).\n *\n * Every caller that finds the token stale shares one in-flight refresh, so\n * twenty concurrent requests with an expired token cause one call to the\n * identity service, and one fallback redirect if it fails.\n *\n * Refresh is a credentialed request to the identity domain that relies on its\n * HttpOnly, per-client refresh cookie; the page never holds a refresh token\n * (INV-6.2). When the browser cannot send that cookie, for example because it\n * blocks third-party cookies, the identity service refuses the grant and the\n * session falls back to the silent `prompt=none` redirect.\n */\n\nimport {\n isApiError,\n oauthErrorCode,\n type IdentityHeyApiClient,\n} from \"@fluid-app/identity-api-client\";\nimport { createToken } from \"@fluid-app/identity-api-client/hey-api/sdk\";\nimport type { TokenResponse } from \"@fluid-app/identity-api-client/hey-api/types\";\n\nexport interface Refresher<T> {\n /** Joins the refresh in flight, or starts one. */\n refresh(): Promise<T>;\n inFlight(): boolean;\n}\n\nexport function createRefresher<T>(run: () => Promise<T>): Refresher<T> {\n let inflight: Promise<T> | null = null;\n\n return {\n refresh() {\n inflight ??= run().finally(() => {\n inflight = null;\n });\n return inflight;\n },\n inFlight() {\n return inflight !== null;\n },\n };\n}\n\n/**\n * Asks the identity service for a new access token with the refresh cookie.\n * The body names the client and carries no refresh token.\n */\nexport function refreshWithCookie(\n client: IdentityHeyApiClient,\n clientId: string,\n): Promise<TokenResponse> {\n return createToken({\n client,\n body: { grant_type: \"refresh_token\", client_id: clientId },\n credentials: \"include\",\n });\n}\n\n/**\n * `unavailable` when retrying the same grant later can succeed: the identity\n * service was unreachable, overloaded, or answered `temporarily_unavailable`,\n * which consumes nothing. `rejected` when the grant itself was refused, which\n * includes a refresh cookie the browser did not send.\n */\nexport type RefreshFailure = \"unavailable\" | \"rejected\";\n\nexport function classifyRefreshFailure(error: unknown): RefreshFailure {\n const code = oauthErrorCode(error);\n if (\n code === \"temporarily_unavailable\" ||\n code === \"slow_down\" ||\n code === \"server_error\"\n ) {\n return \"unavailable\";\n }\n if (code) return \"rejected\";\n if (isApiError(error)) {\n return error.status === 0 || error.status >= 500 || error.status === 429\n ? \"unavailable\"\n : \"rejected\";\n }\n return \"unavailable\";\n}\n","/**\n * Token exchange (RFC 8693): company switch, role narrowing, and a root\n * admin's company entry (Phase 007 FR-3, FR-4).\n *\n * An exchanged token comes with no refresh token and never outlives the token\n * it was made from, while the session's own refresh chain keeps the company\n * it was opened with. So the company and roles a tab moved to are remembered\n * as an exchange target, and the session exchanges into it again after every\n * refresh. The target is a company id and role names, which are not secrets\n * and grant nothing on their own; it lives in sessionStorage so a reload of\n * the same tab stays where it was, and other tabs keep their own.\n */\n\nimport {\n oauthErrorCode,\n type IdentityHeyApiClient,\n} from \"@fluid-app/identity-api-client\";\nimport { createToken } from \"@fluid-app/identity-api-client/hey-api/sdk\";\nimport type { TokenResponse } from \"@fluid-app/identity-api-client/hey-api/types\";\n\nimport { classifyRefreshFailure } from \"./refresher\";\nimport type { SessionRole } from \"./types\";\n\nconst TOKEN_EXCHANGE_GRANT =\n \"urn:ietf:params:oauth:grant-type:token-exchange\" as const;\nconst ACCESS_TOKEN_TYPE =\n \"urn:ietf:params:oauth:token-type:access_token\" as const;\n\n/** sessionStorage key for the tab's exchange target. */\nexport const EXCHANGE_TARGET_KEY = \"__fluid_auth_exchange_target\";\n\n/** The company and roles a tab holds by exchange. */\nexport interface ExchangeTarget {\n /** Company `uuid_v7` the exchanged token names. */\n readonly org: string;\n /** Roles to narrow to, or null for the target membership's own role. */\n readonly roles: readonly SessionRole[] | null;\n}\n\ninterface StoredTarget extends ExchangeTarget {\n readonly clientId: string;\n}\n\nfunction isStoredTarget(value: unknown): value is StoredTarget {\n if (typeof value !== \"object\" || value === null) return false;\n const record = value as Record<string, unknown>;\n return (\n typeof record.clientId === \"string\" &&\n typeof record.org === \"string\" &&\n (record.roles === null ||\n (Array.isArray(record.roles) &&\n record.roles.every((role) => typeof role === \"string\")))\n );\n}\n\n/** The tab's exchange target for this client, or null. */\nexport function readExchangeTarget(clientId: string): ExchangeTarget | null {\n try {\n const raw = sessionStorage.getItem(EXCHANGE_TARGET_KEY);\n if (raw === null) return null;\n const parsed: unknown = JSON.parse(raw);\n if (!isStoredTarget(parsed) || parsed.clientId !== clientId) return null;\n return { org: parsed.org, roles: parsed.roles };\n } catch {\n return null;\n }\n}\n\nexport function writeExchangeTarget(\n clientId: string,\n target: ExchangeTarget,\n): void {\n try {\n const stored: StoredTarget = { clientId, ...target };\n sessionStorage.setItem(EXCHANGE_TARGET_KEY, JSON.stringify(stored));\n } catch {\n // Storage unavailable: the tab returns to the session's own company on\n // its next reload, which is where the identity session would take it.\n }\n}\n\nexport function clearExchangeTarget(): void {\n try {\n if (sessionStorage.getItem(EXCHANGE_TARGET_KEY) === null) return;\n sessionStorage.removeItem(EXCHANGE_TARGET_KEY);\n } catch {\n // Storage unavailable: nothing to clear.\n }\n}\n\n/**\n * The roles that rank no higher than `roles`: `root_admin` over `admin` over\n * `rep`, as the identity service ranks a narrowing. A role it does not rank\n * is its own ceiling.\n */\nexport function rolesAtOrBelow(\n roles: readonly SessionRole[],\n): ReadonlySet<SessionRole> {\n const ceiling = new Set<SessionRole>();\n for (const role of roles) {\n ceiling.add(role);\n if (role === \"root_admin\") ceiling.add(\"admin\");\n if (role === \"root_admin\" || role === \"admin\") ceiling.add(\"rep\");\n }\n return ceiling;\n}\n\n/**\n * Exchanges `subjectToken`, the session's own chain token, for one naming\n * `target`. The request carries no cookie: an exchange is judged on its\n * subject token alone and sets nothing.\n */\nexport function exchangeToken(\n client: IdentityHeyApiClient,\n clientId: string,\n subjectToken: string,\n target: ExchangeTarget,\n): Promise<TokenResponse> {\n return createToken({\n client,\n body: {\n grant_type: TOKEN_EXCHANGE_GRANT,\n client_id: clientId,\n subject_token: subjectToken,\n subject_token_type: ACCESS_TOKEN_TYPE,\n requested_token_type: ACCESS_TOKEN_TYPE,\n org: target.org,\n ...(target.roles ? { roles: target.roles.join(\" \") } : {}),\n },\n credentials: \"omit\",\n });\n}\n\n/**\n * `refused` when the identity service will not issue a token for the target:\n * the subject holds no such company or role (`invalid_target`,\n * `invalid_scope`), or the client may not exchange at all. `unusable` when the\n * subject token itself was not accepted (`invalid_request`), so a newer chain\n * token may succeed. `unavailable` when the same request can succeed later.\n */\nexport type ExchangeFailure = \"refused\" | \"unusable\" | \"unavailable\";\n\nexport function classifyExchangeFailure(error: unknown): ExchangeFailure {\n if (classifyRefreshFailure(error) === \"unavailable\") return \"unavailable\";\n const code = oauthErrorCode(error);\n if (code === \"invalid_request\" || code === \"invalid_token\") return \"unusable\";\n return \"refused\";\n}\n","/**\n * Session facts from the identity service's `/userinfo` (INV-6.3). Shared by\n * the browser session and the native session, so neither decodes a token.\n */\n\nimport type { SessionFacts as IdentitySessionFacts } from \"@fluid-app/identity-api-client/hey-api/types\";\n\nimport type { SessionFacts } from \"./types\";\n\n/** Maps `/userinfo` onto the facts an app reads. */\nexport function toSessionFacts(\n info: IdentitySessionFacts,\n expiresAt: number,\n exchanged = false,\n): SessionFacts {\n const actor = info.act?.sub ?? null;\n return {\n subject: info.sub,\n companyId: info.org ?? null,\n roles: [...info.roles],\n actor,\n impersonating: actor !== null,\n sessionId: info.sid,\n authTime: info.auth_time,\n authMethods: [...info.amr],\n expiresAt,\n exchanged,\n };\n}\n","/**\n * Session API types.\n *\n * The session module is the identity-mode path of `@fluid-app/auth`: it\n * redeems a single-use code with PKCE, keeps the access token in memory only,\n * and refreshes through the identity domain. App code receives\n * {@link SessionFacts}, never a token, except through `getAccessToken()`.\n */\n\n/** A role the identity service can grant on a principal token. */\nexport type SessionRole = \"admin\" | \"rep\" | \"customer\" | \"root_admin\";\n\n/**\n * What an app may show or branch on. Read from the identity service's\n * `/userinfo` and token responses, never by decoding a token (INV-6.3).\n */\nexport interface SessionFacts {\n /** Principal URN, for example `urn:fluid:admin:<uuid>`. */\n readonly subject: string;\n /** Company `uuid_v7`, or null on a platform-scoped session. */\n readonly companyId: string | null;\n readonly roles: readonly SessionRole[];\n /** The acting principal's URN while impersonating, otherwise null. */\n readonly actor: string | null;\n readonly impersonating: boolean;\n /** Identity session id, stable across refresh. */\n readonly sessionId: string;\n /** Unix seconds of the last interactive sign-in. */\n readonly authTime: number;\n readonly authMethods: readonly string[];\n /** Epoch milliseconds at which the current access token expires. */\n readonly expiresAt: number;\n /**\n * True while this page holds a token it moved to by exchange, through\n * `switchCompany` or `narrowRoles`, rather than the one the session signed\n * in with. `resetScope` returns to that one. For a root admin this is a\n * company entered by role, which the token itself does not mark: it names\n * the same principal and carries no `act`.\n */\n readonly exchanged: boolean;\n /**\n * Set when the current token was adopted while `/userinfo` could not\n * answer: the facts are the previous token's, kept so the session survives\n * an outage, and not confirmed for this token. An app that pairs the token\n * with one person's data -- a shopper's customer record -- must not until\n * they are confirmed. Absent once `/userinfo` has answered for the token.\n */\n readonly unconfirmed?: true;\n}\n\n/**\n * How sign-in completes for this origin. `identity` is the code flow;\n * `legacy` is today's token handling in `@fluid-app/auth`'s legacy module,\n * which the app keeps running unchanged.\n */\nexport type SessionMode = \"identity\" | \"legacy\";\n\nexport type SessionStatus =\n /** `start()` has not been called. */\n | \"idle\"\n | \"loading\"\n | \"authenticated\"\n /** The page is navigating to the identity service. */\n | \"redirecting\"\n /** Sign-in completes with a legacy token; the legacy module owns it. */\n | \"legacy\"\n /** Signed out, or a redirect was suppressed to avoid a loop. */\n | \"unauthenticated\"\n | \"error\";\n\nexport type SessionErrorCode =\n /** The callback's `state` was missing or did not match. */\n | \"state_mismatch\"\n /** No valid identity session and the interactive flow did not start. */\n | \"login_required\"\n /** A redirect was suppressed by loop detection. */\n | \"redirect_suppressed\"\n /**\n * The redirect could not be prepared, for example because sessionStorage\n * is blocked or Web Crypto is unavailable outside a secure context.\n */\n | \"redirect_failed\"\n /** The identity service could not answer; nothing was consumed. */\n | \"temporarily_unavailable\"\n /**\n * The requested company or roles are not ones this session may hold: a\n * sign-in the identity service refused, a move by token exchange it\n * refused, or a company or role this page moved to that the session no\n * longer holds.\n */\n | \"access_denied\"\n /** The session may enter several companies and named none. */\n | \"account_selection_required\"\n /** An interactive sign-in was asked of a session that is silent-only. */\n | \"interactive_unsupported\"\n /** Redeeming the code failed. */\n | \"redemption_failed\"\n /** The access token could not be refreshed. */\n | \"session_expired\"\n /** No identity session exists in this page. */\n | \"not_signed_in\"\n /**\n * The page signed out but the identity service did not confirm revoking\n * the identity session, which may still be live for other tabs and hosts.\n */\n | \"sign_out_failed\"\n /**\n * The action belongs to the embedding page: an embedded surface that gets\n * its token through the frame bridge cannot switch company itself.\n */\n | \"frame_unsupported\";\n\nexport class SessionError extends Error {\n readonly code: SessionErrorCode;\n\n constructor(code: SessionErrorCode, message?: string) {\n super(message ?? code);\n this.name = \"SessionError\";\n this.code = code;\n }\n}\n\nexport function isSessionError(error: unknown): error is SessionError {\n return error instanceof SessionError;\n}\n\nexport interface SessionSnapshot {\n readonly status: SessionStatus;\n readonly mode: SessionMode | null;\n readonly facts: SessionFacts | null;\n readonly error: SessionError | null;\n}\n\nexport interface StartOptions {\n /**\n * When nothing identifies this origin's mode (no callback, no hint, no\n * legacy token), begin identity sign-in instead of reporting legacy mode.\n * Until Phase 007 routes sign-in by the company flag, only test clients set\n * this.\n */\n interactive?: boolean;\n}\n\nexport interface AccessTokenOptions {\n /**\n * The token a resource server just rejected. When it is still the current\n * token it is refreshed; when another caller already replaced it, the\n * replacement is returned without a second refresh.\n */\n replacing?: string;\n}\n\nexport interface FluidSession {\n /**\n * Completes a callback in the URL, restores the session, or reports legacy\n * mode. Idempotent while a start is in flight.\n *\n * Safe to call again after it rejects. When it rejected with\n * `temporarily_unavailable`, nothing was consumed and a later call can\n * succeed; `getAccessToken()` makes that call itself, and an app that shows\n * the error may also offer a retry that calls this.\n */\n start(options?: StartOptions): Promise<SessionFacts | null>;\n /**\n * Refreshes when needed; never resolves with an expired token. Starts the\n * session first when nothing has, and retries a start that met a\n * temporary identity outage.\n */\n getAccessToken(options?: AccessTokenOptions): Promise<string>;\n /** User, company, roles, and actor; never the raw token. */\n facts(): SessionFacts | null;\n snapshot(): SessionSnapshot;\n /**\n * Moves this page to another company by token exchange, with no\n * navigation, and resolves with the new facts. A person moves to a company\n * where they hold an active membership, with that membership's role; a\n * root admin enters any active company and keeps `root_admin`. Naming the\n * company the session signed in to returns to its own token.\n *\n * The page stays in that company across refreshes and reloads of the same\n * tab; other tabs keep their own. A refused move rejects with\n * `access_denied` and leaves the page as it was.\n */\n switchCompany(companyId: string): Promise<SessionFacts>;\n /**\n * Narrows this page's roles within its current company by token exchange,\n * for example an admin who is also a rep acting as `rep`. Each role must be\n * one the person holds there and none may rank above the current token's;\n * anything else rejects with `access_denied`. `switchCompany` with the\n * current company, or `resetScope`, takes the roles back.\n */\n narrowRoles(roles: readonly SessionRole[]): Promise<SessionFacts>;\n /**\n * Returns this page to the company and roles the session signed in with,\n * such as a root admin leaving a company they entered.\n */\n resetScope(): Promise<SessionFacts>;\n /**\n * Clears memory and revokes this client's refresh cookie. In identity mode\n * it then sends the page through the identity service's `/signout`, which\n * ends the identity session for every app and returns to\n * `postLogoutRedirectUri`; the promise settles as the navigation starts. A\n * revocation the identity service does not confirm is still recorded as\n * owed and reported as `sign_out_failed`.\n */\n signOut(): Promise<void>;\n /**\n * Ends the session when this origin could restore one: a mode hint, the\n * identity mode, or an owed revocation. It then revokes and goes through\n * `/signout` as `signOut` does, returning to the same-origin `returnTo`\n * instead of `afterSignOutUrl`. Resolves true once that navigation is\n * issued, including when the revocation was not confirmed (the owed\n * revocation is retried on a later load). Resolves false, touching\n * nothing, when nothing here could be restored.\n */\n endIfRestorable(options: { returnTo: string }): Promise<boolean>;\n /**\n * Synchronously stops this session from starting a sign-in: in-flight\n * work settles signed out and no `/authorize` navigation is issued until\n * the page is left. Only an explicit leave sets it.\n */\n beginLeave(): void;\n subscribe(listener: (snapshot: SessionSnapshot) => void): () => void;\n}\n\nexport interface SessionConfig {\n /** Identity service origin, for example `https://id.fluid.app`. */\n identityUrl: string;\n /** This app's registered public client id. */\n clientId: string;\n /**\n * A redirect URI registered for the client, on this app's origin. Give it\n * a path of its own: a `code` or `error` arriving on that path is treated as\n * an authorization callback, and one that matches no request from this tab\n * is rejected and removed from the URL.\n */\n redirectUri: string;\n /** Company to scope the session to, when the app knows it. */\n org?: string;\n /**\n * Which sign-in the identity service shows when one is needed. `shopper`\n * is the emailed one-time code for a company's shoppers (Phase 007 FR-5),\n * which needs `org`; absent means the staff sign-in. A usable identity\n * session is answered either way.\n */\n loginKind?: \"staff\" | \"shopper\";\n /**\n * Where `/signout` returns once the identity session has ended. Must equal\n * one of the client's registered redirect URIs exactly. Defaults to\n * `redirectUri`.\n */\n postLogoutRedirectUri?: string;\n /**\n * The page shown after a sign-out, on this app's origin. Defaults to the\n * origin's root.\n */\n afterSignOutUrl?: string;\n /**\n * This session restores only silently and never goes interactive:\n * `start({ interactive: true })` rejects with `interactive_unsupported`,\n * and a fluid-login hand-back becomes a silent request. Sign in with a\n * separate, ordinary session.\n *\n * When the identity service answers with `login_required`,\n * `interaction_required` or `consent_required`, the session is left\n * unauthenticated at the page it left. Any other error still fails as\n * usual. At most one silent request goes out per tab until a session is\n * restored, whatever the outcome. The per-tab marker that enforces this is\n * cleared on every successful adoption, and stays set after a sign-out\n * until then.\n *\n * Do not wire `resolveAuthFailureHandler` (legacy/auth-redirect) to a\n * silent-only session: it starts a real interactive legacy sign-in.\n */\n silentOnly?: boolean;\n fetch?: typeof fetch;\n /** Navigates the page. Defaults to `window.location.assign`. */\n navigate?: (url: string) => void;\n /** Clock in epoch milliseconds. Defaults to `Date.now`. */\n now?: () => number;\n /**\n * The longest `signOut()` waits for a /token exchange already in flight\n * before it revokes regardless, in milliseconds. Defaults to 5000.\n */\n signOutWaitMs?: number;\n /**\n * When nothing identifies this origin's mode (no callback, no hint, no\n * legacy token), ask the identity domain once per tab, with a silent\n * `/authorize?prompt=none`, whether a session opened on another Fluid app\n * already exists, instead of settling legacy at once. A code restores it\n * with no prompt; any refusal, such as `login_required`, settles legacy\n * with the page back where it was and starts no interactive sign-in.\n *\n * For an app on its own origin that people reach already signed in\n * elsewhere and that has no sign-in of its own to route them, such as\n * fluid-themes. It costs one round trip through the identity domain on a\n * tab's first visit with nothing remembered.\n */\n probeWithoutHint?: boolean;\n}\n","/**\n * Token exchange for native clients (Phase 007 FR-3): moves a native\n * session's access token to another company, or narrows its roles, without a\n * new sign-in.\n *\n * The exchanged token comes with no refresh token and never outlives the\n * subject token, so the client keeps its own refresh chain in its\n * {@link NativeSession} and exchanges again after every refresh. The exchange\n * target, a company id and role names, is not a secret and grants nothing on\n * its own, so a client may remember it in its plain configuration.\n */\n\nimport { createIdentityHeyApiClient } from \"@fluid-app/identity-api-client\";\nimport { getUserinfo } from \"@fluid-app/identity-api-client/hey-api/sdk\";\n\nimport {\n classifyExchangeFailure,\n exchangeToken,\n type ExchangeTarget,\n} from \"../session/exchange\";\nimport { toSessionFacts } from \"../session/facts\";\nimport { classifyRefreshFailure } from \"../session/refresher\";\nimport { SessionError, type SessionFacts } from \"../session/types\";\n\nexport type { ExchangeTarget } from \"../session/exchange\";\n\nexport interface NativeExchangeConfig {\n /** Identity service origin, the same one the session signed in with. */\n readonly identityUrl: string;\n /** This client's registered public client id. */\n readonly clientId: string;\n readonly fetch?: typeof fetch;\n /** Clock in epoch milliseconds. Defaults to `Date.now`. */\n readonly now?: () => number;\n}\n\nexport interface ExchangedToken {\n /** Held in memory only; it has no refresh token behind it. */\n readonly accessToken: string;\n /** Epoch milliseconds. */\n readonly expiresAt: number;\n /** From `/userinfo` for the exchanged token, never from decoding it. */\n readonly facts: SessionFacts;\n}\n\n/**\n * Exchanges `subjectToken`, the session's own access token from\n * `NativeSession.getAccessToken()`, for one naming `target`.\n *\n * Rejects with `access_denied` when the identity service refuses the target,\n * `session_expired` when it does not accept the subject token (refresh it and\n * try again), and `temporarily_unavailable` when it could not answer.\n */\nexport async function exchangeNativeToken(\n config: NativeExchangeConfig,\n subjectToken: string,\n target: ExchangeTarget,\n): Promise<ExchangedToken> {\n const now = config.now ?? Date.now;\n const client = createIdentityHeyApiClient({\n baseUrl: config.identityUrl,\n credentials: \"omit\",\n fetch: config.fetch,\n });\n\n let accessToken: string;\n let expiresAt: number;\n try {\n const token = await exchangeToken(\n client,\n config.clientId,\n subjectToken,\n target,\n );\n accessToken = token.access_token;\n expiresAt = now() + token.expires_in * 1000;\n } catch (error) {\n const failure = classifyExchangeFailure(error);\n if (failure === \"unavailable\") {\n throw new SessionError(\"temporarily_unavailable\");\n }\n if (failure === \"unusable\") {\n throw new SessionError(\n \"session_expired\",\n \"The identity service did not accept the token to exchange.\",\n );\n }\n throw new SessionError(\n \"access_denied\",\n \"The identity service refused the exchange.\",\n );\n }\n\n try {\n const info = await getUserinfo({ client, auth: accessToken });\n return {\n accessToken,\n expiresAt,\n facts: toSessionFacts(info, expiresAt, true),\n };\n } catch (error) {\n if (classifyRefreshFailure(error) === \"unavailable\") {\n throw new SessionError(\"temporarily_unavailable\");\n }\n throw new SessionError(\n \"access_denied\",\n \"The identity service rejected the token it exchanged.\",\n );\n }\n}\n","/**\n * PKCE (RFC 7636, S256) and the round-trip record.\n *\n * The verifier and `state` are the only things this module keeps, and only in\n * sessionStorage, only between the redirect to the identity service and the\n * callback that returns from it. {@link takePendingAuthorization} removes the\n * record as it reads it, so the verifier is gone before redemption is even\n * attempted and whatever the outcome.\n */\n\n/** sessionStorage key for the in-flight authorization request. */\nexport const PENDING_AUTHORIZATION_KEY = \"__fluid_auth_pkce\";\n\n/** What the callback needs to finish the request that left this tab. */\nexport interface PendingAuthorization {\n readonly state: string;\n readonly verifier: string;\n readonly redirectUri: string;\n /** Same-origin URL to restore once the callback is handled. */\n readonly returnTo: string;\n /** `none` for a silent attempt; absent for an interactive one. */\n readonly prompt?: \"none\";\n /**\n * The company the request asked for, so a fallback to the interactive flow\n * asks for the same one.\n */\n readonly org?: string;\n /**\n * A silent probe for a session opened on another app; a refusal settles\n * legacy rather than starting the interactive flow.\n */\n readonly probe?: true;\n /**\n * A silent restore that must not start the interactive flow; a\n * `login_required` refusal settles unauthenticated.\n */\n readonly silentOnly?: true;\n /** Epoch milliseconds the request left. */\n readonly createdAt: number;\n}\n\nfunction base64Url(bytes: Uint8Array): string {\n let binary = \"\";\n for (const byte of bytes) binary += String.fromCharCode(byte);\n return btoa(binary)\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n\n/**\n * The Web Crypto calls PKCE needs. Browsers and Node provide `crypto`; a\n * runtime without it, such as React Native, passes an implementation.\n */\nexport interface PkceCrypto {\n getRandomValues(array: Uint8Array<ArrayBuffer>): unknown;\n readonly subtle: Pick<SubtleCrypto, \"digest\">;\n}\n\nfunction randomString(byteLength: number, cryptoImpl: PkceCrypto): string {\n const bytes = new Uint8Array(byteLength);\n cryptoImpl.getRandomValues(bytes);\n return base64Url(bytes);\n}\n\n/** A 43-character verifier from 32 random bytes, the RFC 7636 minimum. */\nexport function createCodeVerifier(cryptoImpl: PkceCrypto = crypto): string {\n return randomString(32, cryptoImpl);\n}\n\n/** An unguessable `state` binding the callback to this tab's request. */\nexport function createState(cryptoImpl: PkceCrypto = crypto): string {\n return randomString(32, cryptoImpl);\n}\n\n/** `BASE64URL(SHA256(verifier))`, the S256 challenge. */\nexport async function createCodeChallenge(\n verifier: string,\n cryptoImpl: PkceCrypto = crypto,\n): Promise<string> {\n const digest = await cryptoImpl.subtle.digest(\n \"SHA-256\",\n new TextEncoder().encode(verifier),\n );\n return base64Url(new Uint8Array(digest));\n}\n\nfunction isPendingAuthorization(value: unknown): value is PendingAuthorization {\n if (!value || typeof value !== \"object\") return false;\n const record = value as Record<string, unknown>;\n return (\n typeof record.state === \"string\" &&\n typeof record.verifier === \"string\" &&\n typeof record.redirectUri === \"string\" &&\n typeof record.returnTo === \"string\" &&\n typeof record.createdAt === \"number\" &&\n (record.prompt === undefined || record.prompt === \"none\") &&\n (record.org === undefined || typeof record.org === \"string\") &&\n (record.probe === undefined || record.probe === true) &&\n (record.silentOnly === undefined || record.silentOnly === true)\n );\n}\n\n/** Records the request about to leave for the identity service. */\nexport function savePendingAuthorization(pending: PendingAuthorization): void {\n sessionStorage.setItem(PENDING_AUTHORIZATION_KEY, JSON.stringify(pending));\n}\n\n/**\n * Reads and removes the pending request. Removal happens first, so neither a\n * failed redemption nor a thrown error can leave the verifier behind.\n */\nexport function takePendingAuthorization(): PendingAuthorization | null {\n let raw: string | null = null;\n try {\n raw = sessionStorage.getItem(PENDING_AUTHORIZATION_KEY);\n // Only a record that exists is removed, so a page with no request in\n // flight, such as every legacy-mode page, writes nothing.\n if (raw !== null) sessionStorage.removeItem(PENDING_AUTHORIZATION_KEY);\n } catch {\n return null;\n }\n if (!raw) return null;\n try {\n const parsed: unknown = JSON.parse(raw);\n return isPendingAuthorization(parsed) ? parsed : null;\n } catch {\n return null;\n }\n}\n","/**\n * Native secure session: the authorization code flow with PKCE for public\n * clients that are not browsers (the CLI, `apps/mist-desktop`,\n * `apps/portal-mobile`), FR-7.\n *\n * The client opens the system browser at the URL `beginSignIn()` returns and\n * hands the redirect it receives (a loopback request for the CLI and\n * desktop, an app-claimed link on mobile) to `completeSignIn()`. The rotating\n * refresh token then lives only in the injected {@link SecureStorage}, which\n * each client backs with its operating system's credential store (INV-6.2);\n * the access token lives only in memory. A restart restores the session from\n * storage without a prompt. Facts come from `/userinfo`, never from decoding\n * a token (INV-6.3).\n *\n * `switchCompany()` moves the session to another company by token exchange\n * (Phase 007 FR-3). The refresh chain keeps the company the session signed in\n * with, so the company moved to is remembered as the session's target and\n * every refresh exchanges into it again. A token is handed out only for the\n * target it was made for, so a refresh or exchange that answers after a move\n * can never hand one company's token to work started for another.\n *\n * Nothing here touches `window`, the DOM, cookies, or Web Storage, so it runs\n * in Node, Electron's main process, and React Native alike.\n */\n\nimport {\n createIdentityHeyApiClient,\n oauthErrorCode,\n type IdentityHeyApiClient,\n} from \"@fluid-app/identity-api-client\";\nimport {\n createToken,\n getUserinfo,\n revokeToken,\n} from \"@fluid-app/identity-api-client/hey-api/sdk\";\nimport type { TokenResponse } from \"@fluid-app/identity-api-client/hey-api/types\";\n\nimport { AUTH_CONSTANTS } from \"../constants\";\nimport { exchangeNativeToken, type ExchangedToken } from \"./exchange\";\nimport { toSessionFacts } from \"../session/facts\";\nimport {\n createCodeChallenge,\n createCodeVerifier,\n createState,\n type PkceCrypto,\n} from \"../session/pkce\";\nimport { classifyRefreshFailure, createRefresher } from \"../session/refresher\";\nimport {\n SessionError,\n type AccessTokenOptions,\n type SessionFacts,\n} from \"../session/types\";\n\n/**\n * Where a native client keeps its refresh token. Implementations must use the\n * operating system's credential store; see `./storage-adapters`.\n */\nexport interface SecureStorage {\n get(key: string): Promise<string | null>;\n set(key: string, value: string): Promise<void>;\n delete(key: string): Promise<void>;\n}\n\nexport interface NativeSessionConfig {\n /** Identity service origin, for example `https://id.fluid.app`. */\n readonly identityUrl: string;\n /** This client's registered public client id. */\n readonly clientId: string;\n /**\n * A redirect URI registered for the client: a loopback URI such as\n * `http://127.0.0.1:53682/callback`, or an app-claimed URI on mobile.\n * Optional when every `beginSignIn()` names its own, as a loopback client\n * on an ephemeral port does (RFC 8252 section 7.3).\n */\n readonly redirectUri?: string;\n readonly storage: SecureStorage;\n /**\n * The storage key for this session's refresh token. Give each profile its\n * own key when a client holds several sessions. Defaults to\n * `fluid-identity.<clientId>`.\n */\n readonly storageKey?: string;\n readonly fetch?: typeof fetch;\n /** Clock in epoch milliseconds. Defaults to `Date.now`. */\n readonly now?: () => number;\n /** Web Crypto for PKCE, where the runtime has no global `crypto`. */\n readonly crypto?: PkceCrypto;\n /**\n * The longest `signOut()` waits for a sign-in or refresh already in flight\n * before it revokes regardless, in milliseconds. Defaults to 5000.\n */\n readonly signOutWaitMs?: number;\n}\n\nexport interface BeginSignInOptions {\n /** Company `uuid_v7` to scope the session to. */\n readonly org?: string;\n /** `login` forces the identity service to show its sign-in. */\n readonly prompt?: \"login\";\n /** Email the person typed, sent as `login_hint` when it looks like one. */\n readonly loginHint?: string;\n /**\n * The redirect URI for this sign-in, overriding the configured one: the\n * loopback URI on the port the client is listening on right now.\n */\n readonly redirectUri?: string;\n}\n\nexport interface RestoreOptions {\n /**\n * Company `uuid_v7` to restore into, by token exchange, when it is not the\n * company the session signed in with. Null or omitted restores the\n * session's own company.\n */\n readonly org?: string | null;\n}\n\nexport type NativeSessionStatus =\n | \"idle\"\n | \"authenticated\"\n | \"unauthenticated\"\n | \"error\";\n\nexport interface NativeSessionSnapshot {\n readonly status: NativeSessionStatus;\n readonly facts: SessionFacts | null;\n readonly error: SessionError | null;\n}\n\nexport interface NativeSession {\n /**\n * Restores the session from secure storage. Resolves with null, and\n * prompts for nothing, when no refresh token is stored or the stored one is\n * no longer accepted.\n */\n restore(options?: RestoreOptions): Promise<SessionFacts | null>;\n /**\n * Prepares a sign-in and returns the `/authorize` URL for the system\n * browser. The verifier and `state` stay in this object's memory.\n */\n beginSignIn(options?: BeginSignInOptions): Promise<string>;\n /**\n * Completes the sign-in begun by `beginSignIn()` with the redirect URL the\n * client received. A `state` that does not match is rejected and nothing is\n * redeemed.\n */\n completeSignIn(callbackUrl: string): Promise<SessionFacts>;\n /**\n * Refreshes when needed; never resolves with an expired token, nor with\n * one made for a company other than the session's current target.\n */\n getAccessToken(options?: AccessTokenOptions): Promise<string>;\n /**\n * Moves the session to the company `org` (`uuid_v7`) by token exchange, or\n * back to the company it signed in with when `org` is null. The refresh\n * token does not move: every later refresh exchanges into `org` again. A\n * refused move rejects with `access_denied` and leaves the session where\n * it was.\n */\n switchCompany(org: string | null): Promise<SessionFacts>;\n facts(): SessionFacts | null;\n snapshot(): NativeSessionSnapshot;\n /** Revokes the refresh token and deletes it from secure storage. */\n signOut(): Promise<void>;\n subscribe(listener: (snapshot: NativeSessionSnapshot) => void): () => void;\n}\n\ninterface PendingSignIn {\n readonly state: string;\n readonly verifier: string;\n readonly redirectUri: string;\n}\n\ninterface HeldToken {\n readonly accessToken: string;\n /** Epoch milliseconds. */\n readonly expiresAt: number;\n /**\n * The target the token was made for: a company `uuid_v7` reached by\n * exchange, or null for the session's own company.\n */\n readonly target: string | null;\n}\n\n/**\n * The longest sign-out waits for a sign-in or refresh already in flight\n * before it revokes regardless.\n */\nconst SIGN_OUT_WAIT_MS = 5_000;\n\nconst LOGIN_HINT_PATTERN = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n\n/** Company ids are uuids, which the identity service compares in any case. */\nfunction sameOrg(\n a: string | null | undefined,\n b: string | null | undefined,\n): boolean {\n if (a === null || a === undefined || b === null || b === undefined) {\n return (a ?? null) === (b ?? null);\n }\n return a.toLowerCase() === b.toLowerCase();\n}\n\nfunction sameRedirect(callback: URL, redirectUri: URL): boolean {\n return (\n callback.protocol === redirectUri.protocol &&\n callback.host === redirectUri.host &&\n callback.pathname === redirectUri.pathname\n );\n}\n\nexport function createNativeSession(\n config: NativeSessionConfig,\n): NativeSession {\n const now = config.now ?? Date.now;\n const storageKey = config.storageKey ?? `fluid-identity.${config.clientId}`;\n // Every read and write of the credential runs in order, one at a time, so\n // a slow write that began before a sign-out can never land after a newer\n // sign-in's write and replace it.\n let storageQueue: Promise<unknown> = Promise.resolve();\n function serial<T>(operation: () => Promise<T>): Promise<T> {\n const next = storageQueue.then(operation, operation);\n storageQueue = next.catch(() => undefined);\n return next;\n }\n const store = {\n get: () => serial(() => config.storage.get(storageKey)),\n set: (value: string) => serial(() => config.storage.set(storageKey, value)),\n delete: () => serial(() => config.storage.delete(storageKey)),\n /** Deletes the credential only if it is still `value`. */\n deleteIf: (value: string) =>\n serial(async () => {\n if ((await config.storage.get(storageKey)) === value) {\n await config.storage.delete(storageKey);\n }\n }),\n };\n const grace = AUTH_CONSTANTS.TOKEN_GRACE_PERIOD_MS;\n // The token handed out, made for `held.target`. Only handed out while that\n // is still the session's `target`.\n let held: HeldToken | null = null;\n // The token the refresh chain itself last issued, with no exchange: every\n // exchange is made from it, never from an exchanged token.\n let chain: HeldToken | null = null;\n // The company the chain names, as `/userinfo` answered for it.\n let chainOrg: string | null = null;\n // The company the session holds by exchange, or null for its own.\n let target: string | null = null;\n let pending: PendingSignIn | null = null;\n let snapshot: NativeSessionSnapshot = {\n status: \"idle\",\n facts: null,\n error: null,\n };\n const listeners = new Set<(snapshot: NativeSessionSnapshot) => void>();\n // Bumped by signOut(), so a refresh that started before it cannot store a\n // rotated refresh token or adopt an access token afterwards.\n let generation = 0;\n // How many signOut() calls are running. While any is, no sign-in or\n // refresh may start, so nothing can rotate the stored credential between a\n // sign-out reading it and revoking it. A count rather than a flag, so one\n // sign-out finishing does not reopen the door while another is mid-way.\n let signOutsRunning = 0;\n\n const client: IdentityHeyApiClient = createIdentityHeyApiClient({\n baseUrl: config.identityUrl,\n // A native client has no cookie jar to offer and holds its refresh token\n // itself, so nothing ambient is sent.\n credentials: \"omit\",\n fetch: config.fetch,\n getAccessToken: () => held?.accessToken,\n });\n\n function update(patch: Partial<NativeSessionSnapshot>): void {\n snapshot = { ...snapshot, ...patch };\n for (const listener of listeners) listener(snapshot);\n }\n\n /** The held token, if it is fresh and made for the current target. */\n function current(): HeldToken | null {\n return held &&\n sameOrg(held.target, target) &&\n now() < held.expiresAt - grace\n ? held\n : null;\n }\n\n /**\n * Keeps a newly issued token pair: the refresh token goes to secure storage\n * before the access token is used, so a crash cannot lose a rotation the\n * server has already made. A response without a refresh token (the reuse\n * grace absorbed a concurrent rotation) keeps the stored one. When the\n * session holds another company by exchange, the new chain token is\n * exchanged into it before anything is handed out.\n */\n async function adopt(\n token: TokenResponse,\n started: number,\n ): Promise<SessionFacts> {\n if (started !== generation) {\n // Signed out while this exchange was out: the refresh token it brought\n // belongs to a session sign-out could not reach, so end it here.\n if (token.refresh_token) await revokeQuietly(token.refresh_token);\n throw new SessionError(\"not_signed_in\");\n }\n if (token.refresh_token) {\n await store.set(token.refresh_token);\n }\n if (started !== generation) {\n // Signed out while storage was being written: undo this write and end\n // the session it would have restored. A credential a newer sign-in has\n // stored since is that sign-in's, and stays.\n if (token.refresh_token) {\n await store.deleteIf(token.refresh_token);\n await revokeQuietly(token.refresh_token);\n }\n throw new SessionError(\"not_signed_in\");\n }\n const issued: HeldToken = {\n accessToken: token.access_token,\n expiresAt: now() + token.expires_in * 1000,\n target: null,\n };\n chain = issued;\n if (target === null) held = issued;\n const info = await getUserinfo({ client, auth: issued.accessToken });\n if (started !== generation) {\n held = null;\n chain = null;\n throw new SessionError(\"not_signed_in\");\n }\n chainOrg = info.org ?? null;\n if (target !== null && !sameOrg(target, chainOrg)) {\n return enter(target, started);\n }\n // The chain already names the target company: no exchange is needed.\n held = { ...issued, target };\n const facts = toSessionFacts(info, issued.expiresAt);\n update({ status: \"authenticated\", facts, error: null });\n return facts;\n }\n\n /**\n * Exchanges the chain token for one naming `org` and holds it. A refusal\n * rejects with `access_denied` and hands nothing out, so work started for\n * `org` can never be given the chain's own company's token instead.\n */\n async function enter(org: string, started: number): Promise<SessionFacts> {\n const from = chain;\n if (!from) throw new SessionError(\"not_signed_in\");\n let exchanged: ExchangedToken;\n try {\n exchanged = await exchangeNativeToken(\n {\n identityUrl: config.identityUrl,\n clientId: config.clientId,\n fetch: config.fetch,\n now,\n },\n from.accessToken,\n { org, roles: null },\n );\n } catch (error) {\n if (started !== generation) throw new SessionError(\"not_signed_in\");\n // An unaccepted subject token is the chain token in doubt, not the\n // stored credential: report it as retryable, so the next request\n // refreshes the chain and exchanges again rather than signing out.\n const sessionError =\n error instanceof SessionError && error.code === \"access_denied\"\n ? new SessionError(\n \"access_denied\",\n `The identity service will not move this session to company ${org}.`,\n )\n : new SessionError(\n \"temporarily_unavailable\",\n error instanceof SessionError && error.code === \"session_expired\"\n ? \"The identity service did not accept the session's token for an exchange; a refresh may.\"\n : \"The identity service could not exchange the session's token; retry later.\",\n );\n update({ error: sessionError });\n throw sessionError;\n }\n // Revoking the chain revokes every token exchanged from it, so a\n // sign-out that ran meanwhile has already ended this one.\n if (started !== generation) throw new SessionError(\"not_signed_in\");\n // An exchanged token never outlives the token it was made from.\n const expiresAt = Math.min(from.expiresAt, exchanged.expiresAt);\n held = { accessToken: exchanged.accessToken, expiresAt, target: org };\n const facts: SessionFacts = { ...exchanged.facts, expiresAt };\n update({ status: \"authenticated\", facts, error: null });\n return facts;\n }\n\n function revoke(refreshToken: string): Promise<unknown> {\n return revokeToken({\n client,\n body: {\n token: refreshToken,\n token_type_hint: \"refresh_token\",\n client_id: config.clientId,\n },\n });\n }\n\n async function revokeQuietly(refreshToken: string): Promise<void> {\n try {\n await revoke(refreshToken);\n } catch {\n // Best effort for a session nobody holds any more.\n }\n }\n\n /**\n * A code was redeemed for `refreshToken`, but the sign-in did not finish:\n * storing the credential or reading `/userinfo` failed. The code is spent,\n * so the session it opened is never just dropped. When the identity service\n * could not answer and the credential is safely stored, it is kept, and\n * `restore()` finishes the sign-in once the service answers. Otherwise, or\n * when the service rejected the token it issued, the session is ended: the\n * credential is deleted and revoked, so nothing live is left behind.\n *\n * Resolves with the error `completeSignIn()` rejects with.\n */\n async function settleUnfinishedSignIn(\n refreshToken: string,\n error: unknown,\n ): Promise<SessionError> {\n // No access token without facts confirmed for it: restore() refreshes.\n held = null;\n let kept = false;\n if (classifyRefreshFailure(error) === \"unavailable\") {\n try {\n kept = (await store.get()) === refreshToken;\n } catch {\n kept = false;\n }\n }\n if (kept) {\n const unavailable = new SessionError(\n \"temporarily_unavailable\",\n \"Signed in, but the identity service could not confirm the session yet; restore() finishes it.\",\n );\n update({ error: unavailable });\n return unavailable;\n }\n try {\n await store.deleteIf(refreshToken);\n } catch {\n // The store itself failed; revoking below still ends the session.\n }\n await revokeQuietly(refreshToken);\n update({ status: \"unauthenticated\", facts: null, error: null });\n return new SessionError(\n \"redemption_failed\",\n \"The sign-in could not be completed, so the session it opened was ended.\",\n );\n }\n\n /** Sign-ins and refreshes in flight, which can each rotate the credential. */\n const work = new Set<Promise<unknown>>();\n\n function tracked<T>(promise: Promise<T>): Promise<T> {\n work.add(promise);\n const forgetWork = () => {\n work.delete(promise);\n };\n promise.then(forgetWork, forgetWork);\n return promise;\n }\n\n /** Waits for work in flight, at most `signOutWaitMs`. */\n async function settleWork(): Promise<void> {\n if (work.size === 0) return;\n let timer: ReturnType<typeof setTimeout> | undefined;\n const bound = new Promise<void>((resolve) => {\n timer = setTimeout(resolve, config.signOutWaitMs ?? SIGN_OUT_WAIT_MS);\n });\n await Promise.race([Promise.allSettled([...work]), bound]);\n clearTimeout(timer);\n }\n\n async function forget(error: SessionError | null): Promise<void> {\n held = null;\n chain = null;\n chainOrg = null;\n await store.delete();\n update({ status: \"unauthenticated\", facts: null, error });\n }\n\n /**\n * Exchanges the stored refresh token. A refusal deletes it: the identity\n * service has ended that chain, so it can never succeed again. An outage\n * keeps it, since nothing was consumed.\n */\n const refresher = createRefresher<HeldToken>(() => tracked(runRefresh()));\n\n async function runRefresh(): Promise<HeldToken> {\n if (signOutsRunning > 0) throw new SessionError(\"not_signed_in\");\n const started = generation;\n const refreshToken = await store.get();\n if (!refreshToken) {\n await forget(null);\n throw new SessionError(\"not_signed_in\");\n }\n let token: TokenResponse;\n try {\n token = await createToken({\n client,\n body: {\n grant_type: \"refresh_token\",\n client_id: config.clientId,\n refresh_token: refreshToken,\n },\n });\n } catch (error) {\n if (started !== generation) throw new SessionError(\"not_signed_in\");\n if (classifyRefreshFailure(error) === \"unavailable\") {\n const unavailable = new SessionError(\"temporarily_unavailable\");\n update({ error: unavailable });\n throw unavailable;\n }\n const expired = new SessionError(\n \"session_expired\",\n `The identity service refused the stored refresh token (${oauthErrorCode(error) ?? \"rejected\"}).`,\n );\n await forget(expired);\n throw expired;\n }\n try {\n await adopt(token, started);\n } catch (error) {\n if (error instanceof SessionError) throw error;\n if (classifyRefreshFailure(error) === \"unavailable\") {\n // The rotated credential is stored; the access token is not handed\n // out without facts confirmed for it, so the next call refreshes.\n held = null;\n throw new SessionError(\"temporarily_unavailable\");\n }\n const expired = new SessionError(\n \"session_expired\",\n \"The identity service rejected the token it issued.\",\n );\n await forget(expired);\n throw expired;\n }\n if (!held) throw new SessionError(\"not_signed_in\");\n return held;\n }\n\n /**\n * A fresh token for the current target: joins or starts a refresh, and\n * refreshes once more when the one it joined was made for a target the\n * session has since moved from.\n */\n async function freshToken(): Promise<HeldToken> {\n for (let attempt = 0; attempt < 2; attempt += 1) {\n const refreshed = await refresher.refresh();\n if (sameOrg(refreshed.target, target)) return refreshed;\n }\n throw new SessionError(\n \"temporarily_unavailable\",\n \"The session moved company while a token was being issued; retry.\",\n );\n }\n\n const session: NativeSession = {\n async restore(options) {\n if (options && \"org\" in options) {\n const org = options.org ?? null;\n if (!sameOrg(org, target)) {\n target = org;\n held = null;\n }\n }\n if (current()) return snapshot.facts;\n try {\n await freshToken();\n return snapshot.facts;\n } catch (error) {\n if (\n error instanceof SessionError &&\n (error.code === \"not_signed_in\" || error.code === \"session_expired\")\n ) {\n return null;\n }\n throw error;\n }\n },\n\n async beginSignIn(options) {\n const redirectUri = options?.redirectUri ?? config.redirectUri;\n if (!redirectUri) {\n throw new SessionError(\n \"redirect_failed\",\n \"No redirect URI was configured or given for this sign-in.\",\n );\n }\n const cryptoImpl = config.crypto;\n const verifier = createCodeVerifier(cryptoImpl);\n const state = createState(cryptoImpl);\n const challenge = await createCodeChallenge(verifier, cryptoImpl);\n pending = { state, verifier, redirectUri };\n\n const url = new URL(\"/authorize\", config.identityUrl);\n url.searchParams.set(\"response_type\", \"code\");\n url.searchParams.set(\"client_id\", config.clientId);\n url.searchParams.set(\"redirect_uri\", redirectUri);\n url.searchParams.set(\"state\", state);\n url.searchParams.set(\"code_challenge\", challenge);\n url.searchParams.set(\"code_challenge_method\", \"S256\");\n if (options?.prompt) url.searchParams.set(\"prompt\", options.prompt);\n if (options?.org) url.searchParams.set(\"org\", options.org);\n const loginHint = options?.loginHint?.trim();\n if (loginHint && LOGIN_HINT_PATTERN.test(loginHint)) {\n url.searchParams.set(\"login_hint\", loginHint);\n }\n return url.toString();\n },\n\n async completeSignIn(callbackUrl) {\n // Taken before anything else, so the verifier is gone whatever happens.\n const request = pending;\n pending = null;\n if (signOutsRunning > 0) throw new SessionError(\"not_signed_in\");\n const started = generation;\n\n let callback: URL;\n try {\n callback = new URL(callbackUrl);\n } catch {\n throw new SessionError(\"state_mismatch\", \"The redirect was not a URL.\");\n }\n const state = callback.searchParams.get(\"state\");\n if (\n !request ||\n !sameRedirect(callback, new URL(request.redirectUri)) ||\n state === null ||\n state !== request.state\n ) {\n throw new SessionError(\n \"state_mismatch\",\n \"The redirect did not match a sign-in this client started.\",\n );\n }\n const error = callback.searchParams.get(\"error\");\n if (error) {\n throw new SessionError(\n error === \"access_denied\" ||\n error === \"login_required\" ||\n error === \"account_selection_required\" ||\n error === \"temporarily_unavailable\"\n ? error\n : \"redemption_failed\",\n `The identity service answered ${error}.`,\n );\n }\n const code = callback.searchParams.get(\"code\");\n if (!code) {\n throw new SessionError(\n \"redemption_failed\",\n \"The redirect had no code.\",\n );\n }\n\n let token: TokenResponse;\n try {\n token = await createToken({\n client,\n body: {\n grant_type: \"authorization_code\",\n code,\n redirect_uri: request.redirectUri,\n client_id: config.clientId,\n code_verifier: request.verifier,\n },\n });\n } catch (redeemError) {\n throw classifyRefreshFailure(redeemError) === \"unavailable\"\n ? new SessionError(\"temporarily_unavailable\")\n : new SessionError(\"redemption_failed\", \"The code was not redeemed.\");\n }\n if (!token.refresh_token) {\n throw new SessionError(\n \"redemption_failed\",\n \"The identity service issued no refresh token to a native client.\",\n );\n }\n // A new sign-in holds the company it signed in to; any company the\n // previous session had moved to by exchange is left behind.\n target = null;\n held = null;\n try {\n return await adopt(token, started);\n } catch (adoptError) {\n if (adoptError instanceof SessionError) throw adoptError;\n throw await settleUnfinishedSignIn(token.refresh_token, adoptError);\n }\n },\n\n async getAccessToken(options) {\n const fresh = current();\n if (fresh && fresh.accessToken !== options?.replacing) {\n return fresh.accessToken;\n }\n return (await freshToken()).accessToken;\n },\n\n async switchCompany(org) {\n if (signOutsRunning > 0) throw new SessionError(\"not_signed_in\");\n // A refresh in flight would exchange into the old target; let it land\n // first, so nothing it answers can follow the move.\n while (refresher.inFlight()) {\n await refresher.refresh().catch(() => undefined);\n }\n const started = generation;\n const previous = target;\n const previousHeld = held;\n target = org;\n if (!sameOrg(previous, org)) held = null;\n try {\n const base = chain && now() < chain.expiresAt - grace ? chain : null;\n if (!base) {\n await freshToken();\n if (!snapshot.facts) throw new SessionError(\"not_signed_in\");\n return snapshot.facts;\n }\n if (org === null || sameOrg(org, chainOrg)) {\n const info = await getUserinfo({ client, auth: base.accessToken });\n if (started !== generation) throw new SessionError(\"not_signed_in\");\n held = { ...base, target: org };\n const facts = toSessionFacts(info, base.expiresAt);\n update({ status: \"authenticated\", facts, error: null });\n return facts;\n }\n return await tracked(enter(org, started));\n } catch (error) {\n // A refused move leaves the session where it was. Nothing from the\n // attempted company was handed out, and the previous company's token\n // is fetched afresh on the next request.\n if (sameOrg(target, org)) {\n target = previous;\n held = started === generation ? previousHeld : null;\n }\n throw error;\n }\n },\n\n facts() {\n return snapshot.facts;\n },\n\n snapshot() {\n return snapshot;\n },\n\n async signOut() {\n signOutsRunning += 1;\n pending = null;\n held = null;\n chain = null;\n chainOrg = null;\n target = null;\n let refreshToken: string | null;\n try {\n // A sign-in or refresh already at /token may be rotating the\n // credential: let it store what it gets, so the token revoked below\n // is the live one. No new one can start meanwhile, and one that does\n // not answer in time revokes its own result once it does (adopt).\n await settleWork();\n generation += 1;\n refreshToken = await store.get();\n // The credential is deleted whatever the revocation does: a sign-out\n // must leave nothing on the device that could restore the session.\n await store.delete();\n } finally {\n signOutsRunning -= 1;\n }\n update({ status: \"unauthenticated\", facts: null, error: null });\n if (!refreshToken) return;\n try {\n await revoke(refreshToken);\n } catch {\n const error = new SessionError(\n \"sign_out_failed\",\n \"Signed out on this device, but the identity service did not confirm revoking the session.\",\n );\n update({ error });\n throw error;\n }\n },\n\n subscribe(listener) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n };\n\n // A redemption rotates nothing yet but creates the credential sign-out\n // must revoke, so sign-out waits for it like a refresh.\n const completeSignIn = session.completeSignIn;\n session.completeSignIn = (callbackUrl) =>\n tracked(completeSignIn(callbackUrl));\n return session;\n}\n","/**\n * {@link SecureStorage} adapters for the three native clients. Each takes the\n * platform module it wraps as an argument, so this package depends on none of\n * Electron, Expo, or a keyring binding; the client passes its own.\n *\n * Every adapter refuses to store anything when the platform cannot encrypt or\n * protect it, rather than falling back to a plain file (AC-7.2).\n */\n\nimport type { SecureStorage } from \"./secure-session\";\n\n/** Raised when the platform offers no protected store for a credential. */\nexport class SecureStorageUnavailableError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"SecureStorageUnavailableError\";\n }\n}\n\n// Electron: safeStorage (Keychain, DPAPI, or libsecret/kwallet)\n\n/** The part of Electron's `safeStorage` this adapter uses. */\nexport interface ElectronSafeStorage {\n isEncryptionAvailable(): boolean;\n encryptString(plainText: string): Uint8Array;\n decryptString(encrypted: Uint8Array): string;\n /** Linux only. `basic_text` means a hard-coded key: no real protection. */\n getSelectedStorageBackend?(): string;\n}\n\n/** File operations for the ciphertext, usually `node:fs/promises`. */\nexport interface CiphertextFiles {\n /** Resolves with null when the file does not exist. */\n read(path: string): Promise<Uint8Array | null>;\n /** Writes with owner-only permissions. */\n write(path: string, data: Uint8Array): Promise<void>;\n /** Succeeds when the file does not exist. */\n remove(path: string): Promise<void>;\n}\n\nexport interface ElectronSecureStorageOptions {\n readonly safeStorage: ElectronSafeStorage;\n readonly files: CiphertextFiles;\n /** Directory for the ciphertext, such as `app.getPath(\"userData\")`. */\n readonly directory: string;\n}\n\n/**\n * Encodes a storage key using only alphanumerics, `.`, `-`, and `_`, which\n * both a file name and expo-secure-store accept. The encoding is one-to-one:\n * `_` itself and every other character become `_` and six hex digits of the\n * code point, so two distinct keys, such as two profiles', never share a\n * slot. No result contains a path separator.\n */\nexport function encodeStorageKey(key: string): string {\n let encoded = \"\";\n for (const char of key) {\n encoded += /^[A-Za-z0-9.-]$/.test(char)\n ? char\n : `_${(char.codePointAt(0) ?? 0).toString(16).padStart(6, \"0\")}`;\n }\n return encoded;\n}\n\nfunction fileNameFor(key: string): string {\n return `${encodeStorageKey(key)}.secret`;\n}\n\n/**\n * Throws {@link SecureStorageUnavailableError} unless `safeStorage` encrypts\n * with a key the operating system protects. A client checks this before it\n * starts a sign-in, so it never obtains a credential it could not keep.\n */\nexport function assertElectronSafeStorageProtected(\n safeStorage: ElectronSafeStorage,\n): void {\n if (!safeStorage.isEncryptionAvailable()) {\n throw new SecureStorageUnavailableError(\n \"The operating system offers no encryption for stored credentials.\",\n );\n }\n if (safeStorage.getSelectedStorageBackend?.() === \"basic_text\") {\n throw new SecureStorageUnavailableError(\n \"No keyring is available; Electron would store the credential under a fixed key.\",\n );\n }\n}\n\n/**\n * Keeps each value encrypted by Electron's `safeStorage`, whose key is held\n * by the operating system's credential store. Only ciphertext reaches disk.\n */\nexport function createElectronSecureStorage(\n options: ElectronSecureStorageOptions,\n): SecureStorage {\n const { safeStorage, files } = options;\n const separator = options.directory.includes(\"\\\\\") ? \"\\\\\" : \"/\";\n const pathFor = (key: string) =>\n `${options.directory.replace(/[\\\\/]+$/, \"\")}${separator}${fileNameFor(key)}`;\n\n const assertProtected = () => assertElectronSafeStorageProtected(safeStorage);\n\n return {\n async get(key) {\n const data = await files.read(pathFor(key));\n if (!data) return null;\n assertProtected();\n return safeStorage.decryptString(data);\n },\n async set(key, value) {\n assertProtected();\n await files.write(pathFor(key), safeStorage.encryptString(value));\n },\n async delete(key) {\n await files.remove(pathFor(key));\n },\n };\n}\n\n// Expo: expo-secure-store (iOS Keychain, Android Keystore)\n\n/** The part of `expo-secure-store` this adapter uses. */\nexport interface ExpoSecureStore {\n isAvailableAsync?(): Promise<boolean>;\n getItemAsync(key: string, options?: object): Promise<string | null>;\n setItemAsync(key: string, value: string, options?: object): Promise<void>;\n deleteItemAsync(key: string, options?: object): Promise<void>;\n}\n\nexport interface ExpoSecureStorageOptions {\n readonly secureStore: ExpoSecureStore;\n /**\n * Passed to every call, for example\n * `{ keychainAccessible: SecureStore.WHEN_UNLOCKED_THIS_DEVICE_ONLY }`.\n */\n readonly storeOptions?: object;\n}\n\nfunction expoKey(key: string): string {\n // expo-secure-store accepts only alphanumerics, `.`, `-`, and `_`.\n return encodeStorageKey(key);\n}\n\nexport function createExpoSecureStorage(\n options: ExpoSecureStorageOptions,\n): SecureStorage {\n const { secureStore, storeOptions } = options;\n\n async function assertAvailable(): Promise<void> {\n if (\n secureStore.isAvailableAsync &&\n !(await secureStore.isAvailableAsync())\n ) {\n throw new SecureStorageUnavailableError(\n \"This device offers no secure store for credentials.\",\n );\n }\n }\n\n return {\n async get(key) {\n await assertAvailable();\n return secureStore.getItemAsync(expoKey(key), storeOptions);\n },\n async set(key, value) {\n await assertAvailable();\n await secureStore.setItemAsync(expoKey(key), value, storeOptions);\n },\n async delete(key) {\n await assertAvailable();\n await secureStore.deleteItemAsync(expoKey(key), storeOptions);\n },\n };\n}\n\n// CLI: the operating system credential store through a keyring binding\n\n/**\n * One credential in the operating system's store, shaped like\n * `@napi-rs/keyring`'s `Entry`: macOS Keychain, Windows Credential Manager,\n * or the Secret Service on Linux.\n */\nexport interface KeyringEntry {\n getPassword(): string | null | undefined;\n setPassword(password: string): void;\n deletePassword(): boolean | void;\n}\n\nexport interface KeyringSecureStorageOptions {\n /** Creates the entry for `service` and `account`, e.g. `new Entry(s, a)`. */\n readonly entry: (service: string, account: string) => KeyringEntry;\n /** The service name entries are filed under, such as `fluid-cli`. */\n readonly service: string;\n}\n\nfunction isMissingEntry(error: unknown): boolean {\n return (\n error instanceof Error &&\n /no (matching )?entry|not found/i.test(error.message)\n );\n}\n\nexport function createKeyringSecureStorage(\n options: KeyringSecureStorageOptions,\n): SecureStorage {\n return {\n async get(key) {\n try {\n return options.entry(options.service, key).getPassword() ?? null;\n } catch (error) {\n if (isMissingEntry(error)) return null;\n throw error;\n }\n },\n async set(key, value) {\n options.entry(options.service, key).setPassword(value);\n },\n async delete(key) {\n try {\n options.entry(options.service, key).deletePassword();\n } catch (error) {\n if (!isMissingEntry(error)) throw error;\n }\n },\n };\n}\n\n// Tests and ephemeral runs\n\n/** Keeps values in this process's memory only; nothing survives a restart. */\nexport function createMemorySecureStorage(): SecureStorage & {\n readonly entries: ReadonlyMap<string, string>;\n} {\n const entries = new Map<string, string>();\n return {\n entries,\n async get(key) {\n return entries.get(key) ?? null;\n },\n async set(key, value) {\n entries.set(key, value);\n },\n async delete(key) {\n entries.delete(key);\n },\n };\n}\n","/**\n * Opens a URL in the user's default browser. Best effort: the sign-in URL is\n * always printed too, for a terminal with no browser to open.\n */\n\nimport { execFile } from \"node:child_process\";\n\nfunction opener(url: string): { command: string; args: string[] } {\n switch (process.platform) {\n case \"darwin\":\n return { command: \"open\", args: [url] };\n case \"win32\":\n // Not `start`, which goes through cmd.exe and would read `&` in the\n // query as a command separator.\n return {\n command: \"rundll32\",\n args: [\"url.dll,FileProtocolHandler\", url],\n };\n default:\n return { command: \"xdg-open\", args: [url] };\n }\n}\n\nexport function openBrowser(url: string): void {\n if (process.env[\"FLUID_NO_BROWSER\"]) return;\n const launch = opener(url);\n try {\n // The callback takes any failure, such as no opener on this machine:\n // the printed URL is then the way in.\n execFile(launch.command, launch.args, () => undefined);\n } catch {\n // As above.\n }\n}\n","/**\n * Which identity service the CLI signs in with, following the API base the\n * CLI already talks to (`FLUID_API_BASE`, production by default).\n */\n\n/** The CLI's registered public client at the identity service. */\nexport const IDENTITY_CLIENT_ID = \"fluid-cli\";\n\n/** The credential store service name the CLI files its sessions under. */\nexport const KEYRING_SERVICE = \"fluid-cli\";\n\nexport const PRODUCTION_IDENTITY_URL = \"https://identity.fluid.app\";\nexport const STAGING_IDENTITY_URL = \"https://identity.stage.fluid.app\";\n\nconst STAGING_API_HOSTS = [/\\.stage\\.fluid\\.app$/];\n\nfunction isLoopbackHost(hostname: string): boolean {\n return (\n hostname === \"localhost\" ||\n hostname === \"127.0.0.1\" ||\n hostname === \"[::1]\" ||\n hostname.endsWith(\".localhost\")\n );\n}\n\n/**\n * The identity service origin for an API base, or null when the API base\n * names no environment with an identity service (a local Rails, say) and\n * `FLUID_IDENTITY_URL` does not name one either.\n *\n * `FLUID_IDENTITY_URL` overrides the derivation. It must be `https`, or\n * `http` on a loopback host for local development.\n */\nexport function resolveIdentityUrl(\n apiBase: string,\n env: NodeJS.ProcessEnv = process.env,\n): string | null {\n const override = env[\"FLUID_IDENTITY_URL\"];\n if (override) {\n let url: URL;\n try {\n url = new URL(override);\n } catch {\n throw new Error(`FLUID_IDENTITY_URL is not a URL: ${override}`);\n }\n const secure =\n url.protocol === \"https:\" ||\n (url.protocol === \"http:\" && isLoopbackHost(url.hostname));\n if (!secure) {\n throw new Error(\n \"FLUID_IDENTITY_URL must use https (http only on a loopback host).\",\n );\n }\n return url.origin;\n }\n\n let hostname: string;\n try {\n hostname = new URL(apiBase).hostname;\n } catch {\n return null;\n }\n if (hostname === \"api.fluid.app\") return PRODUCTION_IDENTITY_URL;\n if (STAGING_API_HOSTS.some((pattern) => pattern.test(hostname))) {\n return STAGING_IDENTITY_URL;\n }\n return null;\n}\n","/**\n * The operating system credential store for identity sessions: macOS\n * Keychain, Windows Credential Manager, or the Secret Service on Linux,\n * through `@napi-rs/keyring`.\n *\n * The binding is loaded only when an identity session needs it, so a\n * platform without a prebuilt binary keeps every legacy command working.\n * There is no fallback to a file: without a credential store, identity\n * sign-in refuses to run (Phase 006 AC-7.2).\n */\n\nimport {\n SecureStorageUnavailableError,\n createKeyringSecureStorage,\n type SecureStorage,\n} from \"@fluid-app/auth/native\";\n\nimport { KEYRING_SERVICE } from \"./environment.js\";\n\nlet loaded: Promise<SecureStorage> | null = null;\n\nexport function loadKeyringStorage(): Promise<SecureStorage> {\n loaded ??= import(\"@napi-rs/keyring\").then(\n ({ Entry }) =>\n createKeyringSecureStorage({\n entry: (service, account) => new Entry(service, account),\n service: KEYRING_SERVICE,\n }),\n (error: unknown) => {\n loaded = null;\n const reason = error instanceof Error ? error.message : String(error);\n throw new SecureStorageUnavailableError(\n `The operating system credential store could not be loaded (${reason}).`,\n );\n },\n );\n return loaded;\n}\n","/**\n * The loopback redirect for a native sign-in (RFC 8252 sections 7.3 and 8.3):\n * a one-shot HTTP server on 127.0.0.1, on a port the operating system picks,\n * that takes the identity service's redirect to `/callback`.\n *\n * It answers only the redirect for the sign-in in progress: a request to any\n * other path, or one whose `state` is not this sign-in's, is refused and the\n * server keeps waiting. It closes after the one redirect it accepts, or when\n * the wait times out.\n */\n\nimport { timingSafeEqual } from \"node:crypto\";\nimport {\n createServer,\n type IncomingMessage,\n type ServerResponse,\n} from \"node:http\";\nimport type { AddressInfo } from \"node:net\";\n\nexport const CALLBACK_PATH = \"/callback\";\nexport const DEFAULT_CALLBACK_TIMEOUT_MS = 5 * 60 * 1000;\n\nexport class LoopbackTimeoutError extends Error {\n constructor() {\n super(\"Timed out waiting for the browser to finish signing in.\");\n this.name = \"LoopbackTimeoutError\";\n }\n}\n\nexport interface LoopbackServer {\n /** `http://127.0.0.1:<port>/callback`, to register as the redirect URI. */\n readonly redirectUri: string;\n /**\n * Resolves with the full redirect URL once a request to `/callback`\n * carries `expectedState`, then closes the server. Rejects with\n * {@link LoopbackTimeoutError} when none arrives within the timeout.\n */\n waitForCallback(expectedState: string): Promise<string>;\n /** Closes the server; safe to call more than once. */\n close(): Promise<void>;\n}\n\nexport interface LoopbackServerOptions {\n /** How long `waitForCallback` waits, in milliseconds. Defaults to 5 min. */\n readonly timeoutMs?: number;\n}\n\nfunction sameState(received: string, expected: string): boolean {\n const a = Buffer.from(received);\n const b = Buffer.from(expected);\n return a.length === b.length && timingSafeEqual(a, b);\n}\n\nfunction escapeHtml(text: string): string {\n return text\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\"/g, \"&quot;\");\n}\n\nfunction page(\n response: ServerResponse,\n status: number,\n title: string,\n message: string,\n): void {\n const body = `<!doctype html><html><head><meta charset=\"utf-8\"><title>${escapeHtml(title)}</title></head><body style=\"font-family:system-ui,sans-serif;max-width:32rem;margin:4rem auto;padding:0 1rem\"><h1 style=\"font-size:1.25rem\">${escapeHtml(title)}</h1><p>${escapeHtml(message)}</p></body></html>`;\n response.writeHead(status, {\n \"Content-Type\": \"text/html; charset=utf-8\",\n \"Cache-Control\": \"no-store\",\n // The redirect URL holds a code; keep it out of any onward request.\n \"Referrer-Policy\": \"no-referrer\",\n Connection: \"close\",\n });\n response.end(body);\n}\n\n/** Starts the server on 127.0.0.1 with an ephemeral port. */\nexport async function startLoopbackServer(\n options: LoopbackServerOptions = {},\n): Promise<LoopbackServer> {\n const timeoutMs = options.timeoutMs ?? DEFAULT_CALLBACK_TIMEOUT_MS;\n let expectedState: string | null = null;\n let settle: {\n resolve: (url: string) => void;\n reject: (error: Error) => void;\n } | null = null;\n let done = false;\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n const server = createServer(\n (request: IncomingMessage, response: ServerResponse) => {\n const url = new URL(request.url ?? \"/\", \"http://127.0.0.1\");\n if (done) {\n page(response, 410, \"Sign-in finished\", \"You can close this tab.\");\n return;\n }\n if (request.method !== \"GET\" || url.pathname !== CALLBACK_PATH) {\n page(response, 404, \"Not found\", \"This is not a Fluid CLI sign-in.\");\n return;\n }\n const state = url.searchParams.get(\"state\");\n if (\n expectedState === null ||\n state === null ||\n !sameState(state, expectedState)\n ) {\n page(\n response,\n 400,\n \"Sign-in not recognised\",\n \"This redirect does not belong to the sign-in the Fluid CLI is waiting for.\",\n );\n return;\n }\n\n done = true;\n const failed = url.searchParams.has(\"error\");\n page(\n response,\n failed ? 400 : 200,\n failed ? \"Sign-in did not complete\" : \"Signed in to the Fluid CLI\",\n failed\n ? \"Return to your terminal for details.\"\n : \"You can close this tab and return to your terminal.\",\n );\n const callbackUrl = new URL(request.url ?? \"/\", redirectUri).toString();\n const waiting = settle;\n settle = null;\n waiting?.resolve(callbackUrl);\n void close();\n },\n );\n\n await new Promise<void>((resolve, reject) => {\n server.once(\"error\", reject);\n server.listen(0, \"127.0.0.1\", () => {\n server.off(\"error\", reject);\n resolve();\n });\n });\n const { port } = server.address() as AddressInfo;\n const redirectUri = `http://127.0.0.1:${port}${CALLBACK_PATH}`;\n\n let closing: Promise<void> | null = null;\n function close(): Promise<void> {\n done = true;\n clearTimeout(timer);\n const waiting = settle;\n settle = null;\n waiting?.reject(new Error(\"The sign-in was cancelled.\"));\n closing ??= new Promise<void>((resolve) => {\n server.close(() => resolve());\n server.closeAllConnections();\n });\n return closing;\n }\n\n return {\n redirectUri,\n waitForCallback(state) {\n if (done) {\n return Promise.reject(new Error(\"The loopback server is closed.\"));\n }\n if (settle || expectedState !== null) {\n return Promise.reject(new Error(\"Already waiting for a callback.\"));\n }\n expectedState = state;\n return new Promise<string>((resolve, reject) => {\n settle = { resolve, reject };\n timer = setTimeout(() => {\n settle = null;\n reject(new LoopbackTimeoutError());\n void close();\n }, timeoutMs);\n });\n },\n close,\n };\n}\n","/**\n * Identity-mode sessions for the CLI (Phase 006 FR-7): the authorization\n * code flow with PKCE through the system browser and a loopback redirect,\n * with the rotating refresh token in the operating system credential store\n * and the access token only in this process's memory.\n *\n * Built on `@fluid-app/auth/native`. Session facts come from the identity\n * service's `/userinfo`, never from decoding a token (INV-6.3). A profile\n * that moved company with `fluid switch` keeps its own refresh chain and\n * exchanges into that company again whenever it needs a token.\n */\n\nimport { randomUUID } from \"node:crypto\";\n\nimport {\n SessionError,\n createNativeSession,\n exchangeNativeToken,\n type ExchangedToken,\n type NativeSession,\n type SecureStorage,\n type SessionFacts,\n type SessionRole,\n} from \"@fluid-app/auth/native\";\n\nimport type {\n IdentityProfile,\n IdentityProfileAuth,\n} from \"../../config/types.js\";\nimport { openBrowser } from \"./browser.js\";\nimport { IDENTITY_CLIENT_ID } from \"./environment.js\";\nimport { loadKeyringStorage } from \"./keyring.js\";\nimport {\n DEFAULT_CALLBACK_TIMEOUT_MS,\n startLoopbackServer,\n} from \"./loopback.js\";\n\n/** What an identity session needs from the machine; replaced in tests. */\nexport interface IdentityDeps {\n /** The credential store. Defaults to the operating system's. */\n readonly storage: () => Promise<SecureStorage>;\n readonly fetch?: typeof fetch;\n readonly openBrowser: (url: string) => void;\n readonly now: () => number;\n readonly callbackTimeoutMs: number;\n /**\n * Waits before each further attempt to finish a sign-in whose code was\n * redeemed while the identity service could not confirm it.\n */\n readonly finishRetryDelaysMs: readonly number[];\n}\n\nconst defaultDeps: IdentityDeps = {\n storage: loadKeyringStorage,\n openBrowser,\n now: Date.now,\n callbackTimeoutMs: DEFAULT_CALLBACK_TIMEOUT_MS,\n finishRetryDelaysMs: [1_000, 2_000, 4_000],\n};\n\nlet deps: IdentityDeps = defaultDeps;\n\n/** Test seam: replaces the machine dependencies until the next reset. */\nexport function configureIdentityForTesting(\n overrides: Partial<IdentityDeps>,\n): void {\n deps = { ...defaultDeps, ...overrides };\n resetIdentitySessions();\n}\n\n// A refresh never redirects, so a session restored from storage names a\n// redirect URI only because the session config requires one.\nconst RESTORE_REDIRECT_URI = \"http://127.0.0.1/callback\";\n\n/** How long before expiry a held token counts as stale. */\nconst EXPIRY_MARGIN_MS = 30_000;\n\ninterface HeldSession {\n readonly session: NativeSession;\n /**\n * Tokens made by exchange, by company id for a move to another company and\n * by company id and roles for a narrowing (see {@link exchangeKey}).\n */\n readonly exchanged: Map<string, ExchangedToken>;\n}\n\nconst sessions = new Map<string, HeldSession>();\n\n/** The latest token handed out per profile, for synchronous readers. */\nconst latest = new Map<string, { token: string; expiresAt: number }>();\nconst refreshTimers = new Map<string, ReturnType<typeof setTimeout>>();\n\n/** Forgets every session held in memory. Storage is untouched. */\nexport function resetIdentitySessions(): void {\n sessions.clear();\n latest.clear();\n for (const timer of refreshTimers.values()) clearTimeout(timer);\n refreshTimers.clear();\n}\n\nfunction sessionKey(auth: IdentityProfileAuth): string {\n return `${auth.identityUrl} ${auth.storageKey}`;\n}\n\nasync function heldSession(auth: IdentityProfileAuth): Promise<HeldSession> {\n const key = sessionKey(auth);\n const existing = sessions.get(key);\n if (existing) return existing;\n const storage = await deps.storage();\n const raced = sessions.get(key);\n if (raced) return raced;\n const held: HeldSession = {\n session: createNativeSession({\n identityUrl: auth.identityUrl,\n clientId: IDENTITY_CLIENT_ID,\n redirectUri: RESTORE_REDIRECT_URI,\n storage,\n storageKey: auth.storageKey,\n fetch: deps.fetch,\n now: deps.now,\n }),\n exchanged: new Map(),\n };\n sessions.set(key, held);\n return held;\n}\n\nexport interface IdentityToken {\n readonly accessToken: string;\n readonly expiresAt: number;\n /** For the token's own company: an exchanged token's when moved. */\n readonly facts: SessionFacts;\n}\n\n/** The roles a token is narrowed to, in one stable order; none for no narrowing. */\nfunction narrowedTo(roles: readonly SessionRole[] | undefined): SessionRole[] {\n return [...new Set(roles ?? [])].sort();\n}\n\n/**\n * Where an exchanged token is held. A narrowed token is held apart from the\n * company's un-narrowed one, so asking for either never hands out the other.\n */\nfunction exchangeKey(org: string, roles: readonly SessionRole[]): string {\n return roles.length === 0 ? org : `${org} ${roles.join(\" \")}`;\n}\n\nasync function exchangeInto(\n auth: IdentityProfileAuth,\n held: HeldSession,\n org: string,\n roles: readonly SessionRole[],\n): Promise<IdentityToken> {\n const config = {\n identityUrl: auth.identityUrl,\n clientId: IDENTITY_CLIENT_ID,\n fetch: deps.fetch,\n now: deps.now,\n };\n const target = { org, roles: roles.length > 0 ? roles : null };\n const subject = await held.session.getAccessToken();\n let exchanged: ExchangedToken;\n try {\n exchanged = await exchangeNativeToken(config, subject, target);\n } catch (error) {\n if (!(error instanceof SessionError) || error.code !== \"session_expired\") {\n throw error;\n }\n // The subject token was not accepted: renew it once and try again.\n const renewed = await held.session.getAccessToken({ replacing: subject });\n exchanged = await exchangeNativeToken(config, renewed, target);\n }\n held.exchanged.set(exchangeKey(org, roles), exchanged);\n return exchanged;\n}\n\n/**\n * The identity service would not narrow a session to roles the person does not\n * hold in the company, for example `rep` for an admin with no rep standing.\n * Matched by `code`, not `instanceof`, because a plugin may load its own copy\n * of this package.\n */\nexport class RoleNotGrantedError extends Error {\n readonly code = \"role_not_granted\";\n constructor(\n readonly roles: readonly SessionRole[],\n /** The company's id. */\n readonly org: string,\n /** The company's name, when the caller knows it. */\n readonly companyName?: string,\n ) {\n super(\n `The identity service did not grant ${roles.join(\" and \")} at ${companyName ?? org}.`,\n );\n this.name = \"RoleNotGrantedError\";\n }\n}\n\n/** Whether an error is a {@link RoleNotGrantedError}, from any copy of it. */\nexport function isRoleNotGrantedError(\n error: unknown,\n): error is RoleNotGrantedError {\n return (\n error instanceof Error &&\n (error as { code?: unknown }).code === \"role_not_granted\"\n );\n}\n\n/**\n * A current access token for an identity profile and the facts behind it,\n * restoring the session from the credential store without a prompt and\n * refreshing or exchanging as needed. `org` defaults to the company the\n * profile moved to; null asks for the session's own company. Rejects with\n * `not_signed_in` when no session is stored or the stored one has ended.\n *\n * `roles` narrows the token to those roles in that company (RFC 8693): the\n * identity service grants it only when the person holds them there now, and\n * the narrowed token is held apart from the un-narrowed one. A session whose\n * own token carries no role beyond `roles` is already narrow enough and keeps\n * its token. Rejects with {@link RoleNotGrantedError} when the service refuses.\n * Without `roles` the token is the company's as before.\n */\nexport async function identityToken(\n auth: IdentityProfileAuth,\n org: string | null = auth.org ?? null,\n roles?: readonly SessionRole[],\n): Promise<IdentityToken> {\n const held = await heldSession(auth);\n const facts = await held.session.restore();\n if (!facts) {\n throw new SessionError(\"not_signed_in\", \"No identity session is stored.\");\n }\n const accessToken = await held.session.getAccessToken();\n const chainFacts = held.session.facts() ?? facts;\n const narrowing = narrowedTo(roles);\n if (narrowing.length === 0) {\n if (!org || org === chainFacts.companyId) {\n return {\n accessToken,\n expiresAt: chainFacts.expiresAt,\n facts: chainFacts,\n };\n }\n const cached = held.exchanged.get(exchangeKey(org, narrowing));\n if (cached && deps.now() < cached.expiresAt - EXPIRY_MARGIN_MS) {\n return cached;\n }\n return exchangeInto(auth, held, org, narrowing);\n }\n\n const target = org ?? chainFacts.companyId;\n if (!target) {\n throw new SessionError(\n \"access_denied\",\n \"A platform session names no company to narrow in.\",\n );\n }\n if (\n target === chainFacts.companyId &&\n chainFacts.roles.length > 0 &&\n chainFacts.roles.every((role) => narrowing.includes(role))\n ) {\n return { accessToken, expiresAt: chainFacts.expiresAt, facts: chainFacts };\n }\n const cached = held.exchanged.get(exchangeKey(target, narrowing));\n if (cached && deps.now() < cached.expiresAt - EXPIRY_MARGIN_MS) {\n return cached;\n }\n try {\n return await exchangeInto(auth, held, target, narrowing);\n } catch (error) {\n if (error instanceof SessionError && error.code === \"access_denied\") {\n throw new RoleNotGrantedError(narrowing, target);\n }\n throw error;\n }\n}\n\n/** The company the session itself signed in to, with no exchange. */\nexport async function sessionFacts(\n auth: IdentityProfileAuth,\n): Promise<SessionFacts> {\n return (await identityToken(auth, null)).facts;\n}\n\n/**\n * An access token for a profile, recorded for {@link peekIdentityToken}.\n * `roles` narrows it as {@link identityToken} does, and a narrowed token is\n * not recorded there. A refusal to narrow rejects with a\n * {@link RoleNotGrantedError} that names the profile's company.\n */\nexport async function profileAccessToken(\n profile: IdentityProfile,\n roles?: readonly SessionRole[],\n): Promise<string> {\n let token: IdentityToken;\n try {\n token = await identityToken(profile.auth, profile.auth.org ?? null, roles);\n } catch (error) {\n if (isRoleNotGrantedError(error)) {\n throw new RoleNotGrantedError(\n error.roles,\n error.org,\n profile.companyName,\n );\n }\n throw error;\n }\n // Synchronous readers take the un-narrowed token; a narrowed one is asked\n // for again each time, and the held exchange answers from memory.\n if (narrowedTo(roles).length === 0) {\n latest.set(profile.name, {\n token: token.accessToken,\n expiresAt: token.expiresAt,\n });\n }\n return token.accessToken;\n}\n\n/**\n * The token last handed out for a profile while it is still current, for\n * synchronous readers such as plugins' `getAuthToken()`. Null when the\n * profile's session has not been primed in this process.\n */\nexport function peekIdentityToken(profileName: string): string | null {\n const held = latest.get(profileName);\n if (!held || deps.now() >= held.expiresAt - EXPIRY_MARGIN_MS) return null;\n return held.token;\n}\n\n/**\n * Restores a profile's session before a command runs, so synchronous token\n * readers find a current token in memory, and keeps it current for a\n * long-running command by refreshing shortly before it expires. The timer\n * never keeps the process alive.\n */\nexport async function primeIdentityProfile(\n profile: IdentityProfile,\n): Promise<void> {\n await profileAccessToken(profile);\n const held = latest.get(profile.name);\n if (!held) return;\n clearTimeout(refreshTimers.get(profile.name));\n const delay = Math.max(\n held.expiresAt - deps.now() - 2 * EXPIRY_MARGIN_MS,\n 5_000,\n );\n const timer = setTimeout(() => {\n refreshTimers.delete(profile.name);\n primeIdentityProfile(profile).catch(() => {\n // The held token expires and readers see none; the next command that\n // needs one reports the sign-in as ended.\n });\n }, delay);\n timer.unref();\n refreshTimers.set(profile.name, timer);\n}\n\n/**\n * Revokes the session's refresh token and deletes it from the credential\n * store. Rejects with `sign_out_failed` when the credential was deleted but\n * the identity service did not confirm the revocation.\n */\nexport async function signOutIdentity(\n auth: IdentityProfileAuth,\n): Promise<void> {\n const held = await heldSession(auth);\n sessions.delete(sessionKey(auth));\n await held.session.signOut();\n}\n\n/**\n * Finishes a sign-in the identity service could not confirm. Its code may\n * already be redeemed and its session stored, and the code is spent either\n * way, so the stored session is the only way in: it is restored, retrying\n * while the service stays unavailable. Whenever that does not succeed --\n * nothing was stored, the service ended the session, the credential store\n * failed, or the service never answered -- the session is signed out\n * (revoked and deleted) before the error is reported, so no live session is\n * left behind with no profile naming it.\n */\nasync function finishStoredSignIn(\n session: NativeSession,\n unavailable: SessionError,\n): Promise<SessionFacts> {\n let failure: unknown = unavailable;\n try {\n for (const delay of deps.finishRetryDelaysMs) {\n await new Promise((resolve) => setTimeout(resolve, delay));\n try {\n const facts = await session.restore();\n if (facts) return facts;\n // Nothing stored, or the service ended the session.\n failure = unavailable;\n break;\n } catch (error) {\n failure = error;\n if (\n !(error instanceof SessionError) ||\n error.code !== \"temporarily_unavailable\"\n ) {\n break;\n }\n }\n }\n } catch (error) {\n failure = error;\n }\n await session.signOut().catch(() => undefined);\n throw failure;\n}\n\nexport interface BrowserSignInOptions {\n readonly identityUrl: string;\n /** Company `uuid_v7` to sign in to, when the caller knows it. */\n readonly org?: string;\n /** Prints a line for the user; the sign-in URL goes through here. */\n readonly log: (line: string) => void;\n}\n\nexport interface BrowserSignIn {\n readonly auth: IdentityProfileAuth;\n readonly facts: SessionFacts;\n readonly accessToken: string;\n}\n\n/**\n * Signs in through the system browser: opens the identity service's\n * `/authorize` with PKCE, takes the redirect on a loopback server, and\n * redeems the code. The refresh token goes to the credential store under a\n * new key, which the returned `auth` names.\n */\nexport async function signInWithBrowser(\n options: BrowserSignInOptions,\n): Promise<BrowserSignIn> {\n const storage = await deps.storage();\n const storageKey = `identity.${randomUUID()}`;\n // Fail before the browser opens, not after the code is redeemed, when the\n // credential store cannot hold anything.\n await storage.set(storageKey, \"pending\");\n await storage.delete(storageKey);\n\n const server = await startLoopbackServer({\n timeoutMs: deps.callbackTimeoutMs,\n });\n const session = createNativeSession({\n identityUrl: options.identityUrl,\n clientId: IDENTITY_CLIENT_ID,\n redirectUri: server.redirectUri,\n storage,\n storageKey,\n fetch: deps.fetch,\n now: deps.now,\n });\n\n let callbackUrl: string;\n try {\n const authorizeUrl = await session.beginSignIn(\n options.org ? { org: options.org } : undefined,\n );\n const state = new URL(authorizeUrl).searchParams.get(\"state\") ?? \"\";\n options.log(\"Opening your browser to sign in. If it does not open, visit:\");\n options.log(authorizeUrl);\n deps.openBrowser(authorizeUrl);\n callbackUrl = await server.waitForCallback(state);\n } finally {\n await server.close();\n }\n\n let facts: SessionFacts;\n try {\n facts = await session.completeSignIn(callbackUrl);\n } catch (error) {\n if (\n !(error instanceof SessionError) ||\n error.code !== \"temporarily_unavailable\"\n ) {\n throw error;\n }\n // Whether a credential was stored is not asked of the store here: one\n // failed read must not leave a stored session unreachable.\n facts = await finishStoredSignIn(session, error);\n }\n const auth: IdentityProfileAuth = {\n mode: \"identity\",\n identityUrl: options.identityUrl,\n storageKey,\n };\n sessions.set(sessionKey(auth), { session, exchanged: new Map() });\n return { auth, facts, accessToken: await session.getAccessToken() };\n}\n\n/** A sentence for the user about an identity failure. */\nexport function describeIdentityError(error: unknown): string {\n if (error instanceof SessionError) {\n switch (error.code) {\n case \"not_signed_in\":\n case \"session_expired\":\n return \"Your Fluid sign-in has ended. Run `fluid login` to sign in again.\";\n case \"access_denied\":\n return \"The Fluid identity service refused the request.\";\n case \"account_selection_required\":\n return \"Choose a company in the browser to finish signing in.\";\n case \"state_mismatch\":\n return \"The browser returned a sign-in this CLI did not start.\";\n case \"temporarily_unavailable\":\n return \"The Fluid identity service could not be reached. Try again shortly.\";\n case \"sign_out_failed\":\n return \"Signed out on this machine, but the identity service did not confirm ending the session.\";\n default:\n return `Identity sign-in failed (${error.code}).`;\n }\n }\n if (\n error instanceof Error &&\n error.name === \"SecureStorageUnavailableError\"\n ) {\n return `Identity sign-in needs the operating system credential store (Keychain, Credential Manager, or Secret Service). ${error.message}`;\n }\n return error instanceof Error ? error.message : String(error);\n}\n","/**\n * XDG-compliant config directory resolution\n *\n * Priority:\n * 1. FLUID_CONFIG_DIR env var (explicit override)\n * 2. ~/.fluid/ on macOS (convention for CLI tools)\n * 3. %APPDATA%/fluid/ on Windows\n * 4. $XDG_CONFIG_HOME/fluid/ on Linux (XDG spec)\n * 5. ~/.config/fluid/ fallback on Linux\n */\n\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nexport function getConfigDir(): string {\n const envOverride = process.env[\"FLUID_CONFIG_DIR\"];\n if (envOverride) return envOverride;\n\n if (process.platform === \"darwin\") {\n return join(homedir(), \".fluid\");\n }\n\n if (process.platform === \"win32\") {\n const appData =\n process.env[\"APPDATA\"] ?? join(homedir(), \"AppData\", \"Roaming\");\n return join(appData, \"fluid\");\n }\n\n const xdgConfig = process.env[\"XDG_CONFIG_HOME\"];\n if (xdgConfig) return join(xdgConfig, \"fluid\");\n\n return join(homedir(), \".config\", \"fluid\");\n}\n\nexport function getConfigFilePath(): string {\n return join(getConfigDir(), \"config.json\");\n}\n","/**\n * Whether this process runs under a host that supplies the base URL and\n * credential through the environment alone. The host sets\n * FLUID_RUNTIME_STRICT_CONFIG=1 on its CLI children; setting it only removes\n * the CLI's config-file authentication, it never adds any.\n */\nexport function isStrictConfig(): boolean {\n return process.env[\"FLUID_RUNTIME_STRICT_CONFIG\"] === \"1\";\n}\n","/**\n * Read/write config.json with atomic writes and safe defaults\n */\n\nimport {\n existsSync,\n mkdirSync,\n readFileSync,\n renameSync,\n unlinkSync,\n writeFileSync,\n} from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { randomBytes } from \"node:crypto\";\nimport { type FluidConfig, createDefaultConfig } from \"./types.js\";\nimport { getConfigFilePath } from \"./paths.js\";\nimport { isStrictConfig } from \"./strict.js\";\n\n/** Under strict config (see {@link isStrictConfig}) stored profiles are dropped; `updateConfig` still sees the stored file. */\nexport function readConfig(): FluidConfig {\n const config = readStoredConfig();\n return isStrictConfig()\n ? { ...config, activeProfile: null, profiles: {} }\n : config;\n}\n\nfunction readStoredConfig(): FluidConfig {\n const configPath = getConfigFilePath();\n\n if (!existsSync(configPath)) {\n return createDefaultConfig();\n }\n\n const raw = readFileSync(configPath, \"utf8\");\n\n let parsed: Partial<FluidConfig>;\n try {\n parsed = JSON.parse(raw) as Partial<FluidConfig>;\n } catch {\n // Corrupted config — fall back to defaults rather than crashing\n return createDefaultConfig();\n }\n\n // Merge with defaults to handle schema evolution\n return withDurableWorkspaceAliases({\n ...createDefaultConfig(),\n ...parsed,\n });\n}\n\nexport function writeConfig(config: FluidConfig): void {\n const configPath = getConfigFilePath();\n const dir = dirname(configPath);\n\n // 0o700: owner-only access so other users cannot enumerate the config dir\n mkdirSync(dir, { recursive: true, mode: 0o700 });\n\n // Atomic write: write to a temp file in the same directory, then rename.\n // rename() is a POSIX atomic operation — a crash mid-write will never leave\n // a partially-written config.json. The temp file must be on the same\n // filesystem as the destination for rename() to succeed.\n const suffix = randomBytes(6).toString(\"hex\");\n const tmpPath = join(dir, `.fluid-config-${suffix}.tmp`);\n\n try {\n writeFileSync(\n tmpPath,\n JSON.stringify(withDurableWorkspaceAliases(config), null, 2) + \"\\n\",\n {\n encoding: \"utf8\",\n // NOTE: mode: 0o600 restricts to owner read/write on POSIX (macOS, Linux).\n // On Windows, Node.js ignores this option — NTFS permissions are not\n // set via the mode parameter. Windows file protection would require\n // icacls or Windows security descriptor APIs.\n mode: 0o600,\n },\n );\n renameSync(tmpPath, configPath);\n } catch (err) {\n // Clean up the temp file if something went wrong before the rename\n try {\n unlinkSync(tmpPath);\n } catch {\n // ignore cleanup errors — the temp file may not exist yet\n }\n throw err;\n }\n}\n\nfunction workspaceSlug(value: string): string {\n return value\n .toLowerCase()\n .trim()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-|-$/g, \"\");\n}\n\nfunction storefrontSlug(value: string): string {\n const withoutScheme = value.trim().replace(/^[a-z][a-z0-9+.-]*:\\/\\//i, \"\");\n const host = withoutScheme.split(/[/?#]/, 1)[0]?.replace(/:\\d+$/, \"\") ?? \"\";\n return workspaceSlug(host.replace(/\\.fluid\\.app$/i, \"\"));\n}\n\n/** Preserve rename history even when `fluid logout` clears the profiles that\n * originally carried it. Derivation on read migrates existing config files;\n * normalization on every write makes the migration durable before profiles\n * are removed. */\nfunction withDurableWorkspaceAliases(config: FluidConfig): FluidConfig {\n const aliases: Record<string, string[]> = {};\n const add = (storefront: string, candidates: readonly string[]): void => {\n const key = storefrontSlug(storefront);\n if (!key) return;\n const values = new Set(aliases[key] ?? []);\n for (const candidate of candidates) {\n const slug = workspaceSlug(candidate);\n if (slug) values.add(slug);\n }\n if (values.size > 0) aliases[key] = [...values];\n };\n\n for (const [storefront, candidates] of Object.entries(\n config.workspaceAliasesByFluidShop ?? {},\n )) {\n if (!Array.isArray(candidates)) continue;\n add(\n storefront,\n candidates.filter(\n (candidate): candidate is string => typeof candidate === \"string\",\n ),\n );\n }\n for (const profile of Object.values(config.profiles)) {\n if (profile.fluidShop) {\n add(profile.fluidShop, [profile.name, profile.companyName]);\n }\n }\n\n return Object.keys(aliases).length > 0 ||\n config.workspaceAliasesByFluidShop !== undefined\n ? { ...config, workspaceAliasesByFluidShop: aliases }\n : config;\n}\n\nexport function updateConfig(\n updater: (config: FluidConfig) => FluidConfig,\n): FluidConfig {\n const config = readStoredConfig();\n const updated = updater(config);\n writeConfig(updated);\n return updated;\n}\n","/**\n * Project-level config (.fluidrc) reader.\n *\n * Walks upward from a starting directory to find a `.fluidrc` file.\n * The file is JSON with a `profile` field that maps to a profile name\n * in `~/.fluid/config.json`.\n *\n * This allows portal repos to pin themselves to a specific CLI profile\n * so that `fluid portal pull` / `push` always use the correct company\n * credentials, regardless of which profile is globally active.\n */\n\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\n\nexport interface ProjectRcConfig {\n readonly profile: string;\n}\n\nconst RC_FILENAME = \".fluidrc\";\n\n/** Cached result keyed by the starting directory. */\nlet cachedResult: { dir: string; config: ProjectRcConfig | null } | undefined;\n\n/**\n * Walk upward from `startDir` looking for a `.fluidrc` file.\n *\n * Returns the parsed config when found, or `null` if no valid `.fluidrc`\n * exists in any ancestor directory.\n *\n * The result is cached per starting directory for the lifetime of the\n * process — CLI commands are short-lived so stale-cache is not a concern.\n */\nexport function findProjectConfig(startDir: string): ProjectRcConfig | null {\n if (cachedResult && cachedResult.dir === startDir) {\n return cachedResult.config;\n }\n\n let dir = startDir;\n while (true) {\n const rcPath = join(dir, RC_FILENAME);\n if (existsSync(rcPath)) {\n try {\n const raw = readFileSync(rcPath, \"utf8\");\n const parsed = JSON.parse(raw) as Partial<ProjectRcConfig>;\n if (typeof parsed.profile === \"string\" && parsed.profile.length > 0) {\n const config: ProjectRcConfig = { profile: parsed.profile };\n cachedResult = { dir: startDir, config };\n return config;\n }\n } catch {\n // Malformed .fluidrc — treat as absent\n }\n\n // Found a .fluidrc but it was invalid or empty — stop searching\n // upward to avoid confusing inheritance from a parent directory.\n cachedResult = { dir: startDir, config: null };\n return null;\n }\n\n const parent = dirname(dir);\n if (parent === dir) break; // reached filesystem root\n dir = parent;\n }\n\n cachedResult = { dir: startDir, config: null };\n return null;\n}\n\n/** Reset the cache. Exposed for tests only. */\nexport function _resetProjectConfigCache(): void {\n cachedResult = undefined;\n}\n","/**\n * Token storage and retrieval from config\n */\n\nimport { readConfig } from \"../config/config.js\";\nimport { findProjectConfig } from \"../config/project-config.js\";\nimport { type FluidProfile, isIdentityProfile } from \"../config/types.js\";\nimport { peekIdentityToken, profileAccessToken } from \"./identity/session.js\";\n\n/** A role a company access token can be narrowed to. */\nexport type AuthRole = \"root_admin\" | \"admin\" | \"rep\";\n\n/**\n * Where a command's credential comes from: a literal token (a flag or an\n * environment variable), or a stored profile.\n */\nexport type CredentialSource =\n | { readonly kind: \"token\"; readonly token: string }\n | { readonly kind: \"profile\"; readonly profile: FluidProfile };\n\n/**\n * Resolve the credential for a named profile, or for the active one when\n * omitted.\n *\n * Resolution order (when `profileName` is omitted — mirrors\n * `createCommandContext` in domain/context.ts):\n * 1. `FLUID_TOKEN` / `FLUID_API_TOKEN` environment variables\n * 2. Project-level `.fluidrc` profile (walks upward from cwd)\n * 3. Global active profile from `~/.fluid/config.json`\n *\n * When `profileName` is provided explicitly (e.g. via `--profile` flag),\n * it takes precedence over the environment variables, `.fluidrc`, and the\n * active profile.\n */\nexport function resolveCredentialSource(\n profileName?: string,\n): CredentialSource | null {\n const config = readConfig();\n\n if (profileName) {\n const profile = config.profiles[profileName];\n return profile ? { kind: \"profile\", profile } : null;\n }\n\n const envToken = process.env[\"FLUID_TOKEN\"] ?? process.env[\"FLUID_API_TOKEN\"];\n if (envToken) return { kind: \"token\", token: envToken };\n\n const projectConfig = findProjectConfig(process.cwd());\n if (projectConfig?.profile) {\n const profile = config.profiles[projectConfig.profile];\n if (profile) return { kind: \"profile\", profile };\n }\n\n if (!config.activeProfile) return null;\n const profile = config.profiles[config.activeProfile];\n return profile ? { kind: \"profile\", profile } : null;\n}\n\n/**\n * Get the auth token for a named profile, or the active profile when omitted,\n * in the order {@link resolveCredentialSource} describes.\n *\n * An identity-mode profile keeps no token in the config file. Its access\n * token lives in this process's memory once the session has been restored,\n * which the CLI does before running a command; until then, and after its\n * session ends, this returns null. Prefer {@link getAuthTokenAsync}, which\n * restores and refreshes the session itself.\n */\nexport function getAuthToken(profileName?: string): string | null {\n const source = resolveCredentialSource(profileName);\n if (!source) return null;\n if (source.kind === \"token\") return source.token;\n if (isIdentityProfile(source.profile)) {\n return peekIdentityToken(source.profile.name);\n }\n return source.profile.token ?? null;\n}\n\n/**\n * Like {@link getAuthToken}, but restores, refreshes, or exchanges an\n * identity-mode profile's session as needed. Resolves with null when there\n * is no credential; rejects when an identity session has ended.\n */\nexport async function getAuthTokenAsync(\n profileName?: string,\n): Promise<string | null> {\n const source = resolveCredentialSource(profileName);\n if (!source) return null;\n if (source.kind === \"token\") return source.token;\n if (isIdentityProfile(source.profile)) {\n return profileAccessToken(source.profile);\n }\n return source.profile.token ?? null;\n}\n\n/**\n * Like {@link getAuthTokenAsync}, for a token that acts only as `roles`. An\n * admin who is also a rep holds an `admin` token by default, which an API that\n * wants a rep refuses; this asks the identity service to narrow the session to\n * `roles` in the company (RFC 8693), and holds that token apart from the\n * default one, which every other reader keeps getting.\n *\n * Only an identity-mode profile can be narrowed. A literal token\n * (`FLUID_TOKEN`) and a legacy profile come back exactly as\n * {@link getAuthTokenAsync} returns them, because their token already is what\n * it is. Resolves with null when there is no credential. Rejects with an\n * error {@link isRoleNotGrantedError} recognises when the person does not\n * hold `roles` in the company, and when an identity session has ended.\n */\nexport async function getRoleAuthTokenAsync(\n roles: readonly AuthRole[],\n profileName?: string,\n): Promise<string | null> {\n const source = resolveCredentialSource(profileName);\n if (!source) return null;\n if (source.kind === \"token\") return source.token;\n if (isIdentityProfile(source.profile)) {\n return profileAccessToken(source.profile, roles);\n }\n return source.profile.token ?? null;\n}\n\n/**\n * Get the active profile, or null if not logged in\n */\nexport function getActiveProfile(): FluidProfile | null {\n const config = readConfig();\n if (!config.activeProfile) return null;\n\n return config.profiles[config.activeProfile] ?? null;\n}\n\n/**\n * List all stored profile names\n */\nexport function listProfileNames(): string[] {\n const config = readConfig();\n return Object.keys(config.profiles);\n}\n","import type { Command } from \"commander\";\n\nimport type { InputResolver } from \"./types.js\";\n\nconst resolvers = new WeakMap<Command, InputResolver>();\n\n/**\n * Declares the local inputs a command's action reads.\n *\n * command - the leaf command whose action reads the inputs\n * resolver - maps commander's parsed opts and args to the input specs; it\n * must be a pure function of opts and args and must not touch the filesystem\n *\n * Returns the command.\n */\nexport function declareInputs(\n command: Command,\n resolver: InputResolver,\n): Command {\n resolvers.set(command, resolver);\n return command;\n}\n\nexport function getInputResolver(command: Command): InputResolver | undefined {\n return resolvers.get(command);\n}\n","import { AsyncLocalStorage } from \"node:async_hooks\";\n\nlet internalDepth = 0;\nconst asyncScope = new AsyncLocalStorage<true>();\n\n/** True while the inputs implementation itself is touching the filesystem. */\nexport function inInternalRead(): boolean {\n return internalDepth > 0 || asyncScope.getStore() === true;\n}\n\nexport function withInternalReads<T>(fn: () => T): T {\n internalDepth++;\n try {\n return fn();\n } finally {\n internalDepth--;\n }\n}\n\n/**\n * Async form of withInternalReads. The scope follows fn's own async\n * continuations only; unrelated concurrent work stays guarded.\n */\nexport async function withInternalReadsAsync<T>(\n fn: () => Promise<T>,\n): Promise<T> {\n return asyncScope.run(true, fn);\n}\n","import { realpathSync } from \"node:fs\";\nimport { isAbsolute, sep } from \"node:path\";\n\nimport { withInternalReads } from \"./scope.js\";\n\nexport interface CanonicalPath {\n lexical: string;\n canonical: string;\n}\n\nexport function lexicalPath(path: string, cwd: string = process.cwd()): string {\n return isAbsolute(path) ? path : `${cwd}${sep}${path}`;\n}\n\n/**\n * The path rule for resolve and expect mode (default mode reads the lexical\n * path so /dev/stdin works): make the path absolute without normalizing, then realpath it natively so `..` after a symlink keeps OS\n * semantics. Throws when the path does not exist.\n */\nexport function canonicalPath(\n path: string,\n cwd: string = process.cwd(),\n): CanonicalPath {\n const lexical = lexicalPath(path, cwd);\n return {\n lexical,\n canonical: withInternalReads(() => realpathSync.native(lexical)),\n };\n}\n","import {\n closeSync,\n constants,\n fstatSync,\n openSync,\n readdirSync,\n readFileSync,\n realpathSync,\n} from \"node:fs\";\nimport { dirname, join } from \"node:path\";\n\nimport { withInternalReads } from \"./scope.js\";\n\nexport function normalizeIgnore(\n ignore: readonly string[] | undefined,\n): string[] {\n const out = new Set<string>();\n for (const pattern of ignore ?? []) {\n const segments = pattern.split(\"/\");\n if (\n pattern === \"\" ||\n /[*?[\\]{}]/.test(pattern) ||\n segments.some((s) => s === \"\" || s === \".\" || s === \"..\")\n ) {\n throw new Error(\n `Invalid tree ignore pattern: ${JSON.stringify(pattern)}`,\n );\n }\n out.add(pattern);\n }\n return [...out].sort(compare);\n}\n\nexport function ignoreKey(\n ignore: readonly string[],\n skipDotNames = false,\n): string {\n return skipDotNames\n ? JSON.stringify({ ignore, skipDotNames: true })\n : JSON.stringify(ignore);\n}\n\nexport function normalizeSkipDotNames(value: unknown): boolean {\n if (value === undefined || typeof value === \"boolean\") return value === true;\n throw new Error(\n `Invalid tree skipDotNames: ${JSON.stringify(value) ?? String(value)}`,\n );\n}\n\nfunction compare(a: string, b: string): number {\n return a < b ? -1 : a > b ? 1 : 0;\n}\n\n/**\n * The single ignore matcher for tree inputs, used by resolve, expect and\n * default mode alike. A pattern without \"/\" matches an entry's basename at\n * any depth; a pattern with \"/\" matches a root-relative path. A match on a\n * directory also excludes every descendant.\n *\n * rel - \"/\"-separated path relative to the tree root\n * ignore - patterns from normalizeIgnore\n *\n * Returns true when rel or any ancestor directory of rel is ignored.\n */\nexport function isIgnored(rel: string, ignore: readonly string[]): boolean {\n const segments = rel.split(\"/\");\n let prefix = \"\";\n for (const segment of segments) {\n prefix = prefix === \"\" ? segment : `${prefix}/${segment}`;\n if (ignore.includes(segment) || ignore.includes(prefix)) return true;\n }\n return false;\n}\n\n/**\n * Lists the regular files under an already canonical root as sorted\n * \"/\"-separated relative paths. Symlink entries are skipped at list time in\n * every mode. Throws if a directory being walked is not its own canonical\n * path. With skipDotNames, every entry whose name starts with \".\" is skipped\n * before its directory is read.\n */\nexport function listTreeFiles(\n root: string,\n ignore: readonly string[],\n skipDotNames = false,\n): string[] {\n const out: string[] = [];\n const walk = (dir: string, relDir: string): void => {\n assertCanonicalDir(dir);\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n const rel = relDir === \"\" ? entry.name : `${relDir}/${entry.name}`;\n if (skipDotNames && entry.name.startsWith(\".\")) continue;\n if (isIgnored(rel, ignore)) continue;\n if (entry.isDirectory()) walk(join(dir, entry.name), rel);\n else if (entry.isFile()) out.push(rel);\n }\n };\n withInternalReads(() => walk(root, \"\"));\n return out.sort(compare);\n}\n\nfunction assertCanonicalDir(dir: string): void {\n if (realpathSync.native(dir) !== dir) {\n throw new Error(`Tree directory changed during read: ${dir}`);\n }\n}\n\n/**\n * Reads one tree member from a canonical root. Rejects a non-canonical parent\n * directory and a symlinked or non-regular final component (O_NOFOLLOW).\n * Known residual: a parent directory swapped between the canonical check and\n * the open is not detected; it needs a local writer to the tree during the\n * command, and Node has no openat2.\n */\nexport function readTreeFileFromDisk(root: string, rel: string): Buffer {\n return withInternalReads(() => {\n const path = join(root, rel);\n assertCanonicalDir(join(root, dirname(rel)));\n const fd = openSync(path, constants.O_RDONLY | constants.O_NOFOLLOW);\n try {\n if (!fstatSync(fd).isFile()) {\n throw new Error(`Tree member is not a regular file: ${path}`);\n }\n return readFileSync(fd);\n } finally {\n closeSync(fd);\n }\n });\n}\n","import { createHash } from \"node:crypto\";\nimport { readFileSync } from \"node:fs\";\n\nimport { canonicalPath } from \"./paths.js\";\nimport { withInternalReads } from \"./scope.js\";\nimport {\n ignoreKey,\n listTreeFiles,\n normalizeIgnore,\n normalizeSkipDotNames,\n readTreeFileFromDisk,\n} from \"./tree.js\";\n\nimport type { InputEntry, InputSpec, TreeEntry } from \"./types.js\";\n\nexport interface Snapshot {\n files: Map<string, Buffer>;\n aliases: Map<string, string>;\n trees: Map<string, Map<string, Buffer>>;\n treeAliases: Map<string, string>;\n refs: Map<string, string>;\n}\n\nexport function treeKey(\n root: string,\n ignore: readonly string[],\n skipDotNames = false,\n): string {\n return `${root}\\0${ignoreKey(ignore, skipDotNames)}`;\n}\n\nexport const MANIFEST_VERSION = 1;\n\nexport function sha256(data: Buffer): string {\n return createHash(\"sha256\").update(data).digest(\"hex\");\n}\n\nexport function resolveInputs(\n specs: readonly InputSpec[],\n cwd: string = process.cwd(),\n): { entries: InputEntry[]; snapshot: Snapshot } {\n return withInternalReads(() => {\n const snapshot: Snapshot = {\n files: new Map(),\n aliases: new Map(),\n trees: new Map(),\n treeAliases: new Map(),\n refs: new Map(),\n };\n const entries: InputEntry[] = [];\n for (const spec of specs) {\n if (spec.kind === \"ref\") {\n snapshot.refs.set(spec.name, spec.value);\n entries.push({ kind: \"ref\", name: spec.name, value: spec.value });\n } else if (spec.kind === \"file\" || spec.kind === \"config\") {\n const { lexical, canonical: path } = canonicalPath(spec.path, cwd);\n const content = readFileSync(path);\n snapshot.aliases.set(lexical, path);\n snapshot.aliases.set(path, path);\n snapshot.files.set(path, content);\n entries.push({\n kind: spec.kind,\n path,\n sha256: sha256(content),\n size: content.length,\n });\n } else {\n if (spec.kind !== \"tree\" || typeof spec.path !== \"string\") {\n throw new Error(\"Invalid tree input spec\");\n }\n const { lexical, canonical: root } = canonicalPath(spec.path, cwd);\n const ignore = normalizeIgnore(spec.ignore);\n const skipDot = normalizeSkipDotNames(spec.skipDotNames);\n const members = new Map<string, Buffer>();\n const files: TreeEntry[\"files\"] = [];\n for (const rel of listTreeFiles(root, ignore, skipDot)) {\n const content = readTreeFileFromDisk(root, rel);\n members.set(rel, content);\n files.push({\n path: rel,\n sha256: sha256(content),\n size: content.length,\n });\n }\n snapshot.treeAliases.set(lexical, root);\n snapshot.treeAliases.set(root, root);\n snapshot.trees.set(treeKey(root, ignore, skipDot), members);\n entries.push({\n kind: \"tree\",\n path: root,\n ignore,\n ...(skipDot ? { skipDotNames: true as const } : {}),\n files,\n });\n }\n }\n return { entries, snapshot };\n });\n}\n\nfunction sortKey(entry: InputEntry): string {\n const id =\n entry.kind === \"ref\"\n ? entry.name\n : entry.kind === \"tree\"\n ? `${entry.path}\\0${ignoreKey(entry.ignore, entry.skipDotNames === true)}`\n : entry.path;\n return `${entry.kind}\\0${id}`;\n}\n\nexport function canonicalEntries(entries: readonly InputEntry[]): InputEntry[] {\n return entries\n .map((e): InputEntry => {\n if (e.kind === \"ref\") {\n return { kind: \"ref\", name: e.name, value: e.value };\n }\n if (e.kind === \"tree\") {\n return {\n kind: \"tree\",\n path: e.path,\n ignore: [...e.ignore],\n ...(e.skipDotNames === true ? { skipDotNames: true as const } : {}),\n files: e.files.map((f) => ({\n path: f.path,\n sha256: f.sha256,\n size: f.size,\n })),\n };\n }\n return { kind: e.kind, path: e.path, sha256: e.sha256, size: e.size };\n })\n .sort((a, b) => {\n const ka = sortKey(a);\n const kb = sortKey(b);\n return ka < kb ? -1 : ka > kb ? 1 : 0;\n });\n}\n\nexport function manifestJson(\n command: string,\n entries: readonly InputEntry[],\n): string {\n return JSON.stringify({\n version: MANIFEST_VERSION,\n command,\n inputs: canonicalEntries(entries),\n });\n}\n","import { readFileSync } from \"node:fs\";\n\nimport { canonicalPath, lexicalPath } from \"./paths.js\";\nimport { withInternalReads } from \"./scope.js\";\nimport { treeKey, type Snapshot } from \"./snapshot.js\";\nimport {\n listTreeFiles,\n normalizeIgnore,\n normalizeSkipDotNames,\n readTreeFileFromDisk,\n} from \"./tree.js\";\nimport type { CommandInputs, InputTree, TreeOptions } from \"./types.js\";\n\nlet activeSnapshot: Snapshot | null = null;\n\nexport function setActiveSnapshot(snapshot: Snapshot | null): void {\n activeSnapshot = snapshot;\n}\n\nexport class InputsUndeclaredError extends Error {\n constructor(path: string) {\n super(`Undeclared input read: ${path}`);\n this.name = \"InputsUndeclaredError\";\n }\n}\n\nfunction declaredKey(path: string, snapshot: Snapshot): string | undefined {\n const lexical = lexicalPath(path);\n const key = snapshot.aliases.get(lexical) ?? lexical;\n return snapshot.files.has(key) ? key : undefined;\n}\n\nfunction bytes(content: Buffer, encoding?: BufferEncoding): never {\n return (\n encoding ? content.toString(encoding) : Buffer.from(content)\n ) as never;\n}\n\nfunction snapshotTree(\n rootPath: string,\n ignore: string[],\n skipDot: boolean,\n snapshot: Snapshot,\n): { root: string; members: Map<string, Buffer> } {\n const root = snapshot.treeAliases.get(lexicalPath(rootPath)) ?? rootPath;\n const members = snapshot.trees.get(treeKey(root, ignore, skipDot));\n if (!members) throw new InputsUndeclaredError(rootPath);\n return { root, members };\n}\n\nfunction openTree(rootPath: string, options?: TreeOptions): InputTree {\n const ignore = normalizeIgnore(options?.ignore);\n const skipDot = normalizeSkipDotNames(options?.skipDotNames);\n let disk: { root: string; known: Set<string> } | undefined;\n const fromDisk = (): { root: string; known: Set<string> } => {\n if (!disk) {\n const root = canonicalPath(rootPath).canonical;\n disk = { root, known: new Set(listTreeFiles(root, ignore, skipDot)) };\n }\n return disk;\n };\n if (activeSnapshot) snapshotTree(rootPath, ignore, skipDot, activeSnapshot);\n else fromDisk();\n return {\n get root() {\n return activeSnapshot\n ? snapshotTree(rootPath, ignore, skipDot, activeSnapshot).root\n : fromDisk().root;\n },\n list() {\n if (activeSnapshot) {\n return [\n ...snapshotTree(\n rootPath,\n ignore,\n skipDot,\n activeSnapshot,\n ).members.keys(),\n ];\n }\n return [...fromDisk().known];\n },\n readFile(rel: string, encoding?: BufferEncoding): never {\n const missing = () => new InputsUndeclaredError(`${rootPath}/${rel}`);\n if (activeSnapshot) {\n const content = snapshotTree(\n rootPath,\n ignore,\n skipDot,\n activeSnapshot,\n ).members.get(rel);\n if (!content) throw missing();\n return bytes(content, encoding);\n }\n const { root, known } = fromDisk();\n if (!known.has(rel)) throw missing();\n return bytes(readTreeFileFromDisk(root, rel), encoding);\n },\n };\n}\n\n/**\n * The reader behind ctx.inputs. Outside expect mode it reads from disk; once\n * an expect-mode snapshot is active it serves only that snapshot and throws\n * for anything undeclared.\n */\nexport function createInputsReader(): CommandInputs {\n return {\n isExpecting: () => activeSnapshot !== null,\n ref(name: string) {\n const snapshot = activeSnapshot;\n if (!snapshot) return undefined;\n const value = snapshot.refs.get(name);\n if (value === undefined) throw new InputsUndeclaredError(`ref ${name}`);\n return value;\n },\n tree: openTree,\n readFile(path: string, encoding?: BufferEncoding): never {\n const snapshot = activeSnapshot;\n if (!snapshot) {\n const content = withInternalReads(() =>\n readFileSync(lexicalPath(path)),\n );\n return (encoding ? content.toString(encoding) : content) as never;\n }\n const key = declaredKey(path, snapshot);\n const content = key === undefined ? undefined : snapshot.files.get(key);\n if (!content) throw new InputsUndeclaredError(path);\n return (\n encoding ? content.toString(encoding) : Buffer.from(content)\n ) as never;\n },\n };\n}\n","import { readFileSync, writeSync } from \"node:fs\";\nimport type { Command } from \"commander\";\n\nimport { getInputResolver } from \"./declare.js\";\nimport { InputsUndeclaredError, setActiveSnapshot } from \"./reader.js\";\nimport {\n canonicalEntries,\n MANIFEST_VERSION,\n manifestJson,\n resolveInputs,\n} from \"./snapshot.js\";\nimport { withInternalReads } from \"./scope.js\";\nimport type { InputEntry } from \"./types.js\";\n\nexport const INPUTS_MODE_ENV = \"FLUID_INPUTS_MODE\";\nexport const INPUTS_EXPECT_ENV = \"FLUID_INPUTS_EXPECT\";\n\nexport const INPUTS_UNDECLARED_CODE = \"inputs_undeclared\";\nexport const INPUTS_UNDECLARED_EXIT = 64;\nexport const INPUTS_MISMATCH_CODE = \"inputs_mismatch\";\nexport const INPUTS_MISMATCH_EXIT = 65;\nexport const INPUTS_RESOLVE_FAILED_EXIT = 1;\n\nexport interface InputsHookIo {\n env: NodeJS.ProcessEnv;\n stdout: (text: string) => void;\n stderr: (text: string) => void;\n exit: (code: number) => never;\n}\n\nfunction writeAll(fd: number, text: string): void {\n const buffer = Buffer.from(text);\n let offset = 0;\n while (offset < buffer.length) {\n try {\n offset += writeSync(fd, buffer, offset);\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code !== \"EAGAIN\") throw error;\n }\n }\n}\n\nconst processIo: InputsHookIo = {\n env: process.env,\n stdout: (text) => writeAll(1, text),\n stderr: (text) => writeAll(2, text),\n exit: (code) => process.exit(code),\n};\n\nexport function inputsModeActive(env: NodeJS.ProcessEnv): boolean {\n return (\n env[INPUTS_MODE_ENV] !== undefined || env[INPUTS_EXPECT_ENV] !== undefined\n );\n}\n\nfunction commandPath(command: Command): string {\n const names: string[] = [];\n for (let c: Command | null = command; c; c = c.parent)\n names.unshift(c.name());\n return names.join(\" \");\n}\n\nfunction readManifest(\n path: string,\n): { version: unknown; command: unknown; inputs: InputEntry[] } | null {\n try {\n const parsed = JSON.parse(\n withInternalReads(() => readFileSync(path, \"utf8\")),\n ) as { version?: unknown; command?: unknown; inputs?: unknown };\n if (!Array.isArray(parsed.inputs)) return null;\n return {\n version: parsed.version,\n command: parsed.command,\n inputs: canonicalEntries(parsed.inputs as InputEntry[]),\n };\n } catch {\n return null;\n }\n}\n\nexport function inputsPreAction(\n actionCommand: Command,\n io: InputsHookIo = processIo,\n): void {\n const mode = io.env[INPUTS_MODE_ENV];\n const expectPath = io.env[INPUTS_EXPECT_ENV];\n setActiveSnapshot(null);\n if (mode === undefined && expectPath === undefined) return;\n const resolveMode = mode === \"resolve\";\n if (\n (mode !== undefined && !resolveMode) ||\n expectPath === \"\" ||\n (resolveMode && expectPath !== undefined)\n ) {\n io.stderr(`${INPUTS_MISMATCH_CODE}\\n`);\n return io.exit(INPUTS_MISMATCH_EXIT);\n }\n\n const resolver = getInputResolver(actionCommand);\n if (!resolver) {\n io.stderr(`${INPUTS_UNDECLARED_CODE}\\n`);\n return io.exit(INPUTS_UNDECLARED_EXIT);\n }\n\n let resolved: ReturnType<typeof resolveInputs>;\n try {\n resolved = resolveInputs(\n resolver(actionCommand.optsWithGlobals(), actionCommand.args),\n );\n } catch (error) {\n if (resolveMode) {\n const message = error instanceof Error ? error.message : String(error);\n io.stderr(`${message}\\n`);\n return io.exit(INPUTS_RESOLVE_FAILED_EXIT);\n }\n io.stderr(`${INPUTS_MISMATCH_CODE}\\n`);\n return io.exit(INPUTS_MISMATCH_EXIT);\n }\n\n if (resolveMode) {\n io.stdout(\n `${manifestJson(commandPath(actionCommand), resolved.entries)}\\n`,\n );\n return io.exit(0);\n }\n\n const expected = readManifest(expectPath as string);\n if (\n !expected ||\n expected.version !== MANIFEST_VERSION ||\n expected.command !== commandPath(actionCommand) ||\n JSON.stringify(expected.inputs) !==\n JSON.stringify(canonicalEntries(resolved.entries))\n ) {\n io.stderr(`${INPUTS_MISMATCH_CODE}\\n`);\n return io.exit(INPUTS_MISMATCH_EXIT);\n }\n setActiveSnapshot(resolved.snapshot);\n}\n\n/**\n * Registers the inputs preAction hook. Register it before any credential\n * hook.\n */\nexport function registerInputsHook(\n program: Command,\n io: InputsHookIo = processIo,\n): void {\n program.hook(\"preAction\", (_program, actionCommand) => {\n inputsPreAction(actionCommand, io);\n });\n}\n\n/**\n * Maps an undeclared read in expect mode to the fixed undeclared exit; any\n * other error is returned to the caller untouched.\n */\nexport function exitOnInputsError(\n error: unknown,\n io: InputsHookIo = processIo,\n): void {\n if (!(error instanceof InputsUndeclaredError)) return;\n io.stderr(`${INPUTS_UNDECLARED_CODE}\\n`);\n io.exit(INPUTS_UNDECLARED_EXIT);\n}\n"],"mappings":";;;;;;;;AAwBA,MAAM,aAAa,OAAO,IAAI,0CAA0C;;;;;AAQxE,SAAgB,gBACd,SACA,YACG;CACH,MAAM,SAAS;AACf,QAAO,cAAc;EAAE,GAAG,OAAO;EAAa,GAAG;EAAY;AAC7D,QAAO;;AAGT,SAAgB,eAAe,SAA4C;AACzE,QAAQ,QAAsB,eAAe;;;;ACW/C,SAAgB,SAAS,SAA2B;AAClD,QAAQ,QAAwC,YAAY;;AAG9D,SAAS,WAAW,SAA2B;AAC7C,QAAQ,QAAwC,kBAAkB;;AAGpE,SAAgB,mBAAmB,SAA6B;AAC9D,QAAO,QAAQ,SAAS,QAAQ,QAAQ,CAAC,SAAS,IAAI,CAAC;;AAGzD,SAAgB,eAAe,SAA4B;AACzD,QAAO,QAAQ,QAAQ,QAAQ,WAAW,CAAC,OAAO,OAAO;;AAG3D,SAAS,eAAe,QAA+B;AACrD,QAAO;EACL,OAAO,OAAO;EACd,MAAM,OAAO,QAAQ;EACrB,OAAO,OAAO,SAAS;EACvB,aAAa,OAAO;EACpB,YAAY,OAAO;EACnB,eAAe,OAAO;EACtB,UAAU,OAAO;EACjB,cAAc,OAAO;EACtB;;AAGH,SAAS,iBAAiB,UAAqC;AAC7D,QAAO;EACL,MAAM,SAAS,MAAM;EACrB,aAAa,SAAS;EACtB,UAAU,SAAS;EACnB,UAAU,SAAS;EACpB;;AAGH,SAAS,YAAY,SAAkB,UAA+B;AACpE,KAAI,aAAa,KAAA,EAAW,QAAO;AAEnC,QAAO,QAAQ,QAAQ,MAAM,WAAW,OAAO,SAAS,QAAQ,GAC5D,OACA;;AAGN,SAAgB,aAAa,MAA+B;CAC1D,MAAM,UAA0B,EAAE;CAElC,SAAS,MAAM,SAAkB,MAAgB,WAAsB;EACrE,MAAM,aAAa,eAAe,QAAQ;EAC1C,MAAM,cAAc,mBAAmB,QAAQ;AAC/C,UAAQ,KAAK;GACX,SAAS,KAAK,KAAK,IAAI;GACvB;GACA,aAAa,QAAQ,aAAa;GAClC,SAAS,QAAQ,SAAS;GAC1B,WAAW,QAAQ,oBAAoB,IAAI,iBAAiB;GAC5D,SAAS,eAAe,QAAQ,CAAC,IAAI,eAAe;GACpD,qBAAqB,UAClB,QAAQ,aAEP,eAAe,SAAS,CAAC,MACtB,WAAW,OAAO,SAAS,YAC7B,CACF,CACA,KAAK,aAAaA,cAAY,SAAS,CAAC,KAAK,IAAI,CAAC;GACrD,aAAa,YAAY,KAAK,QAAQ,IAAI,MAAM,CAAC;GACjD,UAAU,WAAW,QAAQ;GAC7B,QAAQ,YAAY,SAAS,YAAY,OAAO;GAChD,WAAW,YAAY,aAAa;GACrC,CAAC;AACF,OAAK,MAAM,OAAO,YAChB,OAAM,KAAK,CAAC,GAAG,MAAM,IAAI,MAAM,CAAC,EAAE,CAAC,GAAG,WAAW,QAAQ,CAAC;;AAI9D,OAAM,MAAM,CAAC,KAAK,MAAM,CAAC,EAAE,EAAE,CAAC;AAC9B,QAAO;;AAGT,SAAgB,YAAY,SAA2B;CACrD,IAAI,OAAO;AACX,QAAO,KAAK,OAAQ,QAAO,KAAK;AAChC,QAAO;;AAGT,SAAgBA,cAAY,SAA4B;CACtD,MAAM,OAAiB,EAAE;AACzB,MAAK,IAAI,IAAoB,SAAS,GAAG,IAAI,EAAE,OAC7C,MAAK,QAAQ,EAAE,MAAM,CAAC;AAExB,QAAO;;;AAIT,SAAgB,cACd,SACA,MACA,QACgB;CAChB,IAAI,SAAS;AACb,MAAK,MAAM,QAAQ,QAAQ;EACzB,MAAM,OAAO,mBAAmB,OAAO,CAAC,MACrC,QAAQ,IAAI,MAAM,KAAK,QAAQ,IAAI,SAAS,CAAC,SAAS,KAAK,CAC7D;AACD,MAAI,CAAC,KAAM,QAAO,EAAE;AACpB,WAAS;;CAEX,MAAM,OAAOA,cAAY,OAAO;AAChC,QAAO,QAAQ,QAAQ,UACrB,KAAK,OAAO,MAAM,UAAU,MAAM,KAAK,WAAW,KAAK,CACxD;;;;;;;AEnKH,MAAa,iBAAmD;CAC9D,kBAAkB;CAClB,0BAA0BC;CAC3B;;;ACkBD,SAAgB,QAAW,OAAsB;AAC/C,QAAO;EAAE,SAAS;EAAM;EAAO;;AAGjC,SAAgB,QAAW,OAAsB;AAC/C,QAAO;EAAE,SAAS;EAAO;EAAO;;AAKlC,SAAgB,UAAgB,QAA4C;AAC1E,QAAO,OAAO,YAAY;;AAG5B,SAAgB,UAAgB,QAA4C;AAC1E,QAAO,OAAO,YAAY;;AAK5B,SAAgB,SAAY,IAA+B;AACzD,KAAI;AACF,SAAO,QAAQ,IAAI,CAAC;UACb,OAAO;AACd,SAAO,QAAQ,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,MAAM,CAAC,CAAC;;;AAI7E,eAAsB,cACpB,IAC2B;AAC3B,KAAI;AACF,SAAO,QAAQ,MAAM,IAAI,CAAC;UACnB,OAAO;AACd,SAAO,QAAQ,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,OAAO,MAAM,CAAC,CAAC;;;AAI7E,SAAgB,OAAa,QAAyB;AACpD,KAAI,UAAU,OAAO,CAAE,QAAO,OAAO;AAGrC,KAAI,OAAO,iBAAiB,MAAO,OAAM,OAAO;AAChD,OAAM,IAAI,MACR,OAAO,OAAO,UAAU,YAAY,OAAO,UAAU,OACjD,KAAK,UAAU,OAAO,MAAM,GAC5B,OAAO,OAAO,MAAM,CACzB;;AAGH,SAAgB,SAAe,QAAsB,cAAoB;AACvE,KAAI,UAAU,OAAO,CAAE,QAAO,OAAO;AACrC,QAAO;;AAGT,SAAgB,UACd,QACA,IACc;AACd,KAAI,UAAU,OAAO,CAAE,QAAO,QAAQ,GAAG,OAAO,MAAM,CAAC;AACvD,QAAO;;AAGT,SAAgB,SACd,QACA,IACc;AACd,KAAI,UAAU,OAAO,CAAE,QAAO,QAAQ,GAAG,OAAO,MAAM,CAAC;AACvD,QAAO;;AAKT,SAAgB,QAAQ,OAAgC;AACtD,QAAO,iBAAiB;;AAG1B,SAAgB,YAAY,OAAgD;AAC1E,QAAO,iBAAiB,SAAS,UAAU;;AAG7C,SAAgBC,kBAAgB,OAAwB;AACtD,KAAI,iBAAiB,MAAO,QAAO,MAAM;AACzC,KAAI,OAAO,UAAU,SAAU,QAAO;AACtC,QAAO,OAAO,MAAM;;;;;;;;AClGtB,MAAM,iBAAiB;AAGvB,MAAM,oBAAoB;AAE1B,SAAgB,kBAA0B;AACxC,QAAO,QAAQ,IAAI,qBAAqB;;AAG1C,SAAS,WAAW,YAAoB,gBAGtC;CACA,MAAM,aAAa,IAAI,iBAAiB;CACxC,MAAM,QAAQ,iBAAiB,WAAW,OAAO,EAAE,UAAU;AAC7D,QAAO;EAAE,QAAQ,WAAW;EAAQ,eAAe,aAAa,MAAM;EAAE;;AAS1E,MAAa,kBAAkB;CAC7B,eAAe;EACb,MAAM;EACN,SAAS;EACV;CACD,iBAAiB;EACf,MAAM;EACN,SAAS;EACV;CAID,oBAAoB;EAClB,MAAM;EACN,SACE;EACH;CAGD,cAAc;EACZ,MAAM;EACN,SACE;EACH;CACD,cAAc;EACZ,MAAM;EACN,SAAS;EACV;CAMD,cAAc;EACZ,MAAM;EACN,SACE;EACH;CAMD,iBAAiB;EACf,MAAM;EACN,SACE;EACH;CACD,mBAAmB;EACjB,MAAM;EACN,SAAS;EACV;CACD,eAAe;EACb,MAAM;EACN,SAAS;EACV;CACD,WAAW;EACT,MAAM;EACN,SACE;EACH;CACF;AAED,SAAS,eACP,UACA,SACe;AACf,QAAO;EAAE,MAAM,SAAS;EAAM,SAAS,SAAS;EAAS;EAAS;;;;;;AAiDpE,eAAsB,cACpB,OACA,UAAyC,EAAE,EACE;CAC7C,MAAM,EAAE,QAAQ,YAAY,YAAY;AACxC,KAAI;EACF,MAAM,WAAW,MAAM,MACrB,GAAG,QAAQ,WAAW,iBAAiB,CAAC,+BACxC;GACE,QAAQ;GACR,SAAS;IACP,GAAG;IACH,eAAe,UAAU;IAC1B;GACD;GACD,CACF;AAED,MAAI,SAAS,IAAI;GAIf,MAAM,QAHQ,MAAM,SAAS,MAAM,GAGhB,MAAM,SAAS;AAClC,OAAI,CAAC,KACH,QAAO,QACL,eACE,gBAAgB,iBAChB,+CACD,CACF;AAEH,UAAO,QAAQ,EAAE,MAAM,CAAC;;AAG1B,MAAI,SAAS,WAAW,OAAO,SAAS,WAAW,IACjD,QAAO,QACL,eACE,gBAAgB,eAChB,QAAQ,SAAS,OAAO,mDACzB,CACF;EAGH,MAAM,OAAO,MAAM,SAAS,MAAM;AAClC,SAAO,QACL,eACE,gBAAgB,iBAChB,QAAQ,SAAS,OAAO,IAAI,OAC7B,CACF;UACM,KAAK;EACZ,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAChE,SAAO,QAAQ,eAAe,gBAAgB,iBAAiB,QAAQ,CAAC;WAChE;AACR,WAAS;;;;;;;;;;;;AAab,eAAsB,QACpB,OACA,UAA2C,EAAE,EACA;CAC7C,MAAM,EAAE,QAAQ,YAAY,YAAY;AACxC,KAAI;EACF,MAAM,WAAW,MAAM,MACrB,GAAG,iBAAiB,CAAC,+BACrB;GACE,QAAQ;GACR,SAAS;IAAE,GAAG;IAAgB,gBAAgB;IAAoB;GAClE,MAAM,KAAK,UACT,QAAQ,YACJ;IAAE;IAAO,YAAY,QAAQ;IAAW,GACxC,EAAE,OAAO,CACd;GACD;GACD,CACF;AAED,MAAI,SAAS,WAAW,KAAK;GAC3B,MAAM,OAAQ,MAAM,SAAS,MAAM;GAMnC,MAAM,OAAO,KAAK,6BAA6B;GAC/C,MAAM,YACJ,KAAK,6BAA6B;AACpC,OAAI,CAAC,QAAQ,CAAC,UACZ,QAAO,QACL,eACE,gBAAgB,iBAChB,2CACD,CACF;AAEH,UAAO,QAAQ;IAAE;IAAM;IAAW,CAAC;;EAGrC,MAAM,OAAO,MAAM,SAAS,MAAM;AAClC,MAAI,SAAS,WAAW,IACtB,QAAO,QACL,eAAe,gBAAgB,cAAc,aAAa,OAAO,CAClE;AAEH,SAAO,QACL,eACE,gBAAgB,iBAChB,QAAQ,SAAS,OAAO,IAAI,OAC7B,CACF;UACM,KAAK;EACZ,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAChE,SAAO,QAAQ,eAAe,gBAAgB,iBAAiB,QAAQ,CAAC;WAChE;AACR,WAAS;;;;;;;;;AAUb,eAAsB,WACpB,MACA,MACA,UAAsE,EAAE,EACpB;CACpD,MAAM,EAAE,QAAQ,YAAY,YAAY;CACxC,MAAM,UAAU,QAAQ,WAAW,iBAAiB;AACpD,KAAI;EACF,MAAM,WAAW,MAAM,MAAM,GAAG,QAAQ,kCAAkC;GACxE,QAAQ;GACR,SAAS;IAAE,GAAG;IAAgB,gBAAgB;IAAoB;GAClE,MAAM,KAAK,UAAU;IACnB;IACA,6BAA6B,EAAE,mBAAmB,MAAM;IACxD,GAAI,QAAQ,YAAY,EAAE,YAAY,QAAQ,WAAW,GAAG,EAAE;IAC/D,CAAC;GACF;GACD,CAAC;AAEF,MAAI,SAAS,IAAI;GACf,MAAM,OAAQ,MAAM,SAAS,MAAM;AAcnC,OAAI,CAAC,KAAK,cACR,QAAO,QACL,eACE,gBAAgB,cAChB,iDACD,CACF;GAEH,MAAM,YAAY,MAAM,QAAQ,KAAK,UAAU,GAAG,KAAK,YAAY,EAAE;AACrE,UAAO,QAAQ;IACb,UAAU,KAAK,aAAa;IAC5B,WAAW,UAAU,KAAK,OAAO;KAC/B,IAAI,EAAE;KACN,MAAM,EAAE;KACR,UAAU,EAAE;KACZ,KAAK,EAAE;KACP,GAAI,EAAE,YAAY,EAAE,UAAU,EAAE,WAAW,GAAG,EAAE;KACjD,EAAE;IACH,YAAY,KAAK,iBAAiB,aAAa,aAAa;IAC7D,CAAC;;AAGJ,MAAI,SAAS,WAAW,IACtB,QAAO,QAAQ,eAAe,gBAAgB,mBAAmB,CAAC;AAEpE,MAAI,SAAS,WAAW,IACtB,QAAO,QAAQ,eAAe,gBAAgB,aAAa,CAAC;EAE9D,MAAM,OAAO,MAAM,SAAS,MAAM;AAClC,MAAI,SAAS,WAAW,IACtB,QAAO,QACL,eACE,yBAAyB,KAAK,GAC1B,gBAAgB,kBAChB,gBAAgB,aACrB,CACF;AAEH,MAAI,SAAS,WAAW,IACtB,QAAO,QACL,eAAe,gBAAgB,cAAc,aAAa,OAAO,CAClE;AAEH,SAAO,QACL,eACE,gBAAgB,iBAChB,QAAQ,SAAS,OAAO,IAAI,OAC7B,CACF;UACM,KAAK;EACZ,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAChE,SAAO,QAAQ,eAAe,gBAAgB,iBAAiB,QAAQ,CAAC;WAChE;AACR,WAAS;;;;;;;;AAeb,eAAsB,mBACpB,OACA,UAAyC,EAAE,EACa;CACxD,MAAM,EAAE,QAAQ,YAAY,YAAY;AACxC,KAAI;EACF,MAAM,WAAW,MAAM,MACrB,GAAG,QAAQ,WAAW,iBAAiB,CAAC,0DACxC;GACE,QAAQ;GACR,SAAS;IAAE,GAAG;IAAgB,gBAAgB;IAAoB;GAClE,MAAM,KAAK,UAAU,EAAE,OAAO,CAAC;GAC/B;GACD,CACF;AAED,MAAI,SAAS,IAAI;GACf,MAAM,OAAQ,MAAM,SAAS,MAAM;AAInC,OAAI,KAAK,iBAAiB,cAAc,CAAC,KAAK,WAC5C,QAAO,QACL,eACE,gBAAgB,iBAChB,4DACD,CACF;AAEH,UAAO,QAAQ;IAAE,YAAY;IAAY,WAAW,KAAK;IAAY,CAAC;;AAGxE,MAAI,SAAS,WAAW,OAAO,SAAS,WAAW,IACjD,QAAO,QAAQ,eAAe,gBAAgB,UAAU,CAAC;EAG3D,MAAM,OAAO,MAAM,SAAS,MAAM;AAClC,SAAO,QACL,eACE,gBAAgB,iBAChB,QAAQ,SAAS,OAAO,IAAI,OAC7B,CACF;UACM,KAAK;EACZ,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAChE,SAAO,QAAQ,eAAe,gBAAgB,iBAAiB,QAAQ,CAAC;WAChE;AACR,WAAS;;;;;;AAOb,eAAsB,mBACpB,OACA,SAC+C;CAC/C,MAAM,EAAE,QAAQ,YAAY,YAAY;CACxC,MAAM,UAAU,WAAW,iBAAiB;AAC5C,KAAI;EACF,MAAM,WAAW,MAAM,MAAM,GAAG,QAAQ,UAAU;GAChD,QAAQ;GACR,SAAS;IAAE,GAAG;IAAgB,eAAe,UAAU;IAAS;GAChE;GACD,CAAC;AAEF,MAAI,SAAS,IAAI;GACf,MAAM,OAAQ,MAAM,SAAS,MAAM;AAInC,UAAO,SADW,MAAM,QAAQ,KAAK,UAAU,GAAG,KAAK,YAAY,EAAE,EAEzD,KAAK,OAAO;IACpB,IAAI,EAAE;IACN,MAAM,EAAE;IACR,GAAI,OAAO,EAAE,YAAY,WAAW,EAAE,QAAQ,EAAE,SAAS,GAAG,EAAE;IAC/D,EAAE,CACJ;;AAGH,MAAI,SAAS,WAAW,OAAO,SAAS,WAAW,IACjD,QAAO,QACL,eACE,gBAAgB,eAChB,QAAQ,SAAS,SAClB,CACF;EAGH,MAAM,OAAO,MAAM,SAAS,MAAM;AAClC,SAAO,QACL,eACE,gBAAgB,iBAChB,QAAQ,SAAS,OAAO,IAAI,OAC7B,CACF;UACM,KAAK;EACZ,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAChE,SAAO,QAAQ,eAAe,gBAAgB,iBAAiB,QAAQ,CAAC;WAChE;AACR,WAAS;;;;;;;;;AAUb,eAAsB,cACpB,OACA,WACA,SACqD;CACrD,MAAM,EAAE,QAAQ,YAAY,YAAY;CACxC,MAAM,UAAU,WAAW,iBAAiB;AAC5C,KAAI;EACF,MAAM,WAAW,MAAM,MACrB,GAAG,QAAQ,8BAA8B,UAAU,UACnD;GACE,QAAQ;GACR,SAAS;IACP,GAAG;IACH,eAAe,UAAU;IACzB,gBAAgB;IACjB;GACD;GACD,CACF;AAED,MAAI,SAAS,IAAI;GASf,MAAM,WARQ,MAAM,SAAS,MAAM,EAQd;AACrB,OAAI,CAAC,SAAS,IACZ,QAAO,QACL,eACE,gBAAgB,eAChB,yCACD,CACF;AAEH,UAAO,QAAQ;IACb,WAAW,QAAQ;IACnB,aAAa,QAAQ;IACrB,WAAW,QAAQ;IACnB,KAAK,QAAQ;IACd,CAAC;;AAGJ,MAAI,SAAS,WAAW,IACtB,QAAO,QACL,eACE,gBAAgB,mBAChB,WAAW,UAAU,YACtB,CACF;AAGH,MAAI,SAAS,WAAW,OAAO,SAAS,WAAW,IACjD,QAAO,QACL,eACE,gBAAgB,eAChB,QAAQ,SAAS,SAClB,CACF;EAGH,MAAM,OAAO,MAAM,SAAS,MAAM;AAClC,SAAO,QACL,eACE,gBAAgB,eAChB,QAAQ,SAAS,OAAO,IAAI,OAC7B,CACF;UACM,KAAK;EACZ,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAChE,SAAO,QAAQ,eAAe,gBAAgB,iBAAiB,QAAQ,CAAC;WAChE;AACR,WAAS;;;AAuCb,MAAa,eAAe;CAC1B,iBAAiB;EACf,MAAM;EACN,SACE;EACH;CAID,kBAAkB;EAChB,MAAM;EACN,SACE;EACH;CACD,gBAAgB;EACd,MAAM;EACN,SAAS;EACV;CACD,cAAc;EACZ,MAAM;EACN,SACE;EACH;CACD,kBAAkB;EAChB,MAAM;EACN,SAAS;EACV;CACD,wBAAwB;EACtB,MAAM;EACN,SACE;EACH;CACF;AAED,SAAS,YACP,UACA,QAA+C,EAAE,EACpC;AACb,QAAO;EAAE,MAAM,SAAS;EAAM,SAAS,SAAS;EAAS,GAAG;EAAO;;AAGrE,SAAS,SAAS,OAAqD;AACrE,QAAO,UAAU,QAAQ,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,MAAM,GACtE,QACD,KAAA;;;AAIN,SAAS,yBAAyB,MAAuB;AACvD,KAAI;EACF,MAAM,SAAS,SAAS,SAAS,KAAK,MAAM,KAAK,CAAC,EAAE,OAAO;AAC3D,SACE,WAAW,KAAA,KACX,cACE,QACA,+BACA,uBACD,CAAC,SAAS;SAEP;AACN,SAAO;;;AAIX,SAAS,cACP,QACA,QACA,OACU;CACV,MAAM,WAAW,SAAS,OAAO,QAAQ,GAAG;AAC5C,QAAO,MAAM,QAAQ,SAAS,GAC1B,SAAS,QAAQ,MAAmB,OAAO,MAAM,SAAS,GAC1D,EAAE;;AAGR,SAAS,gBAAgB,MAA4B;CACnD,MAAM,SAAS,SAAS,SAAS,KAAK,GAAG,UAAU,IAAI,EAAE;AAGzD,KACE,cAAc,QAAQ,WAAW,YAAY,CAAC,MAAM,MAClD,EAAE,SAAS,QAAQ,CACpB,CAED,QAAO,YAAY,aAAa,iBAAiB;EAAE,QAAQ;EAAK;EAAQ,CAAC;AAE3E,KAAI,cAAc,QAAQ,WAAW,OAAO,CAAC,SAAS,EACpD,QAAO,YAAY,aAAa,kBAAkB;EAAE,QAAQ;EAAK;EAAQ,CAAC;AAE5E,QAAO,YAAY,aAAa,gBAAgB;EAAE,QAAQ;EAAK;EAAQ,CAAC;;AAG1E,SAAS,WAAW,MAA2C;CAC7D,MAAM,OAAO,SAAS,KAAK;CAC3B,MAAM,UAAU,SAAS,OAAO,WAAW;CAC3C,MAAM,cAAc,SAAS,OAAO,gBAAgB;CACpD,MAAM,MAAM,SAAS,OAAO,gBAAgB,GAAG;AAC/C,KACE,OAAO,UAAU,UAAU,YAC3B,OAAO,QAAQ,kBAAkB,YACjC,OAAO,cAAc,UAAU,SAE/B;AAEF,QAAO;EACL,SAAS;GACP,IAAI,QAAQ;GACZ,MAAM,OAAO,QAAQ,WAAW,GAAG;GACnC,WAAW,OAAO,QAAQ,gBAAgB,GAAG;GAC7C,WAAW,QAAQ;GACpB;EACD,aAAa;GACX,IAAI,YAAY;GAChB,OAAO,OAAO,YAAY,YAAY,GAAG;GAC1C;EACD,cAAc,MAAM,QAAQ,IAAI,GAC5B,IAAI,QAAQ,MAAmB,OAAO,MAAM,SAAS,GACrD,EAAE;EACP;;AAGH,eAAe,SAAS,UAAsC;AAC5D,KAAI;AACF,SAAO,MAAM,SAAS,MAAM;SACtB;AACN;;;;;;;;;;AAWJ,eAAsB,OACpB,SAC8C;CAC9C,MAAM,EAAE,QAAQ,YAAY,WAAW,kBAAkB;AACzD,KAAI;EACF,MAAM,WAAW,MAAM,MAAM,GAAG,iBAAiB,CAAC,eAAe;GAC/D,QAAQ;GACR,SAAS;IAAE,GAAG;IAAgB,gBAAgB;IAAoB;GAClE,MAAM,KAAK,UAAU;IACnB,MAAM;KACJ,YAAY,QAAQ;KACpB,WAAW,QAAQ;KACnB,OAAO,QAAQ;KAChB;IACD,SAAS;KACP,MAAM,QAAQ;KACd,WAAW,QAAQ;KACnB,GAAI,QAAQ,QAAQ,EAAE,OAAO,QAAQ,OAAO,GAAG,EAAE;KAClD;IACF,CAAC;GACF;GACD,CAAC;EACF,MAAM,OAAO,MAAM,SAAS,SAAS;AAErC,MAAI,SAAS,WAAW,KAAK;GAC3B,MAAM,UAAU,WAAW,KAAK;AAChC,UAAO,UACH,QAAQ,QAAQ,GAChB,QACE,YAAY,aAAa,wBAAwB;IAC/C,QAAQ;IACR,SAAS;IACV,CAAC,CACH;;AAEP,MAAI,SAAS,WAAW,IAAK,QAAO,QAAQ,gBAAgB,KAAK,CAAC;AAClE,MAAI,SAAS,WAAW,KAAK;GAC3B,MAAM,aAAa,SAAS,KAAK,GAAG;AACpC,UAAO,QACL,YAAY,aAAa,cAAc;IACrC,QAAQ;IACR,GAAI,OAAO,eAAe,WAAW,EAAE,YAAY,GAAG,EAAE;IACzD,CAAC,CACH;;AAEH,MAAI,SAAS,WAAW,IACtB,QAAO,QACL,YAAY,aAAa,kBAAkB,EAAE,QAAQ,KAAK,CAAC,CAC5D;AAEH,SAAO,QACL,YAAY,aAAa,wBAAwB;GAC/C,QAAQ,SAAS;GACjB,SAAS,QAAQ,SAAS;GAC3B,CAAC,CACH;UACM,KAAK;EAGZ,MAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;AAChE,SAAO,QACL,YAAY,aAAa,wBAAwB,EAAE,SAAS,SAAS,CAAC,CACvE;WACO;AACR,WAAS;;;;;;ACpvBb,SAAgB,kBACd,SAC4B;AAC5B,QAAO,SAAS,MAAM,SAAS;;AAwBjC,SAAgB,sBAAmC;AACjD,QAAO;EACL,eAAe;EACf,UAAU,EAAE;EACZ,SAAS,EAAE;EACX,gBAAgB;EACjB;;;;;;;;;ACvCH,SAAgB,eAAe,OAA8B;AAC3D,KAAI,CAAC,SAAS,OAAO,UAAU,YAAY,MAAM,QAAQ,MAAM,CAC7D,QAAO;AAGT,QAAO;;;;;;;;;;;;;;;AAgBT,SAAgB,eAAe,MAAkC;AAC/D,KAAI,CAAC,KACH,QAAO;AAGT,KAAI,KAAK,UAAU,KACjB,QAAO,KAAK;CAGd,MAAM,QAAQ,eAAe,KAAK,MAAM;AACxC,KAAI,SAAS,MAAM,YAAY,KAAA,EAC7B,QAAO,iBAAiB,oBAAoB,MAAM,QAAQ,EAAE,MAAM,QAAQ;AAG5E,QAAO;;;;;;;;;;;AAYT,SAAS,oBAAoB,SAAgC;AAC3D,KAAI,YAAY,KACd,QAAO;CAGT,MAAM,SAAS,eAAe,QAAQ;CACtC,MAAM,MACJ,UAAU,OAAO,UAAU,OAAO,eAAe,OAAO,OAAO,GAAG;AAEpE,SAAS,OAAO,gBAAgB,IAAI,KAAK,EAAE;;;;;;;;;;AAW7C,SAAS,iBACP,aACA,SACc;AACd,KAAI,OAAO,YAAY,YAAY,QAAQ,MAAM,CAAC,WAAW,EAC3D,QAAO;CAGT,MAAM,MAAM,eAAe,YAAY,IAAI,EAAE;AAC7C,KAAI,WAAW,IACb,QAAO;AAGT,QAAO;EAAE,GAAG;EAAK,OAAO,EAAE,SAAS;EAAE;;AAGvC,SAAS,gBACP,QACqC;CACrC,MAAM,SAAkC,EAAE;AAE1C,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,EAAE;EACjD,MAAM,SAAS,eAAe,MAAM;EACpC,MAAM,cAAc,cAAc,MAAM,GACpC,QACA,UAAU,gBAAgB,OAAO;AAErC,MAAI,YACF,QAAO,OAAO;;AAIlB,QAAO,OAAO,KAAK,OAAO,CAAC,SAAS,IAAI,SAAS,KAAA;;AAGnD,SAAS,cAAc,OAAmC;AACxD,QACE,MAAM,QAAQ,MAAM,IACpB,MAAM,SAAS,KACf,MAAM,OAAO,YAAY,OAAO,YAAY,SAAS;;;;;;;ACjFzD,IAAa,WAAb,MAAa,iBAAiB,MAAM;CAClC;;;;;CAMA;;;;;CAMA;CAEA;CAEA,YACE,SACA,QACA,MACA,WACA,MACA;AACA,QAAM,QAAQ;AACd,OAAK,OAAO;AACZ,OAAK,SAAS;AACd,OAAK,OAAO,QAAQ;AACpB,OAAK,OAAO,QAAQ;AACpB,OAAK,YAAY;AAEjB,MAAI,uBAAuB,MAEvB,OAMA,kBAAkB,MAAM,SAAS;;CAIvC,SAOE;AACA,SAAO;GACL,MAAM,KAAK;GACX,SAAS,KAAK;GACd,QAAQ,KAAK;GACb,MAAM,KAAK;GACX,MAAM,KAAK;GACX,WAAW,KAAK;GACjB;;;AAIL,SAAS,mBAAmB,OAAoC;AAC9D,KAAI,OAAO,UAAU,SACnB;CAGF,MAAM,UAAU,MAAM,MAAM;AAC5B,QAAO,QAAQ,SAAS,IAAI,UAAU,KAAA;;AAGxC,SAAS,wBAAwB,SAAsC;AACrE,QACE,mBAAmB,QAAQ,IAAI,eAAe,CAAC,IAC/C,mBAAmB,QAAQ,IAAI,aAAa,CAAC,IAC7C,mBAAmB,QAAQ,IAAI,eAAe,CAAC;;AAInD,SAAS,yBAAyB,MAAmC;AACnE,KAAI,CAAC,QAAQ,OAAO,SAAS,YAAY,MAAM,QAAQ,KAAK,CAC1D;CAGF,MAAM,SAAS;CACf,MAAM,OAAO,OAAO;AAEpB,QACE,mBAAmB,OAAO,WAAW,IACrC,mBAAmB,OAAO,UAAU,KACnC,QAAQ,OAAO,SAAS,YAAY,CAAC,MAAM,QAAQ,KAAK,GACpD,mBAAoB,KAAiC,WAAW,IACjE,mBAAoB,KAAiC,UAAU,GAC/D,KAAA;;;;;AAOR,SAAgB,WAAW,OAAmC;AAC5D,QAAO,iBAAiB;;;;;AA4C1B,SAAgB,kBACd,QACqB;CACrB,MAAM,EACJ,SACA,cACA,aACA,iBAAiB,EAAE,EACnB,aACA,OACA,cACA,qBAAqB,UACnB;CACJ,MAAM,oBAAoB,KAAK,IAAI,GAAG,cAAc,cAAc,EAAE;CACpE,MAAM,0BAA0B,KAAK,IAAI,GAAG,cAAc,eAAe,EAAE;;;;CAK3E,eAAe,aACb,eACiC;EACjC,MAAM,UAAkC;GACtC,QAAQ;GACR,gBAAgB;GAChB,GAAG;GACH,GAAG;GACJ;AAGD,MAAI,cAAc;GAChB,MAAM,QAAQ,MAAM,cAAc;AAClC,OAAI,MACF,SAAQ,gBAAgB,UAAU;;AAItC,SAAO;;;;;;;CAQT,SAAS,QAAQ,UAA0B;AACzC,SAAO,GAAG,UAAU;;;;;;CAOtB,SAAS,SACP,UACA,QACQ;EACR,MAAM,UAAU,QAAQ,SAAS;AAEjC,MAAI,CAAC,UAAU,OAAO,KAAK,OAAO,CAAC,WAAW,EAC5C,QAAO;EAGT,MAAM,cAAc,IAAI,iBAAiB;AAEzC,SAAO,QAAQ,OAAO,CAAC,SAAS,CAAC,KAAK,WAAW;AAC/C,OAAI,UAAU,KAAA,KAAa,UAAU,KACnC;AAGF,OAAI,MAAM,QAAQ,MAAM,EAAE;IAMxB,MAAM,WAAW,IAAI,SAAS,KAAK,GAAG,MAAM,GAAG,IAAI;AACnD,UAAM,SAAS,SAAS,YAAY,OAAO,UAAU,OAAO,KAAK,CAAC,CAAC;cAC1D,OAAO,UAAU,SAE1B,QAAO,QAAQ,MAAM,CAAC,SAAS,CAAC,QAAQ,cAAc;AACpD,QAAI,aAAa,KAAA,KAAa,aAAa,KACzC;AAGF,QAAI,MAAM,QAAQ,SAAS,CACzB,UAAS,SAAS,SAChB,YAAY,OAAO,GAAG,IAAI,GAAG,OAAO,MAAM,OAAO,KAAK,CAAC,CACxD;QAED,aAAY,OAAO,GAAG,IAAI,GAAG,OAAO,IAAI,OAAO,SAAS,CAAC;KAE3D;OAEF,aAAY,OAAO,KAAK,OAAO,MAAM,CAAC;IAExC;EAEF,MAAM,KAAK,YAAY,UAAU;AACjC,SAAO,KAAK,GAAG,QAAQ,GAAG,OAAO;;;;;;CAOnC,eAAe,eACb,UACA,QACA,MACoB;EACpB,MAAM,kBAAkB,wBAAwB,SAAS,QAAQ;AAEjE,MAAI,SAAS,WAAW,OAAO,YAC7B,cAAa;AAGf,MAAI,CAAC,SAAS,IAAI;GAGhB,MAAM,YAAY,MAAM,SAAS,MAAM,CAAC,YAAY,GAAG;AAGvD,OAFoB,SAAS,QAAQ,IAAI,eAAe,EAEvC,SAAS,mBAAmB,EAAE;IAI7C,IAAI;AACJ,QAAI;AACF,cAAS,KAAK,MAAM,UAAU;YACxB;AACN,WAAM,IAAI,SACR,UAAU,MAAM,GAAG,IAAI,IACrB,GAAG,OAAO,8BAA8B,SAAS,UACnD,SAAS,QACT,MACA,gBACD;;IAGH,MAAM,OAAO,eAAe,OAAO;AA2BnC,UAAM,IAAI,UAvBE,cACD;KACL,MAAM,cACJ,OAAO,KAAK,UAAU,YAAY,KAAK,UAAU,OAC5C,KAAK,MAAgC,UACtC,KAAA;KACN,MAAM,cACJ,OAAO,KAAK,UAAU,WAAW,KAAK,QAAQ,KAAA;KAChD,MAAM,UACJ,OAAO,KAAK,YAAY,WAAW,KAAK,UAAU,KAAA;KACpD,MAAM,eACJ,OAAO,KAAK,kBAAkB,WAC1B,KAAK,gBACL,KAAA;AACN,YACE,WACA,gBACA,gBACC,OAAO,gBAAgB,WAAW,cAAc,KAAA;QAEjD,GACJ,KAAA,MAGK,GAAG,OAAO,8BAA8B,SAAS,UACxD,SAAS,QACT,OAAO,eAAe,KAAK,GAAI,QAC/B,mBAAmB,yBAAyB,OAAO,EACnD,KACD;SAED,OAAM,IAAI,SACR,GAAG,OAAO,8BAA8B,SAAS,UACjD,SAAS,QACT,MACA,gBACD;;AAIL,MACE,SAAS,WAAW,OACpB,SAAS,QAAQ,IAAI,iBAAiB,KAAK,IAE3C,QAAO;AAKT,MAFoB,SAAS,QAAQ,IAAI,eAAe,EAEvC,SAAS,mBAAmB,EAAE;GAC7C,MAAM,eAAe,MAAM,SAAS,MAAM;AAE1C,OAAI;AAEF,WADa,KAAK,MAAM,aAAa;WAE/B;AACN,QAAI,mBACF,OAAM,IAAI,SACR,oCACA,SAAS,QACT,MACA,gBACD;AAKH,WAAO,eAAgB,eAA8B;;;AAKzD,SAAO;;CAGT,SAAS,uBAAuB,cAA8B;AAC5D,SAAO,0BAA0B,MAAM,eAAe;;CAGxD,eAAe,oBAAoB,cAAqC;EACtE,MAAM,UAAU,uBAAuB,aAAa;AACpD,MAAI,WAAW,EACb;AAGF,QAAM,IAAI,SAAS,YAAY,WAAW,SAAS,QAAQ,CAAC;;CAG9D,eAAe,sBACb,KACA,cACA,QACmB;EACnB,IAAI,aAAa;AAEjB,SAAO,KACL,KAAI;AACF,UAAO,MAAM,MAAM,KAAK,aAAa;WAC9B,cAAc;AACrB,OAAI,QAAQ,WAAW,cAAc,kBACnC,OAAM;AAGR,iBAAc;AACd,SAAM,oBAAoB,WAAW;AAErC,OAAI,QAAQ,QACV,OAAM;;;;;;CASd,eAAe,QACb,UACA,UAA0B,EAAE,EACR;EACpB,MAAM,EACJ,SAAS,OACT,SAAS,eACT,QACA,MACA,QACA,aACE;EAEJ,MAAM,MAAM,SAAS,SAAS,UAAU,OAAO,GAAG,QAAQ,SAAS;EAEnE,MAAM,UAAU,MAAM,aAAa,cAAc;EAEjD,IAAI;AAEJ,MAAI;GACF,MAAM,eAA4B;IAAE;IAAQ;IAAS;AACrD,OAAI,YAAa,cAAa,cAAc;AAC5C,OAAI,MAAO,cAAa,QAAQ;AAChC,OAAI,SAAU,cAAa,WAAW;GACtC,MAAM,iBACJ,QAAQ,WAAW,QAAQ,KAAK,UAAU,KAAK,GAAG;AACpD,OAAI,eAAgB,cAAa,OAAO;AACxC,OAAI,OAAQ,cAAa,SAAS;AAClC,cAAW,MAAM,sBAAsB,KAAK,cAAc,OAAO;WAC1D,cAAc;AACrB,SAAM,IAAI,SACR,kBAAkB,wBAAwB,QAAQ,aAAa,UAAU,2BACzE,GACA,KACD;;AAGH,SAAO,eAA0B,UAAU,QAAQ,IAAI;;;;;CAMzD,eAAe,oBACb,UACA,UACA,UAEI,EAAE,EACc;EACpB,MAAM,EACJ,SAAS,QACT,SAAS,eACT,QACA,aACE;EAEJ,MAAM,MAAM,QAAQ,SAAS;EAC7B,MAAM,UAAU,MAAM,aAAa,cAAc;AAGjD,SAAO,QAAQ;EAEf,IAAI;AAEJ,MAAI;GACF,MAAM,eAA4B;IAAE;IAAQ;IAAS,MAAM;IAAU;AACrE,OAAI,YAAa,cAAa,cAAc;AAC5C,OAAI,MAAO,cAAa,QAAQ;AAChC,OAAI,SAAU,cAAa,WAAW;AACtC,OAAI,OAAQ,cAAa,SAAS;AAClC,cAAW,MAAM,sBAAsB,KAAK,cAAc,OAAO;WAC1D,cAAc;AACrB,SAAM,IAAI,SACR,kBAAkB,wBAAwB,QAAQ,aAAa,UAAU,2BACzE,GACA,KACD;;AAGH,SAAO,eAA0B,UAAU,QAAQ,IAAI;;AAIzD,QAAO;EACI;EACY;EAGrB,MACE,UACA,QACA,YAEA,QAAmB,UAAU;GAC3B,GAAG;GACH,QAAQ;GACR,GAAI,UAAU,EAAE,QAAQ;GACzB,CAAC;EAEJ,OACE,UACA,MACA,YAEA,QAAmB,UAAU;GAC3B,GAAG;GACH,QAAQ;GACR;GACD,CAAC;EAEJ,MACE,UACA,MACA,YAEA,QAAmB,UAAU;GAC3B,GAAG;GACH,QAAQ;GACR;GACD,CAAC;EAEJ,QACE,UACA,MACA,YAEA,QAAmB,UAAU;GAC3B,GAAG;GACH,QAAQ;GACR;GACD,CAAC;EAEJ,SACE,UACA,YAEA,QAAmB,UAAU;GAC3B,GAAG;GACH,QAAQ;GACT,CAAC;EACL;;;;ACxlBH,SAAS,kBAAkB,OAAoC;AAC7D,KAAI,OAAO,UAAU,SACnB;CAGF,MAAM,UAAU,MAAM,MAAM;AAC5B,QAAO,QAAQ,SAAS,IAAI,UAAU,KAAA;;AAGxC,SAAS,aACP,UACA,OACoB;CACpB,MAAM,OAAO,eAAe,OAAO,KAAK;AAExC,QACE,kBAAkB,SAAS,QAAQ,IAAI,eAAe,CAAC,IACvD,kBAAkB,SAAS,QAAQ,IAAI,aAAa,CAAC,IACrD,kBAAkB,OAAO,WAAW,IACpC,kBAAkB,OAAO,UAAU,IACnC,kBAAkB,MAAM,WAAW,IACnC,kBAAkB,MAAM,UAAU;;AAItC,SAAS,gBACP,OACA,UACQ;CACR,MAAM,cAAc,eAAe,MAAM,MAAM;AAE/C,QACE,kBAAkB,MAAM,QAAQ,IAChC,kBAAkB,MAAM,cAAc,IACtC,kBAAkB,aAAa,QAAQ,IACvC;;;;;;;AASJ,SAAgB,6BACd,UAAyC,EAAE,EACnB;AACxB,SAAQ,OAAO,UAAU,SAAS,mBAAmB;AACnD,MAAI,CAAC,UAAU;AACb,OAAI,iBAAiB,SACnB,QAAO;AAGT,OAAI,QAAQ,kCAAkC,MAAM,CAClD,QAAO;AAKT,OAAI,CAAC,QACH,QAAO;AAGT,UAAO,IAAI,SACT,kBAAkB,iBAAiB,QAAQ,MAAM,UAAU,2BAC3D,GACA,KACD;;AAKH,MAAI,SAAS,MAAM,iBAAiB,YAClC,QAAO,IAAI,SACT,oCACA,SAAS,QACT,MACA,aAAa,UAAU,KAAK,CAC7B;AAGH,UAAQ,kBAAkB,SAAS;EAEnC,MAAM,SAAS,SAAS,UAAU,eAAe,UAAU;EAC3D,MAAM,cAAc,eAAe,MAAM;EACzC,MAAM,YAAY,aAAa,UAAU,YAAY;AAErD,MAAI,YAGF,QAAO,IAAI,SACT,gBAAgB,aAAa,GAAG,OAAO,iBAAiB,EACxD,SAAS,QACT,eAAe,YAAY,EAC3B,WACA,YACD;EAGH,MAAM,SAAS,SAAS,QACrB,IAAI,eAAe,EAClB,SAAS,mBAAmB;EAChC,MAAM,OAAO,kBAAkB,MAAM;AAErC,SAAO,IAAI,SACT,UAAU,OACN,KAAK,MAAM,GAAG,IAAI,GAClB,GAAG,OAAO,8BAA8B,SAAS,UACrD,SAAS,QAIT,SAAU,QAAyB,MACnC,WAGA,KACD;;;;;ACjGL,MAAM,gCACJ,MACA,KACA,UACS;AACT,KAAI,OAAO,UAAU,SACnB,MAAK,OAAO,KAAK,MAAM;KAEvB,MAAK,OAAO,KAAK,KAAK,UAAU,MAAM,CAAC;;AAuB3C,MAAa,qBAAqB,EAChC,iBAAiB,SACf,KAAK,UAAU,OAAO,MAAM,UAC1B,OAAO,UAAU,WAAW,MAAM,UAAU,GAAG,MAChD,EACJ;AAED,MAAa,gCAAgC,EAC3C,iBAAiB,SAA0B;CACzC,MAAM,OAAO,IAAI,iBAAiB;AAElC,QAAO,QAAQ,KAAgC,CAAC,SAAS,CAAC,KAAK,WAAW;AACxE,MAAI,UAAU,KAAA,KAAa,UAAU,KACnC;AAEF,MAAI,MAAM,QAAQ,MAAM,CACtB,OAAM,SAAS,MAAM,6BAA6B,MAAM,KAAK,EAAE,CAAC;MAEhE,8BAA6B,MAAM,KAAK,MAAM;GAEhD;AAEF,QAAO,KAAK,UAAU;GAEzB;AC7CqB,OAAO,QANkB;CAC7C,QAAQ;CACR,WAAW;CACX,QAAQ;CACR,SAAS;CACV,CACqD;;;ACsCtD,SAAgB,gBAAiC,EAC/C,WACA,YACA,YACA,qBACA,mBACA,sBACA,qBACA,kBACA,YACA,KACA,GAAG,WACsD;CACzD,IAAI;CAEJ,MAAM,QACJ,gBACE,OAAe,IAAI,SAAS,YAAY,WAAW,SAAS,GAAG,CAAC;CAEpE,MAAM,eAAe,mBAAmB;EACtC,IAAI,aAAqB,wBAAwB;EACjD,IAAI,UAAU;EACd,MAAM,SAAS,QAAQ,UAAU,IAAI,iBAAiB,CAAC;AAEvD,SAAO,MAAM;AACX,OAAI,OAAO,QAAS;AAEpB;GAEA,MAAM,UACJ,QAAQ,mBAAmB,UACvB,QAAQ,UACR,IAAI,QAAQ,QAAQ,QAA8C;AAExE,OAAI,gBAAgB,KAAA,EAClB,SAAQ,IAAI,iBAAiB,YAAY;AAG3C,OAAI;IACF,MAAM,cAA2B;KAC/B,UAAU;KACV,GAAG;KACH,MAAM,QAAQ;KACd;KACA;KACD;IACD,IAAI,UAAU,IAAI,QAAQ,KAAK,YAAY;AAC3C,QAAI,UACF,WAAU,MAAM,UAAU,KAAK,YAAY;IAK7C,MAAM,WAAW,OADF,QAAQ,SAAS,WAAW,OACb,QAAQ;AAEtC,QAAI,CAAC,SAAS,GACZ,OAAM,IAAI,MACR,eAAe,SAAS,OAAO,GAAG,SAAS,aAC5C;AAEH,QAAI,CAAC,SAAS,KAAM,OAAM,IAAI,MAAM,0BAA0B;IAE9D,MAAM,SAAS,SAAS,KACrB,YAAY,IAAI,mBAAmB,CAAC,CACpC,WAAW;IAEd,IAAI,SAAS;IAEb,MAAM,qBAAqB;AACzB,SAAI;AACF,aAAO,QAAQ;aACT;;AAKV,WAAO,iBAAiB,SAAS,aAAa;AAE9C,QAAI;AACF,YAAO,MAAM;MACX,MAAM,EAAE,MAAM,UAAU,MAAM,OAAO,MAAM;AAC3C,UAAI,KAAM;AACV,gBAAU;AACV,eAAS,OAAO,QAAQ,UAAU,KAAK;MAEvC,MAAM,SAAS,OAAO,MAAM,OAAO;AACnC,eAAS,OAAO,KAAK,IAAI;AAEzB,WAAK,MAAM,SAAS,QAAQ;OAC1B,MAAM,QAAQ,MAAM,MAAM,KAAK;OAC/B,MAAM,YAA2B,EAAE;OACnC,IAAI;AAEJ,YAAK,MAAM,QAAQ,MACjB,KAAI,KAAK,WAAW,QAAQ,CAC1B,WAAU,KAAK,KAAK,QAAQ,aAAa,GAAG,CAAC;gBACpC,KAAK,WAAW,SAAS,CAClC,aAAY,KAAK,QAAQ,cAAc,GAAG;gBACjC,KAAK,WAAW,MAAM,CAC/B,eAAc,KAAK,QAAQ,WAAW,GAAG;gBAChC,KAAK,WAAW,SAAS,EAAE;QACpC,MAAM,SAAS,OAAO,SACpB,KAAK,QAAQ,cAAc,GAAG,EAC9B,GACD;AACD,YAAI,CAAC,OAAO,MAAM,OAAO,CACvB,cAAa;;OAKnB,IAAI;OACJ,IAAI,aAAa;AAEjB,WAAI,UAAU,QAAQ;QACpB,MAAM,UAAU,UAAU,KAAK,KAAK;AACpC,YAAI;AACF,gBAAO,KAAK,MAAM,QAAQ;AAC1B,sBAAa;gBACP;AACN,gBAAO;;;AAIX,WAAI,YAAY;AACd,YAAI,kBACF,OAAM,kBAAkB,KAAK;AAG/B,YAAI,oBACF,QAAO,MAAM,oBAAoB,KAAK;;AAI1C,oBAAa;QACX;QACA,OAAO;QACP,IAAI;QACJ,OAAO;QACR,CAAC;AAEF,WAAI,UAAU,OACZ,OAAM;;;cAIJ;AACR,YAAO,oBAAoB,SAAS,aAAa;AACjD,YAAO,aAAa;;AAGtB;YACO,OAAO;AAEd,iBAAa,MAAM;AAEnB,QACE,wBAAwB,KAAA,KACxB,WAAW,oBAEX;AAQF,UAAM,MAJU,KAAK,IACnB,aAAa,MAAM,UAAU,IAC7B,oBAAoB,IACrB,CACmB;;;;AAO1B,QAAO,EAAE,QAFM,cAAc,EAEZ;;;;AC3OnB,MAAa,yBACX,UAC0B;AAC1B,SAAQ,OAAR;EACE,KAAK,QACH,QAAO;EACT,KAAK,SACH,QAAO;EACT,KAAK,SACH,QAAO;EACT,QACE,QAAO;;;AAIb,MAAa,2BACX,UACsB;AACtB,SAAQ,OAAR;EACE,KAAK,OACH,QAAO;EACT,KAAK,gBACH,QAAO;EACT,KAAK,iBACH,QAAO;EACT,QACE,QAAO;;;AAIb,MAAa,0BACX,UAC0B;AAC1B,SAAQ,OAAR;EACE,KAAK,QACH,QAAO;EACT,KAAK,SACH,QAAO;EACT,KAAK,SACH,QAAO;EACT,QACE,QAAO;;;AAIb,MAAa,uBAAuB,EAClC,eACA,SACA,MACA,OACA,YAGY;AACZ,KAAI,CAAC,SAAS;EACZ,MAAM,gBACJ,gBAAgB,QAAQ,MAAM,KAAK,MAAM,mBAAmB,EAAY,CAAC,EACzE,KAAK,wBAAwB,MAAM,CAAC;AACtC,UAAQ,OAAR;GACE,KAAK,QACH,QAAO,IAAI;GACb,KAAK,SACH,QAAO,IAAI,KAAK,GAAG;GACrB,KAAK,SACH,QAAO;GACT,QACE,QAAO,GAAG,KAAK,GAAG;;;CAIxB,MAAM,YAAY,sBAAsB,MAAM;CAC9C,MAAM,eAAe,MAClB,KAAK,MAAM;AACV,MAAI,UAAU,WAAW,UAAU,SACjC,QAAO,gBAAgB,IAAI,mBAAmB,EAAY;AAG5D,SAAO,wBAAwB;GAC7B;GACA;GACA,OAAO;GACR,CAAC;GACF,CACD,KAAK,UAAU;AAClB,QAAO,UAAU,WAAW,UAAU,WAClC,YAAY,eACZ;;AAGN,MAAa,2BAA2B,EACtC,eACA,MACA,YACqC;AACrC,KAAI,UAAU,KAAA,KAAa,UAAU,KACnC,QAAO;AAGT,KAAI,OAAO,UAAU,SACnB,OAAM,IAAI,MACR,uGACD;AAGH,QAAO,GAAG,KAAK,GAAG,gBAAgB,QAAQ,mBAAmB,MAAM;;AAGrE,MAAa,wBAAwB,EACnC,eACA,SACA,MACA,OACA,OACA,gBAIY;AACZ,KAAI,iBAAiB,KACnB,QAAO,YAAY,MAAM,aAAa,GAAG,GAAG,KAAK,GAAG,MAAM,aAAa;AAGzE,KAAI,UAAU,gBAAgB,CAAC,SAAS;EACtC,IAAI,SAAmB,EAAE;AACzB,SAAO,QAAQ,MAAM,CAAC,SAAS,CAAC,KAAK,OAAO;AAC1C,YAAS;IACP,GAAG;IACH;IACA,gBAAiB,IAAe,mBAAmB,EAAY;IAChE;IACD;EACF,MAAM,eAAe,OAAO,KAAK,IAAI;AACrC,UAAQ,OAAR;GACE,KAAK,OACH,QAAO,GAAG,KAAK,GAAG;GACpB,KAAK,QACH,QAAO,IAAI;GACb,KAAK,SACH,QAAO,IAAI,KAAK,GAAG;GACrB,QACE,QAAO;;;CAIb,MAAM,YAAY,uBAAuB,MAAM;CAC/C,MAAM,eAAe,OAAO,QAAQ,MAAM,CACvC,KAAK,CAAC,KAAK,OACV,wBAAwB;EACtB;EACA,MAAM,UAAU,eAAe,GAAG,KAAK,GAAG,IAAI,KAAK;EACnD,OAAO;EACR,CAAC,CACH,CACA,KAAK,UAAU;AAClB,QAAO,UAAU,WAAW,UAAU,WAClC,YAAY,eACZ;;;;ACzKN,MAAa,gBAAwB;AAErC,MAAa,yBAAyB,EACpC,MACA,KAAK,WACuB;CAC5B,IAAI,MAAM;CACV,MAAM,UAAU,KAAK,MAAM,cAAc;AACzC,KAAI,QACF,MAAK,MAAM,SAAS,SAAS;EAC3B,IAAI,UAAU;EACd,IAAI,OAAO,MAAM,UAAU,GAAG,MAAM,SAAS,EAAE;EAC/C,IAAI,QAA6B;AAEjC,MAAI,KAAK,SAAS,IAAI,EAAE;AACtB,aAAU;AACV,UAAO,KAAK,UAAU,GAAG,KAAK,SAAS,EAAE;;AAG3C,MAAI,KAAK,WAAW,IAAI,EAAE;AACxB,UAAO,KAAK,UAAU,EAAE;AACxB,WAAQ;aACC,KAAK,WAAW,IAAI,EAAE;AAC/B,UAAO,KAAK,UAAU,EAAE;AACxB,WAAQ;;EAGV,MAAM,QAAQ,KAAK;AAEnB,MAAI,UAAU,KAAA,KAAa,UAAU,KACnC;AAGF,MAAI,MAAM,QAAQ,MAAM,EAAE;AACxB,SAAM,IAAI,QACR,OACA,oBAAoB;IAAE;IAAS;IAAM;IAAO;IAAO,CAAC,CACrD;AACD;;AAGF,MAAI,OAAO,UAAU,UAAU;AAC7B,SAAM,IAAI,QACR,OACA,qBAAqB;IACnB;IACA;IACA;IACO;IACP,WAAW;IACZ,CAAC,CACH;AACD;;AAGF,MAAI,UAAU,UAAU;AACtB,SAAM,IAAI,QACR,OACA,IAAI,wBAAwB;IAC1B;IACO;IACR,CAAC,GACH;AACD;;EAGF,MAAM,eAAe,mBACnB,UAAU,UAAU,IAAI,UAAqB,MAC9C;AACD,QAAM,IAAI,QAAQ,OAAO,aAAa;;AAG1C,QAAO;;AAGT,MAAa,UAAU,EACrB,SACA,MACA,OACA,iBACA,KAAK,WAOO;CACZ,MAAM,UAAU,KAAK,WAAW,IAAI,GAAG,OAAO,IAAI;CAClD,IAAI,OAAO,WAAW,MAAM;AAC5B,KAAI,KACF,OAAM,sBAAsB;EAAE;EAAM;EAAK,CAAC;CAE5C,IAAI,SAAS,QAAQ,gBAAgB,MAAM,GAAG;AAC9C,KAAI,OAAO,WAAW,IAAI,CACxB,UAAS,OAAO,UAAU,EAAE;AAE9B,KAAI,OACF,QAAO,IAAI;AAEb,QAAO;;AAGT,SAAgB,oBAAoB,SAIxB;CACV,MAAM,UAAU,QAAQ,SAAS,KAAA;AAGjC,KAFyB,WAAW,QAAQ,gBAEtB;AACpB,MAAI,oBAAoB,QAItB,QAFE,QAAQ,mBAAmB,KAAA,KAAa,QAAQ,mBAAmB,KAE1C,QAAQ,iBAAiB;AAItD,SAAO,QAAQ,SAAS,KAAK,QAAQ,OAAO;;AAI9C,KAAI,QACF,QAAO,QAAQ;;;;AChHnB,MAAaC,iBAAe,OAC1B,MACA,aACgC;CAChC,MAAM,QACJ,OAAO,aAAa,aAAa,MAAM,SAAS,KAAK,GAAG;AAE1D,KAAI,CAAC,MACH;AAGF,KAAI,KAAK,WAAW,SAClB,QAAO,UAAU;AAGnB,KAAI,KAAK,WAAW,QAClB,QAAO,SAAS,KAAK,MAAM;AAG7B,QAAO;;;;AC7BT,MAAa,yBAAsC,EACjD,aAAa,EAAE,EACf,GAAG,SACuB,EAAE,KAAmC;CAC/D,MAAM,mBAAmB,gBAA2B;EAClD,MAAM,SAAmB,EAAE;AAC3B,MAAI,eAAe,OAAO,gBAAgB,SACxC,MAAK,MAAM,QAAQ,aAAa;GAC9B,MAAM,QAAQ,YAAY;AAE1B,OAAI,UAAU,KAAA,KAAa,UAAU,KACnC;GAGF,MAAM,UAAU,WAAW,SAAS;AAEpC,OAAI,MAAM,QAAQ,MAAM,EAAE;IACxB,MAAM,kBAAkB,oBAAoB;KAC1C,eAAe,QAAQ;KACvB,SAAS;KACT;KACA,OAAO;KACP;KACA,GAAG,QAAQ;KACZ,CAAC;AACF,QAAI,gBAAiB,QAAO,KAAK,gBAAgB;cACxC,OAAO,UAAU,UAAU;IACpC,MAAM,mBAAmB,qBAAqB;KAC5C,eAAe,QAAQ;KACvB,SAAS;KACT;KACA,OAAO;KACA;KACP,GAAG,QAAQ;KACZ,CAAC;AACF,QAAI,iBAAkB,QAAO,KAAK,iBAAiB;UAC9C;IACL,MAAM,sBAAsB,wBAAwB;KAClD,eAAe,QAAQ;KACvB;KACO;KACR,CAAC;AACF,QAAI,oBAAqB,QAAO,KAAK,oBAAoB;;;AAI/D,SAAO,OAAO,KAAK,IAAI;;AAEzB,QAAO;;;;;AAMT,MAAa,cACX,gBACuC;AACvC,KAAI,CAAC,YAGH,QAAO;CAGT,MAAM,eAAe,YAAY,MAAM,IAAI,CAAC,IAAI,MAAM;AAEtD,KAAI,CAAC,aACH;AAGF,KACE,aAAa,WAAW,mBAAmB,IAC3C,aAAa,SAAS,QAAQ,CAE9B,QAAO;AAGT,KAAI,iBAAiB,sBACnB,QAAO;AAGT,KACE;EAAC;EAAgB;EAAU;EAAU;EAAS,CAAC,MAAM,SACnD,aAAa,WAAW,KAAK,CAC9B,CAED,QAAO;AAGT,KAAI,aAAa,WAAW,QAAQ,CAClC,QAAO;;AAMX,MAAM,qBACJ,SAGA,SACY;AACZ,KAAI,CAAC,KACH,QAAO;AAET,KACE,QAAQ,QAAQ,IAAI,KAAK,IACzB,QAAQ,QAAQ,SAChB,QAAQ,QAAQ,IAAI,SAAS,EAAE,SAAS,GAAG,KAAK,GAAG,CAEnD,QAAO;AAET,QAAO;;AAGT,eAAsB,cACpB,SAGe;AACf,MAAK,MAAM,QAAQ,QAAQ,YAAY,EAAE,EAAE;AACzC,MAAI,kBAAkB,SAAS,KAAK,KAAK,CACvC;EAGF,MAAM,QAAQ,MAAMC,eAAa,MAAM,QAAQ,KAAK;AAEpD,MAAI,CAAC,MACH;EAGF,MAAM,OAAO,KAAK,QAAQ;AAE1B,UAAQ,KAAK,IAAb;GACE,KAAK;AACH,QAAI,CAAC,QAAQ,MACX,SAAQ,QAAQ,EAAE;AAEpB,YAAQ,MAAM,QAAQ;AACtB;GACF,KAAK;AACH,YAAQ,QAAQ,OAAO,UAAU,GAAG,KAAK,GAAG,QAAQ;AACpD;GAEF;AACE,YAAQ,QAAQ,IAAI,MAAM,MAAM;AAChC;;;;AAKR,MAAa,YAAgC,YAC3C,OAAO;CACL,SAAS,QAAQ;CACjB,MAAM,QAAQ;CACd,OAAO,QAAQ;CACf,iBACE,OAAO,QAAQ,oBAAoB,aAC/B,QAAQ,kBACR,sBAAsB,QAAQ,gBAAgB;CACpD,KAAK,QAAQ;CACd,CAAC;AAEJ,MAAa,gBAAgB,GAAW,MAAsB;CAC5D,MAAM,SAAS;EAAE,GAAG;EAAG,GAAG;EAAG;AAC7B,KAAI,OAAO,SAAS,SAAS,IAAI,CAC/B,QAAO,UAAU,OAAO,QAAQ,UAAU,GAAG,OAAO,QAAQ,SAAS,EAAE;AAEzE,QAAO,UAAU,aAAa,EAAE,SAAS,EAAE,QAAQ;AACnD,QAAO;;AAGT,MAAM,kBAAkB,YAA8C;CACpE,MAAM,UAAmC,EAAE;AAC3C,SAAQ,SAAS,OAAO,QAAQ;AAC9B,UAAQ,KAAK,CAAC,KAAK,MAAM,CAAC;GAC1B;AACF,QAAO;;AAGT,MAAa,gBACX,GAAG,YACS;CACZ,MAAM,gBAAgB,IAAI,SAAS;AACnC,MAAK,MAAM,UAAU,SAAS;AAC5B,MAAI,CAAC,OACH;EAGF,MAAM,WACJ,kBAAkB,UACd,eAAe,OAAO,GACtB,OAAO,QAAQ,OAAO;AAE5B,OAAK,MAAM,CAAC,KAAK,UAAU,SACzB,KAAI,UAAU,KACZ,eAAc,OAAO,IAAI;WAChB,MAAM,QAAQ,MAAM,CAC7B,MAAK,MAAM,KAAK,MACd,eAAc,OAAO,KAAK,EAAY;WAE/B,UAAU,KAAA,EAGnB,eAAc,IACZ,KACA,OAAO,UAAU,WAAW,KAAK,UAAU,MAAM,GAAI,MACtD;;AAIP,QAAO;;AAuBT,IAAM,eAAN,MAAgC;CAC9B,MAAiC,EAAE;CAEnC,QAAc;AACZ,OAAK,MAAM,EAAE;;CAGf,MAAM,IAAgC;EACpC,MAAM,QAAQ,KAAK,oBAAoB,GAAG;AAC1C,MAAI,KAAK,IAAI,OACX,MAAK,IAAI,SAAS;;CAItB,OAAO,IAAmC;EACxC,MAAM,QAAQ,KAAK,oBAAoB,GAAG;AAC1C,SAAO,QAAQ,KAAK,IAAI,OAAO;;CAGjC,oBAAoB,IAAkC;AACpD,MAAI,OAAO,OAAO,SAChB,QAAO,KAAK,IAAI,MAAM,KAAK;AAE7B,SAAO,KAAK,IAAI,QAAQ,GAAG;;CAG7B,OACE,IACA,IAC8B;EAC9B,MAAM,QAAQ,KAAK,oBAAoB,GAAG;AAC1C,MAAI,KAAK,IAAI,QAAQ;AACnB,QAAK,IAAI,SAAS;AAClB,UAAO;;AAET,SAAO;;CAGT,IAAI,IAAyB;AAC3B,OAAK,IAAI,KAAK,GAAG;AACjB,SAAO,KAAK,IAAI,SAAS;;;AAU7B,MAAa,4BAKP;CACJ,OAAO,IAAI,cAAsD;CACjE,SAAS,IAAI,cAA4C;CACzD,UAAU,IAAI,cAAiD;CAChE;AAED,MAAM,yBAAyB,sBAAsB;CACnD,eAAe;CACf,OAAO;EACL,SAAS;EACT,OAAO;EACR;CACD,QAAQ;EACN,SAAS;EACT,OAAO;EACR;CACF,CAAC;AAEF,MAAM,iBAAiB,EACrB,gBAAgB,oBACjB;AAED,MAAa,gBACX,WAAqD,EAAE,MACT;CAC9C,GAAG;CACH,SAAS;CACT,SAAS;CACT,iBAAiB;CACjB,GAAG;CACJ;;;ACtTD,MAAa,gBAAgB,SAAiB,EAAE,KAAa;CAC3D,IAAI,UAAU,aAAa,cAAc,EAAE,OAAO;CAElD,MAAM,mBAA2B,EAAE,GAAG,SAAS;CAE/C,MAAM,aAAa,WAA2B;AAC5C,YAAU,aAAa,SAAS,OAAO;AACvC,SAAO,WAAW;;CAGpB,MAAM,eAAe,oBAKlB;CAEH,MAAM,gBAAgB,OAMpB,YACG;EACH,MAAM,OAAO;GACX,GAAG;GACH,GAAG;GACH,OAAO,QAAQ,SAAS,QAAQ,SAAS,WAAW;GACpD,SAAS,aAAa,QAAQ,SAAS,QAAQ,QAAQ;GACvD,gBAAgB,KAAA;GACjB;AAED,MAAI,KAAK,SACP,OAAM,cAAc,KAAK;AAG3B,MAAI,KAAK,iBACP,OAAM,KAAK,iBAAiB,KAAK;AAGnC,MAAI,KAAK,SAAS,KAAA,KAAa,KAAK,eAClC,MAAK,iBAAiB,KAAK,eAAe,KAAK,KAAK;AAMtD,MAAI,KAAK,SAAS,KAAA,KAAa,KAAK,mBAAmB,GACrD,MAAK,QAAQ,OAAO,eAAe;EAGrC,MAAM,eAAe;AAIrB,SAAO;GAAE,MAAM;GAAc,KAFjB,SAAS,aAAa;GAEA;;CAGpC,MAAM,UAA6B,OAAO,YAAY;EACpD,MAAM,eAAe,QAAQ,gBAAgB,QAAQ;EACrD,MAAM,gBAAgB,QAAQ,iBAAiB,QAAQ;EAEvD,IAAI;EACJ,IAAI;AAEJ,MAAI;GACF,MAAM,EAAE,MAAM,QAAQ,MAAM,cAAc,QAAQ;GAClD,MAAM,cAAuB;IAC3B,UAAU;IACV,GAAG;IACH,MAAM,oBAAoB,KAAK;IAChC;AAED,aAAU,IAAI,QAAQ,KAAK,YAAY;AAEvC,QAAK,MAAM,MAAM,aAAa,QAAQ,IACpC,KAAI,GACF,WAAU,MAAM,GAAG,SAAS,KAAK;GAMrC,MAAM,SAAS,KAAK;AAEpB,cAAW,MAAM,OAAO,QAAQ;AAEhC,QAAK,MAAM,MAAM,aAAa,SAAS,IACrC,KAAI,GACF,YAAW,MAAM,GAAG,UAAU,SAAS,KAAK;GAIhD,MAAM,SAAS;IACb;IACA;IACD;AAED,OAAI,SAAS,IAAI;IACf,MAAM,WACH,KAAK,YAAY,SACd,WAAW,SAAS,QAAQ,IAAI,eAAe,CAAC,GAChD,KAAK,YAAY;AAEvB,QACE,SAAS,WAAW,OACpB,SAAS,QAAQ,IAAI,iBAAiB,KAAK,KAC3C;KACA,IAAI;AACJ,aAAQ,SAAR;MACE,KAAK;MACL,KAAK;MACL,KAAK;AACH,mBAAY,MAAM,SAAS,UAAU;AACrC;MACF,KAAK;AACH,mBAAY,IAAI,UAAU;AAC1B;MACF,KAAK;AACH,mBAAY,SAAS;AACrB;MAEF;AACE,mBAAY,EAAE;AACd;;AAEJ,YAAO,KAAK,kBAAkB,SAC1B,YACA;MACE,MAAM;MACN,GAAG;MACJ;;IAGP,IAAI;AACJ,YAAQ,SAAR;KACE,KAAK;KACL,KAAK;KACL,KAAK;KACL,KAAK;AACH,aAAO,MAAM,SAAS,UAAU;AAChC;KACF,KAAK,QAAQ;MAGX,MAAM,OAAO,MAAM,SAAS,MAAM;AAClC,aAAO,OAAO,KAAK,MAAM,KAAK,GAAG,EAAE;AACnC;;KAEF,KAAK,SACH,QAAO,KAAK,kBAAkB,SAC1B,SAAS,OACT;MACE,MAAM,SAAS;MACf,GAAG;MACJ;;AAGT,QAAI,YAAY,QAAQ;AACtB,SAAI,KAAK,kBACP,OAAM,KAAK,kBAAkB,KAAK;AAGpC,SAAI,KAAK,oBACP,QAAO,MAAM,KAAK,oBAAoB,KAAK;;AAI/C,WAAO,KAAK,kBAAkB,SAC1B,OACA;KACE;KACA,GAAG;KACJ;;GAGP,MAAM,YAAY,MAAM,SAAS,MAAM;GACvC,IAAI;AAEJ,OAAI;AACF,gBAAY,KAAK,MAAM,UAAU;WAC3B;AAIR,SAAM,aAAa;WACZ,OAAO;GACd,IAAI,aAAa;AAEjB,QAAK,MAAM,MAAM,aAAa,MAAM,IAClC,KAAI,GACF,cAAa,MAAM,GACjB,YACA,UACA,SACA,QACD;AAIL,gBAAa,cAAc,EAAE;AAE7B,OAAI,aACF,OAAM;AAIR,UAAO,kBAAkB,SACrB,KAAA,IACA;IACE,OAAO;IACP;IACA;IACD;;;CAIT,MAAM,gBACH,YAAmC,YAClC,QAAQ;EAAE,GAAG;EAAS;EAAQ,CAAC;CAEnC,MAAM,aACH,WAAkC,OAAO,YAA4B;EACpE,MAAM,EAAE,MAAM,QAAQ,MAAM,cAAc,QAAQ;AAClD,SAAO,gBAAgB;GACrB,GAAG;GACH,MAAM,KAAK;GACX;GACA,WAAW,OAAO,KAAK,SAAS;IAC9B,IAAI,UAAU,IAAI,QAAQ,KAAK,KAAK;AACpC,SAAK,MAAM,MAAM,aAAa,QAAQ,IACpC,KAAI,GACF,WAAU,MAAM,GAAG,SAAS,KAAK;AAGrC,WAAO;;GAET,gBAAgB,oBAAoB,KAAK;GAIzC;GACD,CAAC;;CAGN,MAAM,aAAiC,YACrC,SAAS;EAAE,GAAG;EAAS,GAAG;EAAS,CAAC;AAEtC,QAAO;EACL,UAAU;EACV,SAAS,aAAa,UAAU;EAChC,QAAQ,aAAa,SAAS;EAC9B,KAAK,aAAa,MAAM;EACxB;EACA,MAAM,aAAa,OAAO;EAC1B;EACA,SAAS,aAAa,UAAU;EAChC,OAAO,aAAa,QAAQ;EAC5B,MAAM,aAAa,OAAO;EAC1B,KAAK,aAAa,MAAM;EACxB;EACA;EACA,KAAK;GACH,SAAS,UAAU,UAAU;GAC7B,QAAQ,UAAU,SAAS;GAC3B,KAAK,UAAU,MAAM;GACrB,MAAM,UAAU,OAAO;GACvB,SAAS,UAAU,UAAU;GAC7B,OAAO,UAAU,QAAQ;GACzB,MAAM,UAAU,OAAO;GACvB,KAAK,UAAU,MAAM;GACrB,OAAO,UAAU,QAAQ;GAC1B;EACD,OAAO,aAAa,QAAQ;EAC7B;;;;ACvQH,SAAS,aAAa,OAAyB;AAC7C,QACG,iBAAiB,gBAAgB,MAAM,SAAS,gBAChD,iBAAiB,SAAS,MAAM,SAAS;;;;;;;;AAU9C,SAAgB,2BACd,QACQ;CACR,MAAM,SAAS,aAAa;EAC1B,MAAM,OAAO,SAAS;AACpB,OAAI,KAAK,WAAW,SAAU,QAAO,KAAA;AACrC,UAAQ,MAAM,OAAO,kBAAkB,IAAK,KAAA;;EAE9C,SAAS,OAAO;EAChB,aAAa,OAAO,eAAe;EACnC,OAAO,OAAO;EACd,SAAS;GACP,QAAQ;GACR,GAAG,OAAO;GACX;EACD,cAAc;EACf,CAAC;AAEF,QAAO,aAAa,MAAM,IACxB,6BAA6B;EAC3B,kBAAkB,aAAa;AAC7B,OAAI,SAAS,WAAW,IACtB,QAAO,eAAe;;EAG1B,iCAAiC;EAClC,CAAC,CACH;AAED,QAAO;;AAGT,MAAM,oBAAyC,IAAI,IAAoB;CACrE;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;;AAGF,SAAgB,iBAAiB,OAAyC;AACxE,QAAO,OAAO,UAAU,YAAY,kBAAkB,IAAI,MAAM;;;;;;AAOlE,SAAgB,eAAe,OAAuC;AACpE,KAAI,EAAE,iBAAiB,UAAW,QAAO;CACzC,MAAM,OAAO,MAAM,MAAM;AACzB,QAAO,iBAAiB,KAAK,GAAG,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACySzC,MAAa,eACX,YAOA,QAAQ,OAAO,KAKb;CACA,GAAG;CACH,eAAe;CACf,UAAU,CAAC;EAAE,QAAQ;EAAS,MAAM;EAAQ,CAAC;CAC7C,KAAK;CACL,GAAG;CACH,SAAS;EACP,gBAAgB;EAChB,GAAG,QAAQ;EACZ;CACF,CAAC;;;;;;;;;;AAWJ,MAAa,eACX,YAOA,QAAQ,OAAO,IAKb;CACA,eAAe;CACf,UAAU,CAAC;EAAE,QAAQ;EAAU,MAAM;EAAQ,CAAC;CAC9C,KAAK;CACL,GAAG;CACJ,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqMJ,MAAa,eACX,YAOA,QAAQ,OAAO,KAKb;CACA,GAAG;CACH,eAAe;CACf,UAAU,CAAC;EAAE,QAAQ;EAAS,MAAM;EAAQ,CAAC;CAC7C,KAAK;CACL,GAAG;CACH,SAAS;EACP,gBAAgB;EAChB,GAAG,QAAQ;EACZ;CACF,CAAC;;;;;;;;;;;;AC/pBJ,MAAa,iBAAiB;CAM5B,uBAAuB;CAMvB,gBAAgB;CACjB;;;;;;;;;;;;;;;;ACKD,SAAgB,gBAAmB,KAAqC;CACtE,IAAI,WAA8B;AAElC,QAAO;EACL,UAAU;AACR,gBAAa,KAAK,CAAC,cAAc;AAC/B,eAAW;KACX;AACF,UAAO;;EAET,WAAW;AACT,UAAO,aAAa;;EAEvB;;AA0BH,SAAgB,uBAAuB,OAAgC;CACrE,MAAM,OAAO,eAAe,MAAM;AAClC,KACE,SAAS,6BACT,SAAS,eACT,SAAS,eAET,QAAO;AAET,KAAI,KAAM,QAAO;AACjB,KAAI,WAAW,MAAM,CACnB,QAAO,MAAM,WAAW,KAAK,MAAM,UAAU,OAAO,MAAM,WAAW,MACjE,gBACA;AAEN,QAAO;;;;;;;;;;;;;;;;AC3DT,MAAM,uBACJ;AACF,MAAM,oBACJ;;;;;;AAsFF,SAAgB,cACd,QACA,UACA,cACA,QACwB;AACxB,QAAO,YAAY;EACjB;EACA,MAAM;GACJ,YAAY;GACZ,WAAW;GACX,eAAe;GACf,oBAAoB;GACpB,sBAAsB;GACtB,KAAK,OAAO;GACZ,GAAI,OAAO,QAAQ,EAAE,OAAO,OAAO,MAAM,KAAK,IAAI,EAAE,GAAG,EAAE;GAC1D;EACD,aAAa;EACd,CAAC;;AAYJ,SAAgB,wBAAwB,OAAiC;AACvE,KAAI,uBAAuB,MAAM,KAAK,cAAe,QAAO;CAC5D,MAAM,OAAO,eAAe,MAAM;AAClC,KAAI,SAAS,qBAAqB,SAAS,gBAAiB,QAAO;AACnE,QAAO;;;;;ACxIT,SAAgB,eACd,MACA,WACA,YAAY,OACE;CACd,MAAM,QAAQ,KAAK,KAAK,OAAO;AAC/B,QAAO;EACL,SAAS,KAAK;EACd,WAAW,KAAK,OAAO;EACvB,OAAO,CAAC,GAAG,KAAK,MAAM;EACtB;EACA,eAAe,UAAU;EACzB,WAAW,KAAK;EAChB,UAAU,KAAK;EACf,aAAa,CAAC,GAAG,KAAK,IAAI;EAC1B;EACA;EACD;;;;ACqFH,IAAa,eAAb,cAAkC,MAAM;CACtC;CAEA,YAAY,MAAwB,SAAkB;AACpD,QAAM,WAAW,KAAK;AACtB,OAAK,OAAO;AACZ,OAAK,OAAO;;;;;;;;;;;;;;;;;;;;;;;;ACjEhB,eAAsB,oBACpB,QACA,cACA,QACyB;CACzB,MAAM,MAAM,OAAO,OAAO,KAAK;CAC/B,MAAM,SAAS,2BAA2B;EACxC,SAAS,OAAO;EAChB,aAAa;EACb,OAAO,OAAO;EACf,CAAC;CAEF,IAAI;CACJ,IAAI;AACJ,KAAI;EACF,MAAM,QAAQ,MAAM,cAClB,QACA,OAAO,UACP,cACA,OACD;AACD,gBAAc,MAAM;AACpB,cAAY,KAAK,GAAG,MAAM,aAAa;UAChC,OAAO;EACd,MAAM,UAAU,wBAAwB,MAAM;AAC9C,MAAI,YAAY,cACd,OAAM,IAAI,aAAa,0BAA0B;AAEnD,MAAI,YAAY,WACd,OAAM,IAAI,aACR,mBACA,6DACD;AAEH,QAAM,IAAI,aACR,iBACA,6CACD;;AAGH,KAAI;EACF,MAAM,OAAO,MAAM,YAAY;GAAE;GAAQ,MAAM;GAAa,CAAC;AAC7D,SAAO;GACL;GACA;GACA,OAAO,eAAe,MAAM,WAAW,KAAK;GAC7C;UACM,OAAO;AACd,MAAI,uBAAuB,MAAM,KAAK,cACpC,OAAM,IAAI,aAAa,0BAA0B;AAEnD,QAAM,IAAI,aACR,iBACA,wDACD;;;;;AClEL,SAAS,UAAU,OAA2B;CAC5C,IAAI,SAAS;AACb,MAAK,MAAM,QAAQ,MAAO,WAAU,OAAO,aAAa,KAAK;AAC7D,QAAO,KAAK,OAAO,CAChB,QAAQ,OAAO,IAAI,CACnB,QAAQ,OAAO,IAAI,CACnB,QAAQ,OAAO,GAAG;;AAYvB,SAAS,aAAa,YAAoB,YAAgC;CACxE,MAAM,QAAQ,IAAI,WAAW,WAAW;AACxC,YAAW,gBAAgB,MAAM;AACjC,QAAO,UAAU,MAAM;;;AAIzB,SAAgB,mBAAmB,aAAyB,QAAgB;AAC1E,QAAO,aAAa,IAAI,WAAW;;;AAIrC,SAAgB,YAAY,aAAyB,QAAgB;AACnE,QAAO,aAAa,IAAI,WAAW;;;AAIrC,eAAsB,oBACpB,UACA,aAAyB,QACR;CACjB,MAAM,SAAS,MAAM,WAAW,OAAO,OACrC,WACA,IAAI,aAAa,CAAC,OAAO,SAAS,CACnC;AACD,QAAO,UAAU,IAAI,WAAW,OAAO,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACwG1C,MAAM,mBAAmB;AAEzB,MAAM,qBAAqB;;AAG3B,SAAS,QACP,GACA,GACS;AACT,KAAI,MAAM,QAAQ,MAAM,KAAA,KAAa,MAAM,QAAQ,MAAM,KAAA,EACvD,SAAQ,KAAK,WAAW,KAAK;AAE/B,QAAO,EAAE,aAAa,KAAK,EAAE,aAAa;;AAG5C,SAAS,aAAa,UAAe,aAA2B;AAC9D,QACE,SAAS,aAAa,YAAY,YAClC,SAAS,SAAS,YAAY,QAC9B,SAAS,aAAa,YAAY;;AAItC,SAAgB,oBACd,QACe;CACf,MAAM,MAAM,OAAO,OAAO,KAAK;CAC/B,MAAM,aAAa,OAAO,cAAc,kBAAkB,OAAO;CAIjE,IAAI,eAAiC,QAAQ,SAAS;CACtD,SAAS,OAAU,WAAyC;EAC1D,MAAM,OAAO,aAAa,KAAK,WAAW,UAAU;AACpD,iBAAe,KAAK,YAAY,KAAA,EAAU;AAC1C,SAAO;;CAET,MAAM,QAAQ;EACZ,WAAW,aAAa,OAAO,QAAQ,IAAI,WAAW,CAAC;EACvD,MAAM,UAAkB,aAAa,OAAO,QAAQ,IAAI,YAAY,MAAM,CAAC;EAC3E,cAAc,aAAa,OAAO,QAAQ,OAAO,WAAW,CAAC;EAE7D,WAAW,UACT,OAAO,YAAY;AACjB,OAAK,MAAM,OAAO,QAAQ,IAAI,WAAW,KAAM,MAC7C,OAAM,OAAO,QAAQ,OAAO,WAAW;IAEzC;EACL;CACD,MAAM,QAAQ,eAAe;CAG7B,IAAI,OAAyB;CAG7B,IAAI,QAA0B;CAE9B,IAAI,WAA0B;CAE9B,IAAI,SAAwB;CAC5B,IAAI,UAAgC;CACpC,IAAI,WAAkC;EACpC,QAAQ;EACR,OAAO;EACP,OAAO;EACR;CACD,MAAM,4BAAY,IAAI,KAAgD;CAGtE,IAAI,aAAa;CAKjB,IAAI,kBAAkB;CAEtB,MAAM,SAA+B,2BAA2B;EAC9D,SAAS,OAAO;EAGhB,aAAa;EACb,OAAO,OAAO;EACd,sBAAsB,MAAM;EAC7B,CAAC;CAEF,SAAS,OAAO,OAA6C;AAC3D,aAAW;GAAE,GAAG;GAAU,GAAG;GAAO;AACpC,OAAK,MAAM,YAAY,UAAW,UAAS,SAAS;;;CAItD,SAAS,UAA4B;AACnC,SAAO,QACL,QAAQ,KAAK,QAAQ,OAAO,IAC5B,KAAK,GAAG,KAAK,YAAY,QACvB,OACA;;;;;;;;;;CAWN,eAAe,MACb,OACA,SACuB;AACvB,MAAI,YAAY,YAAY;AAG1B,OAAI,MAAM,cAAe,OAAM,cAAc,MAAM,cAAc;AACjE,SAAM,IAAI,aAAa,gBAAgB;;AAEzC,MAAI,MAAM,cACR,OAAM,MAAM,IAAI,MAAM,cAAc;AAEtC,MAAI,YAAY,YAAY;AAI1B,OAAI,MAAM,eAAe;AACvB,UAAM,MAAM,SAAS,MAAM,cAAc;AACzC,UAAM,cAAc,MAAM,cAAc;;AAE1C,SAAM,IAAI,aAAa,gBAAgB;;EAEzC,MAAM,SAAoB;GACxB,aAAa,MAAM;GACnB,WAAW,KAAK,GAAG,MAAM,aAAa;GACtC,QAAQ;GACT;AACD,UAAQ;AACR,MAAI,WAAW,KAAM,QAAO;EAC5B,MAAM,OAAO,MAAM,YAAY;GAAE;GAAQ,MAAM,OAAO;GAAa,CAAC;AACpE,MAAI,YAAY,YAAY;AAC1B,UAAO;AACP,WAAQ;AACR,SAAM,IAAI,aAAa,gBAAgB;;AAEzC,aAAW,KAAK,OAAO;AACvB,MAAI,WAAW,QAAQ,CAAC,QAAQ,QAAQ,SAAS,CAC/C,QAAO,MAAM,QAAQ,QAAQ;AAG/B,SAAO;GAAE,GAAG;GAAQ;GAAQ;EAC5B,MAAM,QAAQ,eAAe,MAAM,OAAO,UAAU;AACpD,SAAO;GAAE,QAAQ;GAAiB;GAAO,OAAO;GAAM,CAAC;AACvD,SAAO;;;;;;;CAQT,eAAe,MAAM,KAAa,SAAwC;EACxE,MAAM,OAAO;AACb,MAAI,CAAC,KAAM,OAAM,IAAI,aAAa,gBAAgB;EAClD,IAAI;AACJ,MAAI;AACF,eAAY,MAAM,oBAChB;IACE,aAAa,OAAO;IACpB,UAAU,OAAO;IACjB,OAAO,OAAO;IACd;IACD,EACD,KAAK,aACL;IAAE;IAAK,OAAO;IAAM,CACrB;WACM,OAAO;AACd,OAAI,YAAY,WAAY,OAAM,IAAI,aAAa,gBAAgB;GAInE,MAAM,eACJ,iBAAiB,gBAAgB,MAAM,SAAS,kBAC5C,IAAI,aACF,iBACA,8DAA8D,IAAI,GACnE,GACD,IAAI,aACF,2BACA,iBAAiB,gBAAgB,MAAM,SAAS,oBAC5C,4FACA,4EACL;AACP,UAAO,EAAE,OAAO,cAAc,CAAC;AAC/B,SAAM;;AAIR,MAAI,YAAY,WAAY,OAAM,IAAI,aAAa,gBAAgB;EAEnE,MAAM,YAAY,KAAK,IAAI,KAAK,WAAW,UAAU,UAAU;AAC/D,SAAO;GAAE,aAAa,UAAU;GAAa;GAAW,QAAQ;GAAK;EACrE,MAAM,QAAsB;GAAE,GAAG,UAAU;GAAO;GAAW;AAC7D,SAAO;GAAE,QAAQ;GAAiB;GAAO,OAAO;GAAM,CAAC;AACvD,SAAO;;CAGT,SAAS,OAAO,cAAwC;AACtD,SAAO,YAAY;GACjB;GACA,MAAM;IACJ,OAAO;IACP,iBAAiB;IACjB,WAAW,OAAO;IACnB;GACF,CAAC;;CAGJ,eAAe,cAAc,cAAqC;AAChE,MAAI;AACF,SAAM,OAAO,aAAa;UACpB;;;;;;;;;;;;;CAgBV,eAAe,uBACb,cACA,OACuB;AAEvB,SAAO;EACP,IAAI,OAAO;AACX,MAAI,uBAAuB,MAAM,KAAK,cACpC,KAAI;AACF,UAAQ,MAAM,MAAM,KAAK,KAAM;UACzB;AACN,UAAO;;AAGX,MAAI,MAAM;GACR,MAAM,cAAc,IAAI,aACtB,2BACA,gGACD;AACD,UAAO,EAAE,OAAO,aAAa,CAAC;AAC9B,UAAO;;AAET,MAAI;AACF,SAAM,MAAM,SAAS,aAAa;UAC5B;AAGR,QAAM,cAAc,aAAa;AACjC,SAAO;GAAE,QAAQ;GAAmB,OAAO;GAAM,OAAO;GAAM,CAAC;AAC/D,SAAO,IAAI,aACT,qBACA,0EACD;;;CAIH,MAAM,uBAAO,IAAI,KAAuB;CAExC,SAAS,QAAW,SAAiC;AACnD,OAAK,IAAI,QAAQ;EACjB,MAAM,mBAAmB;AACvB,QAAK,OAAO,QAAQ;;AAEtB,UAAQ,KAAK,YAAY,WAAW;AACpC,SAAO;;;CAIT,eAAe,aAA4B;AACzC,MAAI,KAAK,SAAS,EAAG;EACrB,IAAI;EACJ,MAAM,QAAQ,IAAI,SAAe,YAAY;AAC3C,WAAQ,WAAW,SAAS,OAAO,iBAAiB,iBAAiB;IACrE;AACF,QAAM,QAAQ,KAAK,CAAC,QAAQ,WAAW,CAAC,GAAG,KAAK,CAAC,EAAE,MAAM,CAAC;AAC1D,eAAa,MAAM;;CAGrB,eAAe,OAAO,OAA2C;AAC/D,SAAO;AACP,UAAQ;AACR,aAAW;AACX,QAAM,MAAM,QAAQ;AACpB,SAAO;GAAE,QAAQ;GAAmB,OAAO;GAAM;GAAO,CAAC;;;;;;;CAQ3D,MAAM,YAAY,sBAAiC,QAAQ,YAAY,CAAC,CAAC;CAEzE,eAAe,aAAiC;AAC9C,MAAI,kBAAkB,EAAG,OAAM,IAAI,aAAa,gBAAgB;EAChE,MAAM,UAAU;EAChB,MAAM,eAAe,MAAM,MAAM,KAAK;AACtC,MAAI,CAAC,cAAc;AACjB,SAAM,OAAO,KAAK;AAClB,SAAM,IAAI,aAAa,gBAAgB;;EAEzC,IAAI;AACJ,MAAI;AACF,WAAQ,MAAM,YAAY;IACxB;IACA,MAAM;KACJ,YAAY;KACZ,WAAW,OAAO;KAClB,eAAe;KAChB;IACF,CAAC;WACK,OAAO;AACd,OAAI,YAAY,WAAY,OAAM,IAAI,aAAa,gBAAgB;AACnE,OAAI,uBAAuB,MAAM,KAAK,eAAe;IACnD,MAAM,cAAc,IAAI,aAAa,0BAA0B;AAC/D,WAAO,EAAE,OAAO,aAAa,CAAC;AAC9B,UAAM;;GAER,MAAM,UAAU,IAAI,aAClB,mBACA,0DAA0D,eAAe,MAAM,IAAI,WAAW,IAC/F;AACD,SAAM,OAAO,QAAQ;AACrB,SAAM;;AAER,MAAI;AACF,SAAM,MAAM,OAAO,QAAQ;WACpB,OAAO;AACd,OAAI,iBAAiB,aAAc,OAAM;AACzC,OAAI,uBAAuB,MAAM,KAAK,eAAe;AAGnD,WAAO;AACP,UAAM,IAAI,aAAa,0BAA0B;;GAEnD,MAAM,UAAU,IAAI,aAClB,mBACA,qDACD;AACD,SAAM,OAAO,QAAQ;AACrB,SAAM;;AAER,MAAI,CAAC,KAAM,OAAM,IAAI,aAAa,gBAAgB;AAClD,SAAO;;;;;;;CAQT,eAAe,aAAiC;AAC9C,OAAK,IAAI,UAAU,GAAG,UAAU,GAAG,WAAW,GAAG;GAC/C,MAAM,YAAY,MAAM,UAAU,SAAS;AAC3C,OAAI,QAAQ,UAAU,QAAQ,OAAO,CAAE,QAAO;;AAEhD,QAAM,IAAI,aACR,2BACA,mEACD;;CAGH,MAAM,UAAyB;EAC7B,MAAM,QAAQ,SAAS;AACrB,OAAI,WAAW,SAAS,SAAS;IAC/B,MAAM,MAAM,QAAQ,OAAO;AAC3B,QAAI,CAAC,QAAQ,KAAK,OAAO,EAAE;AACzB,cAAS;AACT,YAAO;;;AAGX,OAAI,SAAS,CAAE,QAAO,SAAS;AAC/B,OAAI;AACF,UAAM,YAAY;AAClB,WAAO,SAAS;YACT,OAAO;AACd,QACE,iBAAiB,iBAChB,MAAM,SAAS,mBAAmB,MAAM,SAAS,mBAElD,QAAO;AAET,UAAM;;;EAIV,MAAM,YAAY,SAAS;GACzB,MAAM,cAAc,SAAS,eAAe,OAAO;AACnD,OAAI,CAAC,YACH,OAAM,IAAI,aACR,mBACA,4DACD;GAEH,MAAM,aAAa,OAAO;GAC1B,MAAM,WAAW,mBAAmB,WAAW;GAC/C,MAAM,QAAQ,YAAY,WAAW;GACrC,MAAM,YAAY,MAAM,oBAAoB,UAAU,WAAW;AACjE,aAAU;IAAE;IAAO;IAAU;IAAa;GAE1C,MAAM,MAAM,IAAI,IAAI,cAAc,OAAO,YAAY;AACrD,OAAI,aAAa,IAAI,iBAAiB,OAAO;AAC7C,OAAI,aAAa,IAAI,aAAa,OAAO,SAAS;AAClD,OAAI,aAAa,IAAI,gBAAgB,YAAY;AACjD,OAAI,aAAa,IAAI,SAAS,MAAM;AACpC,OAAI,aAAa,IAAI,kBAAkB,UAAU;AACjD,OAAI,aAAa,IAAI,yBAAyB,OAAO;AACrD,OAAI,SAAS,OAAQ,KAAI,aAAa,IAAI,UAAU,QAAQ,OAAO;AACnE,OAAI,SAAS,IAAK,KAAI,aAAa,IAAI,OAAO,QAAQ,IAAI;GAC1D,MAAM,YAAY,SAAS,WAAW,MAAM;AAC5C,OAAI,aAAa,mBAAmB,KAAK,UAAU,CACjD,KAAI,aAAa,IAAI,cAAc,UAAU;AAE/C,UAAO,IAAI,UAAU;;EAGvB,MAAM,eAAe,aAAa;GAEhC,MAAM,UAAU;AAChB,aAAU;AACV,OAAI,kBAAkB,EAAG,OAAM,IAAI,aAAa,gBAAgB;GAChE,MAAM,UAAU;GAEhB,IAAI;AACJ,OAAI;AACF,eAAW,IAAI,IAAI,YAAY;WACzB;AACN,UAAM,IAAI,aAAa,kBAAkB,8BAA8B;;GAEzE,MAAM,QAAQ,SAAS,aAAa,IAAI,QAAQ;AAChD,OACE,CAAC,WACD,CAAC,aAAa,UAAU,IAAI,IAAI,QAAQ,YAAY,CAAC,IACrD,UAAU,QACV,UAAU,QAAQ,MAElB,OAAM,IAAI,aACR,kBACA,4DACD;GAEH,MAAM,QAAQ,SAAS,aAAa,IAAI,QAAQ;AAChD,OAAI,MACF,OAAM,IAAI,aACR,UAAU,mBACR,UAAU,oBACV,UAAU,gCACV,UAAU,4BACR,QACA,qBACJ,iCAAiC,MAAM,GACxC;GAEH,MAAM,OAAO,SAAS,aAAa,IAAI,OAAO;AAC9C,OAAI,CAAC,KACH,OAAM,IAAI,aACR,qBACA,4BACD;GAGH,IAAI;AACJ,OAAI;AACF,YAAQ,MAAM,YAAY;KACxB;KACA,MAAM;MACJ,YAAY;MACZ;MACA,cAAc,QAAQ;MACtB,WAAW,OAAO;MAClB,eAAe,QAAQ;MACxB;KACF,CAAC;YACK,aAAa;AACpB,UAAM,uBAAuB,YAAY,KAAK,gBAC1C,IAAI,aAAa,0BAA0B,GAC3C,IAAI,aAAa,qBAAqB,6BAA6B;;AAEzE,OAAI,CAAC,MAAM,cACT,OAAM,IAAI,aACR,qBACA,mEACD;AAIH,YAAS;AACT,UAAO;AACP,OAAI;AACF,WAAO,MAAM,MAAM,OAAO,QAAQ;YAC3B,YAAY;AACnB,QAAI,sBAAsB,aAAc,OAAM;AAC9C,UAAM,MAAM,uBAAuB,MAAM,eAAe,WAAW;;;EAIvE,MAAM,eAAe,SAAS;GAC5B,MAAM,QAAQ,SAAS;AACvB,OAAI,SAAS,MAAM,gBAAgB,SAAS,UAC1C,QAAO,MAAM;AAEf,WAAQ,MAAM,YAAY,EAAE;;EAG9B,MAAM,cAAc,KAAK;AACvB,OAAI,kBAAkB,EAAG,OAAM,IAAI,aAAa,gBAAgB;AAGhE,UAAO,UAAU,UAAU,CACzB,OAAM,UAAU,SAAS,CAAC,YAAY,KAAA,EAAU;GAElD,MAAM,UAAU;GAChB,MAAM,WAAW;GACjB,MAAM,eAAe;AACrB,YAAS;AACT,OAAI,CAAC,QAAQ,UAAU,IAAI,CAAE,QAAO;AACpC,OAAI;IACF,MAAM,OAAO,SAAS,KAAK,GAAG,MAAM,YAAY,QAAQ,QAAQ;AAChE,QAAI,CAAC,MAAM;AACT,WAAM,YAAY;AAClB,SAAI,CAAC,SAAS,MAAO,OAAM,IAAI,aAAa,gBAAgB;AAC5D,YAAO,SAAS;;AAElB,QAAI,QAAQ,QAAQ,QAAQ,KAAK,SAAS,EAAE;KAC1C,MAAM,OAAO,MAAM,YAAY;MAAE;MAAQ,MAAM,KAAK;MAAa,CAAC;AAClE,SAAI,YAAY,WAAY,OAAM,IAAI,aAAa,gBAAgB;AACnE,YAAO;MAAE,GAAG;MAAM,QAAQ;MAAK;KAC/B,MAAM,QAAQ,eAAe,MAAM,KAAK,UAAU;AAClD,YAAO;MAAE,QAAQ;MAAiB;MAAO,OAAO;MAAM,CAAC;AACvD,YAAO;;AAET,WAAO,MAAM,QAAQ,MAAM,KAAK,QAAQ,CAAC;YAClC,OAAO;AAId,QAAI,QAAQ,QAAQ,IAAI,EAAE;AACxB,cAAS;AACT,YAAO,YAAY,aAAa,eAAe;;AAEjD,UAAM;;;EAIV,QAAQ;AACN,UAAO,SAAS;;EAGlB,WAAW;AACT,UAAO;;EAGT,MAAM,UAAU;AACd,sBAAmB;AACnB,aAAU;AACV,UAAO;AACP,WAAQ;AACR,cAAW;AACX,YAAS;GACT,IAAI;AACJ,OAAI;AAKF,UAAM,YAAY;AAClB,kBAAc;AACd,mBAAe,MAAM,MAAM,KAAK;AAGhC,UAAM,MAAM,QAAQ;aACZ;AACR,uBAAmB;;AAErB,UAAO;IAAE,QAAQ;IAAmB,OAAO;IAAM,OAAO;IAAM,CAAC;AAC/D,OAAI,CAAC,aAAc;AACnB,OAAI;AACF,UAAM,OAAO,aAAa;WACpB;IACN,MAAM,QAAQ,IAAI,aAChB,mBACA,4FACD;AACD,WAAO,EAAE,OAAO,CAAC;AACjB,UAAM;;;EAIV,UAAU,UAAU;AAClB,aAAU,IAAI,SAAS;AACvB,gBAAa;AACX,cAAU,OAAO,SAAS;;;EAG/B;CAID,MAAM,iBAAiB,QAAQ;AAC/B,SAAQ,kBAAkB,gBACxB,QAAQ,eAAe,YAAY,CAAC;AACtC,QAAO;;;;;ACxxBT,IAAa,gCAAb,cAAmD,MAAM;CACvD,YAAY,SAAiB;AAC3B,QAAM,QAAQ;AACd,OAAK,OAAO;;;AAoLhB,SAAS,eAAe,OAAyB;AAC/C,QACE,iBAAiB,SACjB,kCAAkC,KAAK,MAAM,QAAQ;;AAIzD,SAAgB,2BACd,SACe;AACf,QAAO;EACL,MAAM,IAAI,KAAK;AACb,OAAI;AACF,WAAO,QAAQ,MAAM,QAAQ,SAAS,IAAI,CAAC,aAAa,IAAI;YACrD,OAAO;AACd,QAAI,eAAe,MAAM,CAAE,QAAO;AAClC,UAAM;;;EAGV,MAAM,IAAI,KAAK,OAAO;AACpB,WAAQ,MAAM,QAAQ,SAAS,IAAI,CAAC,YAAY,MAAM;;EAExD,MAAM,OAAO,KAAK;AAChB,OAAI;AACF,YAAQ,MAAM,QAAQ,SAAS,IAAI,CAAC,gBAAgB;YAC7C,OAAO;AACd,QAAI,CAAC,eAAe,MAAM,CAAE,OAAM;;;EAGvC;;;;;;;;ACzNH,SAAS,OAAO,KAAkD;AAChE,SAAQ,QAAQ,UAAhB;EACE,KAAK,SACH,QAAO;GAAE,SAAS;GAAQ,MAAM,CAAC,IAAI;GAAE;EACzC,KAAK,QAGH,QAAO;GACL,SAAS;GACT,MAAM,CAAC,+BAA+B,IAAI;GAC3C;EACH,QACE,QAAO;GAAE,SAAS;GAAY,MAAM,CAAC,IAAI;GAAE;;;AAIjD,SAAgB,YAAY,KAAmB;AAC7C,KAAI,QAAQ,IAAI,oBAAqB;CACrC,MAAM,SAAS,OAAO,IAAI;AAC1B,KAAI;AAGF,WAAS,OAAO,SAAS,OAAO,YAAY,KAAA,EAAU;SAChD;;;;;;;;;ACxBV,MAAa,qBAAqB;;AAGlC,MAAa,kBAAkB;AAE/B,MAAa,0BAA0B;AACvC,MAAa,uBAAuB;AAEpC,MAAM,oBAAoB,CAAC,uBAAuB;AAElD,SAAS,eAAe,UAA2B;AACjD,QACE,aAAa,eACb,aAAa,eACb,aAAa,WACb,SAAS,SAAS,aAAa;;;;;;;;;;AAYnC,SAAgB,mBACd,SACA,MAAyB,QAAQ,KAClB;CACf,MAAM,WAAW,IAAI;AACrB,KAAI,UAAU;EACZ,IAAI;AACJ,MAAI;AACF,SAAM,IAAI,IAAI,SAAS;UACjB;AACN,SAAM,IAAI,MAAM,oCAAoC,WAAW;;AAKjE,MAAI,EAFF,IAAI,aAAa,YAChB,IAAI,aAAa,WAAW,eAAe,IAAI,SAAS,EAEzD,OAAM,IAAI,MACR,oEACD;AAEH,SAAO,IAAI;;CAGb,IAAI;AACJ,KAAI;AACF,aAAW,IAAI,IAAI,QAAQ,CAAC;SACtB;AACN,SAAO;;AAET,KAAI,aAAa,gBAAiB,QAAO;AACzC,KAAI,kBAAkB,MAAM,YAAY,QAAQ,KAAK,SAAS,CAAC,CAC7D,QAAO;AAET,QAAO;;;;;;;;;;;;;;AC/CT,IAAI,SAAwC;AAE5C,SAAgB,qBAA6C;AAC3D,YAAW,OAAO,oBAAoB,MACnC,EAAE,YACD,2BAA2B;EACzB,QAAQ,SAAS,YAAY,IAAI,MAAM,SAAS,QAAQ;EACxD,SAAS;EACV,CAAC,GACH,UAAmB;AAClB,WAAS;AAET,QAAM,IAAI,8BACR,8DAFa,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,CAEE,IACtE;GAEJ;AACD,QAAO;;;;;;;;;;;;;;ACjBT,MAAa,gBAAgB;AAC7B,MAAa,8BAA8B,MAAS;AAEpD,IAAa,uBAAb,cAA0C,MAAM;CAC9C,cAAc;AACZ,QAAM,0DAA0D;AAChE,OAAK,OAAO;;;AAsBhB,SAAS,UAAU,UAAkB,UAA2B;CAC9D,MAAM,IAAI,OAAO,KAAK,SAAS;CAC/B,MAAM,IAAI,OAAO,KAAK,SAAS;AAC/B,QAAO,EAAE,WAAW,EAAE,UAAU,gBAAgB,GAAG,EAAE;;AAGvD,SAAS,WAAW,MAAsB;AACxC,QAAO,KACJ,QAAQ,MAAM,QAAQ,CACtB,QAAQ,MAAM,OAAO,CACrB,QAAQ,MAAM,OAAO,CACrB,QAAQ,MAAM,SAAS;;AAG5B,SAAS,KACP,UACA,QACA,OACA,SACM;CACN,MAAM,OAAO,2DAA2D,WAAW,MAAM,CAAC,8IAA8I,WAAW,MAAM,CAAC,UAAU,WAAW,QAAQ,CAAC;AACxR,UAAS,UAAU,QAAQ;EACzB,gBAAgB;EAChB,iBAAiB;EAEjB,mBAAmB;EACnB,YAAY;EACb,CAAC;AACF,UAAS,IAAI,KAAK;;;AAIpB,eAAsB,oBACpB,UAAiC,EAAE,EACV;CACzB,MAAM,YAAY,QAAQ,aAAA;CAC1B,IAAI,gBAA+B;CACnC,IAAI,SAGO;CACX,IAAI,OAAO;CACX,IAAI;CAEJ,MAAM,SAAS,cACZ,SAA0B,aAA6B;EACtD,MAAM,MAAM,IAAI,IAAI,QAAQ,OAAO,KAAK,mBAAmB;AAC3D,MAAI,MAAM;AACR,QAAK,UAAU,KAAK,oBAAoB,0BAA0B;AAClE;;AAEF,MAAI,QAAQ,WAAW,SAAS,IAAI,aAAA,aAA4B;AAC9D,QAAK,UAAU,KAAK,aAAa,mCAAmC;AACpE;;EAEF,MAAM,QAAQ,IAAI,aAAa,IAAI,QAAQ;AAC3C,MACE,kBAAkB,QAClB,UAAU,QACV,CAAC,UAAU,OAAO,cAAc,EAChC;AACA,QACE,UACA,KACA,0BACA,6EACD;AACD;;AAGF,SAAO;EACP,MAAM,SAAS,IAAI,aAAa,IAAI,QAAQ;AAC5C,OACE,UACA,SAAS,MAAM,KACf,SAAS,6BAA6B,8BACtC,SACI,yCACA,sDACL;EACD,MAAM,cAAc,IAAI,IAAI,QAAQ,OAAO,KAAK,YAAY,CAAC,UAAU;EACvE,MAAM,UAAU;AAChB,WAAS;AACT,WAAS,QAAQ,YAAY;AACxB,SAAO;GAEf;AAED,OAAM,IAAI,SAAe,SAAS,WAAW;AAC3C,SAAO,KAAK,SAAS,OAAO;AAC5B,SAAO,OAAO,GAAG,mBAAmB;AAClC,UAAO,IAAI,SAAS,OAAO;AAC3B,YAAS;IACT;GACF;CACF,MAAM,EAAE,SAAS,OAAO,SAAS;CACjC,MAAM,cAAc,oBAAoB,OAAO;CAE/C,IAAI,UAAgC;CACpC,SAAS,QAAuB;AAC9B,SAAO;AACP,eAAa,MAAM;EACnB,MAAM,UAAU;AAChB,WAAS;AACT,WAAS,uBAAO,IAAI,MAAM,6BAA6B,CAAC;AACxD,cAAY,IAAI,SAAe,YAAY;AACzC,UAAO,YAAY,SAAS,CAAC;AAC7B,UAAO,qBAAqB;IAC5B;AACF,SAAO;;AAGT,QAAO;EACL;EACA,gBAAgB,OAAO;AACrB,OAAI,KACF,QAAO,QAAQ,uBAAO,IAAI,MAAM,iCAAiC,CAAC;AAEpE,OAAI,UAAU,kBAAkB,KAC9B,QAAO,QAAQ,uBAAO,IAAI,MAAM,kCAAkC,CAAC;AAErE,mBAAgB;AAChB,UAAO,IAAI,SAAiB,SAAS,WAAW;AAC9C,aAAS;KAAE;KAAS;KAAQ;AAC5B,YAAQ,iBAAiB;AACvB,cAAS;AACT,YAAO,IAAI,sBAAsB,CAAC;AAC7B,YAAO;OACX,UAAU;KACb;;EAEJ;EACD;;ACvHH,IAAI,OAR8B;CAChC,SAAS;CACT;CACA,KAAK,KAAK;CACV,mBAAmB;CACnB,qBAAqB;EAAC;EAAO;EAAO;EAAM;CAC3C;AAcD,MAAM,uBAAuB;;AAG7B,MAAM,mBAAmB;AAWzB,MAAM,2BAAW,IAAI,KAA0B;;AAG/C,MAAM,yBAAS,IAAI,KAAmD;AACtE,MAAM,gCAAgB,IAAI,KAA4C;AAUtE,SAAS,WAAW,MAAmC;AACrD,QAAO,GAAG,KAAK,YAAY,GAAG,KAAK;;AAGrC,eAAe,YAAY,MAAiD;CAC1E,MAAM,MAAM,WAAW,KAAK;CAC5B,MAAM,WAAW,SAAS,IAAI,IAAI;AAClC,KAAI,SAAU,QAAO;CACrB,MAAM,UAAU,MAAM,KAAK,SAAS;CACpC,MAAM,QAAQ,SAAS,IAAI,IAAI;AAC/B,KAAI,MAAO,QAAO;CAClB,MAAM,OAAoB;EACxB,SAAS,oBAAoB;GAC3B,aAAa,KAAK;GAClB,UAAU;GACV,aAAa;GACb;GACA,YAAY,KAAK;GACjB,OAAO,KAAK;GACZ,KAAK,KAAK;GACX,CAAC;EACF,2BAAW,IAAI,KAAK;EACrB;AACD,UAAS,IAAI,KAAK,KAAK;AACvB,QAAO;;;AAWT,SAAS,WAAW,OAA0D;AAC5E,QAAO,CAAC,GAAG,IAAI,IAAI,SAAS,EAAE,CAAC,CAAC,CAAC,MAAM;;;;;;AAOzC,SAAS,YAAY,KAAa,OAAuC;AACvE,QAAO,MAAM,WAAW,IAAI,MAAM,GAAG,IAAI,GAAG,MAAM,KAAK,IAAI;;AAG7D,eAAe,aACb,MACA,MACA,KACA,OACwB;CACxB,MAAM,SAAS;EACb,aAAa,KAAK;EAClB,UAAU;EACV,OAAO,KAAK;EACZ,KAAK,KAAK;EACX;CACD,MAAM,SAAS;EAAE;EAAK,OAAO,MAAM,SAAS,IAAI,QAAQ;EAAM;CAC9D,MAAM,UAAU,MAAM,KAAK,QAAQ,gBAAgB;CACnD,IAAI;AACJ,KAAI;AACF,cAAY,MAAM,oBAAoB,QAAQ,SAAS,OAAO;UACvD,OAAO;AACd,MAAI,EAAE,iBAAiB,iBAAiB,MAAM,SAAS,kBACrD,OAAM;AAIR,cAAY,MAAM,oBAAoB,QADtB,MAAM,KAAK,QAAQ,eAAe,EAAE,WAAW,SAAS,CAAC,EAClB,OAAO;;AAEhE,MAAK,UAAU,IAAI,YAAY,KAAK,MAAM,EAAE,UAAU;AACtD,QAAO;;;;;;;;AAST,IAAa,sBAAb,cAAyC,MAAM;CAC7C,OAAgB;CAChB,YACE,OAEA,KAEA,aACA;AACA,QACE,sCAAsC,MAAM,KAAK,QAAQ,CAAC,MAAM,eAAe,IAAI,GACpF;AARQ,OAAA,QAAA;AAEA,OAAA,MAAA;AAEA,OAAA,cAAA;AAKT,OAAK,OAAO;;;;AAKhB,SAAgB,sBACd,OAC8B;AAC9B,QACE,iBAAiB,SAChB,MAA6B,SAAS;;;;;;;;;;;;;;;;AAkB3C,eAAsB,cACpB,MACA,MAAqB,KAAK,OAAO,MACjC,OACwB;CACxB,MAAM,OAAO,MAAM,YAAY,KAAK;CACpC,MAAM,QAAQ,MAAM,KAAK,QAAQ,SAAS;AAC1C,KAAI,CAAC,MACH,OAAM,IAAI,aAAa,iBAAiB,iCAAiC;CAE3E,MAAM,cAAc,MAAM,KAAK,QAAQ,gBAAgB;CACvD,MAAM,aAAa,KAAK,QAAQ,OAAO,IAAI;CAC3C,MAAM,YAAY,WAAW,MAAM;AACnC,KAAI,UAAU,WAAW,GAAG;AAC1B,MAAI,CAAC,OAAO,QAAQ,WAAW,UAC7B,QAAO;GACL;GACA,WAAW,WAAW;GACtB,OAAO;GACR;EAEH,MAAM,SAAS,KAAK,UAAU,IAAI,YAAY,KAAK,UAAU,CAAC;AAC9D,MAAI,UAAU,KAAK,KAAK,GAAG,OAAO,YAAY,iBAC5C,QAAO;AAET,SAAO,aAAa,MAAM,MAAM,KAAK,UAAU;;CAGjD,MAAM,SAAS,OAAO,WAAW;AACjC,KAAI,CAAC,OACH,OAAM,IAAI,aACR,iBACA,oDACD;AAEH,KACE,WAAW,WAAW,aACtB,WAAW,MAAM,SAAS,KAC1B,WAAW,MAAM,OAAO,SAAS,UAAU,SAAS,KAAK,CAAC,CAE1D,QAAO;EAAE;EAAa,WAAW,WAAW;EAAW,OAAO;EAAY;CAE5E,MAAM,SAAS,KAAK,UAAU,IAAI,YAAY,QAAQ,UAAU,CAAC;AACjE,KAAI,UAAU,KAAK,KAAK,GAAG,OAAO,YAAY,iBAC5C,QAAO;AAET,KAAI;AACF,SAAO,MAAM,aAAa,MAAM,MAAM,QAAQ,UAAU;UACjD,OAAO;AACd,MAAI,iBAAiB,gBAAgB,MAAM,SAAS,gBAClD,OAAM,IAAI,oBAAoB,WAAW,OAAO;AAElD,QAAM;;;;AAKV,eAAsB,aACpB,MACuB;AACvB,SAAQ,MAAM,cAAc,MAAM,KAAK,EAAE;;;;;;;;AAS3C,eAAsB,mBACpB,SACA,OACiB;CACjB,IAAI;AACJ,KAAI;AACF,UAAQ,MAAM,cAAc,QAAQ,MAAM,QAAQ,KAAK,OAAO,MAAM,MAAM;UACnE,OAAO;AACd,MAAI,sBAAsB,MAAM,CAC9B,OAAM,IAAI,oBACR,MAAM,OACN,MAAM,KACN,QAAQ,YACT;AAEH,QAAM;;AAIR,KAAI,WAAW,MAAM,CAAC,WAAW,EAC/B,QAAO,IAAI,QAAQ,MAAM;EACvB,OAAO,MAAM;EACb,WAAW,MAAM;EAClB,CAAC;AAEJ,QAAO,MAAM;;;;;;;AAQf,SAAgB,kBAAkB,aAAoC;CACpE,MAAM,OAAO,OAAO,IAAI,YAAY;AACpC,KAAI,CAAC,QAAQ,KAAK,KAAK,IAAI,KAAK,YAAY,iBAAkB,QAAO;AACrE,QAAO,KAAK;;;;;;;;AASd,eAAsB,qBACpB,SACe;AACf,OAAM,mBAAmB,QAAQ;CACjC,MAAM,OAAO,OAAO,IAAI,QAAQ,KAAK;AACrC,KAAI,CAAC,KAAM;AACX,cAAa,cAAc,IAAI,QAAQ,KAAK,CAAC;CAC7C,MAAM,QAAQ,KAAK,IACjB,KAAK,YAAY,KAAK,KAAK,GAAG,IAAI,kBAClC,IACD;CACD,MAAM,QAAQ,iBAAiB;AAC7B,gBAAc,OAAO,QAAQ,KAAK;AAClC,uBAAqB,QAAQ,CAAC,YAAY,GAGxC;IACD,MAAM;AACT,OAAM,OAAO;AACb,eAAc,IAAI,QAAQ,MAAM,MAAM;;;;;;;AAQxC,eAAsB,gBACpB,MACe;CACf,MAAM,OAAO,MAAM,YAAY,KAAK;AACpC,UAAS,OAAO,WAAW,KAAK,CAAC;AACjC,OAAM,KAAK,QAAQ,SAAS;;;;;;;;;;;;AAa9B,eAAe,mBACb,SACA,aACuB;CACvB,IAAI,UAAmB;AACvB,KAAI;AACF,OAAK,MAAM,SAAS,KAAK,qBAAqB;AAC5C,SAAM,IAAI,SAAS,YAAY,WAAW,SAAS,MAAM,CAAC;AAC1D,OAAI;IACF,MAAM,QAAQ,MAAM,QAAQ,SAAS;AACrC,QAAI,MAAO,QAAO;AAElB,cAAU;AACV;YACO,OAAO;AACd,cAAU;AACV,QACE,EAAE,iBAAiB,iBACnB,MAAM,SAAS,0BAEf;;;UAIC,OAAO;AACd,YAAU;;AAEZ,OAAM,QAAQ,SAAS,CAAC,YAAY,KAAA,EAAU;AAC9C,OAAM;;;;;;;;AAuBR,eAAsB,kBACpB,SACwB;CACxB,MAAM,UAAU,MAAM,KAAK,SAAS;CACpC,MAAM,aAAa,YAAY,YAAY;AAG3C,OAAM,QAAQ,IAAI,YAAY,UAAU;AACxC,OAAM,QAAQ,OAAO,WAAW;CAEhC,MAAM,SAAS,MAAM,oBAAoB,EACvC,WAAW,KAAK,mBACjB,CAAC;CACF,MAAM,UAAU,oBAAoB;EAClC,aAAa,QAAQ;EACrB,UAAU;EACV,aAAa,OAAO;EACpB;EACA;EACA,OAAO,KAAK;EACZ,KAAK,KAAK;EACX,CAAC;CAEF,IAAI;AACJ,KAAI;EACF,MAAM,eAAe,MAAM,QAAQ,YACjC,QAAQ,MAAM,EAAE,KAAK,QAAQ,KAAK,GAAG,KAAA,EACtC;EACD,MAAM,QAAQ,IAAI,IAAI,aAAa,CAAC,aAAa,IAAI,QAAQ,IAAI;AACjE,UAAQ,IAAI,+DAA+D;AAC3E,UAAQ,IAAI,aAAa;AACzB,OAAK,YAAY,aAAa;AAC9B,gBAAc,MAAM,OAAO,gBAAgB,MAAM;WACzC;AACR,QAAM,OAAO,OAAO;;CAGtB,IAAI;AACJ,KAAI;AACF,UAAQ,MAAM,QAAQ,eAAe,YAAY;UAC1C,OAAO;AACd,MACE,EAAE,iBAAiB,iBACnB,MAAM,SAAS,0BAEf,OAAM;AAIR,UAAQ,MAAM,mBAAmB,SAAS,MAAM;;CAElD,MAAM,OAA4B;EAChC,MAAM;EACN,aAAa,QAAQ;EACrB;EACD;AACD,UAAS,IAAI,WAAW,KAAK,EAAE;EAAE;EAAS,2BAAW,IAAI,KAAK;EAAE,CAAC;AACjE,QAAO;EAAE;EAAM;EAAO,aAAa,MAAM,QAAQ,gBAAgB;EAAE;;;AAIrE,SAAgB,sBAAsB,OAAwB;AAC5D,KAAI,iBAAiB,aACnB,SAAQ,MAAM,MAAd;EACE,KAAK;EACL,KAAK,kBACH,QAAO;EACT,KAAK,gBACH,QAAO;EACT,KAAK,6BACH,QAAO;EACT,KAAK,iBACH,QAAO;EACT,KAAK,0BACH,QAAO;EACT,KAAK,kBACH,QAAO;EACT,QACE,QAAO,4BAA4B,MAAM,KAAK;;AAGpD,KACE,iBAAiB,SACjB,MAAM,SAAS,gCAEf,QAAO,mHAAmH,MAAM;AAElI,QAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;;;;;;;;;;;;;;ACzf/D,SAAgB,eAAuB;CACrC,MAAM,cAAc,QAAQ,IAAI;AAChC,KAAI,YAAa,QAAO;AAExB,KAAI,QAAQ,aAAa,SACvB,QAAO,KAAK,SAAS,EAAE,SAAS;AAGlC,KAAI,QAAQ,aAAa,QAGvB,QAAO,KADL,QAAQ,IAAI,cAAc,KAAK,SAAS,EAAE,WAAW,UAAU,EAC5C,QAAQ;CAG/B,MAAM,YAAY,QAAQ,IAAI;AAC9B,KAAI,UAAW,QAAO,KAAK,WAAW,QAAQ;AAE9C,QAAO,KAAK,SAAS,EAAE,WAAW,QAAQ;;AAG5C,SAAgB,oBAA4B;AAC1C,QAAO,KAAK,cAAc,EAAE,cAAc;;;;;;;;;;AC7B5C,SAAgB,iBAA0B;AACxC,QAAO,QAAQ,IAAI,mCAAmC;;;;;;;;ACYxD,SAAgB,aAA0B;CACxC,MAAM,SAAS,kBAAkB;AACjC,QAAO,gBAAgB,GACnB;EAAE,GAAG;EAAQ,eAAe;EAAM,UAAU,EAAE;EAAE,GAChD;;AAGN,SAAS,mBAAgC;CACvC,MAAM,aAAa,mBAAmB;AAEtC,KAAI,CAAC,WAAW,WAAW,CACzB,QAAO,qBAAqB;CAG9B,MAAM,MAAM,aAAa,YAAY,OAAO;CAE5C,IAAI;AACJ,KAAI;AACF,WAAS,KAAK,MAAM,IAAI;SAClB;AAEN,SAAO,qBAAqB;;AAI9B,QAAO,4BAA4B;EACjC,GAAG,qBAAqB;EACxB,GAAG;EACJ,CAAC;;AAGJ,SAAgB,YAAY,QAA2B;CACrD,MAAM,aAAa,mBAAmB;CACtC,MAAM,MAAM,QAAQ,WAAW;AAG/B,WAAU,KAAK;EAAE,WAAW;EAAM,MAAM;EAAO,CAAC;CAOhD,MAAM,UAAU,KAAK,KAAK,iBADX,YAAY,EAAE,CAAC,SAAS,MAAM,CACK,MAAM;AAExD,KAAI;AACF,gBACE,SACA,KAAK,UAAU,4BAA4B,OAAO,EAAE,MAAM,EAAE,GAAG,MAC/D;GACE,UAAU;GAKV,MAAM;GACP,CACF;AACD,aAAW,SAAS,WAAW;UACxB,KAAK;AAEZ,MAAI;AACF,cAAW,QAAQ;UACb;AAGR,QAAM;;;AAIV,SAAS,cAAc,OAAuB;AAC5C,QAAO,MACJ,aAAa,CACb,MAAM,CACN,QAAQ,eAAe,IAAI,CAC3B,QAAQ,UAAU,GAAG;;AAG1B,SAAS,eAAe,OAAuB;AAG7C,QAAO,eAFe,MAAM,MAAM,CAAC,QAAQ,4BAA4B,GAAG,CAC/C,MAAM,SAAS,EAAE,CAAC,IAAI,QAAQ,SAAS,GAAG,IAAI,IAC/C,QAAQ,kBAAkB,GAAG,CAAC;;;;;;AAO1D,SAAS,4BAA4B,QAAkC;CACrE,MAAM,UAAoC,EAAE;CAC5C,MAAM,OAAO,YAAoB,eAAwC;EACvE,MAAM,MAAM,eAAe,WAAW;AACtC,MAAI,CAAC,IAAK;EACV,MAAM,SAAS,IAAI,IAAI,QAAQ,QAAQ,EAAE,CAAC;AAC1C,OAAK,MAAM,aAAa,YAAY;GAClC,MAAM,OAAO,cAAc,UAAU;AACrC,OAAI,KAAM,QAAO,IAAI,KAAK;;AAE5B,MAAI,OAAO,OAAO,EAAG,SAAQ,OAAO,CAAC,GAAG,OAAO;;AAGjD,MAAK,MAAM,CAAC,YAAY,eAAe,OAAO,QAC5C,OAAO,+BAA+B,EAAE,CACzC,EAAE;AACD,MAAI,CAAC,MAAM,QAAQ,WAAW,CAAE;AAChC,MACE,YACA,WAAW,QACR,cAAmC,OAAO,cAAc,SAC1D,CACF;;AAEH,MAAK,MAAM,WAAW,OAAO,OAAO,OAAO,SAAS,CAClD,KAAI,QAAQ,UACV,KAAI,QAAQ,WAAW,CAAC,QAAQ,MAAM,QAAQ,YAAY,CAAC;AAI/D,QAAO,OAAO,KAAK,QAAQ,CAAC,SAAS,KACnC,OAAO,gCAAgC,KAAA,IACrC;EAAE,GAAG;EAAQ,6BAA6B;EAAS,GACnD;;AAGN,SAAgB,aACd,SACa;CAEb,MAAM,UAAU,QADD,kBAAkB,CACF;AAC/B,aAAY,QAAQ;AACpB,QAAO;;;;;;;;;;;;;;;AClIT,MAAM,cAAc;;AAGpB,IAAI;;;;;;;;;;AAWJ,SAAgB,kBAAkB,UAA0C;AAC1E,KAAI,gBAAgB,aAAa,QAAQ,SACvC,QAAO,aAAa;CAGtB,IAAI,MAAM;AACV,QAAO,MAAM;EACX,MAAM,SAAS,KAAK,KAAK,YAAY;AACrC,MAAI,WAAW,OAAO,EAAE;AACtB,OAAI;IACF,MAAM,MAAM,aAAa,QAAQ,OAAO;IACxC,MAAM,SAAS,KAAK,MAAM,IAAI;AAC9B,QAAI,OAAO,OAAO,YAAY,YAAY,OAAO,QAAQ,SAAS,GAAG;KACnE,MAAM,SAA0B,EAAE,SAAS,OAAO,SAAS;AAC3D,oBAAe;MAAE,KAAK;MAAU;MAAQ;AACxC,YAAO;;WAEH;AAMR,kBAAe;IAAE,KAAK;IAAU,QAAQ;IAAM;AAC9C,UAAO;;EAGT,MAAM,SAAS,QAAQ,IAAI;AAC3B,MAAI,WAAW,IAAK;AACpB,QAAM;;AAGR,gBAAe;EAAE,KAAK;EAAU,QAAQ;EAAM;AAC9C,QAAO;;;;;;;;;;;;;;;;;;;;;AChCT,SAAgB,wBACd,aACyB;CACzB,MAAM,SAAS,YAAY;AAE3B,KAAI,aAAa;EACf,MAAM,UAAU,OAAO,SAAS;AAChC,SAAO,UAAU;GAAE,MAAM;GAAW;GAAS,GAAG;;CAGlD,MAAM,WAAW,QAAQ,IAAI,kBAAkB,QAAQ,IAAI;AAC3D,KAAI,SAAU,QAAO;EAAE,MAAM;EAAS,OAAO;EAAU;CAEvD,MAAM,gBAAgB,kBAAkB,QAAQ,KAAK,CAAC;AACtD,KAAI,eAAe,SAAS;EAC1B,MAAM,UAAU,OAAO,SAAS,cAAc;AAC9C,MAAI,QAAS,QAAO;GAAE,MAAM;GAAW;GAAS;;AAGlD,KAAI,CAAC,OAAO,cAAe,QAAO;CAClC,MAAM,UAAU,OAAO,SAAS,OAAO;AACvC,QAAO,UAAU;EAAE,MAAM;EAAW;EAAS,GAAG;;;;;;;;;;;;AAalD,SAAgB,aAAa,aAAqC;CAChE,MAAM,SAAS,wBAAwB,YAAY;AACnD,KAAI,CAAC,OAAQ,QAAO;AACpB,KAAI,OAAO,SAAS,QAAS,QAAO,OAAO;AAC3C,KAAI,kBAAkB,OAAO,QAAQ,CACnC,QAAO,kBAAkB,OAAO,QAAQ,KAAK;AAE/C,QAAO,OAAO,QAAQ,SAAS;;;;;;;AAQjC,eAAsB,kBACpB,aACwB;CACxB,MAAM,SAAS,wBAAwB,YAAY;AACnD,KAAI,CAAC,OAAQ,QAAO;AACpB,KAAI,OAAO,SAAS,QAAS,QAAO,OAAO;AAC3C,KAAI,kBAAkB,OAAO,QAAQ,CACnC,QAAO,mBAAmB,OAAO,QAAQ;AAE3C,QAAO,OAAO,QAAQ,SAAS;;;;;;;;;;;;;;;;AAiBjC,eAAsB,sBACpB,OACA,aACwB;CACxB,MAAM,SAAS,wBAAwB,YAAY;AACnD,KAAI,CAAC,OAAQ,QAAO;AACpB,KAAI,OAAO,SAAS,QAAS,QAAO,OAAO;AAC3C,KAAI,kBAAkB,OAAO,QAAQ,CACnC,QAAO,mBAAmB,OAAO,SAAS,MAAM;AAElD,QAAO,OAAO,QAAQ,SAAS;;;;;AAMjC,SAAgB,mBAAwC;CACtD,MAAM,SAAS,YAAY;AAC3B,KAAI,CAAC,OAAO,cAAe,QAAO;AAElC,QAAO,OAAO,SAAS,OAAO,kBAAkB;;;;;AAMlD,SAAgB,mBAA6B;CAC3C,MAAM,SAAS,YAAY;AAC3B,QAAO,OAAO,KAAK,OAAO,SAAS;;;;ACrIrC,MAAM,4BAAY,IAAI,SAAiC;;;;;;;;;;AAWvD,SAAgB,cACd,SACA,UACS;AACT,WAAU,IAAI,SAAS,SAAS;AAChC,QAAO;;AAGT,SAAgB,iBAAiB,SAA6C;AAC5E,QAAO,UAAU,IAAI,QAAQ;;;;ACtB/B,IAAI,gBAAgB;AACpB,MAAM,aAAa,IAAI,mBAAyB;;AAGhD,SAAgB,iBAA0B;AACxC,QAAO,gBAAgB,KAAK,WAAW,UAAU,KAAK;;AAGxD,SAAgB,kBAAqB,IAAgB;AACnD;AACA,KAAI;AACF,SAAO,IAAI;WACH;AACR;;;;;;;AAQJ,eAAsB,uBACpB,IACY;AACZ,QAAO,WAAW,IAAI,MAAM,GAAG;;;;AChBjC,SAAgB,YAAY,MAAc,MAAc,QAAQ,KAAK,EAAU;AAC7E,QAAO,WAAW,KAAK,GAAG,OAAO,GAAG,MAAM,MAAM;;;;;;;AAQlD,SAAgB,cACd,MACA,MAAc,QAAQ,KAAK,EACZ;CACf,MAAM,UAAU,YAAY,MAAM,IAAI;AACtC,QAAO;EACL;EACA,WAAW,wBAAwB,aAAa,OAAO,QAAQ,CAAC;EACjE;;;;ACdH,SAAgB,gBACd,QACU;CACV,MAAM,sBAAM,IAAI,KAAa;AAC7B,MAAK,MAAM,WAAW,UAAU,EAAE,EAAE;EAClC,MAAM,WAAW,QAAQ,MAAM,IAAI;AACnC,MACE,YAAY,MACZ,YAAY,KAAK,QAAQ,IACzB,SAAS,MAAM,MAAM,MAAM,MAAM,MAAM,OAAO,MAAM,KAAK,CAEzD,OAAM,IAAI,MACR,gCAAgC,KAAK,UAAU,QAAQ,GACxD;AAEH,MAAI,IAAI,QAAQ;;AAElB,QAAO,CAAC,GAAG,IAAI,CAAC,KAAK,QAAQ;;AAG/B,SAAgB,UACd,QACA,eAAe,OACP;AACR,QAAO,eACH,KAAK,UAAU;EAAE;EAAQ,cAAc;EAAM,CAAC,GAC9C,KAAK,UAAU,OAAO;;AAG5B,SAAgB,sBAAsB,OAAyB;AAC7D,KAAI,UAAU,KAAA,KAAa,OAAO,UAAU,UAAW,QAAO,UAAU;AACxE,OAAM,IAAI,MACR,8BAA8B,KAAK,UAAU,MAAM,IAAI,OAAO,MAAM,GACrE;;AAGH,SAAS,QAAQ,GAAW,GAAmB;AAC7C,QAAO,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI;;;;;;;;;;;;;AAclC,SAAgB,UAAU,KAAa,QAAoC;CACzE,MAAM,WAAW,IAAI,MAAM,IAAI;CAC/B,IAAI,SAAS;AACb,MAAK,MAAM,WAAW,UAAU;AAC9B,WAAS,WAAW,KAAK,UAAU,GAAG,OAAO,GAAG;AAChD,MAAI,OAAO,SAAS,QAAQ,IAAI,OAAO,SAAS,OAAO,CAAE,QAAO;;AAElE,QAAO;;;;;;;;;AAUT,SAAgB,cACd,MACA,QACA,eAAe,OACL;CACV,MAAM,MAAgB,EAAE;CACxB,MAAM,QAAQ,KAAa,WAAyB;AAClD,qBAAmB,IAAI;AACvB,OAAK,MAAM,SAAS,YAAY,KAAK,EAAE,eAAe,MAAM,CAAC,EAAE;GAC7D,MAAM,MAAM,WAAW,KAAK,MAAM,OAAO,GAAG,OAAO,GAAG,MAAM;AAC5D,OAAI,gBAAgB,MAAM,KAAK,WAAW,IAAI,CAAE;AAChD,OAAI,UAAU,KAAK,OAAO,CAAE;AAC5B,OAAI,MAAM,aAAa,CAAE,MAAK,KAAK,KAAK,MAAM,KAAK,EAAE,IAAI;YAChD,MAAM,QAAQ,CAAE,KAAI,KAAK,IAAI;;;AAG1C,yBAAwB,KAAK,MAAM,GAAG,CAAC;AACvC,QAAO,IAAI,KAAK,QAAQ;;AAG1B,SAAS,mBAAmB,KAAmB;AAC7C,KAAI,aAAa,OAAO,IAAI,KAAK,IAC/B,OAAM,IAAI,MAAM,uCAAuC,MAAM;;;;;;;;;AAWjE,SAAgB,qBAAqB,MAAc,KAAqB;AACtE,QAAO,wBAAwB;EAC7B,MAAM,OAAO,KAAK,MAAM,IAAI;AAC5B,qBAAmB,KAAK,MAAM,QAAQ,IAAI,CAAC,CAAC;EAC5C,MAAM,KAAK,SAAS,MAAM,UAAU,WAAW,UAAU,WAAW;AACpE,MAAI;AACF,OAAI,CAAC,UAAU,GAAG,CAAC,QAAQ,CACzB,OAAM,IAAI,MAAM,sCAAsC,OAAO;AAE/D,UAAO,aAAa,GAAG;YACf;AACR,aAAU,GAAG;;GAEf;;;;ACxGJ,SAAgB,QACd,MACA,QACA,eAAe,OACP;AACR,QAAO,GAAG,KAAK,IAAI,UAAU,QAAQ,aAAa;;AAKpD,SAAgB,OAAO,MAAsB;AAC3C,QAAO,WAAW,SAAS,CAAC,OAAO,KAAK,CAAC,OAAO,MAAM;;AAGxD,SAAgB,cACd,OACA,MAAc,QAAQ,KAAK,EACoB;AAC/C,QAAO,wBAAwB;EAC7B,MAAM,WAAqB;GACzB,uBAAO,IAAI,KAAK;GAChB,yBAAS,IAAI,KAAK;GAClB,uBAAO,IAAI,KAAK;GAChB,6BAAa,IAAI,KAAK;GACtB,sBAAM,IAAI,KAAK;GAChB;EACD,MAAM,UAAwB,EAAE;AAChC,OAAK,MAAM,QAAQ,MACjB,KAAI,KAAK,SAAS,OAAO;AACvB,YAAS,KAAK,IAAI,KAAK,MAAM,KAAK,MAAM;AACxC,WAAQ,KAAK;IAAE,MAAM;IAAO,MAAM,KAAK;IAAM,OAAO,KAAK;IAAO,CAAC;aACxD,KAAK,SAAS,UAAU,KAAK,SAAS,UAAU;GACzD,MAAM,EAAE,SAAS,WAAW,SAAS,cAAc,KAAK,MAAM,IAAI;GAClE,MAAM,UAAU,aAAa,KAAK;AAClC,YAAS,QAAQ,IAAI,SAAS,KAAK;AACnC,YAAS,QAAQ,IAAI,MAAM,KAAK;AAChC,YAAS,MAAM,IAAI,MAAM,QAAQ;AACjC,WAAQ,KAAK;IACX,MAAM,KAAK;IACX;IACA,QAAQ,OAAO,QAAQ;IACvB,MAAM,QAAQ;IACf,CAAC;SACG;AACL,OAAI,KAAK,SAAS,UAAU,OAAO,KAAK,SAAS,SAC/C,OAAM,IAAI,MAAM,0BAA0B;GAE5C,MAAM,EAAE,SAAS,WAAW,SAAS,cAAc,KAAK,MAAM,IAAI;GAClE,MAAM,SAAS,gBAAgB,KAAK,OAAO;GAC3C,MAAM,UAAU,sBAAsB,KAAK,aAAa;GACxD,MAAM,0BAAU,IAAI,KAAqB;GACzC,MAAM,QAA4B,EAAE;AACpC,QAAK,MAAM,OAAO,cAAc,MAAM,QAAQ,QAAQ,EAAE;IACtD,MAAM,UAAU,qBAAqB,MAAM,IAAI;AAC/C,YAAQ,IAAI,KAAK,QAAQ;AACzB,UAAM,KAAK;KACT,MAAM;KACN,QAAQ,OAAO,QAAQ;KACvB,MAAM,QAAQ;KACf,CAAC;;AAEJ,YAAS,YAAY,IAAI,SAAS,KAAK;AACvC,YAAS,YAAY,IAAI,MAAM,KAAK;AACpC,YAAS,MAAM,IAAI,QAAQ,MAAM,QAAQ,QAAQ,EAAE,QAAQ;AAC3D,WAAQ,KAAK;IACX,MAAM;IACN,MAAM;IACN;IACA,GAAI,UAAU,EAAE,cAAc,MAAe,GAAG,EAAE;IAClD;IACD,CAAC;;AAGN,SAAO;GAAE;GAAS;GAAU;GAC5B;;AAGJ,SAAS,QAAQ,OAA2B;CAC1C,MAAM,KACJ,MAAM,SAAS,QACX,MAAM,OACN,MAAM,SAAS,SACb,GAAG,MAAM,KAAK,IAAI,UAAU,MAAM,QAAQ,MAAM,iBAAiB,KAAK,KACtE,MAAM;AACd,QAAO,GAAG,MAAM,KAAK,IAAI;;AAG3B,SAAgB,iBAAiB,SAA8C;AAC7E,QAAO,QACJ,KAAK,MAAkB;AACtB,MAAI,EAAE,SAAS,MACb,QAAO;GAAE,MAAM;GAAO,MAAM,EAAE;GAAM,OAAO,EAAE;GAAO;AAEtD,MAAI,EAAE,SAAS,OACb,QAAO;GACL,MAAM;GACN,MAAM,EAAE;GACR,QAAQ,CAAC,GAAG,EAAE,OAAO;GACrB,GAAI,EAAE,iBAAiB,OAAO,EAAE,cAAc,MAAe,GAAG,EAAE;GAClE,OAAO,EAAE,MAAM,KAAK,OAAO;IACzB,MAAM,EAAE;IACR,QAAQ,EAAE;IACV,MAAM,EAAE;IACT,EAAE;GACJ;AAEH,SAAO;GAAE,MAAM,EAAE;GAAM,MAAM,EAAE;GAAM,QAAQ,EAAE;GAAQ,MAAM,EAAE;GAAM;GACrE,CACD,MAAM,GAAG,MAAM;EACd,MAAM,KAAK,QAAQ,EAAE;EACrB,MAAM,KAAK,QAAQ,EAAE;AACrB,SAAO,KAAK,KAAK,KAAK,KAAK,KAAK,IAAI;GACpC;;AAGN,SAAgB,aACd,SACA,SACQ;AACR,QAAO,KAAK,UAAU;EACpB,SAAA;EACA;EACA,QAAQ,iBAAiB,QAAQ;EAClC,CAAC;;;;ACrIJ,IAAI,iBAAkC;AAEtC,SAAgB,kBAAkB,UAAiC;AACjE,kBAAiB;;AAGnB,IAAa,wBAAb,cAA2C,MAAM;CAC/C,YAAY,MAAc;AACxB,QAAM,0BAA0B,OAAO;AACvC,OAAK,OAAO;;;AAIhB,SAAS,YAAY,MAAc,UAAwC;CACzE,MAAM,UAAU,YAAY,KAAK;CACjC,MAAM,MAAM,SAAS,QAAQ,IAAI,QAAQ,IAAI;AAC7C,QAAO,SAAS,MAAM,IAAI,IAAI,GAAG,MAAM,KAAA;;AAGzC,SAAS,MAAM,SAAiB,UAAkC;AAChE,QACE,WAAW,QAAQ,SAAS,SAAS,GAAG,OAAO,KAAK,QAAQ;;AAIhE,SAAS,aACP,UACA,QACA,SACA,UACgD;CAChD,MAAM,OAAO,SAAS,YAAY,IAAI,YAAY,SAAS,CAAC,IAAI;CAChE,MAAM,UAAU,SAAS,MAAM,IAAI,QAAQ,MAAM,QAAQ,QAAQ,CAAC;AAClE,KAAI,CAAC,QAAS,OAAM,IAAI,sBAAsB,SAAS;AACvD,QAAO;EAAE;EAAM;EAAS;;AAG1B,SAAS,SAAS,UAAkB,SAAkC;CACpE,MAAM,SAAS,gBAAgB,SAAS,OAAO;CAC/C,MAAM,UAAU,sBAAsB,SAAS,aAAa;CAC5D,IAAI;CACJ,MAAM,iBAAuD;AAC3D,MAAI,CAAC,MAAM;GACT,MAAM,OAAO,cAAc,SAAS,CAAC;AACrC,UAAO;IAAE;IAAM,OAAO,IAAI,IAAI,cAAc,MAAM,QAAQ,QAAQ,CAAC;IAAE;;AAEvE,SAAO;;AAET,KAAI,eAAgB,cAAa,UAAU,QAAQ,SAAS,eAAe;KACtE,WAAU;AACf,QAAO;EACL,IAAI,OAAO;AACT,UAAO,iBACH,aAAa,UAAU,QAAQ,SAAS,eAAe,CAAC,OACxD,UAAU,CAAC;;EAEjB,OAAO;AACL,OAAI,eACF,QAAO,CACL,GAAG,aACD,UACA,QACA,SACA,eACD,CAAC,QAAQ,MAAM,CACjB;AAEH,UAAO,CAAC,GAAG,UAAU,CAAC,MAAM;;EAE9B,SAAS,KAAa,UAAkC;GACtD,MAAM,gBAAgB,IAAI,sBAAsB,GAAG,SAAS,GAAG,MAAM;AACrE,OAAI,gBAAgB;IAClB,MAAM,UAAU,aACd,UACA,QACA,SACA,eACD,CAAC,QAAQ,IAAI,IAAI;AAClB,QAAI,CAAC,QAAS,OAAM,SAAS;AAC7B,WAAO,MAAM,SAAS,SAAS;;GAEjC,MAAM,EAAE,MAAM,UAAU,UAAU;AAClC,OAAI,CAAC,MAAM,IAAI,IAAI,CAAE,OAAM,SAAS;AACpC,UAAO,MAAM,qBAAqB,MAAM,IAAI,EAAE,SAAS;;EAE1D;;;;;;;AAQH,SAAgB,qBAAoC;AAClD,QAAO;EACL,mBAAmB,mBAAmB;EACtC,IAAI,MAAc;GAChB,MAAM,WAAW;AACjB,OAAI,CAAC,SAAU,QAAO,KAAA;GACtB,MAAM,QAAQ,SAAS,KAAK,IAAI,KAAK;AACrC,OAAI,UAAU,KAAA,EAAW,OAAM,IAAI,sBAAsB,OAAO,OAAO;AACvE,UAAO;;EAET,MAAM;EACN,SAAS,MAAc,UAAkC;GACvD,MAAM,WAAW;AACjB,OAAI,CAAC,UAAU;IACb,MAAM,UAAU,wBACd,aAAa,YAAY,KAAK,CAAC,CAChC;AACD,WAAQ,WAAW,QAAQ,SAAS,SAAS,GAAG;;GAElD,MAAM,MAAM,YAAY,MAAM,SAAS;GACvC,MAAM,UAAU,QAAQ,KAAA,IAAY,KAAA,IAAY,SAAS,MAAM,IAAI,IAAI;AACvE,OAAI,CAAC,QAAS,OAAM,IAAI,sBAAsB,KAAK;AACnD,UACE,WAAW,QAAQ,SAAS,SAAS,GAAG,OAAO,KAAK,QAAQ;;EAGjE;;;;ACtHH,MAAa,kBAAkB;AAC/B,MAAa,oBAAoB;AAEjC,MAAa,yBAAyB;AACtC,MAAa,yBAAyB;AACtC,MAAa,uBAAuB;AACpC,MAAa,uBAAuB;AAUpC,SAAS,SAAS,IAAY,MAAoB;CAChD,MAAM,SAAS,OAAO,KAAK,KAAK;CAChC,IAAI,SAAS;AACb,QAAO,SAAS,OAAO,OACrB,KAAI;AACF,YAAU,UAAU,IAAI,QAAQ,OAAO;UAChC,OAAO;AACd,MAAK,MAAgC,SAAS,SAAU,OAAM;;;AAKpE,MAAM,YAA0B;CAC9B,KAAK,QAAQ;CACb,SAAS,SAAS,SAAS,GAAG,KAAK;CACnC,SAAS,SAAS,SAAS,GAAG,KAAK;CACnC,OAAO,SAAS,QAAQ,KAAK,KAAK;CACnC;AAED,SAAgB,iBAAiB,KAAiC;AAChE,QACE,IAAA,yBAAyB,KAAA,KAAa,IAAA,2BAA2B,KAAA;;AAIrE,SAAS,YAAY,SAA0B;CAC7C,MAAM,QAAkB,EAAE;AAC1B,MAAK,IAAI,IAAoB,SAAS,GAAG,IAAI,EAAE,OAC7C,OAAM,QAAQ,EAAE,MAAM,CAAC;AACzB,QAAO,MAAM,KAAK,IAAI;;AAGxB,SAAS,aACP,MACqE;AACrE,KAAI;EACF,MAAM,SAAS,KAAK,MAClB,wBAAwB,aAAa,MAAM,OAAO,CAAC,CACpD;AACD,MAAI,CAAC,MAAM,QAAQ,OAAO,OAAO,CAAE,QAAO;AAC1C,SAAO;GACL,SAAS,OAAO;GAChB,SAAS,OAAO;GAChB,QAAQ,iBAAiB,OAAO,OAAuB;GACxD;SACK;AACN,SAAO;;;AAIX,SAAgB,gBACd,eACA,KAAmB,WACb;CACN,MAAM,OAAO,GAAG,IAAI;CACpB,MAAM,aAAa,GAAG,IAAI;AAC1B,mBAAkB,KAAK;AACvB,KAAI,SAAS,KAAA,KAAa,eAAe,KAAA,EAAW;CACpD,MAAM,cAAc,SAAS;AAC7B,KACG,SAAS,KAAA,KAAa,CAAC,eACxB,eAAe,MACd,eAAe,eAAe,KAAA,GAC/B;AACA,KAAG,OAAO,GAAG,qBAAqB,IAAI;AACtC,SAAO,GAAG,KAAA,GAA0B;;CAGtC,MAAM,WAAW,iBAAiB,cAAc;AAChD,KAAI,CAAC,UAAU;AACb,KAAG,OAAO,GAAG,uBAAuB,IAAI;AACxC,SAAO,GAAG,KAAA,GAA4B;;CAGxC,IAAI;AACJ,KAAI;AACF,aAAW,cACT,SAAS,cAAc,iBAAiB,EAAE,cAAc,KAAK,CAC9D;UACM,OAAO;AACd,MAAI,aAAa;GACf,MAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;AACtE,MAAG,OAAO,GAAG,QAAQ,IAAI;AACzB,UAAO,GAAG,KAAA,EAAgC;;AAE5C,KAAG,OAAO,GAAG,qBAAqB,IAAI;AACtC,SAAO,GAAG,KAAA,GAA0B;;AAGtC,KAAI,aAAa;AACf,KAAG,OACD,GAAG,aAAa,YAAY,cAAc,EAAE,SAAS,QAAQ,CAAC,IAC/D;AACD,SAAO,GAAG,KAAK,EAAE;;CAGnB,MAAM,WAAW,aAAa,WAAqB;AACnD,KACE,CAAC,YACD,SAAS,YAAA,KACT,SAAS,YAAY,YAAY,cAAc,IAC/C,KAAK,UAAU,SAAS,OAAO,KAC7B,KAAK,UAAU,iBAAiB,SAAS,QAAQ,CAAC,EACpD;AACA,KAAG,OAAO,GAAG,qBAAqB,IAAI;AACtC,SAAO,GAAG,KAAA,GAA0B;;AAEtC,mBAAkB,SAAS,SAAS;;;;;;AAOtC,SAAgB,mBACd,SACA,KAAmB,WACb;AACN,SAAQ,KAAK,cAAc,UAAU,kBAAkB;AACrD,kBAAgB,eAAe,GAAG;GAClC;;;;;;AAOJ,SAAgB,kBACd,OACA,KAAmB,WACb;AACN,KAAI,EAAE,iBAAiB,uBAAwB;AAC/C,IAAG,OAAO,GAAG,uBAAuB,IAAI;AACxC,IAAG,KAAA,GAA4B"}