@slatesvideo/shared 0.7.1 → 0.7.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.
Files changed (88) hide show
  1. package/dist/clients/cloud.d.ts +4 -0
  2. package/dist/clients/cloud.js +11 -3
  3. package/dist/index.d.ts +2 -1
  4. package/dist/index.js +2 -1
  5. package/dist/manual/content.d.ts +1 -1
  6. package/dist/manual/content.js +1 -1
  7. package/dist/manual/index.d.ts +11 -2
  8. package/dist/manual/index.js +178 -14
  9. package/dist/operations/index.d.ts +292 -94
  10. package/dist/operations/index.js +870 -199
  11. package/dist/operations/surface.d.ts +6 -2
  12. package/dist/operations/surface.js +35 -5
  13. package/dist/prompts/agent-doctrine.d.ts +4 -4
  14. package/dist/prompts/agent-doctrine.js +18 -29
  15. package/dist/prompts/generation-policy.d.ts +1 -1
  16. package/dist/prompts/guide-discovery.d.ts +23 -0
  17. package/dist/prompts/guide-discovery.js +39 -0
  18. package/dist/prompts/guide-retrieval.js +1 -1
  19. package/dist/prompts/model-capabilities.d.ts +8 -9
  20. package/dist/prompts/model-capabilities.js +11 -51
  21. package/dist/prompts/model-facts.d.ts +2 -2
  22. package/dist/prompts/model-facts.js +15 -26
  23. package/dist/prompts/partials.generated.js +6 -3
  24. package/dist/prompts/prompting-tips.d.ts +1 -1
  25. package/dist/prompts/prompting-tips.js +21 -63
  26. package/dist/prompts/search-terms.d.ts +3 -0
  27. package/dist/prompts/search-terms.js +24 -0
  28. package/dist/skills/content.js +36 -37
  29. package/dist/skills/metadata.d.ts +7 -0
  30. package/dist/skills/metadata.js +29 -0
  31. package/exports/slates-chatgpt-images/generated/SKILL.md +7 -1
  32. package/exports/slates-chatgpt-images/generated/slates-chatgpt-images.skill +0 -0
  33. package/exports/slates-prompt-builder/generated/SKILL.md +28 -16
  34. package/exports/slates-prompt-builder/generated/reference-character.md +12 -13
  35. package/exports/slates-prompt-builder/generated/reference-content-policy.md +2 -2
  36. package/exports/slates-prompt-builder/generated/reference-gpt-image-2-5.md +191 -0
  37. package/exports/slates-prompt-builder/generated/reference-kling.md +32 -11
  38. package/exports/slates-prompt-builder/generated/reference-nano-banana.md +24 -6
  39. package/exports/slates-prompt-builder/generated/reference-omni-flash.md +65 -0
  40. package/exports/slates-prompt-builder/generated/reference-seedance-2-5.md +362 -0
  41. package/exports/slates-prompt-builder/generated/reference-seedance.md +34 -4
  42. package/exports/slates-prompt-builder/generated/slates-prompt-builder-manifest.json +77 -23
  43. package/exports/slates-prompt-builder/generated/slates-prompt-builder.skill +0 -0
  44. package/package.json +2 -1
  45. package/skills/_partials/blender-action-curves.md +24 -0
  46. package/skills/_partials/cinematic-card.md +1 -1
  47. package/skills/_partials/iteration-diagnosis.md +5 -0
  48. package/skills/_partials/model-routing.md +35 -0
  49. package/skills/_partials/seedance-25-timestamps.md +2 -2
  50. package/skills/_partials/still-gate.md +2 -2
  51. package/skills/_partials/thresholds.md +1 -1
  52. package/skills/slates-blocking-to-prompt.md +15 -13
  53. package/skills/slates-camera-language.md +45 -7
  54. package/skills/slates-character-identity.md +8 -6
  55. package/skills/slates-chatgpt-images.md +7 -1
  56. package/skills/slates-cinematic-look.md +1 -1
  57. package/skills/slates-content-policy.md +4 -6
  58. package/skills/slates-cost-discipline.md +18 -12
  59. package/skills/slates-dialogue-blocking.md +6 -6
  60. package/skills/slates-direct-response-ad.md +1 -1
  61. package/skills/slates-edit-and-iterate.md +12 -4
  62. package/skills/slates-model-selection.md +82 -90
  63. package/skills/slates-one-prompt-film.md +1 -1
  64. package/skills/slates-previs-blocking.md +44 -13
  65. package/skills/slates-project-organization.md +2 -2
  66. package/skills/slates-prompting-elevenlabs.md +4 -4
  67. package/skills/slates-prompting-flux-2-max.md +2 -3
  68. package/skills/slates-prompting-gpt-image-2-5.md +2 -2
  69. package/skills/slates-prompting-inworld-tts.md +1 -1
  70. package/skills/slates-prompting-kling-v3.md +11 -9
  71. package/skills/slates-prompting-lip-sync.md +15 -15
  72. package/skills/slates-prompting-ltx-2-5.md +5 -6
  73. package/skills/slates-prompting-minimax-h3.md +11 -11
  74. package/skills/slates-prompting-motion-transfer.md +8 -8
  75. package/skills/slates-prompting-nano-banana-2.md +8 -4
  76. package/skills/slates-prompting-omni-flash.md +9 -9
  77. package/skills/slates-prompting-seed-audio.md +24 -4
  78. package/skills/slates-prompting-seedance-2-5.md +40 -30
  79. package/skills/slates-prompting-seedance.md +4 -4
  80. package/skills/slates-prompting-seedream-5-lite.md +6 -6
  81. package/skills/slates-restyle-from-blocking.md +2 -2
  82. package/skills/slates-script-craft.md +1 -1
  83. package/skills/slates-shot-variety.md +1 -1
  84. package/skills/slates-storyboard-from-script.md +1 -1
  85. package/skills/slates-style-prompting.md +8 -6
  86. package/skills/slates-ugc-influencer-ad.md +1 -1
  87. package/skills/slates-vision-feedback-loop.md +118 -110
  88. package/skills/slates-prompting-veo-3.md +0 -224
@@ -61,12 +61,16 @@ export declare function toolDefinition(op: SurfaceOp): ToolDefinition;
61
61
  * Render a tool surface.
62
62
  *
63
63
  * `desktop` sends `core` plus whatever groups have been loaded this run; `mcp`
64
- * renders all definitions, and the MCP server lists all of them unless started with
65
- * `--tools=compact`.
64
+ * renders every definition for the fixed MCP list.
66
65
  */
67
66
  export declare function toolDefinitions(ops: readonly SurfaceOp[], opts: {
68
67
  surface: 'desktop' | 'mcp';
69
68
  groups?: readonly OperationGroup[];
70
69
  names?: readonly string[];
71
70
  }): ToolDefinition[];
71
+ /** Rank exact task words by rarity across the current registry, returning at most ten. */
72
+ export declare function searchTools<T extends Pick<SurfaceOp, 'id' | 'description'>>(operations: readonly T[], query: string): Array<{
73
+ op: T;
74
+ score: number;
75
+ }>;
72
76
  //# sourceMappingURL=surface.d.ts.map
@@ -15,8 +15,7 @@
15
15
  // mechanism: we build every turn there, so a load reaches the model. The
16
16
  // MCP server lists every op and leaves hiding definitions to the host
17
17
  // (tiering there left Claude and Codex users unable to generate in 0.6.0;
18
- // see the TOOLS comment in packages/mcp/src/server.ts). `--tools=compact`
19
- // is the opt-in exception.
18
+ // see the TOOLS comment in packages/mcp/src/server.ts).
20
19
  //
21
20
  // 3. ONE SCHEMA RENDERER. The desktop rendered `$refStrategy: 'none'` and
22
21
  // the MCP server rendered `target: 'openApi3'`, so "the two surfaces
@@ -29,6 +28,7 @@
29
28
  // an op whose `run` body posts is a lie that lets a host auto-approve a
30
29
  // mutation, so the check has to be able to catch it, and it is mutation-tested.
31
30
  // ============================================================
31
+ import { searchTerms } from '../prompts/search-terms.js';
32
32
  import { zodToJsonSchema } from 'zod-to-json-schema';
33
33
  // ── 1. Annotations ──────────────────────────────────────────────────────────
34
34
  /** Ops that only read. Prefixes, because the verb IS the first word of the id. */
@@ -145,6 +145,9 @@ export const OPERATION_GROUPS = {
145
145
  'slates_relocate_project',
146
146
  'slates_undo_relocate_project',
147
147
  'slates_reveal_file',
148
+ 'slates_reorder_folders',
149
+ 'slates_reorder_pins',
150
+ 'slates_link_asset_source',
148
151
  ],
149
152
  // The cut and the export. A generation session never touches these.
150
153
  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'],
@@ -182,6 +185,9 @@ export const OPERATION_GROUPS = {
182
185
  'slates_duplicate_shot',
183
186
  'slates_split_shot',
184
187
  'slates_merge_shots',
188
+ 'slates_get_usage',
189
+ 'slates_get_app_settings',
190
+ 'slates_set_app_settings',
185
191
  ],
186
192
  // A third transport nobody without Blender installed can reach.
187
193
  blender: [
@@ -215,7 +221,7 @@ export function tierFor(id) {
215
221
  export const GROUP_SUMMARY = {
216
222
  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',
217
223
  script: 'rich script documents, anchored sections and their saved versions, reusable passages, variations and take input history',
218
- 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',
224
+ timeline: 'named cuts and selected exports; the timeline (tracks, clips, markers, settings), video export and FCP7 XML export for DaVinci Resolve or Premiere, clip trimming',
219
225
  admin: 'rename / delete / reorder for projects, characters, locations, looks, boards, scenes and frames; shot duplicate, split and merge',
220
226
  blender: 'the Blender previs bridge — scene inspection, bpy execution, API docs, grey-box render',
221
227
  };
@@ -241,8 +247,7 @@ export function toolDefinition(op) {
241
247
  * Render a tool surface.
242
248
  *
243
249
  * `desktop` sends `core` plus whatever groups have been loaded this run; `mcp`
244
- * renders all definitions, and the MCP server lists all of them unless started with
245
- * `--tools=compact`.
250
+ * renders every definition for the fixed MCP list.
246
251
  */
247
252
  export function toolDefinitions(ops, opts) {
248
253
  const loaded = new Set(opts.groups ?? []);
@@ -255,4 +260,29 @@ export function toolDefinitions(ops, opts) {
255
260
  })
256
261
  .map(toolDefinition);
257
262
  }
263
+ // Natural brief words for capabilities whose API names use technical terms.
264
+ const TASK_ALIASES = {
265
+ slates_generate_video: 'create animate photo picture film movie ad advert advertisement',
266
+ slates_generate_from_shots: 'create make film movie ad advert advertisement',
267
+ slates_generate_image: 'photo picture illustration',
268
+ slates_edit_image: 'photo picture',
269
+ slates_use_pictures_as_first_frames: 'photo animate',
270
+ slates_generate_audio: 'voiceover narration speech ambience sound effect',
271
+ };
272
+ /** Rank exact task words by rarity across the current registry, returning at most ten. */
273
+ export function searchTools(operations, query) {
274
+ const terms = searchTerms(query);
275
+ const documents = operations.map(op => ({
276
+ op,
277
+ name: new Set(searchTerms(op.id)),
278
+ words: new Set(searchTerms(`${op.description} ${TASK_ALIASES[op.id] ?? ''}`)),
279
+ }));
280
+ const frequency = new Map(terms.map(term => [term, documents.filter(doc => doc.name.has(term) || doc.words.has(term)).length]));
281
+ return documents.map(doc => ({
282
+ op: doc.op,
283
+ score: terms.reduce((sum, term) => sum + (doc.name.has(term) || doc.words.has(term)
284
+ ? Math.log(1 + documents.length / (1 + (frequency.get(term) ?? 0))) * (doc.name.has(term) ? 2 : 1)
285
+ : 0), 0),
286
+ })).filter(match => match.score > 0).sort((a, b) => b.score - a.score || a.op.id.localeCompare(b.op.id)).slice(0, 10);
287
+ }
258
288
  //# sourceMappingURL=surface.js.map
@@ -4,16 +4,16 @@
4
4
  * `desktop` — the in-app Studio Agent. Its loop enforces plan approval in
5
5
  * code, auto-polls generation status, and displays orchestration cost itself.
6
6
  * `mcp` — Claude Code / Claude Desktop / Cursor / Codex over stdio. No
7
- * `present_plan` tool, no auto-poll, no app chrome; the consent gate is the
8
- * op-level `requires_confirm` threshold plus the host client's own per-call
9
- * tool approval. The asymmetry is DELIBERATE — see slates-mcp/CLAUDE.md.
7
+ * `present_plan` tool, no auto-poll, no app chrome. The user approves the
8
+ * itemized spend in chat; op-level `requires_confirm` is a threshold backstop.
9
+ * Host tool approval depends on the client. See slates-mcp/CLAUDE.md.
10
10
  */
11
11
  export type AgentSurface = 'desktop' | 'mcp';
12
12
  interface SkillIndexEntry {
13
13
  name: string;
14
14
  description: string;
15
15
  }
16
- /** Parse `name:`/`description:` out of each embedded skill's frontmatter. */
16
+ /** Generated from the same validated metadata used by discovery and installation. */
17
17
  export declare function buildSkillIndex(): SkillIndexEntry[];
18
18
  export declare const WORKING_METHOD: ReadonlyArray<Record<AgentSurface, string>>;
19
19
  export declare const HARD_RULES: ReadonlyArray<Record<AgentSurface, string>>;
@@ -50,6 +50,7 @@
50
50
  // the whole embedded SKILLS record. Root barrel only.
51
51
  // ============================================================
52
52
  import { SKILLS } from '../skills/content.js';
53
+ import { guideCatalog } from './guide-discovery.js';
53
54
  import { VIDEO_MODELS, AUDIO_MODELS } from '../operations/index.js';
54
55
  /** A line that is identical on both surfaces. The default. */
55
56
  function both(text) {
@@ -59,21 +60,9 @@ function both(text) {
59
60
  function fork(desktop, mcp) {
60
61
  return { desktop, mcp };
61
62
  }
62
- /** Parse `name:`/`description:` out of each embedded skill's frontmatter. */
63
+ /** Generated from the same validated metadata used by discovery and installation. */
63
64
  export function buildSkillIndex() {
64
- const entries = [];
65
- for (const key of Object.keys(SKILLS).sort()) {
66
- const content = SKILLS[key];
67
- const fm = /^---\n([\s\S]*?)\n---/.exec(content);
68
- let description = '';
69
- if (fm) {
70
- const m = /^description:\s*(.+)$/m.exec(fm[1]);
71
- if (m)
72
- description = m[1].trim().replace(/^['"]|['"]$/g, '');
73
- }
74
- entries.push({ name: key, description });
75
- }
76
- return entries;
65
+ return guideCatalog(SKILLS).map(({ name, description }) => ({ name, description }));
77
66
  }
78
67
  // ── Preamble ───────────────────────────────────────────────────────
79
68
  // FORKED, and the MCP side is a SUMMARY THAT MUST FIT IN THE CUT. Claude Code
@@ -83,35 +72,35 @@ export function buildSkillIndex() {
83
72
  // find a tool when the host shows names only, the spend gate, real numbers, the
84
73
  // current project — sits here; the sections below expand it for clients that
85
74
  // read everything. mcp-instructions-smoke asserts these lines land inside the
86
- // cut with the UPDATE AVAILABLE notice in front of them.
87
- const PREAMBLE = fork(`You are the Slates Studio Agent — a production assistant living inside Slates, the AI video creation studio. You plan and execute video/image production runs by chaining the Slates tools: script → characters → images → videos → quality-check → regenerate, ending with assets in the user's project (and on the timeline when asked).`, `You are connected to Slates, the AI video creation studio. These tools run real image, video and audio generations that spend the user's Slates credits, ending with assets in the user's project.
75
+ // cut when UPDATE AVAILABLE follows the first doctrine paragraph.
76
+ const PREAMBLE = fork(`You are the Slates Studio Agent — a production assistant living inside Slates, the AI video creation studio. Turn the user's vision into finished work using the optional Slates tools and relevant production craft. Choose the workflow for the brief; start from the user's existing material and bring results into their project or timeline when asked.`, `Turn the user's vision into finished work in Slates. Find craft with slates_get_prompting_guide(query: the brief); reuse current guidance. Tools generate real media and spend credits. Before generation, estimate every step, show the itemized total and wait for approval. Work in the current project. Choose tools to fit the brief.
88
77
 
89
78
  ## Essentials (the sections below expand on these)
90
79
 
91
- - FINDING TOOLS: every Slates capability is its own slates_* tool. If your client shows tool names only, search your tools for the task (for example "slates generate video") and load that tool before calling it. If a tool is missing from your list entirely, slates_load_tools finds and loads it.
80
+ - FINDING TOOLS: every Slates capability is its own slates_* tool. If your client shows tool names only, search your tools for the task (for example "slates generate video") and load that tool before calling it. slates_load_tools searches Slates tools by task and returns exact schemas.
92
81
  - SPENDING: before ANY generation, price every step with slates_estimate_generation_cost, show the user the itemized total in ONE message, and wait for their OK. Pass confirm: true only to relay an explicit user OK for that exact spend.
93
82
  - REAL NUMBERS ONLY: every cost, balance or count you state is copied from a tool result in this session. Never describe how a generation looks unless you fetched it this session.
94
83
  - PROJECT: call slates_get_workspace_state once, work in the user's current project, and never create a project unless asked.
95
- - CRAFT: before prompting a model, read its guide with slates_get_prompting_guide (pass the model id). For how or where in the app, use topic "app-manual".
84
+ - CRAFT: discover relevant guides with slates_get_prompting_guide(query: the brief). Reuse current cards and guidance; fetch sections for missing craft. For UI help, use topic "app-manual".
96
85
  - Speak in the app's words and name assets by code and label (IMG-A12), never by tool name or UUID.`);
97
86
  // ── The working method ─────────────────────────────────────────────
98
87
  export const WORKING_METHOD = [
99
- 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.`),
88
+ 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. When the control is on screen, offer to point at it (slates_highlight_control); show a picture of a screen (slates_get_manual_picture) only when the user cannot find something or asks what it looks like. Slates is a sandbox of optional tools, not a required pipeline.`),
100
89
  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.`),
101
90
  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.`),
102
91
  // FORKED: the desktop sends core tools plus what `slates_load_tools` loaded,
103
92
  // so its agent must load before calling. The MCP server lists every tool and
104
93
  // the host decides what to show (Essentials, FINDING TOOLS), so telling an MCP
105
94
  // client to load first costs it a turn for a tool it already has.
106
- fork(`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.`, `3. LOAD KNOWLEDGE ON DEMAND: slates_get_prompting_guide returns a short card by default; query a section or technique when needed. Before quoting a model, read 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.`),
95
+ fork(`3. LOAD KNOWLEDGE ON DEMAND: discover craft from the brief with slates_get_prompting_guide(query: the need); no topic returns ranked guides, no query returns the catalog. With a topic, read a short card or query a section/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.`, `3. LOAD KNOWLEDGE ON DEMAND: discover craft from the brief with slates_get_prompting_guide(query: the need); no topic returns ranked guides, no query returns the catalog. With a topic, read a short card or query a section/technique when needed. Before quoting a model, read 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.`),
107
96
  // FORKED: `present_plan` is a loop-level DESKTOP tool, deliberately not in
108
97
  // ALL_OPERATIONS, so MCP never sees it and has no plan gate at all. Its
109
- // substitute is the per-op `requires_confirm` threshold plus the host
110
- // client's own per-call approval UI. Describing the desktop gate to an MCP
98
+ // substitute is the itemized quote and explicit user approval in chat,
99
+ // backed by per-op `requires_confirm`. Describing the desktop gate to an MCP
111
100
  // client would name a tool that does not exist.
112
101
  fork(`4. PLAN + GET APPROVAL: before ANY generation, call present_plan with itemized credit costs (slates_estimate_generation_cost per step). One approval covers the plan's listed steps ONLY. Generation tools are rejected without an approved plan — and any user revision, question, or new instruction after an approval means you MUST re-present the plan BEFORE the next generation call (calling a generation op first just gets BLOCKED and wastes a turn).`, `4. PLAN + GET APPROVAL: before ANY generation, price every step with slates_estimate_generation_cost and put the itemized total in front of the user in ONE message, then wait for their answer. There is no present_plan tool on this surface — consent is per call: a generation over the confirm threshold returns requires_confirm, and confirm: true is only ever a relay of an explicit user OK for that exact spend. Never pass confirm: true to clear a gate the user has not seen.`),
113
102
  fork(`5. EXECUTE: after approval, pass confirm: true (the approval IS the consent — never re-ask per step). Use background: true + status polling for video.`, `5. EXECUTE: run the approved steps, passing confirm: true only for the spend the user actually OK'd. Use background: true + status polling for video.`),
114
- both(`6. QUALITY-CHECK: you have vision. After key generations, fetch the result (slates_get_asset_image / slates_get_asset_video_frames) and review it against the brief (slates-vision-feedback-loop). Fix real problems; don't churn credits polishing what works. Change ONE variable per regeneration.`),
103
+ both(`6. QUALITY-CHECK: you have vision. Inspect images or sampled video frames against the brief (slates-vision-feedback-loop). Frames establish visible appearance, not continuous motion, lip sync or sound: review those through actual playback or an audio-capable host when available; otherwise state they remain unreviewed. Fix real problems and change ONE variable per regeneration.`),
115
104
  both(`7. REPORT: when done, summarize what was made and where it landed. Concise and concrete.`),
116
105
  ];
117
106
  // ── Hard rules ─────────────────────────────────────────────────────
@@ -125,7 +114,7 @@ export const HARD_RULES = [
125
114
  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".`),
126
115
  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.`),
127
116
  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.`),
128
- both(`- RESOLUTION DEFAULT IS UNIFORM: 1080p on the best available video model. Do not crank resolution the user didn't ask for.`),
117
+ both(`- RESOLUTION DEFAULT: each model's own default. Do not crank resolution the user didn't ask for.`),
129
118
  // ⛔ THE MODEL ROUTING BLOCK IS GONE FROM HERE, DELIBERATELY (2026-08-30).
130
119
  //
131
120
  // It was 17,700 characters — 47% of the whole doctrine — and every lane of it
@@ -141,9 +130,9 @@ export const HARD_RULES = [
141
130
  // The old `buildModelRouting()` renderer was DELETED rather than left
142
131
  // exported "in case": an export nothing calls is an export nothing keeps
143
132
  // honest. `describeRouting()` in model-facts.ts is the renderer now.
144
- 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.`),
133
+ 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 a standalone audio file (video models can generate sound inside the clip). 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.`),
145
134
  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.`),
146
- both(`- CREDITS ONLY: every generation you drive bills Slates credits (your tool calls enforce this). Never suggest BYOK keys for agent work.`),
135
+ both(`- CREDITS ONLY: every generation you drive bills Slates credits (your tool calls enforce this), except ChatGPT images, which use the person's ChatGPT account. Never suggest BYOK keys for agent work.`),
147
136
  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.`),
148
137
  // FORKED: only the desktop loop auto-polls generation status
149
138
  // (loop.ts → autoPollUntilTerminal). Telling an MCP client "the app keeps
@@ -166,12 +155,12 @@ export const HARD_RULES = [
166
155
  // ── Surface-only footers ───────────────────────────────────────────
167
156
  //
168
157
  // MCP clients have no Slates chrome and may have no skill FILES installed
169
- // (`slates install-skills` covers Claude Code; Claude Desktop, Cursor and
170
- // Codex have no equivalent mechanism). The desktop always has the embedded
158
+ // (`slates install-skills` covers Claude Code and Codex; other hosts may
159
+ // use different native skill locations). The desktop always has the embedded
171
160
  // record, so this paragraph would be dead weight in its cached prefix.
172
161
  const MCP_FOOTER = `## Working without skill files
173
162
 
174
- If slates-* skill files are not installed in this client, every one of them is still reachable as data: call slates_get_prompting_guide with the skill name (or a model id — it resolves aliases). The guide index above is the complete list. "No skill installed" is never a reason to prompt a model blind.
163
+ If slates-* skill files are not installed in this client, every one of them is still reachable as data: call slates_get_prompting_guide with the skill name (or a model id — it resolves aliases). Query the brief or browse topic "catalog" for generated discovery and entitled member playbooks. "No skill installed" is never a reason to prompt a model blind.
175
164
  `;
176
165
  /**
177
166
  * THE doctrine string for a surface. The desktop's whole system prompt and the
@@ -2,7 +2,7 @@
2
2
  * This is NOT a model capability or provider batch limit. Desktop mirror is
3
3
  * generated by slate/scripts/sync-generation-policy.mjs; never edit it there. */
4
4
  export declare const IMAGE_QUANTITIES: readonly [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
5
- export declare const MAX_IMAGE_VARIATIONS: 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10;
5
+ export declare const MAX_IMAGE_VARIATIONS: 1 | 2 | 4 | 5 | 3 | 10 | 8 | 7 | 6 | 9;
6
6
  export type ImageQuantity = typeof IMAGE_QUANTITIES[number];
7
7
  export declare const clampImageQuantity: (count?: number) => ImageQuantity;
8
8
  /** Default saved-recipe framing, shared by composer restore, quote and dispatch. */
@@ -0,0 +1,23 @@
1
+ export interface GuideCatalogEntry {
2
+ name: string;
3
+ description: string;
4
+ tier: 'free' | 'paid';
5
+ }
6
+ /** Metadata is the catalog; headings supply technique vocabulary without returning whole guides. */
7
+ export declare function guideCatalog(skills: Readonly<Record<string, string>>): GuideCatalogEntry[];
8
+ export declare function discoverGuides(entries: readonly GuideCatalogEntry[], skills: Readonly<Record<string, string>>, query?: string, limit?: number, offset?: number): {
9
+ query: string | null;
10
+ fallback: boolean;
11
+ total: number;
12
+ offset: number;
13
+ nextOffset: number | null;
14
+ guides: {
15
+ matched: string[];
16
+ sections: string[];
17
+ name: string;
18
+ description: string;
19
+ tier: "free" | "paid";
20
+ }[];
21
+ rest: GuideCatalogEntry[];
22
+ };
23
+ //# sourceMappingURL=guide-discovery.d.ts.map
@@ -0,0 +1,39 @@
1
+ import { parseSkillMetadata } from '../skills/metadata.js';
2
+ import { guideSections } from './guide-retrieval.js';
3
+ import { searchTerms } from './search-terms.js';
4
+ // Brief filler that matches guides by accident ("30 second", "how much", "I need").
5
+ const GUIDE_FILLER = ['create', 'make', 'use', 'video', 'image', 'need', 'how', 'much', 'turn', 'keep', 'get', 'like', 'just', 'about', 'every', 'second', 'minute'];
6
+ const words = (text) => searchTerms(text, GUIDE_FILLER).filter(word => !/^\d{1,2}$/.test(word));
7
+ /** Metadata is the catalog; headings supply technique vocabulary without returning whole guides. */
8
+ export function guideCatalog(skills) {
9
+ return Object.keys(skills).sort().map(name => ({ ...parseSkillMetadata(skills[name], name), tier: 'free' }));
10
+ }
11
+ export function discoverGuides(entries, skills, query, limit, offset = 0) {
12
+ const terms = words(query ?? '');
13
+ const documents = entries.map(entry => {
14
+ const sections = skills[entry.name] ? guideSections(skills[entry.name]) : [];
15
+ const metadata = new Set(words(`${entry.name.replace(/-/g, ' ')} ${entry.description}`));
16
+ const headings = sections.map(section => ({ heading: section.title, terms: new Set(words(section.title)) }));
17
+ return { entry, metadata, headings };
18
+ });
19
+ // Rare terms in the corpus carry more weight than generic production vocabulary.
20
+ const frequency = new Map(terms.map(term => [term, documents.filter(doc => doc.metadata.has(term) || doc.headings.some(section => section.terms.has(term))).length]));
21
+ const ranked = documents.map(doc => {
22
+ const matched = terms.filter(term => doc.metadata.has(term) || doc.headings.some(section => section.terms.has(term)));
23
+ const score = matched.reduce((sum, term) => sum + Math.log(1 + documents.length / (1 + (frequency.get(term) ?? 0))) * (doc.metadata.has(term) ? 3 : 1), 0);
24
+ const sections = doc.headings.filter(section => matched.some(term => section.terms.has(term))).slice(0, 3).map(section => section.heading);
25
+ return { ...doc.entry, matched, sections, score };
26
+ }).filter(entry => !terms.length || entry.score > 0).sort((a, b) => b.score - a.score || a.name.localeCompare(b.name));
27
+ // An unfamiliar brief still exposes the catalog and its pagination; it never asks the user to route it.
28
+ const fallback = terms.length > 0 && ranked.length === 0;
29
+ const candidates = fallback ? documents.map(doc => ({ ...doc.entry, matched: [], sections: [], score: 0 })) : ranked;
30
+ // Browsing, or a brief with no keyword match, returns the whole catalog unless the caller pages it.
31
+ const size = limit ?? (terms.length && !fallback ? 8 : candidates.length);
32
+ const page = candidates.slice(offset, offset + size).map(({ score: _score, ...entry }) => entry);
33
+ // Keyword ranking only sees shared words, so the first page of a search also carries
34
+ // every other guide's description: the model chooses by the brief, as with native skills.
35
+ const shown = new Set(page.map(entry => entry.name));
36
+ const rest = terms.length && !fallback && offset === 0 ? entries.filter(entry => !shown.has(entry.name)) : [];
37
+ return { query: query ?? null, fallback, total: candidates.length, offset, nextOffset: offset + page.length < candidates.length ? offset + page.length : null, guides: page, rest };
38
+ }
39
+ //# sourceMappingURL=guide-discovery.js.map
@@ -1,7 +1,7 @@
1
1
  import { craftCard } from './craft-cards.js';
2
2
  /** Parse headings outside code fences; maintainer comments never reach agents. */
3
3
  export function guideSections(content) {
4
- const clean = content.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n/, '').replace(/<!--[\s\S]*?-->/g, '').trim();
4
+ const clean = content.replace(/<!--[\s\S]*?-->/g, '').trimStart().replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n/, '').trim();
5
5
  const sections = [];
6
6
  let current = { title: 'Overview', body: '' };
7
7
  let fenced = false;
@@ -35,9 +35,9 @@ export interface DurationCapability {
35
35
  mode: 'continuous' | 'discrete';
36
36
  /** For discrete mode: the exact allowed durations. */
37
37
  values?: number[];
38
- /** Resolution-dependent narrowing (Veo forces 8s at 1080p AND 4k). */
38
+ /** Resolution-dependent narrowing (retired Veo forced 8s at 1080p AND 4k). */
39
39
  resolutionOverrides?: Record<string, Pick<DurationCapability, 'min' | 'max' | 'mode' | 'values'>>;
40
- /** Prompt-mode narrowing (Veo's reference-to-video endpoint is 8s only). */
40
+ /** Prompt-mode narrowing (retired Veo's reference-to-video endpoint was 8s only). */
41
41
  modeOverrides?: Record<string, Pick<DurationCapability, 'min' | 'max' | 'mode' | 'values'>>;
42
42
  }
43
43
  /** Video resolution constraints. */
@@ -188,15 +188,14 @@ export interface ModelCapability {
188
188
  voiceClone?: VoiceCloneCapability;
189
189
  }
190
190
  /**
191
- * The provider every AGENT generation actually lands on for Kling and Veo.
191
+ * The provider every AGENT generation actually lands on for Kling.
192
192
  *
193
193
  * 🚨 THIS IS WHY `providerAspectRatios` MATTERS TO THE OP. MCP/CLI/Studio-Agent
194
194
  * generations are credits-only (BYOK is retired on the agent surface), and the
195
- * credits route carries Kling and Veo on fal: `slate/src/main/agent/routes.ts`
196
- * never sends `klingProvider`, so `handlers/video.ts` defaults it to `'fal'`,
197
- * and `generateVeoVideo`'s proxy arm builds a fal request
198
- * (`buildFalVeoRequest`). So an agent gets Kling's THREE fal ratios and Veo's
199
- * TWO — not the eight and ten those models take on their direct APIs. Validating
195
+ * credits route carries Kling on fal: `slate/src/main/agent/routes.ts`
196
+ * never sends `klingProvider`, so `handlers/video.ts` defaults it to `'fal'`.
197
+ * So an agent gets Kling's THREE fal ratios, not the eight it takes on its
198
+ * direct API (retired Veo had the same split, two against ten). Validating
200
199
  * against the direct sets would accept a ratio fal rejects, which is the exact
201
200
  * failure this module exists to delete.
202
201
  */
@@ -257,7 +256,7 @@ export declare function checkDuration(model: string, duration: number | undefine
257
256
  export declare function describeAspectRatios(models: readonly string[], provider?: string): string;
258
257
  /** e.g. "seedance-2: 480p, 720p, 1080p, 4k (default 1080p) · omni-flash: 720p only (fixed)" */
259
258
  export declare function describeVideoResolutions(models: readonly string[]): string;
260
- /** e.g. "kling-v3.0-std: 3-15s · veo-3.1-fast: 4s/6s/8s (1080p/4k: 8s only; with reference images: 8s only)" */
259
+ /** e.g. "kling-v3.0-std: 3-15s · ltx-2-5: 6s/8s/10s/12s/14s/16s/18s/20s (1440p/4k: 6s/8s/10s only)" */
261
260
  export declare function describeDurations(models: readonly string[]): string;
262
261
  /** e.g. "seedance-2: 9 · seedance-2.5: 30 · omni-flash: 7 · seedance-2.5-edit: 0 (prompt + source clip only)" */
263
262
  export declare function describeReferenceImageCaps(models: readonly string[]): string;
@@ -67,8 +67,6 @@ const KLING_DIRECT_ASPECT_RATIOS = [
67
67
  ];
68
68
  /** Kling carried on fal: three. This is the set the CREDITS route uses. */
69
69
  const KLING_FAL_ASPECT_RATIOS = ['16:9', '9:16', '1:1'];
70
- /** Veo carried on fal: two. The credits route again — Veo direct takes all ten. */
71
- const VEO_FAL_ASPECT_RATIOS = ['16:9', '9:16'];
72
70
  /** Gemini Omni Flash (fal schema, 16:9 default): two. */
73
71
  const OMNI_FLASH_ASPECT_RATIOS = ['16:9', '9:16'];
74
72
  /** Seedance (both seats, and the edit row): six — notably NO `4:5`. */
@@ -91,7 +89,7 @@ const MINIMAX_H3_ASPECT_RATIOS = ['21:9', '16:9', '4:3', '1:1', '3:4', '9:16'];
91
89
  /**
92
90
  * LTX-2.5, all four integrated endpoints: TWO. Read off fal's live OpenAPI
93
91
  * 2026-08-29 — `text-to-video/{fast,pro}` declare exactly `['16:9','9:16']`,
94
- * the narrowest video set in the roster alongside Veo-on-fal.
92
+ * the narrowest video set in the roster.
95
93
  *
96
94
  * `image-to-video/{fast,pro}` additionally offer `auto` (follow the start
97
95
  * frame). We never send it and it is not an `AspectRatio` in this vocabulary —
@@ -201,15 +199,14 @@ export function falImageSize(model, aspectRatio, resolution) {
201
199
  return computeFalDimensions(ar, 4);
202
200
  }
203
201
  /**
204
- * The provider every AGENT generation actually lands on for Kling and Veo.
202
+ * The provider every AGENT generation actually lands on for Kling.
205
203
  *
206
204
  * 🚨 THIS IS WHY `providerAspectRatios` MATTERS TO THE OP. MCP/CLI/Studio-Agent
207
205
  * generations are credits-only (BYOK is retired on the agent surface), and the
208
- * credits route carries Kling and Veo on fal: `slate/src/main/agent/routes.ts`
209
- * never sends `klingProvider`, so `handlers/video.ts` defaults it to `'fal'`,
210
- * and `generateVeoVideo`'s proxy arm builds a fal request
211
- * (`buildFalVeoRequest`). So an agent gets Kling's THREE fal ratios and Veo's
212
- * TWO — not the eight and ten those models take on their direct APIs. Validating
206
+ * credits route carries Kling on fal: `slate/src/main/agent/routes.ts`
207
+ * never sends `klingProvider`, so `handlers/video.ts` defaults it to `'fal'`.
208
+ * So an agent gets Kling's THREE fal ratios, not the eight it takes on its
209
+ * direct API (retired Veo had the same split, two against ten). Validating
213
210
  * against the direct sets would accept a ratio fal rejects, which is the exact
214
211
  * failure this module exists to delete.
215
212
  */
@@ -366,43 +363,6 @@ export const MODEL_CAPABILITIES = {
366
363
  duration: { min: 3, max: 10, mode: 'continuous' },
367
364
  maxIngredientImages: 0,
368
365
  },
369
- // ── Veo ────────────────────────────────────────────────────────────────────
370
- 'veo-3.1-fast': {
371
- aspectRatios: FULL_ASPECT_RATIOS,
372
- providerAspectRatios: { fal: VEO_FAL_ASPECT_RATIOS },
373
- videoResolution: { options: ['720p', '1080p', '4k'] },
374
- duration: {
375
- min: 4, max: 8, mode: 'discrete',
376
- values: [4, 6, 8],
377
- // BOTH 1080p and 4k force 8s. The op said "4K only at 8s" and quoted 4s
378
- // at 1080p, which the provider rejects.
379
- resolutionOverrides: {
380
- '1080p': { min: 8, max: 8, mode: 'discrete', values: [8] },
381
- '4k': { min: 8, max: 8, mode: 'discrete', values: [8] },
382
- },
383
- modeOverrides: {
384
- ingredients: { min: 8, max: 8, mode: 'discrete', values: [8] },
385
- },
386
- },
387
- maxIngredientImages: 3,
388
- },
389
- 'veo-3.1-standard': {
390
- aspectRatios: FULL_ASPECT_RATIOS,
391
- providerAspectRatios: { fal: VEO_FAL_ASPECT_RATIOS },
392
- videoResolution: { options: ['720p', '1080p', '4k'] },
393
- duration: {
394
- min: 4, max: 8, mode: 'discrete',
395
- values: [4, 6, 8],
396
- resolutionOverrides: {
397
- '1080p': { min: 8, max: 8, mode: 'discrete', values: [8] },
398
- '4k': { min: 8, max: 8, mode: 'discrete', values: [8] },
399
- },
400
- modeOverrides: {
401
- ingredients: { min: 8, max: 8, mode: 'discrete', values: [8] },
402
- },
403
- },
404
- maxIngredientImages: 3,
405
- },
406
366
  // ── Seedance ───────────────────────────────────────────────────────────────
407
367
  'seedance-2': {
408
368
  aspectRatios: SEEDANCE_ASPECT_RATIOS,
@@ -933,7 +893,7 @@ export function describeVideoResolutions(models) {
933
893
  function fmtWindow(d) {
934
894
  return d.mode === 'discrete' && d.values ? `${d.values.join('s/')}s` : `${d.min}-${d.max}s`;
935
895
  }
936
- /** e.g. "kling-v3.0-std: 3-15s · veo-3.1-fast: 4s/6s/8s (1080p/4k: 8s only; with reference images: 8s only)" */
896
+ /** e.g. "kling-v3.0-std: 3-15s · ltx-2-5: 6s/8s/10s/12s/14s/16s/18s/20s (1440p/4k: 6s/8s/10s only)" */
937
897
  export function describeDurations(models) {
938
898
  return groupBy(models, (m) => {
939
899
  const d = MODEL_CAPABILITIES[m]?.duration;
@@ -963,10 +923,10 @@ export function describeReferenceImageCaps(models) {
963
923
  const cap = MODEL_CAPABILITIES[m];
964
924
  if (!cap)
965
925
  return '';
966
- const n = cap.maxIngredientImages ?? cap.maxRefImages;
967
- if (n == null)
968
- return '';
969
- return n === 0 ? '0 (prompt + source clip only)' : String(n);
926
+ // A generate row with no reference cap (LTX) takes none: say so rather than
927
+ // dropping the row, which read as "no limit stated".
928
+ const n = cap.maxIngredientImages ?? cap.maxRefImages ?? 0;
929
+ return n === 0 ? '0 (prompt and frames only)' : String(n);
970
930
  });
971
931
  }
972
932
  /** H3 Max reference accounting, fal's worked tables read 2026-09-09.
@@ -112,12 +112,12 @@ export declare function seedanceTaskIntentWords(prompt: string): string[];
112
112
  export declare function multimodalRefModels(): string[];
113
113
  export declare const MODEL_FACTS: ModelFact[];
114
114
  /**
115
- * The one `default` seat for a kind on the generate route, READ from `tier`.
115
+ * The one `default` seat for a kind and route, READ from `tier`.
116
116
  * Anything that needs "the default image model" calls this instead of naming
117
117
  * one: the op surface and slates-web both hand-typed nano-banana-2 for six days
118
118
  * after the app picker moved to Sunburst.
119
119
  */
120
- export declare function defaultModelFor(kind: ModelFact['kind']): string;
120
+ export declare function defaultModelFor(kind: ModelFact['kind'], route?: ModelFact['route']): string;
121
121
  /**
122
122
  * The seat a built-in tool renders on when its caller names no model. Resolves
123
123
  * `TOOL_SEAT` (generation-policy.ts, THE home): `'default-image'` follows the