import { type ItemDistributionInput } from './distribution'; /** The five CC slot groups, in the order `helix item list-slots` prints them. */ export declare const CC_WEARABLE_SLOT_GROUPS: readonly [{ readonly group: "Clothing"; readonly summary: "wearable garments"; readonly slots: readonly ["Cosmetic.Slot.Clothing.Top", "Cosmetic.Slot.Clothing.Bottoms", "Cosmetic.Slot.Clothing.Set", "Cosmetic.Slot.Clothing.Backpack", "Cosmetic.Slot.Clothing.Socks", "Cosmetic.Slot.Clothing.Shoes", "Cosmetic.Slot.Clothing.Underwear.Top", "Cosmetic.Slot.Clothing.Underwear.Bottom", "Cosmetic.Slot.Clothing.Underwear.Leg"]; }, { readonly group: "Accessory"; readonly summary: "attached props, grouped by body zone"; readonly slots: readonly ["Cosmetic.Slot.Accessory.Head.Hat", "Cosmetic.Slot.Accessory.Face.Mask", "Cosmetic.Slot.Accessory.Face.Eyewear", "Cosmetic.Slot.Accessory.Neck.Necklace", "Cosmetic.Slot.Accessory.Ears.Earrings", "Cosmetic.Slot.Accessory.Hands.Gloves", "Cosmetic.Slot.Accessory.Hands.Nails"]; }, { readonly group: "Appearance"; readonly summary: "non-mesh visual layers"; readonly slots: readonly ["Cosmetic.Slot.Appearance.Hair.Main", "Cosmetic.Slot.Appearance.Hair.Facial.Beard", "Cosmetic.Slot.Appearance.Hair.Facial.Mustache", "Cosmetic.Slot.Appearance.Eyes.Eyebrows", "Cosmetic.Slot.Appearance.Eyes.Eyelashes", "Cosmetic.Slot.Appearance.Eyes.Iris", "Cosmetic.Slot.Appearance.Skin.BodyTattoo", "Cosmetic.Slot.Appearance.Skin.FaceTattoo", "Cosmetic.Slot.Appearance.Makeup.Lipstick", "Cosmetic.Slot.Appearance.Makeup.Eyeliner", "Cosmetic.Slot.Appearance.Makeup.Eyeshadow", "Cosmetic.Slot.Appearance.Makeup.Blush"]; }, { readonly group: "Body"; readonly summary: "modular base meshes"; readonly slots: readonly ["Cosmetic.Slot.Body.Head", "Cosmetic.Slot.Body.Upper", "Cosmetic.Slot.Body.Lower", "Cosmetic.Slot.Body.Hands", "Cosmetic.Slot.Body.Feet"]; }, { readonly group: "Custom"; readonly summary: "a custom full-body mesh that overrides the modular body"; readonly slots: readonly ["Cosmetic.Slot.Custom"]; }]; export type CcWearableSlot = (typeof CC_WEARABLE_SLOT_GROUPS)[number]['slots'][number]; /** Every CC slot tag, flattened in group order. Derived — never a second literal. */ export declare const CC_WEARABLE_SLOTS: readonly CcWearableSlot[]; /** True if `tag` is a known CC wearable slot tag. Casing is EXACT. */ export declare function isCcWearableSlot(tag: unknown): tag is CcWearableSlot; export declare const CC_GENDERS: readonly ["male", "female"]; export type CcGender = (typeof CC_GENDERS)[number]; /** True if `value` is a known CC gender. Casing is EXACT (lowercase). */ export declare function isCcGender(value: unknown): value is CcGender; /** A wearable cannot be exported to the Character Creator without a gender. */ export declare function ccGendersAreRequiredForKind(kind: string): boolean; /** * Only wearables (required) and avatars (optional — an avatar is a * `Cosmetic.Slot.Body.*` modular mesh, gendered like anything else) are authored * per gender. A gender on a home item is meaningless and is rejected rather than * silently stored. Deliberately the SAME kind rule as the slot. */ export declare function ccGendersAreAllowedForKind(kind: string): boolean; /** * Split a `--gender male,female` value the way `parseTags` splits tags: trimmed, * empties dropped, deduped, then ordered by `CC_GENDERS` so `female,male` and * `male,female` produce the SAME array. Two publishes that mean the same thing * must send the same value, or a `genders = {male,female}` filter silently * misses half the catalogue. * * Values are NOT lowercased — mirroring the backend's `normalizeCcGenders`. * `Male` is not a CC gender, and quietly repairing it here would hide a client * sending UE's enum casing verbatim. Unknown values are kept so the validator * can reject them BY NAME. */ export declare function parseGenders(value: string): string[]; /** The valid genders, rendered for a terminal (also printed by `list-slots`). */ export declare function renderCcGenderList(): string; /** * Validate the (kind, genders) pair the way the backend does, and throw the same * three failures. Mirrors `validateCcGendersForKind` in helix-backend-api. * * `undefined` and `[]` are the SAME thing — "no genders declared" — because the * column is `NOT NULL DEFAULT '{}'`: an omitted field and an explicit empty list * both land as `{}` server-side and must fail identically for a wearable. */ export declare function assertCcGenders(kind: string, genders: readonly string[] | null | undefined): void; /** * The kinds `POST /universal-items/upload` accepts. Everything else in the * backend's UniversalItemKind enum (emote / prop / bundle / achievement) is not * an uploadable mesh and is rejected server-side, so the CLI never offers it. */ export declare const UPLOADABLE_ITEM_KINDS: readonly ["wearable", "avatar", "add_on", "home_item", "home_shell", "vehicle"]; export type UploadableItemKind = (typeof UPLOADABLE_ITEM_KINDS)[number]; export declare function isUploadableItemKind(k: unknown): k is UploadableItemKind; /** * Resolve a user-typed `--kind` value (current product name, legacy wire * value, or garbage) to the wire value the backend's `UniversalItemKind` * expects — the ONLY thing that ever leaves this function is one of * `UPLOADABLE_ITEM_KINDS`'s existing members, or the input unchanged if it * matches neither an alias nor a wire value (left for `isUploadableItemKind` * to reject with the full accepted list). */ export declare function normalizeItemKind(value: string): string; /** * Human-facing list of accepted `--kind` values, leading with the current * product name and showing the underlying wire value alongside it (so * `grep`ing for `home_item`/`home_shell` still finds this string). Used by * the `--kind` option help and every "unsupported kind" error so the two * never drift apart. */ export declare function describeUploadableItemKinds(): string; /** A wearable cannot be equipped without a slot, so it cannot be published without one. */ export declare function ccSlotIsRequiredForKind(kind: string): boolean; /** * Only wearables (required) and avatars (optional — a full-body avatar maps to * `Cosmetic.Slot.Body.*` or `Cosmetic.Slot.Custom`) occupy a cosmetic slot. A * slot on a home item is meaningless and is rejected rather than stored. */ export declare function ccSlotIsAllowedForKind(kind: string): boolean; /** The grouped tag list, rendered for a terminal (also the body of `list-slots`). */ export declare function renderCcSlotTable(): string; /** * Validate the (kind, slot) pair the way the backend does, and throw the same * three failures with the full tag list attached. Mirrors * `validateCcSlotForKind` in helix-backend-api. */ export declare function assertCcSlot(kind: string, slot: string | null | undefined): void; export declare const MESH_CONTENT_TYPE = "model/gltf-binary"; /** * Content type for the `mesh` part. Binary glTF only — a `.gltf` is a JSON * document whose buffers/textures live in sibling files, so it cannot be * uploaded as one part at all; say that instead of letting the server guess. */ export declare function meshContentTypeFromExtension(file: string): string; /** Content type for the optional `thumbnail` part — png/jpg/jpeg/webp only. */ export declare function thumbnailContentTypeFromExtension(file: string): string; /** * Prove the bytes really are a binary glTF before spending an upload on them: * a GLB starts with the ASCII magic `glTF` followed by a uint32 version. Catches * the two mistakes a renamed export makes — a JSON .gltf saved as .glb, and a * zip/FBX with the extension swapped. */ export declare function assertLooksLikeGlb(bytes: Uint8Array, file: string): void; /** A warning string when the mesh is over the advisory ceiling, else null. */ export declare function meshSizeWarning(size: number, kind: UploadableItemKind, allowOversize: boolean): string | null; export type BuildItemPayloadInput = { kind: UploadableItemKind; title: string; slot?: string | null; /** * Character-Creator gender(s), sent on the wire as `genders` — REQUIRED for a * wearable. Never named `ccGenders`: that is the database column, and the * backend rejects it by name precisely because its request whitelist would * otherwise drop it in silence and store no genders at all. */ genders?: readonly string[] | null; slug?: string; description?: string; /** Omitted means Standard. Present selects Collectible and fixes its supply. */ collectibleSupply?: number; /** Optional first acquisition route created atomically with the definition. */ initialDistribution?: ItemDistributionInput; tags?: string[]; allowOversize?: boolean; /** Add-on compatibility contract. Required for kind=add_on. */ fits?: Record; /** Add-on composition operations. Omitted and [] are equivalent. */ ops?: readonly Record[]; /** Base host class with a published backend slot pack. */ hostKind?: 'vehicle' | 'character'; /** Measured base surface, required alongside hostKind. */ hostSurface?: Record; /** Authored Universal Vehicle package. Required for kind=vehicle. */ vehiclePackage?: Record; }; /** Parse either inline JSON or a JSON file path for a CLI metadata flag. */ export declare function parseJsonOrFile(value: string, flag: string): unknown; export declare function parseJsonObjectOrFile(value: string, flag: string): Record; export declare function parseJsonArrayOrFile(value: string, flag: string): Record[]; /** Split a `--tags a,b,c` value: trimmed, empties dropped, order-preserving dedupe. */ export declare function parseTags(value: string): string[]; /** * Build the JSON object sent as the multipart `payload` field, applying every * local rule first. Keys the caller did not set are OMITTED, never sent as * null — the backend defaults `category`/`usage` from `kind`, and sending a * guess would override a default the server owns. */ export declare function buildItemPayload(input: BuildItemPayloadInput): Record;