@binaryandthread/cli 0.1.2 → 0.1.3

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.
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../store-client/src/redact.ts","../../store-client/src/client.ts","../../store-client/src/defaults.ts","../../store-client/src/host-allowlist.ts","../../store-client/src/colour.ts","../src/voice.ts","../src/brand.ts","../src/commands/shared.ts","../src/image-ladder.ts","../src/commands/view.ts","../src/commands/stage.ts"],"sourcesContent":["// Secret/PII redaction for logs and error envelopes (SECURITY G10).\n//\n// The single rule for the whole channel: Authorization headers, tokens, and full\n// emails NEVER reach a log line, an error message, or an MCP/CLI transcript. This\n// function is applied by the StoreClient's logger and error mapper before anything\n// is emitted. It is consumed by BOTH the CLI and (Wave 4) the MCP — one redactor.\n\nconst SENSITIVE_KEYS = new Set([\n \"authorization\",\n \"token\",\n \"access_token\",\n \"refresh_token\",\n \"jwt\",\n \"idempotency-key\",\n \"x-publishable-api-key\",\n \"password\",\n \"secret\",\n \"cookie\",\n \"set-cookie\",\n])\n\nconst MASK = \"[REDACTED]\"\n\n// Bearer-token-in-a-string (e.g. \"Authorization: Bearer abc.def.ghi\" or a stray \"Bearer xyz\").\nconst BEARER_RE = /Bearer\\s+[A-Za-z0-9._\\-]+/gi\n// A plausible email anywhere in a string.\nconst EMAIL_RE = /([^\\s@]+)@([^\\s@]+\\.[^\\s@]+)/g\n\n/** Mask an email to `d***@x` form (first char + stars, host first char). */\nexport function maskEmail(email: string): string {\n const at = email.indexOf(\"@\")\n if (at <= 0) {\n return email\n }\n const local = email.slice(0, at)\n const host = email.slice(at + 1)\n const localMasked = local[0] + \"*\".repeat(Math.max(2, local.length - 1))\n const hostMasked = host.length > 0 ? host[0] : \"x\"\n return `${localMasked}@${hostMasked}`\n}\n\nfunction redactString(s: string): string {\n return s.replace(BEARER_RE, \"Bearer [REDACTED]\").replace(EMAIL_RE, (_m, _l, _h) =>\n maskEmail(`${_l}@${_h}`)\n )\n}\n\n/**\n * Deep-redact an object/array/string. Returns a structurally-similar value with\n * sensitive keys masked, Bearer tokens stripped, and emails masked. Never throws.\n *\n * `_seen` memoises input-object → its REDACTED output. This does double duty: it makes the\n * function cycle-safe (a revisited object resolves to its own — possibly still-filling — output\n * container) AND ensures a REPEATED reference is redacted every time. The previous WeakSet guard\n * returned the *original* object on revisit, leaking any token/email reachable via a 2nd path (G10).\n * The output container is registered BEFORE recursing so a cycle points at the redacted copy.\n */\nexport function redact<T>(input: T, _seen?: Map<object, unknown>): T {\n if (input == null) {\n return input\n }\n if (typeof input === \"string\") {\n return redactString(input) as unknown as T\n }\n if (typeof input !== \"object\") {\n return input\n }\n\n const seen = _seen ?? new Map<object, unknown>()\n const existing = seen.get(input as object)\n if (existing !== undefined) {\n return existing as T\n }\n\n if (Array.isArray(input)) {\n const out: unknown[] = []\n seen.set(input as object, out) // register before recursing (cycle-safe)\n for (const v of input) {\n out.push(redact(v, seen))\n }\n return out as unknown as T\n }\n\n const out: Record<string, unknown> = {}\n seen.set(input as object, out) // register before recursing (cycle-safe)\n for (const [key, value] of Object.entries(input as Record<string, unknown>)) {\n if (SENSITIVE_KEYS.has(key.toLowerCase())) {\n out[key] = MASK\n } else if (key.toLowerCase() === \"email\" && typeof value === \"string\") {\n out[key] = maskEmail(value)\n } else {\n out[key] = redact(value, seen)\n }\n }\n return out as unknown as T\n}\n","// StoreClient — the ONE shared HTTP client over the B&T Medusa Store API.\n//\n// Design (ROBUSTNESS §0): the clients are DUMB. Every dangerous property lives in the\n// backend. This module is the single place the channel's HTTP-client invariants live so\n// they are written once and contract-tested once (the CLI and the Wave-4 MCP both use it):\n//\n// - publishable key on EVERY request (public by design, T6); customer JWT when present\n// - Idempotency-Key (crypto.randomUUID) on EVERY mutation, STABLE across a retry (§2)\n// - retry 5xx + network errors with jittered backoff; NEVER retry a 4xx (F-A1/F-I2)\n// - region resolved before pricing — never a bare currency literal (F-A2/INV-11)\n// - 404/409/cart.completed on a cart mutation → RecoverableCartError (CLI makes a fresh cart) (F-B2)\n// - all logs + error envelopes pass through redact() — no token/email ever leaks (G10)\n// - NO secret key is ever stored or sent — publishable key only (T6)\n\nimport { redact } from \"./redact\"\nimport type {\n CartLike,\n ClientLogger,\n DropRow,\n HeadlessCheckout,\n OrderSummary,\n PollResult,\n ProductLike,\n RegionLike,\n ReportResult,\n RequestLinkResult,\n RetryPolicy,\n ShippingOptionLike,\n StoreClientOptions,\n VerifyLinkResult,\n WhoAmI,\n} from \"./types\"\n\nconst DEFAULT_RETRY: RetryPolicy = {\n attempts: 3,\n baseDelayMs: 200,\n maxDelayMs: 2000,\n}\n\n/** Cap on how long we'll honor a server's Retry-After, so a hostile/huge value can't hang us. */\nconst RETRY_AFTER_MAX_MS = 10_000\n\n/**\n * Parse an HTTP Retry-After header → ms, or null if absent/unparseable. Accepts both forms:\n * delta-seconds (\"2\") and an HTTP-date. Capped at RETRY_AFTER_MAX_MS (d12).\n */\nfunction parseRetryAfterMs(res: Response): number | null {\n let raw: string | null = null\n try {\n raw = res.headers?.get?.(\"retry-after\") ?? null\n } catch {\n return null\n }\n if (!raw) {\n return null\n }\n const secs = Number(raw)\n let ms: number\n if (Number.isFinite(secs)) {\n ms = secs * 1000\n } else {\n const dateMs = Date.parse(raw)\n if (!Number.isFinite(dateMs)) {\n return null\n }\n ms = dateMs - Date.now()\n }\n return Math.min(Math.max(0, ms), RETRY_AFTER_MAX_MS)\n}\n\n/** A cart error the CLI maps to \"discard local cart_id, create a fresh cart, re-add\" (F-B2). */\nexport class RecoverableCartError extends Error {\n readonly recoverable = true as const\n readonly status: number\n constructor(message: string, status: number) {\n super(message)\n this.name = \"RecoverableCartError\"\n this.status = status\n }\n}\n\n/** A non-recoverable API error. Its message/envelope is already redacted (G10). */\nexport class StoreApiError extends Error {\n readonly status: number\n readonly path: string\n constructor(message: string, status: number, path: string) {\n super(message)\n this.name = \"StoreApiError\"\n this.status = status\n this.path = path\n }\n}\n\ntype HttpMethod = \"GET\" | \"POST\" | \"DELETE\"\n\ninterface RequestOpts {\n body?: unknown\n /** Mutations carry an Idempotency-Key. */\n idempotent?: boolean\n /** Treat a 404/409/cart.completed as a RecoverableCartError (cart mutations). */\n cartRecovery?: boolean\n query?: Record<string, string | number | undefined>\n /**\n * Statuses for which a 4xx is EXPECTED, not an error — the client_error envelope is NOT\n * logged (BUG 3: a guest whoami 401 is the normal unauthenticated path, not noise to dump).\n * The error is still thrown so the caller can branch on it.\n */\n expected4xx?: number[]\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms))\n}\n\n/** crypto.randomUUID with a defensive fallback (older runtimes). */\nfunction newUuid(): string {\n const g = globalThis as unknown as { crypto?: { randomUUID?: () => string } }\n if (g.crypto?.randomUUID) {\n return g.crypto.randomUUID()\n }\n // RFC4122-ish fallback — never the happy path, just defensive.\n return \"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\".replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0\n const v = c === \"x\" ? r : (r & 0x3) | 0x8\n return v.toString(16)\n })\n}\n\nexport class StoreClient {\n private readonly baseUrl: string\n private readonly publishableKey: string\n private readonly jwt?: string\n private readonly fetchImpl: typeof fetch\n private readonly logger: ClientLogger\n private readonly retry: RetryPolicy\n private readonly sleepImpl: (ms: number) => Promise<void>\n\n /** Cached resolved regions (read-only catalog optimisation). */\n private regionsCache: RegionLike[] | null = null\n\n constructor(opts: StoreClientOptions) {\n if (!opts.baseUrl) {\n throw new Error(\"StoreClient: baseUrl is required\")\n }\n if (!opts.publishableKey) {\n throw new Error(\"StoreClient: publishableKey is required\")\n }\n // Guard against accidentally shipping a secret key (T6 — defensive).\n if (/^sk_/.test(opts.publishableKey)) {\n throw new Error(\"StoreClient: refusing a secret key — the client carries only the publishable key (T6)\")\n }\n this.baseUrl = opts.baseUrl.replace(/\\/+$/, \"\")\n this.publishableKey = opts.publishableKey\n this.jwt = opts.jwt\n this.fetchImpl = opts.fetchImpl ?? (globalThis.fetch as typeof fetch)\n this.logger = opts.logger ?? console\n this.retry = { ...DEFAULT_RETRY, ...(opts.retry ?? {}) }\n this.sleepImpl = opts.sleepImpl ?? sleep\n }\n\n // ----- the request engine --------------------------------------------------\n\n private buildUrl(path: string, query?: RequestOpts[\"query\"]): string {\n const url = new URL(this.baseUrl + path)\n if (query) {\n for (const [k, v] of Object.entries(query)) {\n if (v !== undefined && v !== null && v !== \"\") {\n url.searchParams.set(k, String(v))\n }\n }\n }\n return url.toString()\n }\n\n private baseHeaders(): Record<string, string> {\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n \"x-publishable-api-key\": this.publishableKey,\n }\n if (this.jwt) {\n headers[\"Authorization\"] = `Bearer ${this.jwt}`\n }\n return headers\n }\n\n private backoffMs(attemptIndex: number): number {\n // exponential base with full jitter, capped.\n const exp = this.retry.baseDelayMs * 2 ** attemptIndex\n const capped = Math.min(exp, this.retry.maxDelayMs)\n return Math.floor(Math.random() * (capped + 1))\n }\n\n private async request<T>(method: HttpMethod, path: string, opts: RequestOpts = {}): Promise<T> {\n const headers = this.baseHeaders()\n // Generate the idempotency key ONCE — it is reused across retries so a retried\n // mutation can never spawn a duplicate (ROBUSTNESS §2).\n if (opts.idempotent) {\n headers[\"Idempotency-Key\"] = newUuid()\n }\n const url = this.buildUrl(path, opts.query)\n const init: RequestInit = {\n method,\n headers,\n ...(opts.body !== undefined ? { body: JSON.stringify(opts.body) } : {}),\n }\n\n let lastErr: unknown\n for (let attempt = 0; attempt < this.retry.attempts; attempt++) {\n // A server-dictated delay for THIS attempt's backoff (429 Retry-After); null → jittered backoff.\n let retryAfterMs: number | null = null\n try {\n const res = await this.fetchImpl(url as unknown as string, init)\n if (res.ok) {\n // 204 / empty body tolerance\n const text = await res.text()\n return (text ? JSON.parse(text) : {}) as T\n }\n\n // Read the error body defensively (may not be JSON).\n let parsed: Record<string, unknown> = {}\n try {\n const t = await res.text()\n parsed = t ? (JSON.parse(t) as Record<string, unknown>) : {}\n } catch {\n parsed = {}\n }\n\n // Cart recovery: a stale/completed cart on a cart mutation (F-B2).\n if (\n opts.cartRecovery &&\n (res.status === 404 ||\n res.status === 409 ||\n String(parsed.type ?? \"\").includes(\"cart.completed\") ||\n /cart.*complete/i.test(String(parsed.message ?? \"\")))\n ) {\n throw new RecoverableCartError(\n `cart is stale or completed (HTTP ${res.status})`,\n res.status\n )\n }\n\n // 429 Too Many Requests — RETRYABLE, honoring Retry-After (d12). Must be handled BEFORE\n // the generic 4xx-no-retry rule below, or a rate limit becomes a hard failure.\n if (res.status === 429) {\n retryAfterMs = parseRetryAfterMs(res)\n lastErr = new StoreApiError(`rate limited (HTTP 429)`, 429, path)\n this.logWarn(\"store.request.rate_limited\", {\n path,\n attempt: attempt + 1,\n retryAfterMs,\n })\n } else if (res.status >= 400 && res.status < 500) {\n // other 4xx is a client error — do NOT retry.\n // Suppress the envelope for EXPECTED 4xx (e.g. a guest whoami 401 — BUG 3): it's a\n // normal branch the caller handles, not noise to dump to the user's terminal.\n if (!(opts.expected4xx ?? []).includes(res.status)) {\n this.logError(\"store.request.client_error\", { path, status: res.status, body: parsed })\n }\n throw new StoreApiError(\n `request failed (HTTP ${res.status})`,\n res.status,\n path\n )\n } else {\n // 5xx — retryable. Fall through to the retry loop.\n lastErr = new StoreApiError(`upstream error (HTTP ${res.status})`, res.status, path)\n this.logWarn(\"store.request.retryable\", { path, status: res.status, attempt: attempt + 1 })\n }\n } catch (e) {\n // RecoverableCartError / StoreApiError(4xx) must propagate immediately.\n if (e instanceof RecoverableCartError) {\n throw e\n }\n if (e instanceof StoreApiError && e.status >= 400 && e.status < 500) {\n throw e\n }\n // Network error or a 5xx StoreApiError → retryable.\n lastErr = e\n this.logWarn(\"store.request.network_retry\", { path, attempt: attempt + 1 })\n }\n\n // Backoff before the next attempt (skip after the final attempt). A 429 Retry-After, when\n // present, dictates the delay; otherwise jittered exponential backoff (d12).\n if (attempt < this.retry.attempts - 1) {\n await this.sleepImpl(retryAfterMs ?? this.backoffMs(attempt))\n }\n }\n\n // Exhausted retries. The thrown envelope is already free of secrets (we only ever\n // log/throw redacted, derived data — never the request headers/body).\n this.logError(\"store.request.exhausted\", { path, attempts: this.retry.attempts })\n if (lastErr instanceof StoreApiError) {\n throw lastErr\n }\n throw new StoreApiError(\n `request failed after ${this.retry.attempts} attempts: ${path}`,\n 0,\n path\n )\n }\n\n private logWarn(event: string, ctx: Record<string, unknown>): void {\n this.logger.warn(event, redact(ctx))\n }\n\n private logError(event: string, ctx: Record<string, unknown>): void {\n this.logger.error(event, redact(ctx))\n }\n\n // ----- regions (F-A2 / INV-11) ---------------------------------------------\n\n async getRegions(): Promise<RegionLike[]> {\n const data = await this.request<{ regions: RegionLike[] }>(\"GET\", \"/store/regions\")\n this.regionsCache = data.regions ?? []\n return this.regionsCache\n }\n\n /**\n * Resolve a region by country code, falling back to region #1.\n * SEK is region #1 by configuration — NEVER a hardcoded literal.\n */\n async resolveRegion(countryCode?: string): Promise<RegionLike> {\n const regions = this.regionsCache ?? (await this.getRegions())\n if (regions.length === 0) {\n // Fail closed: refuse to price without a resolved region (F-A2).\n throw new Error(\"no regions configured — cannot resolve a region for pricing\")\n }\n if (countryCode) {\n const cc = countryCode.toLowerCase()\n const match = regions.find((r) =>\n (r.countries ?? []).some((c) => (c.iso_2 ?? \"\").toLowerCase() === cc)\n )\n if (match) {\n return match\n }\n }\n // Region #1 — the launch region (SEK today, derived not hardcoded).\n return regions[0]!\n }\n\n // ----- products (region resolved BEFORE pricing) ---------------------------\n\n async listProducts(opts?: { region?: RegionLike; countryCode?: string }): Promise<ProductLike[]> {\n const region = opts?.region ?? (await this.resolveRegion(opts?.countryCode))\n // Paginate (L12): a single limit:100 call silently drops the 101st product. Page through until a\n // short page, with a hard cap so a misbehaving API can never spin this unbounded.\n const PAGE = 100\n const HARD_CAP = 1000\n const all: ProductLike[] = []\n for (let offset = 0; offset < HARD_CAP; offset += PAGE) {\n const data = await this.request<{ products: ProductLike[] }>(\"GET\", \"/store/products\", {\n query: { region_id: region.id, limit: PAGE, offset },\n })\n const page = data.products ?? []\n all.push(...page)\n if (page.length < PAGE) {\n break\n }\n }\n return all\n }\n\n async getProduct(handle: string, opts?: { region?: RegionLike; countryCode?: string }): Promise<ProductLike | null> {\n const region = opts?.region ?? (await this.resolveRegion(opts?.countryCode))\n const data = await this.request<{ products: ProductLike[] }>(\"GET\", \"/store/products\", {\n query: { handle, region_id: region.id, limit: 1 },\n })\n return (data.products ?? [])[0] ?? null\n }\n\n // ----- cart (mutations carry Idempotency-Key) ------------------------------\n\n async createCart(region: RegionLike): Promise<CartLike> {\n const data = await this.request<{ cart: CartLike }>(\"POST\", \"/store/carts\", {\n idempotent: true,\n body: { region_id: region.id },\n })\n return data.cart\n }\n\n async addLineItem(cartId: string, variantId: string, quantity: number): Promise<CartLike> {\n const data = await this.request<{ cart: CartLike }>(\n \"POST\",\n `/store/carts/${encodeURIComponent(cartId)}/line-items`,\n {\n idempotent: true,\n cartRecovery: true,\n body: { variant_id: variantId, quantity },\n }\n )\n return data.cart\n }\n\n async getCart(cartId: string): Promise<CartLike> {\n const data = await this.request<{ cart: CartLike }>(\n \"GET\",\n `/store/carts/${encodeURIComponent(cartId)}`,\n { cartRecovery: true }\n )\n return data.cart\n }\n\n /** Remove a staged line item (bt unstage). Returns the updated cart (Medusa's `parent`). */\n async removeLineItem(cartId: string, itemId: string): Promise<CartLike> {\n const data = await this.request<{ parent?: CartLike; cart?: CartLike }>(\n \"DELETE\",\n `/store/carts/${encodeURIComponent(cartId)}/line-items/${encodeURIComponent(itemId)}`,\n { idempotent: true, cartRecovery: true }\n )\n return (data.parent ?? data.cart) as CartLike\n }\n\n /**\n * Ensure the cart has a shipping method before checkout (BUG 1 — the headless\n * /store/headless/checkout route guards on it, and neither client ever set one).\n *\n * - Idempotent: if the cart already carries a shipping method, no-op (no POST).\n * - Lists options via the STANDARD store route GET /store/shipping-options?cart_id=…\n * (NOT a /headless route — standard routes are always available, no kill-switch).\n * - Picks deterministically: exactly one → use it; several → the cheapest by amount.\n * - The SE launch option is region-scoped (price_type:\"flat\"), so listing by cart_id\n * returns it without a shipping address being set first (confirmed: seed-shipping.ts).\n *\n * TODO(04.5): buyer-choice / intl multiple shipping options — today we auto-pick the\n * cheapest; an explicit buyer chooser belongs to the Phase-6 international pass.\n */\n async ensureCartShippingMethod(cartId: string): Promise<CartLike> {\n // 1. read the authoritative cart; a stale/completed cart → RecoverableCartError (F-B2).\n const cart = await this.getCart(cartId)\n if (cart.shipping_methods && cart.shipping_methods.length > 0) {\n // idempotent: already satisfied.\n return cart\n }\n\n // 2. list options via the STANDARD store route (always available, no kill-switch).\n const data = await this.request<{ shipping_options: ShippingOptionLike[] }>(\n \"GET\",\n \"/store/shipping-options\",\n { query: { cart_id: cartId } }\n )\n const options = data.shipping_options ?? []\n if (options.length === 0) {\n // fail closed: never mint a checkout we know the backend will reject (BUG 1).\n throw new Error(\n \"no shipping option is available for this cart — cannot attach a shipping method\"\n )\n }\n\n // 3. pick deterministically: one → it; several → the cheapest by amount.\n const chosen =\n options.length === 1\n ? options[0]!\n : [...options].sort(\n (a, b) => (a.amount ?? Number.POSITIVE_INFINITY) - (b.amount ?? Number.POSITIVE_INFINITY)\n )[0]!\n\n // 4. attach it — a mutation, so it carries an Idempotency-Key + cart-recovery.\n const res = await this.request<{ cart: CartLike }>(\n \"POST\",\n `/store/carts/${encodeURIComponent(cartId)}/shipping-methods`,\n {\n idempotent: true,\n cartRecovery: true,\n body: { option_id: chosen.id },\n }\n )\n return res.cart\n }\n\n // ----- headless contract (04.5-01) -----------------------------------------\n\n // `channel` is REQUIRED (L11): a default silently mis-attributes analytics for a future caller that\n // forgets it. Both current callers (cli, mcp) pass it explicitly.\n async mintCheckout(cartId: string, email: string | undefined, channel: string): Promise<HeadlessCheckout> {\n return this.request<HeadlessCheckout>(\"POST\", \"/store/headless/checkout\", {\n idempotent: true,\n body: { cart_id: cartId, email, channel },\n })\n }\n\n async poll(sessionId: string): Promise<PollResult> {\n return this.request<PollResult>(\n \"GET\",\n `/store/headless/poll/${encodeURIComponent(sessionId)}`\n )\n }\n\n /** JWT-bound: returns the authenticated customer's own profile (G2). */\n async whoami(): Promise<WhoAmI> {\n // A guest (no/expired JWT) gets a 401/403 — that's the EXPECTED unauthenticated branch the\n // CLI turns into the friendly \"browsing as a guest\" line, NOT an error to dump (BUG 3).\n return this.request<WhoAmI>(\"GET\", \"/store/headless/whoami\", { expected4xx: [401, 403] })\n }\n\n // ----- magic-link auth (04.5-05) -------------------------------------------\n // Passwordless login — magic-link is the only grant (Medusa has no OAuth pairing-grant, G1).\n // request-link emails a single-use link; verify exchanges the link's token for a customer-scoped\n // JWT. The JWT is stored in the OS keychain by the CLI — never here, never in plaintext config (G7/G8).\n\n /** Start a magic-link login. Returns a UNIFORM ack regardless of whether the email exists (G12). */\n async requestMagicLink(email: string): Promise<RequestLinkResult> {\n return this.request<RequestLinkResult>(\"POST\", \"/store/headless/auth/request-link\", {\n idempotent: true,\n body: { email },\n })\n }\n\n /** Exchange a magic-link token for a customer-scoped JWT (the customer is materialized server-side). */\n async verifyMagicLink(token: string): Promise<VerifyLinkResult> {\n return this.request<VerifyLinkResult>(\"POST\", \"/store/headless/auth/verify\", {\n idempotent: true,\n body: { token },\n })\n }\n\n async listDrops(): Promise<DropRow[]> {\n const data = await this.request<{ drops: DropRow[]; count: number }>(\n \"GET\",\n \"/store/headless/drops\"\n )\n return data.drops ?? []\n }\n\n /**\n * File a developer bug report (d14 — `bt report` / MCP report_issue). Returns a correlation\n * reference. Additive: no money/cart path. The backend redacts authoritatively; callers SHOULD\n * pass an already-redacted context (the doctor snapshot). A mutation → carries an Idempotency-Key\n * so a retry can't email the report twice.\n */\n async report(message: string, context?: Record<string, unknown>): Promise<ReportResult> {\n return this.request<ReportResult>(\"POST\", \"/store/headless/report\", {\n idempotent: true,\n body: { message, ...(context !== undefined ? { context } : {}) },\n })\n }\n\n /**\n * The authenticated customer's order history (d13 — `bt log`). JWT-bound; the backend derives the\n * customer from the bearer token and returns MINIMAL fields (status/timeline only — never\n * email/address, F-H3). A guest (no/expired JWT) gets a 401 — the EXPECTED unauthenticated branch\n * we turn into the honest empty history (`[]`), NOT an error to dump (BUG 3).\n */\n async listOrders(): Promise<OrderSummary[]> {\n try {\n const data = await this.request<{ orders: OrderSummary[] }>(\"GET\", \"/store/headless/orders\", {\n expected4xx: [401, 403],\n })\n return data.orders ?? []\n } catch (e) {\n if (e instanceof StoreApiError && (e.status === 401 || e.status === 403)) {\n return []\n }\n throw e\n }\n }\n\n /**\n * Guest order lookup (AUD-8) — POST /store/order-lookup with the order id (display id OR internal\n * id) + the EXACT email in the BODY (never the query string, so the email is not access-logged).\n * The backend returns 404 on any mismatch (no enumeration) → the client surfaces null. Rate-limited\n * server-side; returns minimal status only, never PII (F-H1 / F-H3).\n */\n async getOrder(idOrDisplayId: string | number, email: string): Promise<unknown | null> {\n try {\n return await this.request<unknown>(\"POST\", \"/store/order-lookup\", {\n body: { id: String(idOrDisplayId), email },\n })\n } catch (e) {\n if (e instanceof StoreApiError && e.status === 404) {\n return null\n }\n throw e\n }\n }\n}\n","// Baked-in PUBLIC defaults for the Binary & Thread production channel.\n//\n// These let `npx @binaryandthread/cli` and the MCP server reach the live store with ZERO\n// configuration — the point of the soft-launch tarball (P4.5-7b) and decision #79 (d2).\n// store-client is bundled into both the CLI and the MCP server (tsup `noExternal`), so this\n// is the single source of truth for \"which store\" both clients talk to by default.\n//\n// SAFETY (CLAUDE.md rule 1 / SECURITY T6): the publishable key is PUBLIC BY DESIGN. It is the\n// SAME key embedded in the public storefront's client bundle (binaryandthread.com) and carries\n// no privilege beyond read + cart over the Store API — every dangerous operation lives behind\n// the backend. It is therefore NOT a secret; rule 1 governs secrets, and this is deliberately\n// published. Any env var (BT_API_URL / BT_STORE_URL / BT_PUBLISHABLE_KEY / MEDUSA_*) OVERRIDES\n// these for local dev or staging.\n\n/** The Binary & Thread production Medusa Store API base URL. */\nexport const BT_DEFAULT_STORE_URL = \"https://backend-production-511f5.up.railway.app\"\n\n// The PUBLIC Medusa publishable key (read + cart only; safe to ship — T6). Identical to the key\n// served in the public storefront bundle. NOT a secret (CLAUDE.md rule 1); the inline allow keeps\n// the gitleaks pre-commit hook from flagging a deliberately-published value.\nexport const BT_DEFAULT_PUBLISHABLE_KEY = \"pk_0d7d706860568a0414dff0436aadb9713a2609de3a1b153ef2a6f9694d23f907\" // gitleaks:allow\n","// Image-host allowlist — the canonical source for the WHOLE channel (CLAUDE.md rule 6 / SECURITY G9).\n//\n// We fetch product images ONLY from our own Vercel Blob store. Both the bt CLI (its terminal\n// image ladder) and the Wave-4 MCP (its server-side ImageContent fetch) gate every outbound\n// image fetch on this allowlist. It lives HERE in the shared client — not in the CLI — so the\n// MCP does not take a runtime dependency on the CLI package; the CLI re-exports it from here.\n//\n// HISTORY: a bare `endsWith(allowed)` suffix match was a CRITICAL allowlist-bypass bug fixed in\n// Wave 2 (it would admit `evilblob.vercel-storage.com`). The dot-delimited-subdomain check below\n// is load-bearing security — `host === allowed || host.endsWith(\".\" + allowed)`, never a bare\n// suffix. Do NOT \"simplify\" it.\n\n/**\n * Allowlisted image hosts. We fetch product images ONLY from our own Vercel Blob store\n * (public). NEVER a public temp host (uguu/0x0/catbox/transfer.sh) and NEVER an arbitrary\n * URL — that would be SSRF + a brand-IP leak (CLAUDE.md rule 6).\n */\nexport const BLOB_HOST_ALLOWLIST: readonly string[] = [\n \"blob.vercel-storage.com\", // matches the exact host + *.blob.vercel-storage.com (dot-delimited subdomains only)\n]\n\n/**\n * True if the URL's host is on the Blob allowlist.\n *\n * Exact host OR a true dot-delimited subdomain. NOT a bare suffix: a bare\n * `endsWith(allowed)` would admit `evilblob.vercel-storage.com` (allowlist bypass →\n * SSRF / brand-IP leak, CLAUDE.md rule 6). The dot guard is load-bearing.\n */\nexport function isAllowedImageHost(url: string): boolean {\n let host: string\n try {\n host = new URL(url).hostname.toLowerCase()\n } catch {\n return false\n }\n return BLOB_HOST_ALLOWLIST.some(\n (allowed) => host === allowed || host.endsWith(\".\" + allowed)\n )\n}\n","// Colour resolution — DERIVED from the variant matrix, never hardcoded (CAT-3 forward-compat).\n//\n// The dual-colour catalog (memory: dual-colour-catalog) gives each design ONE product with a\n// Colour × Size variant matrix. A variant's colour comes from, in order of trust:\n// 1. the variant option titled \"Colour\" (Model A: options {Size, Colour}, value title-cased)\n// 2. the variant title, format \"<size> / <Colour>\" (e.g. \"S / White\", \"M / Black\")\n// We NEVER hardcode white/black — future colours (CAT-3) fall out of the data for free.\n//\n// This lives in @binaryandthread/store-client (the canonical types home) so the CLI and the MCP\n// resolve colour the SAME way — written once, tested once.\n\nimport type { ProductLike, VariantLike, ImageLike } from \"./types\"\n\n/** Title-case a colour token for display (\"white\" → \"White\"); leaves multi-word reasonable. */\nexport function displayColour(colour: string): string {\n const c = colour.trim()\n if (!c) return c\n return c.charAt(0).toUpperCase() + c.slice(1).toLowerCase()\n}\n\n/**\n * The colour of a single variant, lowercased — or null if it carries no colour axis (a\n * legacy Size-only product). Reads the \"Colour\" option first, then parses the \"<size> / <Colour>\"\n * title. NEVER assumes a fixed palette.\n */\nexport function variantColour(v: VariantLike): string | null {\n // 1) the explicit Colour option (the authoritative source — memory: never parse the title).\n const opt = (v.options ?? []).find(\n (o) => (o.option?.title ?? \"\").toLowerCase() === \"colour\" || (o.option?.title ?? \"\").toLowerCase() === \"color\"\n )\n if (opt && typeof opt.value === \"string\" && opt.value.trim()) {\n return opt.value.trim().toLowerCase()\n }\n // 2) fall back to the title format \"<size> / <Colour>\".\n const title = (v.title ?? \"\").trim()\n if (title.includes(\"/\")) {\n const parts = title.split(\"/\").map((p) => p.trim())\n const tail = parts[parts.length - 1]\n if (tail) return tail.toLowerCase()\n }\n return null\n}\n\n/**\n * The DISTINCT colours offered by a product, in first-seen order, lowercased. Empty for a\n * legacy Size-only product (no Colour axis) — callers treat that as \"no colour to show\".\n */\nexport function productColours(p: ProductLike): string[] {\n const seen: string[] = []\n for (const v of p.variants ?? []) {\n const c = variantColour(v)\n if (c && !seen.includes(c)) {\n seen.push(c)\n }\n }\n return seen\n}\n\n/** The distinct sizes offered FOR a given colour (lowercased colour match), preserving order. */\nexport function sizesForColour(p: ProductLike, colour: string): string[] {\n const target = colour.trim().toLowerCase()\n const sizes: string[] = []\n for (const v of p.variants ?? []) {\n if (variantColour(v) !== target) continue\n const size = sizeOfVariant(v)\n if (size && !sizes.includes(size)) {\n sizes.push(size)\n }\n }\n return sizes\n}\n\n/** The size label of a variant — the Size option, else the title's leading \"<size>\" segment. */\nexport function sizeOfVariant(v: VariantLike): string | null {\n const opt = (v.options ?? []).find((o) => (o.option?.title ?? \"\").toLowerCase() === \"size\")\n if (opt && typeof opt.value === \"string\" && opt.value.trim()) {\n return opt.value.trim()\n }\n const title = (v.title ?? \"\").trim()\n if (title.includes(\"/\")) {\n const head = title.split(\"/\")[0]?.trim()\n if (head) return head\n }\n // No colour axis → the whole title IS the size (legacy Size-only product).\n return title || null\n}\n\n/**\n * Resolve a variant by size AND colour (the real buyability gap — bt stage / MCP add_to_cart).\n * - size + colour given → exact match on both (case-insensitive).\n * - size only → first variant of that size.\n * - colour only → first variant of that colour.\n * - neither → first variant.\n * Returns undefined when the requested size+colour combination does not exist (the caller MUST\n * surface a clear error — never silently substitute another colour the buyer didn't ask for).\n */\nexport function findVariant(\n p: ProductLike,\n opts: { size?: string; colour?: string } = {}\n): VariantLike | undefined {\n const variants = p.variants ?? []\n const size = opts.size?.trim().toLowerCase()\n const colour = opts.colour?.trim().toLowerCase()\n\n const sizeMatches = (v: VariantLike): boolean => {\n if (!size) return true\n return (sizeOfVariant(v) ?? \"\").toLowerCase() === size || (v.title ?? \"\").toLowerCase() === size\n }\n const colourMatches = (v: VariantLike): boolean => {\n if (!colour) return true\n return variantColour(v) === colour\n }\n\n if (size && colour) {\n // both specified → require BOTH; no fallback to a different colour (a wrong colour is worse\n // than a clear \"that combo isn't available\").\n return variants.find((v) => sizeMatches(v) && colourMatches(v))\n }\n if (colour && !size) {\n return variants.find((v) => colourMatches(v))\n }\n if (size && !colour) {\n // size specified → require it; NO fallback to variants[0] (L10) — a silently-wrong size is worse\n // than undefined, which lets the caller (stage.ts) surface a clear \"no matching variant\" error.\n return variants.find((v) => sizeMatches(v))\n }\n return variants[0]\n}\n\n/**\n * The image URL to render for a given colour. Resolves, in order:\n * 1. the matching variant's own images[] (most precise)\n * 2. a product image whose url/metadata names the colour\n * 3. null → the caller falls back to product.thumbnail + a \"black available\" note.\n * Never throws; a missing colour just yields null.\n */\nexport function imageForColour(p: ProductLike, colour: string): string | null {\n const target = colour.trim().toLowerCase()\n if (!target) return p.thumbnail ?? firstImageUrl(p.images) ?? null\n\n // 1) the colour's variant images.\n for (const v of p.variants ?? []) {\n if (variantColour(v) !== target) continue\n const url = firstImageUrl(v.images)\n if (url) return url\n }\n\n // 2) a product image that names the colour (url substring or metadata.colour tag).\n for (const img of p.images ?? []) {\n const tag = (img.metadata?.[\"colour\"] ?? img.metadata?.[\"color\"] ?? \"\")\n .toString()\n .toLowerCase()\n if (tag === target) {\n const u = (img.url ?? \"\").trim()\n if (u) return u\n }\n }\n for (const img of p.images ?? []) {\n const u = (img.url ?? \"\").trim()\n if (u && new RegExp(`(^|[^a-z])${escapeRegex(target)}([^a-z]|$)`, \"i\").test(u)) {\n return u\n }\n }\n return null\n}\n\nfunction firstImageUrl(images: Array<ImageLike> | null | undefined): string | null {\n for (const img of images ?? []) {\n const u = (img?.url ?? \"\").trim()\n if (u) return u\n }\n return null\n}\n\nfunction escapeRegex(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\")\n}\n","// Voice helpers — the deadpan B&T Agent tone (VOICE.md).\n//\n// Rules these helpers enforce mechanically (VOICE §1.2):\n// - lowercase terminal body text (proper nouns kept by the caller; helpers don't shout)\n// - NO exclamation marks in the CLI, ever (logs don't shout)\n// - the established glyph set only: ✓ ✗ ⚠ ⚙ →\n//\n// Error doctrine (VOICE §1.4): every error names a plausible exit code (the joke), then\n// states what did NOT happen to your money/order (the reassurance) BEFORE the fix, then a\n// copy-pasteable fix LAST. If money is in scope, \"nothing was charged\" comes first.\n\nexport const GLYPH = {\n ok: \"✓\",\n fail: \"✗\",\n warn: \"⚠\",\n gear: \"⚙\",\n arrow: \"→\",\n} as const\n\n/** Strip exclamation marks — the single fastest way to sound like a mascot (VOICE §1.2). */\nfunction noShout(s: string): string {\n return s.replace(/!+/g, \".\")\n}\n\n/** A success log line. The flex is the fact stated flatly. */\nexport function ok(msg: string): string {\n return `${GLYPH.ok} ${noShout(msg)}`\n}\n\n/** A warning / confirm line. Calm, not alarmist. */\nexport function warn(msg: string): string {\n return `${GLYPH.warn} ${noShout(msg)}`\n}\n\n/** A progress/working line. */\nexport function working(msg: string): string {\n return `${GLYPH.gear} ${noShout(msg)}`\n}\n\n/** An empty-state line — the best place for one dry true line (low stakes, high charm). */\nexport function empty(msg: string): string {\n return noShout(msg)\n}\n\nexport interface FailSpec {\n /** A plausible-real exit code or HTTP status (the joke). */\n code: number | string\n /** what went wrong. */\n what: string\n /** money/order reassurance — printed BEFORE the fix when present (non-negotiable). */\n money?: string\n /** a copy-pasteable fix, printed last. */\n fix?: string\n /** an optional link line (e.g. the checkout URL). */\n link?: string\n}\n\n/**\n * An error block. Order is doctrine (VOICE §1.4):\n * line 1: ✗ exit <code> — <what>\n * line 2: <money reassurance> (only if money is in scope — comes first)\n * line 3: fix → <fix>\n * line 4: link → <link>\n */\nexport function fail(spec: FailSpec): string {\n const lines: string[] = []\n lines.push(`${GLYPH.fail} exit ${spec.code} — ${noShout(spec.what)}`)\n if (spec.money) {\n lines.push(` ${noShout(spec.money)}`)\n }\n if (spec.fix) {\n lines.push(` fix ${GLYPH.arrow} ${noShout(spec.fix)}`)\n }\n if (spec.link) {\n lines.push(` link ${GLYPH.arrow} ${spec.link}`)\n }\n return lines.join(\"\\n\")\n}\n\n// ----- easter-egg machinery (governed copy; the rest land in Wave-3 polish) --\n\n/**\n * C1 — the canonical teapot egg. Evergreen Core (★). Delivered straight, cites the RFC.\n * `bt brew coffee` → 418. Implemented here as the canonical example; the rest of the egg\n * catalog (C2–C18) is a governed Wave-3 polish pass (VOICE §3.5 five-gate sign-off).\n */\nexport function brewCoffee(): string {\n // Canonical copy now lives in eggs.teapot(); kept here for back-compat. Brand-voice compliant\n // (no \"print\"/\"shirts\" — memory brand-voice-no-pod-no-tshirt).\n return `${GLYPH.fail} 418 i'm a teapot. we make clothing, not coffee. (RFC 2324)`\n}\n\n/**\n * C4 — version codename. Codenames rotate per minor release; each is a real dev holy war,\n * stated deadpan. Full rotation is governed copy (Wave-3); this returns the seed codename.\n */\nconst CODENAMES = [\n \"tabs-over-spaces\",\n \"vim-not-emacs\",\n \"2-space-indent\",\n \"trailing-comma\",\n] as const\n\nexport function codename(_version: string): string {\n // Stable seed for now; the per-minor rotation table is a Wave-3 governed addition.\n return CODENAMES[0]\n}\n\n/** `bt --version` line: `bt <semver> \"<codename>\"`. */\nexport function versionLine(version: string): string {\n return `bt ${version} \"${codename(version)}\"`\n}\n","// Brand surface — ASCII framing + SPARING cyan accent for the human-facing CLI screens.\n//\n// Design system (design-system/MASTER.md §1.1): on a dark terminal, cyan is the signature\n// accent — used on KEY elements only (prices, the ✓, headers), never washing everything.\n// Box-drawing rules give the IDE/terminal structure the brand wants (VOICE §1, B1).\n//\n// HARD constraints (do not break):\n// - --json output stays MACHINE-CLEAN: brand framing/colour is NEVER applied in --json mode.\n// - NO_COLOR / non-TTY / piped stdout → NO ANSI escapes (colour degrades to plain text).\n// - the box rules are pure ASCII-ish box-drawing chars (no colour) so they survive NO_COLOR.\n//\n// A BrandStyle is an explicit value object (color on/off) so rendering is deterministic and\n// unit-testable; makeBrand() builds the auto-detected one for bin.ts.\n\n/** ANSI codes — only emitted when color is enabled. */\nconst ANSI = {\n reset: \"\\x1b[0m\",\n // cyan: the signature accent. On a dark terminal this is the neon \"glow\" the design system\n // reserves for key elements. 38;5;48 ≈ #00ffc2 family on 256-colour terminals.\n cyan: \"\\x1b[38;5;48m\",\n // green: reserved for the contribution graph ONLY — the GitHub-contribution-green flex on the\n // `bt whoami` card. Not a general accent (that's cyan); a deliberate, single second colour.\n green: \"\\x1b[38;5;42m\",\n dim: \"\\x1b[2m\",\n bold: \"\\x1b[1m\",\n} as const\n\nexport interface BrandStyle {\n /** whether ANSI colour may be emitted (false under NO_COLOR / non-TTY / --no-color / --json). */\n readonly color: boolean\n}\n\n/** Detect whether colour should be emitted. NO_COLOR or a non-TTY stdout → no colour (degrade). */\nexport function colorEnabled(opts: { noColor?: boolean; isTTY?: boolean } = {}): boolean {\n if (opts.noColor) return false\n if (process.env.NO_COLOR !== undefined && process.env.NO_COLOR !== \"\") return false\n const tty = opts.isTTY ?? Boolean(process.stdout.isTTY)\n return tty\n}\n\n/** Build a BrandStyle. In --json mode the caller passes color:false explicitly (machine-clean). */\nexport function makeBrand(opts: { noColor?: boolean; isTTY?: boolean } = {}): BrandStyle {\n return { color: colorEnabled(opts) }\n}\n\n/** A no-colour brand (used in --json paths and tests that assert plain text). */\nexport const PLAIN: BrandStyle = { color: false }\n\nfunction wrap(style: BrandStyle, code: string, s: string): string {\n if (!style.color) return s\n return `${code}${s}${ANSI.reset}`\n}\n\n/** cyan accent — KEY elements only (prices, the ✓, header). Degrades to plain text. */\nexport function cyan(style: BrandStyle, s: string): string {\n return wrap(style, ANSI.cyan, s)\n}\n\n/** dim — secondary/handle text (the handle shown beside the title). */\nexport function dim(style: BrandStyle, s: string): string {\n return wrap(style, ANSI.dim, s)\n}\n\n/** green — the contribution-graph colour ONLY (the `bt whoami` flex). Degrades to plain text. */\nexport function green(style: BrandStyle, s: string): string {\n return wrap(style, ANSI.green, s)\n}\n\n/** bold — used sparingly on headers. */\nexport function bold(style: BrandStyle, s: string): string {\n return wrap(style, ANSI.bold, s)\n}\n\n// ----- box drawing (pure chars, NO colour — survives NO_COLOR verbatim) --------\n\nconst BOX = {\n tl: \"┌\",\n tr: \"┐\",\n bl: \"└\",\n br: \"┘\",\n h: \"─\",\n v: \"│\",\n ml: \"├\",\n mr: \"┤\",\n} as const\n\n/**\n * A header treatment: a left-anchored title with a trailing rule, e.g.\n * ── bt browse ─────────────────────────────────\n * The title may carry a cyan accent; the rule is plain. Deadpan, uncluttered (B1).\n */\nexport function header(style: BrandStyle, title: string, width = 56): string {\n const label = ` ${title} `\n const lead = BOX.h.repeat(2)\n const used = lead.length + label.length\n const tail = BOX.h.repeat(Math.max(2, width - used))\n return `${lead}${cyan(style, label)}${tail}`\n}\n\n/** A plain horizontal rule of the given width (no colour). */\nexport function rule(width = 56): string {\n return BOX.h.repeat(width)\n}\n\n/**\n * Frame a block of pre-rendered lines in a light box. The frame chars are plain (NO_COLOR-safe);\n * the inner lines keep whatever accent they already carry. ANSI escapes inside a line are not\n * counted toward width (so coloured content still aligns). Padding to the requested inner width\n * is best-effort — the box is structure, not a strict table.\n */\nexport function box(lines: string[], opts: { width?: number; title?: string; style?: BrandStyle } = {}): string[] {\n const style = opts.style ?? PLAIN\n const inner = opts.width ?? 56\n const out: string[] = []\n // top: ┌── title ──────┐ (title optional)\n if (opts.title) {\n const label = ` ${opts.title} `\n const lead = BOX.h.repeat(2)\n const used = lead.length + visibleLength(label)\n const tail = BOX.h.repeat(Math.max(1, inner - used))\n out.push(`${BOX.tl}${lead}${cyan(style, label)}${tail}${BOX.tr}`)\n } else {\n out.push(`${BOX.tl}${BOX.h.repeat(inner)}${BOX.tr}`)\n }\n for (const line of lines) {\n const pad = Math.max(0, inner - 1 - visibleLength(line))\n out.push(`${BOX.v} ${line}${\" \".repeat(pad)}${BOX.v}`)\n }\n out.push(`${BOX.bl}${BOX.h.repeat(inner)}${BOX.br}`)\n return out\n}\n\n/** ANSI-escape-stripped length, so coloured content doesn't break alignment. */\nexport function visibleLength(s: string): number {\n // eslint-disable-next-line no-control-regex\n return s.replace(/\\x1b\\[[0-9;]*m/g, \"\").length\n}\n\n// ----- contribution graph (the `bt whoami` flex) -------------------------------\n\n/**\n * The contribution bar for `bt whoami` — one filled green cell per committed order, up to a cap\n * (the core threshold, so a full bar == you reached core). HONEST by construction: each filled cell\n * is one REAL captured order you committed — NOT a faked time-series. (A true GitHub-style per-month\n * grid needs order created_at buckets from the backend — a read-only follow-up, noted in STATE.)\n *\n * Filled cells carry the contribution-green; empty cells are plain ░ so the bar still reads under\n * NO_COLOR. The width is fixed (= cap) so the card aligns regardless of count.\n */\nexport function contributionBar(style: BrandStyle, commits: number, cap = 20): string {\n const n = Number.isFinite(commits) ? Math.max(0, Math.floor(commits)) : 0\n const filledCount = Math.min(n, cap)\n const filled = green(style, \"▇\".repeat(filledCount))\n const empty = \"░\".repeat(Math.max(0, cap - filledCount))\n return `${filled}${empty}`\n}\n\n// ----- colour markers (A3 INFORM) ----------------------------------------------\n\n/** The swatch glyph for a colour name — ⬜ white, ⬛ black, ◼ for anything else (deadpan). */\nexport function colourSwatch(colour: string): string {\n const c = colour.trim().toLowerCase()\n if (c === \"white\") return \"⬜\"\n if (c === \"black\") return \"⬛\"\n return \"◼\"\n}\n\n/**\n * A compact colour marker for a product row: the swatches + the names, e.g. \"⬜⬛ white/black\".\n * Empty string when the product has no colour axis (a legacy Size-only product).\n */\nexport function colourMarker(colours: string[]): string {\n if (colours.length === 0) return \"\"\n const swatches = colours.map((c) => colourSwatch(c)).join(\"\")\n const names = colours.join(\"/\")\n return `${swatches} ${names}`\n}\n","// Shared types + helpers for the bt read/cart command handlers.\n//\n// Each handler is a PURE function (client, config, args) → HandlerResult so it can be unit\n// tested without a live backend or a real filesystem. bin.ts wires them to commander and\n// performs the side effects (print lines, save config).\n\nimport type { Command } from \"commander\"\nimport { variantColour } from \"@binaryandthread/store-client\"\nimport type { StoreClient, ProductLike, VariantLike, CartLineItemLike, BtConfigLike } from \"./types-bridge\"\nimport type { BrandStyle } from \"../brand\"\nimport { PLAIN, makeBrand } from \"../brand\"\n\nexport interface HandlerResult {\n /** human-readable, voice-compliant output lines. */\n lines: string[]\n /** machine-readable payload for --json (when requested). */\n json?: unknown\n /** a config patch the router must persist atomically (e.g. { cart_id }). */\n save?: Partial<BtConfigLike>\n}\n\n/**\n * The optional brand style threaded into a handler's args. Handlers default to PLAIN (no colour)\n * so tests assert deterministic plain text; bin.ts injects the auto-detected style for real runs.\n * In --json mode the router passes PLAIN so machine output is never coloured.\n */\nexport interface Branded {\n brand?: BrandStyle\n}\n\n/** A handler's brand style, defaulting to PLAIN (no colour, no ANSI). */\nexport function brandOf(args: Branded | undefined): BrandStyle {\n return args?.brand ?? PLAIN\n}\n\nexport type ConfigGetter = () => BtConfigLike\nexport type ClientFactory = () => StoreClient\n\nexport interface CommandDeps {\n makeClient: ClientFactory\n loadConfig: ConfigGetter\n}\n\n/** Price as a flat SEK-ish line — currency comes from the variant/region, never a literal. */\nexport function fmtPrice(amount: number | null | undefined, currency: string | null | undefined): string {\n if (amount == null) {\n return \"—\"\n }\n const cur = (currency ?? \"\").toUpperCase()\n // amounts are stored as-is (not cents) per backend convention.\n return cur ? `${amount} ${cur}` : `${amount}`\n}\n\n/** First variant's calculated price + currency. */\nexport function firstPrice(p: ProductLike): { amount: number | null; currency: string | null } {\n const v = (p.variants ?? [])[0]\n return {\n amount: v?.calculated_price?.calculated_amount ?? null,\n currency: v?.calculated_price?.currency_code ?? null,\n }\n}\n\n/** Resolve a variant by size label (case-insensitive option/title match). */\nexport function findVariantBySize(p: ProductLike, size?: string): VariantLike | undefined {\n const variants = p.variants ?? []\n if (!size) {\n return variants[0]\n }\n const target = size.toLowerCase()\n return (\n variants.find((v) => (v.title ?? \"\").toLowerCase() === target) ??\n variants.find((v) =>\n (v.options ?? []).some((o) => (o.value ?? \"\").toLowerCase() === target)\n ) ??\n variants[0]\n )\n}\n\n/** stage marker: ● in stock (main) / ○ on next. */\nexport function stageMarker(channelStage: string | undefined): string {\n return channelStage === \"next\" ? \"○ on next\" : \"● in stock\"\n}\n\n/**\n * The PRIMARY display name for a product (A1): the TITLE — the name checkout/web shows —\n * with the handle shown secondarily/dimmed (still needed for `bt view`/`bt stage`). Falls back\n * to the handle when a product somehow has no title. The handle keeps its case unchanged (A1).\n */\nexport function displayName(p: { title?: string | null; handle?: string | null; id?: string }): {\n title: string\n handle: string\n} {\n const handle = (p.handle ?? p.id ?? \"\").toString()\n const title = (p.title ?? \"\").toString().trim() || handle\n return { title, handle }\n}\n\n/**\n * The colour carried by a cart line — parsed from the variant title the server stores\n * (\"<size> / <Colour>\"), so `bt cart`/`status` can show it clearly (A3). Returns null when the\n * line has no colour axis. The line's `title` field is the product title; the colour rides the\n * variant title, surfaced separately by the server payload where available.\n */\nexport function lineColour(item: { variant_title?: string | null; title?: string | null }): string | null {\n const vt = (item.variant_title ?? \"\").toString()\n if (vt) {\n const c = variantColour({ id: \"x\", title: vt })\n if (c) return c\n }\n return null\n}\n\n/** A short, human line-item handle for display — the last 6 chars of the id (BUG 2). */\nexport function shortItemId(id: string | null | undefined): string {\n const s = (id ?? \"\").toString()\n return s.length <= 6 ? s : s.slice(-6)\n}\n\n/**\n * Render one staged line with a STABLE per-line reference (BUG 2): a 1-based index and the\n * short line-item id, so `bt unstage <ref>` has something concrete to target. Shows the colour\n * the line carries (A3) and accents the price in cyan when a brand style is given. Currency\n * comes from the cart/region, never a literal.\n */\nexport function formatCartLine(\n item: CartLineItemLike,\n index: number,\n currency: string | null | undefined,\n brand?: BrandStyle\n): string {\n const style = brand ?? PLAIN\n const ref = `[${index + 1}]`\n const title = (item.title ?? item.product_title ?? \"item\").toString()\n const idTag = item.id ? dimTag(style, ` (#${shortItemId(item.id)})`) : \"\"\n const colour = lineColour(item)\n const colourTag = colour ? ` · ${colour}` : \"\"\n const price = item.total != null ? ` ${accentPrice(style, fmtPrice(item.total, currency))}` : \"\"\n return ` ${ref} ${title}${colourTag}${idTag} · qty ${item.quantity}${price}`\n}\n\n/** dim a secondary tag (the short id) — plain text under NO_COLOR. Lazy import avoids a cycle. */\nfunction dimTag(style: BrandStyle, s: string): string {\n return style.color ? `\\x1b[2m${s}\\x1b[0m` : s\n}\n\n/** cyan-accent a price — plain text under NO_COLOR (design system: cyan on key elements only). */\nfunction accentPrice(style: BrandStyle, s: string): string {\n return style.color ? `\\x1b[38;5;48m${s}\\x1b[0m` : s\n}\n\n/**\n * Resolve a user-supplied `<ref>` against the cart's line items to a real line-item id (BUG 2).\n * Accepts, in order: a 1-based index (\"1\"), an exact line-item id, the short id (last 6 chars),\n * or a case-insensitive substring of the product title/handle. Returns null when nothing matches\n * (the caller MUST surface a clear error, never a false success).\n */\nexport function resolveLineItemRef(\n items: Array<CartLineItemLike>,\n ref: string\n): CartLineItemLike | null {\n const needle = (ref ?? \"\").trim()\n if (!needle || items.length === 0) {\n return null\n }\n\n // 1) a 1-based index.\n if (/^\\d+$/.test(needle)) {\n const idx = Number(needle) - 1\n if (idx >= 0 && idx < items.length) {\n return items[idx]!\n }\n }\n\n // 2) an exact line-item id, or the short (last-6) id.\n const byId = items.find((li) => li.id === needle || shortItemId(li.id) === needle)\n if (byId) {\n return byId\n }\n\n // 3) a case-insensitive substring of the title (the product word, e.g. \"bezier\"/\"terminal\").\n const lower = needle.toLowerCase()\n const byTitle = items.find((li) =>\n ((li.title ?? li.product_title ?? \"\").toString().toLowerCase()).includes(lower)\n )\n return byTitle ?? null\n}\n\n/**\n * Resolve the global --json / --no-color flags from commander into the output mode every command\n * shares: the `json` boolean and the `brand` style. In --json mode colour is always off (machine\n * output is never coloured); otherwise colour follows --no-color (`opts.color === false`).\n */\nexport function resolveOutput(program: Command): { json: boolean; brand: BrandStyle } {\n const opts = program.opts() as { json?: boolean; color?: boolean }\n const json = Boolean(opts.json)\n const brand = json ? makeBrand({ noColor: true }) : makeBrand({ noColor: opts.color === false })\n return { json, brand }\n}\n\n/** Print a handler result (shared by the command actions). */\nexport function printResult(res: HandlerResult, json: boolean): void {\n if (json && res.json !== undefined) {\n process.stdout.write(JSON.stringify(res.json, null, 2) + \"\\n\")\n return\n }\n process.stdout.write(res.lines.join(\"\\n\") + \"\\n\")\n}\n","// Image ladder (ROBUSTNESS §6, F-A3).\n//\n// Degrades: Kitty/iTerm2/Sixel inline (terminal-image auto-detects) → Unicode half-block\n// → plain text + the Blob URL FLOOR. The Blob URL is ALWAYS emitted as a text line so a\n// buy is never blocked on rendering. Catches every error → \"(image unavailable)\\n<url>\".\n//\n// CLAUDE.md rule 6 / SECURITY G9: only fetch images from OUR Vercel Blob host. Any other\n// host is refused before any network fetch (no SSRF, no brand-IP leak).\n//\n// The allowlist constant + check now live in @binaryandthread/store-client (the CANONICAL\n// source) so the Wave-4 MCP can reuse them WITHOUT depending on this CLI package. We re-export\n// them here so existing CLI imports (and tests) keep working unchanged. The dot-delimited\n// subdomain guard inside isAllowedImageHost is load-bearing security — see store-client.\nexport { BLOB_HOST_ALLOWLIST, isAllowedImageHost } from \"@binaryandthread/store-client\"\nimport { isAllowedImageHost } from \"@binaryandthread/store-client\"\n\nexport interface RenderImageOpts {\n /** Max render height in terminal rows (advisory; passed to terminal-image). */\n height?: number\n /** Respect NO_COLOR — when set we skip ANSI rendering and go straight to the floor. */\n noColor?: boolean\n /** Test seam: force the no-renderer path (URL-only floor). */\n forceNoRenderer?: boolean\n /** Test seam: inject a renderer. Resolves to the rendered string or throws. */\n renderer?: (url: string) => Promise<string>\n /** Test seam: override the byte cap (defaults to IMAGE_BYTE_CAP). */\n byteCap?: number\n}\n\n/**\n * The default render height in terminal rows. The tee mockup is portrait (≈4:5), so a taller\n * render reads sharper than the old 20-row default — both on the high-fidelity inline protocols\n * (iTerm2/Kitty, where rows ≈ image height) and on the half-block fallback (where each row packs\n * two pixels). 28 rows fits a standard terminal without scrolling the prompt off-screen.\n */\nconst DEFAULT_RENDER_HEIGHT = 28\n\n/** Hyperlink-ish floor line: just the plain URL (OSC-8 added by the caller's terminal where supported). */\nfunction urlFloor(url: string): string {\n return ` ${url}`\n}\n\n/** https-only, parity with the MCP image path — an http:/data: URL is never fetched (no plaintext, no inline payload). */\nfunction isHttpsUrl(url: string): boolean {\n try {\n return new URL(url).protocol === \"https:\"\n } catch {\n return false\n }\n}\n\n/**\n * Render an image for the terminal. NEVER throws. ALWAYS returns a string ending with\n * the Blob URL floor. The render is best-effort garnish; the URL is the guarantee.\n */\nexport async function renderImage(blobUrl: string, opts: RenderImageOpts = {}): Promise<string> {\n const noColor = opts.noColor ?? Boolean(process.env.NO_COLOR)\n\n // Refuse a non-allowlisted host OR a non-https URL BEFORE any fetch (rule 6 / G9 — parity with the\n // MCP image path: https-only means no http: plaintext and no data: inline payload). Still emit the floor.\n if (!isAllowedImageHost(blobUrl) || !isHttpsUrl(blobUrl)) {\n return ` (image host not allowed — showing link only)\\n${urlFloor(blobUrl)}`\n }\n\n // NO_COLOR or an explicit no-renderer request → URL-only floor.\n if (noColor || opts.forceNoRenderer) {\n return urlFloor(blobUrl)\n }\n\n try {\n const rendered = opts.renderer\n ? await opts.renderer(blobUrl)\n : await defaultRender(blobUrl, opts)\n return `${rendered}\\n${urlFloor(blobUrl)}`\n } catch {\n // Any failure (blob 404, no terminal protocol, fetch error) → graceful floor (F-A3, M9).\n return ` (image unavailable)\\n${urlFloor(blobUrl)}`\n }\n}\n\n/** Bound the Blob fetch — a hung CDN must not stall `bt view` (matches the MCP image path). */\nconst IMAGE_FETCH_TIMEOUT_MS = 5000\n\n/** ≤2MB — parity with the MCP image path (token/memory-DoS guard, G9). Over this → graceful URL floor. */\nconst IMAGE_BYTE_CAP = 2 * 1024 * 1024\n\n/** Default renderer using terminal-image (lazy import — keeps the native sharp dep off cold paths). */\nasync function defaultRender(url: string, opts: RenderImageOpts): Promise<string> {\n // Lazy dynamic import so a CLI that never views an image doesn't pay the sharp load.\n const mod = (await import(\"terminal-image\")) as {\n default?: { buffer?: (buf: Buffer, o?: unknown) => Promise<string> }\n buffer?: (buf: Buffer, o?: unknown) => Promise<string>\n }\n const terminalImage = mod.default ?? mod\n const bufferFn = terminalImage.buffer\n if (!bufferFn) {\n throw new Error(\"terminal-image.buffer unavailable\")\n }\n // Timeout the fetch: a hung connection (no bytes, no error) would otherwise stall forever.\n // On abort/timeout this throws → caught by renderImage → graceful URL floor (F-A3).\n const ctrl = new AbortController()\n const timer = setTimeout(() => ctrl.abort(), IMAGE_FETCH_TIMEOUT_MS)\n let res: Response\n try {\n res = await fetch(url, { signal: ctrl.signal })\n } finally {\n clearTimeout(timer)\n }\n if (!res.ok) {\n throw new Error(`blob fetch failed: HTTP ${res.status}`)\n }\n // Byte cap (parity with the MCP image path): reject an over-cap body — pre-check the server-declared\n // size, then the actual buffer (Content-Length can be absent/wrong). Over-cap throws → caught by\n // renderImage → graceful URL floor (F-A3), never a huge buffer into memory.\n const cap = opts.byteCap ?? IMAGE_BYTE_CAP\n const declaredLen = Number.parseInt(res.headers?.get?.(\"content-length\") ?? \"\", 10)\n if (Number.isFinite(declaredLen) && declaredLen > cap) {\n throw new Error(\"blob exceeds byte cap (declared)\")\n }\n const buf = Buffer.from(await res.arrayBuffer())\n if (buf.byteLength === 0 || buf.byteLength > cap) {\n throw new Error(\"blob empty or exceeds byte cap\")\n }\n // B5 — sharpness: terminal-image's `buffer()` already auto-detects the HIGH-FIDELITY inline\n // protocols (Kitty graphics, then iTerm2 via term-img) and ONLY falls back to the chunky Unicode\n // half-block when neither is present. We pass `preferNativeRender: true` EXPLICITLY so a future\n // default change can't silently force the half-block path, and `preserveAspectRatio: true` so the\n // portrait tee isn't squashed. The height is tuned up from 20 → 28 rows for a sharper render; we\n // leave width unset so it derives from the image's own aspect ratio (capped to the terminal).\n return bufferFn(buf, {\n height: opts.height ?? DEFAULT_RENDER_HEIGHT,\n preserveAspectRatio: true,\n preferNativeRender: true,\n })\n}\n","// bt view <handle> — render a tee: specs (sizes/variants/price) + the image via the ladder,\n// ALWAYS with the Blob URL as the text floor (VOICE §2.1, F-A3).\n//\n// A1: TITLE is the primary name; the handle is shown (dimmed) so the user knows what to pass to\n// bt stage/bt view.\n// A3: a `colours: ⬜ white · ⬛ black` line, sizes grouped per colour, and `--colour black` renders\n// the BLACK mockup image (resolved from the variant/product images; falls back to the white\n// image + a \"black available\" note when not resolvable).\n// B1: a light header treatment; cyan price accent. Degrades under --json / NO_COLOR.\n\nimport type { Command } from \"commander\"\nimport { empty, fail } from \"../voice\"\nimport { productColours, sizesForColour, imageForColour, displayColour } from \"@binaryandthread/store-client\"\nimport { renderImage } from \"../image-ladder\"\nimport { fmtPrice, displayName, brandOf, printResult, resolveOutput, type HandlerResult, type Branded } from \"./shared\"\nimport { header, cyan, dim, colourSwatch } from \"../brand\"\nimport type { StoreClient, BtConfigLike } from \"./types-bridge\"\n\nexport interface ViewArgs extends Branded {\n handle: string\n colour?: string\n json?: boolean\n noColor?: boolean\n}\n\nexport async function runView(\n client: StoreClient,\n config: BtConfigLike,\n args: ViewArgs\n): Promise<HandlerResult> {\n const style = brandOf(args)\n const region = await client.resolveRegion(config.region)\n const product = await client.getProduct(args.handle, { region })\n\n if (!product) {\n // dry not-found — exit-code framing, no '!'\n return {\n lines: [\n fail({\n code: 1,\n what: `variant ${args.handle} not found.`,\n fix: \"bt browse (to see what's in stock)\",\n }),\n ],\n }\n }\n\n const { title, handle } = displayName(product)\n const colours = productColours(product)\n\n const lines: string[] = []\n lines.push(header(style, title))\n lines.push(` ${dim(style, handle)}`)\n if (product.description) {\n lines.push(empty(` ${product.description}`))\n }\n\n const fabric = (product.metadata?.[\"fabric\"] as string) ?? null\n if (fabric) {\n lines.push(` fabric ${fabric}`)\n }\n\n // colours line + sizes grouped per colour (A3).\n if (colours.length > 0) {\n lines.push(` colours ${colours.map((c) => `${colourSwatch(c)} ${c}`).join(\" · \")}`)\n for (const c of colours) {\n const sizes = sizesForColour(product, c)\n lines.push(` ${colourSwatch(c)} ${c.padEnd(6)} ${sizes.join(\" \")}`)\n }\n } else {\n // legacy Size-only product — the flat size list.\n const sizes = (product.variants ?? []).map((v) => v.title ?? \"?\")\n lines.push(` sizes ${sizes.join(\" \")}`)\n }\n\n const v0 = (product.variants ?? [])[0]\n if (v0?.calculated_price) {\n lines.push(\n ` price ${cyan(style, fmtPrice(v0.calculated_price.calculated_amount, v0.calculated_price.currency_code))} incl. VAT`\n )\n }\n\n // pick the image to render: the requested colour's mockup, or the default thumbnail (A3).\n let imageUrl: string | null = product.thumbnail ?? null\n let blackNote: string | null = null\n if (args.colour) {\n const wanted = args.colour.trim().toLowerCase()\n const resolved = imageForColour(product, wanted)\n if (resolved) {\n imageUrl = resolved\n } else {\n // can't resolve the colour-specific image → render the default + a clear note (A3).\n blackNote = ` ${displayColour(wanted)} available — showing the default mockup (no ${wanted} render on file).`\n }\n }\n\n if (imageUrl) {\n const rendered = await renderImage(imageUrl, { noColor: args.noColor })\n lines.push(rendered)\n }\n if (blackNote) {\n lines.push(blackNote)\n }\n if (colours.length > 1 && !args.colour) {\n const others = colours.filter((c) => c !== colours[0]).join(\", \")\n lines.push(` see another colour → bt view ${handle} --colour ${others.split(\",\")[0]?.trim()}`)\n }\n\n return {\n lines,\n json: args.json\n ? {\n id: product.id,\n handle: product.handle,\n title: product.title,\n fabric,\n colours,\n thumbnail: product.thumbnail,\n variants: (product.variants ?? []).map((v) => ({\n id: v.id,\n size: v.title,\n price: v.calculated_price?.calculated_amount ?? null,\n currency: v.calculated_price?.currency_code ?? null,\n })),\n }\n : undefined,\n }\n}\n\nexport function registerView(\n program: Command,\n makeClient: () => StoreClient,\n loadConfig: () => BtConfigLike\n): void {\n program\n .command(\"view\")\n .alias(\"show\")\n .argument(\"<handle>\", \"the product handle/slug\")\n .option(\"--colour <colour>\", \"render a specific colour's mockup (e.g. black)\")\n .description(\"render a design: specs + image (always with the link as the floor).\")\n .action(async (handle: string, opts: { colour?: string }) => {\n const { json, brand } = resolveOutput(program)\n const noColor = (program.opts() as { color?: boolean }).color === false\n const res = await runView(makeClient(), loadConfig(), {\n handle,\n colour: opts.colour,\n json,\n noColor,\n brand,\n })\n printResult(res, json)\n })\n}\n","// bt stage <handle> [--size L] — reserve stock (git add).\n//\n// Flow (F-B1/F-B2/F-B3):\n// 1. resolve the product + variant (size → variant; default from config.defaultSize)\n// 2. get-or-create a cart (reuse config.cart_id when present, else createCart)\n// 3. addLineItem; on a RecoverableCartError (404/409/cart.completed) DISCARD the local\n// cart_id, create a FRESH cart, re-add, and TELL the user — never silently lose the item\n// 4. persist the cart_id via config.save (the router persists it atomically) BEFORE returning\n// 5. always re-read the cart and render the authoritative server state\n\nimport type { Command } from \"commander\"\nimport { ok, warn, fail } from \"../voice\"\nimport { formatCartLine, brandOf, printResult, resolveOutput, type HandlerResult, type Branded } from \"./shared\"\nimport { RecoverableCartError, findVariant, productColours } from \"@binaryandthread/store-client\"\nimport type { StoreClient, BtConfigLike, RegionLike, CartLike } from \"./types-bridge\"\n\nexport interface StageArgs extends Branded {\n handle: string\n size?: string\n colour?: string\n json?: boolean\n /** resolve + preview the variant WITHOUT mutating any cart (d21). */\n dryRun?: boolean\n}\n\nexport async function runStage(\n client: StoreClient,\n config: BtConfigLike,\n args: StageArgs\n): Promise<HandlerResult> {\n const style = brandOf(args)\n const region: RegionLike = await client.resolveRegion(config.region)\n\n const product = await client.getProduct(args.handle, { region })\n if (!product) {\n return {\n lines: [\n fail({ code: 1, what: `variant ${args.handle} not found.`, fix: \"bt browse\" }),\n ],\n }\n }\n\n const size = args.size ?? config.defaultSize\n // colour: explicit flag → config.defaultColour → first colour the product offers (A3, never\n // hardcoded). A Size-only product has no colour axis (colours = []) and ignores colour.\n const colours = productColours(product)\n const colour =\n args.colour ?? config.defaultColour ?? (colours.length > 0 ? colours[0] : undefined)\n\n const variant = findVariant(product, { size, colour })\n if (!variant) {\n // a clear error when the size+colour combo doesn't exist — never silently pick another colour.\n const have = colours.length ? ` available colours: ${colours.join(\", \")}.` : \"\"\n return {\n lines: [\n fail({\n code: 1,\n what:\n `no variant for ${args.handle}${size ? `#size=${size}` : \"\"}` +\n `${colour ? ` (${colour})` : \"\"}.`,\n fix: `bt view ${args.handle} (to see available sizes + colours)${have}`,\n }),\n ],\n }\n }\n\n // --dry-run (d21): we've resolved a real variant — show what WOULD be staged, mutate nothing.\n // Re-running a dry-run is perfectly idempotent (no cart side-effects), unlike a real stage.\n if (args.dryRun) {\n const colourSuffix = colour ? ` (${colour})` : \"\"\n return {\n lines: [\n ok(`dry-run — would stage ${product.handle ?? args.handle}${size ? `#${size}` : \"\"}${colourSuffix} (nothing staged).`),\n \" run without --dry-run to acquire the inventory lock.\",\n ],\n json: args.json\n ? {\n dry_run: true,\n would_stage: {\n handle: product.handle ?? args.handle,\n size: size ?? null,\n colour: colour ?? null,\n variant_id: variant.id,\n },\n }\n : undefined,\n }\n }\n\n const lines: string[] = []\n let cartId = config.cart_id\n let recovered = false\n\n // get-or-create the cart\n if (!cartId) {\n const fresh = await client.createCart(region)\n cartId = fresh.id\n }\n\n // add the line item, recovering from a stale/completed cart (F-B2)\n try {\n await client.addLineItem(cartId, variant.id, 1)\n } catch (e) {\n if (e instanceof RecoverableCartError) {\n // discard the stale cart_id, create a fresh one, re-add — never lose the item\n const fresh = await client.createCart(region)\n cartId = fresh.id\n await client.addLineItem(cartId, variant.id, 1)\n recovered = true\n } else {\n throw e\n }\n }\n\n // re-read the authoritative server state (F-B3)\n const cart = await client.getCart(cartId)\n\n if (recovered) {\n lines.push(\n warn(\n \"your previous cart had expired or was already committed — created a fresh cart and re-added it.\"\n )\n )\n }\n // the staged ref carries the colour now (A3): \"the-viber#L (black)\".\n const colourSuffix = colour ? ` (${colour})` : \"\"\n lines.push(\n ok(\n `staged ${product.handle ?? args.handle}${size ? `#${size}` : \"\"}${colourSuffix} · inventory lock acquired (held 10m)`\n )\n )\n ;(cart.items ?? []).forEach((item, i) => {\n lines.push(formatCartLine(item, i, cart.currency_code, style))\n })\n const itemCount = (cart.items ?? []).reduce((n, li) => n + (li.quantity ?? 1), 0)\n lines.push(` ${itemCount} item${itemCount === 1 ? \"\" : \"s\"} staged → bt commit (or bt buy to do both)`)\n\n return {\n // persist the cart_id atomically BEFORE returning (the router calls config.save)\n save: { cart_id: cartId },\n lines,\n json: args.json\n ? { cart_id: cartId, recovered, colour: colour ?? null, items: cart.items ?? [], total: cart.total ?? null }\n : undefined,\n }\n}\n\nexport function registerStage(\n program: Command,\n makeClient: () => StoreClient,\n loadConfig: () => BtConfigLike\n): void {\n // saveConfig is imported lazily to keep handlers pure + testable.\n program\n .command(\"stage\")\n .alias(\"add\")\n .argument(\"<handle>\", \"the product handle/slug\")\n .option(\"--size <size>\", \"garment size (default: your last commit's size)\")\n .option(\"--colour <colour>\", \"colour, e.g. white / black (default: the product's first colour)\")\n .option(\"--dry-run\", \"resolve the variant + show what would be staged, without staging\")\n .description(\"reserve stock — acquires an inventory lock, reserves nothing permanent.\")\n .action(async (handle: string, opts: { size?: string; colour?: string; dryRun?: boolean }) => {\n const { json, brand } = resolveOutput(program)\n const res = await runStage(makeClient(), loadConfig(), {\n handle,\n size: opts.size,\n colour: opts.colour,\n dryRun: opts.dryRun,\n json,\n brand,\n })\n if (res.save) {\n // atomic persist (config.saveConfig uses temp-file + rename — F-B1)\n const { saveConfig } = await import(\"../config\")\n saveConfig(res.save)\n }\n printResult(res, json)\n })\n}\n"],"mappings":";;;AAOA,IAAM,iBAAiB,oBAAI,IAAI;EAC7B;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;AACF,CAAC;AAED,IAAM,OAAO;AAGb,IAAM,YAAY;AAElB,IAAM,WAAW;AAGV,SAAS,UAAU,OAAuB;AAC/C,QAAM,KAAK,MAAM,QAAQ,GAAG;AAC5B,MAAI,MAAM,GAAG;AACX,WAAO;EACT;AACA,QAAM,QAAQ,MAAM,MAAM,GAAG,EAAE;AAC/B,QAAM,OAAO,MAAM,MAAM,KAAK,CAAC;AAC/B,QAAM,cAAc,MAAM,CAAC,IAAI,IAAI,OAAO,KAAK,IAAI,GAAG,MAAM,SAAS,CAAC,CAAC;AACvE,QAAM,aAAa,KAAK,SAAS,IAAI,KAAK,CAAC,IAAI;AAC/C,SAAO,GAAG,WAAW,IAAI,UAAU;AACrC;AAEA,SAAS,aAAa,GAAmB;AACvC,SAAO,EAAE,QAAQ,WAAW,mBAAmB,EAAE;IAAQ;IAAU,CAAC,IAAI,IAAI,OAC1E,UAAU,GAAG,EAAE,IAAI,EAAE,EAAE;EACzB;AACF;AAYO,SAAS,OAAU,OAAU,OAAiC;AACnE,MAAI,SAAS,MAAM;AACjB,WAAO;EACT;AACA,MAAI,OAAO,UAAU,UAAU;AAC7B,WAAO,aAAa,KAAK;EAC3B;AACA,MAAI,OAAO,UAAU,UAAU;AAC7B,WAAO;EACT;AAEA,QAAM,OAAO,SAAS,oBAAI,IAAqB;AAC/C,QAAM,WAAW,KAAK,IAAI,KAAe;AACzC,MAAI,aAAa,QAAW;AAC1B,WAAO;EACT;AAEA,MAAI,MAAM,QAAQ,KAAK,GAAG;AACxB,UAAMA,OAAiB,CAAC;AACxB,SAAK,IAAI,OAAiBA,IAAG;AAC7B,eAAW,KAAK,OAAO;AACrBA,WAAI,KAAK,OAAO,GAAG,IAAI,CAAC;IAC1B;AACA,WAAOA;EACT;AAEA,QAAM,MAA+B,CAAC;AACtC,OAAK,IAAI,OAAiB,GAAG;AAC7B,aAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,KAAgC,GAAG;AAC3E,QAAI,eAAe,IAAI,IAAI,YAAY,CAAC,GAAG;AACzC,UAAI,GAAG,IAAI;IACb,WAAW,IAAI,YAAY,MAAM,WAAW,OAAO,UAAU,UAAU;AACrE,UAAI,GAAG,IAAI,UAAU,KAAK;IAC5B,OAAO;AACL,UAAI,GAAG,IAAI,OAAO,OAAO,IAAI;IAC/B;EACF;AACA,SAAO;AACT;AC9DA,IAAM,gBAA6B;EACjC,UAAU;EACV,aAAa;EACb,YAAY;AACd;AAGA,IAAM,qBAAqB;AAM3B,SAAS,kBAAkB,KAA8B;AACvD,MAAI,MAAqB;AACzB,MAAI;AACF,UAAM,IAAI,SAAS,MAAM,aAAa,KAAK;EAC7C,QAAQ;AACN,WAAO;EACT;AACA,MAAI,CAAC,KAAK;AACR,WAAO;EACT;AACA,QAAM,OAAO,OAAO,GAAG;AACvB,MAAI;AACJ,MAAI,OAAO,SAAS,IAAI,GAAG;AACzB,SAAK,OAAO;EACd,OAAO;AACL,UAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,QAAI,CAAC,OAAO,SAAS,MAAM,GAAG;AAC5B,aAAO;IACT;AACA,SAAK,SAAS,KAAK,IAAI;EACzB;AACA,SAAO,KAAK,IAAI,KAAK,IAAI,GAAG,EAAE,GAAG,kBAAkB;AACrD;AAGO,IAAM,uBAAN,cAAmC,MAAM;EACrC,cAAc;EACd;EACT,YAAY,SAAiB,QAAgB;AAC3C,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,SAAS;EAChB;AACF;AAGO,IAAM,gBAAN,cAA4B,MAAM;EAC9B;EACA;EACT,YAAY,SAAiB,QAAgB,MAAc;AACzD,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,SAAS;AACd,SAAK,OAAO;EACd;AACF;AAmBA,SAAS,MAAM,IAA2B;AACxC,SAAO,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,EAAE,CAAC;AACzD;AAGA,SAAS,UAAkB;AACzB,QAAM,IAAI;AACV,MAAI,EAAE,QAAQ,YAAY;AACxB,WAAO,EAAE,OAAO,WAAW;EAC7B;AAEA,SAAO,uCAAuC,QAAQ,SAAS,CAAC,MAAM;AACpE,UAAM,IAAK,KAAK,OAAO,IAAI,KAAM;AACjC,UAAM,IAAI,MAAM,MAAM,IAAK,IAAI,IAAO;AACtC,WAAO,EAAE,SAAS,EAAE;EACtB,CAAC;AACH;AAEO,IAAM,cAAN,MAAkB;EACN;EACA;EACA;EACA;EACA;EACA;EACA;;EAGT,eAAoC;EAE5C,YAAY,MAA0B;AACpC,QAAI,CAAC,KAAK,SAAS;AACjB,YAAM,IAAI,MAAM,kCAAkC;IACpD;AACA,QAAI,CAAC,KAAK,gBAAgB;AACxB,YAAM,IAAI,MAAM,yCAAyC;IAC3D;AAEA,QAAI,OAAO,KAAK,KAAK,cAAc,GAAG;AACpC,YAAM,IAAI,MAAM,4FAAuF;IACzG;AACA,SAAK,UAAU,KAAK,QAAQ,QAAQ,QAAQ,EAAE;AAC9C,SAAK,iBAAiB,KAAK;AAC3B,SAAK,MAAM,KAAK;AAChB,SAAK,YAAY,KAAK,aAAc,WAAW;AAC/C,SAAK,SAAS,KAAK,UAAU;AAC7B,SAAK,QAAQ,EAAE,GAAG,eAAe,GAAI,KAAK,SAAS,CAAC,EAAG;AACvD,SAAK,YAAY,KAAK,aAAa;EACrC;;EAIQ,SAAS,MAAc,OAAsC;AACnE,UAAM,MAAM,IAAI,IAAI,KAAK,UAAU,IAAI;AACvC,QAAI,OAAO;AACT,iBAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,KAAK,GAAG;AAC1C,YAAI,MAAM,UAAa,MAAM,QAAQ,MAAM,IAAI;AAC7C,cAAI,aAAa,IAAI,GAAG,OAAO,CAAC,CAAC;QACnC;MACF;IACF;AACA,WAAO,IAAI,SAAS;EACtB;EAEQ,cAAsC;AAC5C,UAAM,UAAkC;MACtC,gBAAgB;MAChB,yBAAyB,KAAK;IAChC;AACA,QAAI,KAAK,KAAK;AACZ,cAAQ,eAAe,IAAI,UAAU,KAAK,GAAG;IAC/C;AACA,WAAO;EACT;EAEQ,UAAU,cAA8B;AAE9C,UAAM,MAAM,KAAK,MAAM,cAAc,KAAK;AAC1C,UAAM,SAAS,KAAK,IAAI,KAAK,KAAK,MAAM,UAAU;AAClD,WAAO,KAAK,MAAM,KAAK,OAAO,KAAK,SAAS,EAAE;EAChD;EAEA,MAAc,QAAW,QAAoB,MAAc,OAAoB,CAAC,GAAe;AAC7F,UAAM,UAAU,KAAK,YAAY;AAGjC,QAAI,KAAK,YAAY;AACnB,cAAQ,iBAAiB,IAAI,QAAQ;IACvC;AACA,UAAM,MAAM,KAAK,SAAS,MAAM,KAAK,KAAK;AAC1C,UAAM,OAAoB;MACxB;MACA;MACA,GAAI,KAAK,SAAS,SAAY,EAAE,MAAM,KAAK,UAAU,KAAK,IAAI,EAAE,IAAI,CAAC;IACvE;AAEA,QAAI;AACJ,aAAS,UAAU,GAAG,UAAU,KAAK,MAAM,UAAU,WAAW;AAE9D,UAAI,eAA8B;AAClC,UAAI;AACF,cAAM,MAAM,MAAM,KAAK,UAAU,KAA0B,IAAI;AAC/D,YAAI,IAAI,IAAI;AAEV,gBAAM,OAAO,MAAM,IAAI,KAAK;AAC5B,iBAAQ,OAAO,KAAK,MAAM,IAAI,IAAI,CAAC;QACrC;AAGA,YAAI,SAAkC,CAAC;AACvC,YAAI;AACF,gBAAM,IAAI,MAAM,IAAI,KAAK;AACzB,mBAAS,IAAK,KAAK,MAAM,CAAC,IAAgC,CAAC;QAC7D,QAAQ;AACN,mBAAS,CAAC;QACZ;AAGA,YACE,KAAK,iBACJ,IAAI,WAAW,OACd,IAAI,WAAW,OACf,OAAO,OAAO,QAAQ,EAAE,EAAE,SAAS,gBAAgB,KACnD,kBAAkB,KAAK,OAAO,OAAO,WAAW,EAAE,CAAC,IACrD;AACA,gBAAM,IAAI;YACR,oCAAoC,IAAI,MAAM;YAC9C,IAAI;UACN;QACF;AAIA,YAAI,IAAI,WAAW,KAAK;AACtB,yBAAe,kBAAkB,GAAG;AACpC,oBAAU,IAAI,cAAc,2BAA2B,KAAK,IAAI;AAChE,eAAK,QAAQ,8BAA8B;YACzC;YACA,SAAS,UAAU;YACnB;UACF,CAAC;QACH,WAAW,IAAI,UAAU,OAAO,IAAI,SAAS,KAAK;AAIhD,cAAI,EAAE,KAAK,eAAe,CAAC,GAAG,SAAS,IAAI,MAAM,GAAG;AAClD,iBAAK,SAAS,8BAA8B,EAAE,MAAM,QAAQ,IAAI,QAAQ,MAAM,OAAO,CAAC;UACxF;AACA,gBAAM,IAAI;YACR,wBAAwB,IAAI,MAAM;YAClC,IAAI;YACJ;UACF;QACF,OAAO;AAEL,oBAAU,IAAI,cAAc,wBAAwB,IAAI,MAAM,KAAK,IAAI,QAAQ,IAAI;AACnF,eAAK,QAAQ,2BAA2B,EAAE,MAAM,QAAQ,IAAI,QAAQ,SAAS,UAAU,EAAE,CAAC;QAC5F;MACF,SAAS,GAAG;AAEV,YAAI,aAAa,sBAAsB;AACrC,gBAAM;QACR;AACA,YAAI,aAAa,iBAAiB,EAAE,UAAU,OAAO,EAAE,SAAS,KAAK;AACnE,gBAAM;QACR;AAEA,kBAAU;AACV,aAAK,QAAQ,+BAA+B,EAAE,MAAM,SAAS,UAAU,EAAE,CAAC;MAC5E;AAIA,UAAI,UAAU,KAAK,MAAM,WAAW,GAAG;AACrC,cAAM,KAAK,UAAU,gBAAgB,KAAK,UAAU,OAAO,CAAC;MAC9D;IACF;AAIA,SAAK,SAAS,2BAA2B,EAAE,MAAM,UAAU,KAAK,MAAM,SAAS,CAAC;AAChF,QAAI,mBAAmB,eAAe;AACpC,YAAM;IACR;AACA,UAAM,IAAI;MACR,wBAAwB,KAAK,MAAM,QAAQ,cAAc,IAAI;MAC7D;MACA;IACF;EACF;EAEQ,QAAQ,OAAe,KAAoC;AACjE,SAAK,OAAO,KAAK,OAAO,OAAO,GAAG,CAAC;EACrC;EAEQ,SAAS,OAAe,KAAoC;AAClE,SAAK,OAAO,MAAM,OAAO,OAAO,GAAG,CAAC;EACtC;;EAIA,MAAM,aAAoC;AACxC,UAAM,OAAO,MAAM,KAAK,QAAmC,OAAO,gBAAgB;AAClF,SAAK,eAAe,KAAK,WAAW,CAAC;AACrC,WAAO,KAAK;EACd;;;;;EAMA,MAAM,cAAc,aAA2C;AAC7D,UAAM,UAAU,KAAK,gBAAiB,MAAM,KAAK,WAAW;AAC5D,QAAI,QAAQ,WAAW,GAAG;AAExB,YAAM,IAAI,MAAM,kEAA6D;IAC/E;AACA,QAAI,aAAa;AACf,YAAM,KAAK,YAAY,YAAY;AACnC,YAAM,QAAQ,QAAQ;QAAK,CAAC,OACzB,EAAE,aAAa,CAAC,GAAG,KAAK,CAAC,OAAO,EAAE,SAAS,IAAI,YAAY,MAAM,EAAE;MACtE;AACA,UAAI,OAAO;AACT,eAAO;MACT;IACF;AAEA,WAAO,QAAQ,CAAC;EAClB;;EAIA,MAAM,aAAa,MAA8E;AAC/F,UAAM,SAAS,MAAM,UAAW,MAAM,KAAK,cAAc,MAAM,WAAW;AAG1E,UAAM,OAAO;AACb,UAAM,WAAW;AACjB,UAAM,MAAqB,CAAC;AAC5B,aAAS,SAAS,GAAG,SAAS,UAAU,UAAU,MAAM;AACtD,YAAM,OAAO,MAAM,KAAK,QAAqC,OAAO,mBAAmB;QACrF,OAAO,EAAE,WAAW,OAAO,IAAI,OAAO,MAAM,OAAO;MACrD,CAAC;AACD,YAAM,OAAO,KAAK,YAAY,CAAC;AAC/B,UAAI,KAAK,GAAG,IAAI;AAChB,UAAI,KAAK,SAAS,MAAM;AACtB;MACF;IACF;AACA,WAAO;EACT;EAEA,MAAM,WAAW,QAAgB,MAAmF;AAClH,UAAM,SAAS,MAAM,UAAW,MAAM,KAAK,cAAc,MAAM,WAAW;AAC1E,UAAM,OAAO,MAAM,KAAK,QAAqC,OAAO,mBAAmB;MACrF,OAAO,EAAE,QAAQ,WAAW,OAAO,IAAI,OAAO,EAAE;IAClD,CAAC;AACD,YAAQ,KAAK,YAAY,CAAC,GAAG,CAAC,KAAK;EACrC;;EAIA,MAAM,WAAW,QAAuC;AACtD,UAAM,OAAO,MAAM,KAAK,QAA4B,QAAQ,gBAAgB;MAC1E,YAAY;MACZ,MAAM,EAAE,WAAW,OAAO,GAAG;IAC/B,CAAC;AACD,WAAO,KAAK;EACd;EAEA,MAAM,YAAY,QAAgB,WAAmB,UAAqC;AACxF,UAAM,OAAO,MAAM,KAAK;MACtB;MACA,gBAAgB,mBAAmB,MAAM,CAAC;MAC1C;QACE,YAAY;QACZ,cAAc;QACd,MAAM,EAAE,YAAY,WAAW,SAAS;MAC1C;IACF;AACA,WAAO,KAAK;EACd;EAEA,MAAM,QAAQ,QAAmC;AAC/C,UAAM,OAAO,MAAM,KAAK;MACtB;MACA,gBAAgB,mBAAmB,MAAM,CAAC;MAC1C,EAAE,cAAc,KAAK;IACvB;AACA,WAAO,KAAK;EACd;;EAGA,MAAM,eAAe,QAAgB,QAAmC;AACtE,UAAM,OAAO,MAAM,KAAK;MACtB;MACA,gBAAgB,mBAAmB,MAAM,CAAC,eAAe,mBAAmB,MAAM,CAAC;MACnF,EAAE,YAAY,MAAM,cAAc,KAAK;IACzC;AACA,WAAQ,KAAK,UAAU,KAAK;EAC9B;;;;;;;;;;;;;;;EAgBA,MAAM,yBAAyB,QAAmC;AAEhE,UAAM,OAAO,MAAM,KAAK,QAAQ,MAAM;AACtC,QAAI,KAAK,oBAAoB,KAAK,iBAAiB,SAAS,GAAG;AAE7D,aAAO;IACT;AAGA,UAAM,OAAO,MAAM,KAAK;MACtB;MACA;MACA,EAAE,OAAO,EAAE,SAAS,OAAO,EAAE;IAC/B;AACA,UAAM,UAAU,KAAK,oBAAoB,CAAC;AAC1C,QAAI,QAAQ,WAAW,GAAG;AAExB,YAAM,IAAI;QACR;MACF;IACF;AAGA,UAAM,SACJ,QAAQ,WAAW,IACf,QAAQ,CAAC,IACT,CAAC,GAAG,OAAO,EAAE;MACX,CAAC,GAAG,OAAO,EAAE,UAAU,OAAO,sBAAsB,EAAE,UAAU,OAAO;IACzE,EAAE,CAAC;AAGT,UAAM,MAAM,MAAM,KAAK;MACrB;MACA,gBAAgB,mBAAmB,MAAM,CAAC;MAC1C;QACE,YAAY;QACZ,cAAc;QACd,MAAM,EAAE,WAAW,OAAO,GAAG;MAC/B;IACF;AACA,WAAO,IAAI;EACb;;;;EAMA,MAAM,aAAa,QAAgB,OAA2B,SAA4C;AACxG,WAAO,KAAK,QAA0B,QAAQ,4BAA4B;MACxE,YAAY;MACZ,MAAM,EAAE,SAAS,QAAQ,OAAO,QAAQ;IAC1C,CAAC;EACH;EAEA,MAAM,KAAK,WAAwC;AACjD,WAAO,KAAK;MACV;MACA,wBAAwB,mBAAmB,SAAS,CAAC;IACvD;EACF;;EAGA,MAAM,SAA0B;AAG9B,WAAO,KAAK,QAAgB,OAAO,0BAA0B,EAAE,aAAa,CAAC,KAAK,GAAG,EAAE,CAAC;EAC1F;;;;;;EAQA,MAAM,iBAAiB,OAA2C;AAChE,WAAO,KAAK,QAA2B,QAAQ,qCAAqC;MAClF,YAAY;MACZ,MAAM,EAAE,MAAM;IAChB,CAAC;EACH;;EAGA,MAAM,gBAAgB,OAA0C;AAC9D,WAAO,KAAK,QAA0B,QAAQ,+BAA+B;MAC3E,YAAY;MACZ,MAAM,EAAE,MAAM;IAChB,CAAC;EACH;EAEA,MAAM,YAAgC;AACpC,UAAM,OAAO,MAAM,KAAK;MACtB;MACA;IACF;AACA,WAAO,KAAK,SAAS,CAAC;EACxB;;;;;;;EAQA,MAAM,OAAO,SAAiB,SAA0D;AACtF,WAAO,KAAK,QAAsB,QAAQ,0BAA0B;MAClE,YAAY;MACZ,MAAM,EAAE,SAAS,GAAI,YAAY,SAAY,EAAE,QAAQ,IAAI,CAAC,EAAG;IACjE,CAAC;EACH;;;;;;;EAQA,MAAM,aAAsC;AAC1C,QAAI;AACF,YAAM,OAAO,MAAM,KAAK,QAAoC,OAAO,0BAA0B;QAC3F,aAAa,CAAC,KAAK,GAAG;MACxB,CAAC;AACD,aAAO,KAAK,UAAU,CAAC;IACzB,SAAS,GAAG;AACV,UAAI,aAAa,kBAAkB,EAAE,WAAW,OAAO,EAAE,WAAW,MAAM;AACxE,eAAO,CAAC;MACV;AACA,YAAM;IACR;EACF;;;;;;;EAQA,MAAM,SAAS,eAAgC,OAAwC;AACrF,QAAI;AACF,aAAO,MAAM,KAAK,QAAiB,QAAQ,uBAAuB;QAChE,MAAM,EAAE,IAAI,OAAO,aAAa,GAAG,MAAM;MAC3C,CAAC;IACH,SAAS,GAAG;AACV,UAAI,aAAa,iBAAiB,EAAE,WAAW,KAAK;AAClD,eAAO;MACT;AACA,YAAM;IACR;EACF;AACF;AC/iBO,IAAM,uBAAuB;AAK7B,IAAM,6BAA6B;ACHnC,IAAM,sBAAyC;EACpD;;AACF;AASO,SAAS,mBAAmB,KAAsB;AACvD,MAAI;AACJ,MAAI;AACF,WAAO,IAAI,IAAI,GAAG,EAAE,SAAS,YAAY;EAC3C,QAAQ;AACN,WAAO;EACT;AACA,SAAO,oBAAoB;IACzB,CAAC,YAAY,SAAS,WAAW,KAAK,SAAS,MAAM,OAAO;EAC9D;AACF;ACxBO,SAAS,cAAc,QAAwB;AACpD,QAAM,IAAI,OAAO,KAAK;AACtB,MAAI,CAAC,EAAG,QAAO;AACf,SAAO,EAAE,OAAO,CAAC,EAAE,YAAY,IAAI,EAAE,MAAM,CAAC,EAAE,YAAY;AAC5D;AAOO,SAAS,cAAc,GAA+B;AAE3D,QAAM,OAAO,EAAE,WAAW,CAAC,GAAG;IAC5B,CAAC,OAAO,EAAE,QAAQ,SAAS,IAAI,YAAY,MAAM,aAAa,EAAE,QAAQ,SAAS,IAAI,YAAY,MAAM;EACzG;AACA,MAAI,OAAO,OAAO,IAAI,UAAU,YAAY,IAAI,MAAM,KAAK,GAAG;AAC5D,WAAO,IAAI,MAAM,KAAK,EAAE,YAAY;EACtC;AAEA,QAAM,SAAS,EAAE,SAAS,IAAI,KAAK;AACnC,MAAI,MAAM,SAAS,GAAG,GAAG;AACvB,UAAM,QAAQ,MAAM,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC;AAClD,UAAM,OAAO,MAAM,MAAM,SAAS,CAAC;AACnC,QAAI,KAAM,QAAO,KAAK,YAAY;EACpC;AACA,SAAO;AACT;AAMO,SAAS,eAAe,GAA0B;AACvD,QAAM,OAAiB,CAAC;AACxB,aAAW,KAAK,EAAE,YAAY,CAAC,GAAG;AAChC,UAAM,IAAI,cAAc,CAAC;AACzB,QAAI,KAAK,CAAC,KAAK,SAAS,CAAC,GAAG;AAC1B,WAAK,KAAK,CAAC;IACb;EACF;AACA,SAAO;AACT;AAGO,SAAS,eAAe,GAAgB,QAA0B;AACvE,QAAM,SAAS,OAAO,KAAK,EAAE,YAAY;AACzC,QAAM,QAAkB,CAAC;AACzB,aAAW,KAAK,EAAE,YAAY,CAAC,GAAG;AAChC,QAAI,cAAc,CAAC,MAAM,OAAQ;AACjC,UAAM,OAAO,cAAc,CAAC;AAC5B,QAAI,QAAQ,CAAC,MAAM,SAAS,IAAI,GAAG;AACjC,YAAM,KAAK,IAAI;IACjB;EACF;AACA,SAAO;AACT;AAGO,SAAS,cAAc,GAA+B;AAC3D,QAAM,OAAO,EAAE,WAAW,CAAC,GAAG,KAAK,CAAC,OAAO,EAAE,QAAQ,SAAS,IAAI,YAAY,MAAM,MAAM;AAC1F,MAAI,OAAO,OAAO,IAAI,UAAU,YAAY,IAAI,MAAM,KAAK,GAAG;AAC5D,WAAO,IAAI,MAAM,KAAK;EACxB;AACA,QAAM,SAAS,EAAE,SAAS,IAAI,KAAK;AACnC,MAAI,MAAM,SAAS,GAAG,GAAG;AACvB,UAAM,OAAO,MAAM,MAAM,GAAG,EAAE,CAAC,GAAG,KAAK;AACvC,QAAI,KAAM,QAAO;EACnB;AAEA,SAAO,SAAS;AAClB;AAWO,SAAS,YACd,GACA,OAA2C,CAAC,GACnB;AACzB,QAAM,WAAW,EAAE,YAAY,CAAC;AAChC,QAAM,OAAO,KAAK,MAAM,KAAK,EAAE,YAAY;AAC3C,QAAM,SAAS,KAAK,QAAQ,KAAK,EAAE,YAAY;AAE/C,QAAM,cAAc,CAAC,MAA4B;AAC/C,QAAI,CAAC,KAAM,QAAO;AAClB,YAAQ,cAAc,CAAC,KAAK,IAAI,YAAY,MAAM,SAAS,EAAE,SAAS,IAAI,YAAY,MAAM;EAC9F;AACA,QAAM,gBAAgB,CAAC,MAA4B;AACjD,QAAI,CAAC,OAAQ,QAAO;AACpB,WAAO,cAAc,CAAC,MAAM;EAC9B;AAEA,MAAI,QAAQ,QAAQ;AAGlB,WAAO,SAAS,KAAK,CAAC,MAAM,YAAY,CAAC,KAAK,cAAc,CAAC,CAAC;EAChE;AACA,MAAI,UAAU,CAAC,MAAM;AACnB,WAAO,SAAS,KAAK,CAAC,MAAM,cAAc,CAAC,CAAC;EAC9C;AACA,MAAI,QAAQ,CAAC,QAAQ;AAGnB,WAAO,SAAS,KAAK,CAAC,MAAM,YAAY,CAAC,CAAC;EAC5C;AACA,SAAO,SAAS,CAAC;AACnB;AASO,SAAS,eAAe,GAAgB,QAA+B;AAC5E,QAAM,SAAS,OAAO,KAAK,EAAE,YAAY;AACzC,MAAI,CAAC,OAAQ,QAAO,EAAE,aAAa,cAAc,EAAE,MAAM,KAAK;AAG9D,aAAW,KAAK,EAAE,YAAY,CAAC,GAAG;AAChC,QAAI,cAAc,CAAC,MAAM,OAAQ;AACjC,UAAM,MAAM,cAAc,EAAE,MAAM;AAClC,QAAI,IAAK,QAAO;EAClB;AAGA,aAAW,OAAO,EAAE,UAAU,CAAC,GAAG;AAChC,UAAM,OAAO,IAAI,WAAW,QAAQ,KAAK,IAAI,WAAW,OAAO,KAAK,IACjE,SAAS,EACT,YAAY;AACf,QAAI,QAAQ,QAAQ;AAClB,YAAM,KAAK,IAAI,OAAO,IAAI,KAAK;AAC/B,UAAI,EAAG,QAAO;IAChB;EACF;AACA,aAAW,OAAO,EAAE,UAAU,CAAC,GAAG;AAChC,UAAM,KAAK,IAAI,OAAO,IAAI,KAAK;AAC/B,QAAI,KAAK,IAAI,OAAO,aAAa,YAAY,MAAM,CAAC,cAAc,GAAG,EAAE,KAAK,CAAC,GAAG;AAC9E,aAAO;IACT;EACF;AACA,SAAO;AACT;AAEA,SAAS,cAAc,QAA4D;AACjF,aAAW,OAAO,UAAU,CAAC,GAAG;AAC9B,UAAM,KAAK,KAAK,OAAO,IAAI,KAAK;AAChC,QAAI,EAAG,QAAO;EAChB;AACA,SAAO;AACT;AAEA,SAAS,YAAY,GAAmB;AACtC,SAAO,EAAE,QAAQ,uBAAuB,MAAM;AAChD;;;ACrKO,IAAM,QAAQ;AAAA,EACnB,IAAI;AAAA,EACJ,MAAM;AAAA,EACN,MAAM;AAAA,EACN,MAAM;AAAA,EACN,OAAO;AACT;AAGA,SAAS,QAAQ,GAAmB;AAClC,SAAO,EAAE,QAAQ,OAAO,GAAG;AAC7B;AAGO,SAAS,GAAG,KAAqB;AACtC,SAAO,GAAG,MAAM,EAAE,KAAK,QAAQ,GAAG,CAAC;AACrC;AAGO,SAAS,KAAK,KAAqB;AACxC,SAAO,GAAG,MAAM,IAAI,KAAK,QAAQ,GAAG,CAAC;AACvC;AAGO,SAAS,QAAQ,KAAqB;AAC3C,SAAO,GAAG,MAAM,IAAI,KAAK,QAAQ,GAAG,CAAC;AACvC;AAGO,SAAS,MAAM,KAAqB;AACzC,SAAO,QAAQ,GAAG;AACpB;AAsBO,SAAS,KAAK,MAAwB;AAC3C,QAAM,QAAkB,CAAC;AACzB,QAAM,KAAK,GAAG,MAAM,IAAI,UAAU,KAAK,IAAI,WAAM,QAAQ,KAAK,IAAI,CAAC,EAAE;AACrE,MAAI,KAAK,OAAO;AACd,UAAM,KAAK,MAAM,QAAQ,KAAK,KAAK,CAAC,EAAE;AAAA,EACxC;AACA,MAAI,KAAK,KAAK;AACZ,UAAM,KAAK,UAAU,MAAM,KAAK,IAAI,QAAQ,KAAK,GAAG,CAAC,EAAE;AAAA,EACzD;AACA,MAAI,KAAK,MAAM;AACb,UAAM,KAAK,WAAW,MAAM,KAAK,IAAI,KAAK,IAAI,EAAE;AAAA,EAClD;AACA,SAAO,MAAM,KAAK,IAAI;AACxB;AAmBA,IAAM,YAAY;AAAA,EAChB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,SAAS,SAAS,UAA0B;AAEjD,SAAO,UAAU,CAAC;AACpB;AAGO,SAAS,YAAY,SAAyB;AACnD,SAAO,MAAM,OAAO,KAAK,SAAS,OAAO,CAAC;AAC5C;;;AChGA,IAAM,OAAO;AAAA,EACX,OAAO;AAAA;AAAA;AAAA,EAGP,MAAM;AAAA;AAAA;AAAA,EAGN,OAAO;AAAA,EACP,KAAK;AAAA,EACL,MAAM;AACR;AAQO,SAAS,aAAa,OAA+C,CAAC,GAAY;AACvF,MAAI,KAAK,QAAS,QAAO;AACzB,MAAI,QAAQ,IAAI,aAAa,UAAa,QAAQ,IAAI,aAAa,GAAI,QAAO;AAC9E,QAAM,MAAM,KAAK,SAAS,QAAQ,QAAQ,OAAO,KAAK;AACtD,SAAO;AACT;AAGO,SAAS,UAAU,OAA+C,CAAC,GAAe;AACvF,SAAO,EAAE,OAAO,aAAa,IAAI,EAAE;AACrC;AAGO,IAAM,QAAoB,EAAE,OAAO,MAAM;AAEhD,SAAS,KAAK,OAAmB,MAAc,GAAmB;AAChE,MAAI,CAAC,MAAM,MAAO,QAAO;AACzB,SAAO,GAAG,IAAI,GAAG,CAAC,GAAG,KAAK,KAAK;AACjC;AAGO,SAAS,KAAK,OAAmB,GAAmB;AACzD,SAAO,KAAK,OAAO,KAAK,MAAM,CAAC;AACjC;AAGO,SAAS,IAAI,OAAmB,GAAmB;AACxD,SAAO,KAAK,OAAO,KAAK,KAAK,CAAC;AAChC;AAGO,SAAS,MAAM,OAAmB,GAAmB;AAC1D,SAAO,KAAK,OAAO,KAAK,OAAO,CAAC;AAClC;AASA,IAAM,MAAM;AAAA,EACV,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,GAAG;AAAA,EACH,GAAG;AAAA,EACH,IAAI;AAAA,EACJ,IAAI;AACN;AAOO,SAAS,OAAO,OAAmB,OAAe,QAAQ,IAAY;AAC3E,QAAM,QAAQ,IAAI,KAAK;AACvB,QAAM,OAAO,IAAI,EAAE,OAAO,CAAC;AAC3B,QAAM,OAAO,KAAK,SAAS,MAAM;AACjC,QAAM,OAAO,IAAI,EAAE,OAAO,KAAK,IAAI,GAAG,QAAQ,IAAI,CAAC;AACnD,SAAO,GAAG,IAAI,GAAG,KAAK,OAAO,KAAK,CAAC,GAAG,IAAI;AAC5C;AAGO,SAAS,KAAK,QAAQ,IAAY;AACvC,SAAO,IAAI,EAAE,OAAO,KAAK;AAC3B;AAQO,SAAS,IAAI,OAAiB,OAA+D,CAAC,GAAa;AAChH,QAAM,QAAQ,KAAK,SAAS;AAC5B,QAAM,QAAQ,KAAK,SAAS;AAC5B,QAAM,MAAgB,CAAC;AAEvB,MAAI,KAAK,OAAO;AACd,UAAM,QAAQ,IAAI,KAAK,KAAK;AAC5B,UAAM,OAAO,IAAI,EAAE,OAAO,CAAC;AAC3B,UAAM,OAAO,KAAK,SAAS,cAAc,KAAK;AAC9C,UAAM,OAAO,IAAI,EAAE,OAAO,KAAK,IAAI,GAAG,QAAQ,IAAI,CAAC;AACnD,QAAI,KAAK,GAAG,IAAI,EAAE,GAAG,IAAI,GAAG,KAAK,OAAO,KAAK,CAAC,GAAG,IAAI,GAAG,IAAI,EAAE,EAAE;AAAA,EAClE,OAAO;AACL,QAAI,KAAK,GAAG,IAAI,EAAE,GAAG,IAAI,EAAE,OAAO,KAAK,CAAC,GAAG,IAAI,EAAE,EAAE;AAAA,EACrD;AACA,aAAW,QAAQ,OAAO;AACxB,UAAM,MAAM,KAAK,IAAI,GAAG,QAAQ,IAAI,cAAc,IAAI,CAAC;AACvD,QAAI,KAAK,GAAG,IAAI,CAAC,IAAI,IAAI,GAAG,IAAI,OAAO,GAAG,CAAC,GAAG,IAAI,CAAC,EAAE;AAAA,EACvD;AACA,MAAI,KAAK,GAAG,IAAI,EAAE,GAAG,IAAI,EAAE,OAAO,KAAK,CAAC,GAAG,IAAI,EAAE,EAAE;AACnD,SAAO;AACT;AAGO,SAAS,cAAc,GAAmB;AAE/C,SAAO,EAAE,QAAQ,mBAAmB,EAAE,EAAE;AAC1C;AAaO,SAAS,gBAAgB,OAAmB,SAAiB,MAAM,IAAY;AACpF,QAAM,IAAI,OAAO,SAAS,OAAO,IAAI,KAAK,IAAI,GAAG,KAAK,MAAM,OAAO,CAAC,IAAI;AACxE,QAAM,cAAc,KAAK,IAAI,GAAG,GAAG;AACnC,QAAM,SAAS,MAAM,OAAO,SAAI,OAAO,WAAW,CAAC;AACnD,QAAMC,SAAQ,SAAI,OAAO,KAAK,IAAI,GAAG,MAAM,WAAW,CAAC;AACvD,SAAO,GAAG,MAAM,GAAGA,MAAK;AAC1B;AAKO,SAAS,aAAa,QAAwB;AACnD,QAAM,IAAI,OAAO,KAAK,EAAE,YAAY;AACpC,MAAI,MAAM,QAAS,QAAO;AAC1B,MAAI,MAAM,QAAS,QAAO;AAC1B,SAAO;AACT;AAMO,SAAS,aAAa,SAA2B;AACtD,MAAI,QAAQ,WAAW,EAAG,QAAO;AACjC,QAAM,WAAW,QAAQ,IAAI,CAAC,MAAM,aAAa,CAAC,CAAC,EAAE,KAAK,EAAE;AAC5D,QAAM,QAAQ,QAAQ,KAAK,GAAG;AAC9B,SAAO,GAAG,QAAQ,IAAI,KAAK;AAC7B;;;ACjJO,SAAS,QAAQ,MAAuC;AAC7D,SAAO,MAAM,SAAS;AACxB;AAWO,SAAS,SAAS,QAAmC,UAA6C;AACvG,MAAI,UAAU,MAAM;AAClB,WAAO;AAAA,EACT;AACA,QAAM,OAAO,YAAY,IAAI,YAAY;AAEzC,SAAO,MAAM,GAAG,MAAM,IAAI,GAAG,KAAK,GAAG,MAAM;AAC7C;AAGO,SAAS,WAAW,GAAoE;AAC7F,QAAM,KAAK,EAAE,YAAY,CAAC,GAAG,CAAC;AAC9B,SAAO;AAAA,IACL,QAAQ,GAAG,kBAAkB,qBAAqB;AAAA,IAClD,UAAU,GAAG,kBAAkB,iBAAiB;AAAA,EAClD;AACF;AAmBO,SAAS,YAAY,cAA0C;AACpE,SAAO,iBAAiB,SAAS,mBAAc;AACjD;AAOO,SAAS,YAAY,GAG1B;AACA,QAAM,UAAU,EAAE,UAAU,EAAE,MAAM,IAAI,SAAS;AACjD,QAAM,SAAS,EAAE,SAAS,IAAI,SAAS,EAAE,KAAK,KAAK;AACnD,SAAO,EAAE,OAAO,OAAO;AACzB;AAQO,SAAS,WAAW,MAA+E;AACxG,QAAM,MAAM,KAAK,iBAAiB,IAAI,SAAS;AAC/C,MAAI,IAAI;AACN,UAAM,IAAI,cAAc,EAAE,IAAI,KAAK,OAAO,GAAG,CAAC;AAC9C,QAAI,EAAG,QAAO;AAAA,EAChB;AACA,SAAO;AACT;AAGO,SAAS,YAAY,IAAuC;AACjE,QAAM,KAAK,MAAM,IAAI,SAAS;AAC9B,SAAO,EAAE,UAAU,IAAI,IAAI,EAAE,MAAM,EAAE;AACvC;AAQO,SAAS,eACd,MACA,OACA,UACA,OACQ;AACR,QAAM,QAAQ,SAAS;AACvB,QAAM,MAAM,IAAI,QAAQ,CAAC;AACzB,QAAM,SAAS,KAAK,SAAS,KAAK,iBAAiB,QAAQ,SAAS;AACpE,QAAM,QAAQ,KAAK,KAAK,OAAO,OAAO,MAAM,YAAY,KAAK,EAAE,CAAC,GAAG,IAAI;AACvE,QAAM,SAAS,WAAW,IAAI;AAC9B,QAAM,YAAY,SAAS,SAAM,MAAM,KAAK;AAC5C,QAAM,QAAQ,KAAK,SAAS,OAAO,MAAM,YAAY,OAAO,SAAS,KAAK,OAAO,QAAQ,CAAC,CAAC,KAAK;AAChG,SAAO,MAAM,GAAG,IAAI,KAAK,GAAG,SAAS,GAAG,KAAK,aAAU,KAAK,QAAQ,GAAG,KAAK;AAC9E;AAGA,SAAS,OAAO,OAAmB,GAAmB;AACpD,SAAO,MAAM,QAAQ,UAAU,CAAC,YAAY;AAC9C;AAGA,SAAS,YAAY,OAAmB,GAAmB;AACzD,SAAO,MAAM,QAAQ,gBAAgB,CAAC,YAAY;AACpD;AAQO,SAAS,mBACd,OACA,KACyB;AACzB,QAAM,UAAU,OAAO,IAAI,KAAK;AAChC,MAAI,CAAC,UAAU,MAAM,WAAW,GAAG;AACjC,WAAO;AAAA,EACT;AAGA,MAAI,QAAQ,KAAK,MAAM,GAAG;AACxB,UAAM,MAAM,OAAO,MAAM,IAAI;AAC7B,QAAI,OAAO,KAAK,MAAM,MAAM,QAAQ;AAClC,aAAO,MAAM,GAAG;AAAA,IAClB;AAAA,EACF;AAGA,QAAM,OAAO,MAAM,KAAK,CAAC,OAAO,GAAG,OAAO,UAAU,YAAY,GAAG,EAAE,MAAM,MAAM;AACjF,MAAI,MAAM;AACR,WAAO;AAAA,EACT;AAGA,QAAM,QAAQ,OAAO,YAAY;AACjC,QAAM,UAAU,MAAM;AAAA,IAAK,CAAC,QACxB,GAAG,SAAS,GAAG,iBAAiB,IAAI,SAAS,EAAE,YAAY,EAAG,SAAS,KAAK;AAAA,EAChF;AACA,SAAO,WAAW;AACpB;AAOO,SAAS,cAAc,SAAwD;AACpF,QAAM,OAAO,QAAQ,KAAK;AAC1B,QAAM,OAAO,QAAQ,KAAK,IAAI;AAC9B,QAAM,QAAQ,OAAO,UAAU,EAAE,SAAS,KAAK,CAAC,IAAI,UAAU,EAAE,SAAS,KAAK,UAAU,MAAM,CAAC;AAC/F,SAAO,EAAE,MAAM,MAAM;AACvB;AAGO,SAAS,YAAY,KAAoB,MAAqB;AACnE,MAAI,QAAQ,IAAI,SAAS,QAAW;AAClC,YAAQ,OAAO,MAAM,KAAK,UAAU,IAAI,MAAM,MAAM,CAAC,IAAI,IAAI;AAC7D;AAAA,EACF;AACA,UAAQ,OAAO,MAAM,IAAI,MAAM,KAAK,IAAI,IAAI,IAAI;AAClD;;;AC3KA,IAAM,wBAAwB;AAG9B,SAAS,SAAS,KAAqB;AACrC,SAAO,MAAM,GAAG;AAClB;AAGA,SAAS,WAAW,KAAsB;AACxC,MAAI;AACF,WAAO,IAAI,IAAI,GAAG,EAAE,aAAa;AAAA,EACnC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAMA,eAAsB,YAAY,SAAiB,OAAwB,CAAC,GAAoB;AAC9F,QAAM,UAAU,KAAK,WAAW,QAAQ,QAAQ,IAAI,QAAQ;AAI5D,MAAI,CAAC,mBAAmB,OAAO,KAAK,CAAC,WAAW,OAAO,GAAG;AACxD,WAAO;AAAA,EAAoD,SAAS,OAAO,CAAC;AAAA,EAC9E;AAGA,MAAI,WAAW,KAAK,iBAAiB;AACnC,WAAO,SAAS,OAAO;AAAA,EACzB;AAEA,MAAI;AACF,UAAM,WAAW,KAAK,WAClB,MAAM,KAAK,SAAS,OAAO,IAC3B,MAAM,cAAc,SAAS,IAAI;AACrC,WAAO,GAAG,QAAQ;AAAA,EAAK,SAAS,OAAO,CAAC;AAAA,EAC1C,QAAQ;AAEN,WAAO;AAAA,EAA2B,SAAS,OAAO,CAAC;AAAA,EACrD;AACF;AAGA,IAAM,yBAAyB;AAG/B,IAAM,iBAAiB,IAAI,OAAO;AAGlC,eAAe,cAAc,KAAa,MAAwC;AAEhF,QAAM,MAAO,MAAM,OAAO,gBAAgB;AAI1C,QAAM,gBAAgB,IAAI,WAAW;AACrC,QAAM,WAAW,cAAc;AAC/B,MAAI,CAAC,UAAU;AACb,UAAM,IAAI,MAAM,mCAAmC;AAAA,EACrD;AAGA,QAAM,OAAO,IAAI,gBAAgB;AACjC,QAAM,QAAQ,WAAW,MAAM,KAAK,MAAM,GAAG,sBAAsB;AACnE,MAAI;AACJ,MAAI;AACF,UAAM,MAAM,MAAM,KAAK,EAAE,QAAQ,KAAK,OAAO,CAAC;AAAA,EAChD,UAAE;AACA,iBAAa,KAAK;AAAA,EACpB;AACA,MAAI,CAAC,IAAI,IAAI;AACX,UAAM,IAAI,MAAM,2BAA2B,IAAI,MAAM,EAAE;AAAA,EACzD;AAIA,QAAM,MAAM,KAAK,WAAW;AAC5B,QAAM,cAAc,OAAO,SAAS,IAAI,SAAS,MAAM,gBAAgB,KAAK,IAAI,EAAE;AAClF,MAAI,OAAO,SAAS,WAAW,KAAK,cAAc,KAAK;AACrD,UAAM,IAAI,MAAM,kCAAkC;AAAA,EACpD;AACA,QAAM,MAAM,OAAO,KAAK,MAAM,IAAI,YAAY,CAAC;AAC/C,MAAI,IAAI,eAAe,KAAK,IAAI,aAAa,KAAK;AAChD,UAAM,IAAI,MAAM,gCAAgC;AAAA,EAClD;AAOA,SAAO,SAAS,KAAK;AAAA,IACnB,QAAQ,KAAK,UAAU;AAAA,IACvB,qBAAqB;AAAA,IACrB,oBAAoB;AAAA,EACtB,CAAC;AACH;;;AC7GA,eAAsB,QACpB,QACA,QACA,MACwB;AACxB,QAAM,QAAQ,QAAQ,IAAI;AAC1B,QAAM,SAAS,MAAM,OAAO,cAAc,OAAO,MAAM;AACvD,QAAM,UAAU,MAAM,OAAO,WAAW,KAAK,QAAQ,EAAE,OAAO,CAAC;AAE/D,MAAI,CAAC,SAAS;AAEZ,WAAO;AAAA,MACL,OAAO;AAAA,QACL,KAAK;AAAA,UACH,MAAM;AAAA,UACN,MAAM,WAAW,KAAK,MAAM;AAAA,UAC5B,KAAK;AAAA,QACP,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAEA,QAAM,EAAE,OAAO,OAAO,IAAI,YAAY,OAAO;AAC7C,QAAM,UAAU,eAAe,OAAO;AAEtC,QAAM,QAAkB,CAAC;AACzB,QAAM,KAAK,OAAO,OAAO,KAAK,CAAC;AAC/B,QAAM,KAAK,MAAM,IAAI,OAAO,MAAM,CAAC,EAAE;AACrC,MAAI,QAAQ,aAAa;AACvB,UAAM,KAAK,MAAM,MAAM,QAAQ,WAAW,EAAE,CAAC;AAAA,EAC/C;AAEA,QAAM,SAAU,QAAQ,WAAW,QAAQ,KAAgB;AAC3D,MAAI,QAAQ;AACV,UAAM,KAAK,eAAe,MAAM,EAAE;AAAA,EACpC;AAGA,MAAI,QAAQ,SAAS,GAAG;AACtB,UAAM,KAAK,eAAe,QAAQ,IAAI,CAAC,MAAM,GAAG,aAAa,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,KAAK,QAAK,CAAC,EAAE;AACrF,eAAW,KAAK,SAAS;AACvB,YAAM,QAAQ,eAAe,SAAS,CAAC;AACvC,YAAM,KAAK,MAAM,aAAa,CAAC,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,IAAI,MAAM,KAAK,IAAI,CAAC,EAAE;AAAA,IACvE;AAAA,EACF,OAAO;AAEL,UAAM,SAAS,QAAQ,YAAY,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,SAAS,GAAG;AAChE,UAAM,KAAK,eAAe,MAAM,KAAK,IAAI,CAAC,EAAE;AAAA,EAC9C;AAEA,QAAM,MAAM,QAAQ,YAAY,CAAC,GAAG,CAAC;AACrC,MAAI,IAAI,kBAAkB;AACxB,UAAM;AAAA,MACJ,eAAe,KAAK,OAAO,SAAS,GAAG,iBAAiB,mBAAmB,GAAG,iBAAiB,aAAa,CAAC,CAAC;AAAA,IAChH;AAAA,EACF;AAGA,MAAI,WAA0B,QAAQ,aAAa;AACnD,MAAI,YAA2B;AAC/B,MAAI,KAAK,QAAQ;AACf,UAAM,SAAS,KAAK,OAAO,KAAK,EAAE,YAAY;AAC9C,UAAM,WAAW,eAAe,SAAS,MAAM;AAC/C,QAAI,UAAU;AACZ,iBAAW;AAAA,IACb,OAAO;AAEL,kBAAY,MAAM,cAAc,MAAM,CAAC,oDAA+C,MAAM;AAAA,IAC9F;AAAA,EACF;AAEA,MAAI,UAAU;AACZ,UAAM,WAAW,MAAM,YAAY,UAAU,EAAE,SAAS,KAAK,QAAQ,CAAC;AACtE,UAAM,KAAK,QAAQ;AAAA,EACrB;AACA,MAAI,WAAW;AACb,UAAM,KAAK,SAAS;AAAA,EACtB;AACA,MAAI,QAAQ,SAAS,KAAK,CAAC,KAAK,QAAQ;AACtC,UAAM,SAAS,QAAQ,OAAO,CAAC,MAAM,MAAM,QAAQ,CAAC,CAAC,EAAE,KAAK,IAAI;AAChE,UAAM,KAAK,wCAAmC,MAAM,aAAa,OAAO,MAAM,GAAG,EAAE,CAAC,GAAG,KAAK,CAAC,EAAE;AAAA,EACjG;AAEA,SAAO;AAAA,IACL;AAAA,IACA,MAAM,KAAK,OACP;AAAA,MACE,IAAI,QAAQ;AAAA,MACZ,QAAQ,QAAQ;AAAA,MAChB,OAAO,QAAQ;AAAA,MACf;AAAA,MACA;AAAA,MACA,WAAW,QAAQ;AAAA,MACnB,WAAW,QAAQ,YAAY,CAAC,GAAG,IAAI,CAAC,OAAO;AAAA,QAC7C,IAAI,EAAE;AAAA,QACN,MAAM,EAAE;AAAA,QACR,OAAO,EAAE,kBAAkB,qBAAqB;AAAA,QAChD,UAAU,EAAE,kBAAkB,iBAAiB;AAAA,MACjD,EAAE;AAAA,IACJ,IACA;AAAA,EACN;AACF;AAEO,SAAS,aACd,SACA,YACA,YACM;AACN,UACG,QAAQ,MAAM,EACd,MAAM,MAAM,EACZ,SAAS,YAAY,yBAAyB,EAC9C,OAAO,qBAAqB,gDAAgD,EAC5E,YAAY,qEAAqE,EACjF,OAAO,OAAO,QAAgB,SAA8B;AAC3D,UAAM,EAAE,MAAM,MAAM,IAAI,cAAc,OAAO;AAC7C,UAAM,UAAW,QAAQ,KAAK,EAA0B,UAAU;AAClE,UAAM,MAAM,MAAM,QAAQ,WAAW,GAAG,WAAW,GAAG;AAAA,MACpD;AAAA,MACA,QAAQ,KAAK;AAAA,MACb;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AACD,gBAAY,KAAK,IAAI;AAAA,EACvB,CAAC;AACL;;;AC/HA,eAAsB,SACpB,QACA,QACA,MACwB;AACxB,QAAM,QAAQ,QAAQ,IAAI;AAC1B,QAAM,SAAqB,MAAM,OAAO,cAAc,OAAO,MAAM;AAEnE,QAAM,UAAU,MAAM,OAAO,WAAW,KAAK,QAAQ,EAAE,OAAO,CAAC;AAC/D,MAAI,CAAC,SAAS;AACZ,WAAO;AAAA,MACL,OAAO;AAAA,QACL,KAAK,EAAE,MAAM,GAAG,MAAM,WAAW,KAAK,MAAM,eAAe,KAAK,YAAY,CAAC;AAAA,MAC/E;AAAA,IACF;AAAA,EACF;AAEA,QAAM,OAAO,KAAK,QAAQ,OAAO;AAGjC,QAAM,UAAU,eAAe,OAAO;AACtC,QAAM,SACJ,KAAK,UAAU,OAAO,kBAAkB,QAAQ,SAAS,IAAI,QAAQ,CAAC,IAAI;AAE5E,QAAM,UAAU,YAAY,SAAS,EAAE,MAAM,OAAO,CAAC;AACrD,MAAI,CAAC,SAAS;AAEZ,UAAM,OAAO,QAAQ,SAAS,wBAAwB,QAAQ,KAAK,IAAI,CAAC,MAAM;AAC9E,WAAO;AAAA,MACL,OAAO;AAAA,QACL,KAAK;AAAA,UACH,MAAM;AAAA,UACN,MACE,kBAAkB,KAAK,MAAM,GAAG,OAAO,SAAS,IAAI,KAAK,EAAE,GACxD,SAAS,KAAK,MAAM,MAAM,EAAE;AAAA,UACjC,KAAK,WAAW,KAAK,MAAM,wCAAwC,IAAI;AAAA,QACzE,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAIA,MAAI,KAAK,QAAQ;AACf,UAAMC,gBAAe,SAAS,KAAK,MAAM,MAAM;AAC/C,WAAO;AAAA,MACL,OAAO;AAAA,QACL,GAAG,8BAAyB,QAAQ,UAAU,KAAK,MAAM,GAAG,OAAO,IAAI,IAAI,KAAK,EAAE,GAAGA,aAAY,oBAAoB;AAAA,QACrH;AAAA,MACF;AAAA,MACA,MAAM,KAAK,OACP;AAAA,QACE,SAAS;AAAA,QACT,aAAa;AAAA,UACX,QAAQ,QAAQ,UAAU,KAAK;AAAA,UAC/B,MAAM,QAAQ;AAAA,UACd,QAAQ,UAAU;AAAA,UAClB,YAAY,QAAQ;AAAA,QACtB;AAAA,MACF,IACA;AAAA,IACN;AAAA,EACF;AAEA,QAAM,QAAkB,CAAC;AACzB,MAAI,SAAS,OAAO;AACpB,MAAI,YAAY;AAGhB,MAAI,CAAC,QAAQ;AACX,UAAM,QAAQ,MAAM,OAAO,WAAW,MAAM;AAC5C,aAAS,MAAM;AAAA,EACjB;AAGA,MAAI;AACF,UAAM,OAAO,YAAY,QAAQ,QAAQ,IAAI,CAAC;AAAA,EAChD,SAAS,GAAG;AACV,QAAI,aAAa,sBAAsB;AAErC,YAAM,QAAQ,MAAM,OAAO,WAAW,MAAM;AAC5C,eAAS,MAAM;AACf,YAAM,OAAO,YAAY,QAAQ,QAAQ,IAAI,CAAC;AAC9C,kBAAY;AAAA,IACd,OAAO;AACL,YAAM;AAAA,IACR;AAAA,EACF;AAGA,QAAM,OAAO,MAAM,OAAO,QAAQ,MAAM;AAExC,MAAI,WAAW;AACb,UAAM;AAAA,MACJ;AAAA,QACE;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,QAAM,eAAe,SAAS,KAAK,MAAM,MAAM;AAC/C,QAAM;AAAA,IACJ;AAAA,MACE,UAAU,QAAQ,UAAU,KAAK,MAAM,GAAG,OAAO,IAAI,IAAI,KAAK,EAAE,GAAG,YAAY;AAAA,IACjF;AAAA,EACF;AACC,GAAC,KAAK,SAAS,CAAC,GAAG,QAAQ,CAAC,MAAM,MAAM;AACvC,UAAM,KAAK,eAAe,MAAM,GAAG,KAAK,eAAe,KAAK,CAAC;AAAA,EAC/D,CAAC;AACD,QAAM,aAAa,KAAK,SAAS,CAAC,GAAG,OAAO,CAAC,GAAG,OAAO,KAAK,GAAG,YAAY,IAAI,CAAC;AAChF,QAAM,KAAK,MAAM,SAAS,QAAQ,cAAc,IAAI,KAAK,GAAG,mDAA8C;AAE1G,SAAO;AAAA;AAAA,IAEL,MAAM,EAAE,SAAS,OAAO;AAAA,IACxB;AAAA,IACA,MAAM,KAAK,OACP,EAAE,SAAS,QAAQ,WAAW,QAAQ,UAAU,MAAM,OAAO,KAAK,SAAS,CAAC,GAAG,OAAO,KAAK,SAAS,KAAK,IACzG;AAAA,EACN;AACF;AAEO,SAAS,cACd,SACA,YACA,YACM;AAEN,UACG,QAAQ,OAAO,EACf,MAAM,KAAK,EACX,SAAS,YAAY,yBAAyB,EAC9C,OAAO,iBAAiB,iDAAiD,EACzE,OAAO,qBAAqB,kEAAkE,EAC9F,OAAO,aAAa,kEAAkE,EACtF,YAAY,8EAAyE,EACrF,OAAO,OAAO,QAAgB,SAA+D;AAC5F,UAAM,EAAE,MAAM,MAAM,IAAI,cAAc,OAAO;AAC7C,UAAM,MAAM,MAAM,SAAS,WAAW,GAAG,WAAW,GAAG;AAAA,MACrD;AAAA,MACA,MAAM,KAAK;AAAA,MACX,QAAQ,KAAK;AAAA,MACb,QAAQ,KAAK;AAAA,MACb;AAAA,MACA;AAAA,IACF,CAAC;AACD,QAAI,IAAI,MAAM;AAEZ,YAAM,EAAE,WAAW,IAAI,MAAM,OAAO,sBAAW;AAC/C,iBAAW,IAAI,IAAI;AAAA,IACrB;AACA,gBAAY,KAAK,IAAI;AAAA,EACvB,CAAC;AACL;","names":["out","empty","colourSuffix"]}