@cliangdev/conductor 0.27.0 → 0.28.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 (52) hide show
  1. package/assets/claude/agents/conductor-content-art-director.md +16 -4
  2. package/assets/claude/agents/conductor-content-scriptwriter.md +4 -0
  3. package/assets/claude/commands/conductor/creative.md +2 -1
  4. package/assets/claude/skills/conductor-content-studio/SKILL.md +39 -17
  5. package/assets/claude/skills/conductor-content-studio/references/capabilities.md +11 -2
  6. package/assets/claude/skills/conductor-content-studio/references/compliance-checklist.md +1 -0
  7. package/assets/claude/skills/conductor-content-studio/references/idea-template.md +6 -1
  8. package/assets/claude/skills/conductor-content-studio/references/publish-pack-template.md +3 -1
  9. package/assets/claude/skills/conductor-content-studio/references/script-template.md +1 -1
  10. package/assets/claude/skills/conductor-creative/SKILL.md +37 -8
  11. package/assets/cli-manifest.json +8 -0
  12. package/dist/commands/doctor.d.ts +5 -0
  13. package/dist/commands/doctor.d.ts.map +1 -1
  14. package/dist/commands/doctor.js +20 -0
  15. package/dist/commands/doctor.js.map +1 -1
  16. package/dist/creative/assertions.js +57 -7
  17. package/dist/creative/font.js +29 -0
  18. package/dist/creative/frame.css +2 -0
  19. package/dist/creative/frame.js +7 -4
  20. package/dist/creative/layouts/bleed/layout.css +14 -7
  21. package/dist/creative/motion.js +61 -19
  22. package/dist/creative/render.js +21 -2
  23. package/dist/creative/sheet.js +7 -2
  24. package/dist/creative/styles.css +16 -7
  25. package/dist/lib/codex-image.d.ts +51 -0
  26. package/dist/lib/codex-image.d.ts.map +1 -0
  27. package/dist/lib/codex-image.js +329 -0
  28. package/dist/lib/codex-image.js.map +1 -0
  29. package/dist/lib/media-probe.d.ts +4 -0
  30. package/dist/lib/media-probe.d.ts.map +1 -1
  31. package/dist/lib/media-probe.js +5 -2
  32. package/dist/lib/media-probe.js.map +1 -1
  33. package/dist/lib/provenance-sidecar.d.ts +19 -0
  34. package/dist/lib/provenance-sidecar.d.ts.map +1 -0
  35. package/dist/lib/provenance-sidecar.js +39 -0
  36. package/dist/lib/provenance-sidecar.js.map +1 -0
  37. package/dist/mcp/index.d.ts +531 -0
  38. package/dist/mcp/index.d.ts.map +1 -1
  39. package/dist/mcp/index.js +34 -2
  40. package/dist/mcp/index.js.map +1 -1
  41. package/dist/mcp/tools/creative-drafts.d.ts +12 -0
  42. package/dist/mcp/tools/creative-drafts.d.ts.map +1 -1
  43. package/dist/mcp/tools/creative-drafts.js +36 -7
  44. package/dist/mcp/tools/creative-drafts.js.map +1 -1
  45. package/dist/mcp/tools/creative-images.d.ts +39 -0
  46. package/dist/mcp/tools/creative-images.d.ts.map +1 -0
  47. package/dist/mcp/tools/creative-images.js +89 -0
  48. package/dist/mcp/tools/creative-images.js.map +1 -0
  49. package/dist/mcp/tools/creatives.d.ts.map +1 -1
  50. package/dist/mcp/tools/creatives.js +7 -0
  51. package/dist/mcp/tools/creatives.js.map +1 -1
  52. package/package.json +1 -1
@@ -87,6 +87,8 @@
87
87
  }
88
88
  .cc-board--light .cc-headline { color: var(--cc-ink); }
89
89
  .cc-headline em { font-style: italic; color: var(--cc-accent); }
90
+ .cc-headline em.cc-accent-after-space { margin-left: 0.07em; }
91
+ .cc-headline em.cc-accent-before-space { margin-right: 0.04em; }
90
92
  /* Word spans the motion presets animate: inline-block so transforms apply; the spaces between them stay
91
93
  * plain text, so lines wrap exactly as they do without the spans. */
92
94
  .cc-word { display: inline-block; }
@@ -37,8 +37,8 @@
37
37
  import { resolveAd, resolveSequence, renderBoard, fitBoard } from './render.js';
38
38
  import { placements as DEFAULT_PLACEMENTS } from './placements.js';
39
39
  import { layouts as DEFAULT_LAYOUTS } from './layouts/index.js';
40
- import { ensureBrandFont } from './font.js';
41
- import { runAssertions } from './assertions.js';
40
+ import { ensureBrandFont, settleImages } from './font.js';
41
+ import { runAssertions, expectedElements } from './assertions.js';
42
42
  import { applyMotion, seekBackgroundVideo } from './motion.js';
43
43
 
44
44
  function finish(result) {
@@ -83,10 +83,13 @@ async function main() {
83
83
 
84
84
  await document.fonts.ready;
85
85
  await Promise.all([...document.images].map((img) => (img.decode ? img.decode().catch(() => {}) : null)));
86
+ await settleImages(board); // also the bleed photo layer, a CSS background `document.images` does not list
86
87
  // Motion presets restructure the headline (word spans): build that structure, in its final state,
87
88
  // before fitting, so the type is sized for the layout every captured frame will have.
88
89
  if (spec.motion) applyMotion(board, spec.motion, spec.motion.durationSec || 8, { durationSec: spec.motion.durationSec || 8 });
89
90
  fitBoard(board, placementsReg);
91
+ // What this spec requires on the board: a required element that did not render fails the checks.
92
+ const expect = expectedElements(ad, spec.brand);
90
93
 
91
94
  if (spec.motion) {
92
95
  const durationSec = spec.motion.durationSec || 8;
@@ -102,13 +105,13 @@ async function main() {
102
105
  return true;
103
106
  };
104
107
  // A motion frame is captured at 1x (video), so the photo only needs to cover the placement itself.
105
- window.__assertBoard = async () => runAssertions(board, placement, spec.brand || {}, { outputScale: 1 });
108
+ window.__assertBoard = async () => runAssertions(board, placement, spec.brand || {}, { outputScale: 1, expect });
106
109
 
107
110
  finish({ ok: true, width: placement.w, height: placement.h });
108
111
  return;
109
112
  }
110
113
 
111
- const { errors, warnings, checks, width, height } = await runAssertions(board, placement, spec.brand || {});
114
+ const { errors, warnings, checks, width, height } = await runAssertions(board, placement, spec.brand || {}, { expect });
112
115
  finish({ ok: errors.length === 0, errors, warnings, checks, width, height });
113
116
  } catch (err) {
114
117
  finish({ ok: false, error: String((err && err.message) || err) });
@@ -1,16 +1,23 @@
1
1
  /* bleed — photo covers the whole artboard, text sits over a scrim. */
2
2
 
3
3
  .cc-board--bleed {
4
- background-image: var(--cc-photo);
5
- /* motion.js's Ken Burns pan/zoom (a MOTION creative only) sets --cc-bg-size/--cc-bg-pos on the
6
- board; a background-image has no `transform`, so the zoom/pan is expressed as a growing
7
- background-size and a shifting background-position instead. Unset for STILL: falls back to the
8
- original fixed cover/focal. */
9
- background-size: var(--cc-bg-size, cover);
10
- background-position: var(--cc-bg-pos, var(--cc-focal, 50% 50%));
11
4
  justify-content: flex-end;
12
5
  }
13
6
 
7
+ /* The photo: a real layer under the copy (render.js adds it first, so plain DOM order keeps the panel and
8
+ any clip video above it). `cover` + `no-repeat` mean it can never tile or leave a strip uncovered;
9
+ the focal point is its background-position. motion.js's Ken Burns pan/zoom (a MOTION creative only)
10
+ is a transform on this layer, kept inside the overhang a scale >= 1 gives it; unset for STILL. */
11
+ .cc-bg-photo {
12
+ position: absolute; inset: 0;
13
+ background-image: var(--cc-photo);
14
+ background-repeat: no-repeat;
15
+ background-size: cover;
16
+ background-position: var(--cc-focal, 50% 50%);
17
+ transform: var(--cc-bg-transform, none);
18
+ transform-origin: center center;
19
+ }
20
+
14
21
  /* Fill variant (9x16, 4x5, story): the scrim covers the artboard and the
15
22
  copy is distributed from top to bottom over it. */
16
23
  .cc-board__panel--fill {
@@ -136,6 +136,45 @@ function wrapWordsInPlace(container) {
136
136
  container.appendChild(frag);
137
137
  }
138
138
 
139
+ /* ── accent-pop: how far a word may grow without eating the spaces around it ─────────────────────
140
+ * A scaled inline-block keeps its layout box, so growing it moves its ink into the neighbouring spaces:
141
+ * at 1.18x a long accent word swallowed the space before it ("slightdelay") for the first half second.
142
+ * The pop is therefore limited so each side grows by at most POP_GAP_EM of the font size (two accent words share one space, so each takes only a sliver of it), and only on a
143
+ * side that HAS a space (a word glued to punctuation, like "delay" in "delay.", does not grow towards it);
144
+ * the origin is placed so the growth lands on the open sides. Layout-less environments (a DOM shim)
145
+ * report no width and get the plain, uncapped pop. */
146
+
147
+ const POP_PEAK = 1.18;
148
+ const POP_GAP_EM = 0.04;
149
+
150
+ function hasGap(word, direction) {
151
+ const prop = direction < 0 ? 'previousSibling' : 'nextSibling';
152
+ let node = word[prop];
153
+ // The first/last word of the accent <em> neighbours whatever sits beside the <em> itself.
154
+ if (!node && word.parentNode && word.parentNode.tagName === 'EM') node = word.parentNode[prop];
155
+ if (!node) return true; // a line edge: nothing to run into
156
+ if (node.nodeType !== 3) return false;
157
+ return direction < 0 ? /\s$/.test(node.textContent) : /^\s/.test(node.textContent);
158
+ }
159
+
160
+ /** Pure-ish (reads the word's measured width and font size): `{ peak, originX }` — the largest pop scale
161
+ * that keeps the word clear of its neighbours, and the transform-origin x (percent) that puts the growth
162
+ * on the sides that have a space. */
163
+ function popLimits(word) {
164
+ const width = word.offsetWidth;
165
+ const view = word.ownerDocument && word.ownerDocument.defaultView;
166
+ const size = view && view.getComputedStyle ? parseFloat(view.getComputedStyle(word).fontSize) : NaN;
167
+ if (!(width > 0) || !(size > 0)) return { peak: POP_PEAK, originX: 50 };
168
+ const budget = POP_GAP_EM * size;
169
+ const left = hasGap(word, -1) ? budget : 0;
170
+ const right = hasGap(word, 1) ? budget : 0;
171
+ if (left + right === 0) return { peak: 1, originX: 50 };
172
+ return {
173
+ peak: Math.min(POP_PEAK, 1 + (left + right) / width),
174
+ originX: round2((100 * left) / (left + right)),
175
+ };
176
+ }
177
+
139
178
  /* ── headline (preset-dependent) ──────────────────────────────────────────── */
140
179
 
141
180
  function applyHeadline(board, preset, tSec, inEndCard) {
@@ -189,11 +228,12 @@ function applyHeadline(board, preset, tSec, inEndCard) {
189
228
  const fadeProgress = windowProgress(tSec, ACCENT_POP_FADE_WINDOW[0], ACCENT_POP_FADE_WINDOW[1]);
190
229
  setFade(headline, fadeProgress, null);
191
230
  const popProgress = windowProgress(tSec, ACCENT_POP_POP_WINDOW[0], ACCENT_POP_POP_WINDOW[1]);
192
- // Scale settles 1.18 → 1; brightness pulses up and back to 1, so the end card starts with no jump.
193
- const scale = round2(lerp(1.18, 1, popProgress));
231
+ // Scale settles peak → 1; brightness pulses up and back to 1, so the end card starts with no jump.
194
232
  const brightness = round2(1 + 0.3 * Math.sin(Math.PI * popProgress));
195
233
  accentWords.forEach((w) => {
196
- w.style.transformOrigin = '50% 70%';
234
+ const { peak, originX } = popLimits(w);
235
+ const scale = round2(lerp(peak, 1, popProgress));
236
+ w.style.transformOrigin = `${originX}% 70%`;
197
237
  w.style.transform = scale === 1 ? 'none' : `scale(${scale})`;
198
238
  w.style.filter = brightness === 1 ? 'none' : `brightness(${brightness})`;
199
239
  });
@@ -243,26 +283,28 @@ export function backgroundMotionState(motion, tSec, durationSec) {
243
283
  return { scale: 1, panPct: 0 }; // 'none'
244
284
  }
245
285
 
246
- /* "50% 30%" + a pan percent -> "54% 30%": keeps the focal point's own Y, only
247
- * the X drifts (a Ken Burns pan is always horizontal here). */
248
- function panBackgroundPosition(focal, panPct) {
249
- const parts = String(focal || '50% 50%').trim().split(/\s+/);
250
- const y = parts[1] || '50%';
251
- return `${round2(50 + panPct)}% ${y}`;
286
+ /** Pure: the rect (relative to the board/band/card it fills) the background layer occupies once
287
+ * `backgroundMotionState`'s transform is applied to a layer that exactly fills a `w` x `h` box. The
288
+ * transform is `translateX(panPct%) scale(scale)` (translate first, so the pan is in the BOX's own
289
+ * pixels, not the scaled layer's): a scale >= 1 overhangs each side by (scale - 1) / 2, and the pan is
290
+ * kept inside that overhang, so the layer covers the box at every `t`. Exported for tests. */
291
+ export function backgroundLayerRect(w, h, state) {
292
+ const { scale, panPct } = state;
293
+ const width = w * scale;
294
+ const height = h * scale;
295
+ const left = (w - width) / 2 + (panPct / 100) * w;
296
+ const top = (h - height) / 2;
297
+ return { left, top, right: left + width, bottom: top + height, width, height };
252
298
  }
253
299
 
254
300
  function applyBackgroundMotion(board, motion, tSec, durationSec) {
255
301
  const { scale, panPct } = backgroundMotionState(motion, tSec, durationSec);
256
- // Consumed two ways, both with a no-op fallback so a STILL board (which
257
- // never calls applyMotion at all) is untouched:
258
- // - `.cc-board__band img` / `.cc-board__card img` / `.cc-bg-video`: a real
259
- // transform (band/card layouts, and any layout's clip background video).
260
- // - `.cc-board--bleed`'s own CSS `background-image`: background-size/
261
- // -position, since `transform` does not apply to a background image.
262
- board.style.setProperty('--cc-bg-transform', `scale(${scale}) translateX(${panPct}%)`);
263
- board.style.setProperty('--cc-bg-size', `${round2(scale * 100)}%`);
264
- const focal = board.style.getPropertyValue('--cc-focal') || '50% 50%';
265
- board.style.setProperty('--cc-bg-pos', panBackgroundPosition(focal, panPct));
302
+ // One CSS var, consumed by every layout's photo layer, with a no-op fallback so a STILL board (which
303
+ // never calls applyMotion at all) is untouched: `.cc-bg-photo` (bleed's photo layer),
304
+ // `.cc-board__band img` / `.cc-board__card img` and `.cc-bg-video`. The layer is `cover`-sized, never
305
+ // scaled below 1 and never panned past its overhang, so no edge or repeat can show (see
306
+ // backgroundLayerRect). The focal point stays the layer's own object-/background-position.
307
+ board.style.setProperty('--cc-bg-transform', `translateX(${panPct}%) scale(${scale})`);
266
308
  }
267
309
 
268
310
  /* ── the public entry point ───────────────────────────────────────────────── */
@@ -159,7 +159,11 @@ export function resolveSequence(raw, placements, layouts) {
159
159
  if (!raw.sequenceKind || !Array.isArray(seq) || !seq.length) return [];
160
160
  const last = seq.length - 1;
161
161
  return seq.map((frame, i) => {
162
- const merged = resolveAd({ ...raw, ...frame, sequence: undefined, sequenceKind: undefined }, placements, layouts);
162
+ // A beat inherits the creative for anything it leaves out, and the API sends an unset beat field as
163
+ // null: a null must not overwrite the creative's own value (a beat with `photoUrl: null` rendered
164
+ // with no photo at all).
165
+ const own = Object.fromEntries(Object.entries(frame).filter(([, v]) => v != null));
166
+ const merged = resolveAd({ ...raw, ...own, sequence: undefined, sequenceKind: undefined }, placements, layouts);
163
167
  // null and undefined both mean "not set" (the API sends null): the CTA then lands on the last frame only.
164
168
  merged.showCta = frame.cta != null ? frame.cta : i === last;
165
169
  // Body is per-frame opt-in: a hook frame can be a headline and nothing else.
@@ -260,7 +264,13 @@ function buildHeadline(ad, parent) {
260
264
  return h;
261
265
  }
262
266
  if (m[1]) h.appendChild(document.createTextNode(m[1]));
263
- el('em', null, h).textContent = m[2];
267
+ const em = el('em', null, h);
268
+ em.textContent = m[2];
269
+ // Italic correction: a slanted accent leans into the upright word before it (and its last letter
270
+ // overhangs the word after), so a plain word space reads as no space at all ("slightdelay").
271
+ // Widen only the gaps that are real spaces, never one against glued punctuation.
272
+ if (/\s$/.test(m[1])) em.classList.add('cc-accent-after-space');
273
+ if (/^\s/.test(m[3])) em.classList.add('cc-accent-before-space');
264
274
  if (m[3]) h.appendChild(document.createTextNode(m[3]));
265
275
  return h;
266
276
  }
@@ -405,6 +415,7 @@ export function renderBoard(ad, placementKey, placements, layouts, brand, opts)
405
415
  (ad.isSequence ? ' cc-board--seq' : '') +
406
416
  (ad.isStory ? ' cc-board--story' : ''));
407
417
  board.dataset.ratio = placementKey;
418
+ board.dataset.layout = kind;
408
419
 
409
420
  applyBrandTokens(board, brand);
410
421
  board.style.setProperty('--cc-board-w', placement.w + 'px');
@@ -435,6 +446,11 @@ export function renderBoard(ad, placementKey, placements, layouts, brand, opts)
435
446
  let panel;
436
447
  if (photoType === 'background') {
437
448
  board.style.setProperty('--cc-photo', ad.photoUrl ? 'url("' + ad.photoUrl + '")' : 'none');
449
+ // The photo is its own layer, not the board's background: a MOTION creative's Ken Burns zoom/pan is a
450
+ // transform on this layer, which always covers the board (cover, no-repeat) whatever the zoom. A
451
+ // background-size percentage on the board did not: it was relative to the photo's WIDTH, so a
452
+ // landscape photo on a tall board ended up shorter than the board and tiled (see motion.js).
453
+ if (ad.photoUrl) el('div', 'cc-bg-photo', board).setAttribute('aria-hidden', 'true');
438
454
  // The video (when present) must land BEFORE the panel, so the panel's own
439
455
  // stacking (position:relative, later in the DOM) renders above it.
440
456
  if (ad.backgroundVideoUrl) buildBgVideo(board, ad.backgroundVideoUrl);
@@ -474,6 +490,9 @@ export function renderBoard(ad, placementKey, placements, layouts, brand, opts)
474
490
  if (ad.body && !dropBody) {
475
491
  el('p', 'cc-body', panel).textContent = ad.body;
476
492
  }
493
+ // A layout may not have room for a body line at some placements (stacked at 4x5): keep that behaviour,
494
+ // but record it on the board so runAssertions can warn that supplied copy is not on the artwork.
495
+ if (ad.body && dropBody) board.dataset.bodyDropped = 'true';
477
496
  if (ad.showCta !== false) buildCta(brand, panel, placement);
478
497
 
479
498
  if (opts.safe && placement.safe.bottom) {
@@ -15,7 +15,7 @@
15
15
  * // motionKeyTimes()): one ROW per time, each row
16
16
  * // holding every placement in placementKeys at
17
17
  * // that time. Ignored for a sequence creative
18
- * // (MOTION creatives never carry a sequence).
18
+ * // (the backend refuses a MOTION creative with a sequence).
19
19
  * }
20
20
  *
21
21
  * Result contract: window.__RENDER_RESULT = { ok: true, width, height } |
@@ -25,7 +25,7 @@
25
25
  import { resolveAd, resolveSequence, renderBoard, fitAll } from './render.js';
26
26
  import { placements as DEFAULT_PLACEMENTS } from './placements.js';
27
27
  import { layouts as DEFAULT_LAYOUTS } from './layouts/index.js';
28
- import { ensureBrandFont } from './font.js';
28
+ import { ensureBrandFont, settleImages } from './font.js';
29
29
  import { applyMotion } from './motion.js';
30
30
 
31
31
  const SHEET_SCALE = 0.34;
@@ -105,6 +105,11 @@ async function main() {
105
105
  keys.forEach((key) => addCell(ad, key, (placementsReg[key] && placementsReg[key].label) || key));
106
106
  }
107
107
 
108
+ // Every photo and logo must have arrived before fitting (a logo's width shapes the layout) and
109
+ // before the job screenshots the sheet: a sheet captured mid-load showed boards with no photo and no
110
+ // logo, which read as a render that had dropped them.
111
+ await settleImages(sheet);
112
+
108
113
  // Fit every board at its TRUE, unscaled size first (a CSS transform
109
114
  // shrinks the measured rect and would throw fitBoard's line-count math
110
115
  // off), then apply the display scale — the same order mount.js uses.
@@ -134,6 +134,8 @@
134
134
  }
135
135
  .cc-board--light .cc-headline { color: var(--cc-ink); }
136
136
  .cc-headline em { font-style: italic; color: var(--cc-accent); }
137
+ .cc-headline em.cc-accent-after-space { margin-left: 0.07em; }
138
+ .cc-headline em.cc-accent-before-space { margin-right: 0.04em; }
137
139
  /* Word spans the motion presets animate: inline-block so transforms apply; the spaces between them stay
138
140
  * plain text, so lines wrap exactly as they do without the spans. */
139
141
  .cc-word { display: inline-block; }
@@ -245,16 +247,23 @@
245
247
  /* bleed — photo covers the whole artboard, text sits over a scrim. */
246
248
 
247
249
  .cc-board--bleed {
248
- background-image: var(--cc-photo);
249
- /* motion.js's Ken Burns pan/zoom (a MOTION creative only) sets --cc-bg-size/--cc-bg-pos on the
250
- board; a background-image has no `transform`, so the zoom/pan is expressed as a growing
251
- background-size and a shifting background-position instead. Unset for STILL: falls back to the
252
- original fixed cover/focal. */
253
- background-size: var(--cc-bg-size, cover);
254
- background-position: var(--cc-bg-pos, var(--cc-focal, 50% 50%));
255
250
  justify-content: flex-end;
256
251
  }
257
252
 
253
+ /* The photo: a real layer under the copy (render.js adds it first, so plain DOM order keeps the panel and
254
+ any clip video above it). `cover` + `no-repeat` mean it can never tile or leave a strip uncovered;
255
+ the focal point is its background-position. motion.js's Ken Burns pan/zoom (a MOTION creative only)
256
+ is a transform on this layer, kept inside the overhang a scale >= 1 gives it; unset for STILL. */
257
+ .cc-bg-photo {
258
+ position: absolute; inset: 0;
259
+ background-image: var(--cc-photo);
260
+ background-repeat: no-repeat;
261
+ background-size: cover;
262
+ background-position: var(--cc-focal, 50% 50%);
263
+ transform: var(--cc-bg-transform, none);
264
+ transform-origin: center center;
265
+ }
266
+
258
267
  /* Fill variant (9x16, 4x5, story): the scrim covers the artboard and the
259
268
  copy is distributed from top to bottom over it. */
260
269
  .cc-board__panel--fill {
@@ -0,0 +1,51 @@
1
+ export type ImageAspect = 'portrait' | 'square' | 'landscape';
2
+ export declare const ASPECT_SIZES: Record<ImageAspect, string>;
3
+ export declare const NO_CODEX_HINT = "Codex CLI not found \u2014 install it (npm i -g @openai/codex) and run `codex login`, or set CONDUCTOR_CODEX_PATH.";
4
+ export declare const NOT_LOGGED_IN_HINT = "Codex CLI is not logged in \u2014 run `codex login` (see `conductor doctor`).";
5
+ export interface GenerateImagesParams {
6
+ prompt: string;
7
+ aspect?: ImageAspect;
8
+ count?: number;
9
+ referenceImagePaths?: string[];
10
+ outDir: string;
11
+ onProgress?: (message: string) => void;
12
+ timeoutMs?: number;
13
+ }
14
+ export interface GeneratedImage {
15
+ path: string;
16
+ width: number;
17
+ height: number;
18
+ }
19
+ export interface GenerateImagesResult {
20
+ files: GeneratedImage[];
21
+ provenance: {
22
+ source: string;
23
+ licence: string;
24
+ aiGenerated: true;
25
+ };
26
+ revisedPrompt: string;
27
+ outDir: string;
28
+ /** JPEG (or small PNG) of the first image for inline display; absent when none fits ~1 MB. */
29
+ preview?: {
30
+ data: Buffer;
31
+ mimeType: string;
32
+ };
33
+ }
34
+ /** `CONDUCTOR_CODEX_PATH`, else `codex` on PATH. Cached for the process lifetime. */
35
+ export declare function resolveCodexPath(): Promise<string>;
36
+ /** Test-only: the resolved path is cached, so tests that vary availability reset it. */
37
+ export declare function resetCodexPathCache(): void;
38
+ export type CodexStatus = {
39
+ state: 'ready';
40
+ version: string;
41
+ } | {
42
+ state: 'logged-out';
43
+ version: string;
44
+ } | {
45
+ state: 'missing';
46
+ };
47
+ /** For `conductor doctor`: never throws. */
48
+ export declare function checkCodexCli(): Promise<CodexStatus>;
49
+ export declare function buildPrompt(scene: string, aspect: ImageAspect, count: number, hasReferences: boolean): string;
50
+ export declare function generateImages(params: GenerateImagesParams): Promise<GenerateImagesResult>;
51
+ //# sourceMappingURL=codex-image.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"codex-image.d.ts","sourceRoot":"","sources":["../../src/lib/codex-image.ts"],"names":[],"mappings":"AAkBA,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG,QAAQ,GAAG,WAAW,CAAA;AAE7D,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,WAAW,EAAE,MAAM,CAIpD,CAAA;AAED,eAAO,MAAM,aAAa,uHACuF,CAAA;AACjH,eAAO,MAAM,kBAAkB,kFAA6E,CAAA;AAe5G,MAAM,WAAW,oBAAoB;IACnC,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAA;IAC9B,MAAM,EAAE,MAAM,CAAA;IACd,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;IACtC,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,MAAM,CAAA;CACf;AAED,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,cAAc,EAAE,CAAA;IACvB,UAAU,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,IAAI,CAAA;KAAE,CAAA;IAClE,aAAa,EAAE,MAAM,CAAA;IACrB,MAAM,EAAE,MAAM,CAAA;IACd,8FAA8F;IAC9F,OAAO,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAA;CAC7C;AAID,qFAAqF;AACrF,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,MAAM,CAAC,CAQxD;AAED,wFAAwF;AACxF,wBAAgB,mBAAmB,IAAI,IAAI,CAE1C;AA4BD,MAAM,MAAM,WAAW,GACnB;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACnC;IAAE,KAAK,EAAE,YAAY,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACxC;IAAE,KAAK,EAAE,SAAS,CAAA;CAAE,CAAA;AAExB,4CAA4C;AAC5C,wBAAsB,aAAa,IAAI,OAAO,CAAC,WAAW,CAAC,CAoB1D;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,EAAE,aAAa,EAAE,OAAO,GAAG,MAAM,CAa7G;AA0ID,wBAAsB,cAAc,CAAC,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CA+EhG"}