# 피커, 프리셋, 프롬프트

> TypeScript로 피커 옵션을 읽고, 설명으로 피커를 채우고, 노드 프리셋, 팔레트, 무드보드를 관리하며, 프롬프트 마법사로 프롬프트를 개선합니다.

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

**피커**는 [**분위기**](https://nodaro.ai/docs/nodes/creative-controls/mood)나 **렌즈** 같은 크리에이티브 컨트롤 노드로, 선택한 내용이 검증된 문구를 프롬프트에 추가합니다. <strong>`client.pickerCatalogs`</strong>와 <strong>`client.catalogs`</strong>는 각 피커의 유효한 옵션을 반환하고, `analyzeText()`는 장면 설명으로 피커를 채웁니다. <strong>`client.presets`</strong>는 저장된 노드 설정을 관리하고, <strong>`client.palettes`</strong>와 <strong>`client.moodboards`</strong>는 내 색상과 스타일 레퍼런스를 보관하며, <strong>`client.promptHelper`</strong>는 프롬프트 마법사로, 모든 생성 노드의 프롬프트를 개선합니다. 개념은 [크리에이티브 컨트롤](https://nodaro.ai/docs/guides/creative-controls)과 [피커 카탈로그](https://nodaro.ai/docs/developers/picker-catalogs)를 참조하세요.

## 메서드
| 메서드 | 하는 일 |
| --- | --- |
| [`pickerCatalogs.list()`](#pickercatalogslist) | 모든 피커와 그 카탈로그를 나열합니다 |
| [`pickerCatalogs.get(nodeType, opts?)`](#pickercatalogsgetnodetype-opts) | 피커 하나의 옵션을 읽습니다 |
| [`pickerCatalogs.analyzeText(params)`](#pickercatalogsanalyzetextparams) | 텍스트 설명으로 피커 선택을 채웁니다 |
| [`catalogs.list(opts?)`](#catalogslistopts) | 이 배포의 모든 카탈로그를 한 번의 호출로 읽습니다 |
| [`presets.list(nodeType?)`](#presetslistnodetype) | 내가 저장한 프리셋을 나열합니다 |
| [`presets.listGroups(nodeType?)`](#presetslistgroupsnodetype) | 내 프리셋 폴더와 섹션을 나열합니다 |
| [`presets.listFactory(nodeType)`](#presetslistfactorynodetype) | 로그인 없이 노드 유형의 기본 제공 프리셋을 나열합니다 |
| [`presets.create(input)`](#presetscreateinput) 외 | 내 프리셋을 만들고, 수정하고, 삭제하고, 가져오고, 순서를 바꿉니다 |
| [`presets.favorites(nodeType)`](#preset-favorites-and-folders) 외 | 프리셋 즐겨찾기와 폴더를 관리합니다 |
| [`palettes.create(input)`](#clientpalettes) 외 | **이미지 생성**에 쓸 컬러 팔레트를 저장합니다 |
| [`moodboards.create(input)`](#clientmoodboards) 외 | **이미지 생성**에 쓸 무드보드를 저장합니다 |
| [`promptHelper.analyze(input)`](#prompthelperanalyzeinput) | 대략적인 아이디어를 질문으로 바꿉니다 |
| [`promptHelper.generate(input)`](#prompthelpergenerateinput) | 답변으로 프롬프트를 만듭니다 |
| [`promptHelper.enhance(input)`](#prompthelperenhanceinput) | 프롬프트를 한 단계로 개선합니다 |

## client.pickerCatalogs
피커 노드의 옵션 목록입니다. 두 읽기 메서드는 모두 공개이며, 토큰이 필요 없고, 서버가 5분 동안 캐시할 수 있습니다. 코드에서 `@nodaro/prompts`를 가져올 수 있다면 같은 카탈로그가 타입이 지정된 데이터로 그 패키지에 들어 있습니다. 이 메서드들은 그 패키지를 번들로 포함할 수 없는 클라이언트를 위한 것입니다.

### pickerCatalogs.list()
모든 피커를 나열합니다(`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")
```

각 항목에는 `nodeType`, `label`, `catalogId`, `kind`(`single` 또는 `multi`), 단일 차원 피커의 `valueField` 또는 다중 차원 피커의 `fields`, `optionCount`, 그리고 사진이 있는 옵션의 수인 `imageCount`가 있습니다.

### pickerCatalogs.get(nodeType, opts?)
피커 하나의 옵션을 읽습니다(`GET /v1/picker-catalogs/:nodeType`). **분위기** 같은 단일 차원 피커에는 `options`가 있습니다. **인물** 같은 다중 차원 피커에는 `dimensions`가 있으며, 각각 `{ field, label, options }`입니다. 일부 단일 차원 피커에는 주 선택 옆에 추가 설정도 있습니다. `transition`과 `character-fx`에는 `position`, `duration`, `intensity`가 있고, `character-motion`에는 `position`과 `pace`가 있습니다.

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

<TypeTable
type={{
nodeType: { type: 'string', required: true, description: "피커의 노드 유형입니다. 예: mood, lens, person." },
detail: { type: '"compact" | "full"', default: '"compact"', description: "compact는 id, label, category, term, icon, imageUrl을 반환합니다. full은 각 옵션의 description과 promptHint를 추가합니다." },
category: { type: 'string', description: "단일 차원 피커: 이 카테고리의 옵션만 반환합니다." },
field: { type: 'string', description: "다중 차원 피커의 차원 하나, 또는 단일 차원 피커의 추가 설정 하나만 반환합니다." },
}}
/>

```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
```

알 수 없는 노드 유형이면 `NotFoundError`를 발생시킵니다.

**`label`은 표시하고, `term`은 추가하세요.** 모든 옵션에는 두 detail 수준 모두에서 `term`이 있습니다. 프롬프트에 넣을 짧은 전문 용어입니다. `label`은 표시 전용입니다. 한쪽에서 다른 쪽을 만들어 내지 마세요. `auto`나 `none`처럼 아무것도 추가하지 않는 옵션은 `term`이 비어 있습니다.

**사진.** 사진이 있는 옵션에는 두 detail 수준 모두에서 절대 URL인 `imageUrl`이 있습니다. `person`과 `styling`은 `sections`도 반환합니다. 에디터가 설정을 묶어 보여 주는 주제이며, 각각 `{ label, fields, imageUrl? }`입니다. 사진과 음악, 보이스 아트는 설치 자체에서 제공합니다. 렌더링된 룩 미리 보기는 Nodaro CDN에서 제공되며, Nodaro Cloud만 반환합니다. 파일 이름에 콘텐츠 해시가 들어 있으므로 사진을 만료 없이 캐시할 수 있습니다.

```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`의 옵션에는 두 detail 수준 모두에서 선택적인 `motion` 객체가 들어 있습니다. 이 객체는 동작에 필요한 것과 동작 후에 남는 것을 알려 줍니다. `requires`, `startPose`와 `endPose`, `endVisibility`, `handsAfter`, `needsFreeHands`, `kind`, `fixedPace`, `counterpart`, 검색용 `aliases`, 그리고 `replacementId`가 딸린 `deprecated`입니다. 필드가 없으면 알 수 없다는 뜻입니다. 저장된 워크플로를 불러올 때는 폐기된 ID를 유지하고, 새로 선택할 때는 보여 주지 마세요. [캐릭터 모션](https://nodaro.ai/docs/nodes/creative-controls/character-motion)을 참조하세요.

### pickerCatalogs.analyzeText(params)
자유 형식의 장면 설명으로 피커 선택을 채웁니다(`POST /v1/text-to-picker`). [**피커로 설명**](https://nodaro.ai/docs/nodes/image/describe-to-picker)의 텍스트 버전입니다. 피커 유형, 차원, 선택된 ID 하나 또는 여러 개 순서로 키가 지정된 `pickerJson`을 반환합니다. 이 값을 그대로 피커에 불러온 다음, 사용자가 조정하게 하세요. 크레딧이 들며, **피커로 설명**과 같은 요금이 청구됩니다. 기본 모델인 Claude Opus 5.5를 `high`로 쓰면 실행 한 번에 28크레딧이 듭니다.

```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: "장면이나 샷의 설명입니다." },
targetPickers: { type: 'string[]', description: "채울 피커 유형입니다. 생략하면 분석할 수 있는 모든 피커가 대상입니다." },
instructions: { type: 'string', description: "분석에 대한 추가 지시입니다." },
llmModel: { type: 'string', description: "모델 ID입니다. 생략하면 기본값인 Claude Opus 5.5가 쓰입니다." },
reasoningEffort: { type: 'string', description: "추론 강도이며, 모델에 따라 다릅니다. Claude Opus 5.5에서 생략하면 high로 실행됩니다. 다른 모델은 기본값을 유지합니다." },
origin: { type: 'string', description: "내 앱의 이름이며, 작업에 저장됩니다." },
}}
/>

```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"])
```

텍스트가 아무것도 말하지 않는 차원은 빠집니다. 분석은 추측하지 않습니다. `gaps`는 텍스트가 설명하지만 어떤 카탈로그 옵션도 잘 나타내지 못하는 것을 `missingItems`와 `missingCategories`에 나열합니다. 사용자에게는 “X를 찾지 못했습니다. 직접 선택하세요”처럼 보여 주세요.

## client.catalogs
### catalogs.list(opts?)
이 배포가 제공하는 그대로, 모든 피커 카탈로그를 한 번의 호출로 반환합니다(`GET /v1/catalogs`). 배포는 옵션을 교체하거나, 확장하거나, 숨기는 카탈로그 팩을 등록할 수 있습니다. 피커를 직접 그리는 클라이언트는 이를 반영하도록 이 목록을 읽어야 합니다. 공개이며, 5분 동안 캐시할 수 있습니다.

```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는 id, label, category, term, icon을 반환합니다. full은 description과 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`는 배포가 카탈로그 팩을 등록한 경우(`curated: true`)에만 있습니다. 팩이 없으면 카탈로그는 표준 카탈로그입니다. `pickerCatalogs.get()`으로 하나씩 읽으세요. 옵션에는 같은 `imageUrl`이 있고, `person`과 `styling`에는 같은 `sections`가 있습니다.

## client.presets
내가 저장한 노드 프리셋, 내 즐겨찾기, 기본 제공 카탈로그입니다. 프리셋의 `data`는 노드의 저장된 설정입니다. 프리셋을 적용하려면 워크플로를 만들 때 그 `data`를 노드의 데이터에 병합하거나, [이미지 생성 실행](https://nodaro.ai/docs/developers/sdk/nodes#presets-on-generate-image)에 `presetId`를 전달하세요. [프리셋](https://nodaro.ai/docs/concepts/presets)을 참조하세요.

`listFactory()`의 기본 제공 카탈로그는 공개이며 토큰이 필요 없습니다. 내 프리셋의 경우 OAuth 토큰에는 읽기에 `presets:read`, 쓰기에 `assets:write` 스코프가 필요합니다. 내 API 토큰에는 둘 다 필요 없지만, 일부 워크플로로 제한된 토큰은 내 프리셋에 접근할 수 없습니다.

내 프리셋과 기본 제공 프리셋 모두에 필드가 두 개 더 있습니다. 이 필드가 생기기 전의 서버는 이를 생략합니다.

- **`minReferences`**는 프리셋이 쓰는 연결된 레퍼런스 이미지의 수이며, 이 값이 0보다 크면 `needsImage`는 `true`입니다. 레퍼런스 시트, 인물 사진 변신, 편집, 그리고 `{image:N}`을 쓰는 모든 템플릿은 하나 이상이 필요합니다. 이런 프리셋은 그 수만큼 레퍼런스 이미지와 함께 실행하세요.
- **`previewUrl`**은 예시 이미지이거나 `null`입니다. 내 프리셋에서는 프리셋과 함께 저장한 이미지입니다.

### presets.list(nodeType?)
내가 저장한 프리셋을 최신 항목부터 나열합니다(`GET /v1/node-presets`).

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

<TypeTable
type={{
nodeType: { type: 'string', description: "이 노드 유형의 프리셋만 반환합니다. 예: 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
```

`NodePreset`에는 `id`, `nodeType`, `name`, `description`, `data`, `groupId`, `tags`, `sortOrder`, `minReferences`, `needsImage`, `previewUrl`, `createdAt`, `updatedAt`이 있습니다.

### presets.listGroups(nodeType?)
내 프리셋 폴더와 섹션을 나열합니다(`GET /v1/node-preset-groups`).

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

<TypeTable
type={{
nodeType: { type: 'string', description: "이 노드 유형의 그룹만 반환합니다." },
}}
/>

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

각 그룹에는 `id`, `nodeType`, `name`, `kind`(`folder` 또는 `section`), `sortOrder`, `createdAt`, `updatedAt`이 있습니다.

### presets.listFactory(nodeType)
노드 유형의 기본 제공 프리셋을 나열합니다(`GET /v1/node-presets/factory`).

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

<TypeTable
type={{
nodeType: { type: 'string', required: true, description: "노드 유형입니다. 예: generate-video." },
}}
/>

**로그인은 선택 사항입니다.** 카탈로그는 공개이므로 자격 증명이 없는 클라이언트도 읽을 수 있습니다. 방문자에게 이름, 예시 이미지, 사진 수를 보여 주고, 생성할 때만 로그인을 요청하세요.

```ts
// Signed out: a client with no credential can still read the catalog
const visitor = createClient({ baseUrl, auth: { getToken: async () => null } })
const { data } = await visitor.presets.listFactory("generate-image")
const withPicture = data.filter((p) => p.previewUrl)

// A preset that works from photos runs with at least minReferences of them.
const board = data.find((p) => p.id === "generate-image/character-board")!
if (board.needsImage && photos.length >= (board.minReferences ?? 0)) {
await client.nodes.runAndWait("generate-image", { presetId: board.id, referenceImageUrls: photos })
}
```

### presets.create(input)
프리셋을 저장합니다(`POST /v1/node-presets`).

```ts
create(input: CreateNodePresetInput): Promise<NodePreset>
```

<TypeTable
type={{
nodeType: { type: 'string', required: true, description: "generate-image 같은 노드 유형입니다." },
name: { type: 'string', required: true, description: "노드 유형마다 고유합니다. 이미 쓰는 이름은 409 name_taken으로 실패합니다." },
data: { type: 'Record<string, unknown>', required: true, description: "노드 설정으로, 최대 64KB입니다. 런타임 키는 제거됩니다." },
description: { type: 'string', description: "설명입니다." },
groupId: { type: 'string', description: "넣을 폴더나 섹션입니다." },
tags: { type: 'string[]', description: "태그입니다." },
sortOrder: { type: 'number', description: "위치입니다." },
previewUrl: { type: 'string', description: "이 플랫폼에 있는 내 이미지 하나로, 업로드한 이미지나 완료된 결과입니다. 다른 URL은 400 validation_error로 실패합니다." },
}}
/>

```ts
const preset = await client.presets.create({
nodeType: "generate-image",
name: "Studio portrait",
data: { provider: "gpt-image-2", aspectRatio: "3:4", prompt: "{image:1:person}, studio portrait" },
previewUrl: imageUrl, // the image of one of your completed jobs, or an upload
})
preset.needsImage // true, because the prompt names {image:1}
```

프리셋은 이미지의 자체 사본을 보관하므로, 반환된 `previewUrl`은 사본의 주소입니다. 계정 하나에는 프리셋이 최대 5,000개입니다. 이를 넘으면 호출은 `409 limit_reached`로 실패합니다.

### presets.update(id, patch, opts?)와 delete(id, opts?)
```ts
update(id: string, patch: UpdateNodePresetInput, opts?: { expectedUpdatedAt?: string }): Promise<NodePreset>
delete(id: string, opts?: { expectedUpdatedAt?: string }): Promise<{ success: true }>
```

`update()`는 `name`, `description`, `data`, `groupId`, `tags`, `sortOrder`, `previewUrl`(`null`이면 이미지 제거) 중 원하는 것을 바꿉니다. `delete()`는 프리셋을 제거하며, 내 프리셋이 아니면 404로 실패합니다. 읽은 `updatedAt`을 `expectedUpdatedAt`으로 전달하면, 그 뒤로 누군가 프리셋을 바꾼 경우 호출이 `409 conflict`로 실패합니다.

### presets.import(presets)와 reorder(input)
```ts
import(presets: ImportNodePresetInput[]): Promise<{ imported: number }>
reorder(input: ReorderNodePresetsInput): Promise<{ ok: true }>
```

`import()`는 프리셋을 한 번에 최대 500개 저장하며, 각각 `{ nodeType, name, description?, data, previewUrl? }`입니다. 이미 쓰는 이름은 “… (imported 2)”로 저장됩니다. 런타임 키를 제거한 뒤 data가 비는 프리셋은 건너뜁니다. `nodaro presets export`가 쓰는 파일의 항목은 그대로 맞습니다.

`reorder()`는 `{ groups?: [{ id, sortOrder }], presets?: [{ id, groupId?, sortOrder }] }`를 받습니다. 위치를 정하고 프리셋을 폴더로 옮기며, `groupId: null`이면 폴더 밖으로 옮깁니다.

### 프리셋 즐겨찾기와 폴더
```ts
favorites(nodeType: string): Promise<string[]>
addFavorite(input: { nodeType: string; presetId: string }): Promise<{ success: true }>
removeFavorite(input: { nodeType: string; presetId: string }): Promise<{ success: true }>

createGroup(input: { nodeType: string; name: string; kind?: "folder" | "section"; sortOrder?: number }): Promise<NodePresetGroup>
updateGroup(id: string, patch: { name?: string; sortOrder?: number }): Promise<NodePresetGroup>
deleteGroup(id: string): Promise<{ success: true }>
```

즐겨찾기는 `"generate-image/character-board"` 같은 기본 제공 ID나 내 프리셋 ID입니다. 즐겨찾기 추가와 제거는 반복해도 안전합니다. 폴더를 삭제하면 그 안의 프리셋은 폴더 밖으로 옮겨지며, 하나도 삭제되지 않습니다.

## client.palettes
**팔레트**는 이름 아래에 `#RRGGBB` 형식의 색상 2~12개를 묶은 것입니다. `generate-image`에서 팔레트는 연출 값입니다. `direction.colorLook`을 `paletteDirectionId(id)`, 즉 `"palette:<id>"`로 설정하세요. 그러면 모든 색상이 일상적인 이름과 16진수 코드로 프롬프트에 들어갑니다. 팔레트는 내 것만 쓸 수 있으며, 다른 사람의 ID는 404로 응답합니다.

```ts

const palette = await client.palettes.create({ name: "Harbour", colors: ["#0F4C5C", "#E3B23C", "#F28C7A"] })
await client.nodes.runAndWait("generate-image", {
prompt: "a lighthouse at dusk",
direction: { colorLook: paletteDirectionId(palette.id) },
})
```

| 메서드 | 반환 | 참고 |
| --- | --- | --- |
| `list(params?: ListPalettesParams)` | `Promise<PalettePage>` | 최신순입니다. `limit`(1~100, 기본값 50)과 `cursor`를 받습니다. 페이지에 `nextCursor`가 담깁니다. |
| `get(id: string)` | `Promise<ColorPalette>` | 팔레트 하나입니다. |
| `create(input: CreatePaletteInput)` | `Promise<ColorPalette>` | `name`(1~80자), `colors`(대소문자 상관없는 `#RRGGBB` 값 2~12개로, 대문자로 저장됨), `description`(최대 500자의 내 메모로, 프롬프트에는 절대 들어가지 않음)입니다. |
| `update(id: string, input: UpdatePaletteInput)` | `Promise<ColorPalette>` | 전달한 필드만 씁니다. `description: null`이면 지웁니다. |
| `delete(id: string)` | `Promise<void>` | 이후 이 팔레트를 지정하는 실행은 404 `direction_not_found`로 실패합니다. |

## client.moodboards
**무드보드**는 설명과 이미지 1~12장입니다. `generate-image`에서 무드보드는 연출 값입니다. `direction.style`을 `moodboardDirectionId(id)`, 즉 `"moodboard:<id>"`로 설정하세요. 설명은 프롬프트에 들어가고, 이미지는 스타일 레퍼런스로 모델에 전달됩니다. 이미지는 내 레퍼런스 뒤에, 모델의 레퍼런스 한도까지 들어갑니다. 이미지는 사본이 아니라 링크이며, `localhost`나 사설 주소가 아닌 `http(s)` 주소여야 합니다.

```ts

const board = await client.moodboards.create({
name: "Nordic calm",
description: "Moody Nordic interiors, soft daylight, linen and oak",
images: [{ url: "https://example.com/a.png", caption: "soft daylight" }, { url: "https://example.com/b.png" }],
})
const out = await client.nodes.run("generate-image", {
prompt: "a reading corner",
direction: { style: moodboardDirectionId(board.id) },
})
// out.warnings: [{ code: "moodboard_images_not_sent", message }] when some images did not fit
```

| 메서드 | 반환 | 참고 |
| --- | --- | --- |
| `list(params?: ListMoodboardsParams)` | `Promise<MoodboardPage>` | 최신순입니다. `limit`(1~100, 기본값 50)과 `cursor`를 받습니다. 페이지에 `nextCursor`가 담깁니다. |
| `get(id: string)` | `Promise<Moodboard>` | 무드보드 하나입니다. |
| `create(input: CreateMoodboardInput)` | `Promise<Moodboard>` | `name`(1~80자), `description`(최대 1,000자로, 프롬프트에 들어가는 문구), `images`(`{ url, caption? }` 1~12개로, 각 캡션은 최대 200자)입니다. |
| `update(id: string, input: UpdateMoodboardInput)` | `Promise<Moodboard>` | 전달한 필드만 씁니다. `images`는 모든 이미지를 바꿉니다. `description: null`이면 지웁니다. |
| `delete(id: string)` | `Promise<void>` | 무드보드만 삭제합니다. 연결된 이미지는 그대로입니다. |

### 둘 다에 적용되는 규칙
- `PALETTE_DIRECTION_PREFIX`(`"palette:"`), `MOODBOARD_DIRECTION_PREFIX`(`"moodboard:"`), `paletteDirectionId(id)`, `moodboardDirectionId(id)`가 연출 값을 만듭니다.
- `GenerateImageParams.direction`의 타입은 `DirectionFields`이며, 실행 응답의 타입에는 `warnings?: RunNodeWarning[]`이 있습니다.
- 형식이 잘못된 값은 400으로 응답합니다. 내 것이 아닌 ID는 404 `direction_not_found`로 응답합니다. 둘 다 무언가를 만들거나 청구하기 전에 실패합니다.
- 팔레트나 무드보드는 `presetId`와 함께 쓸 수 없습니다. 그런 실행은 400 `validation_error`로 응답하며, 연출에 팔레트나 무드보드를 담아 저장한 프리셋도 마찬가지입니다.
- OAuth 앱 토큰에는 읽기에 `assets:read`, 쓰기에 `assets:write` 스코프가 필요합니다.

## client.promptHelper
프롬프트 마법사입니다. 생성 노드의 프롬프트 작성을 AI가 돕습니다. 세 메서드는 모두 `POST /v1/prompt-helper/wizard`로 요청을 보내며, 호출할 때마다 크레딧이 듭니다. REST 관점은 [프롬프트 마법사](https://nodaro.ai/docs/developers/api/prompt-wizard)를 참조하세요.

세 메서드는 모두 다음 공통 필드를 받습니다.

<TypeTable
type={{
nodeType: { type: 'string', required: true, description: "프롬프트를 쓸 노드입니다. 예: generate-image, generate-video." },
provider: { type: 'string', description: "프롬프트를 쓸 모델입니다." },
style: { type: 'string', description: "지향할 스타일입니다." },
aspectRatio: { type: 'string', description: "목표 화면 비율입니다." },
duration: { type: 'number', description: "동영상의 경우 목표 클립 길이입니다." },
llmModel: { type: 'string', description: "프롬프트를 쓰는 언어 모델입니다." },
reasoningEffort: { type: 'string', description: "모델에 따라 none, low, medium, high, xhigh, max 중 하나입니다. xhigh와 max는 한 단계 위 등급으로 청구되지만, 프리미엄 모델은 프리미엄 그대로입니다. Claude 모델에서는 어떤 추론 강도든 제작사 자체 API에서 실행되며, 한 단계 더 위로 청구됩니다. 최대 premium-direct까지입니다." },
advancedMode: { type: 'boolean', description: "Gemini와 Claude 모델용: 모델 제작사의 자체 API를 사용합니다. 한 단계 위로 청구됩니다. 프리미엄 모델은 premium-direct 가격으로 올라갑니다." },
temperature: { type: 'number', description: "advancedMode 사용 시: 샘플링 온도입니다." },
maxTokens: { type: 'number', description: "advancedMode 사용 시: 답변의 최대 길이이며, 단위는 토큰입니다." },
nodeContext: { type: 'WizardNodeContext', description: "노드에 그 밖에 무엇이 연결되어 있는지 알려 주어, 마법사가 이를 고려하게 합니다." },
userPreference: { type: 'string', description: "따를 선호 사항입니다." },
workflowId: { type: 'string', description: "이 실행을 기록할 워크플로입니다." },
}}
/>

CLI에서도 `nodaro prompt wizard`, `analyze`, `generate`, `enhance`와 `--llm-model`, `--reasoning-effort`로 같은 기능을 사용할 수 있습니다. [CLI](https://nodaro.ai/docs/developers/cli)를 참조하세요.

### promptHelper.analyze(input)
대략적인 아이디어를 노드 유형에 맞는 안내 질문으로 바꿉니다. 질문에 답한 다음, 그 답변을 `generate()`에 전달하세요.

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

<TypeTable
type={{
prompt: { type: 'string', description: "대략적인 아이디어입니다." },
'...': { type: '공통 필드', description: "nodeType과 위의 공통 필드입니다." },
}}
/>

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

### promptHelper.generate(input)
선택한 답변으로 최적화된 프롬프트 하나를 만듭니다. 각 선택은 `{ category, value, isCustom }`입니다.

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

<TypeTable
type={{
selections: { type: 'WizardSelection[]', required: true, description: "선택한 답변이며, 각각 { category, value, isCustom }입니다." },
originalPrompt: { type: 'string', description: "질문의 바탕이 된 대략적인 아이디어입니다." },
'...': { type: '공통 필드', description: "nodeType과 위의 공통 필드입니다." },
}}
/>

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

### promptHelper.enhance(input)
질문 없이 프롬프트를 한 단계로 개선합니다.

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

<TypeTable
type={{
prompt: { type: 'string', description: "개선할 프롬프트입니다." },
'...': { type: '공통 필드', description: "nodeType과 위의 공통 필드입니다." },
}}
/>

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

## 자주 묻는 질문

### direction 객체에 쓸 수 있는 유효한 ID는 어떻게 찾나요?

모든 피커는 client.pickerCatalogs.list()로, 피커 하나의 옵션은 client.pickerCatalogs.get(nodeType)으로 확인하세요. 각 옵션의 id가 direction과 피커 노드가 받는 값입니다.

### label, term, promptHint는 어떻게 다른가요?

label은 표시 이름입니다. term은 간략 모드에서 피커가 추가하는 짧은 전문 용어입니다. promptHint는 전체 모드에서 추가하는 더 긴 문장이며, detail이 full일 때만 반환됩니다.

### SDK로 프롬프트를 개선하려면 어떻게 하나요?

대상 nodeType과 내 프롬프트로 client.promptHelper.enhance를 호출하세요. 개선된 프롬프트가 반환되며, 추천 모델이 함께 반환되기도 합니다. 호출할 때마다 크레딧이 듭니다.

### SDK로 노드 프리셋을 만들거나 변경할 수 있나요?

네. client.presets는 내 프리셋을 만들고, 수정하고, 삭제하고, 가져오고, 순서를 바꾸며, 즐겨찾기와 폴더를 관리합니다. listFactory의 기본 제공 카탈로그는 로그인이 필요 없습니다.

### 이미지 생성 실행에 내 색상을 쓰려면 어떻게 하나요?

client.palettes.create로 팔레트를 저장한 다음, generate-image에 direction.colorLook을 paletteDirectionId(palette.id)로 설정해 전달하세요. 모든 색상이 일상적인 이름과 16진수 코드로 프롬프트에 들어갑니다.
