Parameter Picker Catalogs

Nodaro’s editor has a family of parameter pickers — curated, tile-grid selectors for things like Mood, Lens, Setting, Framing, Lighting, Person, Music Genre, Voice Character, and ~40 more. A picker never calls the API; it contributes a descriptive clause to a downstream node’s prompt. (The deepest multi-dim picker, Person, defaults to a Compact grouped-pill view with a per-device Detailed toggle for the full tile-grid; the view mode is purely presentational and never changes the emitted clause — see the Person node page.)

Every picker’s data — its options, the prompt fragment each option contributes, its categories, and its i18n keys — ships as pure data in @nodaro/shared. So you can build the exact same pickers in your own app, in your own styling, and assemble the exact same prompts, with no API calls and no coupling to Nodaro’s UI.

Library first. Picker catalogs are static config + pure functions, so the preferred path is to import the data and run it locally — typed, offline, tree-shakeable, with no API round-trips. (The SDK/API is otherwise for server state: jobs, credits, uploads, workflows.) A read-only discovery endpoint also exists for clients that can’t bundle the package (e.g. LLM agents over MCP): GET /v1/picker-catalogs, client.pickerCatalogs.list()/get(), nodaro pickers, and the MCP get_picker_catalog tool — see API Integration §11, the SDK Reference, the CLI, and the MCP tools. They return the same data this page documents.

npm install @nodaro/shared @nodaro/sdk

The registry

import { PICKER_CATALOGS, getPickerCatalog, listPickerCatalogs } from "@nodaro/shared"
Export Description
PICKER_CATALOGS readonly PickerCatalog[] — every picker (28 single + 11 multi).
getPickerCatalog(nodeTypeOrCatalogId) One catalog by nodeType (e.g. "mood") or catalogId.
listPickerCatalogs() All of them.
interface PickerOption {
  id: string
  label: string            // English; localize via catalogId (see i18n)
  description?: string
  category?: string         // group id (matches categoryOrder / categoryLabels)
  promptHint: string        // the clause this option contributes ("" for no-op options like "auto")
  term: string              // the short professional term compact hint mode injects ("" for no-op options); `label` is for display
  icon?: string             // reserved; previews are app-side — render your own (see Visual)
}

interface PickerDimension {       // multi-dim pickers; also the secondary parameters of a single-dim picker
  field: string                   // e.g. "shotSize"
  label: string
  options: readonly PickerOption[]
}

interface PickerCatalog {
  nodeType: string                // "mood", "framing", …
  label: string
  catalogId: string               // i18n key
  kind: "single" | "multi"
  valueField?: string             // single: the field a selection writes
  defaultValue?: string
  categoryOrder?: readonly string[]
  categoryLabels?: Readonly<Record<string, string>>
  options?: readonly PickerOption[]      // single-dim
  dimensions?: readonly PickerDimension[] // multi-dim — and a single-dim picker's secondary parameter fields (see below)
}

Single-dimension pickers (e.g. Mood)

A single picker is one choice from a flat (optionally grouped) list. options carries everything you need to render the grid.

import { getPickerCatalog, getParameterPromptHint } from "@nodaro/shared"
import { createClient } from "@nodaro/sdk"

const client = createClient({ apiKey: process.env.NODARO_API_KEY })
const mood = getPickerCatalog("mood")! // { nodeType:"mood", valueField:"mood", options:[…], categoryOrder, categoryLabels }

// 1. Render your own tile-grid (group by category if you like)
function MoodPicker({ value, onChange }: { value?: string; onChange: (id: string) => void }) {
  return (mood.categoryOrder ?? [undefined]).map((cat) => (
    <section key={cat ?? "all"}>
      {cat && <h4>{mood.categoryLabels?.[cat]}</h4>}
      {mood.options!
        .filter((o) => !cat || o.category === cat)
        .map((o) => (
          <button key={o.id} aria-pressed={value === o.id} title={o.description} onClick={() => onChange(o.id)}>
            {o.label}
          </button>
        ))}
    </section>
  ))
}

// 2. Selection → prompt clause → run
const selected = "serene"
const clause = getParameterPromptHint({ type: "mood", data: { mood: selected } })
// (or just: mood.options!.find(o => o.id === selected)!.promptHint)

const prompt = ["a portrait of a woman", clause].filter(Boolean).join(", ")
const { jobIds } = await client.nodes.run("generate-image", { prompt })

Single-dimension pickers with secondary parameters (Transition, Character FX, Character Motion)

Three single pickers carry extra parameter fields beside the main choice: Transition and Character FX each have position, duration and intensity dropdowns, and Character Motion has position and pace. Those are catalogs too — the same { id, label, description, promptHint, term } rows as everything else, each led by a no-op auto — and the catalog exposes them as dimensions in addition to options. Render the picker from options and the dropdowns from dimensions; a client that only sends ids never has to write the timing clause itself.

const fx = getPickerCatalog("character-fx")!
fx.options        // the 57 effects — the main picker
fx.dimensions     // [{ field: "position", … }, { field: "duration", … }, { field: "intensity", … }]

// Each dimension's rows are also exported directly:
//   TRANSITION_POSITIONS / TRANSITION_DURATIONS / TRANSITION_INTENSITIES
//   CHARACTER_FX_POSITIONS / CHARACTER_FX_DURATIONS / CHARACTER_FX_INTENSITIES
//   CHARACTER_MOTION_POSITIONS / CHARACTER_MOTION_PACES

Transition and Character FX share the same ids (start / middle / end / full, instant / short / medium / long, subtle / natural / dynamic / crazy) but not the same wording — a transition occurs and spans the clip, an effect manifests and persists — so always read the rows from the node’s own catalog. Character Motion shares the position ids and adds pace ids (slow-motion / slow / natural / fast / explosive) with its own wording — a movement begins and plays out.

Multi-dimension pickers (e.g. Framing)

Some pickers set several independent fields at once — Framing is shot size and angle and coverage and composition and vantage. Each catalog exposes dimensions, one { field, label, options } per field.

import { getPickerCatalog, getParameterPromptHint } from "@nodaro/shared"

const framing = getPickerCatalog("framing")!
// framing.dimensions = [
//   { field: "shotSize",    label: "Shot Size",   options: [{id:"close-up", …}, …] },
//   { field: "angle",       label: "Angle",       options: [{id:"low-angle", …}, …] },
//   { field: "coverage",    label: "Coverage",    options: […] },
//   { field: "composition", label: "Composition", options: […] },
//   { field: "vantage",     label: "Vantage",     options: […] },
// ]

function FramingPicker({ value, onChange }: {
  value: Record<string, string>
  onChange: (v: Record<string, string>) => void
}) {
  return framing.dimensions!.map((dim) => (
    <section key={dim.field}>
      <h4>{dim.label}</h4>
      {dim.options.map((o) => (
        <button
          key={o.id}
          aria-pressed={value[dim.field] === o.id}
          title={o.description}
          onClick={() => onChange({ ...value, [dim.field]: o.id })}
        >
          {o.label}
        </button>
      ))}
    </section>
  ))
}

// Selection → clause. getParameterPromptHint composes all the set fields:
const value = { shotSize: "close-up", angle: "low-angle", composition: "rule-of-thirds" }
const clause = getParameterPromptHint({ type: "framing", data: value })
// → "close-up shot, …, low-angle, …, rule-of-thirds composition, …"

getParameterPromptHint({ type, data }) is the universal way to turn any selection (single or multi) into its clause — the exact same function Nodaro runs server-side, so your output matches the editor’s. (Per-catalog builders like buildFramingHints(value) exist too, if you prefer.)

Putting it together

// Compose several pickers into one prompt, then generate.
const clauses = [
  getParameterPromptHint({ type: "mood",    data: { mood: "serene" } }),
  getParameterPromptHint({ type: "lens",    data: { lens: "portrait-85mm" } }),
  getParameterPromptHint({ type: "framing", data: { shotSize: "close-up", angle: "eye-level" } }),
]
const prompt = ["a portrait of a woman in a garden", ...clauses].filter(Boolean).join(", ")
const { jobIds } = await client.nodes.run("generate-image", { prompt, model: "gpt-image" })

Localization

Catalog labels are English. Localized strings for 12 locales (en, es, fr, de, pt-BR, ru, hi, ja, ko, zh-CN, he, ar) ship in @nodaro/shared, keyed by each catalog’s catalogId. promptHint clauses and term values stay English (they feed the model).

English needs no setup. For other locales, register the per-locale “sidecar” bundles once at startup, then resolve labels synchronously (with English fallback):

import { registerSidecarLoaders, ensureLocaleCatalogLoaded, resolveLabel } from "@nodaro/shared"

// 1. Once at startup (Vite app): wire the lazy-loaded locale bundles
registerSidecarLoaders(import.meta.glob("/node_modules/@nodaro/shared/src/i18n/*.*.ts"))

// 2. Before rendering a locale, load that catalog's bundle
await ensureLocaleCatalogLoaded(mood.catalogId, "fr")

// 3. Resolve a label (sync; falls back to English if missing or not yet loaded)
const label = resolveLabel(mood.catalogId, option.id, option.label, "fr")

The bundles load lazily, so registerSidecarLoaders takes a glob of loaders (Vite’s import.meta.glob). Backends and tests skip i18n and use the English label directly.

Visual

Picker icon/thumbnails are not shipped — the editor’s previews are bespoke React components, and an external app should render its own visuals in its own style (the data gives you label, description, and category to build a rich grid). icon? is reserved for a future release that bakes static thumbnails into the catalog data.

Reference

   
getParameterPromptHint({ type, data }) Selection → composed prompt clause (universal).
build<Name>Hints(value) Per-catalog clause builder (e.g. buildFramingHints).
get<Name>PromptHint(id) Single option → clause (e.g. getMoodPromptHint).
PICKER_CATALOGS / getPickerCatalog / listPickerCatalogs The registry.

See also: SDK Quickstart · SDK Reference · Embed App Guide

Character Motion metadata

Character Motion catalog options include optional motion metadata at both compact and full detail. It carries authored requires, startPose/endPose, endVisibility, handsAfter, needsFreeHands, kind, fixedPace, counterpart, search aliases, and deprecated/replacementId. Missing fields mean unknown. Preserve retired IDs when loading saved workflows; hide them from new choices. See Character Motion for composition, naming, review and advisory-diagnostic behavior. client.pickerCatalogs.get("character-motion") exposes this as PickerOption.motion; the structural type is CharacterMotionMetadata from @nodaro/shared.