# Pickers, presets and prompts

> Read every picker's valid options, fill pickers from a scene description, load node presets, and improve prompts with the Prompt Wizard from TypeScript.

Source: https://nodaro.ai/docs/developers/sdk/pickers-and-prompts

**Pickers** are the Creative Controls nodes, such as [Mood](https://nodaro.ai/docs/nodes/creative-controls/mood) or Lens, whose choice adds tested wording to a prompt. **`client.pickerCatalogs`** and **`client.catalogs`** return each picker's valid options, and `analyzeText()` fills pickers from a scene description. **`client.presets`** reads saved node settings, and **`client.promptHelper`** is the Prompt Wizard, which improves prompts for any generation node. See [Creative controls](https://nodaro.ai/docs/guides/creative-controls) and [Picker catalogs](https://nodaro.ai/docs/developers/picker-catalogs) for the concepts.

## Methods

| Method | What it does |
| --- | --- |
| [`pickerCatalogs.list()`](#pickercatalogslist) | List every picker and its catalog |
| [`pickerCatalogs.get(nodeType, opts?)`](#pickercatalogsgetnodetype-opts) | Read one picker's options |
| [`pickerCatalogs.analyzeText(params)`](#pickercatalogsanalyzetextparams) | Fill picker choices from a text description |
| [`catalogs.list(opts?)`](#catalogslistopts) | Read every catalog of this deployment in one call |
| [`presets.list(nodeType?)`](#presetslistnodetype) | List your saved presets |
| [`presets.listGroups(nodeType?)`](#presetslistgroupsnodetype) | List your preset folders and sections |
| [`presets.listFactory(nodeType)`](#presetslistfactorynodetype) | List the built-in presets of a node type |
| [`promptHelper.analyze(input)`](#prompthelperanalyzeinput) | Turn a rough idea into questions |
| [`promptHelper.generate(input)`](#prompthelpergenerateinput) | Build a prompt from the answers |
| [`promptHelper.enhance(input)`](#prompthelperenhanceinput) | Improve a prompt in one step |

## client.pickerCatalogs

The option lists of the picker nodes. Both read methods are public, need no token, and can be cached by the server for 5 minutes. If your code can import `@nodaro/shared`, the same catalogs ship there as typed data; these methods are for clients that cannot bundle it.

### pickerCatalogs.list()

Lists every picker (`GET /v1/picker-catalogs`).

```ts
list(): Promise<{ data: PickerCatalogSummary[] }>
```

```ts
const { data: pickers } = await client.pickerCatalogs.list()
const mood = pickers.find((p) => p.nodeType === "mood")
```

Each entry has `nodeType`, `label`, `catalogId`, `kind` (`single` or `multi`), `valueField` for a single-dimension picker or `fields` for a multi-dimension one, `optionCount`, and `imageCount`, the number of options with a picture.

### pickerCatalogs.get(nodeType, opts?)

Reads one picker's options (`GET /v1/picker-catalogs/:nodeType`). A single-dimension picker, such as Mood, has `options`. A multi-dimension picker, such as Person, has `dimensions`, each `{ field, label, options }`. A few single-dimension pickers also have extra settings next to the main choice: `transition` and `character-fx` have `position`, `duration` and `intensity`, and `character-motion` has `position` and `pace`.

```ts
get(nodeType: string, opts?: { detail?: "compact" | "full"; category?: string; field?: string }): Promise<{ data: PickerCatalog }>
```

<TypeTable
type={{
nodeType: { type: 'string', required: true, description: "The picker's node type, such as mood, lens or person." },
detail: { type: '"compact" | "full"', default: '"compact"', description: "compact returns id, label, category, term, icon and imageUrl. full adds each option's description and promptHint." },
category: { type: 'string', description: "Single-dimension pickers: only options of this category." },
field: { type: 'string', description: "Only one dimension of a multi-dimension picker, or one extra setting of a single-dimension picker." },
}}
/>

```ts
const { data } = await client.pickerCatalogs.get("mood", { detail: "full" })
const serene = data.options?.find((o) => o.id === "serene")
console.log(serene?.term)       // the short phrase, added in Compact mode
console.log(serene?.promptHint) // the full sentence, added in Full mode
```

Throws `NotFoundError` for an unknown node type.

**Show `label`, add `term`.** Every option has a `term` at both detail levels: the short professional phrase to put in a prompt. `label` is for display only. Never derive one from the other. An option that adds nothing, such as `auto` or `none`, has an empty `term`.

**Pictures.** An option with a picture has an absolute `imageUrl`, at both detail levels. `person` and `styling` also return `sections`: the topics the editor groups their settings under, each `{ label, fields, imageUrl? }`. Photos and music and voice art are served by the installation itself. The rendered look previews come from the Nodaro CDN, and only Nodaro Cloud returns them. File names carry a content hash, so you can cache the pictures without expiry.

```ts
const { data: person } = await client.pickerCatalogs.get("person")
for (const section of person.sections ?? []) {
const settings = person.dimensions?.filter((d) => section.fields.includes(d.field)) ?? []
for (const setting of settings) {
for (const option of setting.options) renderTile(option.label, option.imageUrl)
}
}
```

**Character Motion.** The options of `character-motion` carry an optional `motion` object at both detail levels. It says what a move needs and leaves: `requires`, `startPose` and `endPose`, `endVisibility`, `handsAfter`, `needsFreeHands`, `kind`, `fixedPace`, `counterpart`, search `aliases`, and `deprecated` with a `replacementId`. A missing field means unknown. Keep retired ids when you load saved workflows, and hide them from new choices. See [Character Motion](https://nodaro.ai/docs/nodes/creative-controls/character-motion).

### pickerCatalogs.analyzeText(params)

Fills picker choices from a free-text scene description (`POST /v1/text-to-picker`), the text version of [Describe to Picker](https://nodaro.ai/docs/nodes/image/describe-to-picker). It returns `pickerJson`, keyed by picker type, then by dimension, then the chosen id or ids. Load pickers from it as it is, then let the user adjust them. It costs credits, billed as Describe to Picker.

```ts
analyzeText(params: TextToPickerParams): Promise<{
jobId: string
pickerJson: Record<string, Record<string, string | string[]>>
gaps?: { missingItems: object[]; missingCategories: object[] }
}>
```

<TypeTable
type={{
text: { type: 'string', required: true, description: "The scene or shot description." },
targetPickers: { type: 'string[]', description: "The picker types to fill. Omit it for every picker that can be analyzed." },
instructions: { type: 'string', description: "Extra guidance for the analysis." },
llmModel: { type: 'string', description: "The model id." },
reasoningEffort: { type: 'string', description: "The reasoning effort, depending on the model." },
origin: { type: 'string', description: "Your app's name, stored on the job." },
}}
/>

```ts
const { pickerJson, gaps } = await client.pickerCatalogs.analyzeText({
text: "Neon-soaked Tokyo alley at night, rain, handheld tracking shot, moody synthwave",
})
console.log(pickerJson["setting"], pickerJson["camera-motion"])
```

Dimensions the text says nothing about are left out: the analysis does not guess. `gaps` lists what the text describes that no catalog option represents well, in `missingItems` and `missingCategories`. Show it to the user as "we could not match X; choose one yourself".

## client.catalogs

### catalogs.list(opts?)

Returns every picker catalog in one call, as this deployment serves it (`GET /v1/catalogs`). A deployment can register catalog packs that replace, extend or hide options. A client that draws its own pickers should read this list to respect them. It is public and can be cached for 5 minutes.

```ts
list(opts?: { detail?: "compact" | "full" }): Promise<{
curated: boolean
packs: number
version: number
data?: ProjectedCatalog[]
}>
```

<TypeTable
type={{
detail: { type: '"compact" | "full"', default: '"compact"', description: "compact returns id, label, category, term and icon. full adds description and promptHint." },
}}
/>

```ts
const { curated, data } = await client.catalogs.list({ detail: "full" })
if (curated) {
const setting = data?.find((c) => c.catalogId === "setting")
console.log(setting?.options?.[0]?.term)
}
```

`data` is present only when the deployment registered catalog packs (`curated: true`). Without packs, the catalogs are the standard ones: read them one by one with `pickerCatalogs.get()`. Options carry the same `imageUrl`, and `person` and `styling` the same `sections`.

## client.presets

Your saved node presets and the built-in ones, read only. A preset's `data` is a node's saved settings. To apply a preset, merge its `data` into a node's data when you build a workflow. An OAuth token needs the `presets:read` scope. See [Presets](https://nodaro.ai/docs/concepts/presets).

### presets.list(nodeType?)

Lists your saved presets, newest first (`GET /v1/node-presets`).

```ts
list(nodeType?: string): Promise<NodePreset[]>
```

<TypeTable
type={{
nodeType: { type: 'string', description: "Only presets of this node type, such as generate-image." },
}}
/>

```ts
const presets = await client.presets.list("generate-image")
const cinematic = presets.find((p) => p.name === "Cinematic Portrait")
// apply: spread cinematic.data into the node's data when you create the workflow
```

A `NodePreset` has `id`, `nodeType`, `name`, `description`, `data`, `groupId`, `tags`, `sortOrder`, `createdAt` and `updatedAt`.

### presets.listGroups(nodeType?)

Lists your preset folders and sections (`GET /v1/node-preset-groups`).

```ts
listGroups(nodeType?: string): Promise<NodePresetGroup[]>
```

<TypeTable
type={{
nodeType: { type: 'string', description: "Only groups of this node type." },
}}
/>

```ts
const groups = await client.presets.listGroups("generate-image")
```

Each group has `id`, `nodeType`, `name`, `kind` (`folder` or `section`), `sortOrder`, `createdAt` and `updatedAt`.

### presets.listFactory(nodeType)

Lists the built-in presets of a node type (`GET /v1/node-presets/factory`).

```ts
listFactory(nodeType: string): Promise<{ data: FactoryPreset[] }>
```

<TypeTable
type={{
nodeType: { type: 'string', required: true, description: "The node type, such as generate-video." },
}}
/>

```ts
const { data } = await client.presets.listFactory("generate-video")
const orbit = data.find((p) => p.id === "generate-video/orbit-360")
```

## client.promptHelper

The Prompt Wizard: AI help to write prompts for generation nodes. All three methods send their request to `POST /v1/prompt-helper/wizard`, and each call costs credits. See [Prompt Wizard](https://nodaro.ai/docs/developers/api/prompt-wizard) for the REST view.

All three take these common fields:

<TypeTable
type={{
nodeType: { type: 'string', required: true, description: "The node the prompt is for, such as generate-image or generate-video." },
provider: { type: 'string', description: "The model the prompt is for." },
style: { type: 'string', description: "A style to aim for." },
aspectRatio: { type: 'string', description: "The target frame shape." },
duration: { type: 'number', description: "The target clip length, for video." },
llmModel: { type: 'string', description: "The language model that writes the prompt." },
reasoningEffort: { type: 'string', description: "none, low, medium, high, xhigh or max, depending on the model. xhigh and max bill one tier higher." },
advancedMode: { type: 'boolean', description: "Gemini models only: use the maker's own API. Bills one tier higher." },
temperature: { type: 'number', description: "The sampling temperature." },
maxTokens: { type: 'number', description: "The longest answer, in tokens." },
nodeContext: { type: 'WizardNodeContext', description: "What else is wired to the node, so the wizard can account for it." },
userPreference: { type: 'string', description: "A preference to follow." },
workflowId: { type: 'string', description: "A workflow to list this run under." },
}}
/>

The CLI offers the same through `nodaro prompt wizard`, `analyze`, `generate` and `enhance`, with `--llm-model` and `--reasoning-effort`. See the [CLI](https://nodaro.ai/docs/developers/cli).

### promptHelper.analyze(input)

Turns a rough idea into guided questions for a node type. Answer them, then pass the answers to `generate()`.

```ts
analyze(input: AnalyzeInput): Promise<{ jobId: string; questions: WizardQuestion[] }>
```

<TypeTable
type={{
prompt: { type: 'string', description: "The rough idea." },
'...': { type: 'common fields', description: "nodeType and the common fields above." },
}}
/>

```ts
const { questions } = await client.promptHelper.analyze({
nodeType: "generate-image",
prompt: "a snow leopard",
})
```

### promptHelper.generate(input)

Builds one optimized prompt from the chosen answers. Each selection is `{ category, value, isCustom }`.

```ts
generate(input: GenerateInput): Promise<{ jobId: string; prompt: string; recommendedModel?: RecommendedModel }>
```

<TypeTable
type={{
selections: { type: 'WizardSelection[]', required: true, description: "The chosen answers, each { category, value, isCustom }." },
originalPrompt: { type: 'string', description: "The rough idea the questions came from." },
'...': { type: 'common fields', description: "nodeType and the common fields above." },
}}
/>

```ts
const { prompt, recommendedModel } = await client.promptHelper.generate({
nodeType: "generate-image",
selections: [{ category: "subject", value: "snow leopard", isCustom: false }],
})
```

### promptHelper.enhance(input)

Improves a prompt in one step, without the questions.

```ts
enhance(input: EnhanceInput): Promise<{ jobId: string; prompt: string; recommendedModel?: RecommendedModel }>
```

<TypeTable
type={{
prompt: { type: 'string', description: "The prompt to improve." },
'...': { type: 'common fields', description: "nodeType and the common fields above." },
}}
/>

```ts
const { prompt } = await client.promptHelper.enhance({
nodeType: "generate-image",
prompt: "snow leopard on a rock",
reasoningEffort: "high",
})
```

## Frequently asked questions

### How do I find the valid ids for the direction object?

Call client.pickerCatalogs.list() for every picker, then client.pickerCatalogs.get(nodeType) for one picker's options. Each option's id is what direction and picker nodes accept.

### What is the difference between label, term and promptHint?

label is the display name. term is the short professional phrase the picker adds in Compact mode. promptHint is the longer sentence it adds in Full mode, returned only with detail full.

### How do I improve a prompt with the SDK?

Call client.promptHelper.enhance with the target nodeType and your prompt. It returns an improved prompt and, sometimes, a recommended model. Each call costs credits.

### Can the SDK create or change node presets?

No. client.presets reads your saved presets, their groups and the built-in presets. Apply a preset by merging its data into a node's settings when you build a workflow.
