@slatesvideo/shared 0.6.11 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/dist/auth.js +2 -2
  2. package/dist/clients/cloud.js +1 -1
  3. package/dist/index.d.ts +1 -1
  4. package/dist/index.js +1 -1
  5. package/dist/manual/content.d.ts +1 -1
  6. package/dist/manual/content.js +1 -1
  7. package/dist/operations/index.d.ts +817 -16
  8. package/dist/operations/index.js +1410 -360
  9. package/dist/operations/surface.d.ts +3 -1
  10. package/dist/operations/surface.js +37 -10
  11. package/dist/prompts/ad-presets.d.ts +77 -0
  12. package/dist/prompts/ad-presets.js +43 -0
  13. package/dist/prompts/agent-doctrine.js +5 -4
  14. package/dist/prompts/banned-tokens.d.ts +4 -29
  15. package/dist/prompts/banned-tokens.js +29 -204
  16. package/dist/prompts/craft-cards.js +2 -2
  17. package/dist/prompts/generation-policy.d.ts +41 -0
  18. package/dist/prompts/generation-policy.js +53 -0
  19. package/dist/prompts/guide-retrieval.d.ts +9 -0
  20. package/dist/prompts/guide-retrieval.js +53 -0
  21. package/dist/prompts/index.d.ts +1 -0
  22. package/dist/prompts/index.js +1 -0
  23. package/dist/prompts/model-capabilities.d.ts +18 -1
  24. package/dist/prompts/model-capabilities.js +72 -19
  25. package/dist/prompts/model-facts.d.ts +34 -2
  26. package/dist/prompts/model-facts.js +66 -5
  27. package/dist/prompts/partials.generated.js +8 -2
  28. package/dist/prompts/prompting-tips.d.ts +1 -1
  29. package/dist/prompts/prompting-tips.js +61 -16
  30. package/dist/prompts/reference-composer.d.ts +2 -0
  31. package/dist/prompts/reference-composer.js +51 -50
  32. package/dist/prompts/script-document.d.ts +165 -0
  33. package/dist/prompts/script-document.js +11 -0
  34. package/dist/prompts/shot-grammar.d.ts +4 -4
  35. package/dist/prompts/shot-grammar.js +3 -3
  36. package/dist/prompts/shot-spec.d.ts +13 -0
  37. package/dist/prompts/shot-spec.js +23 -5
  38. package/dist/skills/content.js +26 -23
  39. package/exports/slates-chatgpt-images/generated/SKILL.md +107 -0
  40. package/exports/slates-chatgpt-images/generated/slates-chatgpt-images.skill +0 -0
  41. package/exports/slates-prompt-builder/generated/SKILL.md +1 -1
  42. package/exports/slates-prompt-builder/generated/reference-character.md +9 -1
  43. package/exports/slates-prompt-builder/generated/reference-kling.md +3 -3
  44. package/exports/slates-prompt-builder/generated/reference-nano-banana.md +22 -10
  45. package/exports/slates-prompt-builder/generated/reference-seedance.md +4 -4
  46. package/exports/slates-prompt-builder/generated/slates-prompt-builder-manifest.json +17 -17
  47. package/exports/slates-prompt-builder/generated/slates-prompt-builder.skill +0 -0
  48. package/package.json +10 -4
  49. package/skills/_partials/cinematic-card.md +8 -0
  50. package/skills/_partials/cinematic-routes-short.md +2 -0
  51. package/skills/_partials/cinematic-tips-short.md +2 -0
  52. package/skills/_partials/decision-log.md +1 -13
  53. package/skills/_partials/image-defaults.md +11 -0
  54. package/skills/_partials/lens-video-split.md +1 -0
  55. package/skills/_partials/reference-rules-core.md +1 -1
  56. package/skills/_partials/sheet-tool-defaults.md +6 -0
  57. package/skills/slates-character-identity.md +9 -1
  58. package/skills/slates-chatgpt-images.md +107 -0
  59. package/skills/slates-cinematic-look.md +237 -0
  60. package/skills/slates-cost-discipline.md +18 -12
  61. package/skills/slates-direct-response-ad.md +13 -53
  62. package/skills/slates-edit-and-iterate.md +1 -1
  63. package/skills/slates-model-selection.md +20 -14
  64. package/skills/slates-one-prompt-film.md +19 -77
  65. package/skills/slates-project-organization.md +7 -3
  66. package/skills/slates-prompting-flux-2-max.md +15 -4
  67. package/skills/slates-prompting-gpt-image-2-5.md +41 -28
  68. package/skills/slates-prompting-kling-v3.md +3 -3
  69. package/skills/slates-prompting-lip-sync.md +1 -1
  70. package/skills/slates-prompting-minimax-h3.md +30 -17
  71. package/skills/slates-prompting-motion-transfer.md +1 -1
  72. package/skills/slates-prompting-nano-banana-2.md +24 -11
  73. package/skills/slates-prompting-seedance-2-5.md +7 -6
  74. package/skills/slates-prompting-seedance.md +5 -5
  75. package/skills/slates-prompting-seedream-5-lite.md +14 -3
  76. package/skills/slates-prompting-veo-3.md +1 -1
  77. package/skills/slates-script-craft.md +45 -0
  78. package/skills/slates-shot-variety.md +11 -40
  79. package/skills/slates-storyboard-from-script.md +14 -66
  80. package/skills/slates-style-prompting.md +54 -54
  81. package/skills/slates-ugc-influencer-ad.md +32 -309
  82. package/skills/slates-vision-feedback-loop.md +2 -1
@@ -37,6 +37,7 @@ export declare const OPERATION_GROUPS: Record<OperationGroup, readonly string[]>
37
37
  export declare function groupFor(id: string): OperationGroup | undefined;
38
38
  /** `extended` iff the op names a group. Absent from every group ⇒ `core`, so a
39
39
  * new op is visible until someone deliberately defers it. */
40
+ export declare const STARTUP_TOOL_IDS: Set<string>;
40
41
  export declare function tierFor(id: string): OperationTier;
41
42
  /** One-line summary of each group, for `slates_load_tools`' own description. */
42
43
  export declare const GROUP_SUMMARY: Record<OperationGroup, string>;
@@ -60,10 +61,11 @@ export declare function toolDefinition(op: SurfaceOp): ToolDefinition;
60
61
  * Render a tool surface.
61
62
  *
62
63
  * `desktop` sends `core` plus whatever groups have been loaded this run; `mcp`
63
- * sends everything, because a stdio server has no run to append to.
64
+ * renders all definitions; the MCP server filters its connection listing from that set.
64
65
  */
65
66
  export declare function toolDefinitions(ops: readonly SurfaceOp[], opts: {
66
67
  surface: 'desktop' | 'mcp';
67
68
  groups?: readonly OperationGroup[];
69
+ names?: readonly string[];
68
70
  }): ToolDefinition[];
69
71
  //# sourceMappingURL=surface.d.ts.map
@@ -11,10 +11,9 @@
11
11
  //
12
12
  // 2. TIERS. 90 ops is 112 KB of descriptions and JSON schemas on EVERY
13
13
  // desktop Studio Agent turn. `core` is what a session needs to work;
14
- // `extended` is deferred behind `slates_load_tools` and appended to the
15
- // run's tool list once a group loads. The MCP server still registers
16
- // everything — a stdio server has no run to append to, and Claude Code
17
- // already defers stdio tool schemas through its own tool search.
14
+ // `extended` is selected through `slates_load_tools`. Both surfaces replace
15
+ // the optional selection on a named/group load. MCP keeps every direct
16
+ // operation callable for compatibility and notifies when its listing changes.
18
17
  //
19
18
  // 3. ONE SCHEMA RENDERER. The desktop rendered `$refStrategy: 'none'` and
20
19
  // the MCP server rendered `target: 'openApi3'`, so "the two surfaces
@@ -54,12 +53,15 @@ const DESTRUCTIVE_IDS = new Set([
54
53
  'slates_delete_character',
55
54
  'slates_delete_environment',
56
55
  'slates_delete_style',
56
+ 'slates_delete_library_item',
57
57
  'slates_delete_storyboard',
58
58
  'slates_delete_scene',
59
59
  'slates_delete_frame',
60
60
  'slates_move_assets_to_folder',
61
61
  'slates_move_assets_to_project',
62
62
  'slates_move_entity_to_project',
63
+ 'slates_relocate_project',
64
+ 'slates_undo_relocate_project',
63
65
  'slates_merge_shots',
64
66
  'slates_split_shot',
65
67
  ]);
@@ -115,6 +117,11 @@ export const OPERATION_GROUPS = {
115
117
  'slates_delete_folder',
116
118
  'slates_set_folder_cover',
117
119
  'slates_move_assets_to_folder',
120
+ 'slates_set_asset_favorite',
121
+ 'slates_export_assets',
122
+ 'slates_list_pins',
123
+ 'slates_pin_references',
124
+ 'slates_unpin_reference',
118
125
  'slates_move_assets_to_project',
119
126
  'slates_copy_assets_to_project',
120
127
  'slates_move_entity_to_project',
@@ -122,15 +129,29 @@ export const OPERATION_GROUPS = {
122
129
  'slates_create_style',
123
130
  'slates_update_style',
124
131
  'slates_delete_style',
132
+ 'slates_list_library',
133
+ 'slates_create_library_item',
134
+ 'slates_update_library_item',
135
+ 'slates_delete_library_item',
136
+ 'slates_manage_library_category',
137
+ 'slates_copy_library_item_to_project',
138
+ 'slates_get_template',
139
+ 'slates_export_template',
140
+ 'slates_import_template',
125
141
  'slates_get_project_directory',
142
+ 'slates_relocate_project',
143
+ 'slates_undo_relocate_project',
126
144
  'slates_reveal_file',
127
145
  ],
128
146
  // The cut and the export. A generation session never touches these.
147
+ script: ['slates_get_script_document', 'slates_update_script_document', 'slates_get_script_sections', 'slates_update_script_section', 'slates_get_script_suggestions', 'slates_update_script_suggestions', 'slates_get_script_uses', 'slates_preview_script_variation', 'slates_create_script_variation', 'slates_get_shot_inputs', 'slates_reuse_shot_take'],
129
148
  timeline: [
149
+ 'slates_list_timelines', 'slates_save_timeline', 'slates_export_cuts',
130
150
  'slates_get_timeline',
131
151
  'slates_add_clip_to_timeline',
132
152
  'slates_reorder_clips',
133
153
  'slates_remove_clip',
154
+ 'slates_manage_timeline_marker',
134
155
  'slates_add_timeline_track',
135
156
  'slates_update_timeline_track',
136
157
  'slates_remove_timeline_track',
@@ -179,14 +200,20 @@ export function groupFor(id) {
179
200
  }
180
201
  /** `extended` iff the op names a group. Absent from every group ⇒ `core`, so a
181
202
  * new op is visible until someone deliberately defers it. */
203
+ export const STARTUP_TOOL_IDS = new Set([
204
+ 'slates_load_tools', 'slates_get_prompting_guide', 'slates_get_workspace_state',
205
+ 'slates_list_projects', 'slates_create_project', 'slates_list_assets',
206
+ 'slates_get_asset_image', 'slates_get_generation_status', 'slates_estimate_generation_cost',
207
+ ]);
182
208
  export function tierFor(id) {
183
- return GROUP_BY_OP.has(id) ? 'extended' : 'core';
209
+ return STARTUP_TOOL_IDS.has(id) ? 'core' : 'extended';
184
210
  }
185
211
  /** One-line summary of each group, for `slates_load_tools`' own description. */
186
212
  export const GROUP_SUMMARY = {
187
- library: 'folders, styles, moving and copying assets between projects, revealing files on disk',
188
- timeline: 'the timeline (tracks, clips, settings), video export and NLE XML export, clip trimming',
189
- admin: 'rename / delete / reorder for projects, characters, environments, storyboards, scenes and frames; shot duplicate, split and merge',
213
+ library: 'folders, the Library (user-named categories of saved references: characters, locations, products, looks), moving and copying assets and Library items between projects, moving a project into the current projects folder, revealing files on disk',
214
+ script: 'rich script documents, anchored sections and their saved versions, reusable passages, variations and take input history',
215
+ timeline: 'named cuts and selected exports; the timeline (tracks, clips, markers, settings), video export and export for DaVinci, Premiere or Final Cut (XML), clip trimming',
216
+ admin: 'rename / delete / reorder for projects, characters, locations, looks, boards, scenes and frames; shot duplicate, split and merge',
190
217
  blender: 'the Blender previs bridge — scene inspection, bpy execution, API docs, grey-box render',
191
218
  };
192
219
  /**
@@ -211,7 +238,7 @@ export function toolDefinition(op) {
211
238
  * Render a tool surface.
212
239
  *
213
240
  * `desktop` sends `core` plus whatever groups have been loaded this run; `mcp`
214
- * sends everything, because a stdio server has no run to append to.
241
+ * renders all definitions; the MCP server filters its connection listing from that set.
215
242
  */
216
243
  export function toolDefinitions(ops, opts) {
217
244
  const loaded = new Set(opts.groups ?? []);
@@ -220,7 +247,7 @@ export function toolDefinitions(ops, opts) {
220
247
  if (opts.surface === 'mcp')
221
248
  return true;
222
249
  const group = groupFor(op.id);
223
- return group === undefined || loaded.has(group);
250
+ return STARTUP_TOOL_IDS.has(op.id) || !!opts.names?.includes(op.id) || (group !== undefined && loaded.has(group));
224
251
  })
225
252
  .map(toolDefinition);
226
253
  }
@@ -0,0 +1,77 @@
1
+ /** Original free starter examples. Model choices and costs resolve at use time.
2
+ * Evidence and source coverage: second-brain/business/projects/slates/research/script-craft/.
3
+ * Preview readiness is explicit; importing a draft never claims a tested output. */
4
+ export declare const AD_PRESETS: readonly [{
5
+ readonly id: "catch-the-small-problem";
6
+ readonly revision: 1;
7
+ readonly name: "Catch the small problem";
8
+ readonly tags: readonly ["product demonstration", "spoken", "everyday"];
9
+ readonly techniques: readonly ["sc-event", "sc-proof", "sc-bridge"];
10
+ readonly description: "A small everyday frustration becomes a visible product demonstration. An original fictional keys-tray example.";
11
+ readonly adaptation: "Replace the object, familiar frustration and observable demonstration. Supply real product facts and the destination for the invitation. Compare the opening and its bridge together; keep the demonstration fixed.";
12
+ readonly sections: readonly [{
13
+ readonly label: "Opening";
14
+ readonly alternatives: readonly [{
15
+ readonly label: "Question";
16
+ readonly text: "Where do your keys go when you get home?\nMine used to land wherever my hand stopped.";
17
+ readonly action: "A hand drops a key ring onto a cluttered entrance table. The keys stop near the edge. An ordinary moment, filmed close enough to see the key ring.";
18
+ }, {
19
+ readonly label: "Search";
20
+ readonly text: "I checked my coat, my bag, and the table.\nThey were under the receipt.";
21
+ readonly action: "The same hand checks a coat pocket, opens a bag, then lifts a receipt to reveal the keys. Let the last action answer the search.";
22
+ }, {
23
+ readonly label: "Objection";
24
+ readonly text: "A tray just for keys?\nThat was my question too.";
25
+ readonly action: "A small tray sits beside a scattered key ring. A hand turns the tray once, then puts it down by the door.";
26
+ }];
27
+ }, {
28
+ readonly label: "Demonstration";
29
+ readonly alternatives: readonly [{
30
+ readonly label: "A place to land";
31
+ readonly text: "Now I leave this by the door.\nKeys go in when I arrive. I pick them up when I leave.";
32
+ readonly action: "The hand places the keys inside the shallow tray. Match the next departure gesture to the same framing: the hand finds the keys in that tray. Show only the behavior described.";
33
+ }];
34
+ }, {
35
+ readonly label: "Invitation";
36
+ readonly alternatives: readonly [{
37
+ readonly label: "See the product";
38
+ readonly text: "Give the small things a place to land.";
39
+ readonly action: "Hold a clear view of the tray with the keys inside. Keep the product unobstructed. The destination and offer are supplied by the creator; no price or claim appears in the image.";
40
+ }];
41
+ }];
42
+ }, {
43
+ readonly id: "silent-small-demonstration";
44
+ readonly revision: 1;
45
+ readonly name: "Let the object explain";
46
+ readonly tags: readonly ["silent", "product demonstration", "modular"];
47
+ readonly techniques: readonly ["sc-event", "sc-proof", "sc-modular"];
48
+ readonly description: "An original silent tray demonstration. The opening event changes while the product action stays fixed.";
49
+ readonly adaptation: "Choose a real product action that reads without speech. Replace the sample object and place. Keep the physical setup legible; a different opening must still lead naturally into the same demonstration. Add sound only when wanted.";
50
+ readonly sections: readonly [{
51
+ readonly label: "Opening event";
52
+ readonly alternatives: readonly [{
53
+ readonly label: "Almost lost";
54
+ readonly text: "";
55
+ readonly action: "Silent overhead view of an entrance table. A key ring slides toward the edge and stops. A hand enters with a shallow tray. No dialogue or on-screen text.";
56
+ }, {
57
+ readonly label: "Found it";
58
+ readonly text: "";
59
+ readonly action: "Silent overhead view of an entrance table. A hand lifts a small stack of receipts, revealing a key ring underneath. The hand brings an empty shallow tray into the same space. No dialogue or on-screen text.";
60
+ }];
61
+ }, {
62
+ readonly label: "Demonstration";
63
+ readonly alternatives: readonly [{
64
+ readonly label: "In reach";
65
+ readonly text: "";
66
+ readonly action: "Silent overhead view of the same table and tray. A hand puts the keys inside the tray, leaves the frame, then returns and picks them up directly. Keep the object and gesture visible. No dialogue or on-screen text.";
67
+ }];
68
+ }, {
69
+ readonly label: "Closing view";
70
+ readonly alternatives: readonly [{
71
+ readonly label: "The result";
72
+ readonly text: "";
73
+ readonly action: "Silent close view of the same tray holding the key ring on the table. Hold the still composition so the result is easy to read. No dialogue or on-screen text.";
74
+ }];
75
+ }];
76
+ }];
77
+ //# sourceMappingURL=ad-presets.d.ts.map
@@ -0,0 +1,43 @@
1
+ /** Original free starter examples. Model choices and costs resolve at use time.
2
+ * Evidence and source coverage: second-brain/business/projects/slates/research/script-craft/.
3
+ * Preview readiness is explicit; importing a draft never claims a tested output. */
4
+ export const AD_PRESETS = [
5
+ {
6
+ id: 'catch-the-small-problem', revision: 1, name: 'Catch the small problem',
7
+ tags: ['product demonstration', 'spoken', 'everyday'], techniques: ['sc-event', 'sc-proof', 'sc-bridge'],
8
+ description: 'A small everyday frustration becomes a visible product demonstration. An original fictional keys-tray example.',
9
+ adaptation: 'Replace the object, familiar frustration and observable demonstration. Supply real product facts and the destination for the invitation. Compare the opening and its bridge together; keep the demonstration fixed.',
10
+ sections: [
11
+ { label: 'Opening', alternatives: [
12
+ { label: 'Question', text: 'Where do your keys go when you get home?\nMine used to land wherever my hand stopped.', action: 'A hand drops a key ring onto a cluttered entrance table. The keys stop near the edge. An ordinary moment, filmed close enough to see the key ring.' },
13
+ { label: 'Search', text: 'I checked my coat, my bag, and the table.\nThey were under the receipt.', action: 'The same hand checks a coat pocket, opens a bag, then lifts a receipt to reveal the keys. Let the last action answer the search.' },
14
+ { label: 'Objection', text: 'A tray just for keys?\nThat was my question too.', action: 'A small tray sits beside a scattered key ring. A hand turns the tray once, then puts it down by the door.' },
15
+ ] },
16
+ { label: 'Demonstration', alternatives: [
17
+ { label: 'A place to land', text: 'Now I leave this by the door.\nKeys go in when I arrive. I pick them up when I leave.', action: 'The hand places the keys inside the shallow tray. Match the next departure gesture to the same framing: the hand finds the keys in that tray. Show only the behavior described.' },
18
+ ] },
19
+ { label: 'Invitation', alternatives: [
20
+ { label: 'See the product', text: 'Give the small things a place to land.', action: 'Hold a clear view of the tray with the keys inside. Keep the product unobstructed. The destination and offer are supplied by the creator; no price or claim appears in the image.' },
21
+ ] },
22
+ ],
23
+ },
24
+ {
25
+ id: 'silent-small-demonstration', revision: 1, name: 'Let the object explain',
26
+ tags: ['silent', 'product demonstration', 'modular'], techniques: ['sc-event', 'sc-proof', 'sc-modular'],
27
+ description: 'An original silent tray demonstration. The opening event changes while the product action stays fixed.',
28
+ adaptation: 'Choose a real product action that reads without speech. Replace the sample object and place. Keep the physical setup legible; a different opening must still lead naturally into the same demonstration. Add sound only when wanted.',
29
+ sections: [
30
+ { label: 'Opening event', alternatives: [
31
+ { label: 'Almost lost', text: '', action: 'Silent overhead view of an entrance table. A key ring slides toward the edge and stops. A hand enters with a shallow tray. No dialogue or on-screen text.' },
32
+ { label: 'Found it', text: '', action: 'Silent overhead view of an entrance table. A hand lifts a small stack of receipts, revealing a key ring underneath. The hand brings an empty shallow tray into the same space. No dialogue or on-screen text.' },
33
+ ] },
34
+ { label: 'Demonstration', alternatives: [
35
+ { label: 'In reach', text: '', action: 'Silent overhead view of the same table and tray. A hand puts the keys inside the tray, leaves the frame, then returns and picks them up directly. Keep the object and gesture visible. No dialogue or on-screen text.' },
36
+ ] },
37
+ { label: 'Closing view', alternatives: [
38
+ { label: 'The result', text: '', action: 'Silent close view of the same tray holding the key ring on the table. Hold the still composition so the result is easy to read. No dialogue or on-screen text.' },
39
+ ] },
40
+ ],
41
+ },
42
+ ];
43
+ //# sourceMappingURL=ad-presets.js.map
@@ -82,7 +82,7 @@ export const WORKING_METHOD = [
82
82
  both(`For HOW/WHERE questions, load slates_get_prompting_guide with topic "app-manual" and relevant query keywords. Teach the documented buttons and tabs, preserving model-specific conditions; do not invent UI paths or mutate the project when the user only asks for instructions. Slates is a sandbox of optional tools, not a required pipeline.`),
83
83
  both(`1. UNDERSTAND the outcome the user wants. If intent is clear, act with sane defaults — don't interrogate. If genuinely ambiguous, batch every question into ONE message.`),
84
84
  both(`2. ORIENT: call slates_get_workspace_state once at the start of a workflow. Work in the user's CURRENT project — this chat lives inside it. NEVER create a new project unless explicitly asked; if there's no current project, ask which to use.`),
85
- both(`3. LOAD KNOWLEDGE ON DEMAND: before prompting any model or running a multi-step workflow, load the matching guide with slates_get_prompting_guide (index below). Only the guides the task needs, when it needs them.`),
85
+ both(`3. LOAD KNOWLEDGE ON DEMAND: slates_get_prompting_guide returns a short card by default; query a section or technique when needed. Use slates_load_tools with query to find a capability, then names to load its exact schema. A load replaces the previous optional selection. Before quoting a model, load its tool schema or routing guide. Use workspace generationDefaults when the user has no preference. Read the model card delivered by the estimate; fetch a section or full guide for an unfamiliar mode or missing detail. Reuse guidance already in context; retrieve it again when omitted or stale.`),
86
86
  // FORKED: `present_plan` is a loop-level DESKTOP tool, deliberately not in
87
87
  // ALL_OPERATIONS, so MCP never sees it and has no plan gate at all. Its
88
88
  // substitute is the per-op `requires_confirm` threshold plus the host
@@ -102,7 +102,8 @@ export const HARD_RULES = [
102
102
  fork(`- COST DISCIPLINE (slates-cost-discipline): never fire a billable generation outside an approved plan. Estimate before you promise. Batch related generations into one plan.`, `- COST DISCIPLINE (slates-cost-discipline): never fire a billable generation the user has not agreed to. Estimate before you promise. Batch related generations into one quote so the user approves a total, not a drip.`),
103
103
  both(`- CONTENT POLICY: before writing prompts involving real people/celebrities, minors, brands/logos, weapons, or gore, load slates-content-policy and build the scene safe from the first word. If a provider rejects (e.g. real-face detection), explain in plain language — refunds for provider rejections are automatic.`),
104
104
  both(`- PROMPT IS LAW (reference doctrine): references are cited inline by name and image number ("Marcus (images 1 and 2)"); the prompt text leads. Never write role-essays about what each reference is "for".`),
105
- both(`- NAMES AND DESCRIPTIONS ARE THE USER'S UI, NOT YOUR NOTEPAD. A project, storyboard or shot name is rendered at the top of the user's screen at all times. Name it after the piece ("Kaiju selfie"), never after your process, and NEVER append your own status or housekeeping — no "shot list", no "v2", no "Written from IMG-A172 and IMG-A182", no "Delete freely", no "scratch". Leave the description empty unless the user gave you one worth keeping: a note-to-self at the top of the screen reads as part of the product, and the user has to look at it every day. Say that kind of thing in your reply to them instead.`),
105
+ both(`- NAMES AND DESCRIPTIONS ARE THE USER'S UI, NOT YOUR NOTEPAD. A project, board or shot name is rendered at the top of the user's screen at all times. Name it after the piece ("Kaiju selfie"), never after your process, and NEVER append your own status or housekeeping — no "shot list", no "v2", no "Written from IMG-A172 and IMG-A182", no "Delete freely", no "scratch". Leave the description empty unless the user gave you one worth keeping: a note-to-self at the top of the screen reads as part of the product, and the user has to look at it every day. Say that kind of thing in your reply to them instead.`),
106
+ both(`- SAY THE APP'S WORDS. Tool and parameter names keep older nouns; the screen does not, and the user only ever sees the screen. Say board (not storyboard), location (not environment) and look (not style) for Library items, timeline (a saved version of it is a cut), version (a saved rewrite of a script section; not alternative), Words or Words + shots (the Script page's switch), the Generate panel (not the quote), and tab (Media, Script and Board; not lens). Describe what you did in plain words ("Checked your project", "Priced 6 clips"), never a tool or parameter name.`),
106
107
  both(`- RESOLUTION DEFAULT IS UNIFORM: 1080p on the best available video model. Do not crank resolution the user didn't ask for.`),
107
108
  // ⛔ THE MODEL ROUTING BLOCK IS GONE FROM HERE, DELIBERATELY (2026-08-30).
108
109
  //
@@ -120,7 +121,7 @@ export const HARD_RULES = [
120
121
  // exported "in case": an export nothing calls is an export nothing keeps
121
122
  // honest. `describeRouting()` in model-facts.ts is the renderer now.
122
123
  both(`- MODEL KINDS: image, video and audio models are disjoint — no image model makes a video, no video model makes a standalone image, no image or video model makes audio. Which SEAT to pick inside a kind is on each generate op's own \`model\` description, and the full table is the slates-model-selection skill.`),
123
- both(`- ASSET CODES + STALENESS: every asset carries a badge code (IMG-A12 / VID-V3 / AUD-S1, top-left of its gallery card); speak about assets by code + label. Asset lists go STALE — the user creates assets in the Slates UI mid-conversation. When the user names a code you have NOT seen in a tool result this session, resolve it with slates_list_assets (use the search filter) BEFORE using it. NEVER guess an asset id or reuse a nearby UUID — pass the exact id a tool returned for that exact code. A wrong start frame burns real credits.`),
124
+ both(`- ASSET CODES + STALENESS: every asset carries a badge code (IMG-A12 / VID-V3 / AUD-S1, top-left of its gallery card); speak about assets by code + label. When the user says "these" or "this one", call slates_get_selection rather than asking which. Asset lists go STALE — the user creates assets in the Slates UI mid-conversation. When the user names a code you have NOT seen in a tool result this session, resolve it with slates_list_assets (use the search filter) BEFORE using it. NEVER guess an asset id or reuse a nearby UUID — pass the exact id a tool returned for that exact code. A wrong start frame burns real credits.`),
124
125
  both(`- CREDITS ONLY: every generation you drive bills Slates credits (your tool calls enforce this). Never suggest BYOK keys for agent work.`),
125
126
  both(`- Do not invent tools, asset ids, or credit prices. If a tool errors, read the error and fix that exact issue; don't repeat the same call unchanged and never switch models to route around a parameter mistake.`),
126
127
  // FORKED: only the desktop loop auto-polls generation status
@@ -138,7 +139,7 @@ export const HARD_RULES = [
138
139
  // compliance 0/8 → 30/32, while the same guidance behind a guide fetch sat at
139
140
  // 13% before and after. This sentence exists only to say the numbers are
140
141
  // there and must be looked at; the craft is the skill.
141
- both(`- READ THE VARIETY COUNTS BEFORE FIRING A SET: slates_list_shots and slates_get_storyboard_with_frames return the distribution (shot sizes, camera moves, runs of three or more) with the rows. If one bucket is the plurality, fix the board before you spend — slates-shot-variety is the craft.`),
142
+ both(`- READ THE VARIETY COUNTS BEFORE FIRING A SET: slates_list_shots and slates_get_storyboard_with_frames return authored framing and movement distributions. Review unintended sameness while preserving deliberate repetition and the user's format; counts do not require a rewrite. slates-shot-variety is the craft.`),
142
143
  both(`- AUDIO LENGTH IS PROMPT-DRIVEN ON SEED AUDIO: it has no duration parameter, so the durationSeconds you pass is written into the prompt AND is what the user is charged, whatever comes back. Choose it deliberately and load slates-prompting-seed-audio before the first call.`),
143
144
  ];
144
145
  // ── Surface-only footers ───────────────────────────────────────────
@@ -1,40 +1,15 @@
1
- /** Which generate op a list applies to. */
2
1
  export type BannedTokenScope = 'image' | 'video';
3
2
  export interface BannedToken {
4
- /** The literal phrase, verbatim from the skill. */
5
3
  token: string;
6
- /** Skill file it was read from — cited in every warning. */
7
4
  skill: string;
8
5
  scope: BannedTokenScope;
9
6
  }
10
- /** Every banned token, in skill-document order. Integrity asserted at load. */
7
+ /** Compatibility exports: there is no cross-model blacklist. */
11
8
  export declare const BANNED_PROMPT_TOKENS: readonly BannedToken[];
12
- export declare function bannedTokensFor(scope: BannedTokenScope): readonly BannedToken[];
13
- /** The never-use list a single skill declares. Empty for a skill with no block. */
9
+ export declare function bannedTokensFor(_scope: BannedTokenScope): readonly BannedToken[];
10
+ export declare function describeBannedTokens(_scope: BannedTokenScope): string;
14
11
  export declare function bannedTokensForSkill(skill: string): readonly BannedToken[];
15
- /**
16
- * A single model's never-use list, for the estimate RESULT.
17
- *
18
- * Deliberately NOT the cross-model list — that one already rides the op
19
- * description on every call. This is the half that could not: the quirk that is
20
- * true of Veo and false of Kling.
21
- */
12
+ export declare function findBannedTokens(prompt: string, _scope: BannedTokenScope, skill?: string): BannedToken[];
22
13
  export declare function describeBannedTokensForSkill(skill: string): string;
23
- /** The tokens a submitted prompt actually contains. `skill` adds that model's
24
- * own list to the modality-wide one — the two overlap for nano-banana-2 and
25
- * seedance, so hits are deduplicated by token. */
26
- export declare function findBannedTokens(prompt: string, scope: BannedTokenScope, skill?: string): BannedToken[];
27
- /**
28
- * The list as it appears INSIDE an op description — always in context, on both
29
- * surfaces, with no call required to see it. Generated, never hand-typed.
30
- */
31
- export declare function describeBannedTokens(scope: BannedTokenScope): string;
32
- /**
33
- * Non-blocking warning for a submitted prompt. Empty string when clean.
34
- *
35
- * Returned in the op RESULT — the one place the agent cannot avoid reading —
36
- * rather than raised as an error. The generation proceeds either way: the
37
- * sandbox doctrine says make state visible, never block.
38
- */
39
14
  export declare function bannedTokenWarning(prompt: string, scope: BannedTokenScope, skill?: string): string;
40
15
  //# sourceMappingURL=banned-tokens.d.ts.map
@@ -1,219 +1,44 @@
1
- // ============================================================
2
- // BANNED PROMPT TOKENS — the "load the guide" rule, made structural.
3
- //
4
- // THE PROBLEM: "before prompting any model, load the matching guide" is a
5
- // sentence in a system prompt with nothing checking it. Measured 2026-08-30 in
6
- // a real Studio Agent session: slates_get_prompting_guide was called ZERO
7
- // times, and the prompt that shipped tripped the skill's own never-use list
8
- // twice (`photorealistic`, `cinematic`). A rule with no check is a suggestion,
9
- // and an LLM is the least reliable enforcer you could pick.
10
- //
11
- // THE FIX, in two halves, neither of which the model can skip:
12
- // (a) the never-use list is INLINED into the generate ops' descriptions.
13
- // Op descriptions are always in context on BOTH surfaces — there is no
14
- // call to omit and no discretion to exercise.
15
- // (b) the submitted prompt is MATCHED against the list and a warning comes
16
- // back in the op result. Non-blocking: the generation proceeds
17
- // (PRODUCT_PHILOSOPHY.md → make state visible, never block).
18
- //
19
- // 🚨 THE LIST IS NEVER HAND-TYPED HERE. It is extracted from the skill files
20
- // themselves, between `<!-- @banned:start -->` / `<!-- @banned:end -->`
21
- // markers. That is this workspace's LLM-docs doctrine — never hand-type a fact
22
- // an LLM will read — and it is the only way the op description and the skill
23
- // cannot drift apart. Change the skill; the description follows on the next
24
- // build. scripts/agent-surface-lockstep-check.mjs fails the build if a token
25
- // in a description no longer appears in its source skill.
26
- //
27
- // Deterministic by construction (document order, no sorting, no dedupe
28
- // reshuffle) because these strings land in the desktop's prompt-cached tool
29
- // prefix, whose byte-stability IS the cache mechanism.
30
- // ============================================================
31
- import { SKILLS } from '../skills/content.js';
32
- /**
33
- * Where each scope's CROSS-MODEL list lives.
34
- *
35
- * One skill per scope on purpose: these are the two lists that are GENERIC to
36
- * their modality (Stable-Diffusion-era tag soup for images, quality
37
- * incantations for video), not model-specific quirks. A per-model list cannot
38
- * ride the op DESCRIPTION — a description is one static string for every call,
39
- * so it cannot change with the `model` argument. That is what
40
- * `bannedTokensForSkill` below is for: the per-model list rides the estimate
41
- * RESULT, where the model has just been named.
42
- */
43
- const BANNED_TOKEN_SOURCES = [
44
- { skill: 'slates-prompting-nano-banana-2', scope: 'image' },
45
- { skill: 'slates-prompting-seedance', scope: 'video' },
46
- ];
47
- /**
48
- * 🚨 THE ENFORCEMENT THAT WORKED COVERED TWO SKILLS OF FIFTEEN.
49
- *
50
- * `describeBannedTokens('image')` was Nano Banana's list and `('video')` was
51
- * Seedance's, so a Veo, Kling, LTX, MiniMax, FLUX, Seedream, GPT-Image or audio
52
- * generation was matched against another model's never-use list and its own was
53
- * enforced by nothing — while four skills (content-policy, lip-sync,
54
- * minimax-h3, motion-transfer) carried never-use prose with no markers at all,
55
- * which is a rule an LLM has to notice.
56
- *
57
- * Every skill that carries an `@banned` block now contributes to a per-skill
58
- * list, delivered on the estimate result beside the craft card. The two above
59
- * stay ALSO on the op descriptions, because a modality-wide list is true of
60
- * every call that op can make.
61
- */
62
- function extractPerSkill() {
63
- const out = new Map();
64
- for (const [skill, content] of Object.entries(SKILLS)) {
65
- // Cheap pre-test: only pay the regex for files that carry the marker.
66
- if (!content.includes('@banned:start'))
67
- continue;
68
- const scope = inferScope(skill);
69
- out.set(skill, extractFromSkill(skill).map((token) => ({ token, skill, scope })));
70
- }
71
- return out;
72
- }
73
- /** Image-lane skills prompt for pixels; everything else is a time-based lane.
74
- * Only used to tag a token for the warning text — the per-skill list is
75
- * matched by SKILL, never by scope, so a wrong guess here cannot mis-enforce. */
76
- function inferScope(skill) {
77
- return /nano-banana|gpt-image|flux|seedream/.test(skill) ? 'image' : 'video';
78
- }
79
- const FENCE_RE = /<!--\s*@banned:start\s*-->([\s\S]*?)<!--\s*@banned:end\s*-->/g;
80
- const HTML_COMMENT_RE = /<!--[\s\S]*?-->/g;
81
- const BACKTICKED_RE = /`([^`\n]+)`/g;
82
- /**
83
- * Pull every backticked phrase out of a skill's fenced block(s).
84
- *
85
- * HTML comments are stripped FIRST: the fence carries a "MACHINE-READ" note to
86
- * whoever edits the skill next, and that note itself contains backticks.
1
+ /** Model-specific prompt advice, extracted from each skill's @banned blocks.
2
+ * Warnings never rewrite or block a prompt. No model's list is universal.
87
3
  */
88
- function extractFromSkill(skill) {
89
- const content = SKILLS[skill];
90
- if (content === undefined) {
91
- throw new Error(`[banned-tokens] no such skill: ${skill}. BANNED_TOKEN_SOURCES must name files in packages/shared/skills/.`);
92
- }
93
- const out = [];
94
- let fence;
95
- FENCE_RE.lastIndex = 0;
96
- let fences = 0;
97
- while ((fence = FENCE_RE.exec(content)) !== null) {
98
- fences += 1;
99
- const body = fence[1].replace(HTML_COMMENT_RE, '');
100
- let m;
101
- BACKTICKED_RE.lastIndex = 0;
102
- while ((m = BACKTICKED_RE.exec(body)) !== null) {
103
- const token = m[1].trim();
104
- if (token && !out.includes(token))
105
- out.push(token);
106
- }
107
- }
108
- if (fences === 0) {
109
- throw new Error(`[banned-tokens] ${skill}.md has no <!-- @banned:start --> / <!-- @banned:end --> block. ` +
110
- `The op description and the prompt warnings are GENERATED from it — restore the markers, ` +
111
- `or drop the skill from BANNED_TOKEN_SOURCES.`);
112
- }
113
- if (out.length === 0) {
114
- throw new Error(`[banned-tokens] ${skill}.md has a @banned block with no backticked tokens in it. ` +
115
- `An empty list would silently disable the check instead of failing it.`);
116
- }
117
- return out;
118
- }
119
- /** Every banned token, in skill-document order. Integrity asserted at load. */
120
- export const BANNED_PROMPT_TOKENS = BANNED_TOKEN_SOURCES.flatMap(({ skill, scope }) => extractFromSkill(skill).map((token) => ({ token, skill, scope })));
121
- const byScope = new Map();
122
- for (const entry of BANNED_PROMPT_TOKENS) {
123
- const list = byScope.get(entry.scope) ?? [];
124
- list.push(entry);
125
- byScope.set(entry.scope, list);
126
- }
127
- export function bannedTokensFor(scope) {
128
- return byScope.get(scope) ?? [];
129
- }
130
- /** Regex cache — one word-boundary matcher per token, built once. */
4
+ import { SKILLS } from '../skills/content.js';
5
+ const bySkill = new Map();
6
+ for (const [skill, content] of Object.entries(SKILLS)) {
7
+ const fences = [...content.matchAll(/<!--\s*@banned:start\s*-->([\s\S]*?)<!--\s*@banned:end\s*-->/g)];
8
+ if (!fences.length)
9
+ continue;
10
+ const tokens = [...new Set(fences.flatMap((f) => [...f[1].replace(/<!--[\s\S]*?-->/g, '').matchAll(/`([^`\n]+)`/g)].map((m) => m[1].trim())))];
11
+ if (!tokens.length)
12
+ throw new Error(`${skill}: @banned block contains no tokens`);
13
+ const scope = /nano-banana|gpt-image|flux|seedream/.test(skill) ? 'image' : 'video';
14
+ bySkill.set(skill, tokens.map((token) => ({ token, skill, scope })));
15
+ }
16
+ /** Compatibility exports: there is no cross-model blacklist. */
17
+ export const BANNED_PROMPT_TOKENS = Object.freeze([]);
18
+ export function bannedTokensFor(_scope) { return BANNED_PROMPT_TOKENS; }
19
+ export function describeBannedTokens(_scope) { return ''; }
20
+ export function bannedTokensForSkill(skill) { return bySkill.get(skill) ?? []; }
131
21
  const matchers = new Map();
132
22
  function matcherFor(token) {
133
- let re = matchers.get(token);
134
- if (!re) {
23
+ let matcher = matchers.get(token);
24
+ if (!matcher) {
135
25
  const escaped = token.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
136
- // \b at both ends so `4k` does not fire inside "84king" and `flawless`
137
- // does not fire inside "flawlessly". Every token starts and ends with a
138
- // word character today; if one ever starts with punctuation, \b would
139
- // anchor wrong — the load-time check below is what would catch it.
140
- re = new RegExp(`\\b${escaped}\\b`, 'i');
141
- matchers.set(token, re);
142
- }
143
- return re;
144
- }
145
- for (const { token, skill } of BANNED_PROMPT_TOKENS) {
146
- if (!/^\w/.test(token) || !/\w$/.test(token)) {
147
- throw new Error(`[banned-tokens] ${skill}: token ${JSON.stringify(token)} does not start and end with a word ` +
148
- `character, so the \\b word-boundary match would never fire. Rewrite the entry or teach ` +
149
- `matcherFor() the new shape.`);
26
+ matcher = new RegExp(`(?<!\\w)${escaped}(?!\\w)`, 'i');
27
+ matchers.set(token, matcher);
150
28
  }
29
+ return matcher;
151
30
  }
152
- /** Every per-skill list, keyed by skill name. */
153
- const bySkill = extractPerSkill();
154
- /** The never-use list a single skill declares. Empty for a skill with no block. */
155
- export function bannedTokensForSkill(skill) {
156
- return bySkill.get(skill) ?? [];
31
+ export function findBannedTokens(prompt, _scope, skill) {
32
+ return skill ? bannedTokensForSkill(skill).filter((b) => matcherFor(b.token).test(prompt)) : [];
157
33
  }
158
- /**
159
- * A single model's never-use list, for the estimate RESULT.
160
- *
161
- * Deliberately NOT the cross-model list — that one already rides the op
162
- * description on every call. This is the half that could not: the quirk that is
163
- * true of Veo and false of Kling.
164
- */
165
34
  export function describeBannedTokensForSkill(skill) {
166
35
  const list = bannedTokensForSkill(skill);
167
- if (list.length === 0)
168
- return '';
169
- return (`NEVER put these in a ${skill.replace('slates-prompting-', '')} prompt: ` +
170
- list.map((b) => `"${b.token}"`).join(', ') +
171
- `. Describe specifically instead (${skill}).`);
36
+ return list.length ? `Avoid these phrases for ${skill}: ${list.map((b) => `"${b.token}"`).join(', ')}. Describe the intended result specifically.` : '';
172
37
  }
173
- /** The tokens a submitted prompt actually contains. `skill` adds that model's
174
- * own list to the modality-wide one — the two overlap for nano-banana-2 and
175
- * seedance, so hits are deduplicated by token. */
176
- export function findBannedTokens(prompt, scope, skill) {
177
- const candidates = [...bannedTokensFor(scope), ...(skill ? bannedTokensForSkill(skill) : [])];
178
- const seen = new Set();
179
- const hits = [];
180
- for (const b of candidates) {
181
- if (seen.has(b.token))
182
- continue;
183
- seen.add(b.token);
184
- if (matcherFor(b.token).test(prompt))
185
- hits.push(b);
186
- }
187
- return hits;
188
- }
189
- /**
190
- * The list as it appears INSIDE an op description — always in context, on both
191
- * surfaces, with no call required to see it. Generated, never hand-typed.
192
- */
193
- export function describeBannedTokens(scope) {
194
- const list = bannedTokensFor(scope);
195
- if (list.length === 0)
196
- return '';
197
- const skills = [...new Set(list.map((b) => b.skill))].join(' / ');
198
- return (`NEVER put these in a prompt (they measurably degrade output — full rationale in ${skills}): ` +
199
- list.map((b) => `"${b.token}"`).join(', ') +
200
- `. Describe specifically instead.`);
201
- }
202
- /**
203
- * Non-blocking warning for a submitted prompt. Empty string when clean.
204
- *
205
- * Returned in the op RESULT — the one place the agent cannot avoid reading —
206
- * rather than raised as an error. The generation proceeds either way: the
207
- * sandbox doctrine says make state visible, never block.
208
- */
209
38
  export function bannedTokenWarning(prompt, scope, skill) {
210
39
  const hits = findBannedTokens(prompt, scope, skill);
211
- if (hits.length === 0)
40
+ if (!hits.length)
212
41
  return '';
213
- const skills = [...new Set(hits.map((b) => b.skill))].join(', ');
214
- return (`⚠️ PROMPT WARNING: your prompt contains ${hits.map((b) => `"${b.token}"`).join(', ')} — ` +
215
- `on the never-use list in ${skills}. Not blocked, and this generation ran as submitted. ` +
216
- `Load that guide with slates_get_prompting_guide and rewrite with specific description ` +
217
- `(named lens, light direction, stock, composition) before the next generation.`);
42
+ return `⚠️ PROMPT WARNING: ${hits.map((b) => `"${b.token}"`).join(', ')} appear in ${skill}'s avoid list. This warning does not change or block your prompt. Describe the intended result specifically; query that guide for details.`;
218
43
  }
219
44
  //# sourceMappingURL=banned-tokens.js.map
@@ -57,7 +57,7 @@ function extractCard(skill, content) {
57
57
  if (body.length > CRAFT_CARD_CEILING) {
58
58
  throw new Error(`[craft-cards] ${skill}.md's @card block is ${body.length} chars, over the ` +
59
59
  `${CRAFT_CARD_CEILING} ceiling. A card rides EVERY estimate result for that model — ` +
60
- `move the overflow into the body of the skill, which slates_get_prompting_guide returns whole.`);
60
+ `move the overflow into the body of the skill, retrieved with slates_get_prompting_guide depth: full.`);
61
61
  }
62
62
  return body;
63
63
  }
@@ -77,6 +77,6 @@ export function describeCraftCard(skill) {
77
77
  const card = craftCard(skill);
78
78
  if (!card)
79
79
  return '';
80
- return `${card}\n\nFull guide (examples, failure modes, sources): slates_get_prompting_guide("${skill}").`;
80
+ return `${card}\n\nFull guide (examples, failure modes, sources): slates_get_prompting_guide({topic: "${skill}", depth: "full"}); use query for one section.`;
81
81
  }
82
82
  //# sourceMappingURL=craft-cards.js.map