@kolbo/mcp 1.64.0 → 1.66.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 +1 -1
- package/skill/SKILL.md +2 -1
- package/skill/references/models/seedance.md +2 -0
- package/skill/references/workflows/marketing-studio.md +1 -1
- package/src/index.js +2 -0
- package/src/tools/generate.js +8 -2
- package/src/tools/review.js +338 -0
- package/src/tools/visual_dna.js +1 -1
package/package.json
CHANGED
package/skill/SKILL.md
CHANGED
|
@@ -99,7 +99,7 @@ Each `references/models/*.md` mirrors the matching skill prompt in `kolbo-api/sr
|
|
|
99
99
|
| `generate_video` | Text-to-video. Does **not** support Visual DNA — use `generate_elements` for character-consistent video. |
|
|
100
100
|
| `generate_video_from_image` | Animate a still. Prompt describes motion, not subject. |
|
|
101
101
|
| `generate_video_from_video` | Restyle/transform an existing video. Keeps original motion. |
|
|
102
|
-
| `generate_elements` | Reference-driven video. **Primary route for DNA → video.** |
|
|
102
|
+
| `generate_elements` | Reference-driven video. **Primary route for DNA → video.** Supports `multi_shots` / `multi_shot_count` (badge/metadata/native flag only — never rewrites the prompt) and short `session_name`. Crafted demos: `enhance_prompt:false` + put shot structure in the prompt. |
|
|
103
103
|
| `generate_first_last_frame` | Keyframe interpolation between two frames. |
|
|
104
104
|
| `generate_lipsync` | Lipsync audio to an image or video face. |
|
|
105
105
|
| `generate_music` | Music generation (Suno + variants). |
|
|
@@ -125,6 +125,7 @@ Each `references/models/*.md` mirrors the matching skill prompt in `kolbo-api/sr
|
|
|
125
125
|
| `clone_voice` / `import_elevenlabs_voice` / `delete_voice` | Custom voices (clone CHARGES CREDITS — confirm first; new voices show in `list_voices`) |
|
|
126
126
|
| `trim_video` | Frame-accurate trim of a Kolbo-hosted video (tool waits and returns the URL). `edit_video` also gained `remove_background`. |
|
|
127
127
|
| `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. |
|
|
128
|
+
| `create_review_asset` / `list_review_assets` / `get_review_asset` / `update_review_asset` / `add_review_version` / `set_review_status` / `delete_review_asset` / `get_review_storage_usage` / review collection + comment + share-link tools | **Kolbo Review** (Frame.io-style): upload first via `upload_media` / ticket / widget → pass `media_id` to `create_review_asset`. Comments are text + optional timecodes. `create_review_share_link` returns a guest URL. |
|
|
128
129
|
| `chat_send_message` / `chat_list_conversations` / `chat_get_messages` | Kolbo chat with optional `media_urls` (up to 10 per call) |
|
|
129
130
|
| `publish_html_artifact` | Publish HTML / SVG / Mermaid to `sites.kolbo.ai`. Server dedupes by content hash. Strict CSP. |
|
|
130
131
|
|
|
@@ -9,6 +9,8 @@ Load this file when the user wants a **Seedance 2 / Seedance 2.0** (ByteDance) v
|
|
|
9
9
|
|
|
10
10
|
**Kolbo MCP routing:** Seedance is a video model — call `generate_video` (text-to-video) or `generate_elements` (when video references / Visual DNA / first-last frames are involved). Run `list_models({ type: "text_to_video" })` and pick a Seedance variant by name.
|
|
11
11
|
|
|
12
|
+
**Multishot (Elements):** Seedance has **no** native provider Multishot switch. Pass `multi_shots: true` on `generate_elements` (optional `multi_shot_count`) for the UI Multishot badge only — it does **not** rewrite or reshape your prompt. **Always send `enhance_prompt: false` for crafted demos** (MCP default) so the prompt stays RAW. Put shot structure in the prompt yourself (`Multishot ON`, `Total: … / N shots`, numbered shots). Optional `session_name` sets a short sidebar title (skips AI auto-name from boilerplate).
|
|
13
|
+
|
|
12
14
|
## Universal Rules (apply to EVERY Seedance prompt)
|
|
13
15
|
|
|
14
16
|
- **First line ALWAYS declares shot structure**: total duration, shot count, aspect ratio. Example: `Total: 15s / 6 shots / 16:9`. Put it at the BOTTOM of the prompt too. For connected narrative sequences the proven phrasing is `N connected cinematic shots, 15 seconds total, 16:9, Multishot ON` — use it and keep `Multishot ON` for any multi-shot story.
|
|
@@ -123,7 +123,7 @@ If the user gives a **product URL** instead of a photo, see `workflows/research-
|
|
|
123
123
|
|
|
124
124
|
## Multi-Slot Board Method (structured shot specs + character consistency)
|
|
125
125
|
|
|
126
|
-
For any multi-shot UGC / review / how-to where the SAME presenter must stay identical across shots, compose the prompt as explicit **slots** and lock identity with a **board-first** pass.
|
|
126
|
+
For any multi-shot UGC / review / how-to where the SAME presenter must stay identical across shots, compose the prompt as explicit **slots** and lock identity with a **board-first** pass. Prefer `generate_elements` with `multi_shots: true` (and a short `session_name`) when animating a single multi-cut Seedance spot; board-first still uses `generate_image` + `generate_elements` / `generate_video_from_image` per slot when you need separate clips.
|
|
127
127
|
|
|
128
128
|
### 1. Structured input slots
|
|
129
129
|
|
package/src/index.js
CHANGED
|
@@ -72,6 +72,7 @@ const { registerArtifactTools } = require('./tools/artifacts');
|
|
|
72
72
|
const { registerProjectTools } = require('./tools/projects');
|
|
73
73
|
const { registerAgentTools } = require('./tools/agents');
|
|
74
74
|
const { registerDocTools } = require('./tools/docs');
|
|
75
|
+
const { registerReviewTools } = require('./tools/review');
|
|
75
76
|
const { registerVoiceTools } = require('./tools/voices');
|
|
76
77
|
const { registerMusicLibraryTools } = require('./tools/music_library');
|
|
77
78
|
const { registerStockLibraryTools } = require('./tools/stock_library');
|
|
@@ -156,6 +157,7 @@ function createServer(opts = {}) {
|
|
|
156
157
|
registerProjectTools(server, client, toolOptions);
|
|
157
158
|
registerAgentTools(server, client, toolOptions);
|
|
158
159
|
registerDocTools(server, client, toolOptions);
|
|
160
|
+
registerReviewTools(server, client, toolOptions);
|
|
159
161
|
registerMusicLibraryTools(server, client, toolOptions);
|
|
160
162
|
registerStockLibraryTools(server, client, toolOptions);
|
|
161
163
|
|
package/src/tools/generate.js
CHANGED
|
@@ -987,10 +987,13 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
987
987
|
image_url: z.string().describe('Public URL of the keyframe image'),
|
|
988
988
|
timestamp_seconds: z.number().describe('Moment on the OUTPUT timeline (seconds, 0 = first frame) where this image is pinned')
|
|
989
989
|
})).optional().describe('Timeline-pinned keyframes for multi-keyframe models (e.g. "flux-3-keyframes", "luma-ray-3-2-storyboard"): the model generates the motion BETWEEN the pinned images. Only models with `supports_keyframes: true` in list_models accept this; cap = `max_keyframes` (FLUX 3: 10). Requires an explicit `duration` — timestamps beyond it are clamped. Ignored by ordinary elements models. OPTIONAL for flux-3-keyframes: if omitted, pass the images via reference_images instead — they are played through IN ORDER, timed from timing language in the prompt or spaced evenly. Pass explicit keyframes only when you need exact control.'),
|
|
990
|
+
multi_shots: z.boolean().optional().describe('Enable Multishot metadata/UI badge (and native provider flag when the model supportsMultiShot). Never rewrites or reshapes the prompt — craft shot structure yourself (`Multishot ON`, Total/SHOT lines). MCP default enhance_prompt=false keeps the prompt raw; multi_shots alone is badge/flag only. For crafted demos: enhance_prompt:false + multi_shots:true.'),
|
|
991
|
+
multi_shot_count: z.number().optional().describe('Explicit shot count when multi_shots=true (e.g. 2). Clamped to ~2 shots/sec of duration.'),
|
|
992
|
+
session_name: z.string().optional().describe('Short sidebar session title (max ~60 chars), e.g. "UGC — Hana bathroom light". When set, skips AI auto-naming from the prompt boilerplate.'),
|
|
990
993
|
project_id: projectIdField,
|
|
991
994
|
session_id: sessionIdField
|
|
992
995
|
},
|
|
993
|
-
async ({ prompt, model, reference_images, reference_videos, reference_audio_urls, audio_url, files, duration, aspect_ratio, motion, preset_id, enhance_prompt = false, visual_dna_ids, resolution, keyframes, project_id, session_id }) => {
|
|
996
|
+
async ({ prompt, model, reference_images, reference_videos, reference_audio_urls, audio_url, files, duration, aspect_ratio, motion, preset_id, enhance_prompt = false, visual_dna_ids, resolution, keyframes, multi_shots, multi_shot_count, session_name, project_id, session_id }) => {
|
|
994
997
|
model = await canonicalModelId(client, model, 'elements'); // lenient id resolution ("z-image" → "z-image/turbo")
|
|
995
998
|
if (!prompt) throw new Error('prompt is required');
|
|
996
999
|
|
|
@@ -1013,6 +1016,9 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
1013
1016
|
if (audio_url) form.append('audio_url', audio_url);
|
|
1014
1017
|
if (resolution) form.append('resolution', resolution);
|
|
1015
1018
|
if (keyframes) form.append('keyframes', JSON.stringify(keyframes));
|
|
1019
|
+
if (multi_shots !== undefined) form.append('multi_shots', String(multi_shots));
|
|
1020
|
+
if (multi_shot_count !== undefined) form.append('multi_shot_count', String(multi_shot_count));
|
|
1021
|
+
if (session_name) form.append('session_name', session_name);
|
|
1016
1022
|
if (project_id) form.append('project_id', project_id);
|
|
1017
1023
|
if (session_id) form.append('session_id', session_id);
|
|
1018
1024
|
for (const f of resolved) {
|
|
@@ -1022,7 +1028,7 @@ function registerGenerateTools(server, client, options = {}) {
|
|
|
1022
1028
|
} else {
|
|
1023
1029
|
// URL-only mode: plain JSON.
|
|
1024
1030
|
startResponse = await client.post('/v1/generate/elements', {
|
|
1025
|
-
prompt, model, reference_images, reference_videos, reference_audio_urls, audio_url, duration, aspect_ratio, motion, preset_id, enhance_prompt, visual_dna_ids, resolution, keyframes, project_id, session_id
|
|
1031
|
+
prompt, model, reference_images, reference_videos, reference_audio_urls, audio_url, duration, aspect_ratio, motion, preset_id, enhance_prompt, visual_dna_ids, resolution, keyframes, multi_shots, multi_shot_count, session_name, project_id, session_id
|
|
1026
1032
|
});
|
|
1027
1033
|
}
|
|
1028
1034
|
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
/* ⛔ BACKWARD COMPATIBILITY: Tool names and arg names below are a PUBLIC
|
|
2
|
+
* CONTRACT. Never rename, remove, or break an existing tool/arg — old cached
|
|
3
|
+
* `npx @kolbo/mcp` installs in the wild will break silently. Add new tools or
|
|
4
|
+
* new OPTIONAL args only. Full rules: ../index.js top-of-file and CLAUDE.md. */
|
|
5
|
+
|
|
6
|
+
const { z } = require('zod');
|
|
7
|
+
const { projectIdField } = require('./_shared');
|
|
8
|
+
|
|
9
|
+
const REVIEW_STATUS = z.enum(['in_progress', 'needs_review', 'approved', 'changes_requested']);
|
|
10
|
+
|
|
11
|
+
function registerReviewTools(server, client) {
|
|
12
|
+
// ─── get_review_storage_usage ────────────────────────────────
|
|
13
|
+
server.tool(
|
|
14
|
+
'get_review_storage_usage',
|
|
15
|
+
'Get Kolbo Review storage usage for the API-key owner (5GB cap across all review versions).',
|
|
16
|
+
{},
|
|
17
|
+
async () => {
|
|
18
|
+
const result = await client.get('/v1/review/storage-usage');
|
|
19
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
20
|
+
}
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
// ─── list_review_assets ──────────────────────────────────────
|
|
24
|
+
server.tool(
|
|
25
|
+
'list_review_assets',
|
|
26
|
+
'List review assets in a project (Frame.io-style client review). Pass `project_id` from `list_projects`. Optional filters: `collection_id`, `status`, pagination.',
|
|
27
|
+
{
|
|
28
|
+
project_id: projectIdField,
|
|
29
|
+
collection_id: z.string().optional().describe('Filter to one review collection.'),
|
|
30
|
+
status: REVIEW_STATUS.optional(),
|
|
31
|
+
page: z.number().optional(),
|
|
32
|
+
limit: z.number().optional(),
|
|
33
|
+
},
|
|
34
|
+
async ({ project_id, collection_id, status, page, limit }) => {
|
|
35
|
+
const params = new URLSearchParams();
|
|
36
|
+
if (project_id) params.set('project_id', project_id);
|
|
37
|
+
if (collection_id) params.set('collection_id', collection_id);
|
|
38
|
+
if (status) params.set('status', status);
|
|
39
|
+
if (page) params.set('page', String(page));
|
|
40
|
+
if (limit) params.set('limit', String(limit));
|
|
41
|
+
const qs = params.toString();
|
|
42
|
+
const result = await client.get(`/v1/review/assets${qs ? '?' + qs : ''}`);
|
|
43
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
44
|
+
}
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
// ─── get_review_asset ────────────────────────────────────────
|
|
48
|
+
server.tool(
|
|
49
|
+
'get_review_asset',
|
|
50
|
+
'Fetch one review asset with all versions, status, and media URLs.',
|
|
51
|
+
{
|
|
52
|
+
asset_id: z.string().describe('Review asset ObjectId from list_review_assets or create_review_asset.'),
|
|
53
|
+
},
|
|
54
|
+
async ({ asset_id }) => {
|
|
55
|
+
const result = await client.get(`/v1/review/assets/${encodeURIComponent(asset_id)}`);
|
|
56
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
57
|
+
}
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
// ─── create_review_asset ─────────────────────────────────────
|
|
61
|
+
server.tool(
|
|
62
|
+
'create_review_asset',
|
|
63
|
+
'Create a Kolbo Review asset with v1 media attached. Upload first via `upload_media`, `create_upload_ticket`, or `media_upload_widget`, then pass the returned `media_id`. Requires `project_id` when the user named a project.',
|
|
64
|
+
{
|
|
65
|
+
name: z.string().describe('Display name for the review asset.'),
|
|
66
|
+
media_id: z.string().describe('MediaLibraryItem id from upload_media / list_media.'),
|
|
67
|
+
project_id: projectIdField,
|
|
68
|
+
collection_id: z.string().optional().describe('Optional review collection folder id.'),
|
|
69
|
+
version_note: z.string().optional().describe('Optional note on the first version (max 1000 chars).'),
|
|
70
|
+
},
|
|
71
|
+
async (args) => {
|
|
72
|
+
const result = await client.post('/v1/review/assets', args);
|
|
73
|
+
return {
|
|
74
|
+
content: [{
|
|
75
|
+
type: 'text',
|
|
76
|
+
text: JSON.stringify({
|
|
77
|
+
...result,
|
|
78
|
+
_hint: 'Asset created. Share with clients via create_review_share_link, or add feedback with create_review_comment.',
|
|
79
|
+
}, null, 2),
|
|
80
|
+
}],
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
// ─── update_review_asset ─────────────────────────────────────
|
|
86
|
+
server.tool(
|
|
87
|
+
'update_review_asset',
|
|
88
|
+
'Rename a review asset, move it to a collection, or switch the active version index.',
|
|
89
|
+
{
|
|
90
|
+
asset_id: z.string(),
|
|
91
|
+
name: z.string().optional(),
|
|
92
|
+
collection_id: z.string().nullable().optional().describe('Collection id, or null to uncollected.'),
|
|
93
|
+
current_version_index: z.number().optional(),
|
|
94
|
+
},
|
|
95
|
+
async ({ asset_id, ...body }) => {
|
|
96
|
+
const result = await client.patch(`/v1/review/assets/${encodeURIComponent(asset_id)}`, body);
|
|
97
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
98
|
+
}
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
// ─── add_review_version ──────────────────────────────────────
|
|
102
|
+
server.tool(
|
|
103
|
+
'add_review_version',
|
|
104
|
+
'Append a new version to an existing review asset from an uploaded `media_id`.',
|
|
105
|
+
{
|
|
106
|
+
asset_id: z.string(),
|
|
107
|
+
media_id: z.string(),
|
|
108
|
+
version_note: z.string().optional(),
|
|
109
|
+
},
|
|
110
|
+
async ({ asset_id, media_id, version_note }) => {
|
|
111
|
+
const result = await client.post(`/v1/review/assets/${encodeURIComponent(asset_id)}/versions`, {
|
|
112
|
+
media_id,
|
|
113
|
+
version_note,
|
|
114
|
+
});
|
|
115
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
116
|
+
}
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
// ─── set_review_status ───────────────────────────────────────
|
|
120
|
+
server.tool(
|
|
121
|
+
'set_review_status',
|
|
122
|
+
'Update review workflow status on an asset (in_progress, needs_review, approved, changes_requested).',
|
|
123
|
+
{
|
|
124
|
+
asset_id: z.string(),
|
|
125
|
+
review_status: REVIEW_STATUS,
|
|
126
|
+
},
|
|
127
|
+
async ({ asset_id, review_status }) => {
|
|
128
|
+
const result = await client.post(`/v1/review/assets/${encodeURIComponent(asset_id)}/status`, {
|
|
129
|
+
review_status,
|
|
130
|
+
});
|
|
131
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
132
|
+
}
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
// ─── delete_review_asset ─────────────────────────────────────
|
|
136
|
+
server.tool(
|
|
137
|
+
'delete_review_asset',
|
|
138
|
+
'Soft-delete a review asset and its underlying review media.',
|
|
139
|
+
{ asset_id: z.string() },
|
|
140
|
+
async ({ asset_id }) => {
|
|
141
|
+
const result = await client.delete(`/v1/review/assets/${encodeURIComponent(asset_id)}`);
|
|
142
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
143
|
+
}
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
// ─── Collections ─────────────────────────────────────────────
|
|
147
|
+
server.tool(
|
|
148
|
+
'list_review_collections',
|
|
149
|
+
'List review collection folders in a project.',
|
|
150
|
+
{ project_id: projectIdField },
|
|
151
|
+
async ({ project_id }) => {
|
|
152
|
+
const params = project_id ? `?project_id=${encodeURIComponent(project_id)}` : '';
|
|
153
|
+
const result = await client.get(`/v1/review/collections${params}`);
|
|
154
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
155
|
+
}
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
server.tool(
|
|
159
|
+
'create_review_collection',
|
|
160
|
+
'Create a review collection folder inside a project.',
|
|
161
|
+
{
|
|
162
|
+
name: z.string(),
|
|
163
|
+
project_id: projectIdField,
|
|
164
|
+
},
|
|
165
|
+
async (args) => {
|
|
166
|
+
const result = await client.post('/v1/review/collections', args);
|
|
167
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
168
|
+
}
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
server.tool(
|
|
172
|
+
'update_review_collection',
|
|
173
|
+
'Rename a review collection.',
|
|
174
|
+
{
|
|
175
|
+
collection_id: z.string(),
|
|
176
|
+
name: z.string(),
|
|
177
|
+
},
|
|
178
|
+
async ({ collection_id, name }) => {
|
|
179
|
+
const result = await client.patch(`/v1/review/collections/${encodeURIComponent(collection_id)}`, { name });
|
|
180
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
181
|
+
}
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
server.tool(
|
|
185
|
+
'delete_review_collection',
|
|
186
|
+
'Soft-delete a review collection (assets become uncollected).',
|
|
187
|
+
{ collection_id: z.string() },
|
|
188
|
+
async ({ collection_id }) => {
|
|
189
|
+
const result = await client.delete(`/v1/review/collections/${encodeURIComponent(collection_id)}`);
|
|
190
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
191
|
+
}
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
// ─── Comments ────────────────────────────────────────────────
|
|
195
|
+
server.tool(
|
|
196
|
+
'list_review_comments',
|
|
197
|
+
'List text comments on a review asset (default: current version media). Optional `version_media_id` for a specific version.',
|
|
198
|
+
{
|
|
199
|
+
asset_id: z.string(),
|
|
200
|
+
version_media_id: z.string().optional(),
|
|
201
|
+
},
|
|
202
|
+
async ({ asset_id, version_media_id }) => {
|
|
203
|
+
const params = version_media_id ? `?version_media_id=${encodeURIComponent(version_media_id)}` : '';
|
|
204
|
+
const result = await client.get(`/v1/review/assets/${encodeURIComponent(asset_id)}/comments${params}`);
|
|
205
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
206
|
+
}
|
|
207
|
+
);
|
|
208
|
+
|
|
209
|
+
server.tool(
|
|
210
|
+
'create_review_comment',
|
|
211
|
+
'Add a text comment on a review asset. Optional video timecodes: time_start / time_end (seconds).',
|
|
212
|
+
{
|
|
213
|
+
asset_id: z.string(),
|
|
214
|
+
body: z.string(),
|
|
215
|
+
time_start: z.number().optional(),
|
|
216
|
+
time_end: z.number().optional(),
|
|
217
|
+
version_media_id: z.string().optional(),
|
|
218
|
+
},
|
|
219
|
+
async ({ asset_id, ...body }) => {
|
|
220
|
+
const result = await client.post(`/v1/review/assets/${encodeURIComponent(asset_id)}/comments`, body);
|
|
221
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
222
|
+
}
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
server.tool(
|
|
226
|
+
'reply_review_comment',
|
|
227
|
+
'Reply to an existing review comment (one level of threading).',
|
|
228
|
+
{
|
|
229
|
+
note_id: z.string().describe('Comment id from list_review_comments.'),
|
|
230
|
+
body: z.string(),
|
|
231
|
+
},
|
|
232
|
+
async ({ note_id, body }) => {
|
|
233
|
+
const result = await client.post(`/v1/review/comments/${encodeURIComponent(note_id)}/reply`, { body });
|
|
234
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
235
|
+
}
|
|
236
|
+
);
|
|
237
|
+
|
|
238
|
+
server.tool(
|
|
239
|
+
'edit_review_comment',
|
|
240
|
+
'Edit your own review comment text and/or timecodes.',
|
|
241
|
+
{
|
|
242
|
+
note_id: z.string(),
|
|
243
|
+
body: z.string().optional(),
|
|
244
|
+
time_start: z.number().optional(),
|
|
245
|
+
time_end: z.number().optional(),
|
|
246
|
+
},
|
|
247
|
+
async ({ note_id, ...body }) => {
|
|
248
|
+
const result = await client.patch(`/v1/review/comments/${encodeURIComponent(note_id)}`, body);
|
|
249
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
250
|
+
}
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
server.tool(
|
|
254
|
+
'delete_review_comment',
|
|
255
|
+
'Delete a review comment (own comment, or any comment if you have full project access).',
|
|
256
|
+
{ note_id: z.string() },
|
|
257
|
+
async ({ note_id }) => {
|
|
258
|
+
const result = await client.delete(`/v1/review/comments/${encodeURIComponent(note_id)}`);
|
|
259
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
260
|
+
}
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
server.tool(
|
|
264
|
+
'resolve_review_comment',
|
|
265
|
+
'Mark a review comment thread as resolved.',
|
|
266
|
+
{ note_id: z.string() },
|
|
267
|
+
async ({ note_id }) => {
|
|
268
|
+
const result = await client.post(`/v1/review/comments/${encodeURIComponent(note_id)}/resolve`, {});
|
|
269
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
270
|
+
}
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
server.tool(
|
|
274
|
+
'unresolve_review_comment',
|
|
275
|
+
'Re-open a resolved review comment thread.',
|
|
276
|
+
{ note_id: z.string() },
|
|
277
|
+
async ({ note_id }) => {
|
|
278
|
+
const result = await client.post(`/v1/review/comments/${encodeURIComponent(note_id)}/unresolve`, {});
|
|
279
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
280
|
+
}
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
// ─── Share links ─────────────────────────────────────────────
|
|
284
|
+
server.tool(
|
|
285
|
+
'create_review_share_link',
|
|
286
|
+
'Create a guest review link for an asset or collection. Returns share_url for clients (no Kolbo account needed).',
|
|
287
|
+
{
|
|
288
|
+
target_type: z.enum(['asset', 'collection']),
|
|
289
|
+
target_id: z.string(),
|
|
290
|
+
role_label: z.string().optional().describe('Guest role label shown in the UI (e.g. Client).'),
|
|
291
|
+
require_email: z.boolean().optional(),
|
|
292
|
+
permissions: z.object({
|
|
293
|
+
canComment: z.boolean().optional(),
|
|
294
|
+
canDownload: z.boolean().optional(),
|
|
295
|
+
canViewOtherComments: z.boolean().optional(),
|
|
296
|
+
canResolveOwn: z.boolean().optional(),
|
|
297
|
+
canSwitchVersions: z.boolean().optional(),
|
|
298
|
+
canSetStatus: z.boolean().optional(),
|
|
299
|
+
}).optional(),
|
|
300
|
+
password: z.string().optional(),
|
|
301
|
+
allowed_emails: z.array(z.string()).optional(),
|
|
302
|
+
expires_at: z.string().optional().describe('ISO8601 expiry datetime.'),
|
|
303
|
+
},
|
|
304
|
+
async ({ target_type, target_id, ...body }) => {
|
|
305
|
+
const path = target_type === 'collection'
|
|
306
|
+
? `/v1/review/collections/${encodeURIComponent(target_id)}/share-links`
|
|
307
|
+
: `/v1/review/assets/${encodeURIComponent(target_id)}/share-links`;
|
|
308
|
+
const result = await client.post(path, body);
|
|
309
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
310
|
+
}
|
|
311
|
+
);
|
|
312
|
+
|
|
313
|
+
server.tool(
|
|
314
|
+
'list_review_share_links',
|
|
315
|
+
'List active share links for a review asset or collection.',
|
|
316
|
+
{
|
|
317
|
+
target_type: z.enum(['asset', 'collection']),
|
|
318
|
+
target_id: z.string(),
|
|
319
|
+
},
|
|
320
|
+
async ({ target_type, target_id }) => {
|
|
321
|
+
const params = new URLSearchParams({ target_type, target_id });
|
|
322
|
+
const result = await client.get(`/v1/review/share-links?${params}`);
|
|
323
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
324
|
+
}
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
server.tool(
|
|
328
|
+
'revoke_review_share_link',
|
|
329
|
+
'Revoke a guest review share link by link id.',
|
|
330
|
+
{ link_id: z.string() },
|
|
331
|
+
async ({ link_id }) => {
|
|
332
|
+
const result = await client.post(`/v1/review/share-links/${encodeURIComponent(link_id)}/revoke`, {});
|
|
333
|
+
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
334
|
+
}
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
module.exports = { registerReviewTools };
|
package/src/tools/visual_dna.js
CHANGED
|
@@ -197,7 +197,7 @@ function registerVisualDnaTools(server, client, options = {}) {
|
|
|
197
197
|
'Generate a reference sheet for a Visual DNA from 1+ reference image URLs — the same step the in-app Visual DNA wizard offers, for EVERY DNA type via `sheet_type`: character = multi-angle turnaround, product = angles + branding/material/construction close-ups, environment = location angles + one signature detail, style = a style board (the same look applied to six varied subjects). The sheet is the single strongest consistency booster for a DNA, and it always preserves the reference\'s original art style (2D stays 2D, photo stays photo). CHARGES CREDITS, so when the user is about to create a DNA, OFFER this first ("want me to generate a reference sheet for stronger consistency? it costs a few credits") and only run it on a yes. Returns `character_sheet_url` — pass it as `character_sheet_url` to `create_visual_dna` with the matching `dna_type`.',
|
|
198
198
|
{
|
|
199
199
|
image_urls: z.array(z.string()).min(1).describe('Reference image URLs of the subject (for characters: front/side/varied angles work best). Use generated-image URLs or upload_media output.'),
|
|
200
|
-
sheet_type: z.enum(['character', 'product', 'environment', 'style']).optional().describe('
|
|
200
|
+
sheet_type: z.enum(['character', 'character_headless', 'product', 'environment', 'style']).optional().describe('Sheet layout. character = front/back/face turnaround. character_headless = wardrobe/body refs with a headless front panel (use when clothing must change without fighting the face sheet). product / environment / style = matching DNA types. Defaults to character.')
|
|
201
201
|
},
|
|
202
202
|
async ({ image_urls, sheet_type }) => {
|
|
203
203
|
const result = await client.post('/v1/visual-dna/character-sheet', { image_urls, ...(sheet_type ? { sheet_type } : {}) });
|