@slatesvideo/shared 0.4.1 → 0.4.3

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.
@@ -93,6 +93,19 @@ export declare const createEnvironment: Operation<{
93
93
  description?: string;
94
94
  style?: string;
95
95
  }>;
96
+ export declare const generateCharacterSheets: Operation<{
97
+ characterId: string;
98
+ projectId: string;
99
+ baseAssetId: string;
100
+ sheetTypes?: ('turnaround' | 'expression')[];
101
+ userNotes?: string;
102
+ }>;
103
+ export declare const generateEnvironmentPlate: Operation<{
104
+ environmentId: string;
105
+ projectId: string;
106
+ baseAssetId?: string;
107
+ userNotes?: string;
108
+ }>;
96
109
  export declare const listStoryboards: Operation<{
97
110
  projectId: string;
98
111
  }>;
@@ -141,7 +154,7 @@ export declare const editImage: Operation<{
141
154
  confirm?: boolean;
142
155
  background?: boolean;
143
156
  }>;
144
- declare const VIDEO_MODELS: readonly ["kling-v3.0-std", "kling-v3.0-pro", "kling-v3.0-omni", "veo-3.1-fast", "veo-3.1-standard", "seedance-2-fast", "seedance-2-std"];
157
+ declare const VIDEO_MODELS: readonly ["kling-v3.0-std", "kling-v3.0-pro", "kling-v3.0-omni", "veo-3.1-fast", "veo-3.1-standard", "seedance-2"];
145
158
  type VideoModel = (typeof VIDEO_MODELS)[number];
146
159
  export declare const generateVideo: Operation<{
147
160
  prompt: string;
@@ -149,14 +162,18 @@ export declare const generateVideo: Operation<{
149
162
  projectId?: string;
150
163
  aspectRatio?: '1:1' | '16:9' | '9:16' | '4:3' | '3:4' | '21:9' | '9:21' | '4:5' | '5:4' | '2:3' | '3:2';
151
164
  duration?: number;
152
- videoResolution?: '720p' | '1080p' | '4k';
153
- seedanceSpeed?: 'economy' | 'priority';
165
+ videoResolution?: '480p' | '720p' | '1080p' | '4k';
154
166
  firstFrameAssetId?: string;
155
167
  lastFrameAssetId?: string;
156
168
  ingredientAssetIds?: string[];
169
+ characterAssetIds?: string[];
170
+ environmentAssetIds?: string[];
171
+ styleAssetIds?: string[];
172
+ videoReferenceAssetId?: string;
157
173
  sound?: boolean;
158
174
  audioLanguage?: 'EN' | 'ZH' | 'JA' | 'KO' | 'ES';
159
175
  generateMusic?: boolean;
176
+ seedanceFace?: boolean;
160
177
  negativePrompt?: string;
161
178
  background?: boolean;
162
179
  confirm?: boolean;
@@ -172,6 +189,7 @@ export declare const generateLipSync: Operation<{
172
189
  ttsSpeed?: number;
173
190
  audioFilePath?: string;
174
191
  avatarModel?: 'avatar-standard' | 'avatar-pro';
192
+ klingProvider?: 'fal' | 'kling';
175
193
  background?: boolean;
176
194
  confirm?: boolean;
177
195
  }>;
@@ -182,6 +200,7 @@ export declare const generateMotionTransfer: Operation<{
182
200
  motionModel?: 'kling-mc-std' | 'kling-mc-pro';
183
201
  characterOrientation?: 'video' | 'image';
184
202
  prompt?: string;
203
+ klingProvider?: 'fal' | 'kling';
185
204
  background?: boolean;
186
205
  confirm?: boolean;
187
206
  }>;
@@ -372,6 +372,51 @@ export const createEnvironment = {
372
372
  return ok(await ctx.desktop().post('/agent/environments', input));
373
373
  },
374
374
  };
375
+ export const generateCharacterSheets = {
376
+ id: 'slates_generate_character_sheets',
377
+ description: "Generate a character's turnaround sheet (4 neutral full-body angles — identity: body, proportions, outfit) AND expression sheet (close-up facial detail) from a base portrait asset, on Nano Banana 2 at 2K, and bind both to the character. THE real character-building workflow — call right after slates_create_character. baseAssetId is the source portrait (a project asset). Both sheets generate by default. Afterward, carry the character into a scene by passing its turnaround + expression asset ids as characterAssetIds to slates_generate_video (or referenceAssetIds to slates_generate_image) so identity stays consistent across shots. Cost ~$0.36 for both sheets.",
378
+ input: z.object({
379
+ characterId: z.string().uuid(),
380
+ projectId: z.string().uuid(),
381
+ baseAssetId: z.string().uuid().describe('The base portrait asset the sheets are generated from.'),
382
+ sheetTypes: z
383
+ .array(z.enum(['turnaround', 'expression']))
384
+ .optional()
385
+ .describe('Which sheets to generate. Default both.'),
386
+ userNotes: z.string().optional().describe('Extra instruction, e.g. "use the woman on the left".'),
387
+ }),
388
+ async run(input, ctx) {
389
+ return ok(await ctx.desktop().post('/agent/characters/generate-sheets', {
390
+ characterId: input.characterId,
391
+ projectId: input.projectId,
392
+ baseAssetIds: [input.baseAssetId],
393
+ sheetTypes: input.sheetTypes,
394
+ userNotes: input.userNotes,
395
+ }));
396
+ },
397
+ };
398
+ export const generateEnvironmentPlate = {
399
+ id: 'slates_generate_environment_plate',
400
+ description: "Generate an environment's single clean establishing plate (Nano Banana 2, 2K) from an optional base image, and bind it as the environment's reference. THE real environment-building workflow — call right after slates_create_environment. Afterward, pass the plate asset id as environmentAssetIds to slates_generate_video (or referenceAssetIds to slates_generate_image) so the location stays consistent across shots. Cost ~$0.18.",
401
+ input: z.object({
402
+ environmentId: z.string().uuid(),
403
+ projectId: z.string().uuid(),
404
+ baseAssetId: z
405
+ .string()
406
+ .uuid()
407
+ .optional()
408
+ .describe('Optional base image to establish from (e.g. a rough location shot).'),
409
+ userNotes: z.string().optional(),
410
+ }),
411
+ async run(input, ctx) {
412
+ return ok(await ctx.desktop().post('/agent/environments/generate-plate', {
413
+ environmentId: input.environmentId,
414
+ projectId: input.projectId,
415
+ baseAssetIds: input.baseAssetId ? [input.baseAssetId] : [],
416
+ userNotes: input.userNotes,
417
+ }));
418
+ },
419
+ };
375
420
  // ── Storyboards ─────────────────────────────────────────────────
376
421
  export const listStoryboards = {
377
422
  id: 'slates_list_storyboards',
@@ -915,19 +960,16 @@ const VIDEO_MODELS = [
915
960
  'kling-v3.0-omni',
916
961
  'veo-3.1-fast',
917
962
  'veo-3.1-standard',
918
- 'seedance-2-fast',
919
- 'seedance-2-std',
963
+ 'seedance-2',
920
964
  ];
921
965
  // Model → registry cost-key. Each provider's keys ship with their own
922
966
  // shape (verified against /api/agent/models):
923
967
  // Kling: kling-v3-{standard|pro|omni}-{N}s — note the user-facing
924
968
  // model id `kling-v3.0-std` maps to registry key `kling-v3-standard`.
925
969
  // Veo: veo-3.1-{fast|standard}[-4k]-{N}s[-audio]
926
- // Seedance: seedance-2-{fast|std}[-priority]-{N}s. Economy (PiAPI) and
927
- // Priority (fal.ai) are DIFFERENT registry keys at DIFFERENT prices —
928
- // priority is ~2.2x economy. The cost key MUST encode the tier or the
929
- // pre-flight quote understates a priority gen (the desktop charges the
930
- // priority key regardless of what we quoted).
970
+ // Seedance: seedance-2-{res}-{N}s (BytePlus ModelArk, sole provider). The
971
+ // cost key encodes resolution (480p/720p/1080p/4k) price scales with
972
+ // resolution, so the key MUST carry it or the pre-flight quote is wrong.
931
973
  const KLING_TIER_MAP = {
932
974
  'kling-v3.0-std': 'kling-v3-standard',
933
975
  'kling-v3.0-pro': 'kling-v3-pro',
@@ -935,10 +977,12 @@ const KLING_TIER_MAP = {
935
977
  };
936
978
  function videoCostKey(input) {
937
979
  if (input.model.startsWith('seedance')) {
938
- // Mirrors seedanceCreditKey() in slate/src/shared/pricing.ts.
939
- return input.seedanceSpeed === 'priority'
940
- ? `${input.model}-priority-${input.duration}s`
941
- : `${input.model}-${input.duration}s`;
980
+ // Mirrors seedanceCreditKey() in slate/src/shared/pricing.ts (face × res × duration).
981
+ // Face route (AI-character face ref) bills the EvoLink relaxed rate (+10%) via
982
+ // the `-face-` key; faceless uses the cheaper BytePlus key.
983
+ const res = input.videoResolution ?? '1080p';
984
+ const face = input.seedanceFace ? '-face' : '';
985
+ return `${input.model}${face}-${res}-${input.duration}s`;
942
986
  }
943
987
  if (input.model.startsWith('veo')) {
944
988
  const is4k = input.videoResolution === '4k';
@@ -972,21 +1016,25 @@ function promptingSkillFor(model) {
972
1016
  }
973
1017
  export const generateVideo = {
974
1018
  id: 'slates_generate_video',
975
- description: 'Generate video via Slates credits. REQUIRED before calling: read the slates-cost-discipline skill plus the per-model prompting skill (slates-prompting-seedance / slates-prompting-kling-v3 / slates-prompting-veo-3) — video models prompt very differently. projectId is REQUIRED for UI integration (the user sees a progress card and the asset lands in the project — without it the call fails). aspectRatio + duration are required (server returns requires_clarification when missing). Cost > $0.50 returns requires_confirm — pass confirm=true after explicit user OK. Veo locks to 16:9 and to 4/6/8s durations (4K only at 8s). Image-to-video via firstFrameAssetId. Frames-to-video via firstFrameAssetId + lastFrameAssetId (Veo / Seedance only). Ingredients via ingredientAssetIds (Kling Omni / Seedance). No skill files installed? Call slates_get_prompting_guide with the per-model guide (\'slates-prompting-veo-3\' / \'slates-prompting-kling-v3\' / \'slates-prompting-seedance\') and \'slates-cost-discipline\' before first use.',
1019
+ description: 'Generate video via Slates credits. REQUIRED before calling: read the slates-cost-discipline skill plus the per-model prompting skill (slates-prompting-seedance / slates-prompting-kling-v3 / slates-prompting-veo-3) — video models prompt very differently. Also read slates-content-policy when the scene involves conflict, creatures, crowds, destruction, weapons, or young characters (build it safe-by-construction so the filter doesn\'t reject or degrade it). projectId is REQUIRED for UI integration (the user sees a progress card and the asset lands in the project — without it the call fails). aspectRatio + duration are required (server returns requires_clarification when missing). Cost > $0.50 returns requires_confirm — pass confirm=true after explicit user OK. Veo locks to 16:9 and to 4/6/8s durations (4K only at 8s). Image-to-video via firstFrameAssetId. Frames-to-video via firstFrameAssetId + lastFrameAssetId (Veo / Seedance only). Ingredients via ingredientAssetIds (Kling Omni / Seedance). No skill files installed? Call slates_get_prompting_guide with the per-model guide (\'slates-prompting-veo-3\' / \'slates-prompting-kling-v3\' / \'slates-prompting-seedance\') and \'slates-cost-discipline\' before first use.',
976
1020
  input: z.object({
977
1021
  prompt: z.string().min(1).max(4000),
978
- model: z.enum(VIDEO_MODELS).describe('Pick deliberately by capability AND cost. Kling V3.0 std = cheapest (no audio); pro = mid; omni = multi-char dialogue + audio. Veo 3.1 = top quality, locks 16:9, audio; fast vs standard. Seedance 2 = ByteDance, audio included, supports first+last frame; pick economy vs priority via seedanceSpeed. For exact per-call credit cost, call slates_estimate_generation_cost or slates_list_available_models — never quote prices from memory (they change).'),
1022
+ model: z.enum(VIDEO_MODELS).describe('Pick deliberately by capability AND cost. Kling V3.0 std = cheapest (no audio); pro = mid; omni = multi-char dialogue + audio. Veo 3.1 = top quality, locks 16:9, audio; fast vs standard. Seedance 2 = ByteDance (BytePlus), audio included, first+last frame + up to 9 reference images, full 480p–4K ladder (native 4K) via videoResolution. For exact per-call credit cost, call slates_estimate_generation_cost or slates_list_available_models — never quote prices from memory (they change).'),
979
1023
  projectId: z.string().uuid().optional().describe('Save into this Slates project. Strongly recommended — the desktop UI shows a progress card live and the asset appears when complete.'),
980
1024
  aspectRatio: z.enum(['1:1', '16:9', '9:16', '4:3', '3:4', '21:9', '9:21', '4:5', '5:4', '2:3', '3:2']).optional().describe('Veo locks to 16:9 — passing anything else will be ignored or fail. Kling/Seedance support all.'),
981
1025
  duration: z.number().int().min(4).max(15).optional().describe('Seconds. Kling: 5-15. Veo: 4, 6, or 8 only (4K only at 8s). Seedance: 4-15. Default 5 if omitted but always be explicit (cost scales linearly).'),
982
- videoResolution: z.enum(['720p', '1080p', '4k']).optional().describe('Veo only. 720p / 1080p same price. 4k more expensive.'),
983
- seedanceSpeed: z.enum(['economy', 'priority']).optional().describe('Seedance only. Economy via PiAPI (cheaper, slower). Priority via fal.ai (faster).'),
1026
+ videoResolution: z.enum(['480p', '720p', '1080p', '4k']).optional().describe('Veo + Seedance. Seedance: 480p/720p/1080p/4K (default 1080p; 4K is native, the most expensive). Veo: 720p/1080p same price, 4K more (8s only).'),
984
1027
  firstFrameAssetId: z.string().uuid().optional().describe('Asset id from the project — used as the starting frame for image-to-video. Must already exist in the project.'),
985
1028
  lastFrameAssetId: z.string().uuid().optional().describe('Asset id from the project — used as the ending frame. Veo and Seedance only. Pairs with firstFrameAssetId for guided transitions.'),
986
1029
  ingredientAssetIds: z.array(z.string().uuid()).max(9).optional().describe('Asset ids used as visual reference / ingredients for Kling Omni or Seedance. Up to 9 (Seedance) or 4 (Kling).'),
1030
+ characterAssetIds: z.array(z.string().uuid()).optional().describe('Asset ids of character sheets — keeps a character consistent across the shot. From a Slates project.'),
1031
+ environmentAssetIds: z.array(z.string().uuid()).optional().describe('Asset ids of environment grids — keeps a location/setting consistent across the shot. From a Slates project.'),
1032
+ styleAssetIds: z.array(z.string().uuid()).optional().describe('Asset ids of style references — locks the visual style of the shot. From a Slates project.'),
1033
+ videoReferenceAssetId: z.string().uuid().optional().describe('Seedance ONLY: an existing VIDEO asset to use as a reference (edit / relocate an existing clip — Seedance ref-to-video). 2-15s. If the clip contains a human/AI character, pair with seedanceFace=true so it routes to the face-capable provider (the default Seedance route blocks people). Ignored by Kling/Veo.'),
987
1034
  sound: z.boolean().optional().describe('Kling Omni / Veo / Seedance: enable audio generation. Default true.'),
988
1035
  audioLanguage: z.enum(['EN', 'ZH', 'JA', 'KO', 'ES']).optional().describe('Kling Omni only — language for dialogue.'),
989
1036
  generateMusic: z.boolean().optional().describe('Kling Omni only — auto-generate background music.'),
1037
+ seedanceFace: z.boolean().optional().describe('Seedance ONLY: set true when a reference/ingredient shows an AI-character\'s FACE. Faces are blocked on the default (cheaper) Seedance route, so this reroutes the gen to a face-capable provider and costs ~10% more (the cost key becomes seedance-2-face-*). Leave false/unset for faceless or object-only references. No effect on Kling/Veo. Real-person faces are not supported on any route.'),
990
1038
  negativePrompt: z.string().optional(),
991
1039
  background: z.boolean().optional().describe(BACKGROUND_DESCRIBE),
992
1040
  confirm: z.boolean().optional().describe('Set true after explicit user OK to bypass the >$0.50 cost confirm gate (which fires for almost every video gen since they\'re expensive).'),
@@ -1044,7 +1092,7 @@ export const generateVideo = {
1044
1092
  duration: input.duration,
1045
1093
  videoResolution: input.videoResolution,
1046
1094
  sound: input.sound,
1047
- seedanceSpeed: input.seedanceSpeed,
1095
+ seedanceFace: input.seedanceFace,
1048
1096
  });
1049
1097
  const entry = registry.models.find((m) => m.model === costKey);
1050
1098
  if (!entry) {
@@ -1070,6 +1118,15 @@ export const generateVideo = {
1070
1118
  for (const id of input.ingredientAssetIds ?? []) {
1071
1119
  referenceRefs.push({ id, type: 'image', role: 'ingredient' });
1072
1120
  }
1121
+ for (const id of input.characterAssetIds ?? []) {
1122
+ referenceRefs.push({ id, type: 'image', role: 'character' });
1123
+ }
1124
+ for (const id of input.environmentAssetIds ?? []) {
1125
+ referenceRefs.push({ id, type: 'image', role: 'environment' });
1126
+ }
1127
+ for (const id of input.styleAssetIds ?? []) {
1128
+ referenceRefs.push({ id, type: 'image', role: 'style' });
1129
+ }
1073
1130
  const hasReferences = referenceRefs.length > 0;
1074
1131
  if ((totalCents > 50 || hasReferences) && !input.confirm) {
1075
1132
  const previews = hasReferences ? await previewAssets(ctx, referenceRefs) : [];
@@ -1114,13 +1171,17 @@ export const generateVideo = {
1114
1171
  aspectRatio: input.aspectRatio,
1115
1172
  duration: input.duration,
1116
1173
  videoResolution: input.videoResolution,
1117
- seedanceSpeed: input.seedanceSpeed,
1118
1174
  firstFrameAssetId: input.firstFrameAssetId,
1119
1175
  lastFrameAssetId: input.lastFrameAssetId,
1120
1176
  ingredientAssetIds: input.ingredientAssetIds ?? [],
1177
+ characterAssetIds: input.characterAssetIds ?? [],
1178
+ environmentAssetIds: input.environmentAssetIds ?? [],
1179
+ styleAssetIds: input.styleAssetIds ?? [],
1180
+ videoReferenceAssetId: input.videoReferenceAssetId,
1121
1181
  sound: input.sound,
1122
1182
  audioLanguage: input.audioLanguage,
1123
1183
  generateMusic: input.generateMusic,
1184
+ seedanceFace: input.seedanceFace,
1124
1185
  negativePrompt: input.negativePrompt,
1125
1186
  background: input.background,
1126
1187
  });
@@ -1170,6 +1231,7 @@ export const generateLipSync = {
1170
1231
  ttsSpeed: z.number().min(0.5).max(2).optional().describe('TTS speech rate. Default 1.0. Range 0.5-2.0.'),
1171
1232
  audioFilePath: z.string().optional().describe('Required when audioMethod=upload. Absolute path to the audio file on the user\'s machine (mp3, wav, m4a).'),
1172
1233
  avatarModel: z.enum(['avatar-standard', 'avatar-pro']).optional().describe('Image-source only. avatar-standard ($0.42/5s) for general use. avatar-pro ($0.86/5s) for sharper face fidelity. Ignored when sourceType=video.'),
1234
+ klingProvider: z.enum(['fal', 'kling']).optional().describe('Provider routing for the Kling call. "fal" (default) uses Slates credits. "kling" uses the user\'s own BYOK Kling key if configured — omit unless the user explicitly wants BYOK.'),
1173
1235
  background: z.boolean().optional().describe(BACKGROUND_DESCRIBE),
1174
1236
  confirm: z.boolean().optional().describe('Set true to bypass the >$0.50 cost confirm gate. Required for avatar-pro.'),
1175
1237
  }),
@@ -1238,6 +1300,7 @@ export const generateLipSync = {
1238
1300
  ttsSpeed: input.ttsSpeed,
1239
1301
  audioFilePath: input.audioFilePath,
1240
1302
  avatarModel: input.avatarModel,
1303
+ klingProvider: input.klingProvider,
1241
1304
  estimatedCost: totalCents,
1242
1305
  background: input.background,
1243
1306
  });
@@ -1283,6 +1346,7 @@ export const generateMotionTransfer = {
1283
1346
  motionModel: z.enum(['kling-mc-std', 'kling-mc-pro']).optional().describe('std ($0.95) for general motion. pro ($1.26) for cleaner anatomy + identity preservation. Default pro — pick std deliberately for cost savings.'),
1284
1347
  characterOrientation: z.enum(['video', 'image']).optional().describe('"video" = use the source video\'s framing. "image" = use the target image\'s framing. Default video.'),
1285
1348
  prompt: z.string().optional().describe('Optional refinement prompt. Read slates-prompting-motion-transfer for guidance.'),
1349
+ klingProvider: z.enum(['fal', 'kling']).optional().describe('Provider routing for the Kling call. "fal" (default) uses Slates credits. "kling" uses the user\'s own BYOK Kling key if configured — omit unless the user explicitly wants BYOK.'),
1286
1350
  background: z.boolean().optional().describe(BACKGROUND_DESCRIBE),
1287
1351
  confirm: z.boolean().optional().describe('Set true to bypass the >$0.50 confirm gate. Required — both tiers exceed.'),
1288
1352
  }),
@@ -1329,6 +1393,7 @@ export const generateMotionTransfer = {
1329
1393
  motionModel,
1330
1394
  characterOrientation: input.characterOrientation ?? 'video',
1331
1395
  prompt: input.prompt,
1396
+ klingProvider: input.klingProvider,
1332
1397
  estimatedCost: totalCents,
1333
1398
  background: input.background,
1334
1399
  });
@@ -1838,12 +1903,12 @@ function resolveGuideTopic(topic) {
1838
1903
  }
1839
1904
  export const getPromptingGuide = {
1840
1905
  id: 'slates_get_prompting_guide',
1841
- description: "Return the full markdown of a bundled Slates prompting/workflow guide. MCP-only clients (Claude Desktop, Smithery) don't get the CLI-installed skill files — call this instead. Accepts a guide name or a model id (e.g. 'veo-3.1-fast', 'kling-v3.0-pro', 'seedance-2-std', 'nano-banana-2') which maps to the right guide. ALWAYS read 'slates-cost-discipline' plus the relevant model guide before your first generation in a session.",
1906
+ description: "Return the full markdown of a bundled Slates prompting/workflow guide. MCP-only clients (Claude Desktop, Smithery) don't get the CLI-installed skill files — call this instead. Accepts a guide name or a model id (e.g. 'veo-3.1-fast', 'kling-v3.0-pro', 'seedance-2', 'nano-banana-2') which maps to the right guide. ALWAYS read 'slates-cost-discipline' plus the relevant model guide before your first generation in a session.",
1842
1907
  input: z.object({
1843
1908
  topic: z
1844
1909
  .string()
1845
1910
  .min(1)
1846
- .describe('Guide name or model id. Guides: slates-cost-discipline, slates-prompting-nano-banana-2, slates-prompting-veo-3, slates-prompting-kling-v3, slates-prompting-seedance, slates-prompting-lip-sync, slates-prompting-motion-transfer, slates-prompting-flux-2-max, slates-prompting-seedream-5-lite, slates-edit-and-iterate, slates-vision-feedback-loop, slates-character-turnaround, slates-storyboard-from-script, slates-direct-response-ad, slates-one-prompt-film'),
1911
+ .describe('Guide name or model id. Guides: slates-cost-discipline, slates-content-policy, slates-prompting-nano-banana-2, slates-prompting-veo-3, slates-prompting-kling-v3, slates-prompting-seedance, slates-prompting-lip-sync, slates-prompting-motion-transfer, slates-prompting-flux-2-max, slates-prompting-seedream-5-lite, slates-edit-and-iterate, slates-vision-feedback-loop, slates-character-turnaround, slates-storyboard-from-script, slates-direct-response-ad, slates-one-prompt-film'),
1847
1912
  }),
1848
1913
  async run(input) {
1849
1914
  const resolved = resolveGuideTopic(input.topic);
@@ -1881,6 +1946,8 @@ export const ALL_OPERATIONS = [
1881
1946
  setCharacterExpression,
1882
1947
  listEnvironments,
1883
1948
  createEnvironment,
1949
+ generateCharacterSheets,
1950
+ generateEnvironmentPlate,
1884
1951
  listStoryboards,
1885
1952
  createStoryboard,
1886
1953
  getStoryboardWithFrames,
@@ -0,0 +1,31 @@
1
+ export interface SubstitutionRule {
2
+ /** The risky construction to avoid. */
3
+ avoid: string;
4
+ /** The safe-by-construction substitution that usually reads more cinematic. */
5
+ use: string;
6
+ }
7
+ /**
8
+ * The substitution table — the core craft move. When a scene drifts toward any
9
+ * `avoid`, re-stage it as the matching `use` before writing the prompt.
10
+ */
11
+ export declare const CONTENT_POLICY_SUBSTITUTIONS: SubstitutionRule[];
12
+ /** The pre-flight checklist — run before delivering any risk-surface prompt. */
13
+ export declare const CONTENT_POLICY_PREFLIGHT: string[];
14
+ /** One-line summary used as a header in skills + the lead magnet. */
15
+ export declare const CONTENT_POLICY_HEADLINE = "Don't depict the harm \u2014 depict the energy, the aftermath, the threat, or the scale. Build the scene safe from the first word.";
16
+ /** The confirmed-safe envelope — pull a drifting scene back toward this shape. */
17
+ export declare const CONTENT_POLICY_SAFE_BENCHMARK = "One original creature, in a generalized monument or amphitheatre, in daylight, no weapons present, performing expressive action (rising, roaring, spreading wings). Original design, generalized location, daylight, expressive rather than violent \u2014 most epic ideas re-stage into this without losing the punch.";
18
+ /** The containment clause — keeps a scene in policy AND grounds its physics. */
19
+ export declare const CONTENT_POLICY_CONTAINMENT = "Give any creature or combat scene a containment rule (\"the fight STAYS at the sea surface\", \"boss scale locked ~2.5 human-heights, NOT kaiju-giant\", \"destruction stays in the evacuated valley\"). It improves coherence (no absurd escalation) AND keeps the scene inside policy \u2014 same clause buys both.";
20
+ /** The hard, non-negotiable rule on minors. Overrides every stylistic goal. */
21
+ export declare const CONTENT_POLICY_MINORS = "Never write romantic, sexual, or suggestive content involving or directed at minors, and never anything that sexualizes a young-presenting character. Any scene with children stays wholesome and age-appropriate. Non-negotiable.";
22
+ /**
23
+ * Canonical markdown block — the SOURCE OF TRUTH the skill markdown, the desktop
24
+ * inline mirror, and the lead-magnet are reconciled AGAINST. NOTE: nothing
25
+ * imports this yet — the desktop carries its own inline copy and the skills
26
+ * hand-author their prose, so a change here must be propagated to those copies
27
+ * in the same pass until the desktop-import + skill-embed wiring lands (see
28
+ * plans/2026-06-25-slates-prompting-system-overhaul.md).
29
+ */
30
+ export declare const CONTENT_POLICY_TEXT: string;
31
+ //# sourceMappingURL=content-policy.d.ts.map
@@ -0,0 +1,108 @@
1
+ // Content-policy-safe construction — the canonical "build the scene safe from
2
+ // the first word" knowledge for every Slates surface (desktop templates, MCP
3
+ // skills, the lead-magnet .skill). Authored ONCE here; consumers derive from it.
4
+ //
5
+ // The principle: don't depict the harm — depict the energy, the aftermath, the
6
+ // threat, or the scale. A standoff is more tense than a massacre; an evacuated
7
+ // city is eerier than a crowd in panic; a roar lands harder than a kill. The
8
+ // substitutions below usually read as MORE cinematic, not less, and they keep a
9
+ // generation from getting silently rejected or degraded by the model's filter.
10
+ //
11
+ // Source: second-brain business/projects/slates/content-strategy/lead-magnets/
12
+ // reference-content-policy.md. SSOT map: business/projects/slates/product/
13
+ // prompting-ssot.md.
14
+ /**
15
+ * The substitution table — the core craft move. When a scene drifts toward any
16
+ * `avoid`, re-stage it as the matching `use` before writing the prompt.
17
+ */
18
+ export const CONTENT_POLICY_SUBSTITUTIONS = [
19
+ {
20
+ avoid: 'Civilians in panic, crowds fleeing under debris',
21
+ use: 'An evacuated / empty city; abandoned streets; a lone figure for scale',
22
+ },
23
+ {
24
+ avoid: 'Weapons firing into buildings or at people',
25
+ use: 'Energy-discharge standoffs, searchlights sweeping, charged auras, shockwaves with no muzzle fire',
26
+ },
27
+ {
28
+ avoid: 'Creatures tearing into each other, gore',
29
+ use: 'A grapple / standoff — roars, near-misses, circling, an energy clash; combat that stays contained (in/on the water, never lifting into the air)',
30
+ },
31
+ {
32
+ avoid: 'Destruction with people in harm’s way',
33
+ use: 'Destruction in uninhabited terrain — glaciers, deserts, ruins, open sea, evacuated zones',
34
+ },
35
+ {
36
+ avoid: 'Realistic guns as the focus',
37
+ use: 'Stylized / fantasy implements, weapons slung-not-fired, the weapon as silhouette or prop only',
38
+ },
39
+ {
40
+ avoid: 'Blood, wounds, death',
41
+ use: 'Impact light, dust, debris, buckling and collapse, a silhouette dropping out of frame',
42
+ },
43
+ {
44
+ avoid: 'Real, named public figures',
45
+ use: 'Original / anonymous characters',
46
+ },
47
+ {
48
+ avoid: 'Real brand logos',
49
+ use: 'Original or generalized branding — except the user’s own product, which is the whole point of a brand film',
50
+ },
51
+ ];
52
+ /** The pre-flight checklist — run before delivering any risk-surface prompt. */
53
+ export const CONTENT_POLICY_PREFLIGHT = [
54
+ 'No civilians depicted in panic/harm; crowds are evacuated or absent.',
55
+ 'No weapons firing at people/buildings; threat is energy / searchlight / silhouette.',
56
+ 'No creature-on-creature or creature-on-person gore; combat is grapple / standoff / roar, contained.',
57
+ 'Destruction is in uninhabited / evacuated terrain.',
58
+ 'Creatures are original ("not based on any franchise"); no real public figures; no real brand logos except the user’s own product.',
59
+ 'Anything with children is wholesome and age-appropriate.',
60
+ ];
61
+ // ── Reusable text fragments (the template-assembly building blocks) ──
62
+ // The exact strings the desktop prompt templates, MCP skills, and lead-magnet
63
+ // compose. Change a rule HERE and every consumer follows.
64
+ /** One-line summary used as a header in skills + the lead magnet. */
65
+ export const CONTENT_POLICY_HEADLINE = "Don't depict the harm — depict the energy, the aftermath, the threat, or the scale. Build the scene safe from the first word.";
66
+ /** The confirmed-safe envelope — pull a drifting scene back toward this shape. */
67
+ export const CONTENT_POLICY_SAFE_BENCHMARK = 'One original creature, in a generalized monument or amphitheatre, in daylight, no weapons present, performing expressive action (rising, roaring, spreading wings). Original design, generalized location, daylight, expressive rather than violent — most epic ideas re-stage into this without losing the punch.';
68
+ /** The containment clause — keeps a scene in policy AND grounds its physics. */
69
+ export const CONTENT_POLICY_CONTAINMENT = 'Give any creature or combat scene a containment rule ("the fight STAYS at the sea surface", "boss scale locked ~2.5 human-heights, NOT kaiju-giant", "destruction stays in the evacuated valley"). It improves coherence (no absurd escalation) AND keeps the scene inside policy — same clause buys both.';
70
+ /** The hard, non-negotiable rule on minors. Overrides every stylistic goal. */
71
+ export const CONTENT_POLICY_MINORS = 'Never write romantic, sexual, or suggestive content involving or directed at minors, and never anything that sexualizes a young-presenting character. Any scene with children stays wholesome and age-appropriate. Non-negotiable.';
72
+ /**
73
+ * Canonical markdown block — the SOURCE OF TRUTH the skill markdown, the desktop
74
+ * inline mirror, and the lead-magnet are reconciled AGAINST. NOTE: nothing
75
+ * imports this yet — the desktop carries its own inline copy and the skills
76
+ * hand-author their prose, so a change here must be propagated to those copies
77
+ * in the same pass until the desktop-import + skill-embed wiring lands (see
78
+ * plans/2026-06-25-slates-prompting-system-overhaul.md).
79
+ */
80
+ export const CONTENT_POLICY_TEXT = `## Content-policy-safe construction
81
+
82
+ ${CONTENT_POLICY_HEADLINE}
83
+
84
+ Write scenes that hit full cinematic impact without ever *needing* to depict prohibited content. This is a craft move, not a compromise — the substitutions below usually read as more cinematic, not less, and they keep your generation from getting silently rejected or degraded by the model's filter. Load this whenever a prompt involves conflict, creatures, crowds, destruction, weapons, or young characters.
85
+
86
+ ### Substitution table
87
+
88
+ | Avoid | Use instead |
89
+ |---|---|
90
+ ${CONTENT_POLICY_SUBSTITUTIONS.map((s) => `| ${s.avoid} | ${s.use} |`).join('\n')}
91
+
92
+ ### The safe benchmark
93
+ ${CONTENT_POLICY_SAFE_BENCHMARK}
94
+
95
+ ### Containment rule — it doubles as a physics win
96
+ ${CONTENT_POLICY_CONTAINMENT}
97
+
98
+ ### Scale and stakes without harm
99
+ Epic stakes come from environmental danger and reaction, not depicted victims: tiny figures diving clear of *collapsing* terrain (not being crushed), a war-horn over an *empty* field, an army *scrambling* across a frozen valley as a titan tears free of a glacier. The danger is the environment; the figures are reacting, not dying. Snow plumes, glowing runes, splintering ice, shockwaves, and dust carry the chaos.
100
+
101
+ ### Minors — hard rule
102
+ ${CONTENT_POLICY_MINORS}
103
+
104
+ ### Pre-flight (run before delivering any risk-surface prompt)
105
+ ${CONTENT_POLICY_PREFLIGHT.map((c) => `- [ ] ${c}`).join('\n')}
106
+
107
+ If a box fails, apply the substitution table before writing the prompt.`;
108
+ //# sourceMappingURL=content-policy.js.map
@@ -1,4 +1,6 @@
1
1
  export * from './reference-rules.js';
2
+ export * from './reference-composer.js';
3
+ export * from './content-policy.js';
2
4
  export * from './style-library.js';
3
5
  export * from './model-facts.js';
4
6
  export * from './character-sheet.js';
@@ -10,6 +10,8 @@
10
10
  // plan). So a change here must be applied to those copies in the same pass for
11
11
  // now. SSOT map: second-brain business/projects/slates/product/prompting-ssot.md
12
12
  export * from './reference-rules.js';
13
+ export * from './reference-composer.js';
14
+ export * from './content-policy.js';
13
15
  export * from './style-library.js';
14
16
  export * from './model-facts.js';
15
17
  export * from './character-sheet.js';
@@ -0,0 +1,46 @@
1
+ export type ReferenceKind = 'character' | 'environment' | 'style' | 'pinned' | 'first-frame' | 'last-frame' | 'video';
2
+ export interface ReferenceMedia {
3
+ path: string;
4
+ mediaKind: 'image' | 'video';
5
+ }
6
+ /**
7
+ * A named bucket of reference media that can be @mentioned. The whole definition
8
+ * of a reference. It does NOT mandate a structure — one photo or eight angles,
9
+ * the composer treats them all as "this is one named thing".
10
+ */
11
+ export interface ReferenceGroup {
12
+ /** '@Marcus' | '@Cafe' | '#noir' | null (pinned / frames / picked video) */
13
+ token: string | null;
14
+ /** Display + citation name: 'Marcus' | 'the cafe' | 'noir'. Used verbatim. */
15
+ name: string;
16
+ kind: ReferenceKind;
17
+ /** A group can carry several images (e.g. turnaround + expression). */
18
+ media: ReferenceMedia[];
19
+ }
20
+ export interface ComposedReferences {
21
+ /** The composed prompt: user's words lead, references cited inline as "image N". */
22
+ prompt: string;
23
+ /** Free-reference image paths in cited order — flatten yields "image 1..N". */
24
+ orderedImagePaths: string[];
25
+ /** Video reference paths in cited order — "video 1..M". */
26
+ orderedVideoPaths: string[];
27
+ }
28
+ /**
29
+ * Compose the raw prompt (mentions intact) + an ORDERED list of reference groups
30
+ * into the named prompt text + ordered media the API receives. The group order
31
+ * IS the send order — image numbers are assigned by walking the list, so the
32
+ * caller controls numbering by ordering the list (default: pinned/base first,
33
+ * then @mentions in first-appearance order, then #style last).
34
+ *
35
+ * @param rawPrompt the user's prompt with @mentions / #tags still in it
36
+ * @param groups the single ordered ReferenceGroup[] (rail + prompt read this)
37
+ * @param opts.startImageNumber images already attached AHEAD of these groups
38
+ * (e.g. a grid-cell base that is "image 1" before the free-refs). Free-ref
39
+ * numbering and orderedImagePaths begin after it. Default 0.
40
+ */
41
+ export interface ComposeOptions {
42
+ startImageNumber?: number;
43
+ startVideoNumber?: number;
44
+ }
45
+ export declare function composeReferences(rawPrompt: string, groups: ReferenceGroup[], opts?: ComposeOptions): ComposedReferences;
46
+ //# sourceMappingURL=reference-composer.d.ts.map