@cliangdev/conductor 0.21.0 → 0.23.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 (74) hide show
  1. package/assets/claude/skills/conductor-creative/SKILL.md +168 -0
  2. package/assets/claude/skills/conductor-publisher/SKILL.md +4 -0
  3. package/assets/cli-manifest.json +99 -0
  4. package/dist/commands/creative.d.ts +3 -0
  5. package/dist/commands/creative.d.ts.map +1 -0
  6. package/dist/commands/creative.js +63 -0
  7. package/dist/commands/creative.js.map +1 -0
  8. package/dist/creative/assertions.js +221 -0
  9. package/dist/creative/font.js +39 -0
  10. package/dist/creative/frame.css +126 -0
  11. package/dist/creative/frame.html +29 -0
  12. package/dist/creative/frame.js +76 -0
  13. package/dist/creative/job/render.mjs +203 -0
  14. package/dist/creative/job/server.mjs +72 -0
  15. package/dist/creative/job/transport.mjs +167 -0
  16. package/dist/creative/layouts/bleed/layout.css +47 -0
  17. package/dist/creative/layouts/bleed/layout.json +17 -0
  18. package/dist/creative/layouts/card/layout.css +24 -0
  19. package/dist/creative/layouts/card/layout.json +17 -0
  20. package/dist/creative/layouts/index.js +62 -0
  21. package/dist/creative/layouts/index.json +1 -0
  22. package/dist/creative/layouts/split/layout.css +22 -0
  23. package/dist/creative/layouts/split/layout.json +17 -0
  24. package/dist/creative/layouts/stacked/layout.css +40 -0
  25. package/dist/creative/layouts/stacked/layout.json +23 -0
  26. package/dist/creative/placements.js +228 -0
  27. package/dist/creative/placements.json +268 -0
  28. package/dist/creative/render.js +457 -0
  29. package/dist/creative/sheet.html +27 -0
  30. package/dist/creative/sheet.js +94 -0
  31. package/dist/creative/styles.css +314 -0
  32. package/dist/creative/tokens.css +33 -0
  33. package/dist/index.js +2 -0
  34. package/dist/index.js.map +1 -1
  35. package/dist/lib/creative-browser.d.ts +25 -0
  36. package/dist/lib/creative-browser.d.ts.map +1 -0
  37. package/dist/lib/creative-browser.js +36 -0
  38. package/dist/lib/creative-browser.js.map +1 -0
  39. package/dist/lib/creative-id.d.ts +8 -0
  40. package/dist/lib/creative-id.d.ts.map +1 -0
  41. package/dist/lib/creative-id.js +14 -0
  42. package/dist/lib/creative-id.js.map +1 -0
  43. package/dist/lib/creative-render.d.ts +38 -0
  44. package/dist/lib/creative-render.d.ts.map +1 -0
  45. package/dist/lib/creative-render.js +95 -0
  46. package/dist/lib/creative-render.js.map +1 -0
  47. package/dist/lib/image-dimensions.d.ts +20 -0
  48. package/dist/lib/image-dimensions.d.ts.map +1 -0
  49. package/dist/lib/image-dimensions.js +115 -0
  50. package/dist/lib/image-dimensions.js.map +1 -0
  51. package/dist/lib/media-probe.d.ts +55 -0
  52. package/dist/lib/media-probe.d.ts.map +1 -0
  53. package/dist/lib/media-probe.js +221 -0
  54. package/dist/lib/media-probe.js.map +1 -0
  55. package/dist/lib/plugin-assets.d.ts.map +1 -1
  56. package/dist/lib/plugin-assets.js +1 -0
  57. package/dist/lib/plugin-assets.js.map +1 -1
  58. package/dist/lib/poster-sheet.d.ts +10 -0
  59. package/dist/lib/poster-sheet.d.ts.map +1 -0
  60. package/dist/lib/poster-sheet.js +57 -0
  61. package/dist/lib/poster-sheet.js.map +1 -0
  62. package/dist/mcp/index.d.ts +18 -0
  63. package/dist/mcp/index.d.ts.map +1 -1
  64. package/dist/mcp/index.js +313 -1
  65. package/dist/mcp/index.js.map +1 -1
  66. package/dist/mcp/tools/creatives.d.ts +114 -0
  67. package/dist/mcp/tools/creatives.d.ts.map +1 -0
  68. package/dist/mcp/tools/creatives.js +477 -0
  69. package/dist/mcp/tools/creatives.js.map +1 -0
  70. package/dist/mcp/tools/insights.d.ts.map +1 -1
  71. package/dist/mcp/tools/insights.js +14 -0
  72. package/dist/mcp/tools/insights.js.map +1 -1
  73. package/package.json +6 -3
  74. package/scripts/build-creative.mjs +93 -0
@@ -0,0 +1,314 @@
1
+ /* styles.css — the single stylesheet a consumer needs: tokens.css + frame.css
2
+ * + every layouts/<name>/layout.css, concatenated in registry order so a
3
+ * layout can rely on frame.css cascading first. This file is generated by
4
+ * hand-concatenation (no build step); if you edit tokens.css, frame.css, or
5
+ * a layout.css, re-run the same concatenation (or copy the change into both
6
+ * the source file and here) and let test/registry.test.mjs catch drift on
7
+ * the individual files it checks. A consumer that only ever wants one board
8
+ * on the page can import this single file; components/layouts.js callers
9
+ * that want to import files individually still can (see README).
10
+ */
11
+
12
+ /* ── from tokens.css ── */
13
+ /* tokens.css — neutral fallback tokens for the creative artboard.
14
+ *
15
+ * This package renders no brand: every color and font here is a plain,
16
+ * unbranded default so a board with no brand tokens applied still renders
17
+ * something legible. The real values come from a workspace's Brand Kit,
18
+ * applied at render time as inline custom properties on the board's own root
19
+ * element (see render.js's applyBrandTokens / mount.js's mountBoard) — never
20
+ * written here and never on `:root`, so two different kits can render on the
21
+ * same page (e.g. two creatives side by side) without colliding.
22
+ *
23
+ * Scoped to .cc-board so it cannot collide with the host app's own styles.
24
+ * All custom properties are prefixed --cc- for the same reason.
25
+ */
26
+
27
+ .cc-board {
28
+ --cc-accent: #6B7280; /* neutral accent; a kit's `tokens.accent` overrides this */
29
+ --cc-accent-2: #4B5563; /* a kit's `tokens.accent2` overrides this */
30
+
31
+ --cc-d-bg: #1C1C1E; /* dark artboard background and scrim end stop */
32
+ --cc-d-ink: #FFFFFF; /* headline on dark */
33
+
34
+ --cc-l-bg: #F2F2F7; /* light artboard background */
35
+ --cc-l-card: #FFFFFF; /* light-ground card/chip background */
36
+ --cc-ink: #222222; /* headline on light */
37
+ --cc-ink-2: #717171; /* body on light */
38
+
39
+ --cc-sky: #4AADDB; /* safe-zone review guide only, never in an export */
40
+
41
+ --cc-elev-overlay: 0 8px 24px rgba(0,0,0,0.12);
42
+ --cc-elev-high: 0 8px 32px rgba(0,0,0,0.10);
43
+
44
+ --cc-font: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
45
+ }
46
+
47
+ /* ── from frame.css ── */
48
+ /* frame.css — the shared creative artboard rules: everything a layout does
49
+ * not own. Ratios and layouts are both data, not hardcoded here.
50
+ *
51
+ * Placements (artboard sizes and per-placement numbers: lockup, body, cta,
52
+ * card padding) live in placements.json / placements.js. render.js reads
53
+ * them and sets CSS custom properties on the board (--cc-board-w,
54
+ * --cc-lockup-gap, --cc-body-size, ...).
55
+ *
56
+ * Layouts (structural families: stacked, bleed, card, split, and any added
57
+ * later) live in layouts/<name>/layout.json + layout.css. This file keeps
58
+ * only what every layout shares: the artboard, the lockup, the headline,
59
+ * the body, the cta row, sequence anchoring, and the safe-zone review
60
+ * guide.
61
+ *
62
+ * NOTHING structural here may be position:absolute. A PNG exporter that
63
+ * screenshots this DOM (the future render job) can drop absolutely
64
+ * positioned layers, so every photo and scrim stays in normal flow: photos
65
+ * are painted as background-image on the artboard or a band, scrims are
66
+ * background-image on the text panel.
67
+ *
68
+ * This file has no @import and no font-face: fonts are brand data (a kit's
69
+ * `fontUrl`), loaded by mount.js's loadFont(), never bundled here.
70
+ */
71
+
72
+ /* ── Artboard ─────────────────────────────────────────────────────────────── */
73
+ .cc-board {
74
+ position: relative;
75
+ overflow: hidden;
76
+ box-sizing: border-box;
77
+ font-family: var(--cc-font);
78
+ background-color: var(--cc-d-bg);
79
+ -webkit-font-smoothing: antialiased;
80
+ flex: none;
81
+ display: flex;
82
+ flex-direction: column;
83
+ width: var(--cc-board-w);
84
+ height: var(--cc-board-h);
85
+ }
86
+ .cc-board--light { background-color: var(--cc-l-bg); }
87
+
88
+ .cc-board__panel {
89
+ position: relative; /* keeps the panel above the glow */
90
+ box-sizing: border-box;
91
+ display: flex; flex-direction: column; align-items: flex-start;
92
+ }
93
+
94
+ /* ── Sequence frames (stories and carousels) ──────────────────────────────── */
95
+ /* Anchored to the bottom on every frame so the eye does not have to re-find the
96
+ copy between taps or swipes. The CTA appears only on the last frame. */
97
+ .cc-board--seq .cc-lockup { position: absolute; top: var(--cc-safe-top, 48px); left: 88px; }
98
+
99
+ /* ── Lockup ───────────────────────────────────────────────────────────────── */
100
+ /* Renders only when the brand kit has a mark and/or a wordmark image; a kit
101
+ with neither renders no lockup at all (see render.js's buildLockup). */
102
+ .cc-lockup { display: inline-flex; align-items: center; flex: none; align-self: flex-start; gap: var(--cc-lockup-gap); }
103
+ .cc-lockup__icon { display: block; width: var(--cc-icon-size); height: var(--cc-icon-size); border-radius: var(--cc-icon-radius); }
104
+ .cc-lockup__mark { display: block; height: var(--cc-mark-height); }
105
+ .cc-lockup--spacer { visibility: hidden; }
106
+
107
+ /* Chip variant: a white pill behind the lockup, for busy photography (ported
108
+ from nexus's schema.json `lockup: "chip"` / render.js). `--cc-l-card` and
109
+ `--cc-elev-overlay` are tokens.css's neutral light-ground surface and
110
+ shadow — brand-agnostic, so a white pill works over any brand's photo. */
111
+ .cc-lockup--chip {
112
+ border-radius: 999px;
113
+ background: var(--cc-l-card);
114
+ box-shadow: var(--cc-elev-overlay);
115
+ padding: var(--cc-chip-pad);
116
+ gap: var(--cc-chip-gap);
117
+ }
118
+ .cc-lockup--chip .cc-lockup__icon {
119
+ width: var(--cc-chip-icon, var(--cc-icon-size));
120
+ height: var(--cc-chip-icon, var(--cc-icon-size));
121
+ border-radius: var(--cc-chip-icon-radius, var(--cc-icon-radius));
122
+ }
123
+ .cc-lockup--chip .cc-lockup__mark { height: var(--cc-chip-mark, var(--cc-mark-height)); }
124
+
125
+ /* ── Headline ─────────────────────────────────────────────────────────────── */
126
+ .cc-headline {
127
+ margin: 0;
128
+ font-size: var(--cc-h-size);
129
+ line-height: var(--cc-h-leading, 1.04);
130
+ font-weight: 800;
131
+ letter-spacing: var(--cc-h-track, -2.3px);
132
+ color: var(--cc-d-ink);
133
+ text-wrap: balance;
134
+ }
135
+ .cc-board--light .cc-headline { color: var(--cc-ink); }
136
+ .cc-headline em { font-style: italic; color: var(--cc-accent); }
137
+
138
+ /* ── Body ─────────────────────────────────────────────────────────────────── */
139
+ .cc-body {
140
+ margin: 0;
141
+ font-weight: 500;
142
+ text-wrap: pretty;
143
+ font-size: var(--cc-body-size);
144
+ line-height: var(--cc-body-lh);
145
+ max-width: var(--cc-body-max-w);
146
+ color: var(--cc-body-color);
147
+ }
148
+ .cc-board--light .cc-body { font-size: var(--cc-light-body-size, var(--cc-body-size)); color: var(--cc-ink-2); }
149
+
150
+ /* ── CTA row ──────────────────────────────────────────────────────────────── */
151
+ /* Renders only when the kit has a badge image and/or a ctaClaim string; a kit
152
+ with neither renders no cta row at all (see render.js's buildCta). */
153
+ .cc-cta { display: flex; align-items: center; flex-wrap: wrap; flex: none; gap: var(--cc-cta-gap); margin-top: var(--cc-cta-margin-top, 0); }
154
+
155
+ /* The badge is brand artwork (a kit's `logos.badge`): sized by height only in
156
+ render.js, its own intrinsic aspect ratio preserved. Nothing here may
157
+ restyle it — no border, no border-radius, no filter, no shadow, no overlay —
158
+ since a store badge (Apple's, Google's, or a kit's own) typically comes with
159
+ guidelines against exactly that. */
160
+ .cc-badge { display: block; flex: none; width: auto; }
161
+
162
+ .cc-cta__note { font-weight: 600; font-size: var(--cc-note-size); color: var(--cc-note-color); }
163
+ .cc-board--light .cc-cta__note { color: var(--cc-ink-2); }
164
+
165
+ /* ── Safe zones (review guide only, never exported) ───────────────────────── */
166
+ .cc-safe { position: absolute; inset: 0; pointer-events: none; }
167
+ .cc-safe__top { position: absolute; top: 0; left: 0; right: 0; height: var(--cc-safe-top);
168
+ background: rgba(74,173,219,0.28); border-bottom: 2px dashed var(--cc-sky); }
169
+ .cc-safe__bottom { position: absolute; bottom: 0; left: 0; right: 0; height: var(--cc-safe-bottom);
170
+ background: rgba(74,173,219,0.28); border-top: 2px dashed var(--cc-sky); }
171
+ .cc-safe__right { position: absolute; bottom: var(--cc-safe-bottom); top: var(--cc-safe-top); right: 0;
172
+ width: var(--cc-safe-right);
173
+ background: rgba(74,173,219,0.20); border-left: 2px dashed var(--cc-sky); }
174
+
175
+ /* ── from layouts/stacked/layout.css ── */
176
+ /* stacked — photo band pinned top over a solid text panel. */
177
+
178
+ .cc-board__band {
179
+ position: relative;
180
+ flex: 0 0 auto;
181
+ height: var(--cc-band-h);
182
+ overflow: hidden;
183
+ margin-bottom: -2px; /* hides a subpixel seam against the panel below */
184
+ }
185
+ .cc-board__band img {
186
+ position: absolute; inset: 0;
187
+ width: 100%; height: 100%;
188
+ object-fit: cover;
189
+ object-position: var(--cc-focal, 50% 30%);
190
+ }
191
+ .cc-board__scrim {
192
+ position: absolute; left: 0; right: 0; bottom: 0; height: 220px;
193
+ background: linear-gradient(to top,
194
+ var(--cc-d-bg) 0%, var(--cc-d-bg) 18%,
195
+ rgba(28,28,30,0.75) 42%, rgba(28,28,30,0.25) 72%, rgba(28,28,30,0) 100%);
196
+ }
197
+ .cc-board__glow {
198
+ position: absolute; right: -200px; bottom: -260px;
199
+ width: 720px; height: 720px; border-radius: 50%;
200
+ background: radial-gradient(circle, color-mix(in srgb, var(--cc-accent) 13%, transparent), transparent 68%);
201
+ pointer-events: none;
202
+ }
203
+
204
+ .cc-board--stacked .cc-board__panel { flex: 1 1 auto; min-height: 0; }
205
+ .cc-r-9x16.cc-board--stacked .cc-board__panel,
206
+ .cc-r-story.cc-board--stacked .cc-board__panel {
207
+ padding: 88px 88px var(--cc-pad-b, var(--cc-safe-bottom, 430px));
208
+ justify-content: space-between;
209
+ gap: 26px;
210
+ }
211
+ .cc-r-4x5.cc-board--stacked .cc-board__panel {
212
+ padding: 0 88px 88px;
213
+ justify-content: center;
214
+ gap: 20px;
215
+ }
216
+
217
+ /* ── from layouts/bleed/layout.css ── */
218
+ /* bleed — photo covers the whole artboard, text sits over a scrim. */
219
+
220
+ .cc-board--bleed {
221
+ background-image: var(--cc-photo);
222
+ background-size: cover;
223
+ background-position: var(--cc-focal, 50% 50%);
224
+ justify-content: flex-end;
225
+ }
226
+
227
+ /* Fill variant (9x16, 4x5, story): the scrim covers the artboard and the
228
+ copy is distributed from top to bottom over it. */
229
+ .cc-board__panel--fill {
230
+ flex: 1 1 auto;
231
+ height: 100%;
232
+ justify-content: space-between;
233
+ }
234
+ .cc-r-9x16 .cc-board__panel--fill,
235
+ .cc-r-story .cc-board__panel--fill {
236
+ padding: 48px 88px var(--cc-safe-bottom, 430px);
237
+ background-image: linear-gradient(to top,
238
+ rgba(0,0,0,0.94) 0%, rgba(0,0,0,0.88) 42%,
239
+ rgba(0,0,0,0.68) 62%, rgba(0,0,0,0.34) 80%, rgba(0,0,0,0.55) 100%);
240
+ }
241
+ .cc-r-4x5 .cc-board__panel--fill {
242
+ padding: 48px 88px 96px;
243
+ background-image: linear-gradient(to top,
244
+ rgba(0,0,0,0.94) 0%, rgba(0,0,0,0.88) 48%,
245
+ rgba(0,0,0,0.62) 70%, rgba(0,0,0,0.30) 86%, rgba(0,0,0,0.50) 100%);
246
+ }
247
+ /* The chip's own padding already reads as a visual left inset over a full-bleed photo; nudge it back
248
+ flush with the panel's other content (ported from nexus's bleed layout.css). */
249
+ .cc-board__panel--fill .cc-lockup--chip { margin-left: -40px; }
250
+
251
+ /* Sequence frames (stories and carousels): anchored bottom so the eye does
252
+ not have to re-find the copy between taps or swipes. */
253
+ .cc-board--seq .cc-board__panel--fill { justify-content: flex-end; gap: 28px; }
254
+ .cc-board--story.cc-board--bleed .cc-board__panel--fill { padding-top: var(--cc-safe-top, 250px); }
255
+
256
+ /* Bottom variant (every 1x1, forced via perPlacement). */
257
+ .cc-board__panel--bottom {
258
+ flex: 0 0 auto;
259
+ padding: 220px 72px 48px;
260
+ gap: 14px;
261
+ background-image: linear-gradient(to top,
262
+ rgba(28,28,30,0.97) 0%, rgba(28,28,30,0.95) 42%,
263
+ rgba(28,28,30,0.74) 66%, rgba(28,28,30,0.26) 88%, rgba(28,28,30,0) 100%);
264
+ }
265
+
266
+ /* ── from layouts/card/layout.css ── */
267
+ /* card — the light layout: inset rounded photo card on a light page. */
268
+
269
+ .cc-board--card { padding: var(--cc-card-pad); gap: var(--cc-card-gap); }
270
+
271
+ .cc-board__card {
272
+ position: relative;
273
+ width: 100%;
274
+ height: var(--cc-band-h);
275
+ flex: 0 0 auto;
276
+ border-radius: 24px;
277
+ overflow: hidden;
278
+ box-shadow: var(--cc-elev-high);
279
+ }
280
+ .cc-board__card img {
281
+ position: absolute; inset: 0;
282
+ width: 100%; height: 100%;
283
+ object-fit: cover;
284
+ object-position: var(--cc-focal, 50% 40%);
285
+ }
286
+ .cc-board--card .cc-board__panel {
287
+ flex: 1 1 auto; min-height: 0;
288
+ justify-content: center;
289
+ gap: 28px;
290
+ }
291
+
292
+ /* ── from layouts/split/layout.css ── */
293
+ /* split — photo on the top half, plain panel below, no scrim. Proves a fourth
294
+ layout needs only this folder plus an entry in layouts/index.json. */
295
+
296
+ .cc-board--split .cc-board__band {
297
+ position: relative;
298
+ flex: 0 0 auto;
299
+ height: var(--cc-band-h);
300
+ overflow: hidden;
301
+ }
302
+ .cc-board--split .cc-board__band img {
303
+ position: absolute; inset: 0;
304
+ width: 100%; height: 100%;
305
+ object-fit: cover;
306
+ object-position: var(--cc-focal, 50% 30%);
307
+ }
308
+ .cc-board--split .cc-board__panel {
309
+ flex: 1 1 auto; min-height: 0;
310
+ padding: 72px 88px;
311
+ justify-content: center;
312
+ gap: 24px;
313
+ background-color: var(--cc-d-bg);
314
+ }
@@ -0,0 +1,33 @@
1
+ /* tokens.css — neutral fallback tokens for the creative artboard.
2
+ *
3
+ * This package renders no brand: every color and font here is a plain,
4
+ * unbranded default so a board with no brand tokens applied still renders
5
+ * something legible. The real values come from a workspace's Brand Kit,
6
+ * applied at render time as inline custom properties on the board's own root
7
+ * element (see render.js's applyBrandTokens / mount.js's mountBoard) — never
8
+ * written here and never on `:root`, so two different kits can render on the
9
+ * same page (e.g. two creatives side by side) without colliding.
10
+ *
11
+ * Scoped to .cc-board so it cannot collide with the host app's own styles.
12
+ * All custom properties are prefixed --cc- for the same reason.
13
+ */
14
+
15
+ .cc-board {
16
+ --cc-accent: #6B7280; /* neutral accent; a kit's `tokens.accent` overrides this */
17
+ --cc-accent-2: #4B5563; /* a kit's `tokens.accent2` overrides this */
18
+
19
+ --cc-d-bg: #1C1C1E; /* dark artboard background and scrim end stop */
20
+ --cc-d-ink: #FFFFFF; /* headline on dark */
21
+
22
+ --cc-l-bg: #F2F2F7; /* light artboard background */
23
+ --cc-l-card: #FFFFFF; /* light-ground card/chip background */
24
+ --cc-ink: #222222; /* headline on light */
25
+ --cc-ink-2: #717171; /* body on light */
26
+
27
+ --cc-sky: #4AADDB; /* safe-zone review guide only, never in an export */
28
+
29
+ --cc-elev-overlay: 0 8px 24px rgba(0,0,0,0.12);
30
+ --cc-elev-high: 0 8px 32px rgba(0,0,0,0.10);
31
+
32
+ --cc-font: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
33
+ }
package/dist/index.js CHANGED
@@ -14,6 +14,7 @@ import { registerConfig } from './commands/config.js';
14
14
  import { registerDashboard } from './commands/dashboard.js';
15
15
  import { registerStatus } from './commands/status.js';
16
16
  import { registerLint } from './commands/lint.js';
17
+ import { registerCreative } from './commands/creative.js';
17
18
  const program = new Command();
18
19
  program
19
20
  .name('conductor')
@@ -30,5 +31,6 @@ registerConfig(program);
30
31
  registerDashboard(program);
31
32
  registerStatus(program);
32
33
  registerLint(program);
34
+ registerCreative(program);
33
35
  program.parse();
34
36
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAA;AACtC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAEnC,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AAC9C,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAA;AAC9C,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACjD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC/C,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAA;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAEjD,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAA;AAE7B,OAAO;KACJ,IAAI,CAAC,WAAW,CAAC;KACjB,WAAW,CAAC,qDAAqD,CAAC;KAClE,OAAO,CAAC,OAAO,CAAC,CAAA;AAEnB,aAAa,CAAC,OAAO,CAAC,CAAA;AACtB,cAAc,CAAC,OAAO,CAAC,CAAA;AACvB,YAAY,CAAC,OAAO,CAAC,CAAA;AACrB,cAAc,CAAC,OAAO,CAAC,CAAA;AACvB,aAAa,CAAC,OAAO,CAAC,CAAA;AACtB,YAAY,CAAC,OAAO,CAAC,CAAA;AACrB,WAAW,CAAC,OAAO,CAAC,CAAA;AACpB,cAAc,CAAC,OAAO,CAAC,CAAA;AACvB,iBAAiB,CAAC,OAAO,CAAC,CAAA;AAC1B,cAAc,CAAC,OAAO,CAAC,CAAA;AACvB,YAAY,CAAC,OAAO,CAAC,CAAA;AAErB,OAAO,CAAC,KAAK,EAAE,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,aAAa,EAAE,MAAM,QAAQ,CAAA;AACtC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAEnC,MAAM,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AAC9C,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAA;AAC9C,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACjD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC/C,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAA;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAA;AACrD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AAEzD,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAA;AAE7B,OAAO;KACJ,IAAI,CAAC,WAAW,CAAC;KACjB,WAAW,CAAC,qDAAqD,CAAC;KAClE,OAAO,CAAC,OAAO,CAAC,CAAA;AAEnB,aAAa,CAAC,OAAO,CAAC,CAAA;AACtB,cAAc,CAAC,OAAO,CAAC,CAAA;AACvB,YAAY,CAAC,OAAO,CAAC,CAAA;AACrB,cAAc,CAAC,OAAO,CAAC,CAAA;AACvB,aAAa,CAAC,OAAO,CAAC,CAAA;AACtB,YAAY,CAAC,OAAO,CAAC,CAAA;AACrB,WAAW,CAAC,OAAO,CAAC,CAAA;AACpB,cAAc,CAAC,OAAO,CAAC,CAAA;AACvB,iBAAiB,CAAC,OAAO,CAAC,CAAA;AAC1B,cAAc,CAAC,OAAO,CAAC,CAAA;AACvB,YAAY,CAAC,OAAO,CAAC,CAAA;AACrB,gBAAgB,CAAC,OAAO,CAAC,CAAA;AAEzB,OAAO,CAAC,KAAK,EAAE,CAAA"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Finds a locally-installed browser to render Creatives with, using `playwright-core` (a dependency
3
+ * of this package, not the browser-bundling `playwright`). `conductor-creative/job/render.mjs`'s own
4
+ * CLI bootstrap depends on full `playwright` (it ships its own Chromium download) because it's meant
5
+ * to run inside a container built for that purpose; the CLI/MCP server run on a user's own machine,
6
+ * so they discover whatever Chromium-family browser is already there instead of bundling one — the
7
+ * same shape nexus-marketing's `rexipe-social` MCP server used.
8
+ *
9
+ * `playwright-core` itself is lazy-imported (see `findBrowserFactory`) so requiring it doesn't add
10
+ * to MCP server startup cost for sessions that never call a creative tool.
11
+ */
12
+ import type { Browser } from 'playwright-core';
13
+ export type BrowserFactory = () => Promise<Browser>;
14
+ export declare const CHROME_INSTALL_HINT: string;
15
+ /**
16
+ * Returns a factory that, when the render core first asks for a browser, launches the first one it can
17
+ * find in the order above. Launching is deferred to that call on purpose: `run()` fetches the spec before
18
+ * it asks for a browser, and a browser launched ahead of a spec fetch that then fails is never closed —
19
+ * it keeps the CLI process alive and leaks a Chrome process from the MCP server.
20
+ *
21
+ * The factory throws `CHROME_INSTALL_HINT` plus what each attempt failed with when nothing launches;
22
+ * `run()` catches that and reports it as the render's failure.
23
+ */
24
+ export declare function findBrowserFactory(log?: (...args: unknown[]) => void): Promise<BrowserFactory>;
25
+ //# sourceMappingURL=creative-browser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"creative-browser.d.ts","sourceRoot":"","sources":["../../src/lib/creative-browser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,OAAO,EAAiB,MAAM,iBAAiB,CAAA;AAE7D,MAAM,MAAM,cAAc,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,CAAA;AAEnD,eAAO,MAAM,mBAAmB,QAI4C,CAAA;AAa5E;;;;;;;;GAQG;AACH,wBAAsB,kBAAkB,CAAC,GAAG,GAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAe,GAAG,OAAO,CAAC,cAAc,CAAC,CAe9G"}
@@ -0,0 +1,36 @@
1
+ export const CHROME_INSTALL_HINT = 'No installed browser could be launched for creative rendering. Try one of:\n' +
2
+ ' - Google Chrome (launched via Playwright channel "chrome")\n' +
3
+ ' - Microsoft Edge (launched via Playwright channel "msedge")\n' +
4
+ ' - a Playwright-managed Chromium: run `npx playwright install chromium`';
5
+ const ATTEMPTS = [
6
+ { label: 'system Chrome (channel: chrome)', options: { channel: 'chrome' } },
7
+ { label: 'system Edge (channel: msedge)', options: { channel: 'msedge' } },
8
+ { label: 'Playwright-managed Chromium', options: {} },
9
+ ];
10
+ /**
11
+ * Returns a factory that, when the render core first asks for a browser, launches the first one it can
12
+ * find in the order above. Launching is deferred to that call on purpose: `run()` fetches the spec before
13
+ * it asks for a browser, and a browser launched ahead of a spec fetch that then fails is never closed —
14
+ * it keeps the CLI process alive and leaks a Chrome process from the MCP server.
15
+ *
16
+ * The factory throws `CHROME_INSTALL_HINT` plus what each attempt failed with when nothing launches;
17
+ * `run()` catches that and reports it as the render's failure.
18
+ */
19
+ export async function findBrowserFactory(log = () => { }) {
20
+ const { chromium } = await import('playwright-core');
21
+ return async () => {
22
+ const errors = [];
23
+ for (const attempt of ATTEMPTS) {
24
+ try {
25
+ const browser = await chromium.launch(attempt.options);
26
+ log(`using ${attempt.label}`);
27
+ return browser;
28
+ }
29
+ catch (err) {
30
+ errors.push(`${attempt.label}: ${err instanceof Error ? err.message : String(err)}`);
31
+ }
32
+ }
33
+ throw new Error(`${CHROME_INSTALL_HINT}\n\nTried:\n${errors.map((e) => ` - ${e}`).join('\n')}`);
34
+ };
35
+ }
36
+ //# sourceMappingURL=creative-browser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"creative-browser.js","sourceRoot":"","sources":["../../src/lib/creative-browser.ts"],"names":[],"mappings":"AAeA,MAAM,CAAC,MAAM,mBAAmB,GAC9B,8EAA8E;IAC9E,gEAAgE;IAChE,iEAAiE;IACjE,0EAA0E,CAAA;AAO5E,MAAM,QAAQ,GAAc;IAC1B,EAAE,KAAK,EAAE,iCAAiC,EAAE,OAAO,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE;IAC5E,EAAE,KAAK,EAAE,+BAA+B,EAAE,OAAO,EAAE,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE;IAC1E,EAAE,KAAK,EAAE,6BAA6B,EAAE,OAAO,EAAE,EAAE,EAAE;CACtD,CAAA;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,MAAoC,GAAG,EAAE,GAAE,CAAC;IACnF,MAAM,EAAE,QAAQ,EAAE,GAAG,MAAM,MAAM,CAAC,iBAAiB,CAAC,CAAA;IACpD,OAAO,KAAK,IAAI,EAAE;QAChB,MAAM,MAAM,GAAa,EAAE,CAAA;QAC3B,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;YAC/B,IAAI,CAAC;gBACH,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,CAAA;gBACtD,GAAG,CAAC,SAAS,OAAO,CAAC,KAAK,EAAE,CAAC,CAAA;gBAC7B,OAAO,OAAO,CAAA;YAChB,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,MAAM,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,KAAK,KAAK,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;YACtF,CAAC;QACH,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,GAAG,mBAAmB,eAAe,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IAClG,CAAC,CAAA;AACH,CAAC"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Creatives are shown to people by display id — "12a", "12b" — on the web page, in list_creatives and in
3
+ * the copyable "render this" commands, while the API addresses them by UUID. Tools and the CLI accept
4
+ * either and resolve a display id here, so a model or a person can pass what they were shown.
5
+ */
6
+ import type { Config } from '../mcp/config.js';
7
+ export declare function resolveCreativeId(idOrDisplayId: string, config: Config): Promise<string>;
8
+ //# sourceMappingURL=creative-id.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"creative-id.d.ts","sourceRoot":"","sources":["../../src/lib/creative-id.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AAM9C,wBAAsB,iBAAiB,CAAC,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAU9F"}
@@ -0,0 +1,14 @@
1
+ import { apiGet } from '../mcp/api.js';
2
+ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
3
+ const DISPLAY_ID = /^\d+[a-z]$/i;
4
+ export async function resolveCreativeId(idOrDisplayId, config) {
5
+ const value = idOrDisplayId.trim();
6
+ if (UUID.test(value) || !DISPLAY_ID.test(value))
7
+ return value;
8
+ const creatives = await apiGet(`/api/v2/projects/${config.projectId}/marketing/creatives`, config);
9
+ const match = creatives.find((c) => (c.displayId ?? '').toLowerCase() === value.toLowerCase());
10
+ if (!match)
11
+ throw new Error(`No Creative ${value} in this workspace — call list_creatives for the ids that exist.`);
12
+ return match.id;
13
+ }
14
+ //# sourceMappingURL=creative-id.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"creative-id.js","sourceRoot":"","sources":["../../src/lib/creative-id.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAA;AAEtC,MAAM,IAAI,GAAG,iEAAiE,CAAA;AAC9E,MAAM,UAAU,GAAG,aAAa,CAAA;AAEhC,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,aAAqB,EAAE,MAAc;IAC3E,MAAM,KAAK,GAAG,aAAa,CAAC,IAAI,EAAE,CAAA;IAClC,IAAI,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAA;IAC7D,MAAM,SAAS,GAAG,MAAM,MAAM,CAC5B,oBAAoB,MAAM,CAAC,SAAS,sBAAsB,EAC1D,MAAM,CACP,CAAA;IACD,MAAM,KAAK,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,KAAK,KAAK,CAAC,WAAW,EAAE,CAAC,CAAA;IAC9F,IAAI,CAAC,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,eAAe,KAAK,kEAAkE,CAAC,CAAA;IACnH,OAAO,KAAK,CAAC,EAAE,CAAA;AACjB,CAAC"}
@@ -0,0 +1,38 @@
1
+ import type { Config } from '../mcp/config.js';
2
+ export interface RenderFrame {
3
+ id?: string;
4
+ placementKey: string;
5
+ platform?: string;
6
+ sequenceIndex?: number | null;
7
+ url?: string;
8
+ width?: number;
9
+ height?: number;
10
+ sizeBytes?: number;
11
+ warnings?: string[];
12
+ /** Video-frame fields — only present on a CLIP creative's frames, which reference a video, not a JPEG. */
13
+ durationSeconds?: number;
14
+ hasAudio?: boolean;
15
+ posterUrl?: string;
16
+ contentType?: string;
17
+ }
18
+ export interface RenderCreativeParams {
19
+ creativeId: string;
20
+ previewOnly?: boolean;
21
+ renderer?: string;
22
+ workflowRunId?: string;
23
+ }
24
+ export interface RenderCreativeResult {
25
+ ok: boolean;
26
+ renderId?: string;
27
+ state?: string;
28
+ error?: string | null;
29
+ frames: RenderFrame[];
30
+ }
31
+ /**
32
+ * Renders a Creative end to end. STILL creatives find a local browser and run the render core against
33
+ * the external API, reading back the stored render (frames with signed URLs, final state) whether or
34
+ * not the render itself succeeded — a FAILED render still has an id and an error message worth
35
+ * returning. CLIP (and MOTION) creatives never launch a browser — see {@link renderViaApi}.
36
+ */
37
+ export declare function renderCreative(params: RenderCreativeParams, config: Config, log?: (...args: unknown[]) => void): Promise<RenderCreativeResult>;
38
+ //# sourceMappingURL=creative-render.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"creative-render.d.ts","sourceRoot":"","sources":["../../src/lib/creative-render.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAA;AAI9C,MAAM,WAAW,WAAW;IAC1B,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,YAAY,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;IACnB,0GAA0G;IAC1G,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,WAAW,oBAAoB;IACnC,UAAU,EAAE,MAAM,CAAA;IAClB,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,OAAO,CAAA;IACX,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,MAAM,EAAE,WAAW,EAAE,CAAA;CACtB;AA+ED;;;;;GAKG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,oBAAoB,EAC5B,MAAM,EAAE,MAAM,EACd,GAAG,GAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAe,GAC3C,OAAO,CAAC,oBAAoB,CAAC,CAyC/B"}
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Runs a Creative render locally: loads the render core copied into `dist/creative/` at build time
3
+ * (see `scripts/build-creative.mjs`), drives it against the external v2 creatives API with this
4
+ * project's own API key, and reads back the stored result. Shared by the `conductor creative render`
5
+ * CLI command and the `render_creative` MCP tool so the two never drift.
6
+ *
7
+ * The render core itself (`conductor-creative/job/render.mjs`'s `run()`) knows nothing about how it's
8
+ * launched or what browser it gets — that's `job/transport.mjs`'s and this file's job respectively.
9
+ */
10
+ import { existsSync } from 'node:fs';
11
+ import { dirname, join } from 'node:path';
12
+ import { fileURLToPath } from 'node:url';
13
+ import { apiGet, apiPost, ApiError } from '../mcp/api.js';
14
+ import { findBrowserFactory } from './creative-browser.js';
15
+ /** Where `npm run build` copies `conductor-creative/`'s runtime files, relative to this file's own
16
+ * compiled location (`dist/lib/creative-render.js` -> `dist/creative/job/`). */
17
+ function jobDir() {
18
+ const here = dirname(fileURLToPath(import.meta.url));
19
+ return join(here, '..', 'creative', 'job');
20
+ }
21
+ async function loadRenderCore() {
22
+ const dir = jobDir();
23
+ const renderPath = join(dir, 'render.mjs');
24
+ const transportPath = join(dir, 'transport.mjs');
25
+ if (!existsSync(renderPath) || !existsSync(transportPath)) {
26
+ throw new Error(`Creative render core not found under ${dir}. This is a packaging bug in @cliangdev/conductor ` +
27
+ '(the build step that copies conductor-creative/ into dist/creative/ did not run) — ' +
28
+ 'reinstall the CLI, or in this repo run `npm run build`.');
29
+ }
30
+ const [renderMod, transportMod] = await Promise.all([
31
+ import(renderPath),
32
+ import(transportPath),
33
+ ]);
34
+ return { run: renderMod.run, createApiTransport: transportMod.createApiTransport };
35
+ }
36
+ /**
37
+ * A CLIP (or, once PR 2 lands, MOTION) creative has no local browser render path — the backend either
38
+ * assembles the frames itself synchronously (CLIP) or refuses with a 422 explaining why (MOTION, until
39
+ * its renderer ships). Either way this just POSTs the render and relays what came back — `ApiError`'s
40
+ * `message` is already the server's own plain-English sentence (an RFC 7807 `detail`), so a MOTION 422
41
+ * reaches the caller unchanged rather than wrapped in a generic "never started" message.
42
+ */
43
+ async function renderViaApi(params, config) {
44
+ try {
45
+ const detail = await apiPost(`/api/v2/projects/${config.projectId}/marketing/creatives/${params.creativeId}/renders`, { previewOnly: !!params.previewOnly, renderer: params.renderer, workflowRunId: params.workflowRunId }, config);
46
+ return { ok: detail.state === 'SUCCEEDED', renderId: detail.id, state: detail.state, error: detail.error ?? null, frames: detail.frames ?? [] };
47
+ }
48
+ catch (err) {
49
+ if (err instanceof ApiError) {
50
+ return { ok: false, error: err.message, frames: [] };
51
+ }
52
+ throw err;
53
+ }
54
+ }
55
+ /**
56
+ * Renders a Creative end to end. STILL creatives find a local browser and run the render core against
57
+ * the external API, reading back the stored render (frames with signed URLs, final state) whether or
58
+ * not the render itself succeeded — a FAILED render still has an id and an error message worth
59
+ * returning. CLIP (and MOTION) creatives never launch a browser — see {@link renderViaApi}.
60
+ */
61
+ export async function renderCreative(params, config, log = () => { }) {
62
+ const creative = await apiGet(`/api/v2/projects/${config.projectId}/marketing/creatives/${params.creativeId}`, config);
63
+ if (creative.kind && creative.kind !== 'STILL') {
64
+ return renderViaApi(params, config);
65
+ }
66
+ const { run, createApiTransport } = await loadRenderCore();
67
+ const transport = createApiTransport({
68
+ apiUrl: config.apiUrl,
69
+ apiKey: config.apiKey,
70
+ projectId: config.projectId,
71
+ creativeId: params.creativeId,
72
+ previewOnly: !!params.previewOnly,
73
+ renderer: params.renderer,
74
+ workflowRunId: params.workflowRunId,
75
+ });
76
+ const browserFactory = await findBrowserFactory(log);
77
+ const ok = await run({ transport, browserFactory, log });
78
+ const renderId = transport.getRenderId();
79
+ if (!renderId) {
80
+ return { ok: false, error: 'The render was never started — the API rejected the request before a render id was assigned.', frames: [] };
81
+ }
82
+ try {
83
+ const detail = await apiGet(`/api/v2/projects/${config.projectId}/marketing/creatives/${params.creativeId}/renders/${renderId}`, config);
84
+ return { ok, renderId, state: detail.state, error: detail.error ?? null, frames: detail.frames ?? [] };
85
+ }
86
+ catch (err) {
87
+ return {
88
+ ok,
89
+ renderId,
90
+ error: `Render finished (${ok ? 'succeeded' : 'failed'}) but reading the result back failed: ${err instanceof Error ? err.message : String(err)}`,
91
+ frames: [],
92
+ };
93
+ }
94
+ }
95
+ //# sourceMappingURL=creative-render.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"creative-render.js","sourceRoot":"","sources":["../../src/lib/creative-render.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Reads an image's format and pixel dimensions straight from its file header — no decode, no
3
+ * external image library. Used by `upload_creative_photo`, which needs width/height and content
4
+ * type before minting an upload (the backend's `CreateCreativePhotoRequest` requires both, since
5
+ * Conductor has no server-side image pipeline and WebP in particular cannot be probed there).
6
+ *
7
+ * Covers exactly the three formats the Creative photo API accepts (image/png, image/jpeg,
8
+ * image/webp). Returns `null` for anything else or a header that doesn't parse — callers should
9
+ * treat that as "not a usable photo" rather than guessing.
10
+ */
11
+ export type CreativePhotoContentType = 'image/png' | 'image/jpeg' | 'image/webp';
12
+ export interface ImageInfo {
13
+ contentType: CreativePhotoContentType;
14
+ width: number;
15
+ height: number;
16
+ }
17
+ /** Sniffs the format from magic bytes and parses width/height from the header. `null` when the
18
+ * bytes aren't a recognizable PNG, JPEG or WebP, or the header is truncated/malformed. */
19
+ export declare function readImageDimensions(bytes: Uint8Array): ImageInfo | null;
20
+ //# sourceMappingURL=image-dimensions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image-dimensions.d.ts","sourceRoot":"","sources":["../../src/lib/image-dimensions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,MAAM,MAAM,wBAAwB,GAAG,WAAW,GAAG,YAAY,GAAG,YAAY,CAAA;AAEhF,MAAM,WAAW,SAAS;IACxB,WAAW,EAAE,wBAAwB,CAAA;IACrC,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;CACf;AAuGD;0FAC0F;AAC1F,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,UAAU,GAAG,SAAS,GAAG,IAAI,CAEvE"}