cueframe 0.8.0 → 0.8.2

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.
@@ -60,8 +60,8 @@ affected behavior and regressions; reserve full-film judging and export for the
60
60
  4. **Seed and author.** `new_composition` at 9:16 → `apply_composition` with the footage beats
61
61
  and `setCropIntents` segments tiling each clip (subject-focused, motivated zooms).
62
62
  5. **Author every graphic as a bespoke card.** `kind:"card"` HTML using the brand's type and its
63
- palette as FIELDS — plates, bands, blocks of the brand color, not tinted defaults. Persist
64
- the card, then shell it with `applyMotionPreset` in a FOLLOWING `apply_composition` call. The
63
+ palette as FIELDS — plates, bands, blocks of the brand color, not tinted defaults. Animate
64
+ it in its CSS (`--cf-enter`/`--cf-exit`) or with `clip.setKeyframes` in a FOLLOWING call. The
65
65
  test: if another brand's hex swapped in would still look correct, the layout isn't bespoke
66
66
  enough — redo it.
67
67
  6. **Honor the audio decision.** If silence was requested, preserve it without searching for a bed. Otherwise, when music serves the plan: `search_resources` for a bed honoring the kit's
@@ -78,10 +78,10 @@ Chromium captures; the clock never advances). Use these instead:
78
78
  2. **`transitionIn` on any overlay clip** — `{ transitionIn: { type: "fade", duration: 0.4 } }`
79
79
  now applies to overlay-family clips (cards, components, primitives), not just video.
80
80
  Use it for entrances; author exits in-card via `--cf-exit`.
81
- 3. **`applyMotionPreset` op** — shell a static card into a frame-driven component in one op:
82
- `{ type: "applyMotionPreset", clipId, preset }` (unknown presets are rejected with the
83
- full preset list). The layout stays verbatim; the preset adds entrance/idle/exit
84
- choreography. The result is a component you can `get_component_source` and refine.
81
+ 3. **`clip.setKeyframes` op** — animate the whole clip on one channel:
82
+ `{ type: "clip.setKeyframes", clipId, property, keyframes: [{ time, value, ease? }, …] }`
83
+ for `opacity`, `scale`, `rotation`, `position.x` or `position.y` (times in seconds from the
84
+ clip's start). For motion beyond that, author a native component with `create_component`.
85
85
 
86
86
  ---
87
87
 
@@ -208,7 +208,7 @@ soundtrack is a measurable share of whether anyone keeps watching.
208
208
  reach for `volumeDb` only when you intend to break the hierarchy.
209
209
 
210
210
  0d. **A brand kit can carry a SOUND identity — set it once, it rides every
211
- compose.** `create_brand_kit`'s `audio` block (echoed on `get_brand_kit` +
211
+ compose.** `create_brand_kit`'s `audio` block (echoed on
212
212
  `get_profile`) holds the brand's sonic taste; every ref is a palette reference
213
213
  (a pack `soundId` | `bedId` | an org `mediaId` from `list_catalog`'s `audio[]`).
214
214
  Four optional fields, resolution precedence **brand > org > pack** (the audio
@@ -328,7 +328,7 @@ Each playbook is an illustrative format default; explicit intent and scope gover
328
328
 
329
329
  **Frameworks:** Hook → Problem → Solution → Proof → CTA. Decide the CTA first, script it last; write for the ear.
330
330
  **A fully animated explainer is a FIRST-CLASS grounded format**, not a fallback for missing footage. An explainer built on authored diagrams of the *actual* mechanism (its real pipeline, the real data) is grounded — the grounding is that each visual depicts THIS thing specifically, not a decorative loop. Reach for it deliberately, not as a consolation for "no footage."
331
- **Reach for:** an authored `card` clip (your own HTML/typography, shelled with `applyMotionPreset`) to anchor an abstract mechanism with a real diagram — process/algorithm/data viz, not cinematic footage · `lower-third` for term labels · one idea per beat · when using real footage, narrate to the detected transcript timing.
331
+ **Reach for:** an authored `card` clip (your own HTML/typography, animated in its CSS or with `clip.setKeyframes`) to anchor an abstract mechanism with a real diagram — process/algorithm/data viz, not cinematic footage · `lower-third` for term labels · one idea per beat · when using real footage, narrate to the detected transcript timing.
332
332
  **Anti-patterns:** a wall of narration over stock gradient loops with no visual keyed to the words (the generic-filler tell — a purposeful diagram of the real mechanism is the opposite); explaining features before establishing the problem; using `generate_media` text-to-video to fake a "product" that doesn't match the real UI (it hallucinates specifics).
333
333
  **Quality bar:** every beat has a visual keyed to what's being said — grounded and specific to this content, not decoration; the hook lands in 3s.
334
334
 
@@ -378,12 +378,12 @@ Each playbook is an illustrative format default; explicit intent and scope gover
378
378
  | Name someone / label a step | `lower-third` scene from `list_catalog` |
379
379
  | Title / closing wordmark | `cinematic-title` scene (closing, not opening) |
380
380
  | Grade the look | `color-grade` scene/effect |
381
- | Anchor an abstract mechanism | An authored `card` clip + `applyMotionPreset` (diagrams of the real mechanism — NOT cinematic; a first-class grounded path) |
381
+ | Anchor an abstract mechanism | An authored `card` clip animated in its CSS or with `clip.setKeyframes` (diagrams of the real mechanism — NOT cinematic; a first-class grounded path) |
382
382
  | Long-form → clips | `suggest_briefs` → `wait_job(kind:"suggestions")` → `compose({ suggestionId })` |
383
383
  | Auto-reframe to another aspect | `derive_composition` (content-aware 9:16 / 1:1 / 4:5 from a 16:9 master) |
384
384
  | Discover what to build with | `list_catalog` — pass filters such as `category`/`kind`/`tier`/`useCase`/`mood`/`intent`, plus `limit` and the returned pagination cursor. For builtin packages, request `projection:"component-library"` and `origin:"builtin"`, then resolve the selected exact identity before insertion. Each entry carries `placement` (how to author it), `tier`/`useCase`, and `fixedCopy` (baked text no param changes). Read `cueframe://component/{id}` for the full **prop schema**. |
385
385
  | Own / fork a custom graphic | `get_component_source` (exact scoped v3 pin for builtins) / `create_component` / `update_component` (preserve the complete source snapshot) — **see `cueframe-component-authoring`** for the full fork→preview→push loop |
386
- | Several cells that share space and must reflow together (feature grid, rail + fluid content, cast + type) | An authored component on `FlexLayout` from `@cueframe/animate` — tracks are **weights** (`{ weight: 0, basis: px }` = content-sized, pushes neighbours), `tracksEnd` animates the reflow per frame, `fits` per cell (`stretch`/`contain`/`cover`/`position`/`scale`/`matte`). Static PiP / split is clip `region` + `fit` instead. Never CSS-scale the layout. **See `cueframe-component-authoring`** |
386
+ | Several cells that share space and must reflow together (feature grid, rail + fluid content, cast + type) | An authored card or native component laid out with CSS flex/grid (fixed tracks for content-sized cells, `fr` for the rest), animated from the frame. Static PiP / split is clip `region` + `fit` instead. Never CSS-scale the layout. **See `cueframe-component-authoring`** |
387
387
  | Seed + edit the timeline | `apply_composition` (atomic batch ops; `dry_run:true` to check, `if_match` for OCC) — this is where clips, reframe, captions, and format are authored |
388
388
  | Set the brand | `create_brand_kit` (402-gated) → set `brandKitId` on the **project** (`create_project` / `update_project`). It lives on the project, NOT on a composition op — trying to set it elsewhere is silently dropped. |
389
389
  | Shape-check (NOT quality) | `validate_composition` → `{ valid, errors[] }` (`unknown_primitive`, `invalid_primitive_params`, `clip_source_track_mismatch`, `reframe_coverage_gap`, …); does **not** check trim bounds or image-on-video-track (those fail at save/render) |
@@ -199,14 +199,13 @@ npx -y cueframe@0.8 init --json
199
199
  # 2a. Scaffold a component in cueframe/components/<id>/ as owned, editable source.
200
200
  npx -y cueframe@0.8 new component my-badge --json
201
201
 
202
- # Built-in forks require an exact v3 sourceSnapshot; retain its TSX, definition and manifest.
203
- # The CLI sends index.tsx + definition.json + the workspace asset manifest as one v3 source.
202
+ # The scaffold is a native (version 4) source: template.html + definition.json, with its
203
+ # manifest in cueframe.json. Built-in forks copy the exact template, definition and manifest.
204
204
 
205
- # 3. Edit cueframe/components/<id>/ — it's standard Remotion. The component is mounted
206
- # with params/assets/render; definition.json declares its source-v3 capabilities and timing.
207
- # The nested asset manifest remains version 2.
205
+ # 3. Edit cueframe/components/<id>/template.html (one HyperFrames sub-composition ending in
206
+ # __cfScene.mount(...)) and definition.json (inputs, defaults, timing, capabilities).
208
207
 
209
- # 4. Publish to CueFrame so it can render (uploads index.tsx as the component's tsxSource).
208
+ # 4. Publish to CueFrame so it can render (the server admits the source or names the rule it breaks).
210
209
  npx -y cueframe@0.8 push my-badge --json # one component
211
210
  npx -y cueframe@0.8 sync --json # every component in the workspace
212
211
 
@@ -27,12 +27,12 @@ See the `cueframe-scene-shot` skill and `cueframe://scene-shot`. Author a compon
27
27
  you need behaviour the scene contract does not express.
28
28
 
29
29
  **Source format matters:**
30
- - Every executable component uses source v3: keep the complete `sourceSnapshot`, including `definition`,
31
- `manifest`, and `tsxSource`. Its definition declares input schema, defaults, timing,
32
- brand/media bindings and capabilities. Do not infer these from the source code or flatten
33
- a v3 source into a v2 module. The nested asset manifest can still have `version: 2`.
34
- - The mount signature is `({ durationInFrames, params, assets, render }) => JSX`.
35
- Source2 inputs require explicit migration; they are not accepted for editing or rendering.
30
+ - New and changed components are native (source version 4): `{version:4, componentId, template,
31
+ manifest, definition}`. `template` is one HyperFrames sub-composition whose script ends in one
32
+ `__cfScene.mount(...)`; the manifest is `{version:4, runtime:'hyperframes', hfContract:1,
33
+ kind:'scene', assets, graphics, alphaPolicy}`. The definition declares input schema, defaults,
34
+ timing, brand/media bindings and capabilities. The server instructions' Component Contract has
35
+ the full rules. Version-3 TSX sources are retired and refused as `component_source_retired`.
36
36
  - Neither renderer runs authored source in the editor process. Local and hosted execution
37
37
  use isolated render processes and exact immutable source revisions.
38
38
 
@@ -40,14 +40,14 @@ you need behaviour the scene contract does not express.
40
40
 
41
41
  Use the live tool schema for request envelopes; hosted write fields are normally under `body`.
42
42
 
43
- 1. **Discover and read a built-in:** call `list_components` with
43
+ 1. **Discover and read a built-in:** call `list_catalog` with
44
44
  `projection: "component-library", origin: "builtin", libraryKind: "definition"`.
45
45
  Select the intended card and preserve its complete scoped source reference. Call
46
46
  `get_component_source` with that reference's `componentId`, `sourceVersionId`,
47
47
  `builtinCollection` (its scope collection).
48
48
  ID-only builtin reads are unsupported. An unavailable exact pin is a refusal; never
49
49
  retry another scope or an unpinned head.
50
- 2. **Author/own it:** edit the complete v3 `sourceSnapshot`; use your chosen owned ID
50
+ 2. **Author/own it:** edit the complete native `sourceSnapshot`; use your chosen owned ID
51
51
  consistently in its `componentId` and the request. Send it as `body.source` to
52
52
  `create_component`, with required metadata. Preserve its definition, capability
53
53
  declarations, manifest and source format. For an existing component, keep its ID and
@@ -69,96 +69,89 @@ Use the live tool schema for request envelopes; hosted write fields are normally
69
69
  Set preview duration deliberately: `durationInFrames` is not a trim window and can retime
70
70
  duration-dependent motion. Preserve production timing; inspect the live tool's request shape.
71
71
  4. **Check integration:** add it as a `{kind:'component', componentId, props:{},
72
- execution:{version:3,componentId,sourceVersionId,params,seed}, placement:{version:3,kind:'clip'}}` clip
72
+ execution:{version:4,componentId,sourceVersionId,params,seed,assets}, placement:{version:3,kind:'clip'}}` clip
73
73
  (`apply_composition`), then inspect saved-composition stills or a scoped `preview_clip` for
74
74
  placement and motion. Use `create_render` → `wait_job kind:"render"` when delivering a
75
75
  finished MP4, not per tweak.
76
76
 
77
77
  ## Path B — CLI / coding agent (customer-owned source under git)
78
78
 
79
- Source lives in `cueframe/components/<id>/` (the `cueframe.json` workspace); edit locally as
80
- standard Remotion, push to render.
79
+ Source lives in `cueframe/components/<id>/` (the `cueframe.json` workspace); edit the native
80
+ template locally, push to render.
81
81
 
82
82
  ```bash
83
83
  npx -y cueframe@0.8 init --json # scaffold cueframe.json + cueframe/components/
84
84
  npx -y cueframe@0.8 new component my-badge --json # scaffold a component in the workspace
85
- # New scaffolds contain index.tsx, config.ts and definition.json (source v3).
86
- # For a builtin fork, retain its TSX, complete definition and asset manifest together.
87
- # …edit cueframe/components/<id>/ in your editor (standard Remotion)…
88
- npx -y cueframe@0.8 push my-badge --json # upload index.tsx as the component tsxSource
85
+ # New scaffolds contain template.html and definition.json (native source, version 4).
86
+ # …edit cueframe/components/<id>/template.html and definition.json in your editor…
87
+ npx -y cueframe@0.8 push my-badge --json # send the native source; the server admits it
89
88
  # or: npx -y cueframe@0.8 sync --json # push every component in the workspace
90
89
  # reference the component id in your composition, then render:
91
90
  npx -y cueframe@0.8 render <projectId> -o ./out.mp4 --json
92
91
  ```
93
92
 
94
- The CLI writes `index.tsx` (module), `config.ts` (metadata) and `definition.json` (complete v3
95
- input schema, defaults, timing and capabilities). Its asset manifest lives at
96
- `cueframe.json` → `components.<id>.manifest`; that nested manifest retains version 2.
97
- `push`/`sync` send these as one v3 `source` snapshot. For a builtin fork, copy **all three source
98
- parts** from the selected `sourceSnapshot`: TSX, definition and manifest; choose the owned ID
99
- consistently. Never replace a definition with the blank scaffold's defaults.
100
- Older workspaces without `definition.json` are refused before API calls: review and declare
101
- their real behavior instead of running `new --force` over existing source. To preview before
93
+ The CLI writes `template.html` and `definition.json` (input schema, defaults, timing and
94
+ capabilities). The version-4 manifest lives at `cueframe.json` → `components.<id>.manifest`.
95
+ `push`/`sync` send these as one native `source`, checked against the contract's source schema;
96
+ the server's admission (template lint, determinism, declarations) refuses with a named rule.
97
+ For a builtin fork, copy **all three parts** from the selected `sourceSnapshot`: template,
98
+ definition and manifest. A workspace component that still has a version-3 `index.tsx` is
99
+ refused as `component_source_retired` before any API call; author it again as a native template. To preview before
102
100
  a full render, push it and use the appropriate component preview loop.
103
101
 
104
102
  ## Path C — Director editing a local desktop project (project-embedded source)
105
103
 
106
- When `get_context` reports the project lane as local, motion does not require an account or an org
107
- component. When exposed, use `open_component_preview` to compile an isolated preview (new source,
108
- with an explicit `component_definition`, or `component_id` to preserve an existing library source), `update_component_preview`
109
- with exact-text edits (`{oldText, newText, replaceAll?}`, each `oldText` matching once), and
110
- `render_component_preview_frame` for exact frame evidence. Inspect playback for motion;
111
- `commit_component_preview` publishes the checked source to the library and pins the clip, with a
112
- fresh ETag. `read_component` and `edit_component` read and edit the library directly. Close the preview when finished. This loop does not bake a timeline video and
113
- does not imply HMR: source updates still compile.
114
-
115
- If those tools are unavailable, add/update through `apply_composition` and inspect the local
116
- timeline. An inline custom overlay has this shape:
117
-
118
- ```json
119
- {
120
- "type": "clip.add",
121
- "clip": {
122
- "id": "local-motion",
123
- "kind": "overlay",
124
- "startTime": 0,
125
- "duration": 3,
126
- "source": {
127
- "kind": "overlay",
128
- "primitiveId": "custom",
129
- "params": {
130
- "jsxSource": "export default function Graphic({ durationInFrames, params, assets, render }) { ... }",
131
- "componentManifest": { "version": 2, "assets": {}, "graphics": { "api": "canvas2d", "required": true, "accelerationPreference": "software-allowed" }, "alphaPolicy": "requires-transparency" },
132
- "componentDefinition": { "inputSchema": { "type": "object", "properties": {}, "additionalProperties": false }, "defaults": {}, "timing": { "defaultDuration": { "frames": 90, "fps": 30 } }, "brandBindings": [], "capabilities": [{ "kind": "clip" }] },
133
- "componentAssets": {}
134
- }
135
- }
136
- },
137
- "newTrack": true
138
- }
139
- ```
140
-
141
- The desktop compiles the module in an isolated child process and creates it in the desktop's component
142
- library as a complete v3 source. Declare the actual parameter schema and defaults in
143
- `componentDefinition`; the empty schema above is for a graphic with no custom parameters.
144
- Keep a fork's existing definition and nested manifest together with its source. Inline source creates a component only: a clip whose component already exists with other
145
- source is refused as `component_exists`, and that component changes through edits.
146
- The timeline and final export render that exact version; no preview media is baked. A compile failure
147
- refuses the whole batch and leaves the composition untouched. Use `create_component` only for a reusable
148
- org-catalog component on a reachable cloud lane.
149
-
150
- For media-backed graphics, declare each slot in `componentManifest.assets`, bind the slot to a
151
- project media id in `componentAssets`, and read its request-scoped handle from `assets`. The renderer
152
- localizes and hashes the file before execution. Remote URLs, undeclared assets, arbitrary filesystem
153
- paths, WebGL substitution, and static/card substitution are not fallback paths.
104
+ When `get_context` reports the project lane as local, motion does not need an account or an org
105
+ component. Components here are native (source version 4) too:
106
+ - **New component:** open a draft with `open_component_preview`, passing `template`,
107
+ `component_manifest` (`{version:4, runtime:'hyperframes', hfContract:1, kind:'scene', assets,
108
+ graphics, alphaPolicy}`) and `component_definition`.
109
+ - **Existing component:** pass `component_id` instead, to start from the clip's pinned version or
110
+ the library version.
154
111
 
155
- Declare `componentManifest.alphaPolicy` explicitly: `requires-transparency` for overlays and
156
- `allows-opaque` for intentional full-frame plates. Both adapters verify the same field.
112
+ The draft is admitted in full: template lint, determinism and the variable declaration.
113
+ `data-composition-variables` must equal the definition's projection (its scalar inputSchema
114
+ properties, then the manifest's slots).
157
115
 
158
- Normal static imports are supported only from the runtime allowlist: React, Remotion, Three.js
159
- WebGPU/TSL, React Three Fiber/Drei, Remotion Three, and CueFrame animation helpers. Use ordinary hooks,
160
- loops, refs, constructors, typed arrays, canvas, and WebGPU when needed. Drive visible animation from
161
- `render.frame`/`render.fps` and seed variation from `render.seed`; ambient time/randomness is refused.
116
+ Edit the draft with `update_component_preview` (exact-text `{oldText, newText, replaceAll?}`
117
+ edits). Check exact frames with `render_component_preview_frame`, and inspect playback for motion.
118
+ `commit_component_preview` publishes the checked source to the library and pins the clip, with a
119
+ fresh ETag. `read_component` and `edit_component` read and edit the library directly. Close the
120
+ preview when finished.
121
+
122
+ Media slots are image or video, declared in `component_manifest.assets` and bound to project
123
+ media ids in `component_assets`. The template passes them to `MediaTileScene` as
124
+ `streams: { hero: { asset: assets.hero } }`. Declare `alphaPolicy` explicitly:
125
+ `requires-transparency` for overlays, `allows-opaque` for full-frame plates.
126
+
127
+ Every frame must be a pure function of `render.frame`, params and `render.seed`. Timers, `Date`,
128
+ network, imports, other globals and CSS animation are refused at admission. The timeline and final
129
+ export render the exact committed version. Use `create_component` only for a reusable org-catalog
130
+ component on a reachable cloud lane.
131
+
132
+ ## Upgrading an old project
133
+
134
+ `get_composition` returns `needsUpgrade` only when clips from before the native cutover remain:
135
+ `[{clipId, kind, componentId?, sourceVersionId?, primitiveId?, successorComponentId?,
136
+ successorSourceVersionId?}]`. Saves and renders refuse those clips. A retired component is
137
+ refused with `component_source_retired`, and a retired
138
+ primitive with `render_unknown_primitive`. Upgrade only the listed clips. Keep every other clip,
139
+ timing, track and brand-kit binding as it is.
140
+
141
+ - **Any entry with a `successorSourceVersionId`:** call `repin_component` with its clip ids. It
142
+ moves them to the native successor and keeps their saved inputs.
143
+ - **`v3-component` with no successor** (a custom pre-cutover component): read its source, params,
144
+ timing and placement, and author a native replacement with `create_component` as a version-4
145
+ template following the server instructions' Component Contract.
146
+ - **`unpinned-component`:** the clip carries a `retired` record (what was stored before the cutover), with its
147
+ props preserved. Re-author it from those props the same way.
148
+ - **`retired-primitive`:** rebuild what `primitiveId` drew from the clip's params, as a card or
149
+ a native component.
150
+
151
+ For each replacement, use `apply_composition` to replace only that clip at the same start,
152
+ duration, track and placement, pinned to the new exact `sourceVersionId`. Map the old params
153
+ onto the new inputs; do not invent new content. Verify each clip with `preview_frame` at a time
154
+ inside its window and confirm `needsUpgrade` is gone. Only then render (`create_render`).
162
155
 
163
156
  ## Choosing what to fork
164
157
  Use the `component-library` projection to choose a definition, inspect its input schema and
@@ -166,41 +159,28 @@ capabilities, then fetch its exact scoped source as described in Path A. Presets
166
159
  are different card kinds: follow their exact definition/document references rather than treating
167
160
  those IDs as source IDs. Inspect appropriate previews before judging visual behavior.
168
161
 
169
- ## Layout inside a component — `FlexLayout` / `solveFlex`
170
- When a graphic has several cells that must share space — a feature grid, a fixed rail beside
171
- fluid content, a cast of objects next to type — don't hand-place pixels. The layout helpers
172
- are in `@cueframe/animate` (on the import allowlist):
173
-
174
- ```tsx
175
- import { FlexLayout } from '@cueframe/animate';
176
- const R = params.region ?? { x: 0, y: 0, w: 1, h: 1 };
177
- <FlexLayout
178
- tracks={[{ weight: 0, basis: 520 }, 1, { weight: 3, split: [1, 1] }]}
179
- tracksEnd={[{ weight: 0, basis: 520 }, 1, { weight: 1, split: [2, 1] }]}
180
- fits={[{ mode: 'stretch' }, { mode: 'position' }, { mode: 'matte', aspect: 1 }, { mode: 'stretch' }]}
181
- gap={16} width={R.w * width} height={R.h * height}>
182
- <Rail /> <Type /> <Art /> <Block />
183
- </FlexLayout>
162
+ ## Layout inside a component — CSS flex/grid
163
+
164
+ When several cells must share space (a feature grid, a fixed rail beside fluid content, a cast of
165
+ objects next to type), lay them out with CSS flex or grid in the HTML, not hand-placed pixels.
166
+ Use the template of a native component, or a `card`:
167
+
168
+ ```html
169
+ <div style="display:grid; grid-template-columns: 520px 1fr 3fr; gap:16px; width:100%; height:100%">
170
+ <div class="rail"></div><div class="type"></div><div class="art"></div>
171
+ </div>
184
172
  ```
185
173
 
186
- - Tracks are **weights, not pixels**; `{ weight: 0, basis: 520 }` is content-sized and pushes its
187
- neighbours. Children fill the leaves depth-first (a `split` nests cells on the other axis).
188
- - `tracksEnd` animates: weights ease across the clip and **every cell re-solves per frame**. A cell
189
- present on only one side enters from / exits to zero. Layout is the motion — no keyframed positions.
190
- - `fits`, one per leaf: `stretch | contain | cover | position | scale | matte`. `position` keeps type at
191
- its natural size; `matte` clips a window instead of resizing the art.
192
- - Inside a `params.region` container pass `width`/`height` = the region's pixel size so nested splits and
193
- aspect fits solve against the region, not the frame. **Never CSS-`scale()` the layout** — it resamples;
194
- size the container instead.
195
- - 3D in the same rig: `solveFlex(tree, size, weights)` returns the boxes; `flexBoxToThree(box, size)`
196
- centres a mesh under `<ThreeCanvas orthographic>` (frustum pinned 1:1 to pixels), so one canvas holds
197
- every object exactly on its cell.
198
- - Same inputs, same boxes everywhere: the solve is pure and yoga-backed; nothing is measured from the DOM.
199
- Text is placed, not measured — declare sizes with `basis` when a cell must fit its content.
174
+ - Fixed tracks (`520px`) hold content-sized cells. Fractions (`1fr`, `3fr`) share what is left.
175
+ - Animate a reflow from the frame, never from time: in a card, drive `grid-template-columns`
176
+ or `flex-basis` from `var(--cf-progress)` (or `--cf-enter`/`--cf-exit`); in a native template,
177
+ derive it from `render.frame`.
178
+ - Size the container to its region instead of CSS-`scale()`-ing the layout, which resamples.
179
+ Use `object-fit` for media in a cell.
200
180
 
201
181
  ## Don't
202
182
  - Don't expect a forked primitive to use rich named props — read `params` inside and use the explicit
203
183
  `assets` and `render` bags for media and frame state.
204
- - When a built-in fits the request, fork its exact v3 snapshot using Path A. Never discard its
205
- definition by copying only its TSX; preserve the definition and manifest too.
184
+ - When a built-in fits the request, fork its exact snapshot using Path A. Never discard its
185
+ definition by copying only its template; preserve the definition and manifest too.
206
186
  - Don't `create_component` a fresh id each fix iteration — `update_component` (same id) is the loop.
@@ -107,9 +107,9 @@ Read `critique` — it names the lowest criterion first and says why. Fix **that
107
107
  thing** (re-author the relevant ops), then score again. One criterion per pass:
108
108
  chasing all four at once thrashes. Example: editorial 4 "two subjects off-center
109
109
  vs centered intent" → tighten the reframe onto face-0 → re-score. A layout critique
110
- ("cramped", "unbalanced", "text crowds the art") on a component built with
111
- `FlexLayout` is a `tracks` weight or `fits` change — re-author those and re-solve; never
112
- nudge pixels (see `cueframe-component-authoring`).
110
+ ("cramped", "unbalanced", "text crowds the art") on a laid-out card or component is a
111
+ change to its CSS grid tracks or flex basis. Re-author those; never nudge pixels (see
112
+ `cueframe-component-authoring`).
113
113
 
114
114
  ## 5. Stop — then render
115
115
 
@@ -82,12 +82,11 @@ product demo:
82
82
  | device/browser frame | `device-mockup-zoom` / `browser-flow` | overlay |
83
83
  | captions (if narrated) | the top-level `captions` field | — |
84
84
  | finish/look | `color-grade` + `vignette` | effect |
85
- | several cells sharing space that should reflow (feature grid, rail + fluid content) | author a component on `FlexLayout` from `@cueframe/animate` (see `cueframe-component-authoring`) | overlay (authored) |
85
+ | several cells sharing space that should reflow (feature grid, rail + fluid content) | author one card or native component laid out with CSS flex/grid (see `cueframe-component-authoring`) | overlay (authored) |
86
86
 
87
87
  Static PiP / split-screen is `region` + `fit` on the clips (EX3 / EX4). Cells that must
88
- **reflow together** or hold mixed content are one authored component built on `FlexLayout`;
89
- the catalog `flex-layout` primitive is that same layout, but a bare composition overlay has
90
- no children, so on its own it draws only the rig outlines.
88
+ **reflow together** or hold mixed content are one authored card or native component laid out
89
+ with CSS flex/grid.
91
90
 
92
91
  A primitive is the `source` of a clip; the clip wraps it with `id`/`startTime`/`duration`:
93
92
  - Overlay clip (on a `kind:"overlay"` track): `{ "id":"t1", "startTime":1, "duration":3, "source":{ "kind":"overlay", "primitiveId":"lower-third", "params":{…}, "zPlane":"front" } }`.
@@ -54,7 +54,7 @@ A correction to one sibling does not require rendering the master or the other u
54
54
  the beat exists to show.
55
55
  4. **Fix per-sibling, surgically.** Where a crop or card misbehaves, `apply_composition` on
56
56
  THAT sibling — nudge a `setCropIntents` segment, resize a card for the narrower frame
57
- (re-shell with `applyMotionPreset` in a following call if you replace the card). Don't
57
+ (re-apply its `clip.setKeyframes` if you replace the card). Don't
58
58
  re-edit the story; the story is the master's.
59
59
  5. **Confirm the audio carried.** The master's decided audio — bed, ducking, beat-aligned cuts
60
60
  (`materializeGrid` markers), or its intentional silence — rides into each sibling. Verify
@@ -55,8 +55,8 @@ affected window and regressions without automatically rerunning the full-film ju
55
55
  `frame-center` in the gaps, tiling every clip. The single biggest amateur-to-pro delta in
56
56
  product video.
57
57
  5. **Cards for the claims and the end card.** Key lines and the closing CTA are `kind:"card"`
58
- HTML in the brand's type and palette — persisted first, then shelled with
59
- `applyMotionPreset` in a FOLLOWING `apply_composition` call. One CTA on the end card, held
58
+ HTML in the brand's type and palette, animated in its CSS or with
59
+ `clip.setKeyframes` in a FOLLOWING `apply_composition` call. One CTA on the end card, held
60
60
  long enough to read, plus the date if there is one.
61
61
  6. **Audio when requested or appropriate.** Preserve explicit silence; otherwise: `search_resources` for a bed honoring the brand kit's
62
62
  `musicMood`, energy arcing toward the reveal; place it `audioRole:"music"`,
@@ -58,8 +58,8 @@ affected window and regressions without automatically rerunning the full-film ju
58
58
  `active-speaker` or `face` focus when a person carries it, `point` focus on the action
59
59
  otherwise. A wide shot letterboxed into 9:16 is a fail, not a fallback.
60
60
  5. **Author the type as cards.** Brand text is `kind:"card"` HTML — your typography,
61
- palette-as-fields, considered alignment — persisted first, then shelled with
62
- `applyMotionPreset` in a FOLLOWING `apply_composition` call. Catalog text primitives are
61
+ palette-as-fields, considered alignment — animated in its CSS or with
62
+ `clip.setKeyframes` in a FOLLOWING `apply_composition` call. Catalog text primitives are
63
63
  utility only.
64
64
  6. **Honor requested silence or existing audio.** When a bed is appropriate, search a bed with `search_resources` honoring
65
65
  the brand kit's `musicMood`, place it as an `audioRole:"music"` clip, then `materializeGrid`
@@ -61,8 +61,8 @@ keep the final re-render separate from previews used to resolve individual styli
61
61
  hex is silent, which is why you hunt it by eye.
62
62
  5. **Fix the holdouts surgically.** `apply_composition` edits per offending clip: rebind card
63
63
  fields to `$brand:` tokens (so the NEXT rebrand really is one line), swap the old-wordmark
64
- media, re-grade footage toward the new palette. A replaced card gets re-shelled with
65
- `applyMotionPreset` in a FOLLOWING call. If the requested scope is styling, retain its layout; rebuild only for an explicitly requested redesign.
64
+ media, re-grade footage toward the new palette. A replaced card gets its
65
+ `clip.setKeyframes` re-applied in a FOLLOWING call. If the requested scope is styling, retain its layout; rebuild only for an explicitly requested redesign.
66
66
  6. **Review audio only when in scope.** A styling correction preserves existing audio and timing; for a broader brand change: check the bed against the
67
67
  new kit's `musicMood`. Wrong mood → `search_resources` a replacement (license recorded),
68
68
  same role and ducking, `materializeGrid` re-run if cut timing shifts. Still right → say so
@@ -45,8 +45,8 @@ motion and listen to affected audio, not only stills or judge scores. Keep the u
45
45
  scope and acceptance criteria; approved simulated UI is not evidence of a real product capture.
46
46
 
47
47
  - **DEFAULT** Brand/text graphics are AUTHORED — `kind:"card"` HTML (your
48
- typography, layout, palette-as-fields) shelled with `applyMotionPreset`,
49
- or a custom component. Catalog text primitives are for utility only.
48
+ typography, layout, palette-as-fields) animated in CSS or with `clip.setKeyframes`,
49
+ or a native component (`create_component`). Catalog text primitives are for utility only.
50
50
  **Consider:** a brand graphic rendered from a catalog text primitive.
51
51
  - **DEFAULT** Audio is decided, never defaulted: a music bed (curated packs via
52
52
  `import_resource {kind:"music-pack"}`) or ambience/loops (Freesound via
package/package.json CHANGED
@@ -1,18 +1,17 @@
1
1
  {
2
2
  "name": "cueframe",
3
- "version": "0.8.0",
4
- "description": "Agent-native CLI for CueFrame — author compositions and render video from the terminal (early-access preview).",
3
+ "version": "0.8.2",
4
+ "description": "CueFrame video editing CLI: manage media, edit compositions and render MP4s from terminals and scripts.",
5
5
  "keywords": [
6
6
  "cueframe",
7
- "video",
8
- "render",
7
+ "video-editing",
8
+ "video-rendering",
9
9
  "composition",
10
10
  "cli",
11
- "agent",
12
- "ffmpeg",
13
- "remotion"
11
+ "automation",
12
+ "mcp"
14
13
  ],
15
- "homepage": "https://docs.cueframe.ai",
14
+ "homepage": "https://cueframe.ai/cli",
16
15
  "author": "CueFrame, Inc.",
17
16
  "license": "Apache-2.0",
18
17
  "type": "module",
@@ -46,8 +45,8 @@
46
45
  "@types/node": "^24.0.0",
47
46
  "tsup": "^8.5.1",
48
47
  "vitest": "^4.1.2",
49
- "@cueframe/sdk": "^0.6.1",
50
- "@cueframe/skills": "0.2.2"
48
+ "@cueframe/sdk": "^0.7.0",
49
+ "@cueframe/skills": "0.3.0"
51
50
  },
52
51
  "repository": {
53
52
  "type": "git",
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env node
2
- import{E as a}from"./chunk-ZRBY7WBO.js";import"./chunk-3QSA3EBY.js";export{a as analyzeCommand};
@@ -1,12 +0,0 @@
1
- #!/usr/bin/env node
2
- import{a as g}from"./chunk-3QSA3EBY.js";var u=class extends Error{status;code;details;requestId;constructor(t,o,n){super(o.message),this.name="ApiError",this.status=t,this.code=o.code,this.details=o.details,this.requestId=n}},v=class extends Error{url;timeoutMs;constructor(t,o){super(`Request timed out after ${o}ms: ${t}`),this.name="TimeoutError",this.url=t,this.timeoutMs=o}},q=class extends Error{url;constructor(t,o){super(`Request failed before a response: ${t}`,{cause:o}),this.name="NetworkError",this.url=t}},y={baseUrl:""};function I(e){return y={timeoutMs:3e4,...e},y}function w(){return y}function et(e,t){return/^https?:\/\//.test(t)?t:e.replace(/\/$/,"")+(t.startsWith("/")?t:`/${t}`)}async function F(e,t={}){if(!y.baseUrl)throw new Error("@cueframe/sdk: createClient({baseUrl}) must be called before issuing requests.");let o=y.fetch??fetch,n=et(y.baseUrl,e),r=new Headers({accept:"application/json"});for(let[c,f]of Object.entries(y.headers??{}))r.set(c,f);t.headers&&new Headers(t.headers).forEach((c,f)=>r.set(f,c)),y.apiKey&&!r.has("authorization")&&r.set("authorization",`Bearer ${y.apiKey}`);let s=y.timeoutMs??3e4,i=new AbortController,p=setTimeout(()=>i.abort(),s),d=t.signal?AbortSignal.any([t.signal,i.signal]):i.signal;try{let c=await o(n,{...t,headers:r,signal:d}),f={};c.headers.forEach((S,x)=>{f[x.toLowerCase()]=S});let l=null;if(c.status!==204)if((c.headers.get("content-type")??"").includes("application/json"))try{l=await c.json()}catch{l=null}else l=await c.text();return{status:c.status,statusText:c.statusText,headers:f,body:l,requestId:f["x-request-id"]}}catch(c){throw t.signal?.aborted?c:i.signal.aborted?new v(n,s):new q(n,c)}finally{clearTimeout(p)}}async function a(e,t={}){let o=await F(e,t);if(o.status>=400){let n;throw o.body&&typeof o.body=="object"&&"error"in o.body?n=o.body.error??{code:"unknown",message:o.statusText}:n={code:"unknown",message:o.statusText},new u(o.status,n,o.requestId)}if(o.status!==204)return o.body}var tt={unauthorized:"The credential is invalid or expired for this target, or it belongs to a different environment. Re-authenticate the way you connected: reconnect the MCP server, run `cueframe login` in a terminal, or supply a current API key.",forbidden:"The credential's permissions don't cover this operation. The server message above names the exact missing ones and the `permissions` body that grants them; a key's permissions are fixed at creation, so this needs a NEW key, not a change to this one.",not_found:"No such resource in this org (cross-org access also reads as 404). Check the id, or enumerate with the matching list command/tool.",media_not_ready:"Media is still processing. Wait for the `media.transcribed` webhook or retry after ~30s.",suggestions_already_complete:"Suggestions already exist for this media item. To re-run, call DELETE /v1/media/:id/suggestions first to reset state.",precondition_required:"Fetch the resource with GET first to obtain its ETag, then resend the write with `If-Match: <etag>`. `cueframe composition get` returns one for you.",stale_etag:"Resource was modified since your last GET \u2014 `details.currentEtag` is the value to thread next. Re-GET (or run `cueframe composition get`) and rebase your edits before retrying.",conflict:"ETag mismatch \u2014 the resource was modified since your last GET. Re-fetch and merge before retrying.",media_in_use:"Media is referenced by other resources. See `details.projectIds` and `details.sessionIds` \u2014 remove those references before deleting.",session_in_use:"Session is referenced by a composition. See `details.projectIds` \u2014 remove those references before deleting.",render_not_cancellable:"Render is already in a terminal state (complete / error / cancelled) and cannot be cancelled.",rate_limited:"Rate limit exceeded. Back off using the `retry-after` response header.",invalid_composition:"Composition violates an invariant (see `details`). Common ones: clip_source_track_kind_mismatch, track_clips_overlap, transition_no_predecessor.",clip_not_found:"The clip suggestion publicId doesn't exist in this org. Run `cueframe clips <mediaItemId>` to enumerate valid sug_* ids.",service_unavailable:"The processing pipeline is temporarily unavailable. Retry shortly; if it persists, contact support@cueframe.ai with the requestId.",payment_required:"The request exceeds a billing limit. For generation, the estimated cost is over the per-call ceiling (see `details.estimateUsd` / `details.ceilingUsd`) \u2014 lower the requested duration or raise the ceiling."};function j(e){return e instanceof u?tt[e.code]??null:null}async function*ot(e,t,o){let n=w();if(!n?.baseUrl)throw new Error(`@cueframe/sdk: createClient({baseUrl}) must be called before ${t}.`);let r=n.baseUrl.replace(/\/$/,"")+e,s=new Headers({accept:"text/event-stream",...n.headers??{}});n.apiKey&&!s.has("authorization")&&s.set("authorization",`Bearer ${n.apiKey}`);let p=await(n.fetch??fetch)(r,{method:"GET",headers:s,signal:o.signal});if(!p.ok){let l;try{l=(await p.json()).error??{code:"unknown",message:p.statusText}}catch{l={code:"unknown",message:p.statusText}}throw new u(p.status,l,p.headers.get("x-request-id")??void 0)}if(!p.body)throw new Error("SSE response has no body");let d=p.body.getReader(),c=new TextDecoder("utf-8"),f="";try{for(;;){let{value:l,done:S}=await d.read();if(S)break;f+=c.decode(l,{stream:!0});let x=f.indexOf(`
3
-
4
- `);for(;x!==-1;){let Qe=f.slice(0,x);f=f.slice(x+2);let B=rt(Qe);B&&(yield B),x=f.indexOf(`
5
-
6
- `)}}}finally{try{d.releaseLock()}catch{}}}function rt(e){if(!e.trim())return null;let t=null,o=[];for(let n of e.split(`
7
- `)){if(!n||n.startsWith(":"))continue;let r=n.indexOf(":");if(r===-1)continue;let s=n.slice(0,r),i=n.slice(r+1).replace(/^ /,"");s==="event"?t=i:s==="data"&&o.push(i)}return!t||o.length===0?null:{event:t,data:o.join(`
8
- `)}}async function*nt(e,t,o={}){let n=`/v1/projects/${encodeURIComponent(e)}/renders/${encodeURIComponent(t)}/stream`;for await(let r of ot(n,"iterRenderEvents",o)){let s=st(r);s&&(yield s)}}function st(e){let{event:t,data:o}=e;if(o===null||t!=="progress"&&t!=="complete"&&t!=="error")return null;let n;try{n=JSON.parse(o)}catch{return null}return{event:t,data:n}}var b={};g(b,{composeFromSuggestion:()=>dt,getComposeFromSuggestionUrl:()=>G,getListMediaSuggestionsUrl:()=>L,getResetMediaSuggestionsUrl:()=>$,getSuggestBriefsUrl:()=>N,listMediaSuggestions:()=>ut,resetMediaSuggestions:()=>yt,suggestBriefs:()=>lt});var G=e=>`/v1/projects/${e}/composition/from-suggestion`,dt=async(e,t,o)=>{let n=r=>{if(!r)return{};if(r instanceof Headers)return Object.fromEntries(r.entries());if(Symbol.iterator in r)return Object.fromEntries(Array.from(r,i=>Array.from(i)));let s={};for(let[i,p]of Object.entries(r))p!==void 0&&(s[i]=p);return s};return a(G(e),{...o,method:"POST",headers:{"Content-Type":"application/json",...n(o?.headers)},body:JSON.stringify(t)})},L=(e,t)=>{let o=new URLSearchParams;Object.entries(t||{}).forEach(([r,s])=>{s!==void 0&&o.append(r,s===null?"null":String(s))});let n=o.toString();return n.length>0?`/v1/media/${e}/suggestions?${n}`:`/v1/media/${e}/suggestions`},ut=async(e,t,o)=>a(L(e,t),{...o,method:"GET"}),N=e=>`/v1/media/${e}/suggestions`,lt=async(e,t)=>a(N(e),{...t,method:"POST"}),$=e=>`/v1/media/${e}/suggestions`,yt=async(e,t)=>a($(e),{...t,method:"DELETE"});var X={};g(X,{createComponent:()=>Rt,getComponent:()=>bt,getComponentSource:()=>ht,getCreateComponentUrl:()=>_,getGetComponentSourceUrl:()=>z,getGetComponentUrl:()=>V,getListCatalogUrl:()=>J,getPatchComponentUrl:()=>W,getResolveBuiltinComponentUrl:()=>D,getUpdateComponentUrl:()=>H,listCatalog:()=>xt,patchComponent:()=>St,resolveBuiltinComponent:()=>gt,updateComponent:()=>Ct});var D=e=>{let t=new URLSearchParams;Object.entries(e||{}).forEach(([n,r])=>{r!==void 0&&t.append(n,r===null?"null":String(r))});let o=t.toString();return o.length>0?`/v1/components/builtin/resolve?${o}`:"/v1/components/builtin/resolve"},gt=async(e,t)=>a(D(e),{...t,method:"GET"}),J=e=>{let t=new URLSearchParams;Object.entries(e||{}).forEach(([n,r])=>{r!==void 0&&t.append(n,r===null?"null":String(r))});let o=t.toString();return o.length>0?`/v1/components?${o}`:"/v1/components"},xt=async(e,t)=>a(J(e),{...t,method:"GET"}),_=()=>"/v1/components",Rt=async(e,t)=>{let o=n=>{if(!n)return{};if(n instanceof Headers)return Object.fromEntries(n.entries());if(Symbol.iterator in n)return Object.fromEntries(Array.from(n,s=>Array.from(s)));let r={};for(let[s,i]of Object.entries(n))i!==void 0&&(r[s]=i);return r};return a(_(),{...t,method:"POST",headers:{"Content-Type":"application/json",...o(t?.headers)},body:JSON.stringify(e)})},H=e=>`/v1/components/${e}`,Ct=async(e,t,o)=>{let n=r=>{if(!r)return{};if(r instanceof Headers)return Object.fromEntries(r.entries());if(Symbol.iterator in r)return Object.fromEntries(Array.from(r,i=>Array.from(i)));let s={};for(let[i,p]of Object.entries(r))p!==void 0&&(s[i]=p);return s};return a(H(e),{...o,method:"PUT",headers:{"Content-Type":"application/json",...n(o?.headers)},body:JSON.stringify(t)})},W=e=>`/v1/components/${e}`,St=async(e,t,o)=>{let n=r=>{if(!r)return{};if(r instanceof Headers)return Object.fromEntries(r.entries());if(Symbol.iterator in r)return Object.fromEntries(Array.from(r,i=>Array.from(i)));let s={};for(let[i,p]of Object.entries(r))p!==void 0&&(s[i]=p);return s};return a(W(e),{...o,method:"PATCH",headers:{"Content-Type":"application/json",...n(o?.headers)},body:JSON.stringify(t)})},V=e=>`/v1/components/${e}`,bt=async(e,t)=>a(V(e),{...t,method:"GET"}),z=(e,t)=>{let o=new URLSearchParams;Object.entries(t||{}).forEach(([r,s])=>{s!==void 0&&o.append(r,s===null?"null":String(s))});let n=o.toString();return n.length>0?`/v1/components/${e}/source?${n}`:`/v1/components/${e}/source`},ht=async(e,t,o)=>a(z(e,t),{...o,method:"GET"});var te={};g(te,{cancelExport:()=>At,createFcpxmlExport:()=>It,createPremiereExport:()=>wt,getCancelExportUrl:()=>ee,getCreateFcpxmlExportUrl:()=>Z,getCreatePremiereExportUrl:()=>Y,getExport:()=>jt,getGetExportUrl:()=>Q});var Z=e=>`/v1/projects/${e}/exports/fcpxml`,It=async(e,t,o)=>{let n=r=>{if(!r)return{};if(r instanceof Headers)return Object.fromEntries(r.entries());if(Symbol.iterator in r)return Object.fromEntries(Array.from(r,i=>Array.from(i)));let s={};for(let[i,p]of Object.entries(r))p!==void 0&&(s[i]=p);return s};return a(Z(e),{...o,method:"POST",headers:{"Content-Type":"application/json",...n(o?.headers)},body:JSON.stringify(t)})},Y=e=>`/v1/projects/${e}/exports/premiere`,wt=async(e,t,o)=>{let n=r=>{if(!r)return{};if(r instanceof Headers)return Object.fromEntries(r.entries());if(Symbol.iterator in r)return Object.fromEntries(Array.from(r,i=>Array.from(i)));let s={};for(let[i,p]of Object.entries(r))p!==void 0&&(s[i]=p);return s};return a(Y(e),{...o,method:"POST",headers:{"Content-Type":"application/json",...n(o?.headers)},body:JSON.stringify(t)})},Q=(e,t)=>`/v1/projects/${e}/exports/${t}`,jt=async(e,t,o)=>a(Q(e,t),{...o,method:"GET"}),ee=(e,t)=>`/v1/projects/${e}/exports/${t}/cancel`,At=async(e,t,o)=>a(ee(e,t),{...o,method:"POST"});var Ce={};g(Ce,{analyzeTimeline:()=>Yt,analyzeTranscript:()=>Zt,createMediaUpload:()=>Ft,createUploadToken:()=>Bt,deleteMedia:()=>Dt,finalizeMedia:()=>Lt,generateMedia:()=>Nt,getAnalyzeTimelineUrl:()=>Re,getAnalyzeTranscriptUrl:()=>xe,getCreateMediaUploadUrl:()=>ne,getCreateUploadTokenUrl:()=>re,getDeleteMediaUrl:()=>ce,getFinalizeMediaUrl:()=>ie,getGenerateMediaUrl:()=>ae,getGetMediaContextUrl:()=>de,getGetMediaTranscriptUrl:()=>me,getGetMediaUrl:()=>fe,getImportMediaUrl:()=>pe,getListMediaFactsUrl:()=>ye,getListMediaUrl:()=>se,getMedia:()=>Jt,getMediaContext:()=>Ht,getMediaTranscript:()=>_t,getPurchaseMediaFactUrl:()=>ge,getSearchLibraryUrl:()=>oe,getTriggerMatteBakeUrl:()=>le,getTriggerSubjectDetectionUrl:()=>ue,importMedia:()=>$t,listMedia:()=>Gt,listMediaFacts:()=>zt,purchaseMediaFact:()=>Xt,searchLibrary:()=>Kt,triggerMatteBake:()=>Vt,triggerSubjectDetection:()=>Wt});var oe=e=>{let t=new URLSearchParams;Object.entries(e||{}).forEach(([n,r])=>{r!==void 0&&t.append(n,r===null?"null":String(r))});let o=t.toString();return o.length>0?`/v1/media/search?${o}`:"/v1/media/search"},Kt=async(e,t)=>a(oe(e),{...t,method:"GET"}),re=()=>"/v1/uploads/token",Bt=async(e,t)=>{let o=n=>{if(!n)return{};if(n instanceof Headers)return Object.fromEntries(n.entries());if(Symbol.iterator in n)return Object.fromEntries(Array.from(n,s=>Array.from(s)));let r={};for(let[s,i]of Object.entries(n))i!==void 0&&(r[s]=i);return r};return a(re(),{...t,method:"POST",headers:{"Content-Type":"application/json",...o(t?.headers)},body:JSON.stringify(e)})},ne=()=>"/v1/media",Ft=async(e,t)=>{let o=n=>{if(!n)return{};if(n instanceof Headers)return Object.fromEntries(n.entries());if(Symbol.iterator in n)return Object.fromEntries(Array.from(n,s=>Array.from(s)));let r={};for(let[s,i]of Object.entries(n))i!==void 0&&(r[s]=i);return r};return a(ne(),{...t,method:"POST",headers:{"Content-Type":"application/json",...o(t?.headers)},body:JSON.stringify(e)})},se=e=>{let t=new URLSearchParams;Object.entries(e||{}).forEach(([n,r])=>{r!==void 0&&t.append(n,r===null?"null":String(r))});let o=t.toString();return o.length>0?`/v1/media?${o}`:"/v1/media"},Gt=async(e,t)=>a(se(e),{...t,method:"GET"}),ie=e=>`/v1/media/${e}/finalize`,Lt=async(e,t,o)=>{let n=r=>{if(!r)return{};if(r instanceof Headers)return Object.fromEntries(r.entries());if(Symbol.iterator in r)return Object.fromEntries(Array.from(r,i=>Array.from(i)));let s={};for(let[i,p]of Object.entries(r))p!==void 0&&(s[i]=p);return s};return a(ie(e),{...o,method:"POST",headers:{"Content-Type":"application/json",...n(o?.headers)},body:JSON.stringify(t)})},ae=()=>"/v1/media/generate",Nt=async(e,t)=>{let o=n=>{if(!n)return{};if(n instanceof Headers)return Object.fromEntries(n.entries());if(Symbol.iterator in n)return Object.fromEntries(Array.from(n,s=>Array.from(s)));let r={};for(let[s,i]of Object.entries(n))i!==void 0&&(r[s]=i);return r};return a(ae(),{...t,method:"POST",headers:{"Content-Type":"application/json",...o(t?.headers)},body:JSON.stringify(e)})},pe=()=>"/v1/media/import",$t=async(e,t)=>{let o=n=>{if(!n)return{};if(n instanceof Headers)return Object.fromEntries(n.entries());if(Symbol.iterator in n)return Object.fromEntries(Array.from(n,s=>Array.from(s)));let r={};for(let[s,i]of Object.entries(n))i!==void 0&&(r[s]=i);return r};return a(pe(),{...t,method:"POST",headers:{"Content-Type":"application/json",...o(t?.headers)},body:JSON.stringify(e)})},ce=e=>`/v1/media/${e}`,Dt=async(e,t)=>a(ce(e),{...t,method:"DELETE"}),fe=e=>`/v1/media/${e}`,Jt=async(e,t)=>a(fe(e),{...t,method:"GET"}),me=(e,t)=>{let o=new URLSearchParams;Object.entries(t||{}).forEach(([r,s])=>{s!==void 0&&o.append(r,s===null?"null":String(s))});let n=o.toString();return n.length>0?`/v1/media/${e}/transcript?${n}`:`/v1/media/${e}/transcript`},_t=async(e,t,o)=>a(me(e,t),{...o,method:"GET"}),de=e=>`/v1/media/${e}/context`,Ht=async(e,t)=>a(de(e),{...t,method:"GET"}),ue=(e,t)=>{let o=new URLSearchParams;Object.entries(t||{}).forEach(([r,s])=>{s!==void 0&&o.append(r,s===null?"null":String(s))});let n=o.toString();return n.length>0?`/v1/media/${e}/detect-subjects?${n}`:`/v1/media/${e}/detect-subjects`},Wt=async(e,t,o)=>a(ue(e,t),{...o,method:"POST"}),le=e=>`/v1/media/${e}/matte`,Vt=async(e,t,o)=>{let n=r=>{if(!r)return{};if(r instanceof Headers)return Object.fromEntries(r.entries());if(Symbol.iterator in r)return Object.fromEntries(Array.from(r,i=>Array.from(i)));let s={};for(let[i,p]of Object.entries(r))p!==void 0&&(s[i]=p);return s};return a(le(e),{...o,method:"POST",headers:{"Content-Type":"application/json",...n(o?.headers)},body:JSON.stringify(t)})},ye=(e,t)=>{let o=new URLSearchParams;Object.entries(t||{}).forEach(([r,s])=>{s!==void 0&&o.append(r,s===null?"null":String(s))});let n=o.toString();return n.length>0?`/v1/media/${e}/facts?${n}`:`/v1/media/${e}/facts`},zt=async(e,t,o)=>a(ye(e,t),{...o,method:"GET"}),ge=e=>`/v1/media/${e}/facts`,Xt=async(e,t,o)=>{let n=r=>{if(!r)return{};if(r instanceof Headers)return Object.fromEntries(r.entries());if(Symbol.iterator in r)return Object.fromEntries(Array.from(r,i=>Array.from(i)));let s={};for(let[i,p]of Object.entries(r))p!==void 0&&(s[i]=p);return s};return a(ge(e),{...o,method:"POST",headers:{"Content-Type":"application/json",...n(o?.headers)},body:JSON.stringify(t)})},xe=e=>`/v1/media/${e}/transcript/analyze`,Zt=async(e,t,o)=>{let n=r=>{if(!r)return{};if(r instanceof Headers)return Object.fromEntries(r.entries());if(Symbol.iterator in r)return Object.fromEntries(Array.from(r,i=>Array.from(i)));let s={};for(let[i,p]of Object.entries(r))p!==void 0&&(s[i]=p);return s};return a(xe(e),{...o,method:"POST",headers:{"Content-Type":"application/json",...n(o?.headers)},body:JSON.stringify(t)})},Re=()=>"/v1/timeline-analysis",Yt=async(e,t)=>{let o=n=>{if(!n)return{};if(n instanceof Headers)return Object.fromEntries(n.entries());if(Symbol.iterator in n)return Object.fromEntries(Array.from(n,s=>Array.from(s)));let r={};for(let[s,i]of Object.entries(n))i!==void 0&&(r[s]=i);return r};return a(Re(),{...t,method:"POST",headers:{"Content-Type":"application/json",...o(t?.headers)},body:JSON.stringify(e)})};var ke={};g(ke,{createProject:()=>eo,deleteProject:()=>ro,getCreateProjectUrl:()=>be,getDeleteProjectUrl:()=>ve,getGetProjectContextUrl:()=>qe,getGetProjectUrl:()=>he,getListProjectsUrl:()=>Se,getPatchProjectUrl:()=>Pe,getProject:()=>to,getProjectContext:()=>no,listProjects:()=>Qt,patchProject:()=>oo});var Se=e=>{let t=new URLSearchParams;Object.entries(e||{}).forEach(([n,r])=>{r!==void 0&&t.append(n,r===null?"null":String(r))});let o=t.toString();return o.length>0?`/v1/projects?${o}`:"/v1/projects"},Qt=async(e,t)=>a(Se(e),{...t,method:"GET"}),be=()=>"/v1/projects",eo=async(e,t)=>{let o=n=>{if(!n)return{};if(n instanceof Headers)return Object.fromEntries(n.entries());if(Symbol.iterator in n)return Object.fromEntries(Array.from(n,s=>Array.from(s)));let r={};for(let[s,i]of Object.entries(n))i!==void 0&&(r[s]=i);return r};return a(be(),{...t,method:"POST",headers:{"Content-Type":"application/json",...o(t?.headers)},body:JSON.stringify(e)})},he=e=>`/v1/projects/${e}`,to=async(e,t)=>a(he(e),{...t,method:"GET"}),Pe=e=>`/v1/projects/${e}`,oo=async(e,t,o)=>{let n=r=>{if(!r)return{};if(r instanceof Headers)return Object.fromEntries(r.entries());if(Symbol.iterator in r)return Object.fromEntries(Array.from(r,i=>Array.from(i)));let s={};for(let[i,p]of Object.entries(r))p!==void 0&&(s[i]=p);return s};return a(Pe(e),{...o,method:"PATCH",headers:{"Content-Type":"application/json",...n(o?.headers)},body:JSON.stringify(t)})},ve=e=>`/v1/projects/${e}`,ro=async(e,t)=>a(ve(e),{...t,method:"DELETE"}),qe=e=>`/v1/projects/${e}/context`,no=async(e,t)=>a(qe(e),{...t,method:"GET"});var Ue={};g(Ue,{cancelRender:()=>co,createRender:()=>so,getCancelRenderUrl:()=>je,getCreateRenderUrl:()=>Ee,getGetRenderUrl:()=>we,getListProjectRendersUrl:()=>Te,getPreviewClipUrl:()=>Ie,getRefreshRenderUrlUrl:()=>Oe,getRender:()=>po,getRetryRenderUrl:()=>Ae,getStreamRenderUrl:()=>Me,listProjectRenders:()=>io,previewClip:()=>ao,refreshRenderUrl:()=>mo,retryRender:()=>fo,streamRender:()=>uo});var Ee=e=>`/v1/projects/${e}/renders`,so=async(e,t,o)=>{let n=r=>{if(!r)return{};if(r instanceof Headers)return Object.fromEntries(r.entries());if(Symbol.iterator in r)return Object.fromEntries(Array.from(r,i=>Array.from(i)));let s={};for(let[i,p]of Object.entries(r))p!==void 0&&(s[i]=p);return s};return a(Ee(e),{...o,method:"POST",headers:{"Content-Type":"application/json",...n(o?.headers)},body:JSON.stringify(t)})},Te=(e,t)=>{let o=new URLSearchParams;Object.entries(t||{}).forEach(([r,s])=>{s!==void 0&&o.append(r,s===null?"null":String(s))});let n=o.toString();return n.length>0?`/v1/projects/${e}/renders?${n}`:`/v1/projects/${e}/renders`},io=async(e,t,o)=>a(Te(e,t),{...o,method:"GET"}),Ie=e=>`/v1/projects/${e}/preview-clip`,ao=async(e,t,o)=>{let n=r=>{if(!r)return{};if(r instanceof Headers)return Object.fromEntries(r.entries());if(Symbol.iterator in r)return Object.fromEntries(Array.from(r,i=>Array.from(i)));let s={};for(let[i,p]of Object.entries(r))p!==void 0&&(s[i]=p);return s};return a(Ie(e),{...o,method:"POST",headers:{"Content-Type":"application/json",...n(o?.headers)},body:JSON.stringify(t)})},we=(e,t)=>`/v1/projects/${e}/renders/${t}`,po=async(e,t,o)=>a(we(e,t),{...o,method:"GET"}),je=(e,t)=>`/v1/projects/${e}/renders/${t}/cancel`,co=async(e,t,o)=>a(je(e,t),{...o,method:"POST"}),Ae=(e,t)=>`/v1/projects/${e}/renders/${t}/retry`,fo=async(e,t,o)=>a(Ae(e,t),{...o,method:"POST"}),Oe=(e,t)=>`/v1/projects/${e}/renders/${t}/refresh-url`,mo=async(e,t,o)=>a(Oe(e,t),{...o,method:"POST"}),Me=(e,t)=>`/v1/projects/${e}/renders/${t}/stream`,uo=async(e,t,o)=>a(Me(e,t),{...o,method:"GET"});var $e={};g($e,{createSession:()=>xo,deleteSession:()=>Co,getCreateSessionUrl:()=>Be,getDeleteSessionUrl:()=>Ge,getGetMuxJobUrl:()=>Ne,getGetSessionUrl:()=>Fe,getListSessionsUrl:()=>Ke,getMuxJob:()=>bo,getSession:()=>Ro,getTriggerMuxJobUrl:()=>Le,listSessions:()=>go,triggerMuxJob:()=>So});var Ke=e=>{let t=new URLSearchParams;Object.entries(e||{}).forEach(([n,r])=>{r!==void 0&&t.append(n,r===null?"null":String(r))});let o=t.toString();return o.length>0?`/v1/sessions?${o}`:"/v1/sessions"},go=async(e,t)=>a(Ke(e),{...t,method:"GET"}),Be=()=>"/v1/sessions",xo=async(e,t)=>{let o=n=>{if(!n)return{};if(n instanceof Headers)return Object.fromEntries(n.entries());if(Symbol.iterator in n)return Object.fromEntries(Array.from(n,s=>Array.from(s)));let r={};for(let[s,i]of Object.entries(n))i!==void 0&&(r[s]=i);return r};return a(Be(),{...t,method:"POST",headers:{"Content-Type":"application/json",...o(t?.headers)},body:JSON.stringify(e)})},Fe=e=>`/v1/sessions/${e}`,Ro=async(e,t)=>a(Fe(e),{...t,method:"GET"}),Ge=e=>`/v1/sessions/${e}`,Co=async(e,t)=>a(Ge(e),{...t,method:"DELETE"}),Le=e=>`/v1/sessions/${e}/mux-job`,So=async(e,t)=>a(Le(e),{...t,method:"POST"}),Ne=e=>`/v1/sessions/${e}/mux-job`,bo=async(e,t)=>a(Ne(e),{...t,method:"GET"});var k="X-CueFrame-Client";var Eo=/^[A-Za-z0-9][A-Za-z0-9._+-]{0,31}$/,To=["web","console","clips"];function Io(e,t){return To.includes(e)===(t==="browser")}function De(e){if(!Eo.test(e.version))throw new Error(`${k} cannot carry the version ${JSON.stringify(e.version)}.`);if(!Io(e.surface,e.os))throw new Error(`${k} cannot carry ${e.surface} on ${e.os}.`);return`${e.surface}/${e.version}/${e.os}`}function Je(e){return e==="darwin"?"macos":e==="win32"?"windows":e==="linux"?"linux":"unknown"}import{readFileSync as wo,writeFileSync as jo,mkdirSync as Ao,existsSync as Oo}from"fs";import{join as _e}from"path";import{homedir as Mo}from"os";var m={Success:0,UserError:2,ApiClientError:3,ApiServerError:4,NetworkError:5,PaymentRequired:6};function A(e){if(e instanceof u){if(e.status===402)return m.PaymentRequired;if(e.status>=500)return m.ApiServerError;if(e.status>=400)return m.ApiClientError}let t=e?.exitCode;return typeof t=="number"?t:m.NetworkError}function DP(e){return e===402?m.PaymentRequired:e>=500?m.ApiServerError:e>=400?m.ApiClientError:m.Success}function h(e){process.exit(e)}var He=_e(Mo(),".cueframe"),E=_e(He,"auth.json"),R={local:"http://127.0.0.1:3211",prod:"https://api.cueframe.ai"},Uo="prod";function zP(e=Uo){return process.env.CUEFRAME_API_URL??R[e]??R.prod}var P=class extends Error{constructor(o,n){super(n);this.code=o;this.name="AuthStoreError"}code;exitCode=m.UserError};function XP(e){if(!/^cf_(?:live|test)_[A-Za-z0-9_-]{8,}$/.test(e))throw new P("auth_invalid","Invalid API key. Expected a key beginning with cf_live_ or cf_test_.")}function We(){if(!Oo(E))return{targets:{}};let e=wo(E,"utf-8");try{return JSON.parse(e)}catch{throw new P("auth_config_corrupt",`${E} is corrupt and cannot be parsed. Delete it and re-run \`cueframe login\`.`)}}function Ko(e){Ao(He,{recursive:!0,mode:448}),jo(E,JSON.stringify(e,null,2),{mode:384})}function ZP(e,t="prod",o){let n=We(),r=R[t]??R.prod;n.targets[t]={apiKey:e,apiUrl:o??r},Ko(n)}function Ve(e="prod"){let t=process.env.CUEFRAME_API_KEY,o=process.env.CUEFRAME_API_URL;if(t)return{apiKey:t,apiUrl:o??R[e]??R.prod};let r=We().targets[e];if(!r)throw new P("auth_missing",`No auth configured for target "${e}". Run \`cueframe login --target ${e}\` (browser sign-in) or \`cueframe auth <api-key> --target ${e}\` (CI / headless).`);return r}function Bo(){return{[k]:De({surface:"cli",version:"0.8.0",os:Je(process.platform)})}}function ze(e){let{apiKey:t,apiUrl:o}=Ve(e);I({baseUrl:o,apiKey:t,headers:Bo()})}function T(e){let t=e instanceof u?`${e.message} (${e.code}${e.status?`, ${e.status}`:""})`:e instanceof Error?e.message:String(e),o=j(e);return o?`${t}
9
- \u2192 ${o}`:t}import Fo from"ora";import Go from"pino";var Lo="v1",Xe=Go({level:"trace",base:{schemaVersion:Lo},messageKey:"event",timestamp:()=>`,"ts":${Date.now()}`,formatters:{level:()=>({_:0})}});function O(e,t){return Fo({text:e,isSilent:!!t}).start()}function C(e,t,o={}){e.json&&Xe.info(o,t)}function iv(e,t,o){if(e.json){console.log(JSON.stringify(t,null,2));return}o()}function M(e,t,o,n){let r=o instanceof Error?o.message:String(o),s=typeof o?.code=="string"?o.code:void 0,i=o?.status;e.json?Xe.info({phase:t,message:r,...s?{code:s}:{},...typeof i=="number"?{status:i}:{},exitCode:n},"error"):process.stderr.write(`\u2716 ${t}: ${T(o)}
10
- `)}function U(e,t,o,n){e.json&&M(e,t,o,n)}function av(e,t){t.json||console.log(e)}function No(e){for(let t=0;t<e.length;t++){let o=e.charCodeAt(t);if(o<=31||o===127)return!0}return!1}function Ye(e,t,o){e.length===0&&Ze(`${t} is empty.`,o),No(e)&&Ze(`${t} contains a control character (newline, tab, or null byte). Identifiers must be plain text \u2014 check for a stray copy-paste artifact.`,o)}function Ze(e,t){M(t,"preflight",new Error(e),m.UserError),h(m.UserError)}var $o=5e3,K=300*1e3;async function xv(e,t){if(Ye(e,"mediaItemId",t),t.dryRun){C(t,"analyze_dry_run",{mediaItemId:e,target:t.target,wait:!!t.wait,wouldCall:"POST /v1/media/:id/suggestions"}),t.json||console.log(`[dry-run] Would trigger clip analysis for ${e} on ${t.target}.`);return}ze(t.target),C(t,"analyze_prepare",{mediaItemId:e,target:t.target,wait:!!t.wait});let o=O("Triggering clip analysis\u2026",t.json),n;try{n=await b.suggestBriefs(e),o.succeed(`Analysis ${n.alreadyRunning?"already running":"started"}`)}catch(i){o.fail(`Failed to start analysis: ${T(i)}`),U(t,"analyze_prepare",i,A(i)),h(A(i))}if(C(t,"analyze_ready",{mediaItemId:e,status:n.status,alreadyRunning:!!n.alreadyRunning}),!t.wait){t.json||console.log(`
11
- Run \`cueframe clips ${e}\` to check results.`);return}let r=O("Waiting for analysis to complete\u2026",t.json),s=Date.now();for(;;){await new Promise(i=>setTimeout(i,$o)),Date.now()-s>K&&(r.fail(`Timed out after ${Math.round(K/1e3)}s. Run \`cueframe clips ${e}\` to check later.`),U(t,"analyze_progress",new Error(`Analysis did not complete within ${Math.round(K/1e3)}s`),m.UserError),h(m.UserError));try{let i=await b.listMediaSuggestions(e);if(i.suggestions.length>0){r.succeed(`Analysis complete \u2014 ${i.suggestions.length} clips found`),C(t,"analyze_complete",{mediaItemId:e,durationMs:Date.now()-s,suggestionCount:i.suggestions.length,suggestions:i.suggestions}),t.json||i.suggestions.forEach((d,c)=>{let f=(d.trim.end-d.trim.start).toFixed(1);console.log(`
12
- ${c+1}. ${d.title} (${d.id})`),console.log(` ${d.trim.start.toFixed(1)}s \u2013 ${d.trim.end.toFixed(1)}s (${f}s) score: ${d.score}`),console.log(` ${d.reason}`)});return}let p=Math.round((Date.now()-s)/1e3);r.text=`Waiting for analysis\u2026 (${p}s)`,C(t,"analyze_progress",{mediaItemId:e,elapsedSec:p})}catch{}}}export{u as a,I as b,F as c,a as d,j as e,nt as f,b as g,X as h,te as i,Ce as j,ke as k,Ue as l,$e as m,m as n,A as o,DP as p,h as q,zP as r,XP as s,ZP as t,Bo as u,ze as v,T as w,O as x,C as y,iv as z,M as A,U as B,av as C,Ye as D,xv as E};