@kolbo/mcp 1.104.1 → 1.105.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.104.1",
3
+ "version": "1.105.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": {
package/src/apps/html.js CHANGED
@@ -128,6 +128,10 @@ function applyTheme(ctx) {
128
128
  } catch (e) {}
129
129
  }
130
130
  window.kolbo.ready(function (ctx) { applyTheme(ctx); window.kolbo.notifySize(); });
131
+ // The header mark is the way into the app (the footer link is no longer shown).
132
+ document.addEventListener('click', function (e) {
133
+ if (e.target && e.target.closest && e.target.closest('#logo')) window.kolbo.openLink('https://app.kolbo.ai');
134
+ });
131
135
  window.kolbo.onThemeChange(applyTheme);
132
136
 
133
137
  // Model chip: real icon when the API provided one, brand monogram fallback.
@@ -163,6 +167,7 @@ function ensurePeek() {
163
167
  // nofullscreen: host iframes are never granted allow="fullscreen", so the
164
168
  // native button is always greyed out. The lightbox IS the fullscreen view.
165
169
  + '<video id="peek-video" playsinline controls controlslist="nofullscreen"></video></div>'
170
+ + '<div class="k-peek-strip" id="peek-strip" hidden></div>'
166
171
  + '<button type="button" class="k-peek-nav prev" id="peek-prev" aria-label="Previous"></button>'
167
172
  + '<button type="button" class="k-peek-nav next" id="peek-next" aria-label="Next"></button>';
168
173
  card.appendChild(box);
@@ -247,6 +252,25 @@ function showPeekItem(time) {
247
252
  el('peek-count').textContent = many ? (peekIndex + 1) + ' / ' + peekList.length : '';
248
253
  el('peek-prev').hidden = !many;
249
254
  el('peek-next').hidden = !many;
255
+ var strip = el('peek-strip');
256
+ strip.hidden = !many;
257
+ Array.prototype.forEach.call(strip.children, function (t, i) {
258
+ t.classList.toggle('on', i === peekIndex);
259
+ if (i === peekIndex && t.scrollIntoView) { try { t.scrollIntoView({ block: 'nearest', inline: 'nearest' }); } catch (e) {} }
260
+ });
261
+ }
262
+ // Filmstrip under the lightbox: every item in the set, current one outlined.
263
+ function buildPeekStrip() {
264
+ var strip = el('peek-strip');
265
+ strip.innerHTML = peekList.length > 1 ? peekList.map(function (it, i) {
266
+ var inner = it.kind === 'video'
267
+ ? '<video src="' + esc(it.url) + '#t=0.1" muted playsinline preload="metadata"></video>'
268
+ : it.kind === 'image' ? '<img src="' + esc(it.url) + '" alt="" loading="lazy">' : kindIcon(it.kind);
269
+ return '<button type="button" class="k-peek-thumb" data-strip="' + i + '" aria-label="Item ' + (i + 1) + '">' + inner + '</button>';
270
+ }).join('') : '';
271
+ Array.prototype.forEach.call(strip.children, function (t) {
272
+ t.onclick = function (e) { e.stopPropagation(); peekIndex = +t.getAttribute('data-strip'); showPeekItem(0); };
273
+ });
250
274
  }
251
275
  function stepPeek(d) {
252
276
  var box = el('peek');
@@ -265,6 +289,7 @@ function openPeek(url, kind, cap, opts) {
265
289
  Array.prototype.forEach.call(document.querySelectorAll('video'), function (v) {
266
290
  if (v.id !== 'peek-video') { try { v.pause(); } catch (e) {} }
267
291
  });
292
+ buildPeekStrip();
268
293
  showPeekItem(opts.time || 0);
269
294
  box.hidden = false;
270
295
  document.documentElement.classList.add('k-peek-open');
@@ -287,6 +312,7 @@ function closePeek(hostExited) {
287
312
  box.hidden = true;
288
313
  document.documentElement.classList.remove('k-peek-open');
289
314
  el('peek-img').removeAttribute('src');
315
+ el('peek-strip').innerHTML = '';
290
316
  var vid = el('peek-video');
291
317
  if (vid) { try { vid.pause(); } catch (e) {} vid.removeAttribute('src'); }
292
318
  if (peekWentFullscreen) {
package/src/apps/theme.js CHANGED
@@ -11,8 +11,9 @@
11
11
  const KOLBO_CSS = `
12
12
  :root {
13
13
  --bg: #0f0f0f;
14
- --card: rgba(38, 38, 38, 0.90);
15
- --card-solid: #262626;
14
+ --card: rgba(22, 22, 25, 0.96);
15
+ --card-solid: #161619;
16
+ --panel: rgba(255, 255, 255, 0.035);
16
17
  --surface: rgba(255, 255, 255, 0.03);
17
18
  --surface-2: rgba(255, 255, 255, 0.06);
18
19
  --border: rgba(255, 255, 255, 0.08);
@@ -25,8 +26,8 @@ const KOLBO_CSS = `
25
26
  --success: #22c55e;
26
27
  --error: #ef4444;
27
28
  --warning: #f59e0b;
28
- --radius-card: 16px;
29
- --radius-btn: 8px;
29
+ --radius-card: 18px;
30
+ --radius-btn: 10px;
30
31
  --spring: cubic-bezier(0.34, 1.56, 0.64, 1);
31
32
  --smooth: cubic-bezier(0.25, 0.46, 0.45, 0.94);
32
33
  --specular: inset 0 1px 0 rgba(255, 255, 255, 0.18);
@@ -36,6 +37,7 @@ const KOLBO_CSS = `
36
37
  --bg: #f5f5f2;
37
38
  --card: rgba(255, 255, 255, 0.85);
38
39
  --card-solid: #ffffff;
40
+ --panel: rgba(0, 0, 0, 0.035);
39
41
  --surface: rgba(0, 0, 0, 0.02);
40
42
  --surface-2: rgba(0, 0, 0, 0.04);
41
43
  --border: rgba(0, 0, 0, 0.08);
@@ -65,28 +67,34 @@ body {
65
67
  -webkit-backdrop-filter: blur(20px) saturate(180%);
66
68
  border: 1px solid var(--border);
67
69
  border-radius: var(--radius-card);
68
- box-shadow: 0 4px 24px rgba(0, 0, 0, 0.25), var(--specular);
70
+ box-shadow: 0 2px 16px rgba(0, 0, 0, 0.22), var(--specular);
69
71
  overflow: hidden;
70
72
  animation: k-in 400ms var(--spring);
71
73
  }
72
74
  @keyframes k-in { from { opacity: 0; transform: translateY(6px) scale(0.985); } to { opacity: 1; transform: none; } }
73
75
 
74
76
  .k-head {
75
- display: flex; align-items: center; gap: 10px;
76
- padding: 12px 16px;
77
- border-bottom: 1px solid var(--border);
77
+ display: flex; align-items: center; gap: 8px;
78
+ padding: 12px 14px 10px;
78
79
  }
79
- .k-logo { display: flex; align-items: center; gap: 8px; font-weight: 600; font-size: 13px; letter-spacing: -0.01em; }
80
+ /* The mark sits at the far right and the card is named by what it shows:
81
+ the brand word next to the title was chrome, not information. */
82
+ .k-logo { order: 99; margin-left: auto; display: flex; align-items: center; cursor: pointer; opacity: 0.9; }
83
+ .k-logo:hover { opacity: 1; }
84
+ .k-logo > span { display: none; }
80
85
  .k-logo svg { display: block; }
81
- .k-head .k-title { color: var(--text-muted); font-size: 12.5px; font-weight: 500; }
86
+ .k-head .k-title { color: var(--text); font-size: 13.5px; font-weight: 650; letter-spacing: -0.01em; }
82
87
  .k-head .k-spacer { flex: 1; }
83
88
 
84
- .k-body { padding: 14px 16px; }
85
- .k-prompt { color: var(--text-muted); font-size: 12.5px; margin-bottom: 4px; word-break: break-word;
86
- display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
89
+ .k-body { padding: 0 12px 12px; }
90
+ .k-prompt { color: var(--text-muted); font-size: 12.5px; margin: 0 2px 2px; word-break: break-word;
91
+ display: -webkit-box; -webkit-line-clamp: 1; -webkit-box-orient: vertical; overflow: hidden;
87
92
  user-select: text; -webkit-user-select: text; cursor: text; }
88
93
  .k-prompt.expanded { -webkit-line-clamp: unset; white-space: pre-wrap; }
89
- .k-text-tools { display: flex; gap: 2px; justify-content: flex-end; margin: 0 0 10px; }
94
+ .k-text-tools { display: flex; gap: 2px; justify-content: flex-end; margin: -2px 0 6px; }
95
+ /* The prompt's Copy / Expand sit on the prompt's own line, not a row of their own. */
96
+ #prompt { padding-right: 132px; }
97
+ #prompt + .k-text-tools { margin: -24px 0 8px; }
90
98
  .k-text-btn {
91
99
  display: inline-flex; align-items: center; gap: 4px;
92
100
  padding: 3px 8px; border: 0; border-radius: 6px;
@@ -113,20 +121,19 @@ body {
113
121
  .k-caption + .k-text-tools { margin: 0 2px 6px; justify-content: flex-start; }
114
122
 
115
123
  /* ---- Chips ---- */
116
- .k-chips { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; margin-bottom: 12px; }
124
+ .k-chips { display: flex; flex-wrap: wrap; gap: 4px; align-items: center; margin: 0 0 10px; }
117
125
  .k-chip {
118
126
  display: inline-flex; align-items: center; gap: 5px;
119
- padding: 3px 9px; border-radius: 999px;
120
- background: var(--surface-2); border: 1px solid var(--border);
121
- box-shadow: var(--specular);
122
- font-size: 11px; font-weight: 500; color: var(--text-muted);
127
+ padding: 3px 8px; border-radius: 999px;
128
+ background: var(--surface-2); border: 0;
129
+ font-size: 10.5px; font-weight: 500; color: var(--text-muted);
123
130
  white-space: nowrap;
124
131
  font-family: 'JetBrains Mono', ui-monospace, monospace;
125
132
  letter-spacing: 0.01em;
126
133
  animation: k-chip-in 200ms var(--spring);
127
134
  }
128
135
  @keyframes k-chip-in { from { opacity: 0; transform: translateX(-6px); } to { opacity: 1; transform: none; } }
129
- .k-chip.brand { background: var(--brand-soft); border-color: rgba(59, 130, 246, 0.3); color: var(--brand); }
136
+ .k-chip.brand { background: var(--brand-soft); color: var(--brand); }
130
137
  .k-chip img { width: 14px; height: 14px; border-radius: 4px; object-fit: cover; }
131
138
  .k-chip .k-mono-icon {
132
139
  width: 14px; height: 14px; border-radius: 4px; background: var(--brand);
@@ -139,7 +146,7 @@ body {
139
146
  .k-peek {
140
147
  position: absolute; inset: 0; z-index: 20;
141
148
  display: flex; flex-direction: column;
142
- background: rgba(6, 8, 12, 0.96);
149
+ background: #0b0b0d;
143
150
  }
144
151
  .k-peek[hidden] { display: none !important; }
145
152
  /* An inline lightbox needs room: a short card (one list row, a 3-tile grid)
@@ -172,13 +179,29 @@ html.k-peek-open .k-card { min-height: 460px; }
172
179
  display: inline-flex; align-items: center; justify-content: center;
173
180
  }
174
181
  .k-peek-nav[hidden] { display: none; }
182
+ .k-peek-strip { flex: none; display: flex; gap: 6px; padding: 0 12px 12px;
183
+ overflow-x: auto; scrollbar-width: none; }
184
+ .k-peek-strip > :first-child { margin-left: auto; }
185
+ .k-peek-strip > :last-child { margin-right: auto; }
186
+ .k-peek-strip[hidden] { display: none; }
187
+ .k-peek-strip::-webkit-scrollbar { display: none; }
188
+ .k-peek-thumb { flex: none; width: 44px; height: 44px; padding: 0; border-radius: 8px; overflow: hidden; cursor: pointer;
189
+ border: 2px solid transparent; background: #1c1c20; color: #fff; opacity: 0.55;
190
+ display: inline-flex; align-items: center; justify-content: center;
191
+ transition: opacity 150ms var(--smooth), border-color 150ms var(--smooth); }
192
+ .k-peek-thumb:hover { opacity: 0.9; }
193
+ .k-peek-thumb.on { border-color: var(--brand); opacity: 1; }
194
+ .k-peek-thumb img, .k-peek-thumb video { width: 100%; height: 100%; object-fit: cover; display: block; pointer-events: none; }
175
195
  .k-peek-nav:hover { background: var(--brand); border-color: var(--brand); }
176
196
  .k-peek-nav.prev { left: 10px; }
177
197
  .k-peek-nav.next { right: 10px; }
178
198
  /* Host granted fullscreen: the lightbox owns the whole viewport, not the card. */
179
199
  html.k-peek-fs, html.k-peek-fs body { height: 100%; overflow: hidden; }
180
200
  html.k-peek-fs .k-peek { position: fixed; inset: 0; z-index: 50; }
181
- html.k-peek-fs .k-card { min-height: 0; }
201
+ /* backdrop-filter (and any transform) makes the card the containing block for
202
+ position:fixed descendants, so a GRANTED fullscreen still showed the lightbox
203
+ card-sized. Drop them while the lightbox owns the viewport. */
204
+ html.k-peek-fs .k-card { min-height: 0; backdrop-filter: none; -webkit-backdrop-filter: none; transform: none; animation: none; }
182
205
  /* Expand button on every inline video — replaces the native fullscreen button,
183
206
  which a host iframe without allow="fullscreen" always greys out. */
184
207
  .k-vexpand {
@@ -227,8 +250,8 @@ video::-webkit-media-controls-fullscreen-button { display: none !important; }
227
250
  .k-gen-grid.n2 { grid-template-columns: 1fr 1fr; }
228
251
  .k-gen-grid.n3, .k-gen-grid.n4 { grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); }
229
252
  .k-skel {
230
- position: relative; border-radius: 10px; overflow: hidden;
231
- background: var(--surface-2); border: 1px solid var(--border);
253
+ position: relative; border-radius: 12px; overflow: hidden;
254
+ background: var(--surface-2);
232
255
  min-height: 120px; max-height: 300px;
233
256
  /* width:100% is load-bearing, not cosmetic. Without a definite width the
234
257
  aspect-ratio transfers the OTHER way: a 16/9 cell in a 196px auto-fill
@@ -319,29 +342,33 @@ video::-webkit-media-controls-fullscreen-button { display: none !important; }
319
342
  /* Keep the whole completed card under the host's iframe height cap (~800px):
320
343
  header + prompt + chips + viewer + thumbs + actions + footer must all fit,
321
344
  or claude.ai adds an inner scrollbar. Click the image to expand in-Claude. */
322
- .k-viewer { margin-bottom: 10px; }
323
- .k-viewer img, .k-viewer video { display: block; width: 100%;
345
+ /* Media sits in an inset panel. The image is sized to itself (width:auto) so a
346
+ portrait result no longer floats in a wide black letterbox. */
347
+ .k-viewer { margin-bottom: 0; padding: 8px; border-radius: 16px; background: var(--panel);
348
+ display: flex; justify-content: center; }
349
+ .k-viewer img, .k-viewer video { display: block; max-width: 100%;
324
350
  /* Fixed px, no vh: vh inside an iframe is the height the host granted, so a
325
351
  vh-based cap grew as the iframe grew and the card chased its own tail. */
326
- max-height: 320px; object-fit: contain;
327
- border-radius: 12px; background: #000; border: 1px solid var(--border); cursor: zoom-in; }
328
- .k-viewer video { cursor: default; }
352
+ max-height: 400px; object-fit: contain;
353
+ border-radius: 10px; cursor: zoom-in; }
354
+ .k-viewer img { width: auto; height: auto; }
355
+ .k-viewer video { width: 100%; background: #000; cursor: default; }
356
+ #stage > .k-gen-grid, #stage > .k-grid { padding: 8px; border-radius: 16px; background: var(--panel); }
329
357
 
330
358
  .k-expand-hint { display: none; }
331
- .k-thumbs { display: flex; gap: 6px; margin: 10px 0 2px; }
332
- .k-thumbs .k-thumb { width: 48px; height: 48px; border-radius: 8px; overflow: hidden; cursor: pointer;
333
- border: 2px solid transparent; opacity: 0.75; transition: all 150ms var(--smooth); flex: none; }
359
+ .k-thumbs { display: flex; gap: 6px; margin: 8px 0 0; justify-content: center; }
360
+ .k-thumbs .k-thumb { width: 44px; height: 44px; border-radius: 9px; overflow: hidden; cursor: pointer;
361
+ border: 2px solid transparent; opacity: 0.6; transition: all 150ms var(--smooth); flex: none; }
334
362
  .k-thumbs .k-thumb:hover { opacity: 1; }
335
363
  .k-thumbs .k-thumb.active { border-color: var(--brand); opacity: 1; }
336
364
  .k-thumbs .k-thumb img { width: 100%; height: 100%; object-fit: cover; }
337
365
 
338
366
  /* ---- Buttons ---- */
339
- .k-actions { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; padding-top: 12px; }
367
+ .k-actions { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; padding-top: 10px; }
340
368
  .k-btn {
341
369
  display: inline-flex; align-items: center; gap: 6px;
342
- padding: 7px 14px; border-radius: var(--radius-btn);
343
- border: 1px solid var(--border); background: var(--surface-2);
344
- box-shadow: var(--specular);
370
+ padding: 7px 13px; border-radius: var(--radius-btn);
371
+ border: 1px solid transparent; background: var(--surface-2);
345
372
  color: var(--text); font-size: 12px; font-weight: 600; font-family: inherit;
346
373
  cursor: pointer; transition: all 150ms var(--smooth);
347
374
  text-decoration: none; user-select: none;
@@ -384,16 +411,18 @@ video::-webkit-media-controls-fullscreen-button { display: none !important; }
384
411
  tiles below a thumbnail worth looking at. The 520px block further down wins
385
412
  under 520 and takes it to 2. */
386
413
  @media (max-width: 620px) { .k-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
387
- .k-tile { position: relative; aspect-ratio: 1; border-radius: 12px; overflow: hidden;
388
- border: 1px solid var(--border); background: var(--surface); cursor: pointer;
389
- transition: transform 250ms var(--spring), box-shadow 250ms var(--smooth), border-color 150ms var(--smooth); }
390
- .k-tile:hover { transform: translateY(-2px); border-color: var(--border-strong);
391
- box-shadow: 0 10px 26px rgba(0, 0, 0, 0.42); }
392
- .k-tile img, .k-tile video { display: block; width: 100%; height: 100%; object-fit: cover; }
414
+ .k-tile { position: relative; aspect-ratio: 1; border-radius: 10px; overflow: hidden;
415
+ background: var(--surface-2); cursor: pointer; }
416
+ .k-tile img, .k-tile video { display: block; width: 100%; height: 100%; object-fit: cover;
417
+ transition: transform 350ms var(--smooth), filter 200ms var(--smooth); }
418
+ .k-tile:hover img { transform: scale(1.03); filter: brightness(1.06); }
393
419
  .k-tile-fb { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
394
420
  color: var(--text-faint); font-size: 24px; }
395
421
  .k-tile-cap { position: absolute; left: 0; right: 0; bottom: 0; z-index: 2; pointer-events: none;
396
- padding: 20px 8px 7px; background: linear-gradient(transparent, rgba(0, 0, 0, 0.78)); }
422
+ padding: 20px 8px 7px; background: linear-gradient(transparent, rgba(0, 0, 0, 0.78));
423
+ opacity: 0; transition: opacity 150ms var(--smooth); }
424
+ .k-tile:hover .k-tile-cap, .k-tile:focus-within .k-tile-cap { opacity: 1; }
425
+ @media (hover: none) { .k-tile-cap { opacity: 1; } }
397
426
  .k-tile-t { font-size: 11px; font-weight: 600; color: #fff;
398
427
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
399
428
  .k-tile-s { font-size: 10px; color: rgba(255, 255, 255, 0.66);
@@ -409,7 +438,8 @@ video::-webkit-media-controls-fullscreen-button { display: none !important; }
409
438
  display: inline-flex; align-items: center; justify-content: center; cursor: pointer;
410
439
  transition: background 150ms var(--smooth); }
411
440
  .k-tile-act:hover { background: var(--brand); border-color: var(--brand); }
412
- .k-tile-play { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 2; }
441
+ .k-tile-play { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); z-index: 2;
442
+ width: 46px; height: 38px; border-radius: 12px; border: 0; background: rgba(40, 44, 50, 0.62); font-size: 15px; }
413
443
 
414
444
  /* ---- Pager (media grid + list) ----
415
445
  A page of results at a fixed card height, instead of a card that grows a row
@@ -434,7 +464,7 @@ video::-webkit-media-controls-fullscreen-button { display: none !important; }
434
464
 
435
465
  /* ---- Audio rows ---- */
436
466
  .k-audio-row { display: flex; align-items: center; gap: 10px; padding: 9px 10px;
437
- border-radius: 10px; border: 1px solid var(--border); background: var(--surface);
467
+ border-radius: 12px; background: var(--panel);
438
468
  margin-bottom: 6px; transition: background 150ms var(--smooth); }
439
469
  .k-audio-row:hover { background: var(--surface-2); }
440
470
  .k-audio-art { width: 40px; height: 40px; border-radius: 8px; object-fit: cover; flex: none;
@@ -467,8 +497,8 @@ video::-webkit-media-controls-fullscreen-button { display: none !important; }
467
497
  .k-generated-audio .k-audio-player { grid-column: 1 / -1; }
468
498
  /* Touch-friendly MCP App layout (Claude iOS/Android iframe). */
469
499
  .k-head { padding: 10px 12px; gap: 8px; }
470
- .k-body { padding: 10px 12px 12px; }
471
- .k-footer { padding: 8px 12px 12px; }
500
+ .k-body { padding: 0 10px 10px; }
501
+ .k-footer { padding: 0 12px 10px; }
472
502
  .k-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
473
503
  .k-tile-act { width: 30px; height: 30px; }
474
504
  .k-pager-btn { width: 34px; height: 34px; }
@@ -494,8 +524,10 @@ video::-webkit-media-controls-fullscreen-button { display: none !important; }
494
524
  background: rgba(239, 68, 68, 0.08); border: 1px solid rgba(239, 68, 68, 0.25);
495
525
  color: #fca5a5; font-size: 12.5px; }
496
526
  .k-empty { padding: 22px; text-align: center; color: var(--text-faint); font-size: 12.5px; }
497
- .k-footer { display: flex; align-items: center; gap: 6px; padding: 8px 16px 12px;
527
+ .k-footer { display: flex; align-items: center; gap: 6px; padding: 0 14px 10px;
498
528
  font-size: 10.5px; color: var(--text-faint); }
529
+ .k-footer > span:not(.k-credits) { display: none; }
530
+ .k-footer:not(:has(.k-credits:not(:empty))) { display: none; }
499
531
  .k-footer a { color: var(--text-faint); text-decoration: none; }
500
532
  .k-footer a:hover { color: var(--brand); }
501
533
  .k-credits { margin-left: auto; font-family: 'JetBrains Mono', monospace; }