@cueframe/kernel 0.2.22 → 0.2.23

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.
@@ -38,6 +38,19 @@ export type ToolResult = {
38
38
  structuredContent?: Record<string, unknown>;
39
39
  };
40
40
  export declare function text(s: string): ToolResult;
41
+ /**
42
+ * A JSON response, carried BOTH ways: the rendered prose a human reads, and the
43
+ * parsed object a program can consume.
44
+ *
45
+ * Curated tools used to return prose only, so `create_render` promised
46
+ * `{id,status,warnings?}` and delivered "Render queued.\n\nrenderId: …". A
47
+ * generic MCP consumer had to scrape it (CUE-15 P0). `structuredContent` is the
48
+ * protocol's channel for exactly this; the text block stays as display.
49
+ *
50
+ * Attached only for a non-null, non-array OBJECT, which is what the protocol
51
+ * allows — a bare array or scalar would be rejected by a validating client.
52
+ */
53
+ export declare function textWithData(s: string, data: unknown): ToolResult;
41
54
  export declare function errText(s: string): ToolResult;
42
55
  export declare function firstText(r: ToolResult): string;
43
56
  export declare function sniffImageMediaType(base64: string): string | undefined;
@@ -45,7 +58,9 @@ export declare function sniffAudioMediaType(base64: string): string | undefined;
45
58
  export declare function withMedia(payload: string, media: {
46
59
  data: string;
47
60
  kind: 'image' | 'audio';
48
- }[]): ToolResult;
61
+ }[],
62
+ /** The parsed response, carried alongside the media for programmatic callers. */
63
+ data?: unknown): ToolResult;
49
64
  export declare function fetchAsBase64(url: string, opts?: {
50
65
  timeoutMs?: number;
51
66
  maxBytes?: number;
@@ -312,7 +312,7 @@ Call wait_job(kind="compose", id="${j.composeJobId}", projectId="${a.projectId}"
312
312
  name: "apply_composition",
313
313
  operationId: "applyComposition",
314
314
  pathArgs: { projectId: "id" },
315
- description: 'Atomically apply an array of CompositionOps to a project composition. All ops succeed or none do (transactional). Pass `if_match` for optimistic-concurrency control (409 stale_etag on mismatch) and `dry_run: true` to validate the batch without persisting. Use this to seed a composition (add media clips) or make targeted edits (set a crop intent, reorder clips, set the format). CAPTIONS: attach content with {type:"captions.fromTranscript", mediaId, window?} \u2014 the server slices the cached word-timed transcript, rebases times, and sentence-splits into segments (no pasting words; 422 media_no_transcript if the media has no speech); style it with {type:"setCaptionStyle", style}; or set explicit words with {type:"setCaptions", segments, style?} (e.g. translations or your own VO timings). Brand is NOT set here \u2014 it lives on the project (create_project / PATCH /projects/:id with brandKitId). CARDS: a {kind:"card", html, tokens} clip projects each `tokens` KEY `x` to the CSS custom property `--cf-x`, so the html must read it as `var(--cf-x)` \u2014 write `tokens:{"plate":"#0af"}` + `background:var(--cf-plate)`. Do NOT put dashes in the key: `"--plate"` becomes `--cf---plate` and your `var(--plate)` then resolves to NOTHING, which CSS silently degrades to a transparent background and black text. Responses carry `warnings[]` when a card reads a var nothing defines. SCENE SHOTS: for a 3D product shot \u2014 a tilted page screenshot, a lit laptop/phone showing a real screenshot, two-colour type, a colour-field backdrop, focus/grain/vignette and a camera move \u2014 use {type:"scene.add", clipId, newTrack|trackId, startTime, duration, scene, assets} and revise it with {type:"scene.update", clipId, scene?, assets?, startTime?, duration?}. You author a scene SPEC, never wrapper TSX; the server registers the component for you. There is NO built-in scene primitive in list_catalog \u2014 READ cueframe://scene-shot first for the JSON schema, the canonical V2 world-objects recipe with nested groups, slabs, pages, phones, laptops, world/screen text, admitted surfaces, camera motion, lighting, and the current resource limits.',
315
+ description: 'Atomically apply an array of CompositionOps to a project composition. All ops succeed or none do (transactional). Pass `if_match` for optimistic-concurrency control (409 stale_etag on mismatch) and `dry_run: true` to validate the batch without persisting. Use this to seed a composition (add media clips) or make targeted edits (set a crop intent, reorder clips, set the format). CAPTIONS: attach content with {type:"captions.fromTranscript", mediaId, window?} \u2014 the server slices the cached word-timed transcript, rebases times, and sentence-splits into segments (no pasting words; 422 media_no_transcript if the media has no speech); style it with {type:"setCaptionStyle", style}; or set explicit words with {type:"setCaptions", segments, style?} (e.g. translations or your own VO timings). Brand is NOT set here \u2014 it lives on the project (create_project / update_project with brandKitId \u2014 or PATCH /projects/:id over REST). CARDS: a {kind:"card", html, tokens} clip projects each `tokens` KEY `x` to the CSS custom property `--cf-x`, so the html must read it as `var(--cf-x)` \u2014 write `tokens:{"plate":"#0af"}` + `background:var(--cf-plate)`. Do NOT put dashes in the key: `"--plate"` becomes `--cf---plate` and your `var(--plate)` then resolves to NOTHING, which CSS silently degrades to a transparent background and black text. Responses carry `warnings[]` when a card reads a var nothing defines. SCENE SHOTS: for a 3D product shot \u2014 a tilted page screenshot, a lit laptop/phone showing a real screenshot, two-colour type, a colour-field backdrop, focus/grain/vignette and a camera move \u2014 use {type:"scene.add", clipId, newTrack|trackId, startTime, duration, scene, assets} and revise it with {type:"scene.update", clipId, scene?, assets?, startTime?, duration?}. You author a scene SPEC, never wrapper TSX; the server registers the component for you. There is NO built-in scene primitive in list_catalog \u2014 READ cueframe://scene-shot first for the JSON schema, the canonical V2 world-objects recipe with nested groups, slabs, pages, phones, laptops, world/screen text, admitted surfaces, camera motion, lighting, and the current resource limits.',
316
316
  okFormat: (j, a) => {
317
317
  const warns = formatWarnings(j);
318
318
  if (j && typeof j.valid === "boolean") {
@@ -348,7 +348,7 @@ ${JSON.stringify(r.errors ?? [], null, 2)}${warns}`;
348
348
  name: "create_render",
349
349
  operationId: "createRender",
350
350
  pathArgs: { projectId: "id" },
351
- description: "Queue an MP4 render of a project's composition. Returns { id (renderId), status, warnings? } \u2014 `warnings[]` carries non-fatal advisories (omitted when none). The render themes from the project's brand kit (bind one via create_project / PATCH /projects/:id with brandKitId; without one the output is unbranded). Then call wait_job(kind=\"render\", id=renderId, projectId=...) to block until the file is ready and get the download URL, or register a webhook (create_webhook on render.completed \u2014 status rides the payload).",
351
+ description: "Queue an MP4 render of a project's composition. Returns { id (renderId), status, warnings? } \u2014 `warnings[]` carries non-fatal advisories (omitted when none). The render themes from the project's brand kit (bind one via create_project / update_project with brandKitId \u2014 or PATCH /projects/:id over REST; without one the output is unbranded). Then call wait_job(kind=\"render\", id=renderId, projectId=...) to block until the file is ready and get the download URL, or register a webhook (create_webhook on render.completed \u2014 status rides the payload).",
352
352
  okFormat: (j, a) => `Render queued.
353
353
 
354
354
  renderId: ${j.id}
@@ -552,7 +552,7 @@ Poll get_media_facts(mediaItemId="${a.mediaItemId}", kind="${a.body?.kind}", sta
552
552
  {
553
553
  name: "create_project",
554
554
  operationId: "createProject",
555
- description: "Create a new project \u2014 the unit you compose and render into. compose / apply_composition / create_render / the preview family all require a projectId; this mints one. Pass name (required) plus optionally a format (aspectRatio/fps/resolution) and brandKitId. brandKitId binds a brand kit to the PROJECT (pass the kitId from create_brand_kit): colors/fonts/motion theme every render of this project automatically (omit it and the output is unbranded). Bind or change it later via PATCH /projects/:id. Returns the ProjectResponse (id + metadata)."
555
+ description: "Create a new project \u2014 the unit you compose and render into. compose / apply_composition / create_render / the preview family all require a projectId; this mints one. Pass name (required) plus optionally a format (aspectRatio/fps/resolution) and brandKitId. brandKitId binds a brand kit to the PROJECT (pass the kitId from create_brand_kit): colors/fonts/motion theme every render of this project automatically (omit it and the output is unbranded). Bind or change it later via update_project (PATCH /projects/:id over REST). Returns the ProjectResponse (id + metadata)."
556
556
  },
557
557
  {
558
558
  name: "new_composition",
@@ -806,7 +806,7 @@ Render it with create_render(projectId="${a.projectId}", body={ compositionId: "
806
806
  {
807
807
  name: "create_brand_kit",
808
808
  operationId: "createBrandKit",
809
- description: "Create or UPSERT a brand kit (re-POSTing the same kitId updates it in place \u2014 this is also the brand-kit UPDATE path). Sets fonts/colors/logo. This is brand step 0 of the workflow: author the kit, then bind it to a PROJECT so colors/fonts/motion theme the output AUTOMATICALLY \u2014 pass its kitId as brandKitId to create_project, or attach it to an existing project via PATCH /projects/:id. Without a bound kit, create_render produces UNBRANDED output. Tenant-scoped (no project arg)."
809
+ description: "Create or UPSERT a brand kit (re-POSTing the same kitId updates it in place \u2014 this is also the brand-kit UPDATE path). Sets fonts/colors/logo. This is brand step 0 of the workflow: author the kit, then bind it to a PROJECT so colors/fonts/motion theme the output AUTOMATICALLY \u2014 pass its kitId as brandKitId to create_project, or attach it to an existing project via update_project (PATCH /projects/:id over REST). Without a bound kit, create_render produces UNBRANDED output. Tenant-scoped (no project arg)."
810
810
  },
811
811
  {
812
812
  name: "extract_brand_kit",
@@ -5,7 +5,7 @@ import {
5
5
  isOk,
6
6
  opById,
7
7
  safeParse
8
- } from "./chunk-URQQTAHZ.js";
8
+ } from "./chunk-RGKDDR7H.js";
9
9
 
10
10
  // ../mcp/src/wait.ts
11
11
  var defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
@@ -2,10 +2,10 @@ import {
2
2
  querySchemaShape,
3
3
  serializeQuery,
4
4
  waitForJob
5
- } from "./chunk-NG7DP46H.js";
5
+ } from "./chunk-EDR75OU7.js";
6
6
  import {
7
7
  CURATED_TOOLS
8
- } from "./chunk-DJWCD3RF.js";
8
+ } from "./chunk-3OOMVQ6I.js";
9
9
  import {
10
10
  GENERATED_OPERATIONS,
11
11
  apiErrorFrom,
@@ -21,8 +21,9 @@ import {
21
21
  safeParse,
22
22
  sniffImageMediaType,
23
23
  text,
24
+ textWithData,
24
25
  withMedia
25
- } from "./chunk-URQQTAHZ.js";
26
+ } from "./chunk-RGKDDR7H.js";
26
27
 
27
28
  // ../mcp/src/prompts.ts
28
29
  var skillListCache = null;
@@ -3616,7 +3617,7 @@ function registerResources(server, ex) {
3616
3617
  }),
3617
3618
  {
3618
3619
  title: "Component catalog",
3619
- description: "The component catalog: the FULL built-in primitive catalog (96 primitives + 7 scenes \u2014 lower-third, cinematic-title, color-grade, the scenes \u2026) PLUS your org-installed custom components. This is 'discover what to build with'. resources/list enumerates EVERY entry with its discovery facets (source/category/kind/tier/useCase) and a placement hint; read one for its full prop schema. NOT only your authored components. URI: cueframe://component/{componentId}.",
3620
+ description: "The component catalog: the FULL built-in primitive catalog (lower-third, cinematic-title, color-grade, the `scenes`-category templates \u2026) PLUS your org-installed custom components. This is 'discover what to build with'. resources/list enumerates EVERY entry with its discovery facets (source/category/kind/tier/useCase) and a placement hint; read one for its full prop schema. NOT only your authored components. NOTE the word 'scene' means two different things: the `scenes` CATEGORY here is composed built-in templates you place by primitiveId, whereas a SCENE SHOT (the scene.add op, cueframe://scene-shot) is a 3D spec you author and has no catalog entry at all. URI: cueframe://component/{componentId}.",
3620
3621
  mimeType: "application/json"
3621
3622
  },
3622
3623
  async (uri, { componentId }) => jsonResource(uri, await read(ex, "getComponent", () => String(componentId)))
@@ -3760,9 +3761,9 @@ function registerCuratedTools(server, ex, opts) {
3760
3761
  const urls = spec.preview.urls(json).filter(Boolean);
3761
3762
  const data = await fetchPreviews(urls, spec.preview.cap ?? 12);
3762
3763
  report({ preview: { urls: urls.length, fetched: data.length } });
3763
- if (data.length) return withMedia(payload, data.map((d) => ({ data: d, kind })));
3764
+ if (data.length) return withMedia(payload, data.map((d) => ({ data: d, kind })), json);
3764
3765
  }
3765
- return text(payload);
3766
+ return textWithData(payload, json);
3766
3767
  }, query);
3767
3768
  } catch (err) {
3768
3769
  return errText(`${spec.name} failed: ${fmtErr(err)}`);
@@ -9344,6 +9344,10 @@ function encodePaymentSignature(payload) {
9344
9344
  function text(s) {
9345
9345
  return { content: [{ type: "text", text: s }] };
9346
9346
  }
9347
+ function textWithData(s, data) {
9348
+ const structured = data !== null && typeof data === "object" && !Array.isArray(data) ? data : void 0;
9349
+ return structured ? { content: [{ type: "text", text: s }], structuredContent: structured } : { content: [{ type: "text", text: s }] };
9350
+ }
9347
9351
  function errText(s) {
9348
9352
  return { content: [{ type: "text", text: s }], isError: true };
9349
9353
  }
@@ -9400,7 +9404,7 @@ function sniffAudioMediaType(base64) {
9400
9404
  if (head.length >= 4 && head[0] === 255 && (head[1] & 224) === 224 && (head[1] >> 3 & 3) !== 1 && (head[1] >> 1 & 3) !== 0 && head[2] >> 4 > 0 && head[2] >> 4 < 15 && (head[2] >> 2 & 3) !== 3) return "audio/mpeg";
9401
9405
  return void 0;
9402
9406
  }
9403
- function withMedia(payload, media) {
9407
+ function withMedia(payload, media, data) {
9404
9408
  const blocks = [{ type: "text", text: payload }];
9405
9409
  for (const m of media) {
9406
9410
  if (!m.data) continue;
@@ -9411,7 +9415,8 @@ function withMedia(payload, media) {
9411
9415
  }
9412
9416
  blocks.push({ type: m.kind, data: m.data, mimeType });
9413
9417
  }
9414
- return { content: blocks };
9418
+ const structured = data !== null && typeof data === "object" && !Array.isArray(data) ? data : void 0;
9419
+ return structured ? { content: blocks, structuredContent: structured } : { content: blocks };
9415
9420
  }
9416
9421
  var MAX_PREVIEW_BYTES = 2 * 1024 * 1024;
9417
9422
  var MAX_PREVIEW_BYTES_TOTAL = 3 * 1024 * 1024;
@@ -9590,6 +9595,7 @@ async function dispatchOp(ex, op, mapArg, body, extra, label, formatOk, query) {
9590
9595
  export {
9591
9596
  GENERATED_OPERATIONS,
9592
9597
  text,
9598
+ textWithData,
9593
9599
  errText,
9594
9600
  firstText,
9595
9601
  sniffImageMediaType,
package/dist/compose.js CHANGED
@@ -5,9 +5,6 @@ import {
5
5
  import "./chunk-JQMPHKLU.js";
6
6
  import "./chunk-ZZRBKGDA.js";
7
7
  import "./chunk-GULDS3VE.js";
8
- import {
9
- catalog as catalog4
10
- } from "./chunk-YV5AYMNK.js";
11
8
  import {
12
9
  catalog as catalog7
13
10
  } from "./chunk-PRSRV7AT.js";
@@ -22,23 +19,26 @@ import {
22
19
  import "./chunk-QGAWDLYX.js";
23
20
  import {
24
21
  catalog as catalog6
25
- } from "./chunk-VLJOIUHL.js";
22
+ } from "./chunk-A76GTLSA.js";
26
23
  import "./chunk-S5H6GH6M.js";
27
- import "./chunk-QAGGJHAF.js";
28
24
  import {
29
25
  catalog as catalog5
30
26
  } from "./chunk-RAXHCRVV.js";
31
27
  import {
32
28
  catalog as catalog2
33
- } from "./chunk-LD4XQQRL.js";
34
- import "./chunk-MFHXCHRI.js";
29
+ } from "./chunk-AMKPFIF4.js";
35
30
  import {
36
31
  catalog as catalog8
37
32
  } from "./chunk-RL5SO653.js";
38
33
  import "./chunk-LT4PWSR2.js";
39
34
  import "./chunk-FWSJLHSV.js";
40
35
  import "./chunk-ZFEXATWN.js";
36
+ import {
37
+ catalog as catalog4
38
+ } from "./chunk-YV5AYMNK.js";
41
39
  import "./chunk-XHGZ2E77.js";
40
+ import "./chunk-QAGGJHAF.js";
41
+ import "./chunk-MFHXCHRI.js";
42
42
  import "./chunk-W2245F6G.js";
43
43
  import "./chunk-DEDZKZ6G.js";
44
44
  import "./chunk-TZ7DKL2G.js";
@@ -490,20 +490,48 @@ function resolveClipKind(clip, track) {
490
490
  return resolved;
491
491
  }
492
492
  var CompositionOp = z.discriminatedUnion("type", [
493
- z.object({ type: z.literal("clip.add"), clip: TimelineClip, trackId: z.string().optional(), newTrack: z.boolean().optional(), newTrackId: z.string().optional() }).strict(),
494
- z.object({ type: z.literal("clip.remove"), clipId: z.string() }).strict(),
495
- z.object({ type: z.literal("clip.update"), clipId: z.string(), patch: TimelineClip.partial() }).strict(),
496
- z.object({ type: z.literal("removeClipsByMediaId"), mediaId: z.string() }).strict(),
497
- z.object({ type: z.literal("loadOverlays"), clips: z.array(TimelineClip) }).strict(),
498
- z.object({ type: z.literal("addMarker"), marker: Marker }).strict(),
499
- z.object({ type: z.literal("removeMarker"), markerId: z.string() }).strict(),
500
- z.object({ type: z.literal("updateMarker"), markerId: z.string(), updates: Marker.partial() }).strict(),
501
- z.object({ type: z.literal("setCropIntents"), clipId: z.string(), segments: z.array(ReframeSegment).nullable() }).strict(),
502
- z.object({ type: z.literal("setEffects"), clipId: z.string(), effects: z.array(PerClipEffect) }).strict(),
503
- z.object({ type: z.literal("setCaptionStyle"), style: CaptionStyle.partial() }).strict(),
504
- z.object({ type: z.literal("setCaptionDepth"), zPlane: z.enum(["front", "behind-subject"]) }).strict(),
505
- z.object({ type: z.literal("setCaptions"), segments: z.array(CaptionSegment), style: CaptionStyle.partial().optional() }).strict(),
506
- z.object({ type: z.literal("applyMotionPreset"), clipId: z.string(), preset: z.string(), params: z.object({ enterMs: z.number().optional(), exitMs: z.number().optional(), staggerMs: z.number().optional() }).strict().optional() }).strict(),
493
+ z.object({ type: z.literal("clip.add"), clip: TimelineClip, trackId: z.string().optional(), newTrack: z.boolean().optional(), newTrackId: z.string().optional() }).strict().describe(
494
+ "Add a clip to the timeline. The clip self-routes to a track by FAMILY (`clip.kind`, else derived from `source.kind`, else the target track) \u2014 a `media` clip MUST carry `kind` (video|audio|image) or a `trackId`, else it is rejected as ambiguous. `trackId` targets an existing SAME-family track; `newTrack:true` (with optional pinned `newTrackId`) creates a fresh track of that family."
495
+ ),
496
+ z.object({ type: z.literal("clip.remove"), clipId: z.string() }).strict().describe(
497
+ "Remove a clip by id (located across every track)."
498
+ ),
499
+ z.object({ type: z.literal("clip.update"), clipId: z.string(), patch: TimelineClip.partial() }).strict().describe(
500
+ "Patch an existing clip by id (cross-family). The patch is a partial TimelineClip. NOTE: `source` is REPLACED wholesale \u2014 the patch merges at the top level only, so a partial `source` WIPES the source fields you omit. Patch the whole source object when changing it. (To change reframe/excluded ranges, prefer setCropIntents / addExcludedRange, not a source patch.)"
501
+ ),
502
+ z.object({ type: z.literal("removeClipsByMediaId"), mediaId: z.string() }).strict().describe(
503
+ "Remove EVERY clip sourced from a given mediaId (e.g. after deleting that media)."
504
+ ),
505
+ z.object({ type: z.literal("loadOverlays"), clips: z.array(TimelineClip) }).strict().describe(
506
+ "Replace the overlay channel wholesale with the given clips (bulk set, not a merge)."
507
+ ),
508
+ z.object({ type: z.literal("addMarker"), marker: Marker }).strict().describe(
509
+ "Add a timeline marker (a named time point/range; carry extensions in `metadata`)."
510
+ ),
511
+ z.object({ type: z.literal("removeMarker"), markerId: z.string() }).strict().describe(
512
+ "Remove a marker by id."
513
+ ),
514
+ z.object({ type: z.literal("updateMarker"), markerId: z.string(), updates: Marker.partial() }).strict().describe(
515
+ "Patch a marker's fields by id (partial merge)."
516
+ ),
517
+ z.object({ type: z.literal("setCropIntents"), clipId: z.string(), segments: z.array(ReframeSegment).nullable() }).strict().describe(
518
+ "Set (or CLEAR, with `segments: null`) the camera/reframe channel on a MEDIA clip \u2014 the time-aligned source\u2192output viewport (focus, zoom, ease, bias). This is the PREFERRED way to author reframing; do NOT reframe via a whole-source `clip.update` patch. Segments tile the clip's duration."
519
+ ),
520
+ z.object({ type: z.literal("setEffects"), clipId: z.string(), effects: z.array(PerClipEffect) }).strict().describe(
521
+ "Set (or CLEAR, with `effects: []`) a clip's EFFECT channel \u2014 the registered effect primitives stacked onto that clip, each `{ primitiveId, params? }`. WHOLESALE: the array REPLACES the clip's current effects, so send the full stack (read the clip first to add one). This is the ONLY way to change effects on an existing clip \u2014 do NOT reach for a `clip.update` patch, whose top-level merge is the source-clobbering footgun documented above."
522
+ ),
523
+ z.object({ type: z.literal("setCaptionStyle"), style: CaptionStyle.partial() }).strict().describe(
524
+ "Merge style overrides (font/color/position/emphasis) onto the composition's caption layer. Partial \u2014 omitted fields keep their current value; creates the caption container if absent."
525
+ ),
526
+ z.object({ type: z.literal("setCaptionDepth"), zPlane: z.enum(["front", "behind-subject"]) }).strict().describe(
527
+ "Set the caption compositing plane. Use `behind-subject` to place captions behind the segmented foreground subject; use `front` for conventional captions above all video layers."
528
+ ),
529
+ z.object({ type: z.literal("setCaptions"), segments: z.array(CaptionSegment), style: CaptionStyle.partial().optional() }).strict().describe(
530
+ "Replace the caption SEGMENTS wholesale \u2014 author the word-timed caption layer directly (each segment = one sentence/page of `words` with timeline-ms `startMs`/`endMs`, optional `emphasis`). This is how a client with its OWN word timings (e.g. a generated VO take) gets word-synced captions without a source transcript; the compose lane's transcript slice writes the same layer. `style`, when given, merges with setCaptionStyle semantics; an omitted style keeps the existing one. Renderer paginates within a segment and never pools words across two \u2014 keep segments sentence-bounded."
531
+ ),
532
+ z.object({ type: z.literal("applyMotionPreset"), clipId: z.string(), preset: z.string(), params: z.object({ enterMs: z.number().optional(), exitMs: z.number().optional(), staggerMs: z.number().optional() }).strict().optional() }).strict().describe(
533
+ "Shell a STATIC `card` clip with a frame-driven MOTION preset \u2014 the public card-shelling op (the layout renders VERBATIM; only the container animates in/out). The server wraps the card in the chosen `preset` (fade-up / slide-in-left / slide-in-right / scale-pop / stagger-reveal), transpiles it, mints it as a LIVE component, and swaps the clip source to that component (the bakeless live lane \u2014 no box). `params` tunes timing (`enterMs`/`exitMs`, `staggerMs` for stagger-reveal). The target must be a static card (components already carry motion; library overlays already move); re-run overwrites idempotently ONLY while the target is still a card (an uncompilable shell); once shelled, the clip is a COMPONENT \u2014 to change its motion, remove and re-add the card, then re-shell. Honored under `dry_run` (validates preset + target without transpiling or persisting). ORDERING: the preset resolves its target against the PERSISTED composition \u2014 a card added in the SAME batch is not yet visible to it. Persist the clip.add batch first, then shell in a following call."
534
+ ),
507
535
  // Scene shots (spec §"Authoring, persistence, and discovery"). Route-materialized: the host
508
536
  // registers the generated wrapper source through its component store and rewrites these to
509
537
  // addTrack / clip.add / clip.update carrying `props.componentRef`; the agent edits a scene
@@ -535,24 +563,52 @@ var CompositionOp = z.discriminatedUnion("type", [
535
563
  scene: SceneShotSpecV2.optional(),
536
564
  assets: SceneAssetBindings.optional()
537
565
  }).strict().describe("Revise a scene shot in place by clip id. Send a replacement `scene` and/or `assets`; timing changes use the ordinary clip timing fields (`startTime`, `duration`, `transitionIn`). Omitted fields keep their persisted values and the MERGED result is re-validated as a whole, so a partial edit cannot leave the shot invalid. Changing only values or asset IDs updates props and bindings and REUSES the pinned source; changing the referenced SLOT SET regenerates the wrapper manifest and registers a new immutable source version atomically. Undo restores the source reference, spec, bindings and timing together. Topology, asset bindings, text content, font properties, device kind and colours are STATIC within a shot's animation \u2014 change them here (a revised spec) rather than trying to animate them. This is not a source trim or slip: a shortened or extended clip resamples the SAME normalized animation over its new duration, and trim/slip requests on generated animation are rejected rather than reinterpreted as media trims. In one batch, `scene.update` may target a shot added by an earlier `scene.add`."),
538
- z.object({ type: z.literal("materializeGrid"), clipId: z.string(), grid: z.enum(["beat", "word", "shot"]), maxMarkers: z.number().int().positive().max(2e3).optional() }).strict(),
566
+ z.object({ type: z.literal("materializeGrid"), clipId: z.string(), grid: z.enum(["beat", "word", "shot"]), maxMarkers: z.number().int().positive().max(2e3).optional() }).strict().describe(
567
+ 'Materialize a clip\'s SOURCE-media temporal GRID as timeline markers \u2014 the rhythm authority you cut to. `grid:"beat"` reads a MUSIC bed\'s beat map (get_media_context.beatGrid), `grid:"word"` the VO word onsets (wordGrid); each grid point is projected from media time to TIMELINE time through the clip\'s placement (startTime, trim, playbackRate, excludedRanges) and written as a `kind:"beat"|"word"` marker (`metadata.sourceClipId` set). Points outside the clip\'s visible window are skipped. Re-running REPLACES the prior markers from the same clip+grid (idempotent). Land cuts and transition MIDPOINTS on the markers (a 0.5s transition starts 0.25s before its marker). `maxMarkers` (default 500, ceiling 2000) \u2014 exceeding it fails loud with the count. `grid:"shot"` is not yet exposed (shot_grid_unavailable). The target must be a media clip; absent evidence fails loud (grid_unavailable) naming how to get it (declare audioRole:"music" at ingest / transcribe).'
568
+ ),
539
569
  z.object({
540
570
  type: z.literal("captions.fromTranscript"),
541
571
  mediaId: z.string().min(1),
542
572
  window: z.object({ startSec: z.number().min(0), endSec: z.number().positive() }).strict().optional()
543
- }).strict(),
544
- z.object({ type: z.literal("setFormat"), format: Format.pick({ aspectRatio: true, fps: true, resolution: true }) }).strict(),
545
- z.object({ type: z.literal("addTrack"), kind: Track.shape.kind, name: z.string().optional(), id: z.string().optional() }).strict(),
546
- z.object({ type: z.literal("removeTrack"), trackId: z.string() }).strict(),
547
- z.object({ type: z.literal("updateTrack"), trackId: z.string(), updates: Track.pick({ name: true, enabled: true, volume: true }).partial() }).strict(),
548
- z.object({ type: z.literal("reorderTracks"), fromIndex: z.number(), toIndex: z.number() }).strict(),
549
- z.object({ type: z.literal("moveClipToTrack"), clipId: z.string(), targetTrackId: z.string(), newStartTime: z.number().optional() }).strict(),
550
- z.object({ type: z.literal("duplicateClip"), clipId: z.string(), newClipId: z.string().optional() }).strict(),
551
- z.object({ type: z.literal("cutAndRipple"), start: z.number(), end: z.number() }).strict(),
552
- z.object({ type: z.literal("ripple"), time: z.number(), amount: z.number() }).strict(),
553
- z.object({ type: z.literal("addExcludedRange"), clipId: z.string(), sourceStart: z.number(), sourceEnd: z.number(), reason: z.string().optional() }).strict(),
554
- z.object({ type: z.literal("removeExcludedRange"), clipId: z.string(), rangeIdx: z.number() }).strict()
555
- ]);
573
+ }).strict().describe(
574
+ "Attach caption CONTENT from the media's cached transcript \u2014 no pasting words. ROUTE-MATERIALIZED like applyMotionPreset/materializeGrid: the server slices the word-timed transcript to `window` (source seconds; absent = the whole media), rebases times, sentence-splits into segments, and applies it as `setCaptions`. 422 media_no_transcript when the media has no transcript or no words in the window. For your OWN word timings use setCaptions directly."
575
+ ),
576
+ z.object({ type: z.literal("setFormat"), format: Format.pick({ aspectRatio: true, fps: true, resolution: true }) }).strict().describe(
577
+ "Set the output format \u2014 `aspectRatio` (16:9|9:16|1:1|4:5) plus optional `fps` and `resolution`. Merges: an omitted field keeps the current value."
578
+ ),
579
+ z.object({ type: z.literal("addTrack"), kind: Track.shape.kind, name: z.string().optional(), id: z.string().optional() }).strict().describe(
580
+ "Add an EMPTY track of a given family (video|audio|image|overlay|effect). Optional pinned `id`."
581
+ ),
582
+ z.object({ type: z.literal("removeTrack"), trackId: z.string() }).strict().describe(
583
+ "Remove a track AND all its clips by id."
584
+ ),
585
+ z.object({ type: z.literal("updateTrack"), trackId: z.string(), updates: Track.pick({ name: true, enabled: true, volume: true }).partial() }).strict().describe(
586
+ "Patch a track's `name` / `enabled` by id."
587
+ ),
588
+ z.object({ type: z.literal("reorderTracks"), fromIndex: z.number(), toIndex: z.number() }).strict().describe(
589
+ "Move a track from one index to another (changes layer/compositing order)."
590
+ ),
591
+ z.object({ type: z.literal("moveClipToTrack"), clipId: z.string(), targetTrackId: z.string(), newStartTime: z.number().optional() }).strict().describe(
592
+ "Move a clip to another SAME-family track, optionally at a new `newStartTime`."
593
+ ),
594
+ z.object({ type: z.literal("duplicateClip"), clipId: z.string(), newClipId: z.string().optional() }).strict().describe(
595
+ "Duplicate a clip by id. Pin `newClipId` to make the copy's id deterministic (required for a clean undo/inverse)."
596
+ ),
597
+ z.object({ type: z.literal("cutAndRipple"), start: z.number(), end: z.number() }).strict().describe(
598
+ "Server-authoritative: delete the [start,end] time window (seconds) and ripple every downstream clip/marker/caption earlier to close the gap."
599
+ ),
600
+ z.object({ type: z.literal("ripple"), time: z.number(), amount: z.number() }).strict().describe(
601
+ "Server-authoritative: shift everything at/after `time` (seconds) by `amount` seconds (+ opens a gap, \u2212 closes one)."
602
+ ),
603
+ z.object({ type: z.literal("addExcludedRange"), clipId: z.string(), sourceStart: z.number(), sourceEnd: z.number(), reason: z.string().optional() }).strict().describe(
604
+ "Server-authoritative: exclude a [sourceStart,sourceEnd] span (seconds) of a clip's SOURCE (an internal trim) and ripple downstream \u2014 the preferred way to drop a sub-range vs a source patch."
605
+ ),
606
+ z.object({ type: z.literal("removeExcludedRange"), clipId: z.string(), rangeIdx: z.number() }).strict().describe(
607
+ "Server-authoritative: remove a clip's previously-added excluded range by index (re-includes that span, ripples downstream)."
608
+ )
609
+ ]).describe(
610
+ "One scoped composition operation. Discriminated by `type` \u2014 `clip.add` / `clip.remove` / `clip.update` patch clips across any family; the rest cover markers, reframe, effects, captions, format, tracks, and the server-authoritative ripple recomputes. Every variant is `.strict()`: an unknown field is refused, not ignored."
611
+ );
556
612
  function mergedCaptionStyle(composition, patch) {
557
613
  const existing = composition.captions?.style ?? {};
558
614
  if (!patch) return existing;
@@ -2,10 +2,10 @@ import {
2
2
  querySchemaShape,
3
3
  serializeQuery,
4
4
  waitForJob
5
- } from "../chunk-NG7DP46H.js";
5
+ } from "../chunk-EDR75OU7.js";
6
6
  import {
7
7
  CURATED_TOOLS
8
- } from "../chunk-DJWCD3RF.js";
8
+ } from "../chunk-3OOMVQ6I.js";
9
9
  import {
10
10
  apiErrorFrom,
11
11
  bodySchemaFor,
@@ -16,7 +16,7 @@ import {
16
16
  opById,
17
17
  safeParse,
18
18
  sniffImageMediaType
19
- } from "../chunk-URQQTAHZ.js";
19
+ } from "../chunk-RGKDDR7H.js";
20
20
  import "../chunk-2ESYSVXG.js";
21
21
 
22
22
  // ../mcp/src/adapter.ts
package/dist/mcp/core.js CHANGED
@@ -16,8 +16,9 @@ import {
16
16
  sniffAudioMediaType,
17
17
  sniffImageMediaType,
18
18
  text,
19
+ textWithData,
19
20
  withMedia
20
- } from "../chunk-URQQTAHZ.js";
21
+ } from "../chunk-RGKDDR7H.js";
21
22
  import "../chunk-2ESYSVXG.js";
22
23
  export {
23
24
  __resetPreviewCache,
@@ -37,5 +38,6 @@ export {
37
38
  sniffAudioMediaType,
38
39
  sniffImageMediaType,
39
40
  text,
41
+ textWithData,
40
42
  withMedia
41
43
  };
@@ -10,7 +10,7 @@ import {
10
10
  formatResourceCandidates,
11
11
  formatTranscriptPage,
12
12
  formatWarnings
13
- } from "../chunk-DJWCD3RF.js";
13
+ } from "../chunk-3OOMVQ6I.js";
14
14
  import "../chunk-2ESYSVXG.js";
15
15
  export {
16
16
  CRUD_COVERAGE,
@@ -7,10 +7,10 @@ import {
7
7
  registerRawTools,
8
8
  registerSkillPrompts,
9
9
  titleCase
10
- } from "../chunk-BAVEHXVC.js";
11
- import "../chunk-NG7DP46H.js";
12
- import "../chunk-DJWCD3RF.js";
13
- import "../chunk-URQQTAHZ.js";
10
+ } from "../chunk-LPFVUT3S.js";
11
+ import "../chunk-EDR75OU7.js";
12
+ import "../chunk-3OOMVQ6I.js";
13
+ import "../chunk-RGKDDR7H.js";
14
14
  import "../chunk-2ESYSVXG.js";
15
15
  export {
16
16
  RAW_EXCLUDED_OPERATION_IDS,
package/dist/mcp.js CHANGED
@@ -7,10 +7,10 @@ import {
7
7
  buildServer,
8
8
  registerCuratedTools,
9
9
  registerRawTools
10
- } from "./chunk-BAVEHXVC.js";
11
- import "./chunk-NG7DP46H.js";
12
- import "./chunk-DJWCD3RF.js";
13
- import "./chunk-URQQTAHZ.js";
10
+ } from "./chunk-LPFVUT3S.js";
11
+ import "./chunk-EDR75OU7.js";
12
+ import "./chunk-3OOMVQ6I.js";
13
+ import "./chunk-RGKDDR7H.js";
14
14
  import "./chunk-2ESYSVXG.js";
15
15
  export {
16
16
  API_URLS,
@@ -12,9 +12,9 @@ import {
12
12
  Vignette,
13
13
  catalog,
14
14
  laneDepths
15
- } from "../chunk-LD4XQQRL.js";
16
- import "../chunk-MFHXCHRI.js";
15
+ } from "../chunk-AMKPFIF4.js";
17
16
  import "../chunk-XHGZ2E77.js";
17
+ import "../chunk-MFHXCHRI.js";
18
18
  import "../chunk-W2245F6G.js";
19
19
  import "../chunk-DEDZKZ6G.js";
20
20
  import "../chunk-TZ7DKL2G.js";
@@ -30,14 +30,14 @@ import {
30
30
  VisualDocsSnippet,
31
31
  VoiceWaveform,
32
32
  catalog
33
- } from "../chunk-VLJOIUHL.js";
33
+ } from "../chunk-A76GTLSA.js";
34
34
  import {
35
35
  DashboardPopulate
36
36
  } from "../chunk-S5H6GH6M.js";
37
- import "../chunk-QAGGJHAF.js";
38
37
  import "../chunk-LT4PWSR2.js";
39
38
  import "../chunk-FWSJLHSV.js";
40
39
  import "../chunk-XHGZ2E77.js";
40
+ import "../chunk-QAGGJHAF.js";
41
41
  import "../chunk-W2245F6G.js";
42
42
  import "../chunk-2ESYSVXG.js";
43
43
  export {
@@ -1,6 +1,3 @@
1
- import {
2
- catalog as catalog4
3
- } from "./chunk-YV5AYMNK.js";
4
1
  import {
5
2
  catalog as catalog7
6
3
  } from "./chunk-PRSRV7AT.js";
@@ -15,23 +12,26 @@ import {
15
12
  import "./chunk-QGAWDLYX.js";
16
13
  import {
17
14
  catalog as catalog6
18
- } from "./chunk-VLJOIUHL.js";
15
+ } from "./chunk-A76GTLSA.js";
19
16
  import "./chunk-S5H6GH6M.js";
20
- import "./chunk-QAGGJHAF.js";
21
17
  import {
22
18
  catalog as catalog5
23
19
  } from "./chunk-RAXHCRVV.js";
24
20
  import {
25
21
  catalog as catalog2
26
- } from "./chunk-LD4XQQRL.js";
27
- import "./chunk-MFHXCHRI.js";
22
+ } from "./chunk-AMKPFIF4.js";
28
23
  import {
29
24
  catalog as catalog8
30
25
  } from "./chunk-RL5SO653.js";
31
26
  import "./chunk-LT4PWSR2.js";
32
27
  import "./chunk-FWSJLHSV.js";
33
28
  import "./chunk-ZFEXATWN.js";
29
+ import {
30
+ catalog as catalog4
31
+ } from "./chunk-YV5AYMNK.js";
34
32
  import "./chunk-XHGZ2E77.js";
33
+ import "./chunk-QAGGJHAF.js";
34
+ import "./chunk-MFHXCHRI.js";
35
35
  import "./chunk-W2245F6G.js";
36
36
  import "./chunk-DEDZKZ6G.js";
37
37
  import "./chunk-TZ7DKL2G.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cueframe/kernel",
3
- "version": "0.2.22",
3
+ "version": "0.2.23",
4
4
  "type": "module",
5
5
  "description": "Publish-only aggregate of CueFrame's isomorphic kernel packages — composition, primitives, authoring, MCP, and friends shipped as one npm artifact. Members remain separate workspace packages; this only changes what ships.",
6
6
  "files": [
@@ -17,25 +17,25 @@
17
17
  "tsup": "^8.3.5",
18
18
  "typescript": "^5.9.3",
19
19
  "@cueframe/account-model": "0.1.0",
20
- "@cueframe/animate": "0.1.0",
21
- "@cueframe/cloud-import": "0.1.0",
22
20
  "@cueframe/brand-derivation": "0.1.0",
23
21
  "@cueframe/authoring": "0.1.0",
24
- "@cueframe/composition": "0.1.0",
22
+ "@cueframe/animate": "0.1.0",
23
+ "@cueframe/cloud-import": "0.1.0",
25
24
  "@cueframe/compose": "0.1.0",
26
- "@cueframe/core": "0.1.0",
27
25
  "@cueframe/component-runtime": "0.1.0",
28
- "@cueframe/errors": "0.1.0",
29
- "@cueframe/director": "0.1.0",
30
- "@cueframe/composition-ops": "0.1.0",
26
+ "@cueframe/composition": "0.1.0",
27
+ "@cueframe/core": "0.1.0",
31
28
  "@cueframe/foundation": "0.1.0",
29
+ "@cueframe/director": "0.1.0",
30
+ "@cueframe/errors": "0.1.0",
31
+ "@cueframe/media-model": "0.1.0",
32
+ "@cueframe/perception": "0.1.0",
32
33
  "@cueframe/plane": "0.1.0",
33
34
  "@cueframe/nle-export": "0.1.0",
34
35
  "@cueframe/mcp": "0.1.0",
35
- "@cueframe/media-model": "0.1.0",
36
36
  "@cueframe/render-harness": "0.1.0",
37
- "@cueframe/perception": "0.1.0",
38
- "@cueframe/primitives": "0.1.0"
37
+ "@cueframe/primitives": "0.1.0",
38
+ "@cueframe/composition-ops": "0.1.0"
39
39
  },
40
40
  "peerDependencies": {
41
41
  "gsap": "3.13.0",
@@ -1,9 +1,6 @@
1
1
  import {
2
2
  DashboardPopulate
3
3
  } from "./chunk-S5H6GH6M.js";
4
- import {
5
- useFrameScale
6
- } from "./chunk-QAGGJHAF.js";
7
4
  import {
8
5
  resolveTypeface
9
6
  } from "./chunk-LT4PWSR2.js";
@@ -14,6 +11,9 @@ import {
14
11
  FitText,
15
12
  resolveFitFontSize
16
13
  } from "./chunk-XHGZ2E77.js";
14
+ import {
15
+ useFrameScale
16
+ } from "./chunk-QAGGJHAF.js";
17
17
  import {
18
18
  ACCENT,
19
19
  ACCENT_DEEP,
@@ -1,9 +1,9 @@
1
- import {
2
- EASING_BY_INTENT
3
- } from "./chunk-MFHXCHRI.js";
4
1
  import {
5
2
  FitText
6
3
  } from "./chunk-XHGZ2E77.js";
4
+ import {
5
+ EASING_BY_INTENT
6
+ } from "./chunk-MFHXCHRI.js";
7
7
  import {
8
8
  ACCENT,
9
9
  ACCENT_STRONG,