@kolbo/mcp 1.44.0 → 1.46.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kolbo/mcp",
3
- "version": "1.44.0",
3
+ "version": "1.46.0",
4
4
  "description": "Kolbo AI MCP Server - Generate images, videos, music, speech, and sound effects from Claude Code",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -438,7 +438,7 @@ function renderActions(sc) {
438
438
  openPromptRow('Describe the motion (optional)…', function (text) {
439
439
  window.kolbo.sendMessage('Animate this image into a short video' +
440
440
  '\\nšŸŽ¬ Reference image: ' + currentUrl() +
441
- '\\nModel: smart select — pick the best image-to-video model' +
441
+ '\\nModel: pick a specific image-to-video model that best fits this image (do NOT use Smart Select / auto)' +
442
442
  (text ? '\\nMotion prompt: ' + text : '\\nMotion prompt: subtle cinematic motion, slow push-in'));
443
443
  });
444
444
  });
package/src/index.js CHANGED
@@ -66,7 +66,6 @@ const { registerVisualDnaTools } = require('./tools/visual_dna');
66
66
  const { registerMoodboardTools } = require('./tools/moodboards');
67
67
  const { registerMediaTools } = require('./tools/media');
68
68
  const { registerPresetTools } = require('./tools/presets');
69
- const { registerAppBuilderTools } = require('./tools/app_builder');
70
69
  const { registerArtifactTools } = require('./tools/artifacts');
71
70
  const { registerProjectTools } = require('./tools/projects');
72
71
  const { registerAgentTools } = require('./tools/agents');
@@ -74,7 +73,6 @@ const { registerDocTools } = require('./tools/docs');
74
73
  const { registerVoiceTools } = require('./tools/voices');
75
74
  const { registerMusicLibraryTools } = require('./tools/music_library');
76
75
  const { registerStockLibraryTools } = require('./tools/stock_library');
77
- const { registerShortsCreatorTools } = require('./tools/shorts_creator');
78
76
  const { registerApps, attachToolWidgetMeta } = require('./apps');
79
77
 
80
78
  /**
@@ -113,13 +111,13 @@ function createServer(opts = {}) {
113
111
  'PROJECT CONTRACT (read this before generating anything):',
114
112
  'Everything in Kolbo lives inside a PROJECT — sessions, generations, and media are all project-scoped.',
115
113
  '1. When the user names a project ("in my Acme project", "for the summer campaign"), call `list_projects` ONCE to resolve the name to an id, then pass that id as `project_id` on EVERY subsequent generate_* / chat_send_message / upload_media call in the conversation. The target project is per-call, NOT sticky — any call that omits `project_id` silently lands in the default "API Generations" bucket (flagged is_default:true), which users experience as their work going to the wrong project.',
116
- '2. `list_projects` lists the user\'s platform projects (for generations/media/chat). `app_builder_list_projects` is a DIFFERENT tool that scopes App Builder coding sessions only — never use one where the other is meant (both return the same projects but via different endpoints, different shapes). The App Builder surface (project → session → deployed app with GitHub+Supabase+live URL → end-users who hit `/api/apps/:appId/ai/*`) is a SEPARATE world from the generation tools — `app_builder_generate_app` is NOT a media-generation call, and an App Builder `session_id` is NOT interchangeable with a chat/generation `session_id`. Read `skill/references/workflows/app-builder.md` before the first App Builder turn; App Builder is preview-only, do not proactively advertise it.',
114
+ '2. `list_projects` lists the user\'s platform projects (for generations/media/chat). Do not confuse a generation `session_id` with any other session type — they are not interchangeable.',
117
115
  '3. Misplaced work is fixable: `move_media` / `bulk_move_media` / `move_folder_contents` move media items between projects; `move_session` moves a whole session (plus its media) to another project. If the user says a generation landed in the wrong project, move it rather than regenerating.',
118
116
  '4. If the user has not mentioned any project, omit `project_id` — the default bucket is correct in that case. Do not ask which project to use unless the user\'s intent is ambiguous.',
119
117
  '5. Written deliverables (plans, briefs, scripts, research summaries) can live in Kolbo too: author them as AI Docs with `create_doc` (project-scoped, editable in the app, shareable via `share_doc`).',
120
118
  '6. DIRECTOR / BATCH JOBS: generate_creative_director runs its scenes (image OR video) in parallel and only reports state="completed" once EVERY scene is terminal. Video batches can take many minutes. If the tool returns `_timed_out:true`, the batch is STILL RUNNING on the server — call `get_creative_director_status` with the returned generation_id and keep checking until state="completed" to collect all scene outputs. NEVER conclude a Director run failed and fall back to plain generate_image/generate_video without first checking status — doing so wastes the user\'s credits by paying twice. If scenes already carry image_urls/video_urls, they are done; do not regenerate.',
121
119
  '7. SESSION CONTINUITY: keep one workflow in ONE session. chat_send_message and the generation tools return a `session_id` — for follow-ups, refinements, retries, or additional steps on the SAME task/theme, pass that same `session_id` back on the next call instead of starting fresh. Only OMIT session_id (start a new session) when the user genuinely switches to an unrelated task. Do not open a new conversation/session for every message of the same workflow — it fragments the user\'s history and loses context.',
122
- '8. LOCAL FILES / CHAT ATTACHMENTS: remote MCP tools CANNOT read files the user attached to the chat. When a claude.ai (browser/mobile) user has a local image/video/audio/document to use as a generation input, IMMEDIATELY call `media_upload_widget` — an upload card appears in the chat, they upload, and stable Kolbo CDN URLs come back in a follow-up message. Never ask them to re-attach the file in chat and never invent a URL. On Claude Desktop/Code with filesystem access, use `upload_media` with the absolute local path instead.',
120
+ '8. LOCAL FILES / REFERENCE MEDIA — HOW TO HANDLE EVERY CASE: (A) User has a LOCAL file (audio, video, image, document) on their machine: if you have filesystem access (Claude Desktop / Code / IDE / any stdio MCP client) → call `upload_media` with the absolute local path OR pass the path directly to tools like `transcribe_audio` which accept local paths natively. If you have NO filesystem access (claude.ai browser/mobile) → call `media_upload_widget` IMMEDIATELY, an upload card appears, the user uploads, and a `media.kolbo.ai` CDN URL comes back — use that URL for any follow-up tool call. (B) You already have a public URL (media.kolbo.ai, any CDN, any direct link) → pass it directly to the tool. All Kolbo tools accept public URLs. NEVER search for DO Spaces keys, DigitalOcean credentials, or server-side upload credentials. NEVER ask the user to put the file on Google Drive, Dropbox, or Loom. NEVER invent or guess a URL. NEVER base64 a large file — use upload_media instead.',
123
121
  '9. MODEL SELECTION: ALWAYS pass a specific `model` on every generation tool — do NOT omit it. Omitting falls back to "Smart Select" auto-routing, which we deliberately avoid because it hides the model choice from the user and often picks a generic default. Choose the model that best fits the task and the user\'s intent (quality, speed, style, capability). If you are unsure which model to use for a given type, call `list_models` with the matching `type` and pick the recommended/flagship one, then pass its `identifier`. Only use Smart Select (omit `model`) if the user EXPLICITLY asks you to auto-pick.',
124
122
  '10. IMAGE EDITING: for ANY prompt-driven / content edit of an existing image — "make it night", changing scene/lighting/colors, adding/removing/replacing objects, restyling — use `generate_image_edit` (it runs on strong dedicated editing models, same as image generation). Do NOT use `edit_image` for content edits — `edit_image` is ONLY for mechanical enhancements (upscale, reframe, remove-background, skin retouch). Its `magic_edit` operation is deprecated in favor of `generate_image_edit`.'
125
123
  ].join('\n')
@@ -138,14 +136,12 @@ function createServer(opts = {}) {
138
136
  registerMoodboardTools(server, client, toolOptions);
139
137
  registerMediaTools(server, client, toolOptions);
140
138
  registerPresetTools(server, client, toolOptions);
141
- registerAppBuilderTools(server, client, toolOptions);
142
139
  registerArtifactTools(server, client, toolOptions);
143
140
  registerProjectTools(server, client, toolOptions);
144
141
  registerAgentTools(server, client, toolOptions);
145
142
  registerDocTools(server, client, toolOptions);
146
143
  registerMusicLibraryTools(server, client, toolOptions);
147
144
  registerStockLibraryTools(server, client, toolOptions);
148
- registerShortsCreatorTools(server, client, toolOptions);
149
145
 
150
146
  // MCP Apps widget resources (ui://kolbo/*). Registering resources is inert
151
147
  // for text-only hosts — they never fetch them.
package/src/tools/chat.js CHANGED
@@ -14,7 +14,7 @@ function registerChatTools(server, client) {
14
14
  'Send a chat message to Kolbo AI. Starts a new conversation (omit session_id) or continues an existing one. Returns the assistant response when complete. Supports image/video/audio analysis via media_urls — pass public URLs and the model auto-routes to a vision-capable model (e.g. Gemini) when media is detected. Supports web search and deep think modes.',
15
15
  {
16
16
  message: z.string().describe('The user message to send'),
17
- model: z.string().optional().describe('Model identifier (e.g. "gpt-4o", "claude-sonnet-4-6"). Omit for Smart Select (auto) — recommended default. When media_urls contains video or audio, Smart Select automatically routes to a Gemini vision model regardless of this field.'),
17
+ model: z.string().optional().describe('Model identifier (e.g. "gpt-4o", "claude-sonnet-4-6"). Prefer passing a SPECIFIC model (list_models type="text") — omitting falls back to Smart Select auto-routing, which we avoid unless the user explicitly asks for auto-pick. Exception: when media_urls contains video or audio, omitting is fine — routing goes to a Gemini vision model regardless of this field.'),
18
18
  session_id: z.string().optional().describe('Existing chat session ID to continue. Omit to start a new conversation.'),
19
19
  system_prompt: z.string().optional().describe('System prompt for the conversation. Only applied when creating a new session.'),
20
20
  web_search: z.boolean().optional().describe('Enable web search for this message. Default: false'),
@@ -61,15 +61,16 @@ function registerGenerateTools(server, client, options = {}) {
61
61
  moodboard_id: z.string().optional().describe('Moodboard ID (from list_moodboards / get_moodboard) whose master_prompt and style_guide should be applied to this generation.'),
62
62
  enable_web_search: z.boolean().optional().describe('Enable web-search grounding for the prompt (useful for current events, brand references, real-world accuracy). Default: false'),
63
63
  resolution: z.string().optional().describe('Image resolution tier: "1K" (~1024px), "2K" (Full HD), "3K" (QHD), or "4K" (UHD). Model-dependent — call list_models and read supported_resolutions on the chosen model. Read resolution_multipliers on the same model to predict credit cost. Omit to use the model default.'),
64
+ quality: z.string().optional().describe('Quality tier for models that support it (e.g. "low", "medium", "high", "auto"). Check list_models → supported_qualities on the chosen model. "auto" is normalised to "medium" on gpt-image-2. Omit to use the model default.'),
64
65
  preset_id: z.string().optional().describe('Preset ID from list_presets type="image" to apply a saved style preset to this generation.'),
65
66
  cinematic: CINEMATIC_SCHEMA,
66
67
  project_id: projectIdField
67
68
  },
68
- async ({ prompt, model, aspect_ratio, enhance_prompt, num_images, reference_images, visual_dna_ids, moodboard_id, enable_web_search, resolution, preset_id, cinematic, project_id }) => {
69
+ async ({ prompt, model, aspect_ratio, enhance_prompt, num_images, reference_images, visual_dna_ids, moodboard_id, enable_web_search, resolution, quality, preset_id, cinematic, project_id }) => {
69
70
  model = await canonicalModelId(client, model); // lenient id resolution ("z-image" → "z-image/turbo")
70
71
  const gen = await client.post('/v1/generate/image', {
71
72
  prompt, model, aspect_ratio, enhance_prompt, num_images,
72
- reference_images, visual_dna_ids, moodboard_id, enable_web_search, resolution, preset_id, cinematic, project_id
73
+ reference_images, visual_dna_ids, moodboard_id, enable_web_search, resolution, quality, preset_id, cinematic, project_id
73
74
  });
74
75
 
75
76
  if (ui()) return uiGenerating({
@@ -1157,38 +1158,109 @@ function registerGenerateTools(server, client, options = {}) {
1157
1158
  // ─── edit_image ────────────────────────────────────────────
1158
1159
  server.tool(
1159
1160
  'edit_image',
1160
- 'Apply a MECHANICAL enhancement to an existing image: upscale resolution, reframe (change aspect ratio), remove background, or portrait skin retouching. āš ļø For any PROMPT-DRIVEN / CONTENT edit — changing the scene, lighting, or time of day, adding/removing/replacing objects, restyling, recoloring — use `generate_image_edit` instead (stronger dedicated editing models, better results). Do NOT use this tool for "make it night / add sunglasses / change the background to X"-type edits. Returns the edited image URL when complete.',
1161
+ 'Apply a targeted AI edit to an existing image. Covers mechanical enhancements (upscale, reframe, remove background, skin retouching) AND creative operations (inpaint, erase, face swap, background replace, camera angle, zoom out, multi-shot grid, split/upscale). āš ļø For open-ended PROMPT-DRIVEN content edits — "make it night", restyling, adding/removing objects — use `generate_image_edit` instead; it runs on stronger dedicated editing models and produces better results.',
1161
1162
  {
1162
- image_url: z.string().describe('URL of the source image to edit'),
1163
- operation: z.enum(['upscale', 'reframe', 'removebg', 'enhance_skin', 'magic_edit'])
1164
- .describe('Mechanical edit operation: "upscale" (increase resolution 2×–4Ɨ), "reframe" (change aspect ratio), "removebg" (remove background), "enhance_skin" (portrait retouching). NOTE: "magic_edit" (text-guided content edit) is DEPRECATED here — use `generate_image_edit` for prompt-driven edits instead; it produces better results on stronger models.'),
1165
- model: z.string().optional().describe('Model identifier override. Omit to use the default model for the operation.'),
1166
- scale: z.number().optional().describe('Upscale factor: 2, 3, or 4. Only used when operation="upscale". Default: 2.'),
1167
- aspect_ratio: z.string().optional().describe('Target aspect ratio (e.g., "16:9", "9:16", "1:1"). Required for operation="reframe".'),
1163
+ image_url: z.string().describe('URL of the primary source image to edit.'),
1164
+
1165
+ operation: z.enum([
1166
+ 'upscale', 'clarity_upscale',
1167
+ 'reframe', 'zoom_out',
1168
+ 'removebg', 'background_replace',
1169
+ 'enhance_skin',
1170
+ 'inpaint', 'erase',
1171
+ 'face_swap',
1172
+ 'camera_angle',
1173
+ 'split', 'split_upscale',
1174
+ 'multi_shot',
1175
+ 'magic_edit'
1176
+ ]).describe([
1177
+ 'Edit operation:',
1178
+ '"upscale" — increase resolution by 2Ɨ, 3Ɨ, or 4Ɨ (use `scale`). "clarity_upscale" — AI-powered clarity upscale with detail enhancement (use `resolution`).',
1179
+ '"reframe" — change aspect ratio (requires `aspect_ratio`, e.g. "16:9" or "9:16").',
1180
+ '"zoom_out" — expand the image outward, filling new areas with AI-generated content.',
1181
+ '"removebg" — remove the image background, output is transparent PNG.',
1182
+ '"background_replace" — remove background and replace it with AI-generated content from `prompt`.',
1183
+ '"enhance_skin" — portrait skin retouching (use `skin_strength`: "subtle" | "realistic" | "pimple" | "freckle").',
1184
+ '"inpaint" — paint over a masked area using `mask_image_url` (B&W mask, white = fill area) and optional `prompt`. Add reference images via `additional_images`.',
1185
+ '"erase" — erase an object defined by `mask_image_url` (white = erase area).',
1186
+ '"face_swap" — swap the face in `image_url` with the face from `mask_image_url` (required).',
1187
+ '"camera_angle" — generate the image from a different camera angle. Set `generate_all_angles=true` for a full set. Use `prompt` to guide the angle.',
1188
+ '"split" — split the image into a 3Ɨ3 grid of tiles. "split_upscale" — split into a grid and upscale each tile.',
1189
+ '"multi_shot" — generate a 3Ɨ3 multi-shot grid of scenes (uses `additional_images` as reference shots). Use `resolution` for output quality.',
1190
+ '"magic_edit" (DEPRECATED) — prompt-driven content edit. Prefer `generate_image_edit` for better results.',
1191
+ ].join(' ')),
1192
+
1193
+ model: z.string().optional()
1194
+ .describe('Model identifier override. Omit to use the platform default for the operation.'),
1195
+
1196
+ // ── upscale ────────────────────────────────────────────
1197
+ scale: z.number().optional()
1198
+ .describe('Upscale factor: 2, 3, or 4. Used with operation="upscale". Default: 2.'),
1199
+
1200
+ resolution: z.string().optional()
1201
+ .describe('Target output resolution (e.g. "4k", "2k", "1080p"). Used with "clarity_upscale", "split_upscale", "multi_shot".'),
1202
+
1203
+ // ── reframe ────────────────────────────────────────────
1204
+ aspect_ratio: z.string().optional()
1205
+ .describe('Target aspect ratio (e.g. "16:9", "9:16", "1:1", "4:3"). Required for operation="reframe".'),
1206
+
1207
+ // ── enhance_skin ───────────────────────────────────────
1168
1208
  skin_strength: z.enum(['subtle', 'realistic', 'pimple', 'freckle']).optional()
1169
- .describe('Skin enhancement style. Only used when operation="enhance_skin". Default: "realistic".'),
1170
- prompt: z.string().optional().describe('Text instruction — only for the deprecated operation="magic_edit". Prefer `generate_image_edit` for prompt-driven edits.'),
1209
+ .describe('Skin enhancement preset. Used with operation="enhance_skin". Default: "realistic".'),
1210
+
1211
+ // ── inpaint / erase / face_swap / background_replace / zoom_out / camera_angle / magic_edit ──
1212
+ prompt: z.string().optional()
1213
+ .describe('Text instruction guiding the edit. Required for "background_replace". Used with "inpaint", "zoom_out", "camera_angle", and the deprecated "magic_edit".'),
1214
+
1215
+ mask_image_url: z.string().optional()
1216
+ .describe('URL of a mask image (black & white; white = affected area). Required for "inpaint" and "erase". For "face_swap", this is the face reference image.'),
1217
+
1218
+ additional_images: z.array(z.string()).optional()
1219
+ .describe('Extra reference image URLs (up to 8). For "inpaint": reference images that guide style/content. For "multi_shot": the set of scene reference shots. For "magic_edit": additional source images for composite edits.'),
1220
+
1221
+ // ── camera_angle ───────────────────────────────────────
1222
+ generate_all_angles: z.boolean().optional()
1223
+ .describe('When true, generates a full set of camera angles instead of just one. Only used with operation="camera_angle".'),
1224
+
1225
+ // ── quality / prompt enhancement ───────────────────────
1226
+ quality: z.string().optional()
1227
+ .describe('Output quality preset (e.g. "high", "standard"). Applies where the underlying model supports quality tiers.'),
1228
+
1229
+ ai_optimize: z.boolean().optional()
1230
+ .describe('Whether to let Kolbo AI enhance your prompt before sending to the model. Default: true. Set false to use your prompt exactly as written.'),
1231
+
1171
1232
  project_id: projectIdField
1172
1233
  },
1173
- async ({ image_url, operation, model, scale, aspect_ratio, skin_strength, prompt, project_id }) => {
1174
- model = await canonicalModelId(client, model); // lenient id resolution ("z-image" → "z-image/turbo")
1175
- if (operation === 'magic_edit' && !prompt) throw new Error('prompt is required for magic_edit operation');
1176
- if (operation === 'reframe' && !aspect_ratio) throw new Error('aspect_ratio is required for reframe operation');
1234
+ async ({
1235
+ image_url, operation, model, scale, aspect_ratio, skin_strength, prompt,
1236
+ mask_image_url, additional_images, generate_all_angles, resolution, quality, ai_optimize,
1237
+ project_id
1238
+ }) => {
1239
+ model = await canonicalModelId(client, model);
1240
+
1241
+ // Basic validation
1242
+ if (operation === 'reframe' && !aspect_ratio) throw new Error('aspect_ratio is required for reframe');
1243
+ if (operation === 'background_replace' && !prompt) throw new Error('prompt is required for background_replace');
1244
+ if (operation === 'face_swap' && !mask_image_url && !(additional_images && additional_images.length > 0)) {
1245
+ throw new Error('mask_image_url (face reference) is required for face_swap');
1246
+ }
1177
1247
 
1178
1248
  const gen = await client.post('/v1/edit/image', {
1179
- image_url, operation, model, scale, aspect_ratio, skin_strength, prompt, project_id
1249
+ image_url, operation, model, scale, aspect_ratio, skin_strength, prompt,
1250
+ mask_image_url, additional_images, generate_all_angles, resolution, quality, ai_optimize,
1251
+ project_id
1180
1252
  });
1181
1253
 
1182
1254
  if (ui()) return uiGenerating({
1183
1255
  tool: 'edit_image', kind: 'image', gen, client, model,
1184
1256
  prompt: prompt || operation,
1185
- settings: { mode: operation, aspect_ratio },
1257
+ settings: { mode: operation, aspect_ratio, scale, resolution },
1186
1258
  reference_image: image_url
1187
1259
  });
1188
1260
 
1189
1261
  const result = await pollUntilDone(client, gen.generation_id, {
1190
1262
  interval: (gen.poll_interval_hint || 5) * 1000,
1191
- timeout: 180000
1263
+ timeout: 300000 // 5 min — split_upscale and multi_shot can take longer
1192
1264
  });
1193
1265
 
1194
1266
  return {
@@ -1208,39 +1280,139 @@ function registerGenerateTools(server, client, options = {}) {
1208
1280
  // ─── edit_video ────────────────────────────────────────────
1209
1281
  server.tool(
1210
1282
  'edit_video',
1211
- 'Apply a targeted AI edit to an existing video. Operations: upscale (4K resolution boost), reframe (change aspect ratio), generate_audio (add AI-generated sound/music from a prompt), remove_watermark, face_swap (replace faces using a reference image URL), extend (lengthen at start or end), magic_edit (restyle/transform with a prompt), lipsync (sync an audio track to a face in the video). Returns the edited video URL when complete.',
1283
+ 'Apply a targeted AI edit to an existing video. Covers both mechanical edits (upscale, reframe, remove watermark, remove background) and creative operations (generate audio, face swap, extend, lipsync, inpaint, retake, magic edit). Returns the edited video URL when complete.',
1212
1284
  {
1213
- video_url: z.string().describe('URL of the source video to edit'),
1214
- operation: z.enum(['upscale', 'reframe', 'generate_audio', 'remove_watermark', 'face_swap', 'extend', 'magic_edit', 'lipsync', 'remove_background'])
1215
- .describe('Edit operation: "upscale", "reframe" (requires aspect_ratio), "generate_audio" (requires prompt), "remove_watermark", "face_swap" (requires image_url), "extend" (requires duration), "magic_edit" (requires prompt), "lipsync" (requires audio_url), "remove_background" (removes/greenscreens the video background)'),
1216
- model: z.string().optional().describe('Model identifier override. Omit to use the default model for the operation.'),
1217
- aspect_ratio: z.string().optional().describe('Target aspect ratio (e.g., "16:9", "9:16"). Required for operation="reframe".'),
1218
- scale: z.number().optional().describe('Upscale factor. Only used when operation="upscale".'),
1219
- prompt: z.string().optional().describe('Text prompt. Required for operation="magic_edit" and "generate_audio". Optional hint for "extend".'),
1220
- image_url: z.string().optional().describe('URL of the reference face image. Required for operation="face_swap".'),
1221
- audio_url: z.string().optional().describe('URL of the audio track to sync. Required for operation="lipsync".'),
1222
- duration: z.number().optional().describe('Seconds of video to generate. Required for operation="extend". Typical range: 1–20.'),
1223
- mode: z.string().optional().describe('Where to extend: "start" or "end". Only used when operation="extend". Default: "end".'),
1285
+ video_url: z.string().describe('URL of the source video to edit.'),
1286
+
1287
+ operation: z.enum([
1288
+ 'upscale', 'reframe',
1289
+ 'generate_audio', 'remove_watermark',
1290
+ 'face_swap', 'extend', 'magic_edit',
1291
+ 'lipsync', 'remove_background',
1292
+ 'inpaint', 'retake'
1293
+ ]).describe([
1294
+ 'Edit operation:',
1295
+ '"upscale" — boost to 4K/2K resolution (use `scale` for factor, `resolution` for target, `target_fps` for frame rate).',
1296
+ '"reframe" — change aspect ratio (requires `aspect_ratio`; use `grid_position_x`/`grid_position_y` to control where the original sits).',
1297
+ '"generate_audio" — add AI-generated audio from `prompt`. Optionally split into `sound_effect_prompt` and `background_music_prompt`. Set `original_sound=true` to keep original audio alongside.',
1298
+ '"remove_watermark" — AI-powered watermark removal.',
1299
+ '"face_swap" — replace the face in the video with the face from `image_url` (required).',
1300
+ '"extend" — lengthen the video at `mode` ("start" or "end") by `duration` seconds. Optional `prompt` and `context` guide the generated content.',
1301
+ '"magic_edit" — restyle or transform the video with `prompt` (required).',
1302
+ '"lipsync" — sync a face to audio. Provide `audio_url` (audio file) OR `text_prompt` (will synthesize speech).',
1303
+ '"remove_background" — remove or greenscreen the video background. Use `refine_edges=true` for cleaner cutouts, `subject_is_person=true` for portrait-optimized mode.',
1304
+ '"inpaint" — replace a region of the video. Provide `mask_video_url` (B&W mask, white=fill area), `prompt` for what to generate, and optionally `object_prompt` (what to replace) and `video_strength` (0–1, how strongly to follow the original).',
1305
+ '"retake" — regenerate a segment of the video. Use `start_time` (seconds) + `duration` + optional `prompt`.',
1306
+ ].join(' ')),
1307
+
1308
+ model: z.string().optional()
1309
+ .describe('Model identifier override. Omit to use the platform default for the operation.'),
1310
+
1311
+ // ── upscale ────────────────────────────────────────────
1312
+ scale: z.number().optional()
1313
+ .describe('Upscale factor (e.g. 2, 4). Used with operation="upscale".'),
1314
+ resolution: z.string().optional()
1315
+ .describe('Target resolution (e.g. "4k", "2k", "1080p"). Used with "upscale" and "reframe".'),
1316
+ target_fps: z.number().optional()
1317
+ .describe('Target frame rate (e.g. 24, 30, 60). Used with operation="upscale".'),
1318
+
1319
+ // ── reframe ────────────────────────────────────────────
1320
+ aspect_ratio: z.string().optional()
1321
+ .describe('Target aspect ratio (e.g. "16:9", "9:16", "1:1"). Required for operation="reframe".'),
1322
+ grid_position_x: z.number().optional()
1323
+ .describe('Horizontal position (0.0–1.0) of the original content within the reframed canvas. Used with "reframe". Default: 0.5 (center).'),
1324
+ grid_position_y: z.number().optional()
1325
+ .describe('Vertical position (0.0–1.0) of the original content within the reframed canvas. Used with "reframe". Default: 0.5 (center).'),
1326
+
1327
+ // ── generate_audio ─────────────────────────────────────
1328
+ prompt: z.string().optional()
1329
+ .describe('Text prompt. Required for "magic_edit". Used with "generate_audio", "extend", "inpaint", "retake", and optionally "lipsync" (for text-to-speech instead of audio_url).'),
1330
+ sound_effect_prompt: z.string().optional()
1331
+ .describe('Separate prompt for sound effects layer. Used with operation="generate_audio" when you want to specify SFX and music independently.'),
1332
+ background_music_prompt: z.string().optional()
1333
+ .describe('Separate prompt for background music layer. Used with operation="generate_audio" alongside `sound_effect_prompt`.'),
1334
+ original_sound: z.boolean().optional()
1335
+ .describe('When true, keeps the original video audio and mixes in the generated audio. Used with "generate_audio". Default: false.'),
1336
+ cfg_strength: z.number().optional()
1337
+ .describe('Guidance strength for audio generation (higher = follows prompt more strictly). Used with "generate_audio".'),
1338
+
1339
+ // ── face_swap ──────────────────────────────────────────
1340
+ image_url: z.string().optional()
1341
+ .describe('URL of the reference face image. Required for operation="face_swap".'),
1342
+
1343
+ // ── extend ─────────────────────────────────────────────
1344
+ duration: z.number().optional()
1345
+ .describe('Seconds of content to generate. Used with "extend" (required) and "retake" (optional). Typical range: 1–20.'),
1346
+ mode: z.string().optional()
1347
+ .describe('Extension direction: "start" or "end". Used with "extend". For "retake": replacement mode (default: "replace_audio_and_video"). Default: "end".'),
1348
+ context: z.string().optional()
1349
+ .describe('Additional context to guide what gets generated in the extended segment. Used with "extend".'),
1350
+
1351
+ // ── lipsync ────────────────────────────────────────────
1352
+ audio_url: z.string().optional()
1353
+ .describe('URL of the audio track to sync to the face. Required for "lipsync" unless `text_prompt` is provided.'),
1354
+ text_prompt: z.string().optional()
1355
+ .describe('Text to synthesize as speech and sync to the face. Used with "lipsync" as an alternative to `audio_url`.'),
1356
+
1357
+ // ── remove_background ──────────────────────────────────
1358
+ refine_edges: z.boolean().optional()
1359
+ .describe('Apply edge refinement for cleaner background removal. Used with "remove_background". Default: false.'),
1360
+ subject_is_person: z.boolean().optional()
1361
+ .describe('Optimize background removal for a human subject (portrait mode). Used with "remove_background". Default: false.'),
1362
+
1363
+ // ── inpaint ────────────────────────────────────────────
1364
+ mask_video_url: z.string().optional()
1365
+ .describe('URL of a mask video (B&W; white = area to fill). Used with operation="inpaint".'),
1366
+ object_prompt: z.string().optional()
1367
+ .describe('Description of the object being replaced (helps the model understand what to remove). Used with "inpaint".'),
1368
+ video_strength: z.number().optional()
1369
+ .describe('How closely to follow the original video (0.0–1.0; higher = closer to original). Used with "inpaint".'),
1370
+
1371
+ // ── retake ─────────────────────────────────────────────
1372
+ start_time: z.number().optional()
1373
+ .describe('Start time in seconds of the segment to retake. Used with operation="retake".'),
1374
+
1224
1375
  project_id: projectIdField
1225
1376
  },
1226
- async ({ video_url, operation, model, aspect_ratio, scale, prompt, image_url, audio_url, duration, mode, project_id }) => {
1227
- model = await canonicalModelId(client, model); // lenient id resolution ("z-image" → "z-image/turbo")
1228
- if (operation === 'magic_edit' && !prompt) throw new Error('prompt is required for magic_edit');
1229
- if (operation === 'generate_audio' && !prompt) throw new Error('prompt is required for generate_audio');
1230
- if (operation === 'reframe' && !aspect_ratio) throw new Error('aspect_ratio is required for reframe');
1231
- if (operation === 'face_swap' && !image_url) throw new Error('image_url (reference face) is required for face_swap');
1232
- if (operation === 'lipsync' && !audio_url) throw new Error('audio_url is required for lipsync');
1233
- if (operation === 'extend' && !duration) throw new Error('duration is required for extend');
1377
+ async ({
1378
+ video_url, operation, model, aspect_ratio, scale, prompt,
1379
+ image_url, audio_url, duration, mode,
1380
+ target_fps, resolution,
1381
+ grid_position_x, grid_position_y,
1382
+ sound_effect_prompt, background_music_prompt, original_sound, cfg_strength,
1383
+ refine_edges, subject_is_person,
1384
+ text_prompt, context,
1385
+ mask_video_url, object_prompt, video_strength,
1386
+ start_time,
1387
+ project_id
1388
+ }) => {
1389
+ model = await canonicalModelId(client, model);
1390
+
1391
+ // Validation
1392
+ if (operation === 'magic_edit' && !prompt) throw new Error('prompt is required for magic_edit');
1393
+ if (operation === 'generate_audio'&& !prompt) throw new Error('prompt is required for generate_audio');
1394
+ if (operation === 'reframe' && !aspect_ratio)throw new Error('aspect_ratio is required for reframe');
1395
+ if (operation === 'face_swap' && !image_url) throw new Error('image_url (reference face) is required for face_swap');
1396
+ if (operation === 'lipsync' && !audio_url && !text_prompt) throw new Error('audio_url or text_prompt is required for lipsync');
1397
+ if (operation === 'extend' && !duration) throw new Error('duration is required for extend');
1234
1398
 
1235
1399
  const gen = await client.post('/v1/edit/video', {
1236
1400
  video_url, operation, model, aspect_ratio, scale, prompt,
1237
- image_url, audio_url, duration, mode, project_id
1401
+ image_url, audio_url, duration, mode,
1402
+ target_fps, resolution,
1403
+ grid_position_x, grid_position_y,
1404
+ sound_effect_prompt, background_music_prompt, original_sound, cfg_strength,
1405
+ refine_edges, subject_is_person,
1406
+ text_prompt, context,
1407
+ mask_video_url, object_prompt, video_strength,
1408
+ start_time,
1409
+ project_id
1238
1410
  });
1239
1411
 
1240
1412
  if (ui()) return uiGenerating({
1241
1413
  tool: 'edit_video', kind: 'video', gen, client, model,
1242
1414
  prompt: prompt || operation,
1243
- settings: { mode: operation, duration, aspect_ratio },
1415
+ settings: { mode: operation, duration, aspect_ratio, resolution },
1244
1416
  reference_image: image_url
1245
1417
  });
1246
1418
 
@@ -44,22 +44,16 @@ function modelChips(m) {
44
44
 
45
45
  // structuredContent for ui://kolbo/catalog.html — see src/apps/widgets/catalog.js
46
46
  // Deliberately CURATED, not exhaustive: the widget is a picker, not a database.
47
- // Smart Select is pinned first, each group shows the recommended/new models
48
- // (max 6), and the total count chip tells the user how many exist overall.
47
+ // Each group shows the recommended/new models (max 6), and the total count
48
+ // chip tells the user how many exist overall. Smart Select / "Auto" rows are
49
+ // deliberately EXCLUDED — we always want a specific model chosen (server
50
+ // instruction #9): auto-routing hides the model choice and the generation
51
+ // metadata used to read just "Auto".
49
52
  function buildCatalogStructured(models, type) {
50
53
  const groups = [];
51
54
  const byName = new Map();
52
55
  const isAuto = (m) => /^auto$|smart.select/i.test(String(m.name || '')) || /smart-select|k_auto/i.test(String(m.identifier || ''));
53
56
 
54
- // Pinned Smart Select entry (replaces the confusing "Other / Auto" row).
55
- const smartSelect = {
56
- name: 'Smart Select',
57
- icon: null,
58
- description: 'Recommended — automatically routes to the best model for your prompt, quality and cost.',
59
- chips: ['AUTO'],
60
- use_hint: 'Generate with Smart Select (omit the model) — ask me what I want to create first.',
61
- };
62
-
63
57
  // Recommended + new models float to the top of each group.
64
58
  const ranked = [...models].filter((m) => !isAuto(m)).sort((a, b) => {
65
59
  const score = (m) => (m.recommended ? 2 : 0) + (m.new_model || m.newModel ? 1 : 0);
@@ -84,7 +78,7 @@ function buildCatalogStructured(models, type) {
84
78
  widget: 'catalog',
85
79
  title: 'Kolbo AI Models' + (type ? ' — ' + type : ''),
86
80
  total_available: models.length,
87
- groups: [{ name: 'Recommended', models: [smartSelect] }, ...groups],
81
+ groups,
88
82
  };
89
83
  }
90
84