---
name: kingkont
description: Use this skill when the user works with a KingKont project — a film/series scene editor where each scene is a folder with scene.json. Triggers — фразы вроде «открой/запусти kingkont», «открой редактор сцен», «добавь сцену/персонажа/локацию», «добавь ноду на холст», «сгенерируй картинку/видео/голос/SFX/музыку/текст», или работа с папкой содержащей _characters/ _locations/ <scene>/scene.json. Skill знает формат данных и использует CLI `kingkont <cmd>` (open/list/board/add-node/gen/connect/rm-node/upload/voices/balance) для управления нодами и запуска генераций — ключи берутся автоматически из настроек приложения. ВАЖНО: (1) каждая нода живёт в конкретной доске — если юзер не указал доску, СПРОСИ; не создавай новые сцены без явного запроса. (2) Перед генерацией image/video проверь scene.json → settings.aspectRatio; если поле отсутствует — СПРОСИ юзера под какой формат генерить (9:16/16:9/1:1/...), сохрани выбор в scene.json как дефолт. (3) Для text-нод НЕ используй `kingkont gen --kind=text` — пиши текст сам в ответе, потом сохраняй через `kingkont add-node --kind=text --text="..."` (heredoc для многострочного). Для визуальной работы поднимает UI через `npx -y kingkont serve`.
---

# KingKont · Chatium — Skill

KingKont — нод-редактор сцен фильма/сериала с AI-генерацией. Каждая сцена/
персонаж/локация — папка на диске с `scene.json` и подпапками для медиа.
Формат plain JSON + plain files: всё можно читать и менять руками или
через AI-агента, не запуская сам редактор.

## Когда использовать этот skill

- Юзер работает в папке-проекте KingKont (есть `_characters/`,
  `_locations/`, любая папка с `scene.json`, или файл `CLAUDE.md` в корне).
- Просит «открой редактор», «запусти kingkont», «открой проект» —
  поднимаешь UI через `npx -y kingkont serve` (см. ниже).
- Просит править данные: «добавь персонажа Анна», «добавь сцену
  «Прощание»», «вставь текстовую ноду на сцену 3 с этим текстом», «свяжи
  ноды @Анна и @сценарий», «сгенерируй картинку», «озвучь реплику» — это
  работа со scene.json и/или вызов API; делается без открытия UI.
- Любые правки тексту, перестановка нод, чистка истории, бэкап — все
  через прямую правку `scene.json` и `texts/*.md`.

## ⚠️ ОБЯЗАТЕЛЬНОЕ ПРАВИЛО: всегда уточняй доску

**НИКОГДА не создавай новые сцены без явного запроса юзера.** Каждая нода
живёт в конкретной доске (сцене / персонаже / локации). Если юзер просит
«сгенерируй картинку», «добавь ноду», «вставь голос» — это значит
«внутри какой-то существующей доски», а не «создай новую».

### Алгоритм перед любой операцией создания нод

1. Запусти `kingkont open <project>` чтобы получить список существующих
   досок проекта.
2. Если юзер не указал доску явно — **спроси у него** в чём именно
   работать:
   - Если есть только одна сцена / `currentBoard` сохранён — используй её.
   - Если несколько и юзер не уточнил — выведи нумерованный список и
     спроси номер. Пример: «У тебя есть сцены: 1) Серия 1, 2) Серия 2,
     3) Серия 4.1. В какой добавить?»
3. Не предлагай создать новую сцену пока юзер сам не попросит словами
   вроде «новая сцена», «создай сцену», «добавь эпизод».
4. Если юзер прислал N фото — все ноды идут в **одну** выбранную доску,
   а не в N разных.

### Как создавать новые сцены (только когда явно попросили)

Команды CLI для add-node — НЕ создают сцену; они работают только с
существующими досками. Чтобы создать новую сцену, нужно:

```bash
mkdir -p <project>/<имя_новой_сцены>
echo '{"nodes":[],"connections":[]}' > <project>/<имя_новой_сцены>/scene.json
# Дальше — обычные kingkont add-node / kingkont gen с этой доской
```

Аналогично для персонажа: `<project>/_characters/<имя>/scene.json`.
Для локации: `<project>/_locations/<имя>/scene.json`.

## ⚠️ Aspect ratio для image/video — обязательно уточняй

Каждая доска имеет дефолтный aspect ratio в `scene.json` →
`settings.aspectRatio`. Это поле управляет под какой формат генерится
картинка/видео.

### Алгоритм перед `kingkont gen --kind=image|video`

1. **Прочитай `scene.json` выбранной доски.**
2. **Если `settings.aspectRatio` задан** → используй его автоматически
   (CLI сам подхватит — флаг `--aspect-ratio` не нужен).
3. **Если `settings.aspectRatio` отсутствует** — НЕ предполагай и НЕ
   ставь дефолт сам. **Спроси юзера**: «Под какой формат генерим? —
   9:16 (вертикаль), 16:9 (горизонталь), 1:1 (квадрат), 4:5, 4:3, 3:4,
   2:3, 3:2, 21:9. По умолчанию — 9:16.»
4. После ответа юзера **сохрани его выбор как дефолт** для этой доски,
   чтобы в следующий раз не спрашивать. Сделай это правкой `scene.json`:
   добавь/обнови ключ `"settings": { "aspectRatio": "<выбранный>" }`.
5. После этого можешь запускать `kingkont gen --aspect-ratio=<выбор>` —
   или без флага, CLI прочитает scene.

### Пример

```bash
# Юзер: "сгенери картинку героя в Серии 1"
# Шаг 1: проверяем текущий aspect
kingkont board <project> 'Серия 1' --json | jq '.settings.aspectRatio'
# null → не задан

# Шаг 2: спрашиваем юзера. Получаем «вертикаль».
# Шаг 3: правим scene.json — ставим aspectRatio: '9:16'

# Шаг 4: генерим (CLI прочитает settings.aspectRatio автоматически)
kingkont gen <project> 'Серия 1' --kind=image --prompt="..."

# Альтернативно: указать явно (CLI отдаст приоритет флагу)
kingkont gen <project> 'Серия 1' --kind=image --prompt="..." --aspect-ratio=9:16
```

Если юзер сам в просьбе указал формат («сгенери картинку 16:9», «вертикальную»,
«квадратное видео») — используй его сразу, не спрашивай. Заодно обнови
`settings.aspectRatio`, если он отличается от текущего и юзер явно сказал
«пусть в этой сцене будет такой формат» (или если поле было null).

### Дефолтные промпты сцены

`scene.json → settings.defaultPrompts` — массив:
```json
[
  { "id": "uuid", "text": "cinematic lighting", "kinds": ["image","video"], "enabled": true },
  { "id": "uuid", "text": "тёплый тон", "kinds": ["audio"], "enabled": false }
]
```

Каждый prompt:
- `text` — собственно строка
- `kinds` — список kind'ов где он применим (`image / video / audio / text`)
- `enabled` — true/false: применяется ли по умолчанию (юзер может в gen-modal'е переключить для конкретной генерации)

CLI `kingkont gen <kind>` берёт ВСЕ промпты с `enabled !== false` и
текущим kind в их `kinds`, конкатенирует через `, ` и префиксует к
user-prompt'у. Чтобы отключить целиком для одной генерации — флаг
`--no-default-prompt`. В stderr пишется `[gen] applied N default
prompt(s) for <kind>: "..."`.

Юзер обычно настраивает defaults через UI (правый клик на сцену →
«📝 Дефолтные промпты…»). Через CLI — правь `scene.json` напрямую,
добавляя/удаляя элементы массива.

Backward-compat: если в scene.json остался старый формат
`settings.defaultPrompts: { image: "..." }` — CLI и UI конвертируют
его в массив на лету; следующее сохранение перепишет в новый формат.

#### Когда заполнять defaultPrompts по умолчанию

Если юзер описывает сцену **как кадр фильма/сериала/последовательности** —
сразу при создании сцены пропиши в `defaultPrompts` визуальный стиль кадров.
Это сэкономит юзеру тонну ручного ввода и сохранит консистентность всех
генераций в сцене.

Что добавлять (зависит от описания):

- **Технические характеристики камеры**: «35mm film», «70mm IMAX», «shot
  on Arri Alexa», «handheld camera», «steadicam», «drone shot», «macro
  lens», «anamorphic lens (cinematic widescreen with horizontal lens
  flares)».
- **Освещение**: «natural daylight», «golden hour», «overcast soft
  lighting», «harsh midday sun», «cinematic three-point lighting»,
  «practical sources only», «moody low-key lighting», «high-key bright»,
  «neon accents».
- **Композиция и план**: «wide establishing shot», «medium close-up»,
  «extreme close-up», «over-the-shoulder», «Dutch angle», «symmetrical
  Wes-Anderson framing», «rule of thirds».
- **Цветовая палитра / grading**: «teal and orange», «desaturated muted
  tones», «vintage Kodachrome», «cold blue tint», «warm sepia», «black
  and white high contrast».
- **Жанровый/режиссёрский референс**: «in the style of Christopher
  Nolan», «Wes Anderson aesthetic», «David Fincher dark moody», «Wong
  Kar-wai dreamy slow-motion», «Roger Deakins cinematography»,
  «Studio Ghibli animation style», «1970s grindhouse film grain».
- **Период/эпоха**: «1990s film aesthetic», «retro VHS look»,
  «contemporary photoreal», «futuristic cyberpunk neon».
- **Атмосфера**: «foggy atmosphere», «dust particles in the air»,
  «volumetric god rays», «slight motion blur», «shallow depth of field
  with bokeh».

Пример: юзер сказал «давай сцену про детектива в нуар-фильме 50-х в
дождливом Нью-Йорке».

Создай сцену, и сразу после `add-board` добавь в `scene.json →
settings.defaultPrompts`:

```json
[
  { "id": "...", "text": "1950s film noir aesthetic, black and white high contrast, dramatic shadows", "kinds": ["image","video"], "enabled": true },
  { "id": "...", "text": "rainy New York street, wet asphalt reflecting neon signs, foggy atmosphere", "kinds": ["image","video"], "enabled": true },
  { "id": "...", "text": "anamorphic lens, shallow depth of field, cinematic composition", "kinds": ["image","video"], "enabled": true }
]
```

Раздели на 2-4 prompt'а (а не один длинный) — юзер потом сможет временно
выключить какой-то один в gen-modal'е (например прохладную палитру для
конкретной картинки, оставив остальное).

После создания сцены **сообщи юзеру** что defaultPrompts настроены, и
покажи текстом что именно — чтобы он мог поправить.

#### Раскадровка: разноси повторяющееся в @-ссылки или defaultPrompts

Когда юзер просит **раскадровку** (storyboard) — серию кадров одной
сцены/эпизода — **не дублируй общие части в каждый промпт**. Вместо
повторения извлекай их одним из двух способов:

**1. Persistent описания → отдельные ноды (text/image) + @-ссылки.**

Если в каждом кадре повторяется один и тот же **персонаж, локация,
объект, реквизит** — заведи отдельную text/image-ноду и в промптах
кадров ссылайся на неё через `@имя`. CLI и UI резолвят `[@имя]` в
полный текст ноды (или прикладывают image как референс) на этапе
генерации.

Пример. Юзер просит раскадровку «детектив идёт по улице, видит труп,
звонит партнёру». Не пиши в каждый промпт «молодой детектив в потрёпанном
сером плаще с блокнотом, тёмные волосы, усталое лицо». Вместо:

```bash
# Сначала — text-нода с описанием персонажа.
kingkont add-node <project> <board> --kind=text --name="Анна-детектив" \
  --text="Молодая женщина-следователь лет 30, потрёпанный серый плащ, \
тёмные волосы собраны в хвост, усталое лицо, в руках блокнот."

# Потом каждый кадр — короткий промпт с @-ссылкой:
kingkont gen <project> <board> --kind=image --name="кадр-1" \
  --prompt="[@Анна-детектив] идёт вдоль кирпичной стены, ночь, дождь" \
  --refs="@Анна-детектив"

kingkont gen <project> <board> --kind=image --name="кадр-2" \
  --prompt="[@Анна-детектив] склоняется над телом, освещение от уличного фонаря" \
  --refs="@Анна-детектив"
```

Это даёт **визуальную консистентность** (модель видит то же описание),
плюс если юзер захочет уточнить персонажа — поправит ОДНУ ноду, не все
кадры.

**2. Universal стиль/съёмка → defaultPrompts сцены.**

Если повторяется НЕ конкретный объект, а **как мы снимаем эту сцену**
(освещение, камера, цветокор, жанр) — кладёшь в `settings.defaultPrompts`
(см. раздел выше про defaultPrompts). Каждый кадр потом не нуждается в
этих словах — они автоматически префиксуются ко всем генерациям.

Чек-лист при раскадровке:

- [ ] Персонажи / локации / реквизит описаны как text/image-ноды → ссылки `@`
- [ ] Стиль съёмки (камера / свет / палитра / жанр) → `settings.defaultPrompts`
- [ ] Промпт КАДРА содержит ТОЛЬКО что отличает его от других — действие,
      ракурс, момент в кадре

Если соблюсти оба правила — типичный кадр умещается в одну фразу
(«[@Анна-детектив] открывает дверь»), и вся сцена выглядит цельно.

## ⚠️ Text-ноды — генерируй САМ, не через `kingkont gen --kind=text`

Ты — Claude. Когда юзер просит «напиши диалог», «придумай реплику»,
«сделай сценарий», «текстовую ноду с описанием…» — **генерируй текст
прямо в ответе**, как обычный chat-output. **Не запускай**
`kingkont gen --kind=text` — это бессмысленный round-trip через
Chatium/OpenRouter (тратит кредиты юзера, медленнее, и качество
скорее всего хуже того что ты сгенерируешь сам).

После того как ты сгенерировал текст — **сохрани его в ноду** через
`kingkont add-node --kind=text` (с `--text="..."` или через heredoc для
многострочного контента):

```bash
# Однострочный текст:
kingkont add-node <project> <board> --kind=text --name=replica \
  --text="— Ты опять опоздал."

# Многострочный — heredoc, чтобы не экранировать кавычки и переводы строк:
kingkont add-node <project> <board> --kind=text --name=script \
  --text="$(cat <<'EOF'
СЦЕНА 1.
Полицейский участок. Поздний вечер.

— Где была между 8 и 11?
— У подруги. Можешь проверить.
— Уже проверили.
EOF
)"
```

CLI создаст ноду и положит контент в `<board>/texts/<имя>_<id8>.md` с
правильным `n.file` указателем в `scene.json`.

### Когда `kingkont gen --kind=text` всё-таки нужен

Только если юзер **явно просит** использовать другую модель — например
«сгенерируй через Claude Sonnet 4 в KingKont» или «через Gemini».
В таких редких кейсах:

```bash
kingkont gen <project> <board> --kind=text --name=name \
  --prompt="..." --model=anthropic/claude-sonnet-4
```

В обычной просьбе «напиши X» — **пиши сам**.

## ⚠️ Изменения визуально — добавляй ноды по одной

Если открыто приложение KingKont, оно поллит `scene.json` каждые 3 сек
и автоматически перерисовывает холст когда обнаруживает внешнюю правку.
Это значит **юзер видит каждую новую ноду в течение нескольких секунд**.

Поэтому когда нужно добавить несколько нод — **не батчи их в один скрипт**,
запускай `kingkont add-node` (или `gen`) по одной команде за раз. Между
командами юзер видит как ноды появляются прогрессивно.

✅ Хорошо (юзер видит появление каждой ноды):
```bash
kingkont add-node <project> <board> --kind=text --name=intro --text="..."
# (юзер видит ноду на холсте в течение нескольких секунд)
kingkont add-node <project> <board> --kind=text --name=dialog --text="..."
# (видит вторую)
kingkont add-node <project> <board> --kind=text --name=outro --text="..."
# (видит третью)
```

❌ Плохо (юзер видит «бах» — все ноды одним кадром):
```bash
# Все три node'а пишутся в scene.json одним save'ом — на холсте они
# появятся одновременно.
kingkont add-node ... --kind=text --name=intro && \
kingkont add-node ... --kind=text --name=dialog && \
kingkont add-node ... --kind=text --name=outro
```
(Это технически работает, но менее «живое» ощущение.)

Для длинных операций (десяток+ нод) можно объединять — юзер не различит
батчи быстрее ~1 сек. Но от 3 до 5-7 нод — добавляй по одной.

## Как открыть UI редактора

```bash
npx -y kingkont serve
```

Печатает `▶ KingKont готов: http://localhost:17893/`. Скажи юзеру открыть
этот URL в **Chrome / Edge / Brave** (Safari/Firefox не поддерживают
File System Access API). На macOS можно открыть автоматом:

```bash
open -a "Google Chrome" "http://localhost:17893/"
```

Альтернатива — Electron-приложение (без браузера): `npx -y kingkont`.

## Формат проектной папки

```
<root>/
├── CLAUDE.md             ← подробный формат (создаётся редактором при первом
│                            открытии в его UI; идентичен этому skill, но
│                            живёт рядом с проектом)
├── _characters/
│   └── <имя>/            ← одна доска на персонажа
├── _locations/
│   └── <имя>/            ← одна доска на локацию
├── <имя сцены>/          ← сцены лежат в корне как любые папки
│   │                       (имена не начинающиеся с `_` и `.`,
│   │                        не равные _characters / _locations)
│   ├── scene.json        ← главный файл — описан ниже
│   ├── clips/            ← видео (mp4, mov, webm)
│   ├── images/           ← картинки (jpg, png, webp)
│   ├── audio/            ← аудио (mp3, wav, m4a)
│   └── texts/            ← .md-файлы текстовых нод
├── _deleted/             ← корзина: при удалении ноды/доски файл/папка
│                            переезжает сюда. Cmd+Z в редакторе
│                            восстанавливает.
└── (любые другие файлы редактор не трогает)
```

## Формат `scene.json` (полный)

```json5
{
  "nodes": [
    {
      "id": "uuid",                          // UUID v4, уникальный в пределах доски
      "type": "image" | "video" | "audio" | "text",
      "name": "опц. имя для @-ссылок",       // используется в [@name] и автокомплите
      "file": "images/foo.jpg",              // относительный путь в папке доски
      "x": 100, "y": 200,                    // координаты на холсте (логические px)
      "width": 280, "height": 320,           // опц., если юзер ресайзил
      "generated": {                          // опц., если создано генерацией
        "kind": "image" | "video" | "audio" | "text",
        "subKind": "sfx" | "music",          // только для audio: тип эффекта
        "prompt": "...",                     // финальный промпт после resolve
        "rawPrompt": "...",                  // исходный с [@ref]
        "translatedPrompt": "...",           // для SFX/music: en-перевод (для ElevenLabs)
        "model": "nano-banana-2" | "bytedance/seedance-2"
               | "eleven_v3" | "eleven-sfx" | "eleven-music"
               | "anthropic/claude-sonnet-4" | ...,
        "modelKey": "ключ модели для KIE",
        "voiceId": "...",                    // для голоса
        "voiceName": "...",
        "tones": ["шепот", ...],             // ElevenLabs v3 audio-tags
        "durationSeconds": 3,                // для SFX
        "durationMs": 30000,                 // для music
        "refs": [{ "name": "...", "type": "image", "file": "..." }],
        "state": "submitting" | "queued" | "generating" | "success" | "error",
        "taskId": "..."                      // KIE job id
      },
      "status": "generating" | "draft" | "error",  // отсутствует если нода готова
      "error": "...",
      "description": "опц. текст-аннотация под image/video нодой",  // см. раздел «Описания нод»
      "linkedBoard": {                       // опц., ссылка на другую доску
        "kind": "episode" | "character" | "location",
        "name": "Сцена 2"                    // имя целевой доски (handle резолвится в runtime)
      },
      "history": [...],                      // история правок (через ⇄ Заменить)
      "historyIndex": 0
    }
    // Текст ноды (n.text для type=text) в JSON НЕ хранится; контент лежит в texts/<file>.md
  ],
  "connections": [
    { "from": "node-id", "to": "node-id" }   // направленная связь
  ],
  "view": {
    "scrollLeft": 0,                          // pan/zoom холста для восстановления
    "scrollTop": 0,
    "zoom": 1.0
  },
  "character": {                              // только для _characters/<name>/scene.json
    "characterSheet": "images/sheet.jpg",     // референс-картинка персонажа
    "voice": "elevenlabs-voice-id",           // дефолтный голос для генерации
    "voiceName": "...",
    "tone": "по умолчанию",
    "commonTones": ["шепот", "крик", ...],
    "lastTones": [],
    "replicas": [
      { "id": "...", "text": "...", "audio": "audio/replica_1.mp3", ... }
    ]
  },
  "location": {                               // только для _locations/<name>/scene.json
    "sheet": "images/sheet.jpg"
  },
  "timeline": {                               // монтажный лист сцены
    "tracks": [
      {
        "id": "tv",
        "name": "Видео" | "Голос" | "Эффекты" | "Музыка" | ...,
        "kind": "video" | "audio",
        "clips": [
          {
            "id": "uuid",
            "nodeId": "node-id" | null,       // привязка к ноде (если из неё)
            "type": "video" | "image" | "audio",
            "file": "clips/foo.mp4",
            "name": "...",
            "duration": 5.0,                  // в секундах
            "start": 0,                       // позиция начала на дорожке
            "trimStart": 0,                   // обрезка спереди
            "sourceDuration": 5.0,            // полная длительность файла
            "_durationLoaded": true,
            "groupId": "...",                 // опц., группа клипов
            "disabled": false                 // skip при playback
          }
        ]
      }
    ],
    "playhead": 0.27                          // секунды
  },
  "history": {                                // undo/redo стек редактора
    "past":  [{ "ts": ..., "label": "...", "snap": "JSON-string", "movedFiles": [...] }],
    "future": [...]
  }
}
```

## Текстовые ноды

`node.text` в JSON отсутствует. Контент текстовой ноды лежит в
`texts/<filename>.md` (путь в `node.file`). Редактируй .md руками; редактор
подхватит изменения при следующем открытии доски.

Чтобы создать новую текстовую ноду через правку файлов:

1. Записать содержимое в `<board>/texts/some-name.md`.
2. Добавить ноду в `<board>/scene.json`:
   ```json5
   {
     "id": "<uuid v4>",
     "type": "text",
     "name": "опционально, для @-ссылок",
     "file": "texts/some-name.md",
     "x": 100, "y": 100
   }
   ```

## Описания нод (image / video)

`image` и `video` ноды могут нести **текст-аннотацию** — `node.description`.
Это полноценная замена «соседней text-ноды»: вместо отдельной ноды юзер
может прикрепить описание прямо к картинке/видео, и оно «едет» вместе с
нодой по холсту.

**Формат** (поле в ноде):
```json5
{
  "type": "image" | "video",
  "file": "images/foo.jpg",
  "x": 100, "y": 100, "width": 280, "height": 280,
  "description": "Лес ночью, луна за облаками, тишина — общий план."
}
```

Поддерживается только для `type` ∈ `{image, video}`. Для `audio` и `text` —
игнорируется (у text — сам текст уже в `.md`, у audio — иногда нет визуального
места под caption).

**Где отображается**:

Описание рендерится **вне** ноды — как отдельный sibling-блок на холсте,
плотно под нижней границей ноды. Раньше пробовали инлайн внутри `.node`,
но `image/video` задают фиксированный aspect-ratio + `paint-containment`
у `.node` клипит overflow → текст ниже не был виден. Теперь:

- Позиция: `left = node.x`, `top = node.y + node.height + 4`
- Ширина = `node.width` (точно по ширине ноды)
- Высота = `auto` по контенту (текст любой длины помещается)
- Движется вместе с нодой при drag, resize, fit-to-media

**UI**:
- ПКМ на image/video ноде → «📝 Добавить описание…» (или «📝 Изменить
  описание…» если уже есть). Открывает большую textarea-модалку (Cmd+Enter
  — сохранить).
- Если описание пустое — sidecar-элемент удаляется из DOM (нода визуально
  возвращается к «голой» картинке).

**Когда использовать описание vs соседнюю text-ноду**:

- **Описание** — короткая аннотация, привязанная к этому конкретному кадру:
  «фон облака», «крупный план рук», «свет от свечи». Юзер видит контекст
  картинки не уходя глазами в сторону.
- **Отдельная text-нода** — самостоятельный смысловой блок (диалог, сценарий,
  заметка), который должен быть отдельной сущностью на холсте (рисуем
  связи, переставляем независимо от кадра).

**Через CLI / правку scene.json**:

```bash
# Добавить описание к существующей ноде (правка scene.json напрямую):
# найди ноду по name или id, добавь поле "description"
```

Прямой CLI-флаг для description пока **не реализован**. Если нужно массово
проставить описания — правь `scene.json` через `jq` или скриптом, редактор
подхватит при следующем открытии доски.

## Ссылки на доски (link-to-scene)

Нода может содержать ссылку на другую сцену/персонажа/локацию того же
проекта. Используется для нелинейной навигации — например, story-board
с миниатюрами сцен, по которым юзер кликает чтобы перейти.

**Формат** (поле в ноде):
```json5
"linkedBoard": {
  "kind": "episode" | "character" | "location",
  "name": "Сцена 2"
}
```

**UI**:
- ПКМ на ноде → «🔗 Сделать ссылку на сцену…» (picker всех досок проекта,
  кроме текущей). Меняется на «🔗 Перейти → <имя>» + «⛓️‍💥 Убрать ссылку»
  когда ссылка уже есть.
- На ноде со ссылкой — **большая явная кнопка «Перейти → <имя сцены>»**
  внизу ноды (чуть выше нижней границы, абсолютно позиционирована поверх
  контента). Раньше был маленький значок 🔗 в правом-верхнем углу, но юзер
  попросил убрать в пользу явной кнопки — она читабельнее на маленьких
  preview-картинках. Для `drawing`-нод (стрелок) кнопка скрыта — она там
  визуально странная.
- В share-модалке (расшарить проект) рядом с «Расшарить» есть кнопка
  «↗ Перейти» — открывает share-ссылку в новой вкладке для быстрого
  превью что увидит получатель.
- Дабл-клик на ноде = переход (перебивает обычное действие dblclick типа
  fullscreen-просмотра).
- Если целевая доска переименована/удалена — алерт «не найдена».

**Handle не сериализуется** — хранится только `{kind, name}`. При переходе
редактор резолвит через `listEpisodes/listCharacters/listLocations`.
Поэтому ссылка переживает clone-проекта (новые директории, та же
структура). Но переименование целевой доски ломает ссылку — нужно
обновлять вручную в scene.json или пересоздавать через ПКМ.

**Через CLI**: прямого `kingkont link-node` пока нет — правь
`scene.json` напрямую, добавь к ноде поле `linkedBoard`.

## Connections как авто-референсы (incoming edges)

Если в image/video/text-ноду **«входит» другая нода** (есть `connection`
где `to === thisNode.id`) — её содержимое **автоматически** идёт в refs
при генерации/регенерации этой ноды. Юзер не обязан дублировать `@name`
в промпте — граф уже выражает связь.

Правила (применяются при genSubmit и regenerateInto):
- Источник `type: image` → добавляется как image-реф (всегда).
- Источник `type: video` → добавляется как video-реф для image/video-нод;
  для text-нод **пропускается** (OpenRouter chat API не глотает video).
- Источник без `file` (ещё не сгенерирован / draft) → пропускается, чтобы
  не блокировать gen alert'ом «нода ещё не готова».
- Источник `type: text|audio|drawing|label` → не добавляется в refs
  (для text-источников юзер должен явно писать `[@name]` в промпте,
  это позволяет контролировать ГДЕ инлайнится содержимое).

Кейсы где это даёт «бесплатные» референсы:
- **New нода через anchor-drag**: тянешь линию от image-ноды на пустое
  место → меню → «Сгенерить картинку» → новая нода получает источник как
  ref без писанины в промпте.
- **Regen существующей ноды с несколькими incoming**: связал draft-image с
  тремя image-источниками (характер + локация + propsheet), нажал
  «↻ Перегенерировать» — все три попадают в refs автоматически.

Если нужно НЕ использовать какой-то incoming источник как реф — удали
саму connection (`scene.json → connections[]`) или временно переименуй
источник так чтобы он не нашёлся (или unfile его — `delete n.file`).

## Файловые операции

- **Удаление ноды**: файл переезжает в `<root>/_deleted/<уникальное-имя>`.
  Cmd+Z в редакторе восстанавливает (через movedFiles в history).
- **Замена/перегенерация**: новый файл записывается в подпапку, старый
  сохраняется в `node.history[]` для отката (⇄ Заменить).
- **Импорт через drag-drop**: файлы копируются в правильную подпапку с
  уникальным именем, нода создаётся автоматически.

## API-ключи (для генерации)

Хранятся в `~/Library/Application Support/KingKont/settings.json` (macOS)
или `~/.config/KingKont/settings.json` (Linux):

```json
{
  "kieKey":         "...",   // для картинок и видео (api.kie.ai)
  "elevenKey":      "...",   // для голоса/SFX/музыки (elevenlabs.io)
  "openrouterKey":  "..."    // для текста и vision (openrouter.ai)
}
```

Если ключа не хватает — попроси юзера задать в редакторе через `Cmd+,`.

## Работа без UI через CLI (предпочтительно)

`kingkont` устанавливается глобально: `npm i -g kingkont`. После этого все
команды ниже работают **без открытого приложения** — правят `scene.json`
напрямую через FS, а генерации запускают через те же провайдеры, что и UI
(KIE / Chatium / ElevenLabs / OpenRouter), используя ключи из
`settings.json`.

> Все `<project>` — путь к корню проекта KingKont (где живут `_characters/`,
> `_locations/`, папки сцен).
> Все `<board>` принимаются в любом из форматов:
> `Scene1`, `_characters/Anna`, `characters/Anna`, `_locations/Office`.
> `<nodeId>` принимается как полный uuid либо как имя ноды (через `--name`).

### Чтение

```bash
kingkont open <project>                    # структура проекта (с --json — JSON)
kingkont list <project>                    # списки досок
kingkont board <project> <board>           # одна доска (с --json — полный JSON)
kingkont status <project> <board> <id>     # прогресс in-flight задачи
kingkont balance                           # балансы всех включённых провайдеров
kingkont voices                            # все ElevenLabs голоса
```

### Создание / правка нод

```bash
# Просто добавить пустую ноду без генерации:
kingkont add-node <project> <board> --kind=text  --name=plot --text="..."
kingkont add-node <project> <board> --kind=image --name=hero
kingkont add-node <project> <board> --kind=video --name=intro [--x=200 --y=100]

# Связать ноды (по id или по name):
kingkont connect <project> <board> plot hero

# Удалить ноду (файл переезжает в _deleted/):
kingkont rm-node <project> <board> hero
```

### Генерация — главная команда `gen`

`gen` совмещает «создать ноду + сразу запустить генерацию + дождаться + сохранить
файл в нужную подпапку доски». Блокируется до завершения (для image — секунды,
для video — до 10 мин).

```bash
# Картинка через KingKont/KIE (модель — короткий ключ или полный slug)
kingkont gen <project> <board> --kind=image --prompt="..." --model=nano-banana-2
kingkont gen <project> <board> --kind=image --prompt="..." --model=grok --aspect-ratio=16:9

# С референсами — @-ссылки на ноды/character/location в проекте
kingkont gen <project> <board> --kind=image --prompt="hero in @office" \
  --refs=@Anna,@office --model=nano-banana-2

# Видео (Seedance, Veo, Kling и др. через Chatium):
kingkont gen <project> <board> --kind=video --prompt="..." \
  --model=seedance-2 --aspect-ratio=16:9 --duration=5
kingkont gen <project> <board> --kind=video --prompt="..." \
  --model=kling-3.0 --refs=@hero       # i2v: ref → image_urls автоматически

# Текст: НЕ запускай `kingkont gen --kind=text` — пиши текст САМ в чате
# (ты Claude, генерация через приложение бессмысленный round-trip).
# Затем сохрани результат в ноду одной командой:
kingkont add-node <project> <board> --kind=text --name=script \
  --text="$(cat <<'EOF'
Здесь полный текст, который ты сам сгенерировал в ответе юзеру.
Многострочный, с диалогами, ремарками — всё что нужно.
EOF
)"
# (см. отдельный раздел «Text-ноды — генерируй сам, не через CLI» ниже.)

# Голос (TTS):
kingkont gen <project> <board> --kind=audio --prompt="Привет, мир" \
  --voice=JBFqnCBsd6RMkjVDRZzb

# SFX (звуковой эффект, лучше en-промпт):
kingkont gen <project> <board> --kind=audio --sub-kind=sfx --prompt="heavy door slam" --duration=3

# Музыка:
kingkont gen <project> <board> --kind=audio --sub-kind=music \
  --prompt="cinematic orchestral hopeful" --duration-ms=30000
```

После `gen` нода имеет `n.file` (относительный путь к сохранённому файлу),
а в `n.generated.creditsCharged` — стоимость, если её вернул провайдер.

### Перезапустить ноду в статусе `error` или `draft`

```bash
kingkont generate <project> <board> <nodeId>
```

Берёт `n.generated.prompt` и `n.generated.model` из существующей ноды, резолвит
её `n.generated.refs`, запускает заново.

### Доступные модели

| `--kind` | Поставщик | `--model` ключи |
|---|---|---|
| `image` | KingKont / KIE | `nano-banana-2`, `nano-banana-pro`, `grok`, `grok-i2i`, `seedream-5-lite`, `flux-schnell`, `sdxl-lightning` |
| `video` | KingKont | `seedance-2`, `seedance-2-fast`, `veo-3.1`, `kling-o1`, `kling-3.0`, `kling-v3-omni`, `wan-2.7-i2v`, `runway`, `grok-i2v`, `grok-t2v` |
| `video` | KIE | `seedance-2` |
| `text` | KingKont / OpenRouter | `anthropic/claude-sonnet-4`, `openai/gpt-4o`, `google/gemini-2.0-flash`, etc |
| `audio` (TTS) | KingKont | `qwen/qwen3-tts`, `elevenlabs/v3`, `minimax/speech-02-hd`, `google/gemini-3.1-flash-tts-preview` |
| `audio` (TTS) | ElevenLabs | `eleven_v3` (форсится при прямом ElevenLabs-подключении) |
| `audio` (sfx/music) | KingKont / ElevenLabs | без `--model` |

Если указанная модель не поддерживается активным провайдером — CLI вернёт
понятную ошибку со списком доступных.

### Загрузка файла в storage

```bash
kingkont upload <file>      # печатает публичный URL
```

Полезно когда юзер просит «сделай эту картинку референсом для генерации» —
сначала аплоадим, потом передаём URL в `--refs`.

### `--json` для машино-читаемого вывода

Все команды поддерживают `--json` — печатают строгий JSON в stdout (логи и
прогресс генерации идут в stderr). Используй для парсинга в скриптах.

## HTTP API (альтернатива — когда UI или сервер запущены)

Если по каким-то причинам CLI недоступен (другая машина, скрипт с curl,
интеграция в другой инструмент) — все те же возможности доступны через HTTP.
Запусти `npx -y kingkont serve` и дёргай endpoints:

| Метод | Путь | Что |
|---|---|---|
| POST | `/api/text` | текст: `{prompt, model, system?, images?}` → `{text, model, cost?}` |
| POST | `/api/tts` | TTS: `{text, voice/voiceId, ttsModel?}` → `audio/mpeg` |
| POST | `/api/sfx` | SFX: `{text, durationSeconds?, promptInfluence?}` → `audio/mpeg` |
| POST | `/api/music` | Music: `{prompt, durationMs?}` → `audio/mpeg` |
| POST | `/api/generate` | image/video: `{kind, prompt, model, imageInputs?, aspectRatio?, ...}` → `{taskId}` |
| GET | `/api/poll?taskId=...` | poll → `{status: pending\|done\|error, url?, cost?}` |
| POST | `/api/upload` | upload binary, header `X-File-Name: ...` → `{url, hash, size}` |
| GET | `/api/proxy?url=...` | прокси для CORS-bypass |
| GET | `/api/balance/all` | балансы всех провайдеров |
| GET | `/api/voices` | список ElevenLabs голосов |

Те же ключи и роутинг, что и CLI — не дублируй логику в curl-скриптах,
лучше используй `kingkont`.

## Распространённые сценарии для агента

**«Открой редактор»** → `npx -y kingkont serve` + сообщить URL для Chrome.

### Юзер просит сгенерировать что-то (картинку/видео/голос/SFX/текст)

**Сначала всегда** — определи доску. Не угадывай и не создавай новую.

```bash
kingkont open <project>     # JSON со списком всех досок
```

Если юзер уже назвал доску в реплике («добавь в Серию 1», «на сцене Прощание»)
— используй её. Иначе спроси: «У тебя есть сцены: 1) Серия 1, 2) Серия 2,
3) Серия 4.1. В какой добавить?» — и **жди ответа**.

Если юзер прислал несколько фото/референсов и сказал «вот, сгенери что-то по
ним» — это НЕ повод создать N новых сцен по одной на каждое фото. Это
повод спросить «в какой сцене разложить эти N кадров?».

После того как доска определена:

```bash
kingkont gen <project> <board> --kind=image --prompt="..." --refs=@ref1
```

### Юзер просит добавить персонажа / локацию / новую сцену

Только тогда создавай папки. Триггерные фразы: «новая сцена», «добавь
сцену», «создай эпизод», «новый персонаж», «добавь персонажа», «новая
локация».

**«Добавь персонажа Анна»**:
```bash
mkdir -p <project>/_characters/Анна
echo '{"nodes":[],"connections":[]}' > <project>/_characters/Анна/scene.json
# Дальше — генерим character sheet:
kingkont gen <project> _characters/Анна --kind=image --name=sheet \
  --prompt="портрет молодой женщины-следователя" --model=nano-banana-2
```

**«Создай новую сцену "Финал"»**:
```bash
mkdir -p <project>/Финал
echo '{"nodes":[],"connections":[]}' > <project>/Финал/scene.json
```
После этого можно генерить ноды (текст — пишешь сам, см. раздел ниже;
картинки/видео/аудио — через `kingkont gen`).

### Узкие сценарии

**«Добавь текстовую ноду на сцену "Прощание"»**:
```bash
kingkont add-node <project> Прощание --kind=text --name=plot --text="ТЕКСТ"
```

**«Свяжи ноды @А и @Б»** (в одной доске):
```bash
kingkont connect <project> Прощание А Б
```

**«Сгенерируй SFX "камни сыпятся" в Серии 1»**:
```bash
kingkont gen <project> 'Серия 1' --kind=audio --sub-kind=sfx \
  --prompt="rocks falling, gravel cascading" --duration=3
```
(SFX-модель ElevenLabs работает лучше на английском — переводи русский промпт.)

**«Сделай i2v видео из этой картинки»** (нода @hero уже в доске):
```bash
kingkont gen <project> <board> --kind=video --refs=@hero \
  --model=kling-3.0 --prompt="hero turns and walks away"
```

## Не трогай руками

- `node.history[]`, `metadata.history.past/future` — снимки сцены для Cmd+Z.
- `id` нод (UUID) — на них завязаны связи и timeline-clips.
- `_deleted/` — корзина (юзер может восстановить через Cmd+Z в редакторе).

## Шорткаты редактора (для подсказок юзеру)

- `Cmd+O` — открыть проект
- `Cmd+N` — новая сцена
- `Cmd+W` — закрыть проект (при втором нажатии — свернуть)
- `Cmd+,` — настройки (API-ключи)
- `Cmd+Z` / `Cmd+Shift+Z` — undo/redo
- `Cmd+X / C / V` — cut/copy/paste нод и клипов
- ПКМ по ноде / клипу / папке — контекст-меню
- ПКМ на пустом холсте — добавить новую ноду (Текст / Сгенерить ...)
- Тяга от точки на правом краю ноды → меню «что генерить»
