@fluid-app/fluid-cli-portal 0.1.53 → 0.1.54
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.
- package/dist/{backend-dev-plugin-B9-S4Is-.mjs → backend-dev-plugin-CY3yxgJ1.mjs} +1 -1
- package/dist/{backend-dev-plugin-B9-S4Is-.mjs.map → backend-dev-plugin-CY3yxgJ1.mjs.map} +1 -1
- package/dist/index.d.mts +622 -421
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +719 -1815
- package/dist/index.mjs.map +1 -1
- package/dist/{portal-dev-plugin-ByYelgFg.mjs → portal-dev-plugin-CHlxpXc5.mjs} +1 -1
- package/dist/{portal-dev-plugin-ByYelgFg.mjs.map → portal-dev-plugin-CHlxpXc5.mjs.map} +1 -1
- package/dist/portal-widget-dev-plugin-B53Lf9Ke.mjs +144 -0
- package/dist/portal-widget-dev-plugin-B53Lf9Ke.mjs.map +1 -0
- package/dist/pull-CUR8tbW9.mjs +1633 -0
- package/dist/pull-CUR8tbW9.mjs.map +1 -0
- package/dist/sdk.gen-DuPhfdxN.mjs +469 -0
- package/dist/sdk.gen-DuPhfdxN.mjs.map +1 -0
- package/dist/src-CpH5SpDH.mjs +2157 -0
- package/dist/src-CpH5SpDH.mjs.map +1 -0
- package/dist/vite-plugin.d.mts +4 -1
- package/dist/vite-plugin.d.mts.map +1 -1
- package/dist/vite-plugin.mjs +5 -3
- package/package.json +7 -5
- package/templates/base/AGENTS.md +4 -0
- package/templates/base/skills/fluid-portal-authoring/SKILL.md +113 -1
- package/dist/pull-C3yj9L07.mjs +0 -1232
- package/dist/pull-C3yj9L07.mjs.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pull-CUR8tbW9.mjs","names":["getAuthToken","getAuthToken","createClient","createClient","sdk.fluidOsV0ListFluidOsdefinitions","sdk.fluidOsV0ListFluidOsscreens","sdk.fluidOsV0GetFluidOsscreen","sdk.fluidOsV0CreateFluidOsscreen","sdk.fluidOsV0UpdateFluidOsscreen","sdk.fluidOsV0DeleteFluidOsscreen","sdk.fluidOsV0ListFluidOsthemes","sdk.fluidOsV0CreateFluidOstheme","sdk.fluidOsV0UpdateFluidOstheme","sdk.fluidOsV0DeleteFluidOstheme","sdk.fluidOsV0ListFluidOsnavigations","sdk.fluidOsV0CreateFluidOsnavigation","sdk.fluidOsV0UpdateFluidOsnavigation","sdk.fluidOsV0DeleteFluidOsnavigation","sdk.fluidOsV0ListFluidOsnavigationItems","sdk.fluidOsV0CreateFluidOsnavigationItem","sdk.fluidOsV0UpdateFluidOsnavigationItem","sdk.fluidOsV0DeleteFluidOsnavigationItem","sdk.fluidOsV0ListFluidOsprofiles","sdk.fluidOsV0CreateFluidOsprofile","sdk.fluidOsV0UpdateFluidOsprofile","sdk.fluidOsV0DeleteFluidOsprofile","sdk.fluidOsV0ListFluidOsversions","sdk.fluidOsV0CreateFluidOsversion","sdk.fluidOsV0UpdateFluidOsversion"],"sources":["../src/utils/standalone-portal.ts","../src/commands/pull-definition.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/fluidos/src/hey-api-client.ts","../../../api-clients/fluidos/src/json.ts","../src/api/fluidos.ts","../src/utils/atomic-write.ts","../src/utils/mappings.ts","../src/utils/snapshot.ts","../src/utils/transform.ts","../src/commands/pull.ts"],"sourcesContent":["import { lstat } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\nexport const STANDALONE_PORTAL_MARKER = \".fluid-portal.json\";\n\n/**\n * Standalone portal definitions never synchronize with Fluid OS. Check only\n * for marker presence here: an unreadable or malformed marker must not turn a\n * local-only project into an accidental remote target.\n */\nexport async function assertStandalonePortalRemoteActionAllowed(\n projectDirectory: string,\n action: string,\n): Promise<void> {\n try {\n await lstat(join(projectDirectory, STANDALONE_PORTAL_MARKER));\n } catch (error) {\n if (\n typeof error === \"object\" &&\n error !== null &&\n \"code\" in error &&\n error.code === \"ENOENT\"\n ) {\n return;\n }\n throw error;\n }\n\n throw new Error(\n `This is a standalone Portal SDK project. It is local-only in Fluid; portal/ is the source of truth, and Fluid portal pull, push, version, and publish commands do not apply. Local SDK portal publishing will be added with Vercel hosting. ${action} was refused.`,\n );\n}\n","import chalk from \"chalk\";\n\n/** Minimal shape both the id and name lookups need. */\nexport interface DefinitionChoice {\n readonly id: number;\n readonly name?: string | null;\n}\n\n/** Pick a definition by explicit id, else by name.\n *\n * Mirrors clone.ts's resolver of the same name, and for the same\n * reason: definition NAMES REPEAT. clone's `--id` help says so\n * outright (\"avoids ambiguity when names repeat\") and clone.test.ts\n * pins it. pull only ever looked up by name — first match wins — so a\n * caller holding an unambiguous id had no way to use it, and Mist\n * called pull by name immediately after cloning by id.\n *\n * Returns null when neither is supplied, so the caller falls through\n * to the interactive selector. */\nexport function resolveDefinition(\n definitions: readonly DefinitionChoice[],\n appName?: string,\n rawDefinitionId?: string,\n): DefinitionChoice | null {\n if (rawDefinitionId !== undefined) {\n if (!/^[1-9]\\d*$/.test(rawDefinitionId)) {\n throw new Error(\"--id must be a positive integer\");\n }\n const definitionId = Number.parseInt(rawDefinitionId, 10);\n const match = definitions.find((d) => d.id === definitionId);\n if (!match) {\n throw new Error(`No definition found with id ${definitionId}.`);\n }\n return match;\n }\n if (appName) {\n const match = definitions.find(\n (d) => d.name?.toLowerCase() === appName.toLowerCase(),\n );\n if (!match) {\n const availableNames = definitions\n .map((d) => chalk.cyan(d.name ?? \"(unnamed)\"))\n .join(\", \");\n throw new Error(\n `No definition found matching \"${appName}\". Available: ${availableNames}`,\n );\n }\n return match;\n }\n return null;\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, 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 * `??` rather than `||` so an explicitly empty `errors` (`\"\"`, `0`, `false`)\n * is preserved instead of silently falling back to the whole envelope.\n */\nexport function toApiErrorData(body: ApiErrorBody): ApiErrorData {\n if (!body) {\n return null;\n }\n\n return (body.errors ?? body) as ApiErrorData;\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[]\n value.forEach((item) => queryString.append(`${key}[]`, 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 { createHeyApiErrorInterceptor } from \"@fluid-app/api-client-core\";\nimport { type Client, createClient } from \"@fluid-app/hey-api-fetch-runtime\";\n\nexport type FluidOsHeyApiClient = Client;\n\nexport interface FluidOsHeyApiClientConfig {\n baseUrl: string;\n fetch?: typeof fetch;\n getAuthToken?: () =>\n | string\n | null\n | undefined\n | Promise<string | null | undefined>;\n defaultHeaders?: Record<string, string>;\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\nexport function createFluidOsHeyApiClient(\n config: FluidOsHeyApiClientConfig,\n): Client {\n const client = createClient({\n auth: async () => (await config.getAuthToken?.()) ?? undefined,\n baseUrl: config.baseUrl,\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","import type { FluidOsDynamicObject } from \"./hey-api/types.gen\";\n\nexport function toFluidOsDynamicObject(value: null): null;\nexport function toFluidOsDynamicObject(\n value: Record<string, unknown>,\n): FluidOsDynamicObject;\nexport function toFluidOsDynamicObject(\n value: Record<string, unknown> | null,\n): FluidOsDynamicObject | null;\nexport function toFluidOsDynamicObject(\n value: Record<string, unknown> | null,\n): FluidOsDynamicObject | null {\n if (value === null) return null;\n\n const serialized = JSON.stringify(value);\n if (serialized === undefined) {\n throw new TypeError(\"FluidOS payload cannot be serialized as JSON.\");\n }\n\n return JSON.parse(serialized) as FluidOsDynamicObject;\n}\n\nexport function toNumberArray(value: unknown): number[] | undefined {\n if (!Array.isArray(value)) return undefined;\n return value.filter((item): item is number => typeof item === \"number\");\n}\n\nexport function toStringArray(value: unknown): string[] | undefined {\n if (!Array.isArray(value)) return undefined;\n return value.filter((item): item is string => typeof item === \"string\");\n}\n","import {\n createFluidOsHeyApiClient,\n toFluidOsDynamicObject,\n type FluidOsHeyApiClient,\n} from \"@fluid-app/fluidos-api-client\";\nimport * as sdk from \"@fluid-app/fluidos-api-client/sdk\";\nimport type {\n FluidOsV0CreateFluidOsnavigationData,\n FluidOsV0CreateFluidOsnavigationItemData,\n FluidOsV0CreateFluidOsprofileData,\n FluidOsV0CreateFluidOsscreenData,\n FluidOsV0CreateFluidOsthemeData,\n FluidOsV0ListFluidOsdefinitionsData,\n FluidOsV0ListFluidOsnavigationsData,\n FluidOsV0ListFluidOsprofilesData,\n FluidOsV0ListFluidOsscreensData,\n FluidOsV0ListFluidOsthemesData,\n FluidOsV0ListFluidOsversionsData,\n FluidOsV0UpdateFluidOsnavigationData,\n FluidOsV0UpdateFluidOsnavigationItemData,\n FluidOsV0UpdateFluidOsprofileData,\n FluidOsV0UpdateFluidOsscreenData,\n FluidOsV0UpdateFluidOsthemeData,\n FluidOsV0UpdateFluidOsversionData,\n} from \"@fluid-app/fluidos-api-client/types\";\n\nexport type FluidOsClient = FluidOsHeyApiClient;\ntype RequiredApiResult<T> = Promise<Exclude<Awaited<T>, undefined>>;\n\nexport { toFluidOsDynamicObject };\n\nexport function createFluidOsClient(options: {\n readonly baseUrl: string;\n readonly token: string;\n}): FluidOsClient {\n return createFluidOsHeyApiClient({\n baseUrl: options.baseUrl,\n getAuthToken: () => options.token,\n });\n}\n\nexport const fluidOsApi = {\n listDefinitions: (\n client: FluidOsClient,\n query?: FluidOsV0ListFluidOsdefinitionsData[\"query\"],\n ): RequiredApiResult<\n ReturnType<typeof sdk.fluidOsV0ListFluidOsdefinitions>\n > => sdk.fluidOsV0ListFluidOsdefinitions({ client, query }),\n listScreens: (\n client: FluidOsClient,\n definitionId: number,\n query?: FluidOsV0ListFluidOsscreensData[\"query\"],\n ): RequiredApiResult<ReturnType<typeof sdk.fluidOsV0ListFluidOsscreens>> =>\n sdk.fluidOsV0ListFluidOsscreens({\n client,\n path: { definition_id: definitionId },\n query,\n }),\n getScreen: (\n client: FluidOsClient,\n definitionId: number,\n id: number,\n ): RequiredApiResult<ReturnType<typeof sdk.fluidOsV0GetFluidOsscreen>> =>\n sdk.fluidOsV0GetFluidOsscreen({\n client,\n path: { definition_id: definitionId, id },\n }),\n createScreen: (\n client: FluidOsClient,\n definitionId: number,\n body: FluidOsV0CreateFluidOsscreenData[\"body\"],\n ): RequiredApiResult<ReturnType<typeof sdk.fluidOsV0CreateFluidOsscreen>> =>\n sdk.fluidOsV0CreateFluidOsscreen({\n body,\n client,\n path: { definition_id: definitionId },\n }),\n updateScreen: (\n client: FluidOsClient,\n definitionId: number,\n id: number,\n body: FluidOsV0UpdateFluidOsscreenData[\"body\"],\n ): RequiredApiResult<ReturnType<typeof sdk.fluidOsV0UpdateFluidOsscreen>> =>\n sdk.fluidOsV0UpdateFluidOsscreen({\n body,\n client,\n path: { definition_id: definitionId, id },\n }),\n deleteScreen: (\n client: FluidOsClient,\n definitionId: number,\n id: number,\n ): RequiredApiResult<ReturnType<typeof sdk.fluidOsV0DeleteFluidOsscreen>> =>\n sdk.fluidOsV0DeleteFluidOsscreen({\n client,\n path: { definition_id: definitionId, id },\n }),\n listThemes: (\n client: FluidOsClient,\n definitionId: number,\n query?: FluidOsV0ListFluidOsthemesData[\"query\"],\n ): RequiredApiResult<ReturnType<typeof sdk.fluidOsV0ListFluidOsthemes>> =>\n sdk.fluidOsV0ListFluidOsthemes({\n client,\n path: { definition_id: definitionId },\n query,\n }),\n createTheme: (\n client: FluidOsClient,\n definitionId: number,\n body: FluidOsV0CreateFluidOsthemeData[\"body\"],\n ): RequiredApiResult<ReturnType<typeof sdk.fluidOsV0CreateFluidOstheme>> =>\n sdk.fluidOsV0CreateFluidOstheme({\n body,\n client,\n path: { definition_id: definitionId },\n }),\n updateTheme: (\n client: FluidOsClient,\n definitionId: number,\n id: number,\n body: FluidOsV0UpdateFluidOsthemeData[\"body\"],\n ): RequiredApiResult<ReturnType<typeof sdk.fluidOsV0UpdateFluidOstheme>> =>\n sdk.fluidOsV0UpdateFluidOstheme({\n body,\n client,\n path: { definition_id: definitionId, id },\n }),\n deleteTheme: (\n client: FluidOsClient,\n definitionId: number,\n id: number,\n ): RequiredApiResult<ReturnType<typeof sdk.fluidOsV0DeleteFluidOstheme>> =>\n sdk.fluidOsV0DeleteFluidOstheme({\n client,\n path: { definition_id: definitionId, id },\n }),\n listNavigations: (\n client: FluidOsClient,\n definitionId: number,\n query?: FluidOsV0ListFluidOsnavigationsData[\"query\"],\n ): RequiredApiResult<\n ReturnType<typeof sdk.fluidOsV0ListFluidOsnavigations>\n > =>\n sdk.fluidOsV0ListFluidOsnavigations({\n client,\n path: { definition_id: definitionId },\n query,\n }),\n createNavigation: (\n client: FluidOsClient,\n definitionId: number,\n body: FluidOsV0CreateFluidOsnavigationData[\"body\"],\n ): RequiredApiResult<\n ReturnType<typeof sdk.fluidOsV0CreateFluidOsnavigation>\n > =>\n sdk.fluidOsV0CreateFluidOsnavigation({\n body,\n client,\n path: { definition_id: definitionId },\n }),\n updateNavigation: (\n client: FluidOsClient,\n definitionId: number,\n id: number,\n body: FluidOsV0UpdateFluidOsnavigationData[\"body\"],\n ): RequiredApiResult<\n ReturnType<typeof sdk.fluidOsV0UpdateFluidOsnavigation>\n > =>\n sdk.fluidOsV0UpdateFluidOsnavigation({\n body,\n client,\n path: { definition_id: definitionId, id },\n }),\n deleteNavigation: (\n client: FluidOsClient,\n definitionId: number,\n id: number,\n ): RequiredApiResult<\n ReturnType<typeof sdk.fluidOsV0DeleteFluidOsnavigation>\n > =>\n sdk.fluidOsV0DeleteFluidOsnavigation({\n client,\n path: { definition_id: definitionId, id },\n }),\n listNavigationItems: (\n client: FluidOsClient,\n definitionId: number,\n navigationId: number,\n ): RequiredApiResult<\n ReturnType<typeof sdk.fluidOsV0ListFluidOsnavigationItems>\n > =>\n sdk.fluidOsV0ListFluidOsnavigationItems({\n client,\n path: { definition_id: definitionId, navigation_id: navigationId },\n }),\n createNavigationItem: (\n client: FluidOsClient,\n definitionId: number,\n navigationId: number,\n body: FluidOsV0CreateFluidOsnavigationItemData[\"body\"],\n ): RequiredApiResult<\n ReturnType<typeof sdk.fluidOsV0CreateFluidOsnavigationItem>\n > =>\n sdk.fluidOsV0CreateFluidOsnavigationItem({\n body,\n client,\n path: { definition_id: definitionId, navigation_id: navigationId },\n }),\n updateNavigationItem: (\n client: FluidOsClient,\n definitionId: number,\n navigationId: number,\n id: number,\n body: FluidOsV0UpdateFluidOsnavigationItemData[\"body\"],\n ): RequiredApiResult<\n ReturnType<typeof sdk.fluidOsV0UpdateFluidOsnavigationItem>\n > =>\n sdk.fluidOsV0UpdateFluidOsnavigationItem({\n body,\n client,\n path: {\n definition_id: definitionId,\n id,\n navigation_id: navigationId,\n },\n }),\n deleteNavigationItem: (\n client: FluidOsClient,\n definitionId: number,\n navigationId: number,\n id: number,\n ): RequiredApiResult<\n ReturnType<typeof sdk.fluidOsV0DeleteFluidOsnavigationItem>\n > =>\n sdk.fluidOsV0DeleteFluidOsnavigationItem({\n client,\n path: {\n definition_id: definitionId,\n id,\n navigation_id: navigationId,\n },\n }),\n listProfiles: (\n client: FluidOsClient,\n definitionId: number,\n query?: FluidOsV0ListFluidOsprofilesData[\"query\"],\n ): RequiredApiResult<ReturnType<typeof sdk.fluidOsV0ListFluidOsprofiles>> =>\n sdk.fluidOsV0ListFluidOsprofiles({\n client,\n path: { definition_id: definitionId },\n query,\n }),\n createProfile: (\n client: FluidOsClient,\n definitionId: number,\n body: FluidOsV0CreateFluidOsprofileData[\"body\"],\n ): RequiredApiResult<ReturnType<typeof sdk.fluidOsV0CreateFluidOsprofile>> =>\n sdk.fluidOsV0CreateFluidOsprofile({\n body,\n client,\n path: { definition_id: definitionId },\n }),\n updateProfile: (\n client: FluidOsClient,\n definitionId: number,\n id: number,\n body: FluidOsV0UpdateFluidOsprofileData[\"body\"],\n ): RequiredApiResult<ReturnType<typeof sdk.fluidOsV0UpdateFluidOsprofile>> =>\n sdk.fluidOsV0UpdateFluidOsprofile({\n body,\n client,\n path: { definition_id: definitionId, id },\n }),\n deleteProfile: (\n client: FluidOsClient,\n definitionId: number,\n id: number,\n ): RequiredApiResult<ReturnType<typeof sdk.fluidOsV0DeleteFluidOsprofile>> =>\n sdk.fluidOsV0DeleteFluidOsprofile({\n client,\n path: { definition_id: definitionId, id },\n }),\n listVersions: (\n client: FluidOsClient,\n definitionId: number,\n query?: FluidOsV0ListFluidOsversionsData[\"query\"],\n ): RequiredApiResult<ReturnType<typeof sdk.fluidOsV0ListFluidOsversions>> =>\n sdk.fluidOsV0ListFluidOsversions({\n client,\n path: { definition_id: definitionId },\n query,\n }),\n createVersion: (\n client: FluidOsClient,\n definitionId: number,\n ): RequiredApiResult<ReturnType<typeof sdk.fluidOsV0CreateFluidOsversion>> =>\n sdk.fluidOsV0CreateFluidOsversion({\n client,\n path: { definition_id: definitionId },\n }),\n updateVersion: (\n client: FluidOsClient,\n definitionId: number,\n id: number,\n body: FluidOsV0UpdateFluidOsversionData[\"body\"],\n ): RequiredApiResult<ReturnType<typeof sdk.fluidOsV0UpdateFluidOsversion>> =>\n sdk.fluidOsV0UpdateFluidOsversion({\n body,\n client,\n path: { definition_id: definitionId, id },\n }),\n};\n","/**\n * Atomic file write utility.\n *\n * Uses a write-then-rename pattern so readers always see either the old\n * file or the complete new file — never a partially-written state.\n */\n\nimport { randomBytes } from \"node:crypto\";\nimport { rename, unlink, writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\n\n/**\n * Write a file atomically using a write-then-rename pattern.\n * Ensures readers always see either the old file or the complete new file.\n *\n * If `rename` fails after the temp file has been written, the temp file\n * is cleaned up on a best-effort basis before re-throwing the error.\n */\nexport async function atomicWriteFile(\n filePath: string,\n data: string,\n): Promise<void> {\n const dir = dirname(filePath);\n const tmp = join(dir, `.tmp-${randomBytes(6).toString(\"hex\")}`);\n try {\n await writeFile(tmp, data, \"utf-8\");\n await rename(tmp, filePath);\n } catch (err) {\n // Best-effort cleanup of the temp file\n await unlink(tmp).catch(() => {});\n throw err;\n }\n}\n","/**\n * Slug-to-ID mapping system for portal content sync.\n *\n * Maps human-readable slugs (derived from resource names) to API IDs,\n * enabling the file system to use readable directory/file names while\n * maintaining the link back to server-side resources.\n *\n * Persisted in `.portal-sync/mappings.json`.\n */\n\nimport { readFile, mkdir } from \"node:fs/promises\";\nimport { join, dirname } from \"node:path\";\n\nimport { atomicWriteFile } from \"./atomic-write.js\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Types\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Definition metadata stored at the top level of the mappings file. */\nexport interface DefinitionMapping {\n readonly name: string;\n readonly id: number;\n}\n\n/**\n * Resource types that support slug-to-ID mappings.\n * \"countries\" and \"ranks\" use display names as keys (not slugified).\n */\nexport type MappedResourceType =\n | \"screens\"\n | \"themes\"\n | \"navigations\"\n | \"profiles\"\n | \"countries\"\n | \"ranks\";\n\n/** Full mappings structure persisted to disk. */\nexport interface PortalMappings {\n readonly definition: DefinitionMapping;\n readonly screens: Record<string, number>;\n readonly themes: Record<string, number>;\n readonly navigations: Record<string, number>;\n readonly profiles: Record<string, number>;\n readonly countries: Record<string, number>;\n readonly ranks: Record<string, number>;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Helpers\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Runtime check that a parsed value has the expected PortalMappings shape. */\nfunction isMappings(value: unknown): value is PortalMappings {\n if (!value || typeof value !== \"object\") return false;\n const m = value as Record<string, unknown>;\n\n // Validate inner DefinitionMapping fields\n const def = m.definition as Record<string, unknown> | null | undefined;\n if (!def || typeof def !== \"object\") return false;\n if (typeof def.name !== \"string\" || typeof def.id !== \"number\") return false;\n\n return (\n typeof m.screens === \"object\" &&\n m.screens !== null &&\n typeof m.themes === \"object\" &&\n m.themes !== null &&\n typeof m.navigations === \"object\" &&\n m.navigations !== null &&\n typeof m.profiles === \"object\" &&\n m.profiles !== null &&\n typeof m.countries === \"object\" &&\n m.countries !== null &&\n typeof m.ranks === \"object\" &&\n m.ranks !== null\n );\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Read / Write\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst MAPPINGS_FILE = \"mappings.json\";\n\n/**\n * Read the mappings file from the `.portal-sync/` directory.\n * Returns `null` if the file does not exist.\n */\nexport async function readMappings(\n portalSyncDir: string,\n): Promise<PortalMappings | null> {\n try {\n const filePath = join(portalSyncDir, MAPPINGS_FILE);\n const content = await readFile(filePath, \"utf-8\");\n const parsed: unknown = JSON.parse(content);\n if (!isMappings(parsed)) {\n throw new Error(`Malformed mappings file: ${filePath}`);\n }\n return parsed;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n return null;\n }\n throw err;\n }\n}\n\n/**\n * Write the mappings file to the `.portal-sync/` directory.\n * Creates the directory if it does not exist.\n */\nexport async function writeMappings(\n portalSyncDir: string,\n mappings: PortalMappings,\n): Promise<void> {\n const filePath = join(portalSyncDir, MAPPINGS_FILE);\n await mkdir(dirname(filePath), { recursive: true });\n await atomicWriteFile(filePath, JSON.stringify(mappings, null, 2) + \"\\n\");\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Slug derivation\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Derive a filesystem-safe slug from a resource name.\n *\n * - Lowercases the name\n * - Replaces non-alphanumeric characters (except hyphens) with hyphens\n * - Collapses consecutive hyphens\n * - Trims leading/trailing hyphens\n *\n * If the derived slug collides with an existing slug in `existingSlugs`,\n * a numeric suffix is appended (e.g., `home-2`, `home-3`).\n */\nexport function deriveSlug(\n name: string,\n existingSlugs: ReadonlySet<string> = new Set(),\n): string {\n const base =\n name\n .toLowerCase()\n .replace(/[^a-z0-9-]/g, \"-\")\n .replace(/-+/g, \"-\")\n .replace(/^-|-$/g, \"\") || \"unnamed\";\n\n if (!existingSlugs.has(base)) {\n return base;\n }\n\n // Collision detected — append a numeric suffix\n let counter = 2;\n while (existingSlugs.has(`${base}-${counter}`)) {\n counter++;\n }\n return `${base}-${counter}`;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Lookup helpers\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Resolve a slug to its server-side ID.\n * Returns `undefined` if the slug is not mapped.\n */\nexport function resolveSlugToId(\n mappings: PortalMappings,\n resourceType: MappedResourceType,\n slug: string,\n): number | undefined {\n return mappings[resourceType][slug];\n}\n\n/**\n * Resolve a server-side ID back to its slug.\n * Returns `undefined` if no slug maps to the given ID.\n */\nexport function resolveIdToSlug(\n mappings: PortalMappings,\n resourceType: MappedResourceType,\n id: number,\n): string | undefined {\n const entries = mappings[resourceType];\n for (const [slug, mappedId] of Object.entries(entries)) {\n if (mappedId === id) {\n return slug;\n }\n }\n return undefined;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Mutation helpers (return new objects — no mutation)\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Add or update a mapping entry for a given resource type.\n * Returns a new `PortalMappings` object (immutable update).\n */\nexport function updateMapping(\n mappings: PortalMappings,\n resourceType: MappedResourceType,\n slug: string,\n id: number,\n): PortalMappings {\n return {\n ...mappings,\n [resourceType]: {\n ...mappings[resourceType],\n [slug]: id,\n },\n };\n}\n\n/**\n * Remove a mapping entry for a given resource type.\n * Returns a new `PortalMappings` object (immutable update).\n * If the slug does not exist, the original mappings are returned unchanged.\n */\nexport function removeMapping(\n mappings: PortalMappings,\n resourceType: MappedResourceType,\n slug: string,\n): PortalMappings {\n if (!(slug in mappings[resourceType])) {\n return mappings;\n }\n\n const { [slug]: _removed, ...rest } = mappings[resourceType];\n return {\n ...mappings,\n [resourceType]: rest,\n };\n}\n","/**\n * Hash-based snapshot system for portal content sync.\n *\n * Tracks file hashes to detect local changes since the last pull,\n * enabling efficient push operations that only send modified files.\n *\n * Persisted in `.portal-sync/snapshot.json`.\n */\n\nimport { createHash } from \"node:crypto\";\nimport { readFile, readdir, mkdir, stat } from \"node:fs/promises\";\nimport { join, dirname, relative, sep } from \"node:path\";\n\nimport { atomicWriteFile } from \"./atomic-write.js\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Types\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Maximum number of concurrent file operations. */\nconst MAX_CONCURRENCY = 20;\n\n/** SHA-256 hex digest of a file's contents. */\nexport type FileHash = string;\n\n/** Map of relative file paths to their content hashes. */\nexport type FileHashMap = Record<string, FileHash>;\n\n/** Snapshot structure persisted to `.portal-sync/snapshot.json`. */\nexport interface Snapshot {\n /** Human-readable name of the portal definition. */\n readonly definition: string;\n /** Server-side ID of the portal definition. */\n readonly definition_id: number;\n /** ISO 8601 timestamp of when the snapshot was created. */\n readonly pulled_at: string;\n /** Map of relative file paths → SHA-256 hashes. */\n readonly files: FileHashMap;\n}\n\n/** Result of diffing current files against a snapshot. */\nexport interface SnapshotDiff {\n /** Files that exist on disk but not in the snapshot. */\n readonly new: readonly string[];\n /** Files that exist in both but have different hashes. */\n readonly changed: readonly string[];\n /** Files that exist in the snapshot but not on disk. */\n readonly deleted: readonly string[];\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Helpers\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Run async tasks with bounded concurrency to avoid exhausting file descriptors.\n */\nasync function mapWithLimit<T, R>(\n items: readonly T[],\n limit: number,\n fn: (item: T) => Promise<R>,\n): Promise<R[]> {\n const results: R[] = new Array(items.length);\n let index = 0;\n\n async function worker(): Promise<void> {\n while (index < items.length) {\n const i = index++;\n results[i] = await fn(items[i]!);\n }\n }\n\n const workers = Array.from({ length: Math.min(limit, items.length) }, () =>\n worker(),\n );\n await Promise.all(workers);\n return results;\n}\n\n/** Runtime check that a parsed value has the expected Snapshot shape. */\nfunction isSnapshot(value: unknown): value is Snapshot {\n if (!value || typeof value !== \"object\") return false;\n const s = value as Record<string, unknown>;\n return (\n typeof s.definition === \"string\" &&\n typeof s.definition_id === \"number\" &&\n typeof s.pulled_at === \"string\" &&\n typeof s.files === \"object\" &&\n s.files !== null\n );\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Hashing\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Compute the SHA-256 hash of a file's contents.\n * Returns the hex-encoded digest.\n */\nexport async function computeFileHash(filePath: string): Promise<FileHash> {\n const content = await readFile(filePath);\n return createHash(\"sha256\").update(content).digest(\"hex\");\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Read / Write\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst SNAPSHOT_FILE = \"snapshot.json\";\n\n/**\n * Read the snapshot file from the `.portal-sync/` directory.\n * Returns `null` if the file does not exist.\n */\nexport async function readSnapshot(\n portalSyncDir: string,\n): Promise<Snapshot | null> {\n try {\n const filePath = join(portalSyncDir, SNAPSHOT_FILE);\n const content = await readFile(filePath, \"utf-8\");\n const parsed: unknown = JSON.parse(content);\n if (!isSnapshot(parsed)) {\n throw new Error(`Malformed snapshot file: ${filePath}`);\n }\n return parsed;\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n return null;\n }\n throw err;\n }\n}\n\n/**\n * Write the snapshot file to the `.portal-sync/` directory.\n * Creates the directory if it does not exist.\n */\nexport async function writeSnapshot(\n portalSyncDir: string,\n snapshot: Snapshot,\n): Promise<void> {\n const filePath = join(portalSyncDir, SNAPSHOT_FILE);\n await mkdir(dirname(filePath), { recursive: true });\n await atomicWriteFile(filePath, JSON.stringify(snapshot, null, 2) + \"\\n\");\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Diff\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Recursively collect all files in a directory, returning paths\n * relative to `baseDir`.\n */\nasync function collectFiles(\n dir: string,\n baseDir: string = dir,\n): Promise<string[]> {\n const entries = await readdir(dir, { withFileTypes: true });\n const files: string[] = [];\n\n for (const entry of entries) {\n const fullPath = join(dir, entry.name);\n\n // Skip .portal-sync directory\n if (entry.name === \".portal-sync\") continue;\n\n if (entry.isSymbolicLink()) {\n // Resolve symlink to determine if it points to a file or directory\n const realStat = await stat(fullPath);\n if (realStat.isDirectory()) {\n files.push(...(await collectFiles(fullPath, baseDir)));\n } else if (realStat.isFile()) {\n files.push(relative(baseDir, fullPath).split(sep).join(\"/\"));\n }\n } else if (entry.isDirectory()) {\n files.push(...(await collectFiles(fullPath, baseDir)));\n } else {\n files.push(relative(baseDir, fullPath).split(sep).join(\"/\"));\n }\n }\n\n return files;\n}\n\n/**\n * Compare the current portal directory contents against a snapshot.\n *\n * Returns lists of new, changed, and deleted files (relative paths).\n */\nexport async function diffAgainstSnapshot(\n portalDir: string,\n snapshot: Snapshot,\n): Promise<SnapshotDiff> {\n const currentFiles = await collectFiles(portalDir);\n\n const snapshotPaths = new Set(Object.keys(snapshot.files));\n\n const newFiles: string[] = [];\n const changedFiles: string[] = [];\n\n // Check current files against snapshot (bounded concurrency)\n await mapWithLimit(currentFiles, MAX_CONCURRENCY, async (filePath) => {\n const hash = await computeFileHash(join(portalDir, filePath));\n\n if (!snapshotPaths.has(filePath)) {\n newFiles.push(filePath);\n } else if (snapshot.files[filePath] !== hash) {\n changedFiles.push(filePath);\n }\n });\n\n // Find deleted files (in snapshot but not on disk)\n const currentSet = new Set(currentFiles);\n const deletedFiles = [...snapshotPaths].filter(\n (filePath) => !currentSet.has(filePath),\n );\n\n return {\n new: [...newFiles].sort(),\n changed: [...changedFiles].sort(),\n deleted: [...deletedFiles].sort(),\n };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Build\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Build a fresh snapshot from the current portal directory contents.\n *\n * Hashes every file in `portalDir` (excluding `.portal-sync/`)\n * and records the definition metadata.\n */\nexport async function buildSnapshot(\n portalDir: string,\n definitionName: string,\n definitionId: number,\n): Promise<Snapshot> {\n const files = await collectFiles(portalDir);\n\n const hashEntries = await mapWithLimit(\n files,\n MAX_CONCURRENCY,\n async (filePath) => {\n const hash = await computeFileHash(join(portalDir, filePath));\n return [filePath, hash] as const;\n },\n );\n\n // Sort entries for deterministic output\n hashEntries.sort(([a], [b]) => a.localeCompare(b));\n\n const fileHashes: Record<string, FileHash> = {};\n for (const [path, hash] of hashEntries) {\n fileHashes[path] = hash;\n }\n\n return {\n definition: definitionName,\n definition_id: definitionId,\n pulled_at: new Date().toISOString(),\n files: fileHashes,\n };\n}\n","/**\n * Transformation utilities for converting Fluid OS API responses\n * into the local file format used by the portal content sync system.\n */\n\nimport type {\n FluidOsNavigationBasic,\n FluidOsNavigationItem,\n FluidOsProfile,\n FluidOsScreen,\n FluidOsTheme,\n} from \"@fluid-app/fluidos-api-client/types\";\nimport { toNumberArray, toStringArray } from \"@fluid-app/fluidos-api-client\";\nimport { deriveSlug } from \"./mappings\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// API types (aliases for readability)\n// ─────────────────────────────────────────────────────────────────────────────\n\ntype ApiScreen = FluidOsScreen;\ntype ApiTheme = FluidOsTheme;\ntype ApiNavigationBasic = FluidOsNavigationBasic;\ntype ApiNavigationItem = FluidOsNavigationItem;\ntype ApiProfile = FluidOsProfile;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Local file types\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface LocalScreen {\n readonly name: string;\n readonly component_tree: Record<string, unknown>[];\n}\n\nexport interface LocalTheme {\n readonly name: string;\n readonly config: Record<string, unknown>;\n readonly active: boolean;\n}\n\nexport interface LocalNavigationItem {\n /** Server-owned sync identity. New draft items omit it until push. */\n readonly id?: number;\n readonly icon: string | null;\n readonly label: string | null;\n readonly screen: string | null;\n readonly slug: string | null;\n readonly source: string;\n readonly position: number | null;\n readonly parent_id: number | null;\n readonly children: LocalNavigationItem[];\n}\n\nexport interface LocalNavigation {\n readonly name: string;\n readonly platform: string;\n readonly navigation_items: LocalNavigationItem[];\n}\n\nexport interface LocalProfile {\n readonly name: string;\n readonly default: boolean;\n readonly navigation: string | null;\n readonly mobile_navigation: string | null;\n readonly themes: string[];\n readonly permissions: {\n readonly ranks: number[];\n readonly roles: string[];\n readonly platform: string[];\n readonly countries: number[];\n };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Screen transformation\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Transform an API screen response into the local file format.\n * Strips server-only fields and normalizes `component_tree` to always be an array.\n */\nexport function transformScreen(screen: ApiScreen): LocalScreen {\n const name = screen.name ?? \"\";\n const rawTree = screen.component_tree;\n\n // Normalize component_tree to always be an array\n let componentTree: Record<string, unknown>[];\n if (rawTree == null) {\n componentTree = [];\n } else if (Array.isArray(rawTree)) {\n componentTree = rawTree as Record<string, unknown>[];\n } else {\n componentTree = [rawTree];\n }\n\n return { name, component_tree: componentTree };\n}\n\n/**\n * Derive a slug for a screen. Screens use the existing `slug` field from the API.\n * Falls back to slugifying the name if no slug exists.\n */\nexport function deriveScreenSlug(\n screen: ApiScreen,\n existingSlugs: Set<string>,\n): string {\n if (screen.slug) {\n // Sanitize: strip path-separator and other unsafe characters before using\n // the API slug as a filesystem path segment.\n const safe = screen.slug.replace(/[^a-z0-9_-]/gi, \"-\");\n if (!existingSlugs.has(safe)) {\n return safe;\n }\n return deriveSlug(safe, existingSlugs);\n }\n return deriveSlug(screen.name ?? \"unnamed\", existingSlugs);\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Theme transformation\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Transform an API theme response into the local file format.\n * Passes theme config through as-is (hex conversion is a separate ticket).\n */\nexport function transformTheme(theme: ApiTheme): LocalTheme {\n return {\n name: theme.name ?? \"\",\n config: theme.config ?? {},\n active: theme.active ?? false,\n };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Navigation transformation\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Build an ID → slug lookup map by inverting a slug → id mapping.\n */\nexport function buildIdToSlugMap(\n mappings: Record<string, number>,\n): Map<number, string> {\n const map = new Map<number, string>();\n for (const [slug, id] of Object.entries(mappings)) {\n map.set(id, slug);\n }\n return map;\n}\n\n/**\n * Transform API navigation items into local format.\n * Converts `screen_id` to `screen` slug reference. Keeps `id` for sync.\n */\nexport function transformNavigationItems(\n items: ApiNavigationItem[],\n screenIdToSlug: Map<number, string>,\n): LocalNavigationItem[] {\n return items.map((item) => ({\n id: item.id,\n icon: item.icon ?? null,\n label: item.label ?? null,\n screen: item.screen_id\n ? (screenIdToSlug.get(item.screen_id) ?? null)\n : null,\n slug: item.slug ?? null,\n source: item.source ?? \"user\",\n position: item.position ?? null,\n parent_id: item.parent_id ?? null,\n children: transformNavigationItems(item.children ?? [], screenIdToSlug),\n }));\n}\n\n/**\n * Transform an API navigation with its items into the local file format.\n */\nexport function transformNavigation(\n nav: ApiNavigationBasic,\n items: ApiNavigationItem[],\n screenIdToSlug: Map<number, string>,\n): LocalNavigation {\n return {\n name: nav.name ?? \"\",\n platform: nav.platform,\n navigation_items: transformNavigationItems(items, screenIdToSlug),\n };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Profile transformation\n// ─────────────────────────────────────────────────────────────────────────────\n\n/** Typed alias for call-site clarity. */\nexport const buildNavigationIdToSlugMap: (\n mappings: Record<string, number>,\n) => Map<number, string> = buildIdToSlugMap;\n\n/** Typed alias for call-site clarity. */\nexport const buildThemeIdToSlugMap: (\n mappings: Record<string, number>,\n) => Map<number, string> = buildIdToSlugMap;\n\n/**\n * Transform an API profile response into the local file format.\n * Converts navigation_id, mobile_navigation_id, and theme_ids to slug references.\n *\n * TODO (REP-842): Country/rank IDs in profile permissions need to be translated\n * to human-readable names via the main Fluid API. For now, the permission arrays\n * are written as-is with integer IDs.\n */\nexport function transformProfile(\n profile: ApiProfile,\n navIdToSlug: Map<number, string>,\n themeIdToSlug: Map<number, string>,\n): LocalProfile {\n const navigation = profile.navigation\n ? (navIdToSlug.get(profile.navigation.id) ?? null)\n : null;\n\n const mobileNavigation = profile.mobile_navigation\n ? (navIdToSlug.get(profile.mobile_navigation.id) ?? null)\n : null;\n\n const themes = (profile.themes ?? [])\n .map((t) => themeIdToSlug.get(t.id))\n .filter((slug): slug is string => slug != null);\n\n const permissions = profile.permissions ?? {};\n\n return {\n name: profile.name ?? \"\",\n default: profile.default ?? false,\n navigation,\n mobile_navigation: mobileNavigation,\n themes,\n permissions: {\n ranks: toNumberArray(permissions.ranks) ?? [],\n roles: toStringArray(permissions.roles) ?? [],\n platform: toStringArray(permissions.platform) ?? [],\n countries: toNumberArray(permissions.countries) ?? [],\n },\n };\n}\n","/**\n * `fluid portal pull` command\n *\n * Pulls a Fluid OS definition's resources (screens, themes, navigations,\n * profiles) from the API and writes them to the local `portal/` directory\n * as JSON files, along with `.portal-sync/` metadata for push diffing.\n */\n\nimport { Command } from \"commander\";\nimport { resolveDefinition } from \"./pull-definition.js\";\nimport { getAuthToken, getActiveProfile } from \"@fluid-app/fluid-cli\";\nimport type {\n FluidOsDefinitionBasic,\n FluidOsNavigationItem,\n} from \"@fluid-app/fluidos-api-client/types\";\nimport chalk from \"chalk\";\nimport ora from \"ora\";\nimport pLimit from \"p-limit\";\nimport { join } from \"node:path\";\nimport { mkdir, writeFile, rm, rename } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport prompts from \"prompts\";\nimport {\n createFluidOsClient,\n fluidOsApi,\n type FluidOsClient,\n} from \"../api/fluidos.js\";\n\nimport { deriveSlug, writeMappings } from \"../utils/mappings.js\";\nimport type { PortalMappings } from \"../utils/mappings.js\";\nimport {\n readSnapshot,\n diffAgainstSnapshot,\n buildSnapshot,\n writeSnapshot,\n} from \"../utils/snapshot.js\";\nimport {\n transformScreen,\n deriveScreenSlug,\n transformTheme,\n transformNavigation,\n transformProfile,\n buildIdToSlugMap,\n buildNavigationIdToSlugMap,\n buildThemeIdToSlugMap,\n} from \"../utils/transform.js\";\nimport { assertStandalonePortalRemoteActionAllowed } from \"../utils/standalone-portal.js\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Types\n// ─────────────────────────────────────────────────────────────────────────────\n\ntype ApiDefinitionBasic = FluidOsDefinitionBasic;\n\ninterface PullOptions {\n app?: string;\n /** Definition id. Same reason `clone` has one: names repeat, and a\n * name lookup silently takes the first match. */\n id?: string;\n force?: boolean;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Constants\n// ─────────────────────────────────────────────────────────────────────────────\n\nconst PORTAL_DIR = \"portal\";\nconst PORTAL_SYNC_DIR = \".portal-sync\";\nconst PAGE_LIMIT = 500;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Helpers\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Create an authenticated FluidOS client using the stored CLI profile.\n */\nfunction createClient(): FluidOsClient {\n const token = getAuthToken();\n if (!token) {\n const profile = getActiveProfile();\n if (!profile) {\n throw new Error(\n \"Not logged in. Run \" + chalk.cyan(\"fluid login\") + \" first.\",\n );\n }\n throw new Error(\n \"No auth token found for profile \" +\n chalk.cyan(profile.name) +\n \". Run \" +\n chalk.cyan(\"fluid login\") +\n \" to re-authenticate.\",\n );\n }\n\n const baseUrl = process.env[\"FLUID_API_BASE\"] ?? \"https://api.fluid.app\";\n\n return createFluidOsClient({\n baseUrl,\n token,\n });\n}\n\n/**\n * Select a definition interactively or by name.\n */\nasync function fetchAllDefinitions(\n client: FluidOsClient,\n): Promise<ApiDefinitionBasic[]> {\n const all: ApiDefinitionBasic[] = [];\n let page = 1;\n\n while (true) {\n const response = await fluidOsApi.listDefinitions(client, {\n page,\n per_page: PAGE_LIMIT,\n });\n const batch = response.definitions ?? [];\n all.push(...batch);\n\n const totalPages = response.meta?.total_pages ?? 1;\n if (page >= totalPages || batch.length === 0) break;\n page++;\n }\n\n return all;\n}\n\nasync function selectDefinition(\n client: FluidOsClient,\n appName?: string,\n rawDefinitionId?: string,\n): Promise<ApiDefinitionBasic> {\n const definitions = await fetchAllDefinitions(client);\n\n if (definitions.length === 0) {\n throw new Error(\n \"No Fluid OS definitions found. Create one in the admin dashboard first.\",\n );\n }\n\n const resolved = resolveDefinition(definitions, appName, rawDefinitionId);\n if (resolved) return resolved;\n\n // Interactive selector\n const { definitionId } = await prompts({\n type: \"select\",\n name: \"definitionId\",\n message: \"Select a Fluid OS definition to pull\",\n choices: definitions.map((d) => ({\n title: d.name ?? \"(unnamed)\",\n value: d.id,\n })),\n });\n\n if (definitionId == null) {\n throw new Error(\"No definition selected.\");\n }\n\n const selected = definitions.find((d) => d.id === definitionId);\n if (!selected) {\n throw new Error(\"Selected definition not found.\");\n }\n\n return selected;\n}\n\n/**\n * Check for definition switching conflicts.\n * Returns true if it's safe to proceed.\n */\nasync function checkDefinitionSwitch(\n cwd: string,\n newDefinitionId: number,\n newDefinitionName: string,\n force: boolean,\n): Promise<boolean> {\n const portalDir = join(cwd, PORTAL_DIR);\n const portalSyncDir = join(cwd, PORTAL_SYNC_DIR);\n\n // No existing portal directory — always safe\n if (!existsSync(portalDir)) {\n return true;\n }\n\n // Read existing snapshot to see what definition was pulled\n const snapshot = await readSnapshot(portalSyncDir);\n if (!snapshot) {\n // No snapshot means no prior pull — safe to overwrite\n return true;\n }\n\n // Same definition — this is a refresh\n if (snapshot.definition_id === newDefinitionId) {\n return true;\n }\n\n // Different definition — check for local changes\n const diff = await diffAgainstSnapshot(portalDir, snapshot);\n const hasChanges =\n diff.new.length > 0 || diff.changed.length > 0 || diff.deleted.length > 0;\n\n if (hasChanges && !force) {\n console.log();\n console.log(\n chalk.yellow(\"Warning:\") +\n \" Switching from \" +\n chalk.cyan(snapshot.definition) +\n \" to \" +\n chalk.cyan(newDefinitionName) +\n \" with unpushed local changes.\",\n );\n console.log();\n console.log(\" Modified: \" + diff.changed.length + \" file(s)\");\n console.log(\" New: \" + diff.new.length + \" file(s)\");\n console.log(\" Deleted: \" + diff.deleted.length + \" file(s)\");\n console.log();\n console.log(\n \"Use \" +\n chalk.cyan(\"--force\") +\n \" to discard local changes and switch definitions.\",\n );\n return false;\n }\n\n return true;\n}\n\n/**\n * Write a JSON file to the portal directory, creating subdirectories as needed.\n */\nasync function writePortalFile(\n portalDir: string,\n relativePath: string,\n data: unknown,\n): Promise<void> {\n const filePath = join(portalDir, relativePath);\n const dir = join(filePath, \"..\");\n await mkdir(dir, { recursive: true });\n await writeFile(filePath, JSON.stringify(data, null, 2) + \"\\n\", \"utf-8\");\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Command\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport const pullCommand: Command = new Command(\"pull\")\n .description(\n \"Pull a Fluid OS definition's resources to the local portal/ directory\",\n )\n .option(\"--app <name>\", \"Definition name (skips interactive selector)\")\n .option(\"--id <id>\", \"Definition id (avoids ambiguity when names repeat)\")\n .option(\"--force\", \"Overwrite local changes when switching definitions\")\n .action(async (options: PullOptions) => {\n const cwd = process.cwd();\n await assertStandalonePortalRemoteActionAllowed(cwd, \"portal pull\");\n\n console.log();\n console.log(chalk.blue.bold(\"Fluid Portal Pull\"));\n console.log();\n\n // ── Authenticate ──────────────────────────────────────────────────\n const spinner = ora();\n spinner.start(\"Authenticating...\");\n\n let client: FluidOsClient;\n try {\n client = createClient();\n spinner.succeed(\"Authenticated\");\n } catch (err) {\n spinner.fail(\"Authentication failed\");\n console.log();\n console.log(\n chalk.red(\"Error:\") +\n \" \" +\n (err instanceof Error ? err.message : String(err)),\n );\n console.log();\n process.exit(1);\n }\n\n // ── Select definition ─────────────────────────────────────────────\n let definition: ApiDefinitionBasic;\n try {\n definition = await selectDefinition(client, options.app, options.id);\n } catch (err) {\n console.log();\n console.log(\n chalk.red(\"Error:\") +\n \" \" +\n (err instanceof Error ? err.message : String(err)),\n );\n console.log();\n process.exit(1);\n }\n\n const definitionId = definition.id;\n const definitionName = definition.name ?? \"(unnamed)\";\n\n console.log(\n chalk.gray(\"Definition: \") +\n chalk.white(definitionName) +\n chalk.gray(` (ID: ${definitionId})`),\n );\n console.log();\n\n // ── Check definition switch ───────────────────────────────────────\n let canProceed: boolean;\n try {\n canProceed = await checkDefinitionSwitch(\n cwd,\n definitionId,\n definitionName,\n options.force ?? false,\n );\n } catch (err) {\n console.log();\n console.log(\n chalk.red(\"Error:\") +\n \" \" +\n (err instanceof Error ? err.message : String(err)),\n );\n console.log();\n process.exit(1);\n }\n if (!canProceed) {\n process.exit(1);\n }\n\n // ── Fetch all resources in parallel ───────────────────────────────\n spinner.start(\"Fetching resources...\");\n\n let screenBasics: NonNullable<\n Awaited<ReturnType<typeof fluidOsApi.listScreens>>[\"screens\"]\n >;\n let themes: NonNullable<\n Awaited<ReturnType<typeof fluidOsApi.listThemes>>[\"themes\"]\n >;\n let navigations: NonNullable<\n Awaited<ReturnType<typeof fluidOsApi.listNavigations>>[\"navigations\"]\n >;\n let profiles: NonNullable<\n Awaited<ReturnType<typeof fluidOsApi.listProfiles>>[\"profiles\"]\n >;\n let screens: NonNullable<\n Awaited<ReturnType<typeof fluidOsApi.getScreen>>[\"screen\"]\n >[];\n const navigationItemsMap = new Map<number, FluidOsNavigationItem[]>();\n\n try {\n const [\n screensResponse,\n themesResponse,\n navigationsResponse,\n profilesResponse,\n ] = await Promise.all([\n fluidOsApi.listScreens(client, definitionId, {\n per_page: PAGE_LIMIT,\n }),\n fluidOsApi.listThemes(client, definitionId, {\n per_page: PAGE_LIMIT,\n }),\n fluidOsApi.listNavigations(client, definitionId, {\n per_page: PAGE_LIMIT,\n }),\n fluidOsApi.listProfiles(client, definitionId, {\n per_page: PAGE_LIMIT,\n }),\n ]);\n\n screenBasics = screensResponse.screens ?? [];\n themes = themesResponse.themes ?? [];\n navigations = navigationsResponse.navigations ?? [];\n profiles = profilesResponse.profiles ?? [];\n\n // Warn if any list hits the page limit\n if (screenBasics.length === PAGE_LIMIT) {\n console.warn(\n chalk.yellow(\n `Warning: screen count hit the ${PAGE_LIMIT}-item limit; some screens may be missing.`,\n ),\n );\n }\n if (themes.length === PAGE_LIMIT) {\n console.warn(\n chalk.yellow(\n `Warning: theme count hit the ${PAGE_LIMIT}-item limit; some themes may be missing.`,\n ),\n );\n }\n if (navigations.length === PAGE_LIMIT) {\n console.warn(\n chalk.yellow(\n `Warning: navigation count hit the ${PAGE_LIMIT}-item limit; some navigations may be missing.`,\n ),\n );\n }\n if (profiles.length === PAGE_LIMIT) {\n console.warn(\n chalk.yellow(\n `Warning: profile count hit the ${PAGE_LIMIT}-item limit; some profiles may be missing.`,\n ),\n );\n }\n\n spinner.text = \"Fetching screen details...\";\n\n // Limit concurrency to avoid hitting API rate limits\n const limit = pLimit(10);\n\n // Fetch full screen details (list endpoint doesn't include component_tree)\n screens = await Promise.all(\n screenBasics.map((s) =>\n limit(async () => {\n const res = await fluidOsApi.getScreen(client, definitionId, s.id);\n if (!res.screen) {\n throw new Error(`Failed to fetch details for screen ID ${s.id}`);\n }\n return res.screen;\n }),\n ),\n );\n\n // Fetch navigation items for each navigation.\n // NOTE: The list FluidOS navigation items endpoint does not support\n // pagination query parameters (per_page/page). Navigation items are\n // returned as a nested tree structure and are not paginated.\n spinner.text = \"Fetching navigation items...\";\n await Promise.all(\n navigations.map((nav) =>\n limit(async () => {\n const res = await fluidOsApi.listNavigationItems(\n client,\n definitionId,\n nav.id,\n );\n navigationItemsMap.set(nav.id, res.navigation_items ?? []);\n }),\n ),\n );\n\n spinner.succeed(\n `Fetched ${screens.length} screen(s), ${themes.length} theme(s), ${navigations.length} navigation(s), ${profiles.length} profile(s)`,\n );\n } catch (err) {\n spinner.fail(\"Failed to fetch resources\");\n console.log();\n console.log(\n chalk.red(\"Error:\") +\n \" \" +\n (err instanceof Error ? err.message : String(err)),\n );\n console.log();\n process.exit(1);\n }\n\n // ── Build slug mappings ───────────────────────────────────────────\n spinner.start(\"Transforming resources...\");\n\n // Screen mappings: use existing slug field when available\n const screenSlugs = new Set<string>();\n const screenMappings: Record<string, number> = {};\n for (const screen of screens) {\n const slug = deriveScreenSlug(screen, screenSlugs);\n screenSlugs.add(slug);\n screenMappings[slug] = screen.id;\n }\n\n // Theme mappings: slugify from name\n const themeSlugs = new Set<string>();\n const themeMappings: Record<string, number> = {};\n for (const theme of themes) {\n const slug = deriveSlug(theme.name ?? \"unnamed\", themeSlugs);\n themeSlugs.add(slug);\n themeMappings[slug] = theme.id;\n }\n\n // Navigation mappings: slugify from name\n const navSlugs = new Set<string>();\n const navMappings: Record<string, number> = {};\n for (const nav of navigations) {\n const slug = deriveSlug(nav.name ?? \"unnamed\", navSlugs);\n navSlugs.add(slug);\n navMappings[slug] = nav.id;\n }\n\n // Profile mappings: slugify from name\n const profileSlugs = new Set<string>();\n const profileMappings: Record<string, number> = {};\n for (const profile of profiles) {\n const slug = deriveSlug(profile.name ?? \"unnamed\", profileSlugs);\n profileSlugs.add(slug);\n profileMappings[slug] = profile.id;\n }\n\n // Build reverse-lookup maps for ID → slug references\n const screenIdToSlug = buildIdToSlugMap(screenMappings);\n const navIdToSlug = buildNavigationIdToSlugMap(navMappings);\n const themeIdToSlug = buildThemeIdToSlugMap(themeMappings);\n const profileIdToSlug = buildIdToSlugMap(profileMappings);\n\n spinner.succeed(\"Resources transformed\");\n\n // ── Clean and write files ─────────────────────────────────────────\n spinner.start(\"Writing files...\");\n\n const portalDir = join(cwd, PORTAL_DIR);\n const portalSyncDir = join(cwd, PORTAL_SYNC_DIR);\n\n // Write to a temporary directory first, then atomically swap into place.\n // This ensures the existing portal/ directory is only removed once all\n // new content has been successfully written.\n const tmpPortalDir = portalDir + \".tmp\";\n\n try {\n // Clean up any leftover temp directory from a previous failed run\n if (existsSync(tmpPortalDir)) {\n await rm(tmpPortalDir, { recursive: true });\n }\n await mkdir(tmpPortalDir, { recursive: true });\n\n // Write definition.json\n await writePortalFile(tmpPortalDir, \"definition.json\", {\n name: definitionName,\n });\n\n // Write screens\n for (const screen of screens) {\n const slug = screenIdToSlug.get(screen.id)!;\n const local = transformScreen(screen);\n await writePortalFile(tmpPortalDir, `screens/${slug}.json`, local);\n }\n\n // Write themes\n for (const theme of themes) {\n const slug = themeIdToSlug.get(theme.id)!;\n const local = transformTheme(theme);\n await writePortalFile(tmpPortalDir, `themes/${slug}.json`, local);\n }\n\n // Write navigations\n for (const nav of navigations) {\n const slug = navIdToSlug.get(nav.id)!;\n const items = navigationItemsMap.get(nav.id) ?? [];\n const local = transformNavigation(nav, items, screenIdToSlug);\n await writePortalFile(tmpPortalDir, `navigations/${slug}.json`, local);\n }\n\n // Write profiles\n for (const profile of profiles) {\n const slug = profileIdToSlug.get(profile.id);\n if (slug) {\n const local = transformProfile(profile, navIdToSlug, themeIdToSlug);\n await writePortalFile(tmpPortalDir, `profiles/${slug}.json`, local);\n }\n }\n\n // Atomic swap: remove old directory, rename temp into place\n if (existsSync(portalDir)) {\n await rm(portalDir, { recursive: true });\n }\n await rename(tmpPortalDir, portalDir);\n } catch (err) {\n // Clean up temp directory on failure\n if (existsSync(tmpPortalDir)) {\n await rm(tmpPortalDir, { recursive: true }).catch(() => {});\n }\n spinner.fail(\"Failed to write files\");\n console.log();\n console.log(\n chalk.red(\"Error:\") +\n \" \" +\n (err instanceof Error ? err.message : String(err)),\n );\n console.log();\n process.exit(1);\n }\n\n // ── Write metadata (mappings + snapshot) ────────────────────────────\n // Separated from the file-write block so that a metadata failure does\n // not incorrectly report total failure when portal files are already\n // on disk after the atomic swap.\n try {\n const mappings: PortalMappings = {\n definition: { name: definitionName, id: definitionId },\n screens: screenMappings,\n themes: themeMappings,\n navigations: navMappings,\n profiles: profileMappings,\n countries: {},\n ranks: {},\n };\n await writeMappings(portalSyncDir, mappings);\n\n const snapshot = await buildSnapshot(\n portalDir,\n definitionName,\n definitionId,\n );\n await writeSnapshot(portalSyncDir, snapshot);\n\n spinner.succeed(\"Files written\");\n } catch (err) {\n spinner.warn(\"Files written (metadata sync failed)\");\n console.log();\n console.log(\n chalk.yellow(\"Warning:\") +\n \" Portal files were written successfully, but metadata sync failed.\" +\n \" Run \" +\n chalk.cyan(\"fluid portal pull\") +\n \" again to regenerate metadata.\",\n );\n console.log(\n chalk.gray(\" Detail: \") +\n (err instanceof Error ? err.message : String(err)),\n );\n console.log();\n }\n\n // ── Summary ───────────────────────────────────────────────────────\n console.log();\n console.log(chalk.green.bold(\"Pull complete!\"));\n console.log();\n console.log(chalk.gray(\" portal/definition.json\"));\n console.log(\n chalk.gray(` portal/screens/ `) +\n chalk.white(`${screens.length} file(s)`),\n );\n console.log(\n chalk.gray(` portal/themes/ `) +\n chalk.white(`${themes.length} file(s)`),\n );\n console.log(\n chalk.gray(` portal/navigations/ `) +\n chalk.white(`${navigations.length} file(s)`),\n );\n console.log(\n chalk.gray(` portal/profiles/ `) +\n chalk.white(`${profiles.length} file(s)`),\n );\n console.log(\n chalk.gray(` .portal-sync/ `) +\n chalk.white(\"mappings.json + snapshot.json\"),\n );\n console.log();\n });\n"],"mappings":";;;;;;;;;;;;;AAGA,MAAa,2BAA2B;;;;;;AAOxC,eAAsB,0CACpB,kBACA,QACe;AACf,KAAI;AACF,QAAM,MAAM,KAAK,kBAAkB,yBAAyB,CAAC;UACtD,OAAO;AACd,MACE,OAAO,UAAU,YACjB,UAAU,QACV,UAAU,SACV,MAAM,SAAS,SAEf;AAEF,QAAM;;AAGR,OAAM,IAAI,MACR,+OAA+O,OAAO,eACvP;;;;;;;;;;;;;;;ACXH,SAAgB,kBACd,aACA,SACA,iBACyB;AACzB,KAAI,oBAAoB,KAAA,GAAW;AACjC,MAAI,CAAC,aAAa,KAAK,gBAAgB,CACrC,OAAM,IAAI,MAAM,kCAAkC;EAEpD,MAAM,eAAe,OAAO,SAAS,iBAAiB,GAAG;EACzD,MAAM,QAAQ,YAAY,MAAM,MAAM,EAAE,OAAO,aAAa;AAC5D,MAAI,CAAC,MACH,OAAM,IAAI,MAAM,+BAA+B,aAAa,GAAG;AAEjE,SAAO;;AAET,KAAI,SAAS;EACX,MAAM,QAAQ,YAAY,MACvB,MAAM,EAAE,MAAM,aAAa,KAAK,QAAQ,aAAa,CACvD;AACD,MAAI,CAAC,OAAO;GACV,MAAM,iBAAiB,YACpB,KAAK,MAAM,MAAM,KAAK,EAAE,QAAQ,YAAY,CAAC,CAC7C,KAAK,KAAK;AACb,SAAM,IAAI,MACR,iCAAiC,QAAQ,gBAAgB,iBAC1D;;AAEH,SAAO;;AAET,QAAO;;;;;;;;;ACDT,SAAgB,eAAe,OAA8B;AAC3D,KAAI,CAAC,SAAS,OAAO,UAAU,YAAY,MAAM,QAAQ,MAAM,CAC7D,QAAO;AAGT,QAAO;;;;;;;;AAST,SAAgB,eAAe,MAAkC;AAC/D,KAAI,CAAC,KACH,QAAO;AAGT,QAAQ,KAAK,UAAU;;;;;;;ACOzB,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;;;;;AChHL,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;;;;;AClEL,MAAa,qBAAqB,EAChC,iBAAiB,SACf,KAAK,UAAU,OAAO,MAAM,UAC1B,OAAO,UAAU,WAAW,MAAM,UAAU,GAAG,MAChD,EACJ;AC1BqB,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,MAAaA,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,MAAaC,kBAAgB,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;;;;AC5RH,SAAS,aAAa,OAAyB;AAC7C,QACG,iBAAiB,gBAAgB,MAAM,SAAS,gBAChD,iBAAiB,SAAS,MAAM,SAAS;;AAI9C,SAAgB,0BACd,QACQ;CACR,MAAM,SAASC,eAAa;EAC1B,MAAM,YAAa,MAAM,OAAO,gBAAgB,IAAK,KAAA;EACrD,SAAS,OAAO;EAChB,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;;;;ACxCT,SAAgB,uBACd,OAC6B;AAC7B,KAAI,UAAU,KAAM,QAAO;CAE3B,MAAM,aAAa,KAAK,UAAU,MAAM;AACxC,KAAI,eAAe,KAAA,EACjB,OAAM,IAAI,UAAU,gDAAgD;AAGtE,QAAO,KAAK,MAAM,WAAW;;AAG/B,SAAgB,cAAc,OAAsC;AAClE,KAAI,CAAC,MAAM,QAAQ,MAAM,CAAE,QAAO,KAAA;AAClC,QAAO,MAAM,QAAQ,SAAyB,OAAO,SAAS,SAAS;;AAGzE,SAAgB,cAAc,OAAsC;AAClE,KAAI,CAAC,MAAM,QAAQ,MAAM,CAAE,QAAO,KAAA;AAClC,QAAO,MAAM,QAAQ,SAAyB,OAAO,SAAS,SAAS;;;;ACEzE,SAAgB,oBAAoB,SAGlB;AAChB,QAAO,0BAA0B;EAC/B,SAAS,QAAQ;EACjB,oBAAoB,QAAQ;EAC7B,CAAC;;AAGJ,MAAa,aAAa;CACxB,kBACE,QACA,UAGGC,gCAAoC;EAAE;EAAQ;EAAO,CAAC;CAC3D,cACE,QACA,cACA,UAEAC,4BAAgC;EAC9B;EACA,MAAM,EAAE,eAAe,cAAc;EACrC;EACD,CAAC;CACJ,YACE,QACA,cACA,OAEAC,0BAA8B;EAC5B;EACA,MAAM;GAAE,eAAe;GAAc;GAAI;EAC1C,CAAC;CACJ,eACE,QACA,cACA,SAEAC,6BAAiC;EAC/B;EACA;EACA,MAAM,EAAE,eAAe,cAAc;EACtC,CAAC;CACJ,eACE,QACA,cACA,IACA,SAEAC,6BAAiC;EAC/B;EACA;EACA,MAAM;GAAE,eAAe;GAAc;GAAI;EAC1C,CAAC;CACJ,eACE,QACA,cACA,OAEAC,6BAAiC;EAC/B;EACA,MAAM;GAAE,eAAe;GAAc;GAAI;EAC1C,CAAC;CACJ,aACE,QACA,cACA,UAEAC,2BAA+B;EAC7B;EACA,MAAM,EAAE,eAAe,cAAc;EACrC;EACD,CAAC;CACJ,cACE,QACA,cACA,SAEAC,4BAAgC;EAC9B;EACA;EACA,MAAM,EAAE,eAAe,cAAc;EACtC,CAAC;CACJ,cACE,QACA,cACA,IACA,SAEAC,4BAAgC;EAC9B;EACA;EACA,MAAM;GAAE,eAAe;GAAc;GAAI;EAC1C,CAAC;CACJ,cACE,QACA,cACA,OAEAC,4BAAgC;EAC9B;EACA,MAAM;GAAE,eAAe;GAAc;GAAI;EAC1C,CAAC;CACJ,kBACE,QACA,cACA,UAIAC,gCAAoC;EAClC;EACA,MAAM,EAAE,eAAe,cAAc;EACrC;EACD,CAAC;CACJ,mBACE,QACA,cACA,SAIAC,iCAAqC;EACnC;EACA;EACA,MAAM,EAAE,eAAe,cAAc;EACtC,CAAC;CACJ,mBACE,QACA,cACA,IACA,SAIAC,iCAAqC;EACnC;EACA;EACA,MAAM;GAAE,eAAe;GAAc;GAAI;EAC1C,CAAC;CACJ,mBACE,QACA,cACA,OAIAC,iCAAqC;EACnC;EACA,MAAM;GAAE,eAAe;GAAc;GAAI;EAC1C,CAAC;CACJ,sBACE,QACA,cACA,iBAIAC,oCAAwC;EACtC;EACA,MAAM;GAAE,eAAe;GAAc,eAAe;GAAc;EACnE,CAAC;CACJ,uBACE,QACA,cACA,cACA,SAIAC,qCAAyC;EACvC;EACA;EACA,MAAM;GAAE,eAAe;GAAc,eAAe;GAAc;EACnE,CAAC;CACJ,uBACE,QACA,cACA,cACA,IACA,SAIAC,qCAAyC;EACvC;EACA;EACA,MAAM;GACJ,eAAe;GACf;GACA,eAAe;GAChB;EACF,CAAC;CACJ,uBACE,QACA,cACA,cACA,OAIAC,qCAAyC;EACvC;EACA,MAAM;GACJ,eAAe;GACf;GACA,eAAe;GAChB;EACF,CAAC;CACJ,eACE,QACA,cACA,UAEAC,6BAAiC;EAC/B;EACA,MAAM,EAAE,eAAe,cAAc;EACrC;EACD,CAAC;CACJ,gBACE,QACA,cACA,SAEAC,8BAAkC;EAChC;EACA;EACA,MAAM,EAAE,eAAe,cAAc;EACtC,CAAC;CACJ,gBACE,QACA,cACA,IACA,SAEAC,8BAAkC;EAChC;EACA;EACA,MAAM;GAAE,eAAe;GAAc;GAAI;EAC1C,CAAC;CACJ,gBACE,QACA,cACA,OAEAC,8BAAkC;EAChC;EACA,MAAM;GAAE,eAAe;GAAc;GAAI;EAC1C,CAAC;CACJ,eACE,QACA,cACA,UAEAC,6BAAiC;EAC/B;EACA,MAAM,EAAE,eAAe,cAAc;EACrC;EACD,CAAC;CACJ,gBACE,QACA,iBAEAC,8BAAkC;EAChC;EACA,MAAM,EAAE,eAAe,cAAc;EACtC,CAAC;CACJ,gBACE,QACA,cACA,IACA,SAEAC,8BAAkC;EAChC;EACA;EACA,MAAM;GAAE,eAAe;GAAc;GAAI;EAC1C,CAAC;CACL;;;;;;;;;;;;;;;;ACtSD,eAAsB,gBACpB,UACA,MACe;CAEf,MAAM,MAAM,KADA,QAAQ,SAAS,EACP,QAAQ,YAAY,EAAE,CAAC,SAAS,MAAM,GAAG;AAC/D,KAAI;AACF,QAAM,UAAU,KAAK,MAAM,QAAQ;AACnC,QAAM,OAAO,KAAK,SAAS;UACpB,KAAK;AAEZ,QAAM,OAAO,IAAI,CAAC,YAAY,GAAG;AACjC,QAAM;;;;;;;;;;;;;;;ACuBV,SAAS,WAAW,OAAyC;AAC3D,KAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;CAChD,MAAM,IAAI;CAGV,MAAM,MAAM,EAAE;AACd,KAAI,CAAC,OAAO,OAAO,QAAQ,SAAU,QAAO;AAC5C,KAAI,OAAO,IAAI,SAAS,YAAY,OAAO,IAAI,OAAO,SAAU,QAAO;AAEvE,QACE,OAAO,EAAE,YAAY,YACrB,EAAE,YAAY,QACd,OAAO,EAAE,WAAW,YACpB,EAAE,WAAW,QACb,OAAO,EAAE,gBAAgB,YACzB,EAAE,gBAAgB,QAClB,OAAO,EAAE,aAAa,YACtB,EAAE,aAAa,QACf,OAAO,EAAE,cAAc,YACvB,EAAE,cAAc,QAChB,OAAO,EAAE,UAAU,YACnB,EAAE,UAAU;;AAQhB,MAAM,gBAAgB;;;;;AAMtB,eAAsB,aACpB,eACgC;AAChC,KAAI;EACF,MAAM,WAAW,KAAK,eAAe,cAAc;EACnD,MAAM,UAAU,MAAM,SAAS,UAAU,QAAQ;EACjD,MAAM,SAAkB,KAAK,MAAM,QAAQ;AAC3C,MAAI,CAAC,WAAW,OAAO,CACrB,OAAM,IAAI,MAAM,4BAA4B,WAAW;AAEzD,SAAO;UACA,KAAK;AACZ,MAAK,IAA8B,SAAS,SAC1C,QAAO;AAET,QAAM;;;;;;;AAQV,eAAsB,cACpB,eACA,UACe;CACf,MAAM,WAAW,KAAK,eAAe,cAAc;AACnD,OAAM,MAAM,QAAQ,SAAS,EAAE,EAAE,WAAW,MAAM,CAAC;AACnD,OAAM,gBAAgB,UAAU,KAAK,UAAU,UAAU,MAAM,EAAE,GAAG,KAAK;;;;;;;;;;;;;AAkB3E,SAAgB,WACd,MACA,gCAAqC,IAAI,KAAK,EACtC;CACR,MAAM,OACJ,KACG,aAAa,CACb,QAAQ,eAAe,IAAI,CAC3B,QAAQ,OAAO,IAAI,CACnB,QAAQ,UAAU,GAAG,IAAI;AAE9B,KAAI,CAAC,cAAc,IAAI,KAAK,CAC1B,QAAO;CAIT,IAAI,UAAU;AACd,QAAO,cAAc,IAAI,GAAG,KAAK,GAAG,UAAU,CAC5C;AAEF,QAAO,GAAG,KAAK,GAAG;;;;;;AAWpB,SAAgB,gBACd,UACA,cACA,MACoB;AACpB,QAAO,SAAS,cAAc;;;;;;AAOhC,SAAgB,gBACd,UACA,cACA,IACoB;CACpB,MAAM,UAAU,SAAS;AACzB,MAAK,MAAM,CAAC,MAAM,aAAa,OAAO,QAAQ,QAAQ,CACpD,KAAI,aAAa,GACf,QAAO;;;;;;AAcb,SAAgB,cACd,UACA,cACA,MACA,IACgB;AAChB,QAAO;EACL,GAAG;GACF,eAAe;GACd,GAAG,SAAS;IACX,OAAO;GACT;EACF;;;;;;;AAQH,SAAgB,cACd,UACA,cACA,MACgB;AAChB,KAAI,EAAE,QAAQ,SAAS,eACrB,QAAO;CAGT,MAAM,GAAG,OAAO,UAAU,GAAG,SAAS,SAAS;AAC/C,QAAO;EACL,GAAG;GACF,eAAe;EACjB;;;;;;;;;;;;;ACrNH,MAAM,kBAAkB;;;;AAqCxB,eAAe,aACb,OACA,OACA,IACc;CACd,MAAM,UAAe,IAAI,MAAM,MAAM,OAAO;CAC5C,IAAI,QAAQ;CAEZ,eAAe,SAAwB;AACrC,SAAO,QAAQ,MAAM,QAAQ;GAC3B,MAAM,IAAI;AACV,WAAQ,KAAK,MAAM,GAAG,MAAM,GAAI;;;CAIpC,MAAM,UAAU,MAAM,KAAK,EAAE,QAAQ,KAAK,IAAI,OAAO,MAAM,OAAO,EAAE,QAClE,QAAQ,CACT;AACD,OAAM,QAAQ,IAAI,QAAQ;AAC1B,QAAO;;;AAIT,SAAS,WAAW,OAAmC;AACrD,KAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;CAChD,MAAM,IAAI;AACV,QACE,OAAO,EAAE,eAAe,YACxB,OAAO,EAAE,kBAAkB,YAC3B,OAAO,EAAE,cAAc,YACvB,OAAO,EAAE,UAAU,YACnB,EAAE,UAAU;;;;;;AAYhB,eAAsB,gBAAgB,UAAqC;CACzE,MAAM,UAAU,MAAM,SAAS,SAAS;AACxC,QAAO,WAAW,SAAS,CAAC,OAAO,QAAQ,CAAC,OAAO,MAAM;;AAO3D,MAAM,gBAAgB;;;;;AAMtB,eAAsB,aACpB,eAC0B;AAC1B,KAAI;EACF,MAAM,WAAW,KAAK,eAAe,cAAc;EACnD,MAAM,UAAU,MAAM,SAAS,UAAU,QAAQ;EACjD,MAAM,SAAkB,KAAK,MAAM,QAAQ;AAC3C,MAAI,CAAC,WAAW,OAAO,CACrB,OAAM,IAAI,MAAM,4BAA4B,WAAW;AAEzD,SAAO;UACA,KAAK;AACZ,MAAK,IAA8B,SAAS,SAC1C,QAAO;AAET,QAAM;;;;;;;AAQV,eAAsB,cACpB,eACA,UACe;CACf,MAAM,WAAW,KAAK,eAAe,cAAc;AACnD,OAAM,MAAM,QAAQ,SAAS,EAAE,EAAE,WAAW,MAAM,CAAC;AACnD,OAAM,gBAAgB,UAAU,KAAK,UAAU,UAAU,MAAM,EAAE,GAAG,KAAK;;;;;;AAW3E,eAAe,aACb,KACA,UAAkB,KACC;CACnB,MAAM,UAAU,MAAM,QAAQ,KAAK,EAAE,eAAe,MAAM,CAAC;CAC3D,MAAM,QAAkB,EAAE;AAE1B,MAAK,MAAM,SAAS,SAAS;EAC3B,MAAM,WAAW,KAAK,KAAK,MAAM,KAAK;AAGtC,MAAI,MAAM,SAAS,eAAgB;AAEnC,MAAI,MAAM,gBAAgB,EAAE;GAE1B,MAAM,WAAW,MAAM,KAAK,SAAS;AACrC,OAAI,SAAS,aAAa,CACxB,OAAM,KAAK,GAAI,MAAM,aAAa,UAAU,QAAQ,CAAE;YAC7C,SAAS,QAAQ,CAC1B,OAAM,KAAK,SAAS,SAAS,SAAS,CAAC,MAAM,IAAI,CAAC,KAAK,IAAI,CAAC;aAErD,MAAM,aAAa,CAC5B,OAAM,KAAK,GAAI,MAAM,aAAa,UAAU,QAAQ,CAAE;MAEtD,OAAM,KAAK,SAAS,SAAS,SAAS,CAAC,MAAM,IAAI,CAAC,KAAK,IAAI,CAAC;;AAIhE,QAAO;;;;;;;AAQT,eAAsB,oBACpB,WACA,UACuB;CACvB,MAAM,eAAe,MAAM,aAAa,UAAU;CAElD,MAAM,gBAAgB,IAAI,IAAI,OAAO,KAAK,SAAS,MAAM,CAAC;CAE1D,MAAM,WAAqB,EAAE;CAC7B,MAAM,eAAyB,EAAE;AAGjC,OAAM,aAAa,cAAc,iBAAiB,OAAO,aAAa;EACpE,MAAM,OAAO,MAAM,gBAAgB,KAAK,WAAW,SAAS,CAAC;AAE7D,MAAI,CAAC,cAAc,IAAI,SAAS,CAC9B,UAAS,KAAK,SAAS;WACd,SAAS,MAAM,cAAc,KACtC,cAAa,KAAK,SAAS;GAE7B;CAGF,MAAM,aAAa,IAAI,IAAI,aAAa;CACxC,MAAM,eAAe,CAAC,GAAG,cAAc,CAAC,QACrC,aAAa,CAAC,WAAW,IAAI,SAAS,CACxC;AAED,QAAO;EACL,KAAK,CAAC,GAAG,SAAS,CAAC,MAAM;EACzB,SAAS,CAAC,GAAG,aAAa,CAAC,MAAM;EACjC,SAAS,CAAC,GAAG,aAAa,CAAC,MAAM;EAClC;;;;;;;;AAaH,eAAsB,cACpB,WACA,gBACA,cACmB;CAGnB,MAAM,cAAc,MAAM,aAFZ,MAAM,aAAa,UAAU,EAIzC,iBACA,OAAO,aAAa;AAElB,SAAO,CAAC,UADK,MAAM,gBAAgB,KAAK,WAAW,SAAS,CAAC,CACtC;GAE1B;AAGD,aAAY,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,cAAc,EAAE,CAAC;CAElD,MAAM,aAAuC,EAAE;AAC/C,MAAK,MAAM,CAAC,MAAM,SAAS,YACzB,YAAW,QAAQ;AAGrB,QAAO;EACL,YAAY;EACZ,eAAe;EACf,4BAAW,IAAI,MAAM,EAAC,aAAa;EACnC,OAAO;EACR;;;;;;;;ACxLH,SAAgB,gBAAgB,QAAgC;CAC9D,MAAM,OAAO,OAAO,QAAQ;CAC5B,MAAM,UAAU,OAAO;CAGvB,IAAI;AACJ,KAAI,WAAW,KACb,iBAAgB,EAAE;UACT,MAAM,QAAQ,QAAQ,CAC/B,iBAAgB;KAEhB,iBAAgB,CAAC,QAAQ;AAG3B,QAAO;EAAE;EAAM,gBAAgB;EAAe;;;;;;AAOhD,SAAgB,iBACd,QACA,eACQ;AACR,KAAI,OAAO,MAAM;EAGf,MAAM,OAAO,OAAO,KAAK,QAAQ,iBAAiB,IAAI;AACtD,MAAI,CAAC,cAAc,IAAI,KAAK,CAC1B,QAAO;AAET,SAAO,WAAW,MAAM,cAAc;;AAExC,QAAO,WAAW,OAAO,QAAQ,WAAW,cAAc;;;;;;AAW5D,SAAgB,eAAe,OAA6B;AAC1D,QAAO;EACL,MAAM,MAAM,QAAQ;EACpB,QAAQ,MAAM,UAAU,EAAE;EAC1B,QAAQ,MAAM,UAAU;EACzB;;;;;AAUH,SAAgB,iBACd,UACqB;CACrB,MAAM,sBAAM,IAAI,KAAqB;AACrC,MAAK,MAAM,CAAC,MAAM,OAAO,OAAO,QAAQ,SAAS,CAC/C,KAAI,IAAI,IAAI,KAAK;AAEnB,QAAO;;;;;;AAOT,SAAgB,yBACd,OACA,gBACuB;AACvB,QAAO,MAAM,KAAK,UAAU;EAC1B,IAAI,KAAK;EACT,MAAM,KAAK,QAAQ;EACnB,OAAO,KAAK,SAAS;EACrB,QAAQ,KAAK,YACR,eAAe,IAAI,KAAK,UAAU,IAAI,OACvC;EACJ,MAAM,KAAK,QAAQ;EACnB,QAAQ,KAAK,UAAU;EACvB,UAAU,KAAK,YAAY;EAC3B,WAAW,KAAK,aAAa;EAC7B,UAAU,yBAAyB,KAAK,YAAY,EAAE,EAAE,eAAe;EACxE,EAAE;;;;;AAML,SAAgB,oBACd,KACA,OACA,gBACiB;AACjB,QAAO;EACL,MAAM,IAAI,QAAQ;EAClB,UAAU,IAAI;EACd,kBAAkB,yBAAyB,OAAO,eAAe;EAClE;;;AAQH,MAAa,6BAEc;;AAG3B,MAAa,wBAEc;;;;;;;;;AAU3B,SAAgB,iBACd,SACA,aACA,eACc;CACd,MAAM,aAAa,QAAQ,aACtB,YAAY,IAAI,QAAQ,WAAW,GAAG,IAAI,OAC3C;CAEJ,MAAM,mBAAmB,QAAQ,oBAC5B,YAAY,IAAI,QAAQ,kBAAkB,GAAG,IAAI,OAClD;CAEJ,MAAM,UAAU,QAAQ,UAAU,EAAE,EACjC,KAAK,MAAM,cAAc,IAAI,EAAE,GAAG,CAAC,CACnC,QAAQ,SAAyB,QAAQ,KAAK;CAEjD,MAAM,cAAc,QAAQ,eAAe,EAAE;AAE7C,QAAO;EACL,MAAM,QAAQ,QAAQ;EACtB,SAAS,QAAQ,WAAW;EAC5B;EACA,mBAAmB;EACnB;EACA,aAAa;GACX,OAAO,cAAc,YAAY,MAAM,IAAI,EAAE;GAC7C,OAAO,cAAc,YAAY,MAAM,IAAI,EAAE;GAC7C,UAAU,cAAc,YAAY,SAAS,IAAI,EAAE;GACnD,WAAW,cAAc,YAAY,UAAU,IAAI,EAAE;GACtD;EACF;;;;;;;;;;;;AChLH,MAAM,aAAa;AACnB,MAAM,kBAAkB;AACxB,MAAM,aAAa;;;;AASnB,SAAS,eAA8B;CACrC,MAAM,QAAQ,cAAc;AAC5B,KAAI,CAAC,OAAO;EACV,MAAM,UAAU,kBAAkB;AAClC,MAAI,CAAC,QACH,OAAM,IAAI,MACR,wBAAwB,MAAM,KAAK,cAAc,GAAG,UACrD;AAEH,QAAM,IAAI,MACR,qCACE,MAAM,KAAK,QAAQ,KAAK,GACxB,WACA,MAAM,KAAK,cAAc,GACzB,uBACH;;AAKH,QAAO,oBAAoB;EACzB,SAHc,QAAQ,IAAI,qBAAqB;EAI/C;EACD,CAAC;;;;;AAMJ,eAAe,oBACb,QAC+B;CAC/B,MAAM,MAA4B,EAAE;CACpC,IAAI,OAAO;AAEX,QAAO,MAAM;EACX,MAAM,WAAW,MAAM,WAAW,gBAAgB,QAAQ;GACxD;GACA,UAAU;GACX,CAAC;EACF,MAAM,QAAQ,SAAS,eAAe,EAAE;AACxC,MAAI,KAAK,GAAG,MAAM;EAElB,MAAM,aAAa,SAAS,MAAM,eAAe;AACjD,MAAI,QAAQ,cAAc,MAAM,WAAW,EAAG;AAC9C;;AAGF,QAAO;;AAGT,eAAe,iBACb,QACA,SACA,iBAC6B;CAC7B,MAAM,cAAc,MAAM,oBAAoB,OAAO;AAErD,KAAI,YAAY,WAAW,EACzB,OAAM,IAAI,MACR,0EACD;CAGH,MAAM,WAAW,kBAAkB,aAAa,SAAS,gBAAgB;AACzE,KAAI,SAAU,QAAO;CAGrB,MAAM,EAAE,iBAAiB,MAAM,QAAQ;EACrC,MAAM;EACN,MAAM;EACN,SAAS;EACT,SAAS,YAAY,KAAK,OAAO;GAC/B,OAAO,EAAE,QAAQ;GACjB,OAAO,EAAE;GACV,EAAE;EACJ,CAAC;AAEF,KAAI,gBAAgB,KAClB,OAAM,IAAI,MAAM,0BAA0B;CAG5C,MAAM,WAAW,YAAY,MAAM,MAAM,EAAE,OAAO,aAAa;AAC/D,KAAI,CAAC,SACH,OAAM,IAAI,MAAM,iCAAiC;AAGnD,QAAO;;;;;;AAOT,eAAe,sBACb,KACA,iBACA,mBACA,OACkB;CAClB,MAAM,YAAY,KAAK,KAAK,WAAW;CACvC,MAAM,gBAAgB,KAAK,KAAK,gBAAgB;AAGhD,KAAI,CAAC,WAAW,UAAU,CACxB,QAAO;CAIT,MAAM,WAAW,MAAM,aAAa,cAAc;AAClD,KAAI,CAAC,SAEH,QAAO;AAIT,KAAI,SAAS,kBAAkB,gBAC7B,QAAO;CAIT,MAAM,OAAO,MAAM,oBAAoB,WAAW,SAAS;AAI3D,MAFE,KAAK,IAAI,SAAS,KAAK,KAAK,QAAQ,SAAS,KAAK,KAAK,QAAQ,SAAS,MAExD,CAAC,OAAO;AACxB,UAAQ,KAAK;AACb,UAAQ,IACN,MAAM,OAAO,WAAW,GACtB,qBACA,MAAM,KAAK,SAAS,WAAW,GAC/B,SACA,MAAM,KAAK,kBAAkB,GAC7B,gCACH;AACD,UAAQ,KAAK;AACb,UAAQ,IAAI,iBAAiB,KAAK,QAAQ,SAAS,WAAW;AAC9D,UAAQ,IAAI,iBAAiB,KAAK,IAAI,SAAS,WAAW;AAC1D,UAAQ,IAAI,iBAAiB,KAAK,QAAQ,SAAS,WAAW;AAC9D,UAAQ,KAAK;AACb,UAAQ,IACN,SACE,MAAM,KAAK,UAAU,GACrB,oDACH;AACD,SAAO;;AAGT,QAAO;;;;;AAMT,eAAe,gBACb,WACA,cACA,MACe;CACf,MAAM,WAAW,KAAK,WAAW,aAAa;AAE9C,OAAM,MADM,KAAK,UAAU,KAAK,EACf,EAAE,WAAW,MAAM,CAAC;AACrC,OAAM,UAAU,UAAU,KAAK,UAAU,MAAM,MAAM,EAAE,GAAG,MAAM,QAAQ;;AAO1E,MAAa,cAAuB,IAAI,QAAQ,OAAO,CACpD,YACC,wEACD,CACA,OAAO,gBAAgB,+CAA+C,CACtE,OAAO,aAAa,qDAAqD,CACzE,OAAO,WAAW,qDAAqD,CACvE,OAAO,OAAO,YAAyB;CACtC,MAAM,MAAM,QAAQ,KAAK;AACzB,OAAM,0CAA0C,KAAK,cAAc;AAEnE,SAAQ,KAAK;AACb,SAAQ,IAAI,MAAM,KAAK,KAAK,oBAAoB,CAAC;AACjD,SAAQ,KAAK;CAGb,MAAM,UAAU,KAAK;AACrB,SAAQ,MAAM,oBAAoB;CAElC,IAAI;AACJ,KAAI;AACF,WAAS,cAAc;AACvB,UAAQ,QAAQ,gBAAgB;UACzB,KAAK;AACZ,UAAQ,KAAK,wBAAwB;AACrC,UAAQ,KAAK;AACb,UAAQ,IACN,MAAM,IAAI,SAAS,GACjB,OACC,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,EACpD;AACD,UAAQ,KAAK;AACb,UAAQ,KAAK,EAAE;;CAIjB,IAAI;AACJ,KAAI;AACF,eAAa,MAAM,iBAAiB,QAAQ,QAAQ,KAAK,QAAQ,GAAG;UAC7D,KAAK;AACZ,UAAQ,KAAK;AACb,UAAQ,IACN,MAAM,IAAI,SAAS,GACjB,OACC,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,EACpD;AACD,UAAQ,KAAK;AACb,UAAQ,KAAK,EAAE;;CAGjB,MAAM,eAAe,WAAW;CAChC,MAAM,iBAAiB,WAAW,QAAQ;AAE1C,SAAQ,IACN,MAAM,KAAK,eAAe,GACxB,MAAM,MAAM,eAAe,GAC3B,MAAM,KAAK,SAAS,aAAa,GAAG,CACvC;AACD,SAAQ,KAAK;CAGb,IAAI;AACJ,KAAI;AACF,eAAa,MAAM,sBACjB,KACA,cACA,gBACA,QAAQ,SAAS,MAClB;UACM,KAAK;AACZ,UAAQ,KAAK;AACb,UAAQ,IACN,MAAM,IAAI,SAAS,GACjB,OACC,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,EACpD;AACD,UAAQ,KAAK;AACb,UAAQ,KAAK,EAAE;;AAEjB,KAAI,CAAC,WACH,SAAQ,KAAK,EAAE;AAIjB,SAAQ,MAAM,wBAAwB;CAEtC,IAAI;CAGJ,IAAI;CAGJ,IAAI;CAGJ,IAAI;CAGJ,IAAI;CAGJ,MAAM,qCAAqB,IAAI,KAAsC;AAErE,KAAI;EACF,MAAM,CACJ,iBACA,gBACA,qBACA,oBACE,MAAM,QAAQ,IAAI;GACpB,WAAW,YAAY,QAAQ,cAAc,EAC3C,UAAU,YACX,CAAC;GACF,WAAW,WAAW,QAAQ,cAAc,EAC1C,UAAU,YACX,CAAC;GACF,WAAW,gBAAgB,QAAQ,cAAc,EAC/C,UAAU,YACX,CAAC;GACF,WAAW,aAAa,QAAQ,cAAc,EAC5C,UAAU,YACX,CAAC;GACH,CAAC;AAEF,iBAAe,gBAAgB,WAAW,EAAE;AAC5C,WAAS,eAAe,UAAU,EAAE;AACpC,gBAAc,oBAAoB,eAAe,EAAE;AACnD,aAAW,iBAAiB,YAAY,EAAE;AAG1C,MAAI,aAAa,WAAW,WAC1B,SAAQ,KACN,MAAM,OACJ,iCAAiC,WAAW,2CAC7C,CACF;AAEH,MAAI,OAAO,WAAW,WACpB,SAAQ,KACN,MAAM,OACJ,gCAAgC,WAAW,0CAC5C,CACF;AAEH,MAAI,YAAY,WAAW,WACzB,SAAQ,KACN,MAAM,OACJ,qCAAqC,WAAW,+CACjD,CACF;AAEH,MAAI,SAAS,WAAW,WACtB,SAAQ,KACN,MAAM,OACJ,kCAAkC,WAAW,4CAC9C,CACF;AAGH,UAAQ,OAAO;EAGf,MAAM,QAAQ,OAAO,GAAG;AAGxB,YAAU,MAAM,QAAQ,IACtB,aAAa,KAAK,MAChB,MAAM,YAAY;GAChB,MAAM,MAAM,MAAM,WAAW,UAAU,QAAQ,cAAc,EAAE,GAAG;AAClE,OAAI,CAAC,IAAI,OACP,OAAM,IAAI,MAAM,yCAAyC,EAAE,KAAK;AAElE,UAAO,IAAI;IACX,CACH,CACF;AAMD,UAAQ,OAAO;AACf,QAAM,QAAQ,IACZ,YAAY,KAAK,QACf,MAAM,YAAY;GAChB,MAAM,MAAM,MAAM,WAAW,oBAC3B,QACA,cACA,IAAI,GACL;AACD,sBAAmB,IAAI,IAAI,IAAI,IAAI,oBAAoB,EAAE,CAAC;IAC1D,CACH,CACF;AAED,UAAQ,QACN,WAAW,QAAQ,OAAO,cAAc,OAAO,OAAO,aAAa,YAAY,OAAO,kBAAkB,SAAS,OAAO,aACzH;UACM,KAAK;AACZ,UAAQ,KAAK,4BAA4B;AACzC,UAAQ,KAAK;AACb,UAAQ,IACN,MAAM,IAAI,SAAS,GACjB,OACC,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,EACpD;AACD,UAAQ,KAAK;AACb,UAAQ,KAAK,EAAE;;AAIjB,SAAQ,MAAM,4BAA4B;CAG1C,MAAM,8BAAc,IAAI,KAAa;CACrC,MAAM,iBAAyC,EAAE;AACjD,MAAK,MAAM,UAAU,SAAS;EAC5B,MAAM,OAAO,iBAAiB,QAAQ,YAAY;AAClD,cAAY,IAAI,KAAK;AACrB,iBAAe,QAAQ,OAAO;;CAIhC,MAAM,6BAAa,IAAI,KAAa;CACpC,MAAM,gBAAwC,EAAE;AAChD,MAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,OAAO,WAAW,MAAM,QAAQ,WAAW,WAAW;AAC5D,aAAW,IAAI,KAAK;AACpB,gBAAc,QAAQ,MAAM;;CAI9B,MAAM,2BAAW,IAAI,KAAa;CAClC,MAAM,cAAsC,EAAE;AAC9C,MAAK,MAAM,OAAO,aAAa;EAC7B,MAAM,OAAO,WAAW,IAAI,QAAQ,WAAW,SAAS;AACxD,WAAS,IAAI,KAAK;AAClB,cAAY,QAAQ,IAAI;;CAI1B,MAAM,+BAAe,IAAI,KAAa;CACtC,MAAM,kBAA0C,EAAE;AAClD,MAAK,MAAM,WAAW,UAAU;EAC9B,MAAM,OAAO,WAAW,QAAQ,QAAQ,WAAW,aAAa;AAChE,eAAa,IAAI,KAAK;AACtB,kBAAgB,QAAQ,QAAQ;;CAIlC,MAAM,iBAAiB,iBAAiB,eAAe;CACvD,MAAM,cAAc,2BAA2B,YAAY;CAC3D,MAAM,gBAAgB,sBAAsB,cAAc;CAC1D,MAAM,kBAAkB,iBAAiB,gBAAgB;AAEzD,SAAQ,QAAQ,wBAAwB;AAGxC,SAAQ,MAAM,mBAAmB;CAEjC,MAAM,YAAY,KAAK,KAAK,WAAW;CACvC,MAAM,gBAAgB,KAAK,KAAK,gBAAgB;CAKhD,MAAM,eAAe,YAAY;AAEjC,KAAI;AAEF,MAAI,WAAW,aAAa,CAC1B,OAAM,GAAG,cAAc,EAAE,WAAW,MAAM,CAAC;AAE7C,QAAM,MAAM,cAAc,EAAE,WAAW,MAAM,CAAC;AAG9C,QAAM,gBAAgB,cAAc,mBAAmB,EACrD,MAAM,gBACP,CAAC;AAGF,OAAK,MAAM,UAAU,SAAS;GAC5B,MAAM,OAAO,eAAe,IAAI,OAAO,GAAG;GAC1C,MAAM,QAAQ,gBAAgB,OAAO;AACrC,SAAM,gBAAgB,cAAc,WAAW,KAAK,QAAQ,MAAM;;AAIpE,OAAK,MAAM,SAAS,QAAQ;GAC1B,MAAM,OAAO,cAAc,IAAI,MAAM,GAAG;GACxC,MAAM,QAAQ,eAAe,MAAM;AACnC,SAAM,gBAAgB,cAAc,UAAU,KAAK,QAAQ,MAAM;;AAInE,OAAK,MAAM,OAAO,aAAa;GAC7B,MAAM,OAAO,YAAY,IAAI,IAAI,GAAG;GAEpC,MAAM,QAAQ,oBAAoB,KADpB,mBAAmB,IAAI,IAAI,GAAG,IAAI,EAAE,EACJ,eAAe;AAC7D,SAAM,gBAAgB,cAAc,eAAe,KAAK,QAAQ,MAAM;;AAIxE,OAAK,MAAM,WAAW,UAAU;GAC9B,MAAM,OAAO,gBAAgB,IAAI,QAAQ,GAAG;AAC5C,OAAI,MAAM;IACR,MAAM,QAAQ,iBAAiB,SAAS,aAAa,cAAc;AACnE,UAAM,gBAAgB,cAAc,YAAY,KAAK,QAAQ,MAAM;;;AAKvE,MAAI,WAAW,UAAU,CACvB,OAAM,GAAG,WAAW,EAAE,WAAW,MAAM,CAAC;AAE1C,QAAM,OAAO,cAAc,UAAU;UAC9B,KAAK;AAEZ,MAAI,WAAW,aAAa,CAC1B,OAAM,GAAG,cAAc,EAAE,WAAW,MAAM,CAAC,CAAC,YAAY,GAAG;AAE7D,UAAQ,KAAK,wBAAwB;AACrC,UAAQ,KAAK;AACb,UAAQ,IACN,MAAM,IAAI,SAAS,GACjB,OACC,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,EACpD;AACD,UAAQ,KAAK;AACb,UAAQ,KAAK,EAAE;;AAOjB,KAAI;AAUF,QAAM,cAAc,eATa;GAC/B,YAAY;IAAE,MAAM;IAAgB,IAAI;IAAc;GACtD,SAAS;GACT,QAAQ;GACR,aAAa;GACb,UAAU;GACV,WAAW,EAAE;GACb,OAAO,EAAE;GACV,CAC2C;AAO5C,QAAM,cAAc,eALH,MAAM,cACrB,WACA,gBACA,aACD,CAC2C;AAE5C,UAAQ,QAAQ,gBAAgB;UACzB,KAAK;AACZ,UAAQ,KAAK,uCAAuC;AACpD,UAAQ,KAAK;AACb,UAAQ,IACN,MAAM,OAAO,WAAW,GACtB,4EAEA,MAAM,KAAK,oBAAoB,GAC/B,iCACH;AACD,UAAQ,IACN,MAAM,KAAK,aAAa,IACrB,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,EACpD;AACD,UAAQ,KAAK;;AAIf,SAAQ,KAAK;AACb,SAAQ,IAAI,MAAM,MAAM,KAAK,iBAAiB,CAAC;AAC/C,SAAQ,KAAK;AACb,SAAQ,IAAI,MAAM,KAAK,2BAA2B,CAAC;AACnD,SAAQ,IACN,MAAM,KAAK,8BAA8B,GACvC,MAAM,MAAM,GAAG,QAAQ,OAAO,UAAU,CAC3C;AACD,SAAQ,IACN,MAAM,KAAK,8BAA8B,GACvC,MAAM,MAAM,GAAG,OAAO,OAAO,UAAU,CAC1C;AACD,SAAQ,IACN,MAAM,KAAK,8BAA8B,GACvC,MAAM,MAAM,GAAG,YAAY,OAAO,UAAU,CAC/C;AACD,SAAQ,IACN,MAAM,KAAK,8BAA8B,GACvC,MAAM,MAAM,GAAG,SAAS,OAAO,UAAU,CAC5C;AACD,SAAQ,IACN,MAAM,KAAK,8BAA8B,GACvC,MAAM,MAAM,gCAAgC,CAC/C;AACD,SAAQ,KAAK;EACb"}
|