@kolbo/mcp 1.31.2 → 1.31.4
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
package/src/apps/theme.js
CHANGED
|
@@ -159,6 +159,18 @@ body {
|
|
|
159
159
|
.k-media img, .k-media video { display: block; width: 100%; height: 100%; object-fit: cover; }
|
|
160
160
|
.k-media.selected { outline: 2px solid var(--brand); outline-offset: 1px; }
|
|
161
161
|
|
|
162
|
+
/* ---- Per-item hover download button (multi-image grids, CD scenes, viewer) ---- */
|
|
163
|
+
.k-dl {
|
|
164
|
+
position: absolute; top: 8px; right: 8px; z-index: 3;
|
|
165
|
+
width: 30px; height: 30px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.18);
|
|
166
|
+
background: rgba(0, 0, 0, 0.62); color: #fff; font-size: 14px; line-height: 1;
|
|
167
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
168
|
+
cursor: pointer; opacity: 0; transition: opacity 150ms var(--smooth), background 150ms var(--smooth);
|
|
169
|
+
}
|
|
170
|
+
.k-media:hover .k-dl, .k-viewer:hover .k-dl { opacity: 1; }
|
|
171
|
+
.k-dl:hover { background: var(--brand); border-color: var(--brand); }
|
|
172
|
+
.k-viewer { position: relative; }
|
|
173
|
+
|
|
162
174
|
/* Keep the whole completed card under the host's iframe height cap (~800px):
|
|
163
175
|
header + prompt + chips + viewer + thumbs + actions + footer must all fit,
|
|
164
176
|
or claude.ai adds an inner scrollbar. Click the image to expand in-Claude. */
|
|
@@ -171,7 +171,7 @@ function renderImages(sc, urls) {
|
|
|
171
171
|
selected = Math.min(selected, urls.length - 1);
|
|
172
172
|
// If the host CSP still blocks the image, degrade to open-in-browser rows
|
|
173
173
|
// instead of a broken empty viewer.
|
|
174
|
-
var viewer = '<div class="k-viewer"><img id="main-img" src="' + esc(urls[selected]) + '" alt="" onerror="window.__imgFail && window.__imgFail()"
|
|
174
|
+
var viewer = '<div class="k-viewer"><img id="main-img" src="' + esc(urls[selected]) + '" alt="" onerror="window.__imgFail && window.__imgFail()">' + dlBtnHTML(urls[selected]) + '</div>';
|
|
175
175
|
window.__imgFail = function () { renderLinks(urls); window.kolbo.notifySize(); };
|
|
176
176
|
// Click → expand into an in-Claude fullscreen viewer (all actions stay
|
|
177
177
|
// available); click again (or Exit) collapses back. Hosts that refuse
|
|
@@ -187,10 +187,14 @@ function renderImages(sc, urls) {
|
|
|
187
187
|
}).join('') + '</div>';
|
|
188
188
|
}
|
|
189
189
|
el('stage').innerHTML = viewer + thumbs;
|
|
190
|
+
wireDlButtons(el('stage'));
|
|
190
191
|
Array.prototype.forEach.call(el('stage').querySelectorAll('.k-thumb'), function (t) {
|
|
191
192
|
t.onclick = function () {
|
|
192
193
|
selected = +t.getAttribute('data-i');
|
|
193
194
|
el('main-img').src = state.urls[selected];
|
|
195
|
+
// Keep the viewer's hover download pointing at the newly selected image.
|
|
196
|
+
var dl = el('stage').querySelector('.k-viewer .k-dl');
|
|
197
|
+
if (dl) dl.setAttribute('data-dl', state.urls[selected]);
|
|
194
198
|
Array.prototype.forEach.call(el('stage').querySelectorAll('.k-thumb'), function (x) { x.classList.remove('active'); });
|
|
195
199
|
t.classList.add('active');
|
|
196
200
|
};
|
|
@@ -199,7 +203,9 @@ function renderImages(sc, urls) {
|
|
|
199
203
|
|
|
200
204
|
function renderVideo(sc, urls) {
|
|
201
205
|
el('stage').innerHTML = '<div class="k-viewer"><video id="main-video" src="' + esc(urls[0]) + '"' +
|
|
202
|
-
(sc.thumbnail_url ? ' poster="' + esc(sc.thumbnail_url) + '"' : '') + ' controls playsinline></video
|
|
206
|
+
(sc.thumbnail_url ? ' poster="' + esc(sc.thumbnail_url) + '"' : '') + ' controls playsinline></video>' +
|
|
207
|
+
dlBtnHTML(urls[0]) + '</div>';
|
|
208
|
+
wireDlButtons(el('stage'));
|
|
203
209
|
}
|
|
204
210
|
|
|
205
211
|
function renderAudio(sc, urls) {
|
|
@@ -238,20 +244,62 @@ function renderLinks(urls) {
|
|
|
238
244
|
});
|
|
239
245
|
}
|
|
240
246
|
|
|
247
|
+
// Small hover download button attached to a media cell (per-item downloads —
|
|
248
|
+
// batch grids and CD scenes have no single "current" url for the action row).
|
|
249
|
+
function dlBtnHTML(u) {
|
|
250
|
+
return '<button class="k-dl" data-dl="' + esc(u) + '" title="Download" aria-label="Download">⬇</button>';
|
|
251
|
+
}
|
|
252
|
+
function wireDlButtons(root) {
|
|
253
|
+
Array.prototype.forEach.call((root || document).querySelectorAll('.k-dl[data-dl]'), function (b) {
|
|
254
|
+
b.onclick = function (e) {
|
|
255
|
+
e.stopPropagation();
|
|
256
|
+
window.kolbo.openLink(downloadUrl(b.getAttribute('data-dl')));
|
|
257
|
+
};
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
241
261
|
function renderScenes(sc) {
|
|
242
262
|
el('stage').innerHTML = sc.scenes.map(function (scene) {
|
|
243
263
|
var media = (scene.video_urls || []).map(function (u) {
|
|
244
|
-
return '<div class="k-media"><video src="' + esc(u) + '" controls playsinline></video
|
|
264
|
+
return '<div class="k-media"><video src="' + esc(u) + '" controls playsinline></video>' + dlBtnHTML(u) + '</div>';
|
|
245
265
|
}).join('') + (scene.image_urls || []).map(function (u) {
|
|
246
|
-
return '<div class="k-media"><img src="' + esc(u) + '" alt=""
|
|
266
|
+
return '<div class="k-media" data-focus="' + esc(u) + '"><img src="' + esc(u) + '" alt="">' + dlBtnHTML(u) + '</div>';
|
|
247
267
|
}).join('');
|
|
248
268
|
return '<div style="margin-bottom:14px"><div style="font-size:12px;font-weight:600;color:var(--text-muted);margin-bottom:8px">Scene ' +
|
|
249
269
|
esc(scene.scene_number) + (scene.title ? ' — ' + esc(scene.title) : '') + '</div>' +
|
|
250
270
|
'<div class="k-gen-grid n2">' + media + '</div></div>';
|
|
251
271
|
}).join('');
|
|
272
|
+
wireDlButtons(el('stage'));
|
|
273
|
+
// Click a scene image → fullscreen focus on THAT image (videos keep their
|
|
274
|
+
// native controls; clicking them shouldn't hijack playback).
|
|
275
|
+
Array.prototype.forEach.call(el('stage').querySelectorAll('.k-media[data-focus]'), function (m) {
|
|
276
|
+
m.style.cursor = 'zoom-in';
|
|
277
|
+
m.onclick = function () { focusMedia(m.getAttribute('data-focus')); };
|
|
278
|
+
});
|
|
252
279
|
renderActions(sc);
|
|
253
280
|
}
|
|
254
281
|
|
|
282
|
+
// Fullscreen a single item out of a multi-item grid (Creative Director
|
|
283
|
+
// scenes). Exit restores the grid.
|
|
284
|
+
function focusMedia(url) {
|
|
285
|
+
window.kolbo.requestDisplayMode('fullscreen').then(function (res) {
|
|
286
|
+
if (!(res && res.mode === 'fullscreen')) return window.kolbo.openLink(url);
|
|
287
|
+
isFullscreen = true;
|
|
288
|
+
el('stage').innerHTML = '<div class="k-viewer"><img id="focus-img" src="' + esc(url) + '" alt="" style="cursor:zoom-out">' + dlBtnHTML(url) + '</div>';
|
|
289
|
+
wireDlButtons(el('stage'));
|
|
290
|
+
el('focus-img').onclick = exitFocus;
|
|
291
|
+
applyFullscreen(true, exitFocus);
|
|
292
|
+
window.kolbo.notifySize();
|
|
293
|
+
}).catch(function () { window.kolbo.openLink(url); });
|
|
294
|
+
}
|
|
295
|
+
function exitFocus() {
|
|
296
|
+
window.kolbo.requestDisplayMode('inline').catch(function () {});
|
|
297
|
+
isFullscreen = false;
|
|
298
|
+
applyFullscreen(false);
|
|
299
|
+
renderScenes(state); // restore the grid
|
|
300
|
+
window.kolbo.notifySize();
|
|
301
|
+
}
|
|
302
|
+
|
|
255
303
|
function renderError(msg) {
|
|
256
304
|
clearTimeout(pollTimer);
|
|
257
305
|
|
|
@@ -281,14 +329,14 @@ function toggleFullscreen() {
|
|
|
281
329
|
if (!isFullscreen) window.kolbo.openLink(state.urls && state.urls[selected]);
|
|
282
330
|
});
|
|
283
331
|
}
|
|
284
|
-
function applyFullscreen(on) {
|
|
332
|
+
function applyFullscreen(on, exitHandler) {
|
|
285
333
|
document.documentElement.classList.toggle('k-fullscreen', on);
|
|
286
334
|
var c = el('phase-chip');
|
|
287
335
|
if (on) {
|
|
288
336
|
c.style.display = '';
|
|
289
337
|
c.innerHTML = '✕ ' + esc('Exit');
|
|
290
338
|
c.style.cursor = 'pointer';
|
|
291
|
-
c.onclick = toggleFullscreen;
|
|
339
|
+
c.onclick = exitHandler || toggleFullscreen;
|
|
292
340
|
} else {
|
|
293
341
|
c.style.display = 'none';
|
|
294
342
|
c.onclick = null;
|
|
@@ -311,6 +359,7 @@ function currentUrl() {
|
|
|
311
359
|
|
|
312
360
|
function renderActions(sc) {
|
|
313
361
|
var a = [];
|
|
362
|
+
var hasSingleUrl = !!(sc.urls && sc.urls.length);
|
|
314
363
|
if (sc.kind === 'image') {
|
|
315
364
|
a.push('<button class="k-btn primary" id="btn-animate">🎬 Animate</button>');
|
|
316
365
|
a.push('<button class="k-btn" id="btn-edit">✏️ Edit</button>');
|
|
@@ -318,7 +367,9 @@ function renderActions(sc) {
|
|
|
318
367
|
if (sc.kind === 'video') {
|
|
319
368
|
a.push('<button class="k-btn primary" id="btn-download">⬇ Download</button>');
|
|
320
369
|
a.push('<button class="k-btn" id="btn-analyze">📊 Analyze</button>');
|
|
321
|
-
} else {
|
|
370
|
+
} else if (hasSingleUrl) {
|
|
371
|
+
// Scenes (Creative Director) have no single "current" url — per-item hover
|
|
372
|
+
// download buttons cover them instead.
|
|
322
373
|
a.push('<button class="k-btn" id="btn-download">⬇ Download</button>');
|
|
323
374
|
}
|
|
324
375
|
a.push('<button class="k-btn" id="btn-recreate">↻ Recreate</button>');
|
|
@@ -118,7 +118,12 @@ function useItem(i) {
|
|
|
118
118
|
|
|
119
119
|
window.kolbo.onToolResult(function (result) {
|
|
120
120
|
var sc = result.structuredContent || structured(result);
|
|
121
|
-
if (sc) boot(sc);
|
|
121
|
+
if (sc && sc.items) return boot(sc);
|
|
122
|
+
// No grid data (empty result set, error, or timeout path returned plain
|
|
123
|
+
// text) — collapse instead of showing a dead "Loading…" card forever.
|
|
124
|
+
var card = document.querySelector('.k-card');
|
|
125
|
+
if (card) card.style.display = 'none';
|
|
126
|
+
window.kolbo.notifySize();
|
|
122
127
|
});
|
|
123
128
|
`;
|
|
124
129
|
|
|
@@ -97,7 +97,23 @@ function poll() {
|
|
|
97
97
|
|
|
98
98
|
window.kolbo.onToolResult(function (result) {
|
|
99
99
|
var sc = result.structuredContent || structured(result);
|
|
100
|
-
|
|
100
|
+
// Only boot on a real widget contract (phase present). A text-JSON
|
|
101
|
+
// fallback like { status: 'submitted' } has no phase and would render a
|
|
102
|
+
// bogus "(empty transcript)" completed view.
|
|
103
|
+
if (sc && (sc.phase || sc.widget === 'transcript')) return boot(sc);
|
|
104
|
+
var txt = '';
|
|
105
|
+
try { txt = (result.content || []).filter(function (c) { return c.type === 'text'; }).map(function (c) { return c.text; }).join(' '); } catch (e) {}
|
|
106
|
+
if (result.isError || /error|failed/i.test(txt)) {
|
|
107
|
+
el('phase-chip').style.display = 'none';
|
|
108
|
+
el('stage').classList.remove('k-empty');
|
|
109
|
+
el('stage').innerHTML = '<div class="k-error">⚠ ' + esc((txt || 'Transcription failed').slice(0, 300)) + '</div>';
|
|
110
|
+
window.kolbo.notifySize();
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
// No usable data — collapse instead of a dead "Loading…" card.
|
|
114
|
+
var card = document.querySelector('.k-card');
|
|
115
|
+
if (card) card.style.display = 'none';
|
|
116
|
+
window.kolbo.notifySize();
|
|
101
117
|
});
|
|
102
118
|
`;
|
|
103
119
|
|
package/src/tools/_shared.js
CHANGED
|
@@ -184,9 +184,23 @@ async function resolveToBuffer(source, kind, opts = {}) {
|
|
|
184
184
|
}
|
|
185
185
|
|
|
186
186
|
if (!path.isAbsolute(source)) {
|
|
187
|
-
throw new Error(
|
|
187
|
+
throw new Error(
|
|
188
|
+
`Local file paths must be absolute: ${source}. ` +
|
|
189
|
+
`If you are using Kolbo over a remote connector (e.g. claude.ai), local files are not reachable — ` +
|
|
190
|
+
`pass a public https:// URL instead (upload the file somewhere first, or use a URL from list_media).`
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
let stat;
|
|
194
|
+
try {
|
|
195
|
+
stat = fs.statSync(source);
|
|
196
|
+
} catch (err) {
|
|
197
|
+
throw new Error(
|
|
198
|
+
`Local file not found or unreadable: ${source}. ` +
|
|
199
|
+
`If you are using Kolbo over a remote connector (e.g. claude.ai), local file paths are not reachable — ` +
|
|
200
|
+
`pass a public https:// URL instead (upload the file somewhere first, or use a URL from list_media).` +
|
|
201
|
+
(err && err.code ? ` [${err.code}]` : '')
|
|
202
|
+
);
|
|
188
203
|
}
|
|
189
|
-
const stat = fs.statSync(source);
|
|
190
204
|
if (stat.size > maxBytes) {
|
|
191
205
|
throw new Error(`File ${source} (${stat.size} bytes) exceeds ${maxBytes}-byte limit`);
|
|
192
206
|
}
|