@kolbo/mcp 1.34.0 → 1.35.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/README.md +6 -0
- package/package.json +1 -1
- package/skill/GENERATED.md +1 -1
- package/skill/SKILL.md +17 -4
- package/skill/VERSION +1 -1
- package/skill/references/workflows/media-library.md +8 -6
- package/skill/references/workflows/visual-dna.md +11 -0
- package/src/tools/generate.js +40 -2
- package/src/tools/moodboards.js +51 -0
- package/src/tools/projects.js +139 -0
- package/src/tools/voices.js +53 -0
package/README.md
CHANGED
|
@@ -234,6 +234,12 @@ Every generation tool also accepts an optional `project_id` arg that routes the
|
|
|
234
234
|
| `move_session` | Move a session (generation, chat, transcription…) and ALL its media to another project |
|
|
235
235
|
| `create_doc` / `list_docs` / `get_doc` / `update_doc` / `share_doc` / `delete_doc` | AI Docs (Magic Pad): author project-scoped HTML documents, edit them, get public share links |
|
|
236
236
|
| `list_visual_dna_folders` / `create_visual_dna_folder` / `update_visual_dna_folder` / `delete_visual_dna_folder` / `move_visual_dna_to_folder` | Organize Visual DNA characters into user folders (create/rename/recolor/delete + move DNAs in/out) |
|
|
237
|
+
| `create_project` / `update_project` / `archive_project` / `unarchive_project` | Project lifecycle (create/rename/describe/archive; deletion stays in-app) |
|
|
238
|
+
| `list_sessions` | Enumerate sessions across all types, filterable by project and type |
|
|
239
|
+
| `add_project_context` / `list_project_context` / `delete_project_context` / `get_project_profile` / `regenerate_project_profile` | Project knowledge base (RAG): feed scripts/URLs/notes, read the synthesized living brief |
|
|
240
|
+
| `create_moodboard` / `update_moodboard` / `delete_moodboard` | Build/edit moodboards from image URLs (AI style analysis → master prompt) |
|
|
241
|
+
| `clone_voice` / `import_elevenlabs_voice` / `delete_voice` | Custom voices: clone from an audio sample, import by ElevenLabs ID, delete |
|
|
242
|
+
| `trim_video` | Frame-accurate server-side trim of a Kolbo-hosted video (async job, tool waits) |
|
|
237
243
|
| `check_credits` | Check credit balance |
|
|
238
244
|
| `get_generation_status` | Check one or many generations (`generation_ids`); `wait=true` blocks server-side until done — replaces client polling loops |
|
|
239
245
|
|
package/package.json
CHANGED
package/skill/GENERATED.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# AUTO-GENERATED — do not edit
|
|
2
2
|
|
|
3
3
|
This skill/ tree is mirrored from kolbo-code (the single source of truth)
|
|
4
|
-
by .github/workflows/sync-skill-to-plugin.yml — synced from kolbo-code@
|
|
4
|
+
by .github/workflows/sync-skill-to-plugin.yml — synced from kolbo-code@68ccb23.
|
|
5
5
|
|
|
6
6
|
It is the skill that 'npx @kolbo/mcp install' deploys into the user's agent.
|
|
7
7
|
To change it, edit packages/opencode/skills/kolbo/ in kolbo-code and push;
|
package/skill/SKILL.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
version: 0.
|
|
2
|
+
version: 0.6.1
|
|
3
3
|
name: kolbo
|
|
4
4
|
description: |
|
|
5
5
|
Generate, edit, or analyze creative media via the Kolbo AI MCP server:
|
|
@@ -7,13 +7,15 @@ description: |
|
|
|
7
7
|
music (Suno), TTS (ElevenLabs), 3D, transcription, Visual DNA (character
|
|
8
8
|
consistency), Marketing Studio (UGC + DTC ads + product photoshoot +
|
|
9
9
|
marketplace cards), Creative Director (multi-scene batches), HTML artifact
|
|
10
|
-
publishing (presentations, landing pages, dashboards),
|
|
10
|
+
publishing (presentations, landing pages, dashboards), AI Docs (project
|
|
11
|
+
documents you author and share), and the App Builder.
|
|
11
12
|
|
|
12
13
|
Use when the user wants to generate, create, make, edit, animate, or
|
|
13
14
|
transcribe media: images, video, music, voice/TTS, sound effects, 3D models,
|
|
14
15
|
UGC or TV-spot ads, product / lifestyle / hero shots, Amazon or marketplace
|
|
15
16
|
listings, presentations, landing pages, dashboards, or 'build me an app';
|
|
16
|
-
|
|
17
|
+
to reuse a character or brand (Visual DNA, brand kits); or to save a written
|
|
18
|
+
plan / brief / script / research doc into their Kolbo project (AI Docs).
|
|
17
19
|
|
|
18
20
|
NOT for: video editing / FFmpeg (use video-production), motion graphics
|
|
19
21
|
(use remotion-best-practices), code editing, or general chat.
|
|
@@ -111,9 +113,11 @@ Each `references/models/*.md` mirrors the matching skill prompt in `kolbo-api/sr
|
|
|
111
113
|
|------|---------|
|
|
112
114
|
| `list_models` / `list_voices` / `check_credits` / `get_generation_status` / `get_session_usage` | Discovery + status |
|
|
113
115
|
| `upload_media` / `list_media` / `get_media` / `get_media_stats` / `favorite_media` / `unfavorite_media` / `delete_media` / `restore_media` / `permanently_delete_media` / `move_media` / `bulk_*_media` / `*_media_folder` | Media library — see `workflows/media-library.md` |
|
|
114
|
-
| `create_visual_dna` / `list_visual_dnas` / `get_visual_dna` / `delete_visual_dna` | Visual DNA — see `workflows/visual-dna.md` |
|
|
116
|
+
| `create_visual_dna` / `list_visual_dnas` / `get_visual_dna` / `delete_visual_dna` / `*_visual_dna_folder` (5 folder tools) | Visual DNA + character folders — see `workflows/visual-dna.md` |
|
|
115
117
|
| `list_moodboards` / `get_moodboard` / `list_presets` | Style overlays |
|
|
116
118
|
| `search_stock_media` / `get_stock_sources` / `get_stock_categories` / `get_stock_collections` / `get_stock_asset` / `analyze_script_for_stock` / `import_stock_asset` | Stock library (free, no credits) — EXISTING photos / videos / 3D / SFX / music. For stock **music** use `search_stock_media` with `mediaType: "music"` (semantic vibe query, e.g. "uplifting corporate background") → `get_stock_asset` for downloads. The older `*_music_library` tools are deprecated adapters over this — prefer the stock tools. |
|
|
119
|
+
| `list_projects` / `move_session` | Projects: resolve a project NAME → the `project_id` you pass on generation/upload/doc calls; `move_session` relocates a whole session + its media when work landed in the wrong project. NOT the same as `app_builder_list_projects`. See "Projects — Where Work Lands" below. |
|
|
120
|
+
| `create_doc` / `list_docs` / `get_doc` / `update_doc` / `share_doc` / `delete_doc` | AI Docs (Magic Pad): YOU author full HTML documents (plans, briefs, scripts, research) saved into the user's project, editable in the Kolbo app. `share_doc` returns a public link. `update_doc` content replaces the WHOLE doc — `get_doc` first. |
|
|
117
121
|
| `chat_send_message` / `chat_list_conversations` / `chat_get_messages` | Kolbo chat with optional `media_urls` (up to 10 per call) |
|
|
118
122
|
| `app_builder_*` (9 tools) | Full React app generation — see `workflows/app-builder.md` |
|
|
119
123
|
| `publish_html_artifact` | Publish HTML / SVG / Mermaid to `sites.kolbo.ai`. Server dedupes by content hash. Strict CSP. |
|
|
@@ -160,6 +164,15 @@ A user-named tool — in any language — overrides every other rule. Recognized
|
|
|
160
164
|
|
|
161
165
|
Model types for `list_models`: `text_to_img`, `image_editing`, `text_to_video`, `img_to_video`, `draw_to_video`, `video_to_video`, `elements`, `firstlastgenerations`, `lipsync-image`, `lipsync-video`, `music_gen`, `text_to_speech`, `text_to_sound`, `stt`, `text`, `3d_text_to_model`, `3d_image_to_model`, `3d_multi_image_to_model`, `3d_world`.
|
|
162
166
|
|
|
167
|
+
## 📁 Projects — Where Work Lands (CRITICAL)
|
|
168
|
+
|
|
169
|
+
Everything in Kolbo — sessions, generations, media, docs — lives inside a PROJECT. Getting this wrong is the #1 user complaint ("my work went to the wrong project").
|
|
170
|
+
|
|
171
|
+
1. **User names a project** ("in my Acme project", "for the film") → call `list_projects` ONCE to resolve the name to an ObjectId, then pass that id as `project_id` on **EVERY** subsequent `generate_*` / `upload_media` / `create_doc` / `chat_send_message` call in the conversation. It is **per-call, NOT sticky** — any call that omits it silently lands in the default "API Generations" bucket (`is_default: true`).
|
|
172
|
+
2. **No project mentioned** → omit `project_id`; the default bucket is correct. Don't ask unless intent is ambiguous.
|
|
173
|
+
3. **`list_projects` ≠ `app_builder_list_projects`** — the latter scopes App Builder coding sessions only.
|
|
174
|
+
4. **Work landed in the wrong project? MOVE it, never regenerate**: `move_session` relocates a whole session + all its media (works for any session type — the `session_id` from generation responses, chats, transcriptions); `move_media` / `bulk_move_media` / `move_folder_contents` relocate individual media items.
|
|
175
|
+
|
|
163
176
|
## Cost Awareness — Quick Rules
|
|
164
177
|
|
|
165
178
|
Full tables + formulas in `references/workflows/cost-and-validation.md`. Quick rules:
|
package/skill/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.6.1
|
|
@@ -49,6 +49,7 @@ When you produce a media file LOCALLY — `ffmpeg` via the `video-production` sk
|
|
|
49
49
|
| "Restore it" / "undelete" / "bring it back from trash" | `restore_media` |
|
|
50
50
|
| "Permanently delete" / "wipe it forever" / "free up space" | **confirm with user** → `permanently_delete_media` |
|
|
51
51
|
| "Move this to project X" | `move_media` |
|
|
52
|
+
| "Move this whole session/chat to project X" / "this landed in the wrong project" | `move_session` (moves the session + ALL its media in one call — prefer over per-item `move_media`) |
|
|
52
53
|
| "Clean up old [type]" / "delete everything from [time period]" | `list_media` (find ids) → **confirm** → `bulk_delete_media` |
|
|
53
54
|
| "Restore all from trash" | `list_media include_deleted=true` → `bulk_restore_media` |
|
|
54
55
|
| "Empty my trash" / "purge deleted items" | `list_media include_deleted=true` → **show count, confirm** → `bulk_permanently_delete_media` |
|
|
@@ -68,9 +69,10 @@ When you produce a media file LOCALLY — `ffmpeg` via the `video-production` sk
|
|
|
68
69
|
1. **"Delete" is soft by default.** Use `delete_media` / `bulk_delete_media` for normal "delete" intent — items go to trash for 30 days and are recoverable. Only use `permanently_delete_media` / `bulk_permanently_delete_media` when the user explicitly asks for unrecoverable deletion ("permanently", "forever", "wipe", "free up space"). **Always confirm before either permanent variant.**
|
|
69
70
|
2. **Confirm before destructive folder ops.** `delete_media_folder` detaches items (they stay in the library) but the folder itself is gone — no undo. Confirm with the user.
|
|
70
71
|
3. **`bulk_move_media` is atomic.** If you get a "not all items owned by you" error, do NOT retry partially. Surface the error to the user and let them pick a smaller batch.
|
|
71
|
-
4.
|
|
72
|
-
5.
|
|
73
|
-
6.
|
|
74
|
-
7. **
|
|
75
|
-
8. **
|
|
76
|
-
9.
|
|
72
|
+
4. **`upload_media` accepts `project_id`** — when the user works in a named project, pass it on uploads too (resolve via `list_projects`), or the file lands outside the project.
|
|
73
|
+
5. **Prefer `list_media` filters over post-filtering.** Pass `project_id` / `folder_id` / `category` / `type` / `search` to the backend; don't fetch the whole library and filter client-side.
|
|
74
|
+
6. **`is_favorited` is per-user.** On shared projects, an item can be favorited by you and not by your teammates — the value reflects the calling user only.
|
|
75
|
+
7. **"Empty trash" flow:** `list_media` with `include_deleted=true` → show the count → confirm → `bulk_permanently_delete_media`. Never call the bulk-permanent endpoint without listing first so the user knows the scope.
|
|
76
|
+
8. **Bulk caps:** 1000 ids for `bulk_delete_media` / `bulk_restore_media` / `bulk_permanently_delete_media` / `bulk_move_media`; 500 ids for `add_media_to_folder` / `remove_media_from_folder`. Split larger jobs into successive calls.
|
|
77
|
+
9. **Folder share resolution:** `share_media_folder` takes emails; users not found come back in `not_found`. Report those to the user — don't assume the share succeeded silently. Members can list/add/remove items but cannot delete the folder or reshare it.
|
|
78
|
+
10. **`get_media` accepts a generation_id as a fallback** for the `media_id` arg, so you can chase down items the user references by their original generation rather than by library id.
|
|
@@ -231,3 +231,14 @@ Read `max_visual_dna` from `list_models` for the exact cap, AND `supports_visual
|
|
|
231
231
|
|
|
232
232
|
- **Animating an image** → `generate_video_from_image`; the source image IS the reference, don't add `visual_dna_ids`.
|
|
233
233
|
- **Video DNA support is limited to `generate_elements`** (Seedance 2, Kling O3 Reference, Grok Imagine). `generate_video`, `generate_video_from_image`, and `generate_first_last_frame` all ignore `visual_dna_ids` — for character-consistent video, route through `generate_elements`.
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
## Folders — organizing a large cast
|
|
237
|
+
|
|
238
|
+
Tools: `list_visual_dna_folders`, `create_visual_dna_folder` (`name`, optional hex `color`), `update_visual_dna_folder`, `delete_visual_dna_folder`, `move_visual_dna_to_folder`.
|
|
239
|
+
|
|
240
|
+
- Folders are user-scoped and flat; names are unique per user (409 on duplicate).
|
|
241
|
+
- **Personal DNAs only** — global presets must be imported first; organization DNAs cannot go in personal folders (server rejects with a clear message).
|
|
242
|
+
- **Deleting a folder never deletes DNAs** — contents move back to root (`items_moved_to_root` in the response). Mention this instead of asking for confirmation on non-empty folders.
|
|
243
|
+
- **Creating many characters for one production?** Create the folder FIRST, then `move_visual_dna_to_folder` each DNA right after `create_visual_dna` — don't leave a big cast unsorted at root.
|
|
244
|
+
- To list a folder's contents: `list_visual_dnas` and filter by each profile's `folder_id` (there is no server-side folder filter).
|
package/src/tools/generate.js
CHANGED
|
@@ -1035,8 +1035,8 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
1035
1035
|
'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.',
|
|
1036
1036
|
{
|
|
1037
1037
|
video_url: z.string().describe('URL of the source video to edit'),
|
|
1038
|
-
operation: z.enum(['upscale', 'reframe', 'generate_audio', 'remove_watermark', 'face_swap', 'extend', 'magic_edit', 'lipsync'])
|
|
1039
|
-
.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)'),
|
|
1038
|
+
operation: z.enum(['upscale', 'reframe', 'generate_audio', 'remove_watermark', 'face_swap', 'extend', 'magic_edit', 'lipsync', 'remove_background'])
|
|
1039
|
+
.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)'),
|
|
1040
1040
|
model: z.string().optional().describe('Model identifier override. Omit to use the default model for the operation.'),
|
|
1041
1041
|
aspect_ratio: z.string().optional().describe('Target aspect ratio (e.g., "16:9", "9:16"). Required for operation="reframe".'),
|
|
1042
1042
|
scale: z.number().optional().describe('Upscale factor. Only used when operation="upscale".'),
|
|
@@ -1088,6 +1088,44 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
1088
1088
|
};
|
|
1089
1089
|
}
|
|
1090
1090
|
);
|
|
1091
|
+
|
|
1092
|
+
// ─── trim_video ────────────────────────────────────────────
|
|
1093
|
+
server.tool(
|
|
1094
|
+
'trim_video',
|
|
1095
|
+
'Cut a section out of a video by start/end time (frame-accurate server-side trim, no credits for AI generation — pure processing). Source must be a Kolbo-hosted video URL (generated output or upload_media result). The tool waits for the job to finish (usually seconds) and returns the trimmed video URL.',
|
|
1096
|
+
{
|
|
1097
|
+
video_url: z.string().describe('Kolbo-hosted URL of the source video.'),
|
|
1098
|
+
start_time: z.number().describe('Trim start, in seconds (>= 0).'),
|
|
1099
|
+
end_time: z.number().describe('Trim end, in seconds (> start_time, max 3600).'),
|
|
1100
|
+
project_id: z.string().optional().describe('Project to file the trimmed video into (from list_projects).')
|
|
1101
|
+
},
|
|
1102
|
+
async ({ video_url, start_time, end_time, project_id }) => {
|
|
1103
|
+
const body = { video_url, start_time, end_time };
|
|
1104
|
+
if (project_id) body.project_id = project_id;
|
|
1105
|
+
const submit = await client.post('/v1/video/trim', body);
|
|
1106
|
+
const jobId = submit.jobId || submit.generationId;
|
|
1107
|
+
if (!jobId) return { content: [{ type: 'text', text: JSON.stringify(submit, null, 2) }] };
|
|
1108
|
+
// Poll the dedicated trim progress endpoint until done (~seconds).
|
|
1109
|
+
const deadline = Date.now() + 180000;
|
|
1110
|
+
let last = submit;
|
|
1111
|
+
while (Date.now() < deadline) {
|
|
1112
|
+
await new Promise(r => setTimeout(r, 4000));
|
|
1113
|
+
last = await client.get(`/v1/video/trim/${encodeURIComponent(jobId)}`);
|
|
1114
|
+
if (last.status === 'completed' || last.url || last.videoUrl) break;
|
|
1115
|
+
if (last.status === 'failed') break;
|
|
1116
|
+
}
|
|
1117
|
+
return {
|
|
1118
|
+
content: [{
|
|
1119
|
+
type: 'text',
|
|
1120
|
+
text: JSON.stringify({
|
|
1121
|
+
job_id: jobId,
|
|
1122
|
+
status: last.status || 'processing',
|
|
1123
|
+
video_url: last.url || last.videoUrl || null
|
|
1124
|
+
}, null, 2)
|
|
1125
|
+
}]
|
|
1126
|
+
};
|
|
1127
|
+
}
|
|
1128
|
+
);
|
|
1091
1129
|
}
|
|
1092
1130
|
|
|
1093
1131
|
module.exports = { registerGenerateTools };
|
package/src/tools/moodboards.js
CHANGED
|
@@ -63,6 +63,57 @@ function registerMoodboardTools(server, client, options = {}) {
|
|
|
63
63
|
};
|
|
64
64
|
}
|
|
65
65
|
);
|
|
66
|
+
|
|
67
|
+
// ─── create_moodboard ──────────────────────────────────────
|
|
68
|
+
server.tool(
|
|
69
|
+
'create_moodboard',
|
|
70
|
+
'Create a moodboard from 1–15 image URLs. The server analyzes the images and synthesizes a reusable master style prompt — then pass the returned moodboard id as `moodboard_id` on generation tools to apply the style. Use Kolbo URLs (generated images or `upload_media` output) or any public image URL. Typical flow: generate/upload reference images → create_moodboard → generate with moodboard_id.',
|
|
71
|
+
{
|
|
72
|
+
name: z.string().describe('Moodboard name (1–100 chars).'),
|
|
73
|
+
image_urls: z.array(z.string()).min(1).max(15).describe('1–15 public image URLs. For local files, call upload_media first and use the returned URLs.'),
|
|
74
|
+
style_guide: z.string().optional().describe('Optional style notes (max 500 chars) that steer the analysis, e.g. "focus on the color grading, not the subjects".')
|
|
75
|
+
},
|
|
76
|
+
async ({ name, image_urls, style_guide }) => {
|
|
77
|
+
const body = {
|
|
78
|
+
name,
|
|
79
|
+
images: image_urls.map(u => ({ type: 'url', url: u })),
|
|
80
|
+
...(style_guide ? { style_guide } : {})
|
|
81
|
+
};
|
|
82
|
+
const result = await client.post('/v1/moodboards', body);
|
|
83
|
+
return { content: [{ type: 'text', text: JSON.stringify({ moodboard: result.moodboard, _hint: 'Pass this id as moodboard_id on generate_image / generate_creative_director to apply the style.' }, null, 2) }] };
|
|
84
|
+
}
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
// ─── update_moodboard ──────────────────────────────────────
|
|
88
|
+
server.tool(
|
|
89
|
+
'update_moodboard',
|
|
90
|
+
'Update a moodboard\'s name, style guide, and/or images. Providing `image_urls` REPLACES the whole image set and re-analyzes the style (master prompt regenerates). Owner only.',
|
|
91
|
+
{
|
|
92
|
+
moodboard_id: z.string().describe('Moodboard id (from list_moodboards).'),
|
|
93
|
+
name: z.string().optional().describe('New name.'),
|
|
94
|
+
style_guide: z.string().optional().describe('New style notes (empty string clears them).'),
|
|
95
|
+
image_urls: z.array(z.string()).min(1).max(15).optional().describe('Full replacement image set (1–15 URLs). Omit to keep current images.')
|
|
96
|
+
},
|
|
97
|
+
async ({ moodboard_id, name, style_guide, image_urls }) => {
|
|
98
|
+
const body = {};
|
|
99
|
+
if (name !== undefined) body.name = name;
|
|
100
|
+
if (style_guide !== undefined) body.style_guide = style_guide;
|
|
101
|
+
if (image_urls) body.images = image_urls.map(u => ({ type: 'url', url: u }));
|
|
102
|
+
const result = await client.put(`/v1/moodboards/${encodeURIComponent(moodboard_id)}`, body);
|
|
103
|
+
return { content: [{ type: 'text', text: JSON.stringify(result.moodboard, null, 2) }] };
|
|
104
|
+
}
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
// ─── delete_moodboard ──────────────────────────────────────
|
|
108
|
+
server.tool(
|
|
109
|
+
'delete_moodboard',
|
|
110
|
+
'Permanently delete a moodboard (owner only; system presets cannot be deleted). The underlying image files stay in storage — only the board is removed. Confirm with the user before deleting boards they did not just create.',
|
|
111
|
+
{ moodboard_id: z.string().describe('Moodboard id to delete.') },
|
|
112
|
+
async ({ moodboard_id }) => {
|
|
113
|
+
const result = await client.delete(`/v1/moodboards/${encodeURIComponent(moodboard_id)}`);
|
|
114
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
115
|
+
}
|
|
116
|
+
);
|
|
66
117
|
}
|
|
67
118
|
|
|
68
119
|
module.exports = { registerMoodboardTools };
|
package/src/tools/projects.js
CHANGED
|
@@ -56,6 +56,145 @@ function registerProjectTools(server, client) {
|
|
|
56
56
|
};
|
|
57
57
|
}
|
|
58
58
|
);
|
|
59
|
+
// ─── create_project ────────────────────────────────────────
|
|
60
|
+
server.tool(
|
|
61
|
+
'create_project',
|
|
62
|
+
'Create a new Kolbo project. Use when the user starts a new body of work ("new project for film X", "set up a workspace for the campaign"). After creating, pass the returned id as `project_id` on EVERY subsequent generation/upload/doc call for that work. Plan limits apply (server rejects when the plan\'s project cap is reached).',
|
|
63
|
+
{
|
|
64
|
+
name: z.string().describe('Project name.'),
|
|
65
|
+
description: z.string().optional().describe('Optional description (max 10k chars, markdown OK). Great place for the brief/logline — it also feeds the project\'s AI profile.')
|
|
66
|
+
},
|
|
67
|
+
async ({ name, description }) => {
|
|
68
|
+
const body = { name };
|
|
69
|
+
if (description) body.description = description;
|
|
70
|
+
const result = await client.post('/v1/projects', body);
|
|
71
|
+
return { content: [{ type: 'text', text: JSON.stringify({ project: result.project, _hint: 'Pass this id as project_id on every subsequent call for this work.' }, null, 2) }] };
|
|
72
|
+
}
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
// ─── update_project ────────────────────────────────────────
|
|
76
|
+
server.tool(
|
|
77
|
+
'update_project',
|
|
78
|
+
'Rename a project and/or update its description. Changing the description also refreshes the project\'s AI profile in the background.',
|
|
79
|
+
{
|
|
80
|
+
project_id: z.string().describe('Project ObjectId (from list_projects).'),
|
|
81
|
+
name: z.string().optional().describe('New name.'),
|
|
82
|
+
description: z.string().optional().describe('New description (replaces the old one).')
|
|
83
|
+
},
|
|
84
|
+
async ({ project_id, name, description }) => {
|
|
85
|
+
const body = {};
|
|
86
|
+
if (name !== undefined) body.name = name;
|
|
87
|
+
if (description !== undefined) body.description = description;
|
|
88
|
+
const result = await client.put(`/v1/projects/${encodeURIComponent(project_id)}`, body);
|
|
89
|
+
return { content: [{ type: 'text', text: JSON.stringify(result.project, null, 2) }] };
|
|
90
|
+
}
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
// ─── archive_project / unarchive_project ───────────────────
|
|
94
|
+
server.tool(
|
|
95
|
+
'archive_project',
|
|
96
|
+
'Archive a project — hides it from the default project list without deleting anything. Fully reversible with `unarchive_project`. (Permanent project DELETION is intentionally not available via the API — it cascades to all content and stays an in-app action.)',
|
|
97
|
+
{ project_id: z.string().describe('Project ObjectId to archive.') },
|
|
98
|
+
async ({ project_id }) => {
|
|
99
|
+
const result = await client.put(`/v1/projects/${encodeURIComponent(project_id)}/archive`, {});
|
|
100
|
+
return { content: [{ type: 'text', text: JSON.stringify(result.project, null, 2) }] };
|
|
101
|
+
}
|
|
102
|
+
);
|
|
103
|
+
server.tool(
|
|
104
|
+
'unarchive_project',
|
|
105
|
+
'Restore an archived project back to the active list.',
|
|
106
|
+
{ project_id: z.string().describe('Project ObjectId to unarchive.') },
|
|
107
|
+
async ({ project_id }) => {
|
|
108
|
+
const result = await client.put(`/v1/projects/${encodeURIComponent(project_id)}/unarchive`, {});
|
|
109
|
+
return { content: [{ type: 'text', text: JSON.stringify(result.project, null, 2) }] };
|
|
110
|
+
}
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
// ─── list_sessions ─────────────────────────────────────────
|
|
114
|
+
server.tool(
|
|
115
|
+
'list_sessions',
|
|
116
|
+
'List the user\'s sessions across ALL generation types (image, video, music, chat, transcription…), newest-activity first. Use to answer "what\'s in this project?", to find a session_id for `move_session`, or to locate past work. Filter by `project_id` and/or `type`.',
|
|
117
|
+
{
|
|
118
|
+
project_id: z.string().optional().describe('Restrict to one project (ObjectId from list_projects).'),
|
|
119
|
+
type: z.string().optional().describe('Restrict to one session type: image, video, video_from_image, music, speech, sound, image_edit, creative_director, chat, elements, first_last_frame, lipsync, video_from_video, transcription, global_image_edit, global_video_edit, shorts.'),
|
|
120
|
+
page: z.number().optional().describe('Page number, 1-indexed. Default: 1'),
|
|
121
|
+
limit: z.number().optional().describe('Results per page, max 50. Default: 20')
|
|
122
|
+
},
|
|
123
|
+
async ({ project_id, type, page, limit }) => {
|
|
124
|
+
const params = new URLSearchParams();
|
|
125
|
+
if (project_id) params.set('project_id', project_id);
|
|
126
|
+
if (type) params.set('type', type);
|
|
127
|
+
if (page) params.set('page', String(page));
|
|
128
|
+
if (limit) params.set('limit', String(limit));
|
|
129
|
+
const qs = params.toString();
|
|
130
|
+
const result = await client.get(`/v1/sessions${qs ? '?' + qs : ''}`);
|
|
131
|
+
return { content: [{ type: 'text', text: JSON.stringify({ sessions: result.sessions || [], pagination: result.pagination || null }, null, 2) }] };
|
|
132
|
+
}
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
// ─── Project context / knowledge base (NotebookLM-style) ───
|
|
136
|
+
server.tool(
|
|
137
|
+
'add_project_context',
|
|
138
|
+
'Feed knowledge into a project\'s AI knowledge base (RAG): a website URL or pasted text (script, brief, research, brand facts). The server analyzes it in the background (source returns status "analyzing" and settles on its own) and synthesizes everything into the project\'s living profile. Use when the user says "add this to the project", "here\'s the script", "the project should know about X". Provide exactly ONE of url / text.',
|
|
139
|
+
{
|
|
140
|
+
project_id: z.string().describe('Project ObjectId (from list_projects).'),
|
|
141
|
+
url: z.string().optional().describe('Website URL to fetch and analyze as a source.'),
|
|
142
|
+
text: z.string().optional().describe('Raw text to store as a source (script, notes, research). Kept verbatim for RAG; an AI summary is generated for display.'),
|
|
143
|
+
title: z.string().optional().describe('Optional title for a text source.')
|
|
144
|
+
},
|
|
145
|
+
async ({ project_id, url, text, title }) => {
|
|
146
|
+
if (!url && !text) throw new Error('Provide url or text');
|
|
147
|
+
const path = url
|
|
148
|
+
? `/v1/projects/${encodeURIComponent(project_id)}/context/url`
|
|
149
|
+
: `/v1/projects/${encodeURIComponent(project_id)}/context/text`;
|
|
150
|
+
const body = url ? { url } : { text, ...(title ? { title } : {}) };
|
|
151
|
+
const result = await client.post(path, body);
|
|
152
|
+
return { content: [{ type: 'text', text: JSON.stringify({ source: result.source, _hint: 'Analysis runs in the background — no need to poll; the project profile updates on its own.' }, null, 2) }] };
|
|
153
|
+
}
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
server.tool(
|
|
157
|
+
'list_project_context',
|
|
158
|
+
'List a project\'s knowledge-base sources (URLs, texts, files) with their AI summaries and analysis status.',
|
|
159
|
+
{ project_id: z.string().describe('Project ObjectId.') },
|
|
160
|
+
async ({ project_id }) => {
|
|
161
|
+
const result = await client.get(`/v1/projects/${encodeURIComponent(project_id)}/context`);
|
|
162
|
+
return { content: [{ type: 'text', text: JSON.stringify({ sources: result.sources || [], count: result.count || 0 }, null, 2) }] };
|
|
163
|
+
}
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
server.tool(
|
|
167
|
+
'delete_project_context',
|
|
168
|
+
'Remove one source from a project\'s knowledge base by its file_key (from list_project_context).',
|
|
169
|
+
{
|
|
170
|
+
project_id: z.string().describe('Project ObjectId.'),
|
|
171
|
+
file_key: z.string().describe('The source\'s file_key (URL-encode is handled for you).')
|
|
172
|
+
},
|
|
173
|
+
async ({ project_id, file_key }) => {
|
|
174
|
+
const result = await client.delete(`/v1/projects/${encodeURIComponent(project_id)}/context/${encodeURIComponent(file_key)}`);
|
|
175
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
176
|
+
}
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
server.tool(
|
|
180
|
+
'get_project_profile',
|
|
181
|
+
'Read a project\'s synthesized AI profile — the living markdown brief the platform maintains from the project\'s description, context sources, and activity. Use it to ground your work in what the project is about before generating.',
|
|
182
|
+
{ project_id: z.string().describe('Project ObjectId.') },
|
|
183
|
+
async ({ project_id }) => {
|
|
184
|
+
const result = await client.get(`/v1/projects/${encodeURIComponent(project_id)}/profile`);
|
|
185
|
+
return { content: [{ type: 'text', text: JSON.stringify(result.profile, null, 2) }] };
|
|
186
|
+
}
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
server.tool(
|
|
190
|
+
'regenerate_project_profile',
|
|
191
|
+
'Force-regenerate a project\'s AI profile from its current context sources (also clears any manual-edit lock). Use after adding several new sources when the user wants the brief refreshed now.',
|
|
192
|
+
{ project_id: z.string().describe('Project ObjectId.') },
|
|
193
|
+
async ({ project_id }) => {
|
|
194
|
+
const result = await client.post(`/v1/projects/${encodeURIComponent(project_id)}/profile/regenerate`, {});
|
|
195
|
+
return { content: [{ type: 'text', text: JSON.stringify(result.profile || result, null, 2) }] };
|
|
196
|
+
}
|
|
197
|
+
);
|
|
59
198
|
}
|
|
60
199
|
|
|
61
200
|
module.exports = { registerProjectTools };
|
package/src/tools/voices.js
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
* CONTRACT. Never rename, remove, or break an existing tool/arg. Full rules: ../index.js top-of-file. */
|
|
3
3
|
|
|
4
4
|
const { z } = require('zod');
|
|
5
|
+
const FormData = require('form-data');
|
|
6
|
+
const { resolveToBuffer } = require('./_shared');
|
|
5
7
|
const { UI, uiResult, appsEnabled } = require('../apps');
|
|
6
8
|
|
|
7
9
|
function registerVoiceTools(server, client, options = {}) {
|
|
@@ -60,6 +62,57 @@ function registerVoiceTools(server, client, options = {}) {
|
|
|
60
62
|
return { content: [{ type: 'text', text }] };
|
|
61
63
|
}
|
|
62
64
|
);
|
|
65
|
+
|
|
66
|
+
// ─── clone_voice ───────────────────────────────────────────
|
|
67
|
+
server.tool(
|
|
68
|
+
'clone_voice',
|
|
69
|
+
'Clone a custom TTS voice from an audio sample (the user\'s voice or any voice they own the rights to). Costs credits (provider-dependent) — confirm with the user before firing. Providers: "elevenlabs" (recommended, 2s–180s sample) or "deepdub" (2s–300s). After cloning, the voice appears in `list_voices` and can be used with generate_speech. Free plan caps at 3 custom voices.',
|
|
70
|
+
{
|
|
71
|
+
audio: z.string().describe('URL or absolute local path of the voice sample audio (any common format; converted server-side).'),
|
|
72
|
+
voice_name: z.string().describe('Name for the cloned voice.'),
|
|
73
|
+
provider: z.enum(['elevenlabs', 'deepdub']).optional().describe('Cloning provider. Default: elevenlabs (recommended).'),
|
|
74
|
+
language: z.string().optional().describe('Optional language hint (auto-detected when omitted).'),
|
|
75
|
+
project_id: z.string().optional().describe('Project to associate the voice with (from list_projects). Omit for the default project.')
|
|
76
|
+
},
|
|
77
|
+
async ({ audio, voice_name, provider, language, project_id }) => {
|
|
78
|
+
const resolved = await resolveToBuffer(audio, 'audio');
|
|
79
|
+
const form = new FormData();
|
|
80
|
+
form.append('audioFile', resolved.buffer, { filename: resolved.filename, contentType: resolved.contentType });
|
|
81
|
+
form.append('voiceName', voice_name);
|
|
82
|
+
form.append('provider', provider || 'elevenlabs');
|
|
83
|
+
if (language) form.append('language', language);
|
|
84
|
+
if (project_id) form.append('project_id', project_id);
|
|
85
|
+
const result = await client.postMultipart('/v1/voices/clone', form);
|
|
86
|
+
return { content: [{ type: 'text', text: JSON.stringify({ voice: result.voice || result, _hint: 'Use this voice with generate_speech (find it via list_voices).' }, null, 2) }] };
|
|
87
|
+
}
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
// ─── import_elevenlabs_voice ───────────────────────────────
|
|
91
|
+
server.tool(
|
|
92
|
+
'import_elevenlabs_voice',
|
|
93
|
+
'Import a voice from the ElevenLabs voice library into the user\'s Kolbo voices by its ElevenLabs voice ID. Use when the user already has/knows a specific ElevenLabs voice they want available for generate_speech.',
|
|
94
|
+
{
|
|
95
|
+
elevenlabs_voice_id: z.string().describe('The ElevenLabs voice ID to import.'),
|
|
96
|
+
project_id: z.string().optional().describe('Project to associate the voice with. Omit for the default project.')
|
|
97
|
+
},
|
|
98
|
+
async ({ elevenlabs_voice_id, project_id }) => {
|
|
99
|
+
const body = { elevenLabsVoiceId: elevenlabs_voice_id };
|
|
100
|
+
if (project_id) body.project_id = project_id;
|
|
101
|
+
const result = await client.post('/v1/voices/import-elevenlabs', body);
|
|
102
|
+
return { content: [{ type: 'text', text: JSON.stringify(result.voice || result, null, 2) }] };
|
|
103
|
+
}
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
// ─── delete_voice ──────────────────────────────────────────
|
|
107
|
+
server.tool(
|
|
108
|
+
'delete_voice',
|
|
109
|
+
'Delete one of the user\'s custom cloned voices (owner only, soft delete). Preset/platform voices cannot be deleted. Confirm with the user first.',
|
|
110
|
+
{ voice_id: z.string().describe('The custom voice id to delete (from list_voices — custom voices only).') },
|
|
111
|
+
async ({ voice_id }) => {
|
|
112
|
+
const result = await client.delete(`/v1/voices/${encodeURIComponent(voice_id)}`);
|
|
113
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
114
|
+
}
|
|
115
|
+
);
|
|
63
116
|
}
|
|
64
117
|
|
|
65
118
|
module.exports = { registerVoiceTools };
|