@slatesvideo/shared 0.7.0 → 0.7.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.
@@ -1,5 +1,14 @@
1
1
  import { APP_MANUAL } from './content.js';
2
2
  export { APP_MANUAL };
3
- /** Return whole heading sections, never a truncated sentence or invented UI step. */
4
- export declare function appManualSections(query?: string): string;
3
+ /**
4
+ * A reader over one manual text. The package's own reads go through the shipped manual below; the
5
+ * retrieval check (`scripts/app-manual-retrieval-check.mjs`) reads slate's source manual the same way,
6
+ * before the generator has copied it here.
7
+ */
8
+ export declare function manualReader(manual: string): {
9
+ index: () => string;
10
+ sections: (query?: string) => string;
11
+ };
12
+ export declare const appManualIndex: () => string;
13
+ export declare const appManualSections: (query?: string) => string;
5
14
  //# sourceMappingURL=index.d.ts.map
@@ -1,20 +1,184 @@
1
1
  import { APP_MANUAL } from './content.js';
2
2
  export { APP_MANUAL };
3
- /** Return whole heading sections, never a truncated sentence or invented UI step. */
4
- export function appManualSections(query) {
3
+ /** Stop here when two sections are returned together (~1.5k tokens). */
4
+ const PAIR_CAP_CHARS = 6000;
5
+ const STOP = new Set(
6
+ // Question words and filler: "that has no video yet" should not steer toward a heading that says "yet".
7
+ 'a an and are as at be by can could do does don doesn for from get got had has have how i in into is isn it its just ll me my of on or re s should t than that the then there this to ve what when where which while who why will with won would you your'.split(' '));
8
+ /**
9
+ * One form per word, so the question's word meets the manual's: "saved", "saves" and "save" are one, as
10
+ * are "running" and "run", "categories" and "category". A light stemmer, not Porter: plural, then -ing or
11
+ * -ed, then a doubled final consonant, then a final e. Both sides go through it, so a crude root is fine.
12
+ */
13
+ function stem(word) {
14
+ let w = word;
15
+ if (w.length > 4 && w.endsWith('ies'))
16
+ w = `${w.slice(0, -3)}y`;
17
+ else if (w.length > 3 && w.endsWith('s') && !w.endsWith('ss'))
18
+ w = w.slice(0, -1);
19
+ if (w.length > 5 && w.endsWith('ing'))
20
+ w = w.slice(0, -3);
21
+ else if (w.length > 4 && w.endsWith('ed'))
22
+ w = w.slice(0, -2);
23
+ const last = w[w.length - 1];
24
+ if (w.length > 3 && last === w[w.length - 2] && !'lsz'.includes(last) && !/[aeiou0-9]/.test(last))
25
+ w = w.slice(0, -1);
26
+ if (w.length > 3 && w.endsWith('e'))
27
+ w = w.slice(0, -1);
28
+ return w;
29
+ }
30
+ /** Lowercase, split, drop stop words, stem. */
31
+ function terms(text) {
32
+ return (text.toLowerCase().match(/[\p{L}\p{N}]+/gu) ?? []).filter((w) => !STOP.has(w)).map(stem);
33
+ }
34
+ const pairs = (list) => new Set(list.slice(1).map((w, i) => `${list[i]} ${w}`));
35
+ const counts = (list) => {
36
+ const m = new Map();
37
+ for (const w of list)
38
+ m.set(w, (m.get(w) ?? 0) + 1);
39
+ return m;
40
+ };
41
+ const clean = (text) => text.replace(/<!--[\s\S]*?-->\n?/g, '').replace(/\n{3,}/g, '\n\n').trim();
42
+ const bare = (heading) => heading.replace(/^#+\s*/, '').trim();
43
+ /** The manual's sections, parsed once per text. */
44
+ function parse(manual) {
45
+ const start = manual.indexOf('<slates_reference>');
46
+ const end = manual.lastIndexOf('</slates_reference>');
47
+ const body = start >= 0 && end > start ? manual.slice(start + '<slates_reference>'.length, end) : manual;
48
+ const out = [];
49
+ let parent = null;
50
+ let current = null;
51
+ let fenced = false;
52
+ const flush = () => {
53
+ if (!current)
54
+ return;
55
+ const text = clean(current.lines.join('\n'));
56
+ const words = new Map();
57
+ for (const w of terms(text))
58
+ words.set(w, (words.get(w) ?? 0) + 1);
59
+ out.push({
60
+ heading: current.heading,
61
+ parent: current.parent,
62
+ level: current.level,
63
+ body: text,
64
+ words,
65
+ headingWords: counts(terms(current.heading)),
66
+ headingPairs: pairs(terms(current.heading)),
67
+ parentWords: new Set(terms(current.parent ?? '')),
68
+ });
69
+ };
70
+ for (const line of body.split(/\r?\n/)) {
71
+ if (/^\s*```/.test(line))
72
+ fenced = !fenced;
73
+ const m = fenced ? null : /^(#{1,4}) (.+)$/.exec(line);
74
+ // `#` (the version header) and `####` (a sub-point) stay inside the section they sit in.
75
+ if (m && (m[1].length === 2 || m[1].length === 3)) {
76
+ flush();
77
+ const level = m[1].length;
78
+ if (level === 2)
79
+ parent = m[2].trim();
80
+ current = { heading: m[2].trim(), level, parent: level === 3 ? parent : null, lines: [line] };
81
+ }
82
+ else if (current)
83
+ current.lines.push(line);
84
+ }
85
+ flush();
86
+ return out.filter((s) => s.body.replace(/^#+ .*$/m, '').trim().length > 0);
87
+ }
88
+ /** A `###` heading without its trailing synonyms: "Timeline: markers (add a marker, …)" is "Timeline: markers", which asks for it. */
89
+ const shortHeading = (heading) => heading.replace(/\s*\([^)]*\)\s*$/, '').trim() || heading;
90
+ /**
91
+ * The map, step one: every `##` surface on one line with the words it answers. Asking for a surface by
92
+ * the words before its dash returns its opening and its sections (step two); asking with the words of a
93
+ * question returns the one section that answers it. Each step is small.
94
+ */
95
+ function indexOf(all) {
96
+ const lines = [
97
+ 'Slates app manual. Ask with the words of the question for the section that answers it, or ask for a surface by its name (the words before the —) for its list of sections.',
98
+ '',
99
+ ];
100
+ for (const s of all)
101
+ if (s.level === 2)
102
+ lines.push(`## ${s.heading}`);
103
+ return lines.join('\n');
104
+ }
105
+ /** A surface's own sections, short, for step two. */
106
+ const sectionList = (all, surface) => all.filter((s) => s.parent === surface.heading).map((s) => shortHeading(s.heading)).join(' · ');
107
+ function score(s, qTerms, idf, phrase) {
108
+ let n = 0;
109
+ for (const t of new Set(qTerms)) {
110
+ const w = idf.get(t) ?? 0;
111
+ if (!w)
112
+ continue;
113
+ // A heading word counts 4, a second mention of it 2 more; the surface's words 1; the body up to 1.5.
114
+ const h = s.headingWords.get(t) ?? 0;
115
+ n += w * ((h ? 4 : 0) + (h > 1 ? 2 : 0) + (s.parentWords.has(t) ? 1 : 0) + Math.min(s.words.get(t) ?? 0, 3) * 0.5);
116
+ }
117
+ // Two question words side by side in the heading ("new project", "video from a picture") are the
118
+ // phrasing the heading was written for.
119
+ for (let i = 1; i < qTerms.length; i++) {
120
+ if (s.headingPairs.has(`${qTerms[i - 1]} ${qTerms[i]}`))
121
+ n += 2 * ((idf.get(qTerms[i - 1]) ?? 0) + (idf.get(qTerms[i]) ?? 0));
122
+ }
123
+ if (phrase.length > 3) {
124
+ if (s.heading.toLowerCase().includes(phrase))
125
+ n += 6;
126
+ else if (s.body.toLowerCase().includes(phrase))
127
+ n += 2;
128
+ }
129
+ return n;
130
+ }
131
+ const withContext = (s) => (s.parent ? `## ${s.parent}\n\n${s.body}` : s.body);
132
+ /**
133
+ * With no query, the whole manual. With a query, the best section (a second when it scores within 15%
134
+ * and both fit ~1.5k tokens), then the next headings to ask for. Whole sections only: never a cut
135
+ * sentence, never an invented step.
136
+ */
137
+ function sectionsFor(manual, all, query) {
5
138
  if (!query?.trim())
6
- return APP_MANUAL;
7
- const terms = query.toLowerCase().match(/[\p{L}\p{N}]+/gu) ?? [];
8
- const sections = APP_MANUAL.split(/(?=^#{1,6} )/m);
9
- const ranked = sections.map((text, index) => {
10
- const lower = text.toLowerCase();
11
- const heading = lower.split('\n', 1)[0];
12
- const score = terms.reduce((sum, term) => sum + (heading.includes(term) ? 4 : lower.includes(term) ? 1 : 0), 0);
13
- return { text, index, score };
14
- }).filter((s) => s.score > 0).sort((a, b) => b.score - a.score || a.index - b.index);
139
+ return manual;
140
+ const q = query.trim().toLowerCase();
141
+ const asked = q.replace(/^#+\s*/, '');
142
+ const exact = all.find((s) => s.heading.toLowerCase() === q || bare(s.heading).toLowerCase() === asked) ??
143
+ // A heading without its synonyms ("Timeline: markers"), or a surface by the words before its dash.
144
+ all.find((s) => s.heading.toLowerCase().replace(/\s*\([^)]*\)\s*$/, '') === asked) ??
145
+ all.find((s) => s.level === 2 && s.heading.split(' — ')[0].toLowerCase() === asked);
146
+ if (exact?.level === 3)
147
+ return withContext(exact);
148
+ if (exact) {
149
+ const list = sectionList(all, exact);
150
+ return list ? `${exact.body}\n\nIts sections (ask for one by name, or with the question's words): ${list}` : exact.body;
151
+ }
152
+ const qTerms = terms(query);
153
+ const df = new Map();
154
+ for (const t of new Set(qTerms))
155
+ df.set(t, all.filter((s) => s.words.has(t) || s.headingWords.has(t)).length);
156
+ const idf = new Map([...df].map(([t, d]) => [t, d ? Math.log(1 + all.length / d) : 0]));
157
+ const ranked = all
158
+ .map((s, index) => ({ s, index, score: score(s, qTerms, idf, q) }))
159
+ .filter((r) => r.score > 0)
160
+ .sort((a, b) => b.score - a.score || a.s.body.length - b.s.body.length || a.index - b.index);
15
161
  if (!ranked.length)
16
- return 'No matching manual section. Available headings:\n' + sections
17
- .map((s) => s.split('\n', 1)[0]).filter((s) => s.startsWith('#')).join('\n');
18
- return ranked.slice(0, 5).map((s) => s.text.trim()).join('\n\n');
162
+ return `No manual section matches "${query}". Available headings:\n\n${indexOf(all)}`;
163
+ const [best, second] = ranked;
164
+ const picked = [best];
165
+ if (second && second.score >= best.score * 0.85 && best.s.body.length + second.s.body.length <= PAIR_CAP_CHARS)
166
+ picked.push(second);
167
+ const related = ranked.slice(picked.length, picked.length + 4).map((r) => r.s.heading);
168
+ const text = picked.map((r) => withContext(r.s)).join('\n\n');
169
+ return related.length ? `${text}\n\nRelated sections (ask by heading): ${related.join(' · ')}` : text;
170
+ }
171
+ /**
172
+ * A reader over one manual text. The package's own reads go through the shipped manual below; the
173
+ * retrieval check (`scripts/app-manual-retrieval-check.mjs`) reads slate's source manual the same way,
174
+ * before the generator has copied it here.
175
+ */
176
+ export function manualReader(manual) {
177
+ let all = null;
178
+ const parsed = () => (all ??= parse(manual));
179
+ return { index: () => indexOf(parsed()), sections: (query) => sectionsFor(manual, parsed(), query) };
19
180
  }
181
+ const shipped = manualReader(APP_MANUAL);
182
+ export const appManualIndex = () => shipped.index();
183
+ export const appManualSections = (query) => shipped.sections(query);
20
184
  //# sourceMappingURL=index.js.map
@@ -159,9 +159,25 @@ export declare const getWorkspaceState: Operation<{
159
159
  * up at request time. Exists so "make these into videos" needs no codes typed.
160
160
  */
161
161
  export declare const getSelection: Operation<Record<string, never>>;
162
+ declare const SELECTION_TARGETS: readonly ["media", "board"];
163
+ declare const SELECTION_MODES: readonly ["replace", "add", "remove", "clear", "all"];
164
+ /**
165
+ * SET what is selected, as the user's clicks do: the cards ticked in Media, the Shots ticked on the Board.
166
+ * Pairs with `slates_get_selection`, which reads it: "tick the shots with no clip, then price them" is this op
167
+ * and then the quote. The desktop applies it through the stores the grids write and answers with what SETTLED.
168
+ */
169
+ export declare const setSelection: Operation<{
170
+ surface: (typeof SELECTION_TARGETS)[number];
171
+ mode: (typeof SELECTION_MODES)[number];
172
+ ids?: string[];
173
+ }>;
162
174
  declare const VIEW_LENSES: readonly ["board", "media", "script"];
163
175
  declare const VIEW_CUT_SIDES: readonly ["bottom", "left", "right"];
164
176
  declare const VIEW_DOCK_SECTIONS: readonly ["storyboards", "library", "folders", "pinned"];
177
+ declare const VIEW_BOARD_LEVELS: readonly ["film", "scenes", "shot"];
178
+ declare const VIEW_BOARD_FILTERS: readonly ["all", "with-clip", "without-clip"];
179
+ declare const VIEW_MEDIA_TABS: readonly ["all", "images", "videos", "audio"];
180
+ declare const VIEW_SETTINGS_PANES: readonly ["account", "ai", "storage", "logs", "general", "keys"];
165
181
  type DockSection = (typeof VIEW_DOCK_SECTIONS)[number];
166
182
  /**
167
183
  * How the Slates window is ARRANGED right now — which lens is showing, where
@@ -180,6 +196,7 @@ export declare const getView: Operation<Record<string, never>>;
180
196
  * the timeline full-screen instead, and says so.
181
197
  */
182
198
  export declare const setView: Operation<{
199
+ projectId?: string | null;
183
200
  lens?: (typeof VIEW_LENSES)[number];
184
201
  cut?: {
185
202
  open?: boolean;
@@ -187,6 +204,7 @@ export declare const setView: Operation<{
187
204
  side?: (typeof VIEW_CUT_SIDES)[number];
188
205
  height?: number;
189
206
  width?: number;
207
+ timelineId?: string;
190
208
  };
191
209
  leftDock?: {
192
210
  open?: boolean;
@@ -199,8 +217,145 @@ export declare const setView: Operation<{
199
217
  };
200
218
  script?: {
201
219
  details?: boolean;
220
+ textScale?: number;
221
+ panelShotId?: string | null;
222
+ };
223
+ board?: {
224
+ id?: string;
225
+ sceneId?: string;
226
+ level?: (typeof VIEW_BOARD_LEVELS)[number];
227
+ cardWidth?: number;
228
+ filter?: (typeof VIEW_BOARD_FILTERS)[number];
229
+ linkedClips?: boolean;
230
+ scriptFollowsDrag?: boolean;
231
+ collapsedSceneIds?: string[];
232
+ };
233
+ media?: {
234
+ tab?: (typeof VIEW_MEDIA_TABS)[number];
235
+ folderId?: string | null;
236
+ unfiledOnly?: boolean;
237
+ search?: string;
238
+ favoritesOnly?: boolean;
239
+ linkedOnly?: boolean;
240
+ rounds?: boolean;
241
+ cardSize?: number;
242
+ revealAssetId?: string;
243
+ };
244
+ library?: {
245
+ categoryId: string | null;
246
+ };
247
+ viewer?: {
248
+ assetId: string | null;
249
+ };
250
+ compare?: {
251
+ open?: boolean;
252
+ assetIds?: string[];
253
+ };
254
+ animatic?: {
255
+ open: boolean;
256
+ fromShotId?: string;
257
+ };
258
+ settings?: {
259
+ open: boolean;
260
+ pane?: (typeof VIEW_SETTINGS_PANES)[number];
261
+ };
262
+ composer?: {
263
+ open: boolean;
202
264
  };
203
265
  }>;
266
+ /**
267
+ * SHOW AND POINT (1.6.1). The manual's pictures and a ring around a live control, so an agent can
268
+ * answer "where is it?" with the thing itself instead of a paragraph. Neither changes the project or
269
+ * the view. The pictures come from the INSTALLED app (it ships them), so they show the user's version;
270
+ * the public URL is for a client that renders only links. Decisions 13-16 in second-brain
271
+ * plans/2026-09-30-slates-manual-for-llms-1-6-1-decisions.md.
272
+ */
273
+ export declare const getManualPicture: Operation<{
274
+ id?: string;
275
+ }>;
276
+ export declare const highlightControl: Operation<{
277
+ target?: string;
278
+ caption?: string;
279
+ list?: boolean;
280
+ }>;
281
+ export declare const getComposer: Operation<Record<string, never>>;
282
+ export declare const setComposer: Operation<{
283
+ projectId?: string;
284
+ bindShotId?: string | null;
285
+ clear?: boolean;
286
+ restoreDraft?: boolean;
287
+ restoreSetup?: boolean;
288
+ fromAssetId?: string;
289
+ editSourceAssetId?: string;
290
+ lane?: 'image' | 'video' | 'audio';
291
+ model?: string;
292
+ prompt?: string;
293
+ addMentions?: string[];
294
+ attach?: Array<{
295
+ assetId: string;
296
+ role?: string;
297
+ }>;
298
+ detach?: string[];
299
+ setRole?: Array<{
300
+ assetId: string;
301
+ role: string;
302
+ }>;
303
+ params?: Record<string, string | number | boolean>;
304
+ voice?: {
305
+ presetId: string;
306
+ } | {
307
+ clipAssetId: string;
308
+ } | {
309
+ description: string;
310
+ };
311
+ tool?: {
312
+ sourceAssetId?: string | null;
313
+ speechText?: string;
314
+ drivingVideoAssetId?: string | null;
315
+ characterImageAssetId?: string | null;
316
+ };
317
+ }>;
318
+ /**
319
+ * AGENT PARITY (1.6.1): what a user could do in the window and an agent could not. Each calls a desktop
320
+ * route that runs the window's own function (`slate/src/main/agent/routes-parity.ts`). Plan step 5b,
321
+ * second-brain plans/2026-09-30-slates-manual-for-llms-1-6-1.md.
322
+ */
323
+ export declare const reorderFolders: Operation<{
324
+ projectId: string;
325
+ folderIds: string[];
326
+ }>;
327
+ export declare const reorderPins: Operation<{
328
+ projectId: string;
329
+ assetIds: string[];
330
+ }>;
331
+ export declare const getUsage: Operation<{
332
+ period?: 'week' | 'month' | 'all';
333
+ }>;
334
+ export declare const getAppSettings: Operation<Record<string, never>>;
335
+ type AppSettingsPatch = {
336
+ newProjectLens?: 'media' | 'script' | 'board';
337
+ chatGptImagesEnabled?: boolean;
338
+ studioAgentHost?: 'slates' | 'codex' | 'claude';
339
+ studioAgentCodexModel?: string;
340
+ studioAgentCodexEffort?: string;
341
+ studioAgentClaudeModel?: string;
342
+ studioAgentClaudeEffort?: string;
343
+ };
344
+ export declare const setAppSettings: Operation<AppSettingsPatch>;
345
+ export declare const getAsset: Operation<{
346
+ projectId: string;
347
+ assetId: string;
348
+ }>;
349
+ export declare const linkAssetSource: Operation<{
350
+ projectId: string;
351
+ assetId: string;
352
+ imageAssetId: string | null;
353
+ }>;
354
+ export declare const extractVideoFrame: Operation<{
355
+ projectId: string;
356
+ assetId: string;
357
+ at?: 'first' | 'last' | number;
358
+ }>;
204
359
  export declare const getMe: Operation<Record<string, never>>;
205
360
  export declare const getCreditBalance: Operation<Record<string, never>>;
206
361
  export declare const listAvailableModels: Operation<{
@@ -265,6 +420,7 @@ export declare const generateChatGptImage: Operation<{
265
420
  aspectRatio?: string;
266
421
  referenceAssetIds?: string[];
267
422
  background?: boolean;
423
+ folderId?: string | null;
268
424
  }>;
269
425
  export declare const saveExternalImage: Operation<{
270
426
  projectId: string;
@@ -283,6 +439,7 @@ export declare const uploadReferenceImage: Operation<{
283
439
  filePath?: string;
284
440
  dataUrl?: string;
285
441
  type?: 'image' | 'video' | 'audio';
442
+ folderId?: string | null;
286
443
  }>;
287
444
  export declare const listFolders: Operation<{
288
445
  projectId: string;
@@ -435,6 +592,7 @@ export declare const generateImage: Operation<{
435
592
  referenceAssetIds?: string[];
436
593
  background?: boolean;
437
594
  confirm?: boolean;
595
+ folderId?: string | null;
438
596
  }>;
439
597
  export declare const editImage: Operation<{
440
598
  projectId: string;
@@ -449,6 +607,27 @@ export declare const editImage: Operation<{
449
607
  confirm?: boolean;
450
608
  background?: boolean;
451
609
  }>;
610
+ /**
611
+ * The picture viewer's EXTRACT (its Cells tab): a 2x2 or 3x3 grid picture, cells cropped out and re-rendered
612
+ * at full resolution, each as its own new picture. BILLABLE, and priced by the DESKTOP: its quote is the
613
+ * `imageCost` the viewer's Extract button prints, for this seat, rung and cell count, so the price an agent
614
+ * is shown cannot differ from the button's. The confirm gate is every billable op's: above CONFIRM_CREDITS
615
+ * with no `confirm`, the op answers with the price and the exact prompt, and spends nothing.
616
+ */
617
+ /** The seats the viewer's Cells box offers. Mirrors slate's `GRID_EXTRACT_MODELS` (`shared/prompts/storyboard-grids.ts`), which this package cannot import. */
618
+ declare const GRID_EXTRACT_SEATS: readonly ["nano-banana-2", "flux-2-max", "seedream-5-lite"];
619
+ export declare const extractGridCells: Operation<{
620
+ projectId: string;
621
+ assetId: string;
622
+ cells: string[];
623
+ model?: (typeof GRID_EXTRACT_SEATS)[number];
624
+ resolution?: '1k' | '2k' | '3k' | '4k';
625
+ aspectRatio?: string;
626
+ prompt?: string;
627
+ referenceAssetIds?: string[];
628
+ confirm?: boolean;
629
+ background?: boolean;
630
+ }>;
452
631
  export declare function videoCostKey(input: {
453
632
  model: VideoModel;
454
633
  duration: number;
@@ -522,6 +701,7 @@ export declare const generateVideo: Operation<{
522
701
  negativePrompt?: string;
523
702
  background?: boolean;
524
703
  confirm?: boolean;
704
+ folderId?: string | null;
525
705
  }>;
526
706
  /**
527
707
  * AGENT PARITY for the voice picker. The desktop browses stock voices by
@@ -554,6 +734,7 @@ export declare const generateAudio: Operation<{
554
734
  imageReferenceAssetId?: string;
555
735
  background?: boolean;
556
736
  confirm?: boolean;
737
+ folderId?: string | null;
557
738
  }>;
558
739
  export declare const generateLipSync: Operation<{
559
740
  projectId: string;
@@ -593,12 +774,18 @@ export declare const editVideo: Operation<{
593
774
  seedanceFace?: boolean;
594
775
  background?: boolean;
595
776
  confirm?: boolean;
777
+ folderId?: string | null;
596
778
  }>;
597
779
  export declare const trimVideo: Operation<{
598
780
  projectId: string;
599
781
  assetId: string;
600
782
  inSec?: number;
601
783
  outSec: number;
784
+ pieces?: {
785
+ splitsAtSec?: number[];
786
+ longestSec?: number;
787
+ shareSec?: number;
788
+ };
602
789
  }>;
603
790
  export declare const getGenerationStatus: Operation<{
604
791
  generationId: string;
@@ -878,6 +1065,7 @@ export declare const updateFrame: Operation<{
878
1065
  assetId?: string | null;
879
1066
  sceneId?: string | null;
880
1067
  position?: number;
1068
+ preferredClipId?: string | null;
881
1069
  }>;
882
1070
  /**
883
1071
  * Batch form of `slates_update_frame`.
@@ -900,6 +1088,7 @@ export declare const batchUpdateFrames: Operation<{
900
1088
  assetId?: string | null;
901
1089
  sceneId?: string | null;
902
1090
  position?: number;
1091
+ preferredClipId?: string | null;
903
1092
  }[];
904
1093
  }>;
905
1094
  export declare const deleteFrame: Operation<{
@@ -934,7 +1123,8 @@ type ShotOpParams = Omit<ShotParams, 'aspectRatio' | 'videoResolution' | 'imageR
934
1123
  export declare const createShot: Operation<ShotOpRefs & ShotOpScript & {
935
1124
  projectId: string;
936
1125
  name?: string;
937
- prompt: string;
1126
+ prompt?: string;
1127
+ fromAssetId?: string;
938
1128
  model?: string;
939
1129
  params?: ShotOpParams;
940
1130
  frameId?: string;
@@ -969,6 +1159,15 @@ export declare const listShots: Operation<{
969
1159
  export declare const getShot: Operation<{
970
1160
  shotId: string;
971
1161
  }>;
1162
+ /**
1163
+ * The Board's "Use pictures as first frames": each video Shot with a picture and no first frame starts its
1164
+ * video from that picture. The desktop runs the command's own pick and write (`firstFrameCandidate`,
1165
+ * `withFirstFrame`), so the recipe changes exactly as a click changes it. Free: nothing is generated.
1166
+ */
1167
+ export declare const usePicturesAsFirstFrames: Operation<{
1168
+ projectId: string;
1169
+ shotIds?: string[];
1170
+ }>;
972
1171
  /**
973
1172
  * SPLIT and MERGE — the chop decision, and the only two cross-row operations on
974
1173
  * this surface.