@imgly/codesign-mcp 0.3.5-next.20260917153831.ga054306 → 0.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,7 +2,8 @@
2
2
  name: formats
3
3
  description: >-
4
4
  Canvas-format reference for CoDesign designs — one file per screen/print format (ig-post,
5
- ig-square, ig-story, widescreen, linkedin-post, x-post, deck, poster, flyer, business-card):
5
+ ig-square, ig-story, widescreen, linkedin-post, x-post, youtube-thumbnail, deck, poster, flyer,
6
+ business-card):
6
7
  exact W×H, aspect, medium, platform safe zones, print bleed/dpi. Use when picking or setting up
7
8
  a canvas size, checking a platform safe zone, or preparing print dimensions. Re-composing an
8
9
  existing design into a format is the `resize` skill; new-design intake is the `create` skill.
@@ -16,18 +17,19 @@ across all formats: `skill({ name: 'formats', query: 'safe zone' })`.
16
17
 
17
18
  ## Pick a format
18
19
 
19
- | id | size | aspect | medium | safe zones / print | file |
20
- | --------------- | -------------------------------- | ------- | ------ | -------------------------------- | ------------------ |
21
- | `ig-post` | 1080 × 1350 px | 4:5 | screen | none (no chrome) | `ig-post.md` |
22
- | `ig-square` | 1080 × 1080 px | 1:1 | screen | none (no chrome) | `ig-square.md` |
23
- | `ig-story` | 1080 × 1920 px | 9:16 | screen | **top ~250 px · bottom ~320 px** | `ig-story.md` |
24
- | `widescreen` | 1920 × 1080 px | 16:9 | screen | none (no chrome) | `widescreen.md` |
25
- | `linkedin-post` | 1200 × 627 px | ≈1.91:1 | screen | none; feed may crop edges | `linkedin-post.md` |
26
- | `x-post` | 1600 × 900 px | 16:9 | screen | none; timeline may crop edges | `x-post.md` |
27
- | `deck` | 1920 × 1080 px per slide | 16:9 | screen | none | `deck.md` |
28
- | `poster` | A2 420 × 594 mm / 1080 × 1920 px | — | both | print: +3 mm bleed · ≥300 dpi | `poster.md` |
29
- | `flyer` | A5 · A4 · DL | — | print | +3 mm bleed · ≥300 dpi | `flyer.md` |
30
- | `business-card` | EU 85 × 55 mm · US 3.5 × 2 in | — | print | +3 mm bleed · ≥300 dpi | `business-card.md` |
20
+ | id | size | aspect | medium | safe zones / print | file |
21
+ | ------------------- | -------------------------------- | ------- | ------ | -------------------------------- | ---------------------- |
22
+ | `ig-post` | 1080 × 1350 px | 4:5 | screen | none (no chrome) | `ig-post.md` |
23
+ | `ig-square` | 1080 × 1080 px | 1:1 | screen | none (no chrome) | `ig-square.md` |
24
+ | `ig-story` | 1080 × 1920 px | 9:16 | screen | **top ~250 px · bottom ~320 px** | `ig-story.md` |
25
+ | `widescreen` | 1920 × 1080 px | 16:9 | screen | none (no chrome) | `widescreen.md` |
26
+ | `linkedin-post` | 1200 × 627 px | ≈1.91:1 | screen | none; feed may crop edges | `linkedin-post.md` |
27
+ | `x-post` | 1600 × 900 px | 16:9 | screen | none; timeline may crop edges | `x-post.md` |
28
+ | `youtube-thumbnail` | 1280 × 720 px | 16:9 | screen | bottom-right duration badge | `youtube-thumbnail.md` |
29
+ | `deck` | 1920 × 1080 px per slide | 16:9 | screen | none | `deck.md` |
30
+ | `poster` | A2 420 × 594 mm / 1080 × 1920 px | — | both | print: +3 mm bleed · ≥300 dpi | `poster.md` |
31
+ | `flyer` | A5 · A4 · DL | — | print | +3 mm bleed · ≥300 dpi | `flyer.md` |
32
+ | `business-card` | EU 85 × 55 mm · US 3.5 × 2 in | — | print | +3 mm bleed · ≥300 dpi | `business-card.md` |
31
33
 
32
34
  **Custom sizes:** an explicit `W×H` (+ unit: px, mm, in) is always valid. Screen medium uses the
33
35
  dimensions as-is; print medium adds **3 mm bleed** and targets **≥300 dpi**.
@@ -0,0 +1,17 @@
1
+ # youtube-thumbnail — YouTube video thumbnail
2
+
3
+ - **Size:** 1280 × 720 px
4
+ - **Aspect:** 16:9 (landscape)
5
+ - **Medium:** screen
6
+ - **Upload limit:** 2 MB — JPG, PNG or GIF; export JPEG unless the design needs transparency
7
+
8
+ ## Safe zones
9
+
10
+ - **Bottom-right ~140 × 50 px:** the player's duration badge covers it in feeds and search.
11
+ - **Bottom edge:** a red watch-progress bar overlays it on videos the viewer has started.
12
+
13
+ ## Notes
14
+
15
+ - Shown as small as ~168 × 94 px in sidebars and on phones — headlines need few words, heavy
16
+ weights and an outline or shadow to read at that size.
17
+ - Building a reusable thumbnail template for a channel is the `video-thumbnail-template` skill.
@@ -0,0 +1,141 @@
1
+ ---
2
+ name: video-thumbnail-template
3
+ description: >-
4
+ Use when building a reusable thumbnail template and design guideline for a video channel
5
+ (YouTube and other 16:9 video platforms) — from a channel link whose latest thumbnails set the
6
+ style, from one or more example thumbnails or designs, or from scratch; or when making new
7
+ thumbnails from such a template, one at a time or in a batch.
8
+ ---
9
+
10
+ # video-thumbnail-template
11
+
12
+ Turn a channel's thumbnail style into a design guideline (a brand kit) and a CoDesign template
13
+ with named slots, then fill the template for each new video.
14
+
15
+ Builds on, without restating: the handbook loop, the `brand` skill's kit format, the `layerize`
16
+ skill (Seedream decomposition), the `models` skill, the `formats` skill (`youtube-thumbnail.md`)
17
+ and the `judge` gate.
18
+
19
+ ## Input
20
+
21
+ One of three, resolved with the intake contract (`skill({ name: 'handbook', path: 'intake.md' })`):
22
+
23
+ - **Channel link** — its latest 10 thumbnails set the style.
24
+ - **Example thumbnails or designs** — one or more images, or design files (`.imgly`, `.psd`, …).
25
+ - **Nothing** — start fresh; ask for the visual direction per the intake contract.
26
+
27
+ The format defaults to `youtube-thumbnail` (1280 × 720). Everything goes into one output folder
28
+ (default `./<channel-or-topic>-thumbnails/`): `reference/`, `brand-kit/`, the template and the
29
+ thumbnails. A thumbnail style is the channel's brand: never invent it when references exist.
30
+
31
+ ## 1 — Collect references
32
+
33
+ **Channel link.** Fetch the channel's `/videos` page (e.g. `https://www.youtube.com/@handle/videos`)
34
+ with your own web or shell tools. That tab lists regular uploads only, newest first; the ids are in
35
+ the page source as `"videoId":"<11 chars>"`. Take the first 10 distinct ids and download each
36
+ thumbnail from `https://i.ytimg.com/vi/<id>/maxresdefault.jpg` (fall back to `hqdefault.jpg`) into
37
+ `reference/` — public URLs, no API key or login. If the page cannot be read, ask the user for a
38
+ few video links or thumbnail images instead.
39
+
40
+ **Example thumbnails.** Copy the files the user named into `reference/`. A design file also goes
41
+ through `import` — its layers show the structure directly.
42
+
43
+ **Nothing.** No references; go to step 3 with the direction the user chose.
44
+
45
+ ## 2 — Analyse the layouts
46
+
47
+ Look at every reference yourself. For **each** one, record its layout — positions and sizes as
48
+ fractions of the canvas, so they compare across images:
49
+
50
+ - zones: where the host, the headline and the subject sit, and how much of the canvas each takes
51
+ - headline: the words, line count, the role of each line (hook vs punch word), case, relative
52
+ size, colour, stroke, shadow, closest Google Font
53
+ - host: side, crop, expression, gaze, outline or glow
54
+ - background: image or colour, mood, whether it repeats
55
+ - devices: arrows, badges, frames, blur, emoji
56
+
57
+ Then compare the set: what repeats in most references is the **system**, what appears once is an
58
+ option, and what changes from video to video becomes a **slot**. A set that splits into two or
59
+ three distinct layouts gets one template per layout — name them and ask which to build if unsure.
60
+
61
+ ## 3 — Design guideline (brand kit)
62
+
63
+ Write the guideline as a brand kit in the `brand` skill's format — `brand-kit/tokens.json` +
64
+ `brand-kit/BRAND.md`. Read `skill({ name: 'brand', path: 'reference/brandkit-spec.md' })` for the
65
+ shape and `reference/authoring.md` for the rules (screenshots are its source form D). Fill it from
66
+ the analysis:
67
+
68
+ - `colorPrimitives` / `semanticColors` — the colours sampled from the references (say they are
69
+ sampled)
70
+ - `typography` — the headline font(s), weights, case and the size ratio between the lines
71
+ - `layout` / `composition` — the zones from step 2, as fractions of the canvas
72
+ - `safeArea` — the platform's (the duration badge, bottom right, for YouTube)
73
+ - `imagery` — host crop and treatment, background mood
74
+ - `device` — the recurring devices
75
+ - `voice` — headline formulas: words per line, the punch words in use
76
+ - `logo` — the channel's mark if the thumbnails carry one; otherwise record it in `$meta.gaps`
77
+
78
+ Draft the `avoid` list from what the references never do and confirm it with the user, as
79
+ `authoring.md` requires.
80
+
81
+ **Starting fresh**, the guideline comes first too: turn the chosen direction into the same kit,
82
+ show the user its key choices (palette, fonts, layout) and confirm them before building.
83
+
84
+ ## 4 — Layers and assets
85
+
86
+ When a reference supplies a host, subject or background worth reusing, split it with the `layerize`
87
+ skill's Stage 1 (Seedream) and follow its rules. Keep the person, subject and residual background
88
+ layers; drop the text layers — the headline is rebuilt as live text.
89
+
90
+ A user's host photo that is not cut out goes through background removal (`models` skill,
91
+ `bria-rmbg.md`). A missing background can be generated from the kit's `imagery` mood,
92
+ `format: '16:9'`, with "no people, no text, no logos" in the prompt.
93
+
94
+ ## 5 — Build the template
95
+
96
+ Build from the kit: one page at the format size, the zones from `layout`, back to front. Name the
97
+ slots so they can be filled later:
98
+
99
+ | name | kind | notes |
100
+ | ---------- | ------- | ------------------------------------------------------ |
101
+ | `bg` | graphic | full-bleed image fill, `Cover` |
102
+ | `host` | graphic | cut-out, `Contain`, anchored to the edge it grows from |
103
+ | `subject` | graphic | optional cut-out, `Contain` |
104
+ | `line1` | text | the hook |
105
+ | `line2` | text | optional punch word, `text.case: 'Uppercase'` |
106
+ | `headline` | group | `line1` + `line2` |
107
+
108
+ Drop `host` when the style has no presenter; decoration from the style gets its own descriptive
109
+ name. When a headline line is too long for its column, shrink it until it fits — never let it run
110
+ to the edge or into the safe-area corner.
111
+
112
+ Test the template with a long punch word, a word with a capital diacritic (`ÜBERNIMMT`) and a
113
+ thumbnail without the optional slots, then run the `judge` loop with the kit as the brief. The
114
+ references are the style: tight headlines or a host bleeding off the canvas are not faults.
115
+
116
+ Export it with `export({ format: 'imgly', outPath })` into the output folder and report the kit,
117
+ the slots and any `$meta.gaps`.
118
+
119
+ ## 6 — New thumbnails
120
+
121
+ For each thumbnail, `edit` with `parent` = the **template revision** — never chain one
122
+ thumbnail onto the previous — fill the slots, hide the empty ones, fit the headline, and export
123
+ JPEG (`export({ format: 'jpeg', blockId: <page>, outPath })`; YouTube rejects uploads over 2 MB).
124
+ Look at every export and check it against the kit's `avoid` list.
125
+
126
+ For a batch, agree on a simple table with the user (e.g. a CSV with one row per video: headline
127
+ lines and image files) and repeat the above per row. If they want to render outside the agent,
128
+ write them a small `@cesdk/node` script that loads the exported `.imgly`, fills the same slots by
129
+ name and exports each row; without a CE.SDK licence key it watermarks its output — say so.
130
+
131
+ ## Common mistakes
132
+
133
+ | Mistake | Fix |
134
+ | --------------------------------------- | ---------------------------------------------------------------------- |
135
+ | Styling from one thumbnail of ten | analyse every reference; the system is what repeats |
136
+ | Building before the guideline exists | write and confirm the kit first — the template is built from it |
137
+ | New image fill comes out zoomed/cropped | `forceLoadResources([slot])`, then `resetCrop`, then re-apply the mode |
138
+ | Moving `line1`/`line2` while grouped | positions are relative to the group — ungroup, position, regroup |
139
+ | Typing capitals into `line2` | keep the text as typed; the slot's `text.case` sets it |
140
+ | Sizing Seedream cut-outs by pixel size | place by `bounding_box.absolute` |
141
+ | A person placed with `Cover` | `Contain`, anchored to the bottom edge |
@@ -1 +1 @@
1
- {"version":3,"file":"inline.generated.d.ts","sourceRoot":"","sources":["../../src/assets/inline.generated.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAqmBhD,CAAC;AAEF,gEAAgE;AAChE,eAAO,MAAM,oBAAoB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAEvD,CAAC"}
1
+ {"version":3,"file":"inline.generated.d.ts","sourceRoot":"","sources":["../../src/assets/inline.generated.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAumBhD,CAAC;AAEF,gEAAgE;AAChE,eAAO,MAAM,oBAAoB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAEvD,CAAC"}
@@ -11,7 +11,7 @@ export const INLINE_ASSETS = {
11
11
  "docs/create/SKILL.md": "---\nname: create\ndescription: >-\n Use when the user asks to create / generate / make a NEW design from a prompt — a deck\n or slides, poster, flyer, social post, business card, or a custom-size canvas. Runs the\n guided intake: derive every brief parameter the prompt already answers (class, format,\n audience, vibe, brand, content), ask ONLY the missing questions — one round, chip-style\n options, \"Decide automatically\" defaults — then echo the design brief and hand off to the\n handbook Loop. A brief given to the /codesign prompt lands here as the stated input.\n---\n\n# create — derive-then-ask intake for a new design\n\nTurn \"make me a deck / poster / post\" into a structured **design brief** before any\n`edit` runs. The one rule:\n\n> **Derive every parameter you can from the prompt. Ask only the questions the prompt\n> left unanswered. If nothing is missing, don't ask anything — build.**\n\nThat rule is not local to this skill: it is the shared intake contract in\n`skill({ name: 'handbook', path: 'intake.md' })`, which `brand`, `resize` and `localize`\nfollow with their own parameter tables. This skill is its worked example — the five steps\nbelow are that contract plus the class system it needs. Read the contract for the parts\nthis skill doesn't restate: the never-invent rule, data-driven chips, and why a handoff\nfrom another skill counts as input already given.\n\nThis skill owns the intake only. Zero design rules live here — the brief hands off to\nthe `handbook` Loop (which owns how to build) and the `judge` gate (which owns done).\n\n**Bundled files:** `classes.md` — the full per-class parameter tables and derivation\nnotes. Read it once you know (or must ask) the class. `choice-copy.md` — the grammar\nevery question and chip must follow; read it before wording any question of your own.\n\n## The intake protocol\n\nFive steps, in order:\n\n1. **Classify.** Map the request to a design class: `deck`, `poster`, `flyer`,\n `social-post`, `business-card`, or `custom` (explicit dimensions). If the class is\n ambiguous, it becomes the first — often only — question.\n\n2. **Derive.** Fill the class's parameter table (see `classes.md`) from everything\n already available: the prompt, earlier conversation, attached files/images, a\n provided brand kit. A parameter counts as answered if it is stated explicitly\n (\"10-minute investor pitch\") or safely inferable (\"for our Series A roadshow\" →\n audience: investors). Track each value's source: `stated`, `inferred`, or `default`.\n A brief passed to the `/codesign` prompt counts as `stated`.\n\n3. **Ask — once.** Collect the still-unanswered parameters the class marks `ask` and\n put them to the user in a **single round**, phrased as the class's canned questions\n with their chip options. **Ask through your own environment's question UI whenever\n it has one** — a structured question tool, an elicitation API, any form-style UI:\n options become chips, \"Other\" stays free. Only when your environment offers nothing\n at all, fall back to one compact markdown form listing only the missing questions.\n **The threshold for asking at all is countable, not a judgement call:** two or more\n of the five design-defining parameters (topic, audience, visual direction, brand,\n format) neither stated nor inferable → you ask. And **visual direction and brand are\n never silently defaulted on a first build** — see `skill({ name: 'handbook', path:\n'intake.md' })`.\n Never ask a second round: if an answer is still vague, take the default and say so\n in the brief. Every question offers **\"Decide automatically\"** — only `required`\n parameters (usually just the topic/content) have no default. Word every question\n and chip by `choice-copy.md`: system-POV headers, verb-led titles, no pronouns in\n titles, and each value list internally parallel.\n\n4. **Brief.** Echo the assembled brief in 3–6 lines — class, format, audience, vibe,\n brand, content plan, content language — tagging inferred and defaulted values\n (\"audience: investors, inferred\") so a wrong derivation is correctable at a glance.\n Write the brief in the user's language (handbook §1), and when the design's **content\n language** was defaulted from the conversation rather than stated, say so on its line:\n it is the one derivation the user cannot see coming. Don't block on\n confirmation: state the brief and proceed, unless the user asked to see options\n first (or picked \"Explore a few options\" for the vibe — see below).\n\n5. **Hand off.** Enter the handbook Loop: `parent: null`, `title` from the brief,\n format/page setup from the class's derivation notes. Consult `brand` if a brand kit\n is in play; finish through the `judge` gate as always.\n\n## Exemplar: the `deck` intake\n\nThe canonical question set — offer these chips verbatim when asking:\n\n1. **What's the deck about?** — free text: topic, purpose, key points or content\n already in hand. **Required — the only question with no default.**\n2. **Who's the audience?** — `Executives / leadership` · `Team / internal` ·\n `Customers / prospects` · `Investors` · `Broad / public` · Other.\n Default: inferred from topic, else broad/public.\n3. **How long is the talk?** — `~5 min` · `~10 min` · `~20 min` · `~30+ min` ·\n `Decide automatically` · Other. Derives slide count ≈ 1.5 × minutes, clamped to\n 5–30. Default: ~10 min.\n4. **What visual vibe would you like?** — `Clean & minimal` · `Bold & editorial` ·\n `Corporate / professional` · `Warm & friendly` · `Explore a few options` ·\n `Decide automatically` · Other. Default: decide from topic + audience.\n `Explore a few options` → build 2–3 style frames of the title slide first, preview\n them, let the user pick, then continue the deck in the chosen direction.\n\n **This question is asked, not defaulted, on any first build where the direction is\n not implied** by the request, a supplied brand kit, a reference image or a prior\n design. It is the parameter the user sees first and the one a skipped intake\n invents most often.\n\n **`Explore a few options` is every class's remedy, not just `deck`'s** — whenever\n direction is the only thing left open and the user takes `Decide automatically`,\n prefer building 2–3 style frames of the first page, previewing them, and asking\n which. Showing options costs one round of real work and answers the question far\n better than adjectives do; it is explicitly not the forbidden \"second round\".\n\n5. **Any color or brand preferences?** — free text: brand colors, hex codes,\n `neutral / monochrome`, or an attached brand kit. Blank → designer's choice.\n A brand kit routes through the `brand` skill.\n6. **Do you have the content?** — `Provide the content` · `Provide rough notes` ·\n `Draft from the topic` · Other.\n Default: draft from the topic.\n\nThe other classes follow the same pattern with their own parameters and formats —\nall specced in `classes.md`.\n\n## Worked examples\n\n- _\"Series A pitch deck for our robotics startup, 10 minutes, use our brand kit\n (attached), content in the attached notes.\"_ → class, topic, audience, length,\n brand, and content are all stated or inferable → **zero questions.** Echo the brief,\n build.\n- _\"Make me a deck about Q3 results.\"_ → topic stated; ask audience + length + vibe in\n one round; brand and content take defaults, stated in the brief.\n- _\"A poster.\"_ → nothing but the class is known → ask the poster question set in one\n round (topic required; venue, vibe, brand, imagery with defaults).\n",
12
12
  "docs/create/choice-copy.md": "# Choice copy — one grammar for every list of options\n\nThe rules for any question this server puts to a user with more than one answer:\nthe `onboard` job menu, every `create` intake question, and anything added later.\nThe point is scannability — a list where no two rows share a grammar cannot be\nread at a glance.\n\n1. **Headers ask the user, from the system's side.** \"What would you like to do\n first?\", \"How would you like to start?\", \"What visual vibe would you like?\"\n Never a bare noun label (\"Visual vibe\"), and never a header that narrates what\n you are about to do.\n\n2. **Card titles are verb-led — action verb + object.** \"Convert an existing\n file\", \"Sign in to IMG.LY\", \"Join the Discord\". Not a bare noun (\"A second\n opinion\"), not a fragment (\"One design, every size\").\n\n3. **No personal pronouns in titles** — no _I_, _my_, _you_, _our_. A title is\n an instruction, not a voice. (`onboard`'s \"Rebrand: Enforce your brand design\n rules\" is a deliberate, singular exception: there, the possessive is the\n point.)\n\n4. **Subtext addresses the user as \"you\", or names the capability.** \"Turns\n Photoshop files back into something you can edit\", \"Builds a deck, poster, or\n flyer\". Never in the user's voice (\"I have rough notes\"), and never as \"I\".\n\n## The `Label:` prefix\n\n`onboard`'s six job cards — and only those — carry a label before the phrase:\n`Create: Make something`, `Judge: Give feedback to an existing design`.\nVerb-led on both sides of the colon. They earn it by naming durable capabilities\na user comes back for; a this-or-that choice with a colon label is just noise, so\ntwo-option questions and checklist rows take the bare verb-phrase form.\n\n## Value chips are exempt\n\nRule 2 stops at cards that pick a **path or an action**. Enumerated attribute\nvalues stay value phrases:\n\n```\nprint format A5 · A4 · DL\nsides 1 · 2\ntalk length ~5 min · ~10 min · ~20 min · ~30+ min\nvenue Print · Screen\nplacement Instagram post · Instagram story · LinkedIn · X\n```\n\nVerbing these (\"Print at A4\", \"Plan a 10-minute talk\") disguises a size picker as\nan action menu and buys nothing. What **is** required of value chips is\n**internal** parallelism: every chip in one list takes the same form as its\nsiblings. `Provided · Search assets · Typographic only` mixes a participle, an\nimperative and an adjective for the same parameter — it becomes `Provided ·\nSearched · Type only`.\n\n## Every question carries options\n\nA question with no options is not an intake question. Each one offers **three to five\nconcrete choices**, plus `Decide automatically`, plus Other — even when the parameter\nfeels open-ended. \"What visual vibe would you like?\" with four named directions is\nanswerable in one click; the same question as bare prose is a writing assignment, and\nthe user's most likely reply to a writing assignment is \"you decide\" — which is how a\ndesign ends up in a style nobody picked.\n\nThe single exception is the topic/content **R** parameter: free text, no chips.\n\n## The default chip\n\nEvery optional parameter offers **`Decide automatically`** — same words every\ntime, so it is recognisable across every question. Never \"Decide for me\" (a\npronoun), never \"Skip\" (reads as \"leave it blank\" when something sensible does in\nfact happen).\n\n## In the user's language\n\nThese rules are about **grammar, not English**. When the user writes in another\nlanguage, every header, title and subtext below is written in theirs (handbook\n§1) — and the rules still bind: the header still asks from the system's side, the\ntitles are still verb-led with no pronouns, and each list is still parallel within\nitself. A German menu whose entries mix an imperative and a bare noun is exactly\nas unscannable as an English one.\n\nTwo consequences worth stating:\n\n- **The default chip is one consistent translation per language**, not the\n English words. Pick a rendering of `Decide automatically` and use that same\n rendering on every question in that language — the point of the rule is that a\n user recognises the chip instantly, and that survives translation only if you\n are consistent.\n- **Translate labels, never values.** A chip's visible label is copy; `A4`,\n `ig-story`, `1080×1350`, `#showcase` and anything fed back into a tool call are\n values. Translating a value breaks the call it feeds.\n",
13
13
  "docs/create/classes.md": "# Design classes — parameter tables and derivation notes\n\nFull per-format data (safe zones, bleed/dpi mechanics, all social placements) lives in the\n`formats` skill — the **Format:** lines below are the class defaults only.\n\nEvery class shares the core parameters; each adds its own. Column key:\n**R** = required, no default (must be stated or asked); **A** = ask if underived;\n**D** = defaultable, never worth a question on its own.\n\nCore (all classes): `topic/content` **R** · `format/size` **D** (class default below) ·\n`audience` **A** · `vibe` **A**\\* · `brand/colors` **D**\\* (blank → designer's choice; a\nbrand kit routes through the `brand` skill) · `content availability` **D** ·\n`content language` **A**.\n\n\\* `vibe` and `brand/colors` are **design-defining**: on the **first build of a new\ndesign** they are asked unless the request, a supplied kit, a reference image or a prior\ndesign implies them — never silently defaulted. Their defaults still apply once the user\nhas taken `Decide automatically`. See\n`skill({ name: 'handbook', path: 'intake.md' })`.\n\n`content language` is the language of the words **inside** the design — not the language\nyou are speaking (handbook §1). Derive it: an explicit ask wins, then the language of any\nsupplied copy or topic, then the conversation's language as the default. It is worth a\nchip row only when those disagree; otherwise take it and name it in the brief\n(\"content language: German, from the conversation\"). Chips: the conversation's language ·\n`English` · Other.\n\n## deck\n\n| Parameter | Kind | Chips / values | Default |\n| ----------- | ------- | ------------------------------------------------------------ | ------- |\n| talk length | A | ~5 min · ~10 min · ~20 min · ~30+ min · Decide automatically | ~10 min |\n| slide count | derived | ≈ 1.5 × minutes, clamped to 5–30 | 15 |\n\n**Format:** 1920×1080 px (16:9), one page per slide.\n**Derivation:** length → slide count; content availability decides outline-first\n(draft from topic) vs. layout-first (paste/attach).\n\n## poster\n\n| Parameter | Kind | Chips / values | Default |\n| --------- | ---- | ------------------------------- | -------- |\n| venue | A | Print · Screen | Print |\n| imagery | D | Provided · Searched · Type only | Searched |\n\n**Format:** print → A2 portrait (420×594 mm) + 3 mm bleed; A1/A3 on request; screen →\n1080×1920 px unless stated.\n\n## flyer\n\n| Parameter | Kind | Chips / values | Default |\n| ------------ | ---- | -------------- | ------- |\n| print format | A | A5 · A4 · DL | A5 |\n| sides | D | 1 · 2 | 1 |\n\n**Format:** chosen print size + 3 mm bleed; two-sided → one page per side.\n\n## social-post\n\n| Parameter | Kind | Chips / values | Default |\n| -------------------- | ---- | ----------------------------------------------- | ------------------ |\n| platform + placement | A | Instagram post · Instagram story · LinkedIn · X | Instagram post |\n| copy | D | Provided · Drafted from topic | Drafted from topic |\n\n**Format (derived from placement):** Instagram post 1080×1350 px · Instagram story\n1080×1920 px · LinkedIn 1200×627 px · X 1600×900 px.\n\n## business-card\n\n| Parameter | Kind | Chips / values | Default |\n| ---------------------- | ---- | ------------------------- | ------- |\n| name + contact details | R | free text | — |\n| region format | A | EU 85×55 mm · US 3.5×2 in | EU |\n| sides | D | 1 · 2 | 2 |\n\n**Format:** chosen card size + 3 mm bleed; side 2 defaults to a brand/logo face.\n\n## custom\n\n| Parameter | Kind | Chips / values | Default |\n| ---------- | ---- | -------------------------------------------- | ------- |\n| dimensions | R | explicit width × height (+ unit; px, mm, in) | — |\n| medium | D | Print (adds 3 mm bleed, dpi 300) · Screen | Screen |\n\nUse `custom` whenever the request names explicit dimensions or none of the classes\nfit; everything else about the intake is unchanged.\n",
14
- "docs/formats/SKILL.md": "---\nname: formats\ndescription: >-\n Canvas-format reference for CoDesign designs — one file per screen/print format (ig-post,\n ig-square, ig-story, widescreen, linkedin-post, x-post, deck, poster, flyer, business-card):\n exact W×H, aspect, medium, platform safe zones, print bleed/dpi. Use when picking or setting up\n a canvas size, checking a platform safe zone, or preparing print dimensions. Re-composing an\n existing design into a format is the `resize` skill; new-design intake is the `create` skill.\n---\n\n# formats\n\nThe single source of truth for canvas formats. Each format has its own file — read the file(s)\nfor the format(s) you're targeting via `skill({ name: 'formats', path: '<id>.md' })`, or search\nacross all formats: `skill({ name: 'formats', query: 'safe zone' })`.\n\n## Pick a format\n\n| id | size | aspect | medium | safe zones / print | file |\n| --------------- | -------------------------------- | ------- | ------ | -------------------------------- | ------------------ |\n| `ig-post` | 1080 × 1350 px | 4:5 | screen | none (no chrome) | `ig-post.md` |\n| `ig-square` | 1080 × 1080 px | 1:1 | screen | none (no chrome) | `ig-square.md` |\n| `ig-story` | 1080 × 1920 px | 9:16 | screen | **top ~250 px · bottom ~320 px** | `ig-story.md` |\n| `widescreen` | 1920 × 1080 px | 16:9 | screen | none (no chrome) | `widescreen.md` |\n| `linkedin-post` | 1200 × 627 px | ≈1.91:1 | screen | none; feed may crop edges | `linkedin-post.md` |\n| `x-post` | 1600 × 900 px | 16:9 | screen | none; timeline may crop edges | `x-post.md` |\n| `deck` | 1920 × 1080 px per slide | 16:9 | screen | none | `deck.md` |\n| `poster` | A2 420 × 594 mm / 1080 × 1920 px | — | both | print: +3 mm bleed · ≥300 dpi | `poster.md` |\n| `flyer` | A5 · A4 · DL | — | print | +3 mm bleed · ≥300 dpi | `flyer.md` |\n| `business-card` | EU 85 × 55 mm · US 3.5 × 2 in | — | print | +3 mm bleed · ≥300 dpi | `business-card.md` |\n\n**Custom sizes:** an explicit `W×H` (+ unit: px, mm, in) is always valid. Screen medium uses the\ndimensions as-is; print medium adds **3 mm bleed** and targets **≥300 dpi**.\n\n## Shared rules\n\n- **Safe zones (screen):** platform chrome overlays the canvas edges on some placements — keep\n primary content inside the safe band. Per-format numbers live in each format's file;\n content-only formats (post, square, widescreen) have no chrome and use the full frame inside\n the margins.\n- **Print basics:** every print format adds **3 mm bleed** on all sides; target **≥300 dpi**;\n keep text out of the bleed and inside the **~8% safe margin** (handbook §6.1.10, §6.4); fonts\n embedded/outlined.\n- **Composition is relative to the canvas** — margins, spacing, and type bands all scale with the\n page (handbook §6.4, §6.1.2), so the design rules hold across every format here.\n- **Export at native dimensions** — the page's own pixel (or bleed-inclusive print) size.\n\n## Boundaries\n\n- **Re-composing an existing design into another format** — the `resize` skill (workflow,\n re-composition routine, no-goes). This skill only supplies the target's numbers.\n- **Choosing a format during new-design intake** — the `create` skill derives it from the brief;\n its class defaults mirror this skill's files.\n",
14
+ "docs/formats/SKILL.md": "---\nname: formats\ndescription: >-\n Canvas-format reference for CoDesign designs — one file per screen/print format (ig-post,\n ig-square, ig-story, widescreen, linkedin-post, x-post, youtube-thumbnail, deck, poster, flyer,\n business-card):\n exact W×H, aspect, medium, platform safe zones, print bleed/dpi. Use when picking or setting up\n a canvas size, checking a platform safe zone, or preparing print dimensions. Re-composing an\n existing design into a format is the `resize` skill; new-design intake is the `create` skill.\n---\n\n# formats\n\nThe single source of truth for canvas formats. Each format has its own file — read the file(s)\nfor the format(s) you're targeting via `skill({ name: 'formats', path: '<id>.md' })`, or search\nacross all formats: `skill({ name: 'formats', query: 'safe zone' })`.\n\n## Pick a format\n\n| id | size | aspect | medium | safe zones / print | file |\n| ------------------- | -------------------------------- | ------- | ------ | -------------------------------- | ---------------------- |\n| `ig-post` | 1080 × 1350 px | 4:5 | screen | none (no chrome) | `ig-post.md` |\n| `ig-square` | 1080 × 1080 px | 1:1 | screen | none (no chrome) | `ig-square.md` |\n| `ig-story` | 1080 × 1920 px | 9:16 | screen | **top ~250 px · bottom ~320 px** | `ig-story.md` |\n| `widescreen` | 1920 × 1080 px | 16:9 | screen | none (no chrome) | `widescreen.md` |\n| `linkedin-post` | 1200 × 627 px | ≈1.91:1 | screen | none; feed may crop edges | `linkedin-post.md` |\n| `x-post` | 1600 × 900 px | 16:9 | screen | none; timeline may crop edges | `x-post.md` |\n| `youtube-thumbnail` | 1280 × 720 px | 16:9 | screen | bottom-right duration badge | `youtube-thumbnail.md` |\n| `deck` | 1920 × 1080 px per slide | 16:9 | screen | none | `deck.md` |\n| `poster` | A2 420 × 594 mm / 1080 × 1920 px | — | both | print: +3 mm bleed · ≥300 dpi | `poster.md` |\n| `flyer` | A5 · A4 · DL | — | print | +3 mm bleed · ≥300 dpi | `flyer.md` |\n| `business-card` | EU 85 × 55 mm · US 3.5 × 2 in | — | print | +3 mm bleed · ≥300 dpi | `business-card.md` |\n\n**Custom sizes:** an explicit `W×H` (+ unit: px, mm, in) is always valid. Screen medium uses the\ndimensions as-is; print medium adds **3 mm bleed** and targets **≥300 dpi**.\n\n## Shared rules\n\n- **Safe zones (screen):** platform chrome overlays the canvas edges on some placements — keep\n primary content inside the safe band. Per-format numbers live in each format's file;\n content-only formats (post, square, widescreen) have no chrome and use the full frame inside\n the margins.\n- **Print basics:** every print format adds **3 mm bleed** on all sides; target **≥300 dpi**;\n keep text out of the bleed and inside the **~8% safe margin** (handbook §6.1.10, §6.4); fonts\n embedded/outlined.\n- **Composition is relative to the canvas** — margins, spacing, and type bands all scale with the\n page (handbook §6.4, §6.1.2), so the design rules hold across every format here.\n- **Export at native dimensions** — the page's own pixel (or bleed-inclusive print) size.\n\n## Boundaries\n\n- **Re-composing an existing design into another format** — the `resize` skill (workflow,\n re-composition routine, no-goes). This skill only supplies the target's numbers.\n- **Choosing a format during new-design intake** — the `create` skill derives it from the brief;\n its class defaults mirror this skill's files.\n",
15
15
  "docs/formats/business-card.md": "# business-card\n\n- **Print sizes:** EU 85 × 55 mm (default) · US 3.5 × 2 in (88.9 × 50.8 mm) — each +\n **3 mm bleed**\n- **Medium:** print\n- **Structure:** usually two-sided; side 2 defaults to a brand/logo face\n\n## Print rules\n\n- ≥300 dpi; fonts embedded/outlined; text inside the safe margin, out of the bleed (handbook\n §6.1.10).\n- The tiny canvas makes minimum sizes bite hard: body 9–12 pt where possible, fine print never\n below 5 pt (handbook §6.1.2).\n\n## Notes\n\n- The smallest format in the set — restraint wins: one or two type sizes, generous margins, no\n decoration competing with the name and contact details.\n",
16
16
  "docs/formats/deck.md": "# deck — presentation slides\n\n- **Size:** 1920 × 1080 px per slide\n- **Aspect:** 16:9\n- **Medium:** screen\n- **Structure:** one page per slide\n\n## Notes\n\n- Slide count derives from talk length: **≈ 1.5 × minutes, clamped to 5–30** (a ~10 min talk →\n ~15 slides). The `create` skill's deck intake asks for the length.\n- Every slide shares one grid, one type scale, and one palette — the design-rules composition\n and hierarchy sections (handbook §6.4, §6.3) apply per slide.\n- Projector/screen viewing: keep body text comfortably above the web minimums; slides read at a\n distance.\n",
17
17
  "docs/formats/flyer.md": "# flyer\n\n- **Print sizes:** A5 148 × 210 mm (default) · A4 210 × 297 mm · DL 99 × 210 mm — each +\n **3 mm bleed**\n- **Medium:** print\n- **Structure:** two-sided → one page per side\n\n## Print rules\n\n- ≥300 dpi; fonts embedded/outlined; text inside the **~8% safe margin**, out of the bleed\n (handbook §6.1.10, §6.4).\n- Print minimum type sizes apply: body 9–12 pt, caption ≥7 pt (handbook §6.1.2).\n\n## Notes\n\n- Hand-held reading distance — denser than a poster, but the hierarchy chain (handbook §6.3)\n still needs one dominant element per side.\n- DL is a tall narrow column: short measure, stacked composition.\n",
@@ -22,6 +22,7 @@ export const INLINE_ASSETS = {
22
22
  "docs/formats/poster.md": "# poster\n\n- **Print size:** A2 portrait, 420 × 594 mm + **3 mm bleed** (A1 594 × 841 mm / A3 297 × 420 mm\n on request)\n- **Screen size:** 1080 × 1920 px (9:16) unless stated\n- **Medium:** both — ask (or derive) print vs screen; print is the default venue\n\n## Print rules\n\n- ≥300 dpi; fonts embedded/outlined; text inside the **~8% safe margin**, out of the bleed\n (handbook §6.1.10, §6.4).\n- Work in mm/points against the trimmed size; the bleed is trim allowance, not layout space.\n\n## Notes\n\n- Viewed at a distance — hierarchy carries the poster: one dominant element, display sizes at the\n top of the scale bands (handbook §6.3, §6.1.2).\n- Screen venue behaves like `ig-story` **without** platform safe zones (no chrome).\n",
23
23
  "docs/formats/widescreen.md": "# widescreen — 16:9 landscape\n\n- **Size:** 1920 × 1080 px\n- **Aspect:** 16:9 (wide)\n- **Medium:** screen\n\n## Notes\n\n- Wide and short: re-flow content **horizontally**, shorten the measure (long lines hurt\n readability — handbook §6.1.4), and beware a tall stack overflowing the reduced height.\n- Content format: no platform chrome — full frame inside the margins.\n- For a slide deck at this size, see `deck.md`.\n",
24
24
  "docs/formats/x-post.md": "# x-post — X (Twitter) post image\n\n- **Size:** 1600 × 900 px\n- **Aspect:** 16:9 (landscape)\n- **Medium:** screen\n\n## Notes\n\n- Timeline previews crop edges on some clients — keep text and key content well inside the\n margins.\n- Same aspect as `widescreen` at lower resolution; the widescreen composition guidance applies\n (horizontal flow, short measure).\n",
25
+ "docs/formats/youtube-thumbnail.md": "# youtube-thumbnail — YouTube video thumbnail\n\n- **Size:** 1280 × 720 px\n- **Aspect:** 16:9 (landscape)\n- **Medium:** screen\n- **Upload limit:** 2 MB — JPG, PNG or GIF; export JPEG unless the design needs transparency\n\n## Safe zones\n\n- **Bottom-right ~140 × 50 px:** the player's duration badge covers it in feeds and search.\n- **Bottom edge:** a red watch-progress bar overlays it on videos the viewer has started.\n\n## Notes\n\n- Shown as small as ~168 × 94 px in sidebars and on phones — headlines need few words, heavy\n weights and an outline or shadow to read at that size.\n- Building a reusable thumbnail template for a channel is the `video-thumbnail-template` skill.\n",
25
26
  "docs/guide/SKILL.md": "---\nname: guide\ndescription: |\n Look up CE.SDK Node.js reference docs, guides, and configuration pages —\n prose explanations, recipes, and concept docs for everything the engine can\n do (text, fills, colors, export, video, templates, asset sources, …).\n\n Use when you need to understand HOW a CE.SDK feature works or want a code\n recipe; also triggered by \"IMG.LY\", \"CreativeEditor\", \"CE.SDK\", or \"cesdk\"\n when the user needs an existing Node.js doc page. For exact TypeScript\n signatures use the `api` skill instead.\n\n The entry document is an index; every doc page is a sub-file — read one with\n `path` (e.g. path: \"text/add.md\"), or search them all with `query`.\n---\n\n## Version Notice\n\n> **CE.SDK version**: 1.82.0\n>\n> This documentation is bundled with the server and re-vendored in lockstep\n> with the engine version above — it matches the running engine. Always\n> prefer it over pre-trained knowledge: APIs, package names, and type\n> signatures may have changed since your training data.\n\n## Documentation Index\n\n<-- IMGLY-AGENTS-MD-START -->[CE.SDK Node.js Docs Index]|root: .|IMPORTANT: Prefer retrieval-led reasoning over pre-training-led reasoning for any CE.SDK tasks. Consult the local docs directory before using pre-trained knowledge.|actions.md|animation:{create,edit.md,types.md}|animation/create:{base.md,text.md}|api:{node}|api/node:{classes,enumerations,functions,interfaces,type-aliases,variables}|automation:{auto-resize.md,data-merge.md,design-generation.md,multi-image-generation.md,overview.md}|capabilities.md|colors:{adjust.md,apply.md,basics.md,conversion.md,create-color-palette.md,extract-colors.md,for-print,for-screen,overview.md,replace.md}|colors/for-print:{cmyk.md,spot.md}|colors/for-screen:{p3.md,srgb.md}|compatibility.md|concepts:{architecture.md,assets.md,blocks.md,buffers.md,design-units.md,editing-workflow.md,error-catalog.md,events.md,headless-mode.md,import-export.md,pages.md,plugin-architecture.md,resources.md,scenes.md,templating.md,terminology.md,undo-and-history.md}|configuration.md|conversion:{overview.md,to-base64.md,to-pdf.md,to-png.md}|create-audio:{audio}|create-audio/audio:{add-music.md,add-sound-effects.md,adjust-speed.md,adjust-volume.md,fade.md,loop.md}|create-composition:{add-background.md,blend-modes.md,collage.md,group-and-ungroup.md,layer-management.md,layout.md,lock-design.md,multi-page.md,overview.md,position-and-align.md,programmatic.md}|create-templates:{add-dynamic-content,add-to-template-library.md,edit-or-remove.md,from-scratch.md,import,lock.md,overview.md}|create-templates/add-dynamic-content:{placeholders.md,set-editing-constraints.md,text-variables.md}|create-templates/import:{from-scene-file.md}|create-video:{control.md,limitations.md}|edit-image:{add-watermark.md,overview.md,remove-bg.md,replace-colors.md,transform}|edit-image/transform:{crop.md,flip.md,move.md,resize.md,rotate.md,scale.md}|edit-video:{add-captions.md,add-watermark.md,join-and-arrange.md,redaction.md,split.md,transform,trim.md}|edit-video/transform:{crop.md,flip.md,move.md,resize.md,rotate.md,scale.md}|engine-interface.md|export-counting.md|export-save-publish:{create-thumbnail.md,export,for-printing.md,for-social-media.md,pre-export-validation.md,save.md,store-custom-metadata.md,thumbnail-previews.md}|export-save-publish/export:{audio.md,compress.md,overview.md,partial-export.md,size-limits.md,to-jpeg.md,to-pdf.md,to-png.md,to-raw-data.md,to-webp.md,with-color-mask.md}|file-format-support.md|fills:{color.md,gradient.md,image.md,overview.md,video.md}|filters-and-effects:{apply.md,blur.md,chroma-key-green-screen.md,create-custom-filters.md,create-custom-lut-filter.md,distortion.md,duotone.md,overview.md,support.md}|get-started:{agent-skills.md,build-with-ai.md,bun.md,cesdk-plugin-coding-agents.md,deno.md,mcp-server.md,overview.md,vanilla.md,vanilla-aws-lambda.md,vanilla-clone-github-project.md}|guides.md|import-media:{asset-library,concepts.md,content-json-schema.md,default-assets.md,edit-or-remove-assets.md,file-format-support.md,from-remote-source,overview.md,retrieve-mimetype.md,size-limits.md,source-sets.md}|import-media/asset-library:{refresh-assets.md}|import-media/from-remote-source:{asset-versioning.md,remote-asset.md}|insert-media:{audio.md,images.md,overview.md,shapes-or-stickers.md,videos.md}|key-capabilities.md|key-concepts.md|licensing.md|llms-txt.md|open-the-editor:{blank-canvas.md,from-image.md,from-template.md,from-video.md,import-design,load-scene.md,overview.md,uri-resolver.md}|open-the-editor/import-design:{from-archive.md,from-indesign.md,from-photoshop.md}|outlines:{overview.md,shadows-and-glows.md,strokes.md}|overview.md|performance.md|plugins:{print-ready-pdf.md}|rules:{asset-handling.md,common-pitfalls.md,content-fill-mode.md,enforce-brand-guidelines.md,lock-content.md,moderate-content.md,overview.md,silent-init-errors.md,verify-properties-before-use.md}|security.md|serve-assets.md|settings.md|shapes.md|stickers.md|stickers-and-shapes:{combine.md,create-cutout.md,create-edit,insert-qr-code.md}|stickers-and-shapes/create-edit:{create-shapes.md,create-stickers.md,edit-shapes.md}|text:{add.md,adjust-spacing.md,auto-size.md,edit.md,effects.md,emojis.md,language-support.md,overview.md,styling.md,text-designs.md}|to-v1-19.md|to-v1-82-fonts.md|upgrade.md|use-templates:{apply-template.md,generate.md,overview.md,programmatic.md,replace-content.md}|what-is-cesdk.md|<-- IMGLY-AGENTS-MD-END -->\n\n## API Index\n\n<-- IMGLY-TYPES-MD-START -->\n[CE.SDK Web API Index]|root: .\n\nCreativeEngine:{asset,block,editor,event,scene,variable,actions,shortcuts,reactor,version,addPlugin,unstable_setVideoExportInactivityTimeout,unstable_setExportInactivityTimeout,addPostUpdateCallback,addPreUpdateCallback},... (+7)\nBlockAPI:{export,getDominantColors,exportWithColorMask,exportVideo,exportAudio,loadFromString,loadFromArchiveURL,loadFromURL,saveToString,saveToArchive,create,createFill,getAudioTrackCountFromVideo,createAudioFromVideo,createAudiosFromVideo},... (+404)\nAssetAPI:{registerApplyMiddleware,registerApplyToBlockMiddleware,addSource,addLocalSource,addLocalAssetSourceFromJSONString,addLocalAssetSourceFromJSONURI,removeSource,findAllSources,findAssets,fetchAsset,getGroups,getSupportedMimeTypes,getCredits,name,url},... (+14)\nSceneAPI:{load,loadFromString,loadFromURL,loadFromArchiveURL,saveToString,saveToArchive,create,createVideo,createFromImage,createFromVideo,get,applyTemplateFromString,applyTemplateFromURL,getMode,setMode},... (+25)\nEditorAPI:{unlockWithLicense,isCapabilitySupported,checkCapabilities,startTracking,setTrackingMetadata,getTrackingMetadata,trackEvent,getActiveLicense,getEngineVersion,onStateChanged,setEditMode,getEditMode,unstable_isInteractionHappening,hasSelectedVectorNode,addVectorNode},... (+99)\nEventAPI:{subscribe}\nVariableAPI:{findAll,setString,getString,remove}\nTypes:{AnimationType,AssetResult,BlendMode,Color,DesignBlockId,ExportOptions,PropertyType,Scope,TextCase,VideoExportOptions}\n<-- IMGLY-TYPES-MD-END -->\n\n# CE.SDK Node.js Documentation\n\nLook up documentation for IMG.LY CreativeEditor SDK (Node.js).\n\n## How to Use\n\n1. **Index lookup**: Match the query to a path in the Documentation Index above.\n The index uses compressed format: `dir:{file1.md,file2.md}` means files are\n at `dir/file1.md` and `dir/file2.md`.\n Read a page with the skill tool: `skill({ name: 'guide', path: '<path>.md' })`\n (e.g. for `text/add.md` → `skill({ name: 'guide', path: 'text/add.md' })`)\n2. **Search**: If no exact match exists, search across all bundled pages:\n `skill({ name: 'guide', query: '<keyword>' })` — `query` is a regular\n expression matched per line, case-insensitive unless it contains an\n uppercase letter. Results come back as\n `file:line: content`; follow up with `path` + `offset` to read around a\n hit. Combine `query` with `path` to search a single file.\n3. **Read and respond** with the relevant section and code examples\n4. **Check rules** if the topic involves setup, initialization, or common\n operations: the `rules/` pages (see the index), e.g.\n `skill({ name: 'guide', path: 'rules/common-pitfalls.md' })`\n\n## API Lookup\n\nFor TypeScript API queries (method signatures, types, parameters):\n\n1. **Authoritative signatures**: the `api` skill wraps the exact `.d.ts` the\n engine ships — `skill({ name: 'api', query: '<method>' })`.\n2. **Prose API docs**: this skill bundles a markdown explainer per module:\n `skill({ name: 'guide', path: 'api/<ModuleName>.md' })`\n (e.g. for BlockAPI → `skill({ name: 'guide', path: 'api/BlockAPI.md' })`)\n3. **For common types**: `skill({ name: 'guide', path: 'api/types.md' })`\n\n**Tip**: Verify types against the TypeScript definitions (the `api` skill) —\nCE.SDK evolves rapidly and type shapes may differ from pre-trained knowledge.\n\n## Quick Reference\n\n| Topic | Path |\n|-------|------|\n| Getting started | `get-started/` |\n| API methods | `engine-interface.md` |\n| Configuration | `configuration.md`, `settings.md` |\n| Import media | `import-media/` |\n| Export/save | `export-save-publish/` |\n| Templates | `use-templates/`, `create-templates/` |\n| Text operations | `text/` |\n| Image editing | `edit-image/` |\n| Video editing | `edit-video/`, `create-video/` |\n| Fills & colors | `fills/`, `colors/` |\n| Filters/effects | `filters-and-effects/` |\n| Known pitfalls | `rules/` |\n| API modules | `api/BlockAPI.md`, `api/SceneAPI.md`, etc. |\n\n## Additional Triggers\n\nThis skill also covers queries about CE.SDK block types, asset sources, and feature capabilities.\nIt handles API method lookups — BlockAPI, SceneAPI, EditorAPI, AssetAPI, method signatures,\nreturn types, and \"engine.block\" style queries.\n\n## Related Skills\n\n- The `api` skill for authoritative TypeScript signatures — this skill is\n prose docs; `api` wraps the exact `.d.ts` the engine runs.\n- The `handbook` skill for this server's design loop, tool contract, and\n design rules.\n",
26
27
  "docs/guide/actions.md": "> This is one page of the CE.SDK Node.js documentation. For a complete overview, see the [Node.js Documentation Index](https://img.ly/docs/cesdk/node.md). For all docs in one file, see [llms-full.txt](./llms-full.txt.md).\n\n**Navigation:** [Guides](./guides.md) > [Actions](./actions.md)\n\n---\n\nThe Actions API provides a centralized way to manage and customize actions when running CE.SDK headlessly on Node.js.\n\n> **Note:** The Actions API is available after engine initialization through\n> `engine.actions`. The `@cesdk/node` package exposes the same actions API as\n> the browser engine. There is no prebuilt editor in a headless environment, so\n> the host UI actions of the browser editor (`saveScene`, `exportDesign`,\n> `uploadFile`, and the rest) are not registered.\n\n```typescript file=@cesdk_web_examples/guides-actions-server-js/server-js.ts reference-only\nimport CreativeEngine from '@cesdk/node';\nimport { config } from 'dotenv';\nimport { mkdir, writeFile } from 'fs/promises';\n\nconfig();\n\nasync function main() {\n const engine = await CreativeEngine.init({\n baseURL: process.env.IMGLY_LOCAL_ASSETS_URL\n // license: process.env.CESDK_LICENSE,\n });\n\n try {\n // Create a scene with a page and a selected block so selection-based\n // actions have a target\n engine.scene.create('VerticalStack', {\n page: { size: { width: 800, height: 600 } }\n });\n const page = engine.block.findByType('page')[0];\n\n const block = engine.block.create('graphic');\n engine.block.setShape(block, engine.block.createShape('rect'));\n engine.block.setWidth(block, 200);\n engine.block.setHeight(block, 150);\n engine.block.setPositionX(block, 100);\n engine.block.setPositionY(block, 100);\n engine.block.appendChild(page, block);\n engine.block.setSelected(block, true);\n\n // Engine defaults act on the current selection\n const moved = await engine.actions.run('nudge', { dx: 10, dy: 0 });\n console.log('Selection moved:', moved); // true\n\n await engine.actions.run('selection.duplicate');\n console.log('Blocks on page:', engine.block.getChildren(page).length);\n\n // Explicitly targeted actions take their targets as arguments\n await engine.actions.run('select', { ids: [block] });\n console.log('Selected blocks:', engine.block.findAllSelected()); // [block]\n\n // Register a custom action under your own id\n engine.actions.register('myCompany.audit', (event: string) => {\n const selected = engine.block.findAllSelected();\n return { event, selectedBlocks: selected.length };\n });\n\n const audit = await engine.actions.run('myCompany.audit', 'export');\n console.log('Audit result:', audit); // { event: 'export', selectedBlocks: 1 }\n\n // Override an engine default: hide instead of destroy\n engine.actions.register('selection.delete', () => {\n const selected = engine.block.findAllSelected();\n selected.forEach((id) => engine.block.setVisible(id, false));\n return selected.length;\n });\n\n const hidden = await engine.actions.run('selection.delete');\n console.log('Hidden blocks:', hidden); // 1\n\n // Unregistering an overridden engine default restores the built-in behavior\n engine.actions.unregister('selection.delete');\n\n // Discover registered actions, optionally filtered by a glob matcher\n const all = engine.actions.list();\n console.log('Registered actions:', all.length);\n\n const selectionActions = engine.actions.list({ matcher: 'selection.*' });\n selectionActions.forEach(({ id, enabled }) => {\n console.log(`${id} (enabled: ${enabled})`);\n });\n\n // Test for an action by id\n console.log('Has nudge:', engine.actions.has('nudge')); // true\n\n // get() returns the raw function for actions you registered ...\n const auditFn = engine.actions.get('myCompany.audit');\n if (auditFn != null) {\n console.log('Sync call:', auditFn('archive'));\n }\n\n // ... and undefined for engine-default native actions - use run() for those\n console.log('Get nudge:', engine.actions.get('nudge')); // undefined\n\n // Remove a custom action entirely\n engine.actions.unregister('myCompany.audit');\n console.log('Has audit:', engine.actions.has('myCompany.audit')); // false\n\n // Export the scene as an image to verify the result\n await mkdir('output', { recursive: true });\n const blob = await engine.block.export(page, { mimeType: 'image/png' });\n await writeFile('output/actions.png', Buffer.from(await blob.arrayBuffer()));\n console.log('Exported preview: output/actions.png');\n\n } finally {\n engine.dispose();\n }\n}\n\nmain().catch(console.error);\n```\n\n## The Action Registry\n\nAn action is a named function stored under a string id, such as `history.undo`, `text.toggleBold`, or `zoom`. You run actions by id, override what a built-in id does, add your own, or list what is available.\n\nRegistries are isolated per engine: actions registered on one `CreativeEngine` instance are not visible to another.\n\n### Running an Action\n\nCall `run(id, ...args)` to execute an action. It returns a `Promise` that resolves to the action's result — most engine defaults resolve to `true` or `false` (changed or no-change), a few to `null`.\n\n```ts\n// Undo the last change.\nawait engine.actions.run('history.undo');\n\n// Nudge the current selection 10px to the right.\nawait engine.actions.run('nudge', { dx: 10, dy: 0 });\n\n// Set the fill color of the selected blocks.\nawait engine.actions.run('fill.color', { color: { r: 1, g: 0, b: 0, a: 1 } });\n```\n\nSelection verbs act on the current selection (`engine.block.findAllSelected()`); explicitly targeted actions take ids in their arguments. In a headless script there are no pointer events, so select blocks programmatically with `engine.block.setSelected()` before running a selection verb — without a selection it is a no-op and resolves to `false`.\n\n### Registering and Overriding\n\nCall `register(id, fn)` to add a new action or replace an existing one. Re-registering a default id overrides its behavior everywhere that id is used — including the engine's own dispatches.\n\n```ts\n// Add your own command.\nengine.actions.register('myCompany.report', async (payload) => {\n await sendAnalytics(payload);\n return { ok: true };\n});\n\n// Override a built-in: hide instead of destroy.\nengine.actions.register('selection.delete', () => {\n engine.block\n .findAllSelected()\n .forEach((id) => engine.block.setVisible(id, false));\n});\n```\n\nAn action you register runs directly in JavaScript, so arguments and results pass by reference — non-serializable values such as `Buffer`s or callbacks work. The engine's own dispatches and calls that reach an engine-default native action go through a JSON boundary, so values that cross it must be JSON-serializable.\n\n### Listing and Inspecting\n\nCall `list(options?)` to discover registered actions. It returns an array of `{ id, enabled, argSchema }` objects. Pass a `matcher` glob to filter by id.\n\n```ts\n// Every registered action.\nconst all = engine.actions.list();\n\n// Just the engine defaults under the `text.` namespace.\nconst textActions = engine.actions.list({ matcher: 'text.*' });\n// => [{ id: 'text.toggleBold', enabled: true, argSchema: '…' }, …]\n```\n\nUse `has(id)` to test for an action, `unregister(id)` to remove a custom action or revert an overridden built-in to its default, and `get(id)` to read back a function you registered so you can call it synchronously (it returns `undefined` for engine-default native actions; use `run` for those).\n\n```ts\nengine.actions.has('zoom'); // true\nconst report = engine.actions.get('myCompany.report'); // the raw fn\n```\n\n## API Methods\n\nThe Actions API provides four methods:\n\n- `register(actionId, handler)` - Register an action function for a specific event\n- `get(actionId)` - Retrieve a registered action function\n- `run(actionId, ...args)` - Execute a registered action with the provided arguments (throws if not registered)\n- `list(matcher)` - Lists registered action IDs, optionally filtered by wildcard pattern\n\n## Getting Started\n\nRegister actions after initializing the headless engine:\n\n```javascript\nimport CreativeEngine from '@cesdk/node';\n\nconst engine = await CreativeEngine.init({\n // license: 'YOUR_CESDK_LICENSE_KEY',\n});\n\ntry {\n // Register an action\n engine.actions.register('actionType', async (...args) => {\n // Your custom implementation\n return result;\n });\n\n // Execute a registered action\n await engine.actions.run('actionType', arg1, arg2);\n\n // Or retrieve an action to call it later\n const action = engine.actions.get('actionType');\n\n // List all registered actions\n const allActions = engine.actions.list();\n\n // List actions matching a pattern\n const textActions = engine.actions.list({ matcher: 'text.*' });\n} finally {\n // Dispose the engine when your script is done\n engine.dispose();\n}\n```\n\n## Engine-Default Actions\n\nThe engine seeds its registry with the editor's command vocabulary. These run wherever the engine runs, including headless mode via `@cesdk/node`. Run any of them by id or override them. The full set is discoverable at runtime with `list()`:\n\n### History\n\n- `history.undo` - Undo the last change\n- `history.redo` - Redo the last undone change\n\n### Lifecycle / selection\n\n- `selection.delete` - Delete the selected blocks\n- `selection.duplicate` - Duplicate the selected blocks\n- `selection.group` - Group the selected blocks\n- `selection.ungroup` - Ungroup the selected group\n- `group.enter` - Enter the selected group for editing\n- `group.exit` - Exit the current group\n- `select` - Select blocks by id (dispatched by the engine on click)\n- `selection.all` - Select all blocks on the current page\n- `select.byType` - Select all blocks of a given type\n- `selection.parentOrDeselect` - Select the parent group, or deselect if none\n- `rename` - Rename the selected block\n- `lock.toggle` - Toggle the lock state of the selected blocks\n\n### Transform / arrange\n\n- `nudge` - Move the selection by a pixel delta (`{ dx, dy }`)\n- `transform` - Apply position/size/rotation to blocks\n- `resize` - Resize the selection by a delta or scale\n- `scale` - Scale the selection around an anchor\n- `rotate` - Rotate the selection by degrees (absolute or relative)\n- `flip.horizontal` - Flip the selection horizontally\n- `flip.vertical` - Flip the selection vertically\n- `align.horizontal` - Align the selection horizontally (left/center/right)\n- `align.vertical` - Align the selection vertically (top/center/bottom)\n- `distribute.horizontal` - Distribute the selection horizontally\n- `distribute.vertical` - Distribute the selection vertically\n- `matchSize` - Match the size of the selection to a reference block\n- `bringToFront` - Bring the selection to the front\n- `bringForward` - Bring the selection forward one step\n- `sendBackward` - Send the selection backward one step\n- `sendToBack` - Send the selection to the back\n- `reorder.moveToIndex` - Move the selection to a specific layer index\n- `reparent` - Move the selection under a new parent\n\n### Appearance\n\n- `opacity` - Set the opacity of the selection\n- `visibility.toggle` - Toggle visibility of the selection\n- `blendMode` - Set the blend mode of the selection\n- `fill.color` - Set the solid fill color\n- `fill.toggle` - Toggle the fill on or off\n- `contentFillMode` - Set the content fill mode (crop/cover/contain)\n- `stroke.toggle` - Toggle the stroke on or off\n- `stroke.color` - Set the stroke color\n- `stroke.width` - Set the stroke width\n- `dropShadow.toggle` - Toggle the drop shadow on or off\n- `dropShadow.color` - Set the drop shadow color\n- `dropShadow.offset` - Set the drop shadow offset\n- `dropShadow.blur` - Set the drop shadow blur\n- `blur.toggle` - Toggle the block blur on or off\n- `blur.set` - Set the block blur type\n- `effect.append` - Append an effect to the selection\n- `effect.remove` - Remove an effect by index\n- `effect.clear` - Remove all effects from the selection\n\n### Crop / edit mode\n\n- `crop.enter` - Enter crop mode (dispatched by the engine on double-click)\n- `crop.reset` - Reset the crop to its default\n- `crop.fillFrame` - Fill the frame with the cropped content\n- `editmode.exit` - Exit the current edit mode\n- `text.edit` - Enter text editing (dispatched by the engine on double-click)\n\n### Text\n\n- `text.toggleBold` - Toggle bold on the selected text\n- `text.toggleItalic` - Toggle italic on the selected text\n- `text.fontSize` - Set the font size\n- `text.align` - Set the text alignment (left/center/right)\n- `text.case` - Set the text case (normal/upper/lower/title)\n- `text.color` - Set the text color\n- `text.lineHeight` - Set the line height\n- `text.letterSpacing` - Set the letter spacing\n- `text.list` - Set the list style (none/unordered/ordered)\n- `text.typeface` - Set the typeface\n\n### Pages\n\n- `page.add` - Add a new page\n- `page.selectNext` - Scroll to the next page\n- `page.selectPrevious` - Scroll to the previous page\n- `page.remove` - Remove a page\n- `page.duplicate` - Duplicate a page\n- `page.size` - Set a page's size\n- `page.background.toggle` - Toggle a page's background fill\n- `page.title.edit` - Edit a page's title (dispatched by the engine)\n\n### Scene / view\n\n- `scene.layout` - Set the scene layout (free/stacks)\n- `scene.size` - Set the scene size\n- `zoom` - Set the zoom level/factor around a pivot\n- `zoom.toBlock` - Zoom the camera to fit a block\n- `zoom.toPage` - Zoom the camera to fit a page\n- `zoom.autoFit.toggle` - Toggle auto-fit zoom on an axis\n- `pan` - Pan the viewport by a delta\n\n### Video / timeline / playback\n\n- `selection.split` - Split the selected clip at the playhead\n- `video.playPause` - Toggle playback of the current page\n- `playback.seek` - Seek playback to a time\n- `duration` - Set the duration of the selection\n- `trim.offset` - Set the trim offset of the selection\n- `trim.length` - Set the trim length of the selection\n- `playbackSpeed` - Set the playback speed of the selection\n- `volume` - Set the volume of the selection\n- `mute.toggle` - Toggle mute on the selection\n- `loop.toggle` - Toggle looping on the selection\n- `timeOffset` - Set the time offset of the selection\n- `animation.in` / `animation.in.remove` - Set or remove the in animation\n- `animation.out` / `animation.out.remove` - Set or remove the out animation\n- `animation.loop` / `animation.loop.remove` - Set or remove the loop animation\n\n### Canvas interaction primitives\n\nThese exist for editor hosts that forward pointer input; they are rarely useful in a headless script.\n\n- `drag.begin` - Begin a drag gesture on blocks\n- `drag.end` - End a drag gesture on blocks\n- `secondaryAction` - The secondary (context) action at a position\n\n## Overriding Engine Defaults Safely\n\nSome actions are dispatched by the engine itself in response to user input — for example `select` (on click), `crop.enter` and `text.edit` (on double-click), and `page.title.edit`. The engine reads the result of these within the same update, so an override of one of them must apply its effect **synchronously**.\n\n```ts\n// Synchronous override — runs in the same tick, so the engine sees the result.\nengine.actions.register('select', ({ ids }) => {\n applyMySelection(ids);\n});\n```\n\n> **Caution:** When you override a self-dispatched action (`select`, `crop.enter`,\n> `text.edit`, `page.title.edit`), apply the effect synchronously. An override\n> that defers its work (for example by `await`-ing before applying it) lands a\n> microtask too late for the engine's same-tick read, so the result is ignored\n> for that input. Overriding actions you trigger yourself with `run` has no such\n> constraint.\n\n## Registering Custom Actions with Custom IDs\n\nBeyond the predefined action types, you can register actions with custom IDs for your own application-specific needs:\n\n```javascript\n// Register a custom action\nengine.actions.register('myCustomAction', async data => {\n console.log('Custom action triggered with:', data);\n return { success: true, processedData: data };\n});\n\n// Execute the custom action using run\nconst result = await engine.actions.run('myCustomAction', { someData: 'value' });\n\n// Or retrieve it for conditional execution\nconst customAction = engine.actions.get('myCustomAction');\nif (customAction) {\n const result = await customAction({ someData: 'value' });\n}\n```\n\n## Discovering Registered Actions\n\nUse `list()` to get all registered action IDs or find actions matching a pattern:\n\n```javascript\n// Get all registered action IDs\nconst registeredActions = engine.actions.list();\nconsole.log('Available actions:', registeredActions);\n\n// Find actions matching a pattern\nconst selectionActions = engine.actions.list({ matcher: 'selection.*' });\nconsole.log('Selection actions:', selectionActions);\n```\n\n## Differences from the Browser\n\nThe actions API itself is identical across `@cesdk/node`, `@cesdk/engine`, and the prebuilt editor's `cesdk.actions` — they all write to the same kind of registry. What differs on Node.js is which ids are pre-registered:\n\n- **Host UI actions are absent.** The prebuilt browser editor registers convenience actions such as `saveScene`, `shareScene`, `importScene`, `exportScene`, `exportDesign`, `uploadFile`, `asset.delete`, `scene.create`, and the `video.*.checkSupport` and `timeline.*` actions. On Node.js these ids do not exist — use the engine APIs directly (for example `engine.scene.saveToString()` or `engine.block.export()`), or register your own implementations under the same ids.\n- **Editor keyboard adapters are absent.** Ids the browser editor layers on top of the engine defaults — `copy`, `cut`, `paste`, `zoom.toFit`, `group.enterOrExit`, `selection.nudgeUp`/`Down`/`Left`/`Right` (and their `…Extended` variants), `text.toggleUnderline`, `text.toggleStrikethrough`, `vectorPath.deleteNodeOrPoint` - are not registered. The underlying engine defaults (`nudge`, `group.enter`, `group.exit`, `zoom.toBlock`, and so on) cover the same operations.\n- **No dialogs or downloads.** Engine defaults never open UI, so everything in the registry runs headlessly. Anything you register yourself should also avoid DOM APIs.\n\nPrefer `list()` over hardcoding assumptions when your code needs to know whether an id is available in the current environment.\n\n\n\n---\n\n## More Resources\n\n- **[Node.js Documentation Index](https://img.ly/docs/cesdk/node.md)** - Browse all Node.js documentation\n- **[Complete Documentation](./llms-full.txt.md)** - Full documentation in one file (for LLMs)\n- **[Web Documentation](./node.md)** - Interactive documentation with examples\n- **[Support](mailto:support@img.ly)** - Contact IMG.LY support",
27
28
  "docs/guide/animation.md": "> This is one page of the CE.SDK Node.js documentation. For a complete overview, see the [Node.js Documentation Index](https://img.ly/docs/cesdk/node.md). For all docs in one file, see [llms-full.txt](./llms-full.txt.md).\n\n**Navigation:** [Guides](./guides.md) > [Animation](./animation.md)\n\n---\n\n\n\n\n---\n\n## Related Pages\n\n- [Supported Animation Types](./animation/types.md) - Apply different animation types to design blocks in CE.SDK and configure their properties in server-side applications.\n- [Create Animations](./animation/create.md) - Create entrance, exit, loop, and text animations with CE.SDK preset animation APIs.\n- [Edit Animations](./animation/edit.md) - Modify existing animations in CE.SDK by reading properties, changing duration and easing, adjusting direction, and replacing or removing animations from blocks.\n\n\n---\n\n## More Resources\n\n- **[Node.js Documentation Index](https://img.ly/docs/cesdk/node.md)** - Browse all Node.js documentation\n- **[Complete Documentation](./llms-full.txt.md)** - Full documentation in one file (for LLMs)\n- **[Web Documentation](./node.md)** - Interactive documentation with examples\n- **[Support](mailto:support@img.ly)** - Contact IMG.LY support",
@@ -613,6 +614,7 @@ export const INLINE_ASSETS = {
613
614
  "docs/onboard/SKILL.md": "---\nname: onboard\ndescription: >-\n Use when the user is new here or asks what this server is / what it\n can do (\"what is CoDesign\", \"what can you make\", \"show me\"). A guided, plain-language\n introduction for a HUMAN: explain what CoDesign is in everyday terms, put the six\n jobs it does on the table, let the user pick one, then run it either on the bundled\n demo design or on the user's own idea — closing with the optional (never required)\n IMG.LY sign-in and the ways to earn more credits. Not a design manual: the handbook\n remains the required read before any edit.\n---\n\n# onboard — introduce CoDesign, then do one real job\n\nYou are talking to a person who may have never used this before. The goal is that they\nleave with **one finished thing** and an accurate idea of what to come back for.\n\n**Bundled files:** `demo-brief.md` — the canned demo brief, read only when the user\npicks the demo for the `generate` job. The choice-copy rules every question below\nfollows live in `create/choice-copy.md`.\n\n**Narration mode — this flow only.** Be conversational and a little verbose. Announce\neach step before you take it, and after every `preview` say in one or two sentences\nwhat it just proved (\"I changed only the headline — nothing else re-rolled; that's\nwhat makes this a design file and not a picture\"). Normal design work outside this\nflow keeps the usual concise style.\n\n**Honesty rules, throughout.** Claim only what this server does. Never guess a version\nnumber — call `diagnostics` if asked. Never disparage image or video generators;\nCoDesign works with them. Do not promise video.\n\n**Credits: never quote an amount, and never promise a grant.** Say \"credits\" and\n\"more credits\" — never a number, a total, or a per-design estimate, not for the\nsign-in and not for any rung of the ladder in Beat 5. The amounts live on\n<https://img.ly/codesign> and in the Discord, and change without this server knowing.\nNor can this server verify a Discord join or hand out a credit: the ladder is what\nIMG.LY offers, linked, not something you are granting.\n\n**Terms: link, never paraphrase.** The account, the AI gateway and the credits — how\nthey are earned, and that they expire — are governed by\n<https://img.ly/tos/codesign>. Point at it when the account comes up (Beat 4) and list\nit in the close (Beat 5). Do not summarise a clause, restate a right or a restriction,\nor answer a specific terms question from memory: the page is the authority and it\nchanges without this server knowing. The same discipline as amounts — link, don't\nrecite.\n\n---\n\n## Beat 1 — what this is, in plain words\n\nNo jargon. Do not say \"CE.SDK\", \"engine\", \"revision DAG\", \"blocks\" or \"scene\". Cover,\nin your own words, about a paragraph:\n\n- **I make a real design document, not a picture of one.** Text stays text, shapes\n stay shapes, layers stay layers.\n- **So one word can change without re-rolling everything.** Ask for a different\n headline and only the headline moves.\n- **You can take over at any point.** Every design opens in a browser editor — move\n things yourself, save, and I carry on from your version.\n- **It ends in something usable:** print-quality PDF, or images in whatever size you\n need.\n\n## Beat 2 — the six jobs\n\nPut them on the table, one line each, in plain language. Then ask **one** question —\n_**What would you like to do first?**_ Use your environment's structured question tool\nif it has one (options become chips); otherwise a compact markdown list.\n\nOffer the titles and subtext **verbatim**: they follow the choice-copy rules in\n`create/choice-copy.md`, which govern every list of choices this server puts to a\nuser. Do not paraphrase them into your own voice.\n\n**Verbatim binds the content, not the English.** If the user writes in another\nlanguage, these cards are written in theirs (handbook §1) — keep each title's\n`Label: Action phrase` shape, keep them verb-led and pronoun-free, and keep the\nsubtext saying the same thing. What you must not do is drift the _meaning_ or\nre-voice them. The job ids in the first column are values, never translated.\n\n| Job | Card title | Subtext | Then run |\n| ---------- | ------------------------------------------------------ | --------------------------------------------------------------------------------------- | -------------------------------- |\n| `generate` | **Create: Make something** | Builds a deck, poster, flyer, social post, business card, or any custom size. | `create` |\n| `import` | **Import: Convert an existing file** | Turns Photoshop, InDesign, PowerPoint, PDF, or images back into something you can edit. | `import`, then the handbook Loop |\n| `resize` | **Resize: Ship one design in different sizes** | Re-composes the layout per format and platform — not just scaled. | `resize` |\n| `rebrand` | **Rebrand: Enforce your brand design rules** | Applies your colors, fonts, and logo across the whole design. | `brand` |\n| `localize` | **Localize: Translate a design to multiple languages** | Keeps the layout intact, right-to-left included. | `localize` |\n| `judge` | **Judge: Give feedback to an existing design** | Scores a design against a rubric, then fixes what's weak. | `judge` |\n\nAlways add the closing line: **however you start, you can finish with a print-ready\nPDF or a link that hands the design to a colleague.** That is not a seventh option —\nit is true of all six.\n\nIf the user answers with something not on this list, take them at their word and go to\nthe closest skill; don't force the menu.\n\n## Beat 3 — the demo, or their own idea?\n\nAsk **one** question — _**How would you like to start?**_ — with these two cards,\nverbatim (in the user's language, on the same terms as Beat 2):\n\n| Card title | Subtext |\n| ----------------------- | -------------------------------------------------------------------------------- |\n| **Use the demo design** | Runs the job you picked on a finished example, so the result appears in seconds. |\n| **Describe an idea** | Runs the job on your own brief instead of the example. |\n\nIf they already described their own idea, skip the question and run it.\n\n**Their own idea** → hand straight to the skill from Beat 2, carrying everything they\nsaid. The `create` skill runs its own intake — do not pre-empt it with questions of\nyour own. Note that the three questions above are about **navigation**, not about the\ndesign: `create` still has to ask its own, and a user who has answered three questions\nalready has not answered any of those.\n\n**The demo** — two paths, by job:\n\n- Job `generate` → **build it in front of them.** Read `demo-brief.md` and run it\n through the normal handbook Loop. Narrate as you go, and `preview` your work.\n\n **Check for `asset_generate` before you start.** This build's hero visual is\n generated, so it needs a free IMG.LY sign-in. If the tool isn't in your list,\n **propose the sign-in first** — one line on why (the demo generates its own artwork),\n then `login`. Never start building and hit the wall halfway. If they'd rather not\n sign in, don't build a degraded version and don't leave an empty slot: load the\n finished design instead (below), say plainly that only the _build_ step needs an\n account, and carry on. Beat 4 then has nothing left to ask — skip it.\n\n- Jobs `import`, `resize`, `rebrand`, `localize`, `judge` → **rebuild nothing.**\n Load the finished demo in one call:\n\n ```\n import({ source: { demo: \"cybernews\" } })\n ```\n\n Then do the job they picked on it and show the result against the original: resize\n it to a story format, restyle it to a brand, localize it, or run the `judge` gate\n over it. That contrast is the point — a variation appears in seconds and nothing was\n regenerated from scratch.\n\nWhichever path ran, finish with `view` and give them the editor link, plus an `export`\nwhen a file makes sense for that job. Say explicitly that the link is live and\neditable, and that whatever they change there comes back to you.\n\n## Beat 4 — the optional account (ask once)\n\nDo this **after** the job, once, and never raise it again in the session.\n\nFirst check what is actually available:\n\n- If `asset_generate` is already in your tool list, say so in one line — image\n generation is on, nothing to do — and skip the rest of this beat.\n- If there is no `login` tool, mention the account only as a pointer\n (<https://img.ly/signup>) and ask nothing.\n\nOtherwise, say this honestly:\n\n> Everything you just saw ran with **no account and no API key** — that's the normal\n> way to use this. If you want more, a free IMG.LY sign-in adds the **AI Gateway**: I\n> can generate images from a prompt and drop them into the design as a real layer —\n> the floating glass cubes in the demo design were made exactly that way. Signing in\n> comes with credits to spend on it, and it covers commercial\n> licensing when you get there.\n\nThen ask **one** question — _**How would you like to continue?**_ — with these two\ncards, verbatim (in the user's language, on the same terms as Beat 2):\n\n| Card title | Subtext |\n| ------------------------------- | ---------------------------------------------------------------------------- |\n| **Sign in to IMG.LY** | Free. Adds image generation, and starts you off with credits to spend on it. |\n| **Continue without an account** | Everything except image generation keeps working exactly as it does now. |\n\nOn the first, call `login` — it opens their browser and returns immediately; call\n`login` again to confirm once they're back. On the second, drop it cheerfully:\neverything except image generation works exactly the same.\n\n**Point at the terms once, when the account comes up — don't paraphrase them.** One\nline alongside the question: the account, the gateway and the credits are covered by\nthe CoDesign terms of service, <https://img.ly/tos/codesign>. Link it and move on. You\nare not the authority on what the terms say: never summarise a clause, never state a\nright or a restriction they don't ask about, and if they do ask something specific,\nsend them to the page rather than answering from the description above.\n\n## Beat 5 — close\n\nTwo short blocks, no wall of text.\n\n**First, where they stand.** Print the ladder as a checklist, ticking only what\nactually happened:\n\n```\nWhere you stand:\n ✓ Install CoDesign free, and stays free\n ✓ Sign in to IMG.LY credits added\n □ Join the Discord → more credits\n □ Post a design in #showcase → more credits\n □ Get a design featured → the biggest top-up; we pick one every Friday\n\n Discord https://discord.gg/tCRKuykXQN\n More https://img.ly/codesign\n Terms https://img.ly/tos/codesign\n```\n\nRow 1 is always ticked — installing is free and stays free. Row 2 is ticked **only if\n`login` actually succeeded in this session**; if they declined, or there was no `login`\ntool to call, leave it unticked and point at <https://img.ly/signup>. Rows 3–5 are\nnever ticked: you have no way to know, and guessing would breach the honesty rules.\n\nThe code fence is a layout, not a string: write the rows in the user's language\n(handbook §1). The three URLs and `#showcase` are values and stay exactly as they are.\n\n**Then, three short lines:**\n\n1. **Three starter prompts they can copy**, tailored to the job they picked (e.g.\n _\"resize this to a LinkedIn banner and an A4 poster\"_, _\"restyle it in our brand\n colors #0b0b0b and #ff4d2e\"_, _\"make a German and an Arabic edition\"_).\n2. `skill({ name: 'onboard' })` brings this back any time; `/codesign <brief>` starts a new design directly.\n3. One line, only if they seem to be building software: this runs on IMG.LY's CE.SDK —\n the same design layer is available as an SDK.\n\n**Last line of the whole flow**, as an afterthought and not a lead:\n\n> Credits expire three months after they land — no rush, but they don't wait forever.\n",
614
615
  "docs/onboard/demo-brief.md": "# The demo brief\n\nThe canned brief behind the `generate` job's demo — build it through the normal\nhandbook Loop, exactly as if the user had asked for it.\n\n**This build needs image generation, so it needs an account.** The hero visual is\ngenerated with `asset_generate`, which requires a free IMG.LY sign-in. Do **not**\nstart building and discover this halfway: check for `asset_generate` in your tool\nlist **before** you begin, and if it isn't there, propose the free sign-in first (call\n`login`, then continue once they're back). If they'd rather not sign in, don't build a\ndegraded version and don't leave an empty slot — load the finished design instead\n(`import({ source: { demo: \"cybernews\" } })`), say plainly that the build step needs an\naccount for the image but the design itself doesn't, and carry on with the flow. Beat 4\nthen has nothing left to ask.\n\nThe same design ships finished as `import({ source: { demo: \"cybernews\" } })` — use that\nfor the import / resize / rebrand / localize / judge demos instead of rebuilding it.\n\n---\n\n## Secure Grid — cybersecurity growth report, Instagram post\n\nA data-editorial social post in a modern brutalist register: one massive statistic\nanchoring a dark, grainy canvas, with floating translucent cubes overlapping the\nheadline for depth.\n\n**Format** — 1080 × 1350 px, single page. Instagram portrait (this is a **4:5** ratio —\nnot 4:3, whatever the source brief called it). Strict **80 px** safe-zone margin on all\nsides.\n\n**Palette** — Deep Charcoal `#121212` (background) · Acid Lime `#D1FF00` (the single\naccent) · Optical White `#FFFFFF` (ink). Acid Lime is the **only** vibrant colour on\nthe canvas and appears on exactly two things: the `24%` metric and the `$140 Billion`\namount. Anything else in lime breaks the anchor.\n\n**Typography** — Inter only, weights 400/500/700/800. All sizes are absolute pixels.\nNo emoji anywhere; use Unicode glyphs (`›`, `█`) where a symbol is wanted.\n\n**Layout, back to front**\n\n1. **Background** — solid `#121212` with heavy digital noise / analog film grain. Matte,\n non-reflective. The grain is the surface, not a texture image pasted on top.\n2. **Hero visual** — three to four abstract 3D translucent glass cubes with glowing\n neon-green internal cores, at varied angles, floating asymmetrically from the\n top-left toward the centre-right. Glassmorphism: real refraction, soft glowing\n edges. The cubes fill the frame and **partially overlap the headline** — that\n overlap is what creates depth, so don't tuck them into a corner and don't crop them.\n Generate this with `asset_generate` (see the account note above).\n3. **Top-left brand** — `█ GRID`, 32 px.\n4. **Top-right brand** — `CYBERARCHIVE®`, 28 px, white.\n5. **Context label (mid-left)** — a pill-shaped **outline** chip reading `FinTech Sector ›`.\n Build it as a text block with a native text background (§6.5.1) — never a rectangle\n behind floating text.\n6. **Headline** — `Cloud Vulnerability / Projected to Drop by`, 80 px, bold, white.\n7. **Billboard metric** — `24%` at **240 px**, bold, Acid Lime. This is the visual\n anchor; everything else defers to it.\n8. **Financial sub-text** — `Saving Companies Over $140 Billion Annually` at 38 px, with\n `$140 Billion` in Acid Lime inside a Unicode oval outline.\n9. **Bottom-left metadata** — `INNOVATIVE / SECURITY LABS`, 22 px, stacked on two lines.\n10. **Bottom-right date** — `© 2026`, 22 px.\n\n**Deliverables** — a `preview` of the page at 1080 × 1350, and, if the user wants a\nfile, an export. Run the `judge` gate before calling it done, as with any design.\n",
615
616
  "docs/resize/SKILL.md": "---\nname: resize\ndescription: >-\n Use when resizing or reformatting a CoDesign / CE.SDK design to a new size or aspect ratio\n (ig-post ↔ ig-square ↔ ig-story ↔ widescreen, or a custom W×H); when building the size/format\n editions of a design from an existing design or from a brief; or when a reformatted\n edition has a dead void / letterboxed empty band, drifted or broken margins, off-canvas or\n clipped elements, mis-scaled decoration, or a stretched / squished layout.\n---\n\n# resize\n\nPoint this skill at the current design and one or more target formats, and it produces or repairs the size/format editions by **re-composing** each for its new aspect. It operates on the **latest revision of the current design** (re-ground via `list()` / `history()` if unsure — handbook §1); each produced format is a new revision (a non-destructive fork of the source). Single home for the resize workflow, the re-composition technique, and the reformat **no-goes**.\n\nBuilds on two things it does _not_ restate: handbook §6.3 Hierarchy + §6.4 Composition & grid (margins as % of the shortest dimension, the spacing ladder, balanced whitespace / no dead void, nothing off-canvas) and §6.1.2 type-scale bands; and the handbook's engine mechanics (the `text.font` declaration, auto-reshape, `loadResources`). For the _same-aspect, translate-the-strings_ case, use the `localize` skill instead.\n\n**Scoring:** reformatting is judged with the `judge` skill — every applicable axis ≥ 8. A dead void, a stretched element, a broken margin, or anything off-canvas fails **composition**.\n\n## Reference files (load on demand)\n\nThis `SKILL.md` holds the always-needed spine: inputs, the localize-vs-resize distinction, core\nprinciple, workflow, the re-composition routine, the no-goes, and the **resize composition\nchecklist** (end of this file). Per-format data lives in the **`formats` skill** — exact W×H,\naspect, and platform safe zones, one file per format: `skill({ name: 'formats' })` for the index,\n`skill({ name: 'formats', path: '<id>.md' })` for the format(s) you're building.\n\n## Localize vs resize — snap vs re-compose\n\n**Format is the outer distinction: a different format is a different layout; a different language is the same layout with different strings.**\n\n- **localize** — _same_ canvas: change words + the script's font, then **snap every block\n back to the reference geometry**. Any geometry deviation is drift.\n- **resize** — _new_ canvas: the geometry **must** change. You **re-derive** the layout\n from the design system on the new canvas. Here \"drift\" is not \"moved from the reference\" — it's\n re-composition that **loses the design's identity** or leaves the canvas **unbalanced**.\n\nNever confuse the two: don't snap a resize (that reproduces the source's proportions on the wrong\ncanvas → a dead void), and don't re-compose a localization (that's how localized sets drift).\n\n## Inputs — what to resize, and into what\n\nTwo inputs, resolved before any work by the intake contract — derive, ask once, echo, invent\nnothing: `skill({ name: 'handbook', path: 'intake.md' })`. **R** = required, no default; **A** =\nask if underived; **D** = defaultable.\n\n| Parameter | Kind | Chips / values | Default |\n| -------------- | ------- | ---------------------------------------------------------------------------------------------------- | ------------------------------------- |\n| target formats | **R** | `Instagram post` · `Instagram story` · `LinkedIn` · `Widescreen` · `A4 print` · `Custom W×H` · Other | none |\n| source design | derived | — | latest revision of the current design |\n\n**1. The source** — the latest revision of the current design. Every target format is re-composed _from_ that source — **never resize a resize** (don't chain: fork each format from the native source revision). If the user asks to resize an earlier design, find it via `list()` and its latest revision via `history()` first. If the request is a brief (no design exists yet), **build the native base first** at the brief's canvas per handbook §6, preview and sanity-check it, then treat that revision as the source.\n\n**2. The formats** — named formats (`ig-post`, `ig-square`, `ig-story`, `widescreen`, and the\nrest of the `formats` skill's index) or a custom `W×H`. Resolve each named format's exact size,\naspect, and safe zones from the **`formats` skill** (`skill({ name: 'formats', path: '<id>.md' })`).\n\nIf the format set is not stated, **ask** — _**Which sizes do you need?**_ — with the chips above;\na format set has no sensible default, so never pick one for the user. Then echo the resolved set\n(\"1 design → 3 formats\") before building.\n\n## Core principle — re-compose for the aspect, preserve the system\n\nA new format is a **new layout**. Re-lay it out for the new canvas while holding the **design\nsystem** constant:\n\n- **Invariant (carry to every format):** palette + the single accent; type-scale _bands_ (headline\n / body / caption as % of page height, §6.1.2); margin ratio (§6.4: 8–12% of the shortest side);\n spacing scale (§6.4 ladder); hierarchy order (§6.3); the **group hierarchy** (carry the source's\n grouping — re-group each cluster after re-composition, don't ship a flattened tree); the **region\n of interest** of every image/video fill (the subject stays framed — see the ROI section below);\n each decoration's _role_ (a ghost quote stays a ghost quote); the **element inventory** (every\n content cluster on the source has a counterpart on the new canvas — see below); and the\n **appearance** of every block (fill/gradient, effect/blur stack, opacity, corner radius, stroke,\n shadow, and font weight — carried by forking, not re-created by eye).\n- **Element inventory — nothing silently dropped.** A reformat re-composes; a tight format tempts\n you to drop elements, and the result reads \"way too simplistic / missing elements from the\n original.\" Before finishing, **count**: every text cluster, image, chip, icon, and decoration on\n the source must have a counterpart on the new canvas. An element may be **omitted only\n deliberately** (it genuinely can't earn its place on a smaller canvas) — never by oversight, and\n the omission is a judgment you can name. Re-composing from an _impression_ of the source instead\n of its actual block list is how elements vanish.\n- **Variable (re-derive per canvas):** absolute positions, absolute sizes, wrap width, line count,\n and how the content group is distributed vertically.\n\nNever **stretch or squish** the whole design to force-fit (that distorts type and shapes — resize\nby re-flow, not by scaling the canvas contents). Never leave a **lopsided void** (§6.4).\n\n## Workflow\n\n1. **Read the source's design system** (palette, accent, type bands, margin %, spacing rhythm,\n hierarchy) and **capture its layout as fractions** of the source canvas (positions/sizes as % of\n source W/H — see routine).\n2. **Fork the source** per target format; **capture the region of interest** of every image/video\n fill first (ROI section below); **set the page** to the new W×H and resize the full-bleed\n background to cover it; then **restore each fill's ROI** so the same region of the image stays\n framed — never let a `Cover` fill silently re-center the subject on the new aspect.\n3. **Re-derive from the new canvas:** margins = 8–12% of the new **shortest** dimension; type sizes\n from the new page **height** within their bands (§6.1.2); gaps from the spacing scale (§6.4).\n4. **Proportional re-flow to fill, then balance.** Place blocks by their captured fractions × the\n new canvas (this fills a taller/wider canvas instead of letterboxing it), then **center or\n distribute the content group within the platform safe band** — no top-anchored void. (Safe-zone\n bounds and per-format guidance: the `formats` skill, `<id>.md`.)\n5. **Scale decoration proportionally** to the canvas (a ghost glyph sized as % of page height grows\n on a taller format, shrinks on a shorter one) — keep its role and framing.\n6. **Re-wrap & re-fit:** text re-wraps at the new column width; check **overflow / clipping /\n off-canvas**; adjust size _within its band_ only.\n7. **Let the size/font write carry the rich text.** A `text.fontSize` / `text.font` /\n `text.lineHeight` write reshapes on its own and **preserves every per-character style range** —\n color, weight, style, case, decoration (handbook §6.1.3). A resize keeps the **same string**, so\n there is nothing to capture and replay. What still flattens the accent is a **whole-block**\n styling write: `getProps(id, ['text.color']).text.color` reads run 0 only, and writing it back as\n `text: { color }` collapses the whole block to run 0's formatting. Don't re-apply whole-block\n styling in a resize; if you must, pass the runs from `getProps(id, ['text.ranges'])` back as\n `text: { ranges: [...] }` in the same `setProps` — **every** run, not just the accent.\n8. **`loadResources` → `preview` each format** (dead void / crop / tofu only show in the\n render). Each format lands as its own revision — give each a `note` naming the format (e.g.\n \"ig-story 1080×1920\").\n\n## Proportional re-composition routine\n\nThe resize analog of localize's snap-to-reference — but a **first pass, not the finish**. It fills\nthe new canvas proportionally (killing the letterbox void); you then clamp margins/bands/safe-zones\nand **judge the balance in `preview`**. Resize is judgment-driven; there is no single deterministic\nanswer.\n\n**Capture the source as fractions** (run on the source page):\n\n```js\nconst page = engine.scene.getPages()[0];\nconst pg = await engine.design.getProps(page, ['width', 'height']);\nconst PW = pg.width,\n PH = pg.height;\nconst out = [];\nasync function walk(id) {\n const p = await engine.design.getProps(id, [\n 'type',\n 'width',\n 'height',\n 'position'\n ]);\n const r = {\n type: p.type, // the SHORT name: 'text', 'graphic', 'page'\n fx: p.position.x / PW,\n fy: p.position.y / PH,\n fw: p.width / PW,\n fh: p.height / PH\n };\n if (p.type === 'text') {\n const t = (await engine.design.getProps(id, ['text'])).text;\n r.s = t.string;\n // the size read carries its unit ('32px', always pixels) — parse for math\n r.ffs = parseFloat(t.fontSize) / PH; // fraction of page height\n r.lh = t.lineHeight;\n r.ls = t.letterSpacing;\n r.align = t.horizontalAlignment;\n }\n if (id !== page) out.push(r);\n for (const c of await engine.design.getChildren(id)) await walk(c);\n}\nawait walk(page);\nreturn { type: 'text', text: JSON.stringify({ PW, PH, blocks: out }, null, 1) };\n```\n\n**Apply to a forked source at the new size** (first pass, then clamp):\n\n```js\nconst NW = 1080,\n NH = 1920; // target format\nconst SAFE_TOP = 250,\n SAFE_BOT = 320; // ig-story/reel UI chrome (authoritative per-format values: formats skill, <id>.md); 0 for post/square/widescreen\nconst page = engine.scene.getPages()[0];\nawait engine.design.setProps(page, { width: NW, height: NH });\n\n// full-bleed background → cover the new page (don't reposition it proportionally)\nlet bg;\nfor (const id of await engine.design.getChildren(page)) {\n const p = await engine.design.getProps(id, ['type', 'width']);\n if (p.type === 'graphic' && p.width >= NW * 0.9) {\n bg = id;\n break;\n }\n}\nif (bg !== undefined) {\n const roi = await captureFill(bg); // BEFORE the aspect change — ROI section\n await engine.design.setProps(bg, {\n position: { x: 0, y: 0 },\n width: NW,\n height: NH\n });\n await reapplyFill(bg, roi); // keep the same image region framed on the new aspect\n}\n// ...and captureFill/reapplyFill around EVERY other image/video-fill block you resize.\n\n// content blocks → position/size by captured fractions (fill), then you clamp + balance below\n// for each captured fraction f matched to its block id:\n// await engine.design.setProps(id, { position: { x: f.fx*NW, y: f.fy*NH } }); // fills the taller canvas\n// if (f.fw) await engine.design.setProps(id, { width: f.fw*NW });\n// if (f.ffs) await engine.design.setProps(id, { text: { fontSize: clampToBand(f.ffs*NH) + 'pt' } });\n// // the unit is required; the write auto-reshapes, ranges preserved\n//\n// THEN, not optional:\n// • margins: ensure left/right within 8–12% of min(NW,NH); re-center centered blocks on NW.\n// • safe band: shift the whole content group so nothing vital sits above SAFE_TOP or below NH-SAFE_BOT.\n// • balance: if proportional spread looks airy/crowded, distribute the group evenly or center it —\n// preview and adjust. Scale decoration with the canvas; keep type within §6.1.2 bands.\nreturn {\n type: 'text',\n text: 'first pass placed — now clamp margins/safe-zone/balance and preview'\n};\n```\n\nPlatform safe zones and per-format notes are in the `formats` skill — read `<id>.md` for each\ntarget format.\n\n## Preserve the region of interest (image / video / pattern fills)\n\nRe-flowing positions is only half the job. A block backed by an **image or video fill** shows a\n_window_ onto its source, and that window is recomputed when the block's frame **aspect** changes:\na `Cover` fill re-centers, a `Crop` fill exposes gaps. Left alone, the resize crops the **subject**\nout of frame — the face, product, logo, or focal point that made the asset worth using. That is a\ncomposition **and** craft failure, and it only shows in `preview`.\n\n**The ROI is defined by** (all on the block, via the `api` skill): `contentFill.mode`\n(`Crop | Cover | Contain`); for the auto modes, `contentFill.horizontalAlignment` +\n`contentFill.verticalAlignment` (`Left|Center|Right`, `Top|Center|Bottom`) — this is the focal side\na `Cover` fill keeps; and for `Crop`, `crop.scaleRatio` + `crop.translationX/Y` (translation in\n**% of the crop frame**) + `crop.rotation`, which pin an explicit window.\n\n**Capture before the resize, re-apply after** — around every fill-bearing block, not just the\nbackground:\n\n```js\nasync function captureFill(id) {\n // image/video/pixel fills only\n const caps = await engine.design.getCapabilities(id);\n if (!caps.includes('contentFillMode')) return null;\n const cf = (await engine.design.getProps(id, ['contentFill'])).contentFill;\n const cap = {\n mode: cf.mode,\n hAlign: cf.horizontalAlignment,\n vAlign: cf.verticalAlignment\n };\n if (cap.mode === 'Crop') {\n const c = (await engine.design.getProps(id, ['crop'])).crop;\n cap.crop = {\n sr: c.scaleRatio,\n tx: c.translationX,\n ty: c.translationY,\n rot: c.rotation\n };\n }\n return cap;\n}\n\nasync function reapplyFill(id, cap) {\n if (!cap) return;\n await engine.design.setProps(id, {\n contentFill: {\n mode: cap.mode,\n horizontalAlignment: cap.hAlign, // ROI focus for Cover/Contain\n verticalAlignment: cap.vAlign\n }\n });\n if (cap.mode === 'Crop' && cap.crop) {\n const c = cap.crop;\n await engine.design.setProps(id, {\n crop: { rotation: c.rot, translationX: c.tx, translationY: c.ty }\n });\n await engine.design.adjustCropToFillFrame(id, c.sr); // refill gaps, keep scale ≥ source\n }\n}\n```\n\n**Order matters:** `captureFill` **before** you write the block's `width`/`height`, `reapplyFill`\n**after**. `setProps(id, { crop: null })` forces the mode back to `Cover`, so never call it in a resize\npath. Default to keeping the ROI **centered**; when the brief or the asset's metadata names a focal point, bias\n`alignment` (Cover) or `translation` (Crop) toward it. `preview` each format and confirm the subject\nis still framed — the render is the only place ROI drift shows.\n\n**Crop invariants — the three properties every fill-in-a-frame must hold** (checked in `preview`;\nthey fail independently of ROI focus, and a shape frame — circle/rounded card — makes them easy to\nmiss):\n\n1. **Full coverage, zero gap.** The fill covers its frame completely — no sliver of empty frame at\n any edge (the \"gap at the top of the circle\" defect). If the aspect change exposed a gap,\n `adjustCropToFillFrame` (Crop) or the Cover mode refills it; never leave the frame partly unfilled.\n2. **Uniform scale — no stretch.** The image scales the **same on both axes**; a fill stretched to\n fit a new aspect (`Contain` into a non-matching frame, or a hand-set non-uniform crop) distorts\n the subject. Scale is one ratio, not two.\n3. **Subject inside the window.** After 1–2, the focal point is still within the visible crop, not\n pushed out by the refill. This is the ROI check above — do it last, after coverage is guaranteed.\n\n**Re-group after re-composition.** Resize moves and resizes loose blocks; before you finish, restore\nthe source's **group hierarchy** on the new canvas (handbook §4 \"Group blocks & hierarchy\") so each\nformat ships the same grouped, nested structure — not a flattened tree.\n\n## No-goes / red flags\n\n- **Dead void / letterboxing** — the headline failure: content clustered on one side of a\n taller/wider canvas, an empty band elsewhere. Fill or balance the group; respect safe zones.\n- **Design drift** — re-composition that loses the design's identity: margins wander, the type\n scale or hierarchy shifts, the accent is misused, or decoration loses its role. Re-compose\n _within_ the system, don't redesign.\n- **Stretch / squish** — scaling the canvas contents non-proportionally to force-fit. Never distort\n type or shapes; re-flow instead.\n- **Broken margin rhythm** — margins not re-derived from the new shortest dimension, or asymmetric.\n- **Off-canvas / clipping / overflow** — elements pushed past the new edges; decoration over-cropped;\n text overflowing the new column.\n- **Decoration mis-scale** — a ghost glyph or accent kept at the source's absolute size instead of\n scaling with the canvas.\n- **ROI drift** — a resized image/video fill re-centered and cropped the subject out of frame\n (face, product, logo, focal point). Capture and re-apply the fill's region of interest.\n- **Missing elements** — the reformat re-composed from an impression and silently dropped source\n content (\"too simplistic / elements missing from the original\"). Inventory every source cluster\n against the new canvas; omit only deliberately, never by oversight.\n- **Appearance drift** — a block's fill/gradient/effect/corner-radius/overlay differs from the\n source, or an overlay was added/removed the source didn't have (a cover that \"became darker\").\n Appearance is inherited by forking; don't re-create or restyle it by eye.\n- **Crop broken** — a fill leaves a gap inside its frame, or was stretched non-uniformly. See the\n three crop invariants (coverage, uniform scale, subject-in-window).\n- **Flattened hierarchy** — the resize dropped the source's group blocks and shipped a flat list of\n siblings. Re-group each cluster (nested where nested) on the new canvas.\n- **Ignoring platform safe zones** — vital content under story/reel UI chrome.\n- **tofu / overflow at new wrap widths** — a new measure can push a glyph the font lacks, or wrap to\n a line count that overflows; only `preview` catches it.\n\n## Before reporting done\n\nRun the **resize composition checklist** below against each format's render, then close with a `judge` run per format (the `judge` skill; composition and hierarchy are the axes reformatting most often breaks). Score honestly — a residual void is a composition fail, say so.\n\n### Resize composition checklist\n\nAny \"no\" on an applicable line drops the axis below 8.\n\n**Composition (§6.4)**\n\n- [ ] Margins 8–12% of the new shortest dimension; on the shared grid / one alignment\n- [ ] Whitespace balanced — **no lopsided dead void / letterbox**; content occupies or balances the canvas\n- [ ] Consistent spacing scale (§6.4 ladder); nothing off-canvas, clipped, or overlapping\n- [ ] Primary content within the format's safe band (per its `formats` file; story/reel: clear of top ~250px / bottom ~320px)\n\n**Hierarchy (§6.3) & identity**\n\n- [ ] One dominant element preserved; reading order intact; ≈3 levels\n- [ ] Palette + single accent, type-scale bands (§6.1.2), and each decoration's role carried over\n- [ ] Decoration scaled proportionally to the canvas (not the source's absolute size)\n- [ ] Every image/video fill keeps its **region of interest** — subject/focal point still framed, nothing important cropped out or newly empty (checked in `preview`)\n- [ ] Source's **group hierarchy** restored on the new canvas (grouped clusters, nested where nested — not a flat list)\n- [ ] **Element inventory complete** — every source content cluster (text, image, chip, icon, decoration) has a counterpart on the new canvas; any omission is deliberate and nameable, not an oversight\n- [ ] **Appearance parity** — every block's fill/gradient, effect/blur, opacity, corner radius, stroke, shadow, and font weight match the source; no overlay added or removed the source didn't have (checked against the source render)\n\n**Craft / baseline**\n\n- [ ] No stretch/squish; type re-fit within its band, not distorted\n- [ ] **Crop invariants** hold on every fill-in-frame — full coverage (no gap), uniform scale (no stretch), subject inside the window (checked in `preview`)\n- [ ] Zero tofu / overflow at the new wrap width, checked in `preview`\n- [ ] Native export dimensions (the page's own pixel size)\n\n## Cross-references\n\n- **`formats` skill** — per-format W×H, aspect, and platform safe zones (one file per format id).\n- handbook §6.3 Hierarchy, §6.4 Composition & grid, §6.1.2 type-scale bands — the general composition criteria this skill operationalizes; §6.1.2's canvas table for type sizing by % of page height; and the handbook's engine mechanics (the `text.font` declaration, auto-reshape, `loadResources`, dirty-on-throw).\n- **`judge`** — the acceptance gate every produced format must pass.\n- **`localize`** — the sibling for _translating_ a design at the _same_ size (snap, don't re-compose).\n",
617
+ "docs/video-thumbnail-template/SKILL.md": "---\nname: video-thumbnail-template\ndescription: >-\n Use when building a reusable thumbnail template and design guideline for a video channel\n (YouTube and other 16:9 video platforms) — from a channel link whose latest thumbnails set the\n style, from one or more example thumbnails or designs, or from scratch; or when making new\n thumbnails from such a template, one at a time or in a batch.\n---\n\n# video-thumbnail-template\n\nTurn a channel's thumbnail style into a design guideline (a brand kit) and a CoDesign template\nwith named slots, then fill the template for each new video.\n\nBuilds on, without restating: the handbook loop, the `brand` skill's kit format, the `layerize`\nskill (Seedream decomposition), the `models` skill, the `formats` skill (`youtube-thumbnail.md`)\nand the `judge` gate.\n\n## Input\n\nOne of three, resolved with the intake contract (`skill({ name: 'handbook', path: 'intake.md' })`):\n\n- **Channel link** — its latest 10 thumbnails set the style.\n- **Example thumbnails or designs** — one or more images, or design files (`.imgly`, `.psd`, …).\n- **Nothing** — start fresh; ask for the visual direction per the intake contract.\n\nThe format defaults to `youtube-thumbnail` (1280 × 720). Everything goes into one output folder\n(default `./<channel-or-topic>-thumbnails/`): `reference/`, `brand-kit/`, the template and the\nthumbnails. A thumbnail style is the channel's brand: never invent it when references exist.\n\n## 1 — Collect references\n\n**Channel link.** Fetch the channel's `/videos` page (e.g. `https://www.youtube.com/@handle/videos`)\nwith your own web or shell tools. That tab lists regular uploads only, newest first; the ids are in\nthe page source as `\"videoId\":\"<11 chars>\"`. Take the first 10 distinct ids and download each\nthumbnail from `https://i.ytimg.com/vi/<id>/maxresdefault.jpg` (fall back to `hqdefault.jpg`) into\n`reference/` — public URLs, no API key or login. If the page cannot be read, ask the user for a\nfew video links or thumbnail images instead.\n\n**Example thumbnails.** Copy the files the user named into `reference/`. A design file also goes\nthrough `import` — its layers show the structure directly.\n\n**Nothing.** No references; go to step 3 with the direction the user chose.\n\n## 2 — Analyse the layouts\n\nLook at every reference yourself. For **each** one, record its layout — positions and sizes as\nfractions of the canvas, so they compare across images:\n\n- zones: where the host, the headline and the subject sit, and how much of the canvas each takes\n- headline: the words, line count, the role of each line (hook vs punch word), case, relative\n size, colour, stroke, shadow, closest Google Font\n- host: side, crop, expression, gaze, outline or glow\n- background: image or colour, mood, whether it repeats\n- devices: arrows, badges, frames, blur, emoji\n\nThen compare the set: what repeats in most references is the **system**, what appears once is an\noption, and what changes from video to video becomes a **slot**. A set that splits into two or\nthree distinct layouts gets one template per layout — name them and ask which to build if unsure.\n\n## 3 — Design guideline (brand kit)\n\nWrite the guideline as a brand kit in the `brand` skill's format — `brand-kit/tokens.json` +\n`brand-kit/BRAND.md`. Read `skill({ name: 'brand', path: 'reference/brandkit-spec.md' })` for the\nshape and `reference/authoring.md` for the rules (screenshots are its source form D). Fill it from\nthe analysis:\n\n- `colorPrimitives` / `semanticColors` — the colours sampled from the references (say they are\n sampled)\n- `typography` — the headline font(s), weights, case and the size ratio between the lines\n- `layout` / `composition` — the zones from step 2, as fractions of the canvas\n- `safeArea` — the platform's (the duration badge, bottom right, for YouTube)\n- `imagery` — host crop and treatment, background mood\n- `device` — the recurring devices\n- `voice` — headline formulas: words per line, the punch words in use\n- `logo` — the channel's mark if the thumbnails carry one; otherwise record it in `$meta.gaps`\n\nDraft the `avoid` list from what the references never do and confirm it with the user, as\n`authoring.md` requires.\n\n**Starting fresh**, the guideline comes first too: turn the chosen direction into the same kit,\nshow the user its key choices (palette, fonts, layout) and confirm them before building.\n\n## 4 — Layers and assets\n\nWhen a reference supplies a host, subject or background worth reusing, split it with the `layerize`\nskill's Stage 1 (Seedream) and follow its rules. Keep the person, subject and residual background\nlayers; drop the text layers — the headline is rebuilt as live text.\n\nA user's host photo that is not cut out goes through background removal (`models` skill,\n`bria-rmbg.md`). A missing background can be generated from the kit's `imagery` mood,\n`format: '16:9'`, with \"no people, no text, no logos\" in the prompt.\n\n## 5 — Build the template\n\nBuild from the kit: one page at the format size, the zones from `layout`, back to front. Name the\nslots so they can be filled later:\n\n| name | kind | notes |\n| ---------- | ------- | ------------------------------------------------------ |\n| `bg` | graphic | full-bleed image fill, `Cover` |\n| `host` | graphic | cut-out, `Contain`, anchored to the edge it grows from |\n| `subject` | graphic | optional cut-out, `Contain` |\n| `line1` | text | the hook |\n| `line2` | text | optional punch word, `text.case: 'Uppercase'` |\n| `headline` | group | `line1` + `line2` |\n\nDrop `host` when the style has no presenter; decoration from the style gets its own descriptive\nname. When a headline line is too long for its column, shrink it until it fits — never let it run\nto the edge or into the safe-area corner.\n\nTest the template with a long punch word, a word with a capital diacritic (`ÜBERNIMMT`) and a\nthumbnail without the optional slots, then run the `judge` loop with the kit as the brief. The\nreferences are the style: tight headlines or a host bleeding off the canvas are not faults.\n\nExport it with `export({ format: 'imgly', outPath })` into the output folder and report the kit,\nthe slots and any `$meta.gaps`.\n\n## 6 — New thumbnails\n\nFor each thumbnail, `edit` with `parent` = the **template revision** — never chain one\nthumbnail onto the previous — fill the slots, hide the empty ones, fit the headline, and export\nJPEG (`export({ format: 'jpeg', blockId: <page>, outPath })`; YouTube rejects uploads over 2 MB).\nLook at every export and check it against the kit's `avoid` list.\n\nFor a batch, agree on a simple table with the user (e.g. a CSV with one row per video: headline\nlines and image files) and repeat the above per row. If they want to render outside the agent,\nwrite them a small `@cesdk/node` script that loads the exported `.imgly`, fills the same slots by\nname and exports each row; without a CE.SDK licence key it watermarks its output — say so.\n\n## Common mistakes\n\n| Mistake | Fix |\n| --------------------------------------- | ---------------------------------------------------------------------- |\n| Styling from one thumbnail of ten | analyse every reference; the system is what repeats |\n| Building before the guideline exists | write and confirm the kit first — the template is built from it |\n| New image fill comes out zoomed/cropped | `forceLoadResources([slot])`, then `resetCrop`, then re-apply the mode |\n| Moving `line1`/`line2` while grouped | positions are relative to the group — ungroup, position, regroup |\n| Typing capitals into `line2` | keep the text as typed; the slot's `text.case` sets it |\n| Sizing Seedream cut-outs by pixel size | place by `bounding_box.absolute` |\n| A person placed with `Cover` | `Contain`, anchored to the bottom edge |\n",
616
618
  };
617
619
  /** Binary assets, base64-encoded — see the generator header. */
618
620
  export const INLINE_BINARY_ASSETS = {