@cliangdev/conductor 0.25.0 → 0.27.0

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.
Files changed (50) hide show
  1. package/assets/claude/agents/conductor-coder.md +25 -0
  2. package/assets/claude/agents/conductor-content-art-director.md +68 -0
  3. package/assets/claude/agents/conductor-content-creative-director.md +56 -0
  4. package/assets/claude/agents/conductor-content-ideator.md +49 -0
  5. package/assets/claude/agents/conductor-content-scriptwriter.md +42 -0
  6. package/assets/claude/agents/conductor-content-strategist.md +47 -0
  7. package/assets/claude/commands/conductor/content.md +64 -0
  8. package/assets/claude/commands/conductor/creative.md +46 -46
  9. package/assets/claude/commands/conductor/implement.md +1 -1
  10. package/assets/claude/skills/conductor-content-studio/SKILL.md +323 -0
  11. package/assets/claude/skills/conductor-content-studio/references/capabilities.md +61 -0
  12. package/assets/claude/skills/conductor-content-studio/references/compliance-checklist.md +120 -0
  13. package/assets/claude/skills/conductor-content-studio/references/idea-template.md +54 -0
  14. package/assets/claude/skills/conductor-content-studio/references/platform-specs.md +59 -0
  15. package/assets/claude/skills/conductor-content-studio/references/publish-pack-template.md +121 -0
  16. package/assets/claude/skills/conductor-content-studio/references/rubric.md +99 -0
  17. package/assets/claude/skills/conductor-content-studio/references/script-template.md +53 -0
  18. package/assets/claude/skills/conductor-content-studio/references/shotlist-template.md +44 -0
  19. package/assets/claude/skills/conductor-creative/SKILL.md +111 -45
  20. package/assets/claude/skills/conductor-creative/references/brand-kit-setup.md +41 -0
  21. package/assets/claude/skills/conductor-publisher/SKILL.md +26 -2
  22. package/assets/cli-manifest.json +48 -3
  23. package/dist/creative/assertions.js +22 -11
  24. package/dist/creative/frame.js +3 -3
  25. package/dist/creative/job/file-transport.mjs +211 -0
  26. package/dist/creative/job/render.mjs +84 -2
  27. package/dist/creative/job/server.mjs +70 -13
  28. package/dist/lib/creative-render.d.ts +62 -0
  29. package/dist/lib/creative-render.d.ts.map +1 -1
  30. package/dist/lib/creative-render.js +51 -0
  31. package/dist/lib/creative-render.js.map +1 -1
  32. package/dist/lib/plugin-assets.d.ts.map +1 -1
  33. package/dist/lib/plugin-assets.js +17 -0
  34. package/dist/lib/plugin-assets.js.map +1 -1
  35. package/dist/mcp/index.d.ts +856 -261
  36. package/dist/mcp/index.d.ts.map +1 -1
  37. package/dist/mcp/index.js +164 -75
  38. package/dist/mcp/index.js.map +1 -1
  39. package/dist/mcp/tools/creative-drafts.d.ts +134 -0
  40. package/dist/mcp/tools/creative-drafts.d.ts.map +1 -0
  41. package/dist/mcp/tools/creative-drafts.js +405 -0
  42. package/dist/mcp/tools/creative-drafts.js.map +1 -0
  43. package/dist/mcp/tools/creatives.d.ts +3 -0
  44. package/dist/mcp/tools/creatives.d.ts.map +1 -1
  45. package/dist/mcp/tools/creatives.js.map +1 -1
  46. package/dist/mcp/tools/issues.d.ts.map +1 -1
  47. package/dist/mcp/tools/issues.js +6 -1
  48. package/dist/mcp/tools/issues.js.map +1 -1
  49. package/package.json +1 -1
  50. package/scripts/build-creative.mjs +1 -0
@@ -0,0 +1,121 @@
1
+ # Publish pack
2
+
3
+ Two files in the run folder, written by the orchestrator in stage S5b from the scriptwriter's
4
+ `## Publish copy` section in `script.md`, the art director's `## Publish assets` section in
5
+ `direction.md`, and the compliance results (`compliance-checklist.md`). The pack is what the
6
+ publisher uses later to pre-fill the Post, so it holds only what the Post needs. A run never
7
+ creates a Post.
8
+
9
+ Short-form craft this encodes: the search keyword sits in the caption's opening words, on-screen
10
+ text and speech (TikTok search reads all three); a hook swap is the standard creative iteration;
11
+ hook rate is 3-second plays over impressions and hold rate is completions over 3-second plays;
12
+ posts work with the sound off.
13
+
14
+ ## Hook types
15
+
16
+ Tag every hook, in ideas and in the pack, with one of: `question`, `bold-claim`,
17
+ `pattern-interrupt`, `relatable-pain`, `how-to`, `number`, `curiosity-gap`, `story`,
18
+ `social-proof`. Use another short kebab-case word only when none fits.
19
+
20
+ ## publish-pack.md (for people)
21
+
22
+ One section per platform the brief asks for. Omit a platform it does not.
23
+
24
+ ```
25
+ # Publish pack: <title>
26
+
27
+ Run: <runId> Creative: <display id, or "not yet committed"> Tags: hook <type>, angle <angle>, format <format>
28
+
29
+ ## <Platform> (<feed | reel | story>, as the destination's formats allow)
30
+ - **Caption:** <final text; the search keyword is in the first line, within the first ~125
31
+ characters; within the platform's limit>
32
+ - **Hashtags:** <3 to 5>
33
+ - **First comment (post manually):** <text, or "none">. Conductor cannot post comments.
34
+ - **Cover frame:** <video: timestamp in seconds and what is on it; carousel: card 1; still: n/a>
35
+ - **Alt text:** <one line per image or card, in order>
36
+ - **On-screen text placement:** <where each line sits, inside the safe zone>
37
+ - **Sound:** <source and licence: original audio | Commercial Music Library track "<name>" |
38
+ library track id and its licence | clip's own sound | none>
39
+ - **Posting window:** <best hour and weekday from get_marketing_insights byHour/byWeekday for this
40
+ platform, with the window it came from, or "no data yet">
41
+ - **Options:** <the option keys and values this platform gets, from publish-pack.json>
42
+
43
+ ## Alternate hooks (for variantOf hook experiments)
44
+ 1. <hook line> (<hookType>)
45
+ 2. <hook line> (<hookType>)
46
+ 3. <optional>
47
+
48
+ ## Compliance
49
+ <the table from compliance-checklist.md: check, answer, what was done, option set>
50
+
51
+ ## Third-party assets
52
+ | Asset | Whose | Permission or licence |
53
+ |---|---|---|
54
+ | <music, footage, logo, meme, likeness, trademark, parody> | ... | ... |
55
+ (Write "none" if the post uses only the brand's own and generated-by-Conductor material.)
56
+ ```
57
+
58
+ ## publish-pack.json (for the publisher)
59
+
60
+ ```json
61
+ {
62
+ "creativeId": null,
63
+ "platforms": [
64
+ {
65
+ "platform": "tiktok",
66
+ "format": "feed",
67
+ "captionOverride": "<final caption>",
68
+ "options": { "brandOrganicToggle": true, "isAigc": false, "videoCoverTimestampMs": 1500 }
69
+ },
70
+ {
71
+ "platform": "instagram",
72
+ "format": "feed",
73
+ "captionOverride": "<final caption>",
74
+ "options": { "altText": "<alt text for a single feed image>" }
75
+ }
76
+ ],
77
+ "altText": ["<card 1 alt text>", "<card 2 alt text>"],
78
+ "alternateHooks": [{ "hook": "<line>", "hookType": "question" }],
79
+ "tags": { "hookType": "bold-claim", "angle": "utility-education", "format": "carousel" }
80
+ }
81
+ ```
82
+
83
+ Rules:
84
+
85
+ - `creativeId` is null until the person approves the commit (S6, On Approve), then it is the
86
+ Creative's id. A `FILM` run leaves it null: the publisher is pointed at the pack by the person.
87
+ - `platforms[]` items are shaped to drop straight into `create_post` targets or
88
+ `set_publish_targets` targets: `platform`, `format` (`feed`, `reel` or `story`, and only one the destination's `formats` offers: at the time
89
+ of writing TikTok and YouTube are feed only, Facebook and Instagram offer all three),
90
+ `captionOverride`, and `options`. Name `options` `publishOptions` for `set_publish_targets`. They
91
+ carry no account: the publisher resolves the account (`account` or `connectionId`) from
92
+ `list_publish_targets`. `captionOverride` is ignored on a story.
93
+ - `captionOverride` is the **complete text that gets posted**: the caption, then a blank line, then that
94
+ platform's hashtags. The publisher posts it as-is, so hashtags listed only in `publish-pack.md` never
95
+ reach the platform. The first comment is the one thing that stays out: it is posted by hand.
96
+ - `options` holds real option keys only, in the camelCase the destination reports under
97
+ `optionKeys`. Never add a key that is not in this table, and never `privacyLevel` (the person
98
+ picks it from the account's allowed levels at publish time):
99
+
100
+ | Platform | Keys the pack may set | Applies to |
101
+ |---|---|---|
102
+ | TikTok | `brandContentToggle`, `brandOrganicToggle` | video and photo posts |
103
+ | TikTok | `isAigc` | video posts only |
104
+ | TikTok | `videoCoverTimestampMs` (milliseconds, from the cover frame) | video posts only |
105
+ | TikTok | `disableComment` | video and photo posts |
106
+ | TikTok | `disableDuet`, `disableStitch` | video posts only |
107
+ | Instagram | `altText` (at most 1000 characters) | a single feed image only |
108
+ | Instagram | `audioName`, `shareToFeed`, `collaborators` (at most 3, not on a carousel) | reels only |
109
+ | YouTube | `containsSyntheticMedia` | video |
110
+
111
+ Set only what the compliance check or the plan decided: leave a key out rather than writing a
112
+ default. Keys that need a Post's own asset id (`coverAssetId`, `thumbnailAssetId`,
113
+ `photoCoverIndex`) are not known until media is attached to a Post, so the pack does not carry
114
+ them; the cover frame is in `publish-pack.md` for the publisher to act on.
115
+ - `altText` is one string per image or card, in order. It is also the source for the Creative's
116
+ own `altText` field.
117
+ - `alternateHooks` and `tags` are for learning and experiments, not for publishing. `tags` is also
118
+ copied into `run.json`.
119
+ - `tags.angle` is the ideator's angle: `story-emotion`, `utility-education` or `trend-humor`.
120
+ `tags.format` is the idea's format (`still`, `carousel`, `story`, `motion`, `clip` or `film`).
121
+ - Valid JSON, no comments.
@@ -0,0 +1,99 @@
1
+ # Judging rubric
2
+
3
+ Used by the `conductor-content-creative-director` agent in `text-round` and `visual-round`, and by
4
+ the orchestrator when it needs to break a tie. Everything here exists to counter a known judge
5
+ failure mode, so follow it as written.
6
+
7
+ ## Why it is shaped this way
8
+
9
+ - LLM judges favour longer answers, the first-listed answer and the answer written in their own
10
+ style (Zheng et al. 2023, MT-Bench). So ideas arrive at the same length, under shuffled neutral
11
+ IDs, and finalists are compared in both orders.
12
+ - A judge that scores before it reasons anchors on its first impression, and a judge asked to
13
+ confirm a leader mostly confirms it (Verga et al. 2024 on panels of different models). So:
14
+ reasoning is written first, the judge is a different model tier from the ideators, and a
15
+ devil's-advocate pass runs against the leader.
16
+ - Ideas generated by one assisted process converge on the same few mechanics (Doshi & Hauser 2024),
17
+ and a model asked to debate itself repeats its first stance (Liang et al., degeneration of
18
+ thought). So novelty is scored explicitly, and the leader must be argued against.
19
+
20
+ ## Criteria and weights
21
+
22
+ Score each 1 to 5. Weighted total = sum(score x weight) / 5, so the maximum is 100.
23
+
24
+ | Criterion | Weight | 1 | 3 | 5 |
25
+ |---|---|---|---|---|
26
+ | Hook strength | 25 | Opens on a greeting, a logo or context; nothing makes a stranger stay | A clear promise or question, but generic; could open any account's post | A specific, surprising or tension-filled claim a stranger understands in 1s, spoken and on screen |
27
+ | Audience relevance | 20 | Aimed at "everyone"; ignores the brief's tension | Names the audience's topic but not their actual friction | Opens on a friction the brief names and the audience would recognise as theirs |
28
+ | Brand fit | 15 | Breaks a copy rule or approved-line, or reads off-voice | Within the rules but could be any brand | Uses an approved line or the knowledge page's voice naturally; no rule risk |
29
+ | Feasibility with assets on hand | 15 | Needs assets that do not exist and cannot be made here (and is tagged `RENDER`) | Needs one new photo or clip the user can supply easily | Fully buildable from `media.json` or a render with no photo; or an honest `FILM` with a simple shoot |
30
+ | Novelty | 15 | The mechanic is the most common one for this topic (listicle, "5 tips", before/after) | A familiar mechanic with a fresh angle | A mechanic not seen elsewhere in the pool or in the performance data, still easy to grasp |
31
+ | Shareability | 10 | Nothing to send to a friend; purely informational | Useful or pleasant, but no reason to send | A person would send it to a named other person ("this is you") or save it for later |
32
+
33
+ ## Scoring protocol
34
+
35
+ 1. Read `brief.md`, then every idea in `ideas/pool.md`. Ideas are identified by ID only; ignore the
36
+ order they appear in and any difference in polish.
37
+ 2. For each idea, per criterion, write one or two sentences of reasoning FIRST, citing the idea's
38
+ own words. Then write the score. Never write a score before its reasoning.
39
+ 3. Penalise nothing for length (ideas are length-matched) and reward nothing for confident tone.
40
+ 4. Compute the weighted total. Shortlist the top 3. If ties, apply the tie-break rule below.
41
+ 5. Any idea that breaks a copy rule or contradicts an approved line is capped at 2 on brand fit
42
+ and cannot be a finalist unless the fix is a one-word edit you state.
43
+
44
+ ## Pairwise protocol (visual round)
45
+
46
+ For each pair of finalists (A,B), (A,C), (B,C):
47
+
48
+ 1. Judge the pair twice, once with each order, as two independent comparisons. Write reasoning
49
+ first, then the verdict "first" or "second", then say which idea won.
50
+ 2. If the two orders name different winners (position flipped the result), the pair is a tie.
51
+ 3. Tally wins: win = 1, tie = 0.5 each. The leader has the most points.
52
+
53
+ ## Judging a rendered draft
54
+
55
+ Look at the actual image file, not the text description. Check:
56
+
57
+ - Readability at phone size: shrink it mentally to a thumbnail. Can the headline be read in a glance?
58
+ - Hook legibility in 1 second: is the first thing seen the hook, in high contrast?
59
+ - Safe zones: text out of the platform UI (see `platform-specs.md`), nothing sitting on a face.
60
+ - Brand look: tokens, font, logo lockup, accent phrase on the words that carry the meaning.
61
+ - Whether the picture and the words do different jobs (photo shows, words claim) rather than
62
+ repeating each other.
63
+
64
+ A finalist that could not be rendered keeps its text-round score; say so, and do not treat the
65
+ missing image as a defect of the idea, except when the failure came from copy the validator refused.
66
+ A `FILM` finalist has no image: judge its script and shot description against the same criteria and
67
+ note that the visual round could not test it.
68
+
69
+ ## Devil's-advocate pass
70
+
71
+ After the tally, and before naming a winner, write the strongest case against the leader in at
72
+ least five sentences: the likeliest way it fails with this audience, the platform risk, the
73
+ brand risk, and what the runner-up does better. Then decide whether the case is strong enough to
74
+ change the winner. State the answer plainly. Do not soften the case to protect the leader.
75
+
76
+ ### Veto questions
77
+
78
+ The pass must also answer these four about the leader, each yes or no with one line of reasoning.
79
+ They come from `compliance-checklist.md`.
80
+
81
+ 1. Does it rely on an **unsubstantiated claim** (nothing in the brief, knowledge or approved lines
82
+ confirms it)?
83
+ 2. Is there a **trademark or parody risk** (another company's product, brand, logo or likeness)?
84
+ 3. Is there a **representation or sensitivity problem**?
85
+ 4. Is **disclosure needed** (anyone paid, gifted, an employee or an affiliate) that the brief does
86
+ not already handle?
87
+
88
+ A "yes" that a copy edit can fix is fixed or noted for the scriptwriter. A "yes" that cannot be
89
+ fixed in copy disqualifies the idea: record it in `decision.md` under **Veto**, take the next
90
+ leader on the tally and run the same questions on it. Disclosure that only needs a toggle and a
91
+ caption line is not a disqualifier; note it.
92
+
93
+ ## Tie-break rule
94
+
95
+ If two ideas are within 3 points of weighted total, or tied on the pairwise tally, prefer in order:
96
+ 1. the higher hook-strength score;
97
+ 2. the idea buildable entirely from assets on hand (`RENDER` over `FILM`);
98
+ 3. the higher novelty score;
99
+ 4. the lower ID number (an arbitrary but fixed choice, so no one can say the order was chosen).
@@ -0,0 +1,53 @@
1
+ # Script template
2
+
3
+ `script.md` for the winning idea. Short-form craft rules this encodes: the hook lands in 0 to 3
4
+ seconds, spoken and on screen; something new happens every 3 to 5 seconds; there is a payoff, then
5
+ a call to action; the piece works with the sound off.
6
+
7
+ ```
8
+ # Script: <title>
9
+
10
+ Platform: <platform(s)> Length: <seconds> Tag: RENDER | FILM
11
+ Idea: <id> Source decision: decision.md
12
+
13
+ ## Hook (0-3s)
14
+ - Spoken: <line, max 12 words, or "none">
15
+ - On screen: <text, max 12 words>
16
+ - Visual: <what is shown>
17
+
18
+ ## Beats
19
+ | # | Time | Visual | On-screen text | Spoken / audio | What changes |
20
+ |---|------|--------|----------------|----------------|--------------|
21
+ | 1 | 3-6s | ... | ... | ... | <the new beat or pattern interrupt> |
22
+ | 2 | 6-10s | ... | ... | ... | ... |
23
+
24
+ ## Payoff
25
+ <the line or moment that delivers the hook's promise, with time>
26
+
27
+ ## CTA
28
+ <one action; use the kit's CTA claim or an approved line where there is one>
29
+
30
+ ## Publish copy
31
+ Per platform the brief asks for. The keyword the audience would search for sits in the caption's
32
+ opening words (and in the on-screen text and speech); the first line carries the hook.
33
+ - TikTok: <caption; keep the visible part to about 150 characters>
34
+ - Instagram: <caption; first line carries the hook, max 125 characters shown before "more">
35
+ - YouTube Shorts: <title max 100 characters>
36
+ - Facebook: <caption>
37
+ - Hashtags: <3 to 5, no more>
38
+ - First comment: <text, or "none"; posted by hand, because Conductor cannot post comments>
39
+ - Alternate hooks (hook swaps for variantOf experiments):
40
+ 1. <hook line> (<hookType>)
41
+ 2. <hook line> (<hookType>)
42
+ 3. <optional>
43
+
44
+ Hook types: question, bold-claim, pattern-interrupt, relatable-pain, how-to, number,
45
+ curiosity-gap, story, social-proof.
46
+
47
+ ## Copy-rule check
48
+ <each must-say / must-avoid item from brief.md, and ok or the fix>
49
+ ```
50
+
51
+ Rules: a beat every 3 to 5 seconds; no beat longer than 5 seconds without a visual or text change;
52
+ caption limits are in `platform-specs.md`; alternates differ from the main hook in hook type, not just wording (check current platform guidance); approved lines run
53
+ verbatim; leave out platforms the brief did not ask for.
@@ -0,0 +1,44 @@
1
+ # Shot list and direction template
2
+
3
+ `direction.md` (RENDER) and `shoot/shoot-script.md` (FILM) both use this table. A RENDER direction
4
+ has no camera, so "visual" is the layout or frame, and the final section maps the direction onto
5
+ the Creative's own fields.
6
+
7
+ ```
8
+ # Direction: <title>
9
+
10
+ ## Shot list
11
+ | Time | Visual | On-screen text | VO / audio | Asset |
12
+ |------|--------|----------------|------------|-------|
13
+ | 0-3s | ... | ... | ... | <media id, "to film", or none> |
14
+
15
+ ## Safe zones
16
+ - Keep text out of the top ~14%, the bottom ~35% and ~6% at each side (9:16; check current
17
+ platform guidance).
18
+ - Headline and CTA sit in the middle band. Nothing on a face.
19
+
20
+ ## Sound
21
+ <sound on or off by design; music track id if any; voice-over; captions are always on screen>
22
+
23
+ ## Publish assets
24
+ Per platform the script names:
25
+ - Cover frame: <video: timestamp in seconds and what is on it; carousel: card 1; still: n/a>
26
+ - Alt text: <one line per image or card, in order>
27
+ - On-screen text placement: <where each line sits, inside the safe zone for that platform>
28
+ - Sound source and licence: <original audio | Commercial Music Library track | library track id
29
+ and licence | clip's own sound | none>. Never a trending track a business account cannot license.
30
+
31
+ ## Capture settings (FILM only)
32
+ 9:16 vertical, 1080x1920, 30 fps, sound on, lit from the front, phone steady, shoot extra B-roll.
33
+
34
+ ## Ready-to-paste text (FILM only)
35
+ <on-screen text lines in order, and the captions per platform>
36
+
37
+ ## Mapping to the Creative (RENDER only)
38
+ - kind: STILL | MOTION
39
+ - layout / theme: <from the kit and registry>
40
+ - sequenceKind: story | carousel | none (carouselRatio for a carousel)
41
+ - motion: preset (fade-up | word-by-word | accent-pop | none), durationSec, background (photo or
42
+ clip, with its motion)
43
+ - audio: clip | track | none
44
+ ```
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: conductor-creative
3
- description: Creates on-brand marketing creative through the Conductor MCP tools — static ads and short videos alike, rendered locally on this machine and checked by eye before it's called done. Covers a photo-plus-headline ad at every enabled placement (e.g. TikTok/Reels 9:16, Instagram feed 4:5, Facebook 1:1); a short video made from a still photo or an existing clip (animated text, zoom/pan, background music, a TikTok/Reels/Shorts-ready cut); posting a finished video as-is; a story or carousel sequence; a lettered headline/hook variant for testing; and, when a workspace's Brand Kit has never been set up, setting one up first (colours, font, logo, copy rules) from a website, a brand doc, or a few direct answers. Use when asked to make, design, write or export a social ad, a promo image, a short-form video, an animated ad, or to configure/update a project's brand.
3
+ description: Creates on-brand marketing creative through the Conductor MCP tools — static ads and short videos alike, rendered locally on this machine as a draft, checked by eye and approved before anything is saved to Conductor. Covers a photo-plus-headline ad at every enabled placement (e.g. TikTok/Reels 9:16, Instagram feed 4:5, Facebook 1:1); a short video made from a still photo or an existing clip (animated text, zoom/pan, background music, a TikTok/Reels/Shorts-ready cut); posting a finished video as-is; a story or carousel sequence; a lettered headline/hook variant for testing; and, when a workspace's Brand Kit has never been set up, setting one up first (colours, font, logo, copy rules) from a website, a brand doc, or a few direct answers. Use when asked to make, design, write or export a social ad, a promo image, a short-form video, an animated ad, or to configure/update a project's brand.
4
4
  user-invocable: true
5
5
  allowed-tools: mcp__conductor__*, AskUserQuestion, Read, Glob, Grep, WebFetch
6
6
  ---
@@ -17,7 +17,13 @@ things that already exist for this workspace — a Brand Kit (data: tokens, copy
17
17
  lines) and a Knowledge page (prose: positioning, voice, facts). Read them rather than inventing.
18
18
 
19
19
  Rendering happens locally, through this MCP server, with a browser on this machine — there is no
20
- separate render service to wait on. `render_creative` typically finishes in well under a minute.
20
+ separate render service to wait on. A preview typically finishes in well under a minute.
21
+
22
+ **Draft first, save after approval.** Every Creative here goes draft, look, approve, commit:
23
+ `preview_creative_draft` renders the artwork on this machine and writes **nothing** to Conductor — no
24
+ Creative, no upload — then the person looks at it, and only on their approval does
25
+ `commit_creative_draft` upload the media, save the Creative and do the full render. Never commit a
26
+ draft the person has not seen and approved.
21
27
 
22
28
  ## How to make a Creative
23
29
 
@@ -50,8 +56,8 @@ Prefer an approved line from the Knowledge page verbatim. Otherwise write one th
50
56
  real and specific, not a generic claim.
51
57
 
52
58
  If the Brand Kit's `accentPhraseRequired` is true, mark exactly one phrase per headline with
53
- asterisks: `Every saved link, *finally usable*.` Never zero, never two — `create_creative` and
54
- `update_creative` refuse a headline that gets this wrong, naming the rule.
59
+ asterisks: `Every saved link, *finally usable*.` Never zero, never two — `preview_creative_draft`,
60
+ `create_creative` and `update_creative` refuse a headline that gets this wrong, naming the rule.
55
61
 
56
62
  Keep it short — checked at thumbnail size, not read up close.
57
63
 
@@ -69,10 +75,13 @@ uploading a new one — id, dimensions, checked/blocked state and provenance for
69
75
  the project's media library. A photo needs `checked: true` and not `blocked` before a Creative can
70
76
  go to `READY`.
71
77
 
72
- A new photo goes through `upload_creative_media` (local path or URL), which requires `source` (a
73
- URL or a short provenance note, e.g. "Generated with a named model on a given date") and `licence`
74
- (e.g. "Own work", "Unsplash Licence", "Generated, house use"). This closes a real legal exposure: an
75
- unattributed or unlicensed photo running as paid or organic media.
78
+ A photo that is not in the library yet is previewed straight from its local file (`photoPath` on
79
+ `preview_creative_draft`; a URL is downloaded to a local file first) and is only uploaded when the
80
+ draft is committed — pass `source` (a URL or a short provenance note, e.g. "Generated with a named
81
+ model on a given date") and `licence` (e.g. "Own work", "Unsplash Licence", "Generated, house use")
82
+ to `commit_creative_draft`, which records them on every file it uploads. This closes a real legal
83
+ exposure: an unattributed or unlicensed photo running as paid or organic media. A photo already in
84
+ the library is used by its id (`photoId`) with no upload at all.
76
85
 
77
86
  **Open the photo and look at it before using it.** A picture can break every copy rule with text,
78
87
  a logo or a claim burned into its pixels, and no automated check can see that — this is the first
@@ -83,48 +92,98 @@ Match the crop to the subject: a layout that lays copy over the photo (e.g. `ble
83
92
  the top third in collision with the headline — prefer a layout that keeps the photo and copy on
84
93
  separate panels (e.g. `stacked`) for those.
85
94
 
86
- ### 6. Create, then render
87
-
88
- `create_creative` with the Brand Kit (if not the default), layout, theme, photo, headline, body,
89
- caption and alt text. To test a headline variant of an existing concept, pass `variantOf` with that
90
- Creative's id instead of starting fresh — it inherits everything else and gets the next letter.
91
- Then call `get_creative` to verify what was actually stored.
92
-
93
- `render_creative` (previewOnly: true first, for a quick contact sheet) runs the render locally and
94
- returns the render id, state, frame URLs and any warnings. Then `preview_creative` to actually look
95
- at it — it returns the contact sheet as an image directly, not just a URL to trust.
95
+ ### 6. Preview the draft
96
+
97
+ `preview_creative_draft` with the Brand Kit (if not the default), layout, theme, photo (`photoPath`
98
+ for a local file, `photoId` for one already in the library), headline, body, caption and alt text.
99
+ It renders a contact sheet on this machine and returns it as an image, with the `draftDir` it wrote
100
+ to (`.conductor/drafts/<timestamp>-<slug>/`), the readiness gaps and any render warnings. **Nothing
101
+ has been saved to Conductor.** A copy-rule or accent-phrase violation comes back as an error naming
102
+ the rule and field, without rendering — fix the copy and preview again.
103
+
104
+ To change an existing Creative's copy, layout or photo, preview the edit first with `baseCreativeId`:
105
+ only the fields you pass change, and the commit then updates that Creative instead of creating one.
106
+
107
+ To test a headline variant of an existing concept, preview the new headline the same way
108
+ (`baseCreativeId` plus `headline`) so the person sees it on the artwork — but do **not** commit that
109
+ draft, since a commit would update the original. Once they approve, call `create_creative` with
110
+ `variantOf` and the headline instead: it inherits everything else and gets the next letter.
111
+
112
+ Pass `full: true` to render every placement at full size instead of the contact sheet (slower; the
113
+ image returned is the first placement).
114
+
115
+ **Read `checks` and `passed` before you show anything.** The contact sheet never runs the render's
116
+ safety checks, so the preview also runs them for every placement (and, for a video, its end card)
117
+ and returns them as `checks: [{placementKey, index?, rule, message, severity}]` plus `passed`. An
118
+ `error` check (`safeZone`, `spill`, `contrast`, `font`, `image`, ...) is exactly what would fail the
119
+ full render after the person approved: e.g. an App Store badge intruding into the band TikTok/Reels
120
+ reserve on 9:16. A `warning` (a soft, upscaled photo) does not block, but mention it.
121
+
122
+ When `passed` is false, **do not show the image or ask for approval yet.** Fix the design and
123
+ preview again, repeating until `passed` is true:
124
+
125
+ - the copy collides with a reserved zone: shorten the headline or drop the body;
126
+ - the photo takes too much of a placement: lower its height there with `layoutOverrides.band`
127
+ (e.g. `{"band": {"9x16": 1100}}`);
128
+ - the bottom row (CTA/badge) does not clear the safe zone: raise the clearance with
129
+ `layoutOverrides.padBottom`, or pick a layout that keeps it clear;
130
+ - the text is too large or too small for a placement: set it with `typeOverrides` (below).
131
+
132
+ Tell the person what you changed and why when you do show it. Never offer **Approve & upload**
133
+ while a blocking check fails.
134
+
135
+ **Bigger or smaller text on one placement: use `typeOverrides`, not a different layout.** It pins
136
+ the headline per placement as `{"<placementKey>": [fontSize, lineHeight?, letterSpacing?]}`, e.g.
137
+ `{"9x16": [96], "1x1": [88]}` for a bigger headline on 9:16 and 1:1. `fontSize` is in artboard px
138
+ (a 9:16 board is 1080 wide), `lineHeight` a multiplier (e.g. `1.0`) and `letterSpacing` in px
139
+ (usually negative); leave the last two out to take the engine's defaults for that size. A placement
140
+ you do not name stays auto-fitted. A pinned size is not shrunk to fit, so re-check `checks` after
141
+ setting one. It works on `preview_creative_draft` and `create_creative`/`update_creative`; changing
142
+ the headline or layout of a saved Creative clears pinned sizes unless you send them again.
96
143
 
97
144
  ### 7. Look at the result honestly
98
145
 
99
- **A successful render proves the frame is structurally correct, not that the ad is good.** Its
100
- in-page checks catch text spilling out of a safe zone, a font that failed to load, a photo that
146
+ **Passing checks prove the frame is structurally correct, not that the ad is good.** The checks
147
+ (`passed: true`) catch text spilling out of a safe zone, a font that failed to load, a photo that
101
148
  never decoded, or contrast below the accessible minimum — not whether the headline reads at a
102
149
  glance, whether the accent phrase falls on the words that carry the meaning, or whether the copy is
103
- sitting on the subject's face. Judge those yourself from the image `preview_creative` hands back. If
104
- the copy collides with the photo, switch layout (see step 5) and re-render rather than shrinking
105
- text until it technically fits.
150
+ sitting on the subject's face. Judge those yourself from the image `preview_creative_draft` hands
151
+ back. If the copy collides with the photo, switch layout (see step 5) and preview again rather than
152
+ shrinking text until it technically fits.
153
+
154
+ Only once `passed` is true, show the image to the person and ask with `AskUserQuestion` —
155
+ **Approve & upload**, **Request changes**, **Stop**. If `passed` is false you are not at this step
156
+ yet: fix it and re-preview first (step 6). On changes, preview again with the edits (another draft; nothing was saved). On
157
+ Stop, leave it: the draft stays on this machine under `.conductor/drafts/` and is cleaned up
158
+ automatically once it is committed or two weeks old. Only on **Approve & upload**, call
159
+ `commit_creative_draft({draftDir})` with the `draftDir` the approved preview returned.
106
160
 
107
161
  ### 8. Check readiness before calling it done
108
162
 
109
- `get_creative` includes a `readiness` block: caption, alt text, the photo's checked/blocked state
163
+ The preview already lists the readiness gaps, so fix what you can (caption, alt text, a checked
164
+ photo) before asking for approval. After the commit, `get_creative` includes a `readiness` block: caption, alt text, the photo's checked/blocked state
110
165
  and provenance, and the Creative's own state (not `DRAFT`) are blocking; an AI-generation disclosure
111
166
  is informational only. Do not call a Creative done while `ready` is false.
112
167
 
113
- ### 9. Render for real, then hand off
168
+ ### 9. Commit, then hand off
114
169
 
115
- Once the preview looks right, `render_creative` again with `previewOnly: false` (or omitted) for the
116
- full-size placement frames, then `attach_creative_to_post` with the Post's Work Item id — it maps
117
- each placement to the right destinations (9:16 to TikTok/Reels, 4:5 to Instagram feed, 1:1 to
118
- Facebook, story to story targets) for every destination that has not set its own custom media, and
119
- never touches one that has. Verify with `get_post_status`. From here, use the `conductor-publisher`
120
- skill for scheduling, review and publishing — this skill's job ends at attaching the artwork.
170
+ `commit_creative_draft` uploads the draft's local files, creates the Creative (or updates
171
+ `baseCreativeId`'s), and runs the full-size render in one call, then returns the Creative id and
172
+ display id, the frames and the readiness. It can be run once per draft; if its render fails the
173
+ Creative is already saved — call `render_creative`, don't commit again. Verify with `get_creative`.
174
+ Then `attach_creative_to_post` with the Post's Work Item id — it maps each placement to the right
175
+ destinations (9:16 to TikTok/Reels, 4:5 to Instagram feed, 1:1 to Facebook, story to story targets)
176
+ for every destination that has not set its own custom media, and never touches one that has. Verify
177
+ with `get_post_status`. From here, use the `conductor-publisher` skill for scheduling, review and
178
+ publishing — this skill's job ends at attaching the artwork.
121
179
 
122
180
  ## Making a sequence instead of a single ad
123
181
 
124
182
  For a platform surface that's tapped or swiped through, set `sequenceKind` to `story` (2 to 7
125
- beats, aim for 3 to 5) or `carousel` (2 to 10 cards, 5 to 7 performs best) on `create_creative` or
126
- `update_creative`, with a `sequence` array. Each beat inherits the Creative and overrides only its
127
- own `headline`, and optionally `body`, `photoId` or `cta`. Structure a story hook, mechanism,
183
+ beats, aim for 3 to 5) or `carousel` (2 to 10 cards, 5 to 7 performs best) on
184
+ `preview_creative_draft` (then `commit_creative_draft`), with a `sequence` array. Each beat inherits
185
+ the Creative and overrides only its own `headline`, and optionally `body`, `photoPath` (a local
186
+ photo, uploaded on commit) or `photoId`, or `cta`. Structure a story hook, mechanism,
128
187
  payoff — the first beat has to earn the next tap on its own, since that's where the drop-off is
129
188
  worst. A logo lockup and CTA claim land on the last beat only by default; `cta: true` on an earlier
130
189
  beat overrides that. A carousel renders every card at one shared aspect ratio (`carouselRatio`),
@@ -136,6 +195,9 @@ Not every video needs the brand layout above — a finished clip (a demo recordi
136
195
  something a person or another tool already produced) can go up as-is. That's a CLIP Creative: no
137
196
  photo, headline or layout, just the video.
138
197
 
198
+ A CLIP has no artwork to render, so it is the one flow without a draft preview: open the video
199
+ itself and check it with the person before uploading.
200
+
139
201
  1. Check `list_creative_media({kind: "VIDEO"})` for a clip already in the project before uploading a
140
202
  new one. A new one goes through `upload_creative_media` (local path or URL) — same provenance
141
203
  fields as a photo (`source`, `licence`, `aiGenerated`). It detects video vs. audio vs. photo
@@ -162,10 +224,11 @@ of a static frame — no separate video tool needed. Use it when a still doesn't
162
224
  destination rewards video over an image.
163
225
 
164
226
  1. Pick a photo (step 5) or, for a moving background, a video — check `list_creative_media({kind:
165
- "VIDEO"})` first, then `upload_creative_media` only if nothing already fits — and note its media
166
- id. For background music, `list_creative_media({kind: "AUDIO"})` the same way before uploading a
167
- new track.
168
- 2. `create_creative`/`update_creative` with `kind: "MOTION"` plus:
227
+ "VIDEO"})` first and use its media id if something fits; otherwise pass the local file as
228
+ `clipPath` (previewed from disk, uploaded on commit). For background music,
229
+ `list_creative_media({kind: "AUDIO"})` the same way, else `audioPath`.
230
+ 2. `preview_creative_draft` with `kind: "MOTION"` (to change a saved MOTION Creative, `baseCreativeId`
231
+ instead) plus:
169
232
  - `motion.preset` — how the copy enters: `fade-up` (default), `word-by-word`, `accent-pop`, or
170
233
  `none` (everything visible from the first frame).
171
234
  - `motion.durationSec` — 3 to 60, default 8.
@@ -177,13 +240,13 @@ destination rewards video over an image.
177
240
  - `audio` — `source: "clip"` (the background clip's own recorded sound — the default when there
178
241
  is one), `"track"` (a separate library track: `trackId` from `upload_creative_media`, plus
179
242
  `volume` and `fadeOutSec`), or `"none"`.
180
- 3. `render_creative` runs a local, ffmpeg-driven encode — roughly 20 seconds per placement for an
181
- 8-second video. No ffmpeg on this machine fails with a clear message naming how to get one rather
182
- than silently producing nothing.
183
- 4. `preview_creative` renders three key moments across the timeline into one sheet image (not the
184
- finished video). Judge it the same honest way as step 7 above — does the copy's entrance finish
185
- before the next beat, does the end card actually hold long enough to read — before spending a
186
- full render on it.
243
+ 3. The preview renders three key moments across the timeline into one sheet image (not the finished
244
+ video). Judge it the same honest way as step 7 above — does the copy's entrance finish before the
245
+ next beat, does the end card actually hold long enough to read — and get approval before
246
+ spending a full render on it.
247
+ 4. `commit_creative_draft` then uploads the media, saves the Creative and runs the local,
248
+ ffmpeg-driven encode — roughly 20 seconds per placement for an 8-second video. No ffmpeg on this
249
+ machine fails with a clear message naming how to get one rather than silently producing nothing.
187
250
  5. `attach_creative_to_post`, same as any Creative — one MP4 per placement.
188
251
 
189
252
  A library music track's `licence` has to be recorded on upload (same as a photo's provenance) — the
@@ -203,6 +266,9 @@ Automated checks are real but incomplete. These are the ones worth remembering t
203
266
  - **A Brand Kit with no copy rules, or no accent-phrase requirement, never fails a save on that
204
267
  account.** That is permissive by design, not a bug — but it means the model, not the platform, is
205
268
  the only check on brand voice for that workspace until someone configures rules.
269
+ - **A draft is not a saved Creative.** `preview_creative_draft` writes nothing to Conductor, so a
270
+ Creative or media id does not exist until `commit_creative_draft` runs — don't hand a draft's
271
+ folder to `attach_creative_to_post` or a Post.
206
272
  - **A stale write is refused, not silently overwritten.** `update_creative` requires the Creative's
207
273
  current `version`; a mismatch comes back as a 409 naming the conflict rather than clobbering a
208
274
  concurrent edit — reread with `get_creative` and reapply, don't retry blind.
@@ -0,0 +1,41 @@
1
+ # Brand Kit check and setup
2
+
3
+ Shared by `/conductor:creative` and the `conductor-creative` skill: check whether this workspace's
4
+ Brand Kit has been set up, and offer to set it up if not. The text below is the command's Step 1,
5
+ unchanged.
6
+
7
+ ## Step 1 — Check the brand
8
+
9
+ Call `get_brand_kit` (pass `kitId` only if the person already named a specific brand).
10
+
11
+ **If `configured` is `false`**, this workspace has no brand set up yet — offer to fix that before
12
+ making anything:
13
+
14
+ ```json
15
+ {
16
+ "questions": [{
17
+ "question": "This workspace's Brand Kit hasn't been set up yet (default colours, no font, no logo). Set it up now?",
18
+ "header": "Brand Kit",
19
+ "options": [
20
+ {"label": "From a website", "description": "Fetch a URL and infer colours, voice and name"},
21
+ {"label": "From a brand doc", "description": "Read a local file (guidelines, style doc)"},
22
+ {"label": "Answer a few questions", "description": "Name, colours, font, CTA line, approved lines"},
23
+ {"label": "Skip for now", "description": "Proceed with the default kit as-is"}
24
+ ],
25
+ "multiSelect": false
26
+ }]
27
+ }
28
+ ```
29
+
30
+ - **From a website**: ask for the URL, `WebFetch` it, infer a plausible accent colour, name, CTA
31
+ claim and a couple of approved lines from the copy on the page. Show what you inferred and confirm
32
+ before writing.
33
+ - **From a brand doc**: ask for the file path, `Read` it, extract the same fields, confirm before
34
+ writing.
35
+ - **Answer a few questions**: ask for name, primary/accent colour (hex or a description you convert),
36
+ font (if any), the CTA claim, and any lines that must run verbatim. Keep it to one AskUserQuestion
37
+ batch, not a long interview.
38
+ - Once confirmed, call `update_brand_kit` with whatever was gathered. If a logo file or URL is
39
+ available, `upload_brand_image` for the `mark` slot at least. Verify with `get_brand_kit` —
40
+ `configured` should now read `true`.
41
+ - **Skip for now**: proceed; the skill below will render against the default kit as given.