@cloudinary/asset-management-mcp 0.9.1 → 0.9.3

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.
Files changed (47) hide show
  1. package/README.md +12 -15
  2. package/bin/mcp-server.js +288 -369
  3. package/bin/mcp-server.js.map +13 -13
  4. package/esm/landing-page.d.ts.map +1 -1
  5. package/esm/landing-page.js +9 -3
  6. package/esm/landing-page.js.map +1 -1
  7. package/esm/lib/config.d.ts +3 -3
  8. package/esm/lib/config.js +3 -3
  9. package/esm/mcp-server/apps/app-shared.d.ts +6 -5
  10. package/esm/mcp-server/apps/app-shared.d.ts.map +1 -1
  11. package/esm/mcp-server/apps/app-shared.js +134 -14
  12. package/esm/mcp-server/apps/app-shared.js.map +1 -1
  13. package/esm/mcp-server/apps/asset-details-app.d.ts.map +1 -1
  14. package/esm/mcp-server/apps/asset-details-app.js +7 -14
  15. package/esm/mcp-server/apps/asset-details-app.js.map +1 -1
  16. package/esm/mcp-server/apps/asset-gallery-app.d.ts.map +1 -1
  17. package/esm/mcp-server/apps/asset-gallery-app.js +99 -306
  18. package/esm/mcp-server/apps/asset-gallery-app.js.map +1 -1
  19. package/esm/mcp-server/apps/asset-upload-app.d.ts.map +1 -1
  20. package/esm/mcp-server/apps/asset-upload-app.js +29 -24
  21. package/esm/mcp-server/apps/asset-upload-app.js.map +1 -1
  22. package/esm/mcp-server/apps/config.d.ts.map +1 -1
  23. package/esm/mcp-server/apps/config.js +1 -2
  24. package/esm/mcp-server/apps/config.js.map +1 -1
  25. package/esm/mcp-server/apps/extensions.d.ts.map +1 -1
  26. package/esm/mcp-server/apps/extensions.js +6 -1
  27. package/esm/mcp-server/apps/extensions.js.map +1 -1
  28. package/esm/mcp-server/cli/serve/impl.js +1 -1
  29. package/esm/mcp-server/cli/serve/impl.js.map +1 -1
  30. package/esm/mcp-server/mcp-server.js +1 -1
  31. package/esm/mcp-server/server.js +1 -1
  32. package/esm/types/bigint.d.ts.map +1 -1
  33. package/esm/types/bigint.js +4 -3
  34. package/esm/types/bigint.js.map +1 -1
  35. package/package.json +1 -1
  36. package/src/landing-page.ts +9 -3
  37. package/src/lib/config.ts +3 -3
  38. package/src/mcp-server/apps/app-shared.ts +135 -14
  39. package/src/mcp-server/apps/asset-details-app.ts +7 -13
  40. package/src/mcp-server/apps/asset-gallery-app.ts +99 -305
  41. package/src/mcp-server/apps/asset-upload-app.ts +29 -23
  42. package/src/mcp-server/apps/config.ts +1 -2
  43. package/src/mcp-server/apps/extensions.ts +6 -1
  44. package/src/mcp-server/cli/serve/impl.ts +1 -1
  45. package/src/mcp-server/mcp-server.ts +1 -1
  46. package/src/mcp-server/server.ts +1 -1
  47. package/src/types/bigint.ts +18 -17
@@ -6,7 +6,7 @@
6
6
  * Shares CLDS tokens, MCPApp client, helpers, and detail renderers
7
7
  * with the details app via app-shared.ts.
8
8
  */
9
- import { SHARED_CSS_TOKENS, SHARED_CSS_COMPONENTS, SHARED_JS_MCP_CLIENT, SHARED_JS_HELPERS, SHARED_JS_TOOLTIPS, SHARED_JS_MODAL, SHARED_JS_DETAIL_RENDERERS, SHARED_JS_HOST_CONTEXT, } from "./app-shared.js";
9
+ import { SHARED_CSS_TOKENS, SHARED_CSS_COMPONENTS, SHARED_JS_ICONS, SHARED_JS_MCP_CLIENT, SHARED_JS_HELPERS, SHARED_JS_TOOLTIPS, SHARED_JS_MODAL, SHARED_JS_DETAIL_RENDERERS, SHARED_JS_HOST_CONTEXT, } from "./app-shared.js";
10
10
  import { injectToolName } from "./uri.js";
11
11
  export function getAssetGalleryHtml(toolName) {
12
12
  return injectToolName(ASSET_GALLERY_HTML, toolName);
@@ -25,90 +25,10 @@ const GALLERY_CSS = /* css */ `
25
25
  font-size: var(--cld-font-xxs); color: var(--cld-text3); background: var(--cld-bg3);
26
26
  padding: 2px 8px; border-radius: 20px; font-weight: 500;
27
27
  }
28
- .select-all-btn {
29
- font-size: 12px; color: var(--cld-text2); background: none;
30
- border: 1px solid var(--cld-border); border-radius: var(--cld-radius-sm);
31
- padding: 4px 10px; cursor: pointer; font-family: inherit;
32
- transition: color 0.15s, border-color 0.15s;
33
- }
34
- .select-all-btn:hover { color: var(--cld-accent); border-color: var(--cld-accent); }
35
- .refresh-btn {
36
- background: none; border: 1px solid var(--cld-border); border-radius: var(--cld-radius-sm);
37
- color: var(--cld-text2); cursor: pointer; font-size: 14px; padding: 2px 7px;
38
- line-height: 1; transition: background 0.15s, color 0.15s;
39
- }
40
- .refresh-btn:hover { background: var(--cld-bg3); color: var(--cld-text); }
41
-
42
- /* ── Filter bar ── */
43
- .filter-row {
44
- margin-bottom: var(--cld-sp-md); display: flex; gap: 8px; align-items: center;
45
- }
46
- .filter-text-wrap { position: relative; flex: 1; }
47
- .filter-input {
48
- width: 100%; height: 36px; padding: 0 12px 0 34px;
49
- border: 1px solid var(--cld-border); border-radius: var(--cld-radius);
50
- background: var(--cld-bg); font-size: 12.5px; color: var(--cld-text);
51
- outline: none; font-family: inherit;
52
- transition: border-color 0.18s, box-shadow 0.18s;
53
- }
54
- .filter-input::placeholder { color: var(--cld-text3); }
55
- .filter-input:focus {
56
- border-color: var(--cld-accent);
57
- box-shadow: 0 0 0 3px rgba(52,72,197,0.1);
58
- }
59
- [data-theme="dark"] .filter-input:focus { box-shadow: 0 0 0 3px rgba(13,154,255,0.15); }
60
- .filter-icon {
61
- position: absolute; left: 11px; top: 50%; transform: translateY(-50%);
62
- color: var(--cld-text3); pointer-events: none; display: flex; align-items: center;
63
- }
64
- .filter-clear {
65
- position: absolute; right: 10px; top: 50%; transform: translateY(-50%);
66
- background: none; border: none; color: var(--cld-text3); cursor: pointer;
67
- font-size: 14px; line-height: 1; padding: 2px 4px; border-radius: 4px;
68
- display: none; font-family: inherit;
69
- }
70
- .filter-clear:hover { color: var(--cld-text); background: var(--cld-border); }
71
- .filter-clear.visible { display: block; }
72
-
73
- /* Aspect-ratio dropdown */
74
- .aspect-dropdown { position: relative; flex-shrink: 0; user-select: none; }
75
- .aspect-btn {
76
- height: 36px; padding: 0 10px; border: 1px solid var(--cld-border);
77
- border-radius: var(--cld-radius); background: var(--cld-bg);
78
- font-size: 12.5px; color: var(--cld-text); cursor: pointer;
79
- display: flex; align-items: center; gap: 6px; white-space: nowrap;
80
- transition: border-color 0.18s, box-shadow 0.18s, background 0.18s;
81
- font-family: inherit; outline: none;
82
- }
83
- .aspect-btn:hover { border-color: var(--cld-border2); }
84
- .aspect-btn.active {
85
- border-color: var(--cld-accent); background: var(--cld-accent-bg);
86
- color: var(--cld-accent); font-weight: 600;
87
- }
88
- .aspect-btn-chevron { color: var(--cld-text3); flex-shrink: 0; transition: transform 0.18s; }
89
- .aspect-btn.open .aspect-btn-chevron { transform: rotate(180deg); }
90
- .aspect-menu {
91
- position: absolute; top: calc(100% + 6px); right: 0;
92
- background: var(--cld-bg); border: 1px solid var(--cld-border);
93
- border-radius: 10px; box-shadow: var(--cld-shadow-md);
94
- padding: 4px; min-width: 160px; z-index: 50; display: none;
95
- }
96
- .aspect-menu.open { display: block; }
97
- .aspect-option {
98
- display: flex; align-items: center; gap: 10px;
99
- padding: 7px 10px; border-radius: 6px; font-size: 12.5px;
100
- color: var(--cld-text); cursor: pointer; transition: background 0.18s;
101
- }
102
- .aspect-option:hover { background: var(--cld-bg3); }
103
- .aspect-option.selected { color: var(--cld-accent); font-weight: 600; }
104
- .aspect-opt-icon { color: var(--cld-text3); display: flex; align-items: center; flex-shrink: 0; }
105
- .aspect-option.selected .aspect-opt-icon { color: var(--cld-accent); }
106
- .aspect-check { margin-left: auto; color: var(--cld-accent); opacity: 0; }
107
- .aspect-option.selected .aspect-check { opacity: 1; }
108
- .no-results {
109
- grid-column: 1 / -1; padding: 60px 20px;
110
- text-align: center; color: var(--cld-text3); font-size: 13px;
111
- }
28
+ /* action-btn svg sizing */
29
+ .action-btn svg { width: 12px; height: 12px; fill: none; stroke: currentColor; stroke-width: 2.5; stroke-linecap: round; stroke-linejoin: round; flex-shrink: 0; }
30
+ /* select bar svg */
31
+ .bar-btn svg { width: 13px; height: 13px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; flex-shrink: 0; }
112
32
 
113
33
  /* ── Grid ── */
114
34
  .grid {
@@ -167,18 +87,11 @@ const GALLERY_CSS = /* css */ `
167
87
  opacity: 0; transition: opacity 0.18s; z-index: 4; pointer-events: none;
168
88
  }
169
89
  .card:hover .tags-overlay { opacity: 1; }
170
- .grid.filtering .tags-overlay { opacity: 1; }
171
90
  .tag-overlay {
172
91
  font-size: 10px; color: white;
173
92
  background: rgba(10, 12, 18, 0.55); padding: 2px 7px; border-radius: 20px;
174
93
  backdrop-filter: blur(6px); font-weight: 600; letter-spacing: 0.02em;
175
94
  }
176
- .tag-overlay.tag-match { background: rgba(52, 72, 197, 0.82); }
177
- .tag-overlay mark {
178
- background: rgba(255, 213, 79, 0.5); color: white;
179
- border-radius: 2px; padding: 0 1px;
180
- }
181
-
182
95
  /* Floating action buttons */
183
96
  .card-actions {
184
97
  position: absolute; bottom: 10px; left: 0; right: 0;
@@ -242,18 +155,27 @@ const GALLERY_CSS = /* css */ `
242
155
  }
243
156
 
244
157
  /* ── Multi-select bar ── */
158
+ /* Wrapper takes layout space ONLY when a selection is active. When idle
159
+ * it's display:none so it contributes 0 to scrollHeight (no phantom
160
+ * scrollbar gutter / no extra iframe height). */
161
+ .select-bar-wrap {
162
+ display: none;
163
+ position: sticky; bottom: 0; left: 0; right: 0;
164
+ justify-content: center; pointer-events: none;
165
+ z-index: 100; height: 64px;
166
+ }
167
+ .select-bar-wrap.visible { display: flex; }
245
168
  .select-bar {
246
- position: fixed; bottom: 16px; left: 50%;
247
- transform: translateX(-50%) translateY(80px);
169
+ position: absolute; bottom: 8px;
170
+ transform: translateY(80px);
248
171
  background: #1a1d24; color: white; border-radius: 40px;
249
172
  padding: 0 6px 0 16px; height: 48px;
250
173
  display: flex; align-items: center; gap: 4px;
251
174
  box-shadow: 0 8px 32px rgba(0,0,0,0.4);
252
175
  transition: transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1), opacity 0.2s;
253
- opacity: 0; pointer-events: none; z-index: 100; white-space: nowrap;
176
+ opacity: 0; pointer-events: none; white-space: nowrap;
254
177
  }
255
- .select-bar.visible { transform: translateX(-50%) translateY(0); opacity: 1; pointer-events: all; }
256
- .select-bar-spacer { height: 72px; }
178
+ .select-bar-wrap.visible .select-bar { transform: translateY(0); opacity: 1; pointer-events: all; }
257
179
  .select-count { font-size: 13px; font-weight: 600; margin-right: 8px; }
258
180
  .bar-btn {
259
181
  height: 36px; padding: 0 14px; border: none; border-radius: 30px;
@@ -293,8 +215,6 @@ var pendingCall = {
293
215
  args: null,
294
216
  };
295
217
  var selected = new Set();
296
- var filterQuery = "";
297
- var aspectFilter = "";
298
218
  var app = new MCPApp({ name: "Cloudinary Asset Gallery", version: "1.0.0" });
299
219
  setupHostContext(app);
300
220
 
@@ -318,33 +238,13 @@ function showToast(msg) {
318
238
  _toastTimer = setTimeout(function() { t.classList.remove("show"); }, 2000);
319
239
  }
320
240
 
321
- function getAspect(r) {
322
- if (!r.width || !r.height) return "";
323
- var ratio = r.width / r.height;
324
- if (ratio > 1.1) return "landscape";
325
- if (ratio < 0.9) return "portrait";
326
- return "square";
327
- }
328
-
329
- function highlightText(text, query) {
330
- if (!query) return esc(text);
331
- var lo = text.toLowerCase();
332
- var idx = lo.indexOf(query);
333
- if (idx === -1) return esc(text);
334
- return esc(text.slice(0, idx))
335
- + "<mark>" + esc(text.slice(idx, idx + query.length)) + "</mark>"
336
- + esc(text.slice(idx + query.length));
337
- }
338
-
339
241
  function updateSelectBar() {
340
- var bar = document.getElementById("select-bar");
242
+ var wrap = document.getElementById("select-bar-wrap");
341
243
  var countEl = document.getElementById("select-count");
342
- if (!bar || !countEl) return;
244
+ if (!wrap || !countEl) return;
343
245
  var n = selected.size;
344
246
  countEl.textContent = n + " selected";
345
- bar.classList.toggle("visible", n > 0);
346
- var spacer = document.getElementById("select-bar-spacer");
347
- if (spacer) spacer.style.display = n > 0 ? "" : "none";
247
+ wrap.classList.toggle("visible", n > 0);
348
248
  var btn = document.getElementById("select-all-btn");
349
249
  if (btn) {
350
250
  var visible = getVisibleIndices();
@@ -411,11 +311,9 @@ function copyAssetUrl(type, idx) {
411
311
  var url = r.secure_url || r.url || "";
412
312
  var copyUrl = type === "optimized" ? optimizedUrl(url, r) : url;
413
313
  if (!copyUrl) return;
414
- try {
415
- navigator.clipboard.writeText(copyUrl).then(function() {
416
- showToast(type === "optimized" ? "\\u2728 Optimized URL copied" : "URL copied");
417
- });
418
- } catch(e) { showError("Copy Failed", String(e)); }
314
+ copyText(copyUrl).then(function() {
315
+ showToast(type === "optimized" ? "\\u2728 Optimized URL copied" : "URL copied");
316
+ }).catch(function(e) { showError("Copy Failed", e && e.message ? e.message : String(e)); });
419
317
  }
420
318
 
421
319
  function downloadOne(idx) {
@@ -437,134 +335,74 @@ function copySelectedUrls(type) {
437
335
  urls.push(type === "optimized" ? optimizedUrl(url, r) : url);
438
336
  });
439
337
  if (!urls.length) return;
440
- try {
441
- navigator.clipboard.writeText(urls.join("\\n")).then(function() {
442
- showToast(urls.length + " " + (type === "optimized" ? "optimized " : "") + "URLs copied");
443
- });
444
- } catch(e) { showError("Copy Failed", String(e)); }
338
+ copyText(urls.join("\\n")).then(function() {
339
+ showToast(urls.length + " " + (type === "optimized" ? "optimized " : "") + "URLs copied");
340
+ }).catch(function(e) { showError("Copy Failed", e && e.message ? e.message : String(e)); });
445
341
  }
446
342
 
447
- function downloadSelected() {
448
- var count = 0;
343
+ async function downloadSelected() {
344
+ if (selected.size === 0) return;
345
+
346
+ var picks = [];
449
347
  selected.forEach(function(i) {
450
348
  var r = allResources[i];
451
- if (!r) return;
452
- var url = r.secure_url || r.url || "";
453
- var dl = downloadUrl(url, r);
454
- if (dl) { app._rpc("ui/open-link", { url: dl }); count++; }
349
+ if (r && r.public_id) picks.push(r);
455
350
  });
456
- if (count) showToast("Downloading " + count + " asset" + (count > 1 ? "s" : ""));
457
- }
458
-
459
- function handleFilter() {
460
- var input = document.getElementById("filter-input");
461
- filterQuery = input ? input.value.trim().toLowerCase() : "";
462
-
463
- var clearBtn = document.getElementById("filter-clear");
464
- if (clearBtn) clearBtn.classList.toggle("visible", filterQuery.length > 0);
465
-
466
- var aspectBtn = document.getElementById("aspect-btn");
467
- if (aspectBtn) aspectBtn.classList.toggle("active", aspectFilter !== "");
468
-
469
- var anyFilter = filterQuery.length > 0 || aspectFilter !== "";
470
- var grid = document.getElementById("gallery-grid");
471
- if (grid) grid.classList.toggle("filtering", anyFilter);
351
+ if (!picks.length) return;
352
+
353
+ var requestBody = {
354
+ mode: "create",
355
+ target_format: "zip",
356
+ keep_derived: true,
357
+ target_public_id: "mcp-gallery-archive-" + Date.now(),
358
+ fully_qualified_public_ids: picks.map(function(r) {
359
+ return (r.resource_type || "image") + "/" + (r.type || "upload") + "/" + r.public_id;
360
+ }),
361
+ };
362
+
363
+ var btn = document.getElementById("bar-download");
364
+ var origLabel = btn ? btn.innerHTML : "";
365
+ if (btn) { btn.innerHTML = "Creating archive\\u2026"; btn.disabled = true; }
472
366
 
473
- var visibleCount = 0;
474
- for (var i = 0; i < allResources.length; i++) {
475
- var r = allResources[i];
476
- var card = document.getElementById("card-" + i);
477
- if (!card) continue;
367
+ try {
368
+ var res = await app.callServerTool({
369
+ name: "generate-archive",
370
+ arguments: {
371
+ resource_type: "all",
372
+ RequestBody: requestBody,
373
+ },
374
+ });
478
375
 
479
- var name = (r.public_id || r.filename || "").toLowerCase();
480
- var tags = r.tags || [];
481
- var textMatch = !filterQuery
482
- || name.indexOf(filterQuery) !== -1
483
- || tags.some(function(t) { return t.toLowerCase().indexOf(filterQuery) !== -1; });
484
-
485
- var aspectMatch = !aspectFilter || getAspect(r) === aspectFilter;
486
- var match = textMatch && aspectMatch;
487
- card.style.display = match ? "" : "none";
488
-
489
- var tagsEl = document.getElementById("tags-overlay-" + i);
490
- if (tagsEl && tags.length) {
491
- var maxOv = 3;
492
- var matchedTags = [];
493
- var otherTags = [];
494
- for (var ti = 0; ti < tags.length; ti++) {
495
- var isMatch = filterQuery && tags[ti].toLowerCase().indexOf(filterQuery) !== -1;
496
- if (isMatch) matchedTags.push(tags[ti]);
497
- else otherTags.push(tags[ti]);
498
- }
499
- var shown = matchedTags.slice();
500
- var remaining = maxOv - shown.length;
501
- if (remaining > 0) shown = shown.concat(otherTags.slice(0, remaining));
502
- var hidden = tags.length - shown.length;
503
- var hiddenTags = tags.filter(function(t) { return shown.indexOf(t) === -1; });
504
- tagsEl.innerHTML = shown.map(function(t) {
505
- var matched = filterQuery && t.toLowerCase().indexOf(filterQuery) !== -1;
506
- return '<span class="tag-overlay' + (matched ? ' tag-match' : '') + '">' + highlightText(t, filterQuery) + '</span>';
507
- }).join("") + (hidden > 0 ? '<span class="tag-overlay" title="' + esc(hiddenTags.join(", ")) + '">+' + hidden + '</span>' : '');
376
+ var data = ingestResult(res);
377
+ if (data && (data._error || data._parseError)) {
378
+ showError("Archive Failed", unwrapApiError(data._message));
379
+ return;
508
380
  }
509
-
510
- if (match) visibleCount++;
511
- }
512
-
513
- var badge = document.getElementById("count-badge");
514
- if (badge) {
515
- badge.textContent = anyFilter
516
- ? visibleCount + " of " + allResources.length
517
- : allResources.length + (lastCursor ? "+" : "") + " items";
518
- }
519
-
520
- var noRes = document.getElementById("no-results");
521
- if (visibleCount === 0 && anyFilter) {
522
- if (!noRes && grid) {
523
- noRes = document.createElement("div");
524
- noRes.id = "no-results";
525
- noRes.className = "no-results";
526
- grid.appendChild(noRes);
381
+ var archiveUrl = data && (data.secure_url || data.url);
382
+ if (!archiveUrl) {
383
+ showError("Archive Failed", "No delivery URL returned.");
384
+ return;
527
385
  }
528
- if (noRes) noRes.textContent = "No results" + (filterQuery ? ' for "' + filterQuery + '"' : "") + (aspectFilter ? " in " + aspectFilter + " images" : "");
529
- } else if (noRes) {
530
- noRes.remove();
386
+ try { await copyText(archiveUrl); } catch (e) { /* ignore */ }
387
+ app._rpc("ui/open-link", { url: archiveUrl });
388
+ showToast("Archive saved as raw in Cloudinary \\u2014 opening URL (" + picks.length + " asset" + (picks.length > 1 ? "s" : "") + ")");
389
+ } catch (e) {
390
+ showError("Archive Failed", unwrapApiError(e && e.message ? e.message : String(e)));
391
+ } finally {
392
+ if (btn) { btn.innerHTML = origLabel; btn.disabled = false; }
531
393
  }
532
394
  }
533
395
 
534
- function clearFilter() {
535
- var input = document.getElementById("filter-input");
536
- if (input) input.value = "";
537
- aspectFilter = "";
538
- var label = document.getElementById("aspect-btn-label");
539
- if (label) label.textContent = "All orientations";
540
- document.querySelectorAll(".aspect-option").forEach(function(o) {
541
- o.classList.toggle("selected", o.getAttribute("data-value") === "");
542
- });
543
- handleFilter();
544
- }
545
-
546
- function toggleAspectMenu(e) {
547
- e.stopPropagation();
548
- var btn = document.getElementById("aspect-btn");
549
- var menu = document.getElementById("aspect-menu");
550
- if (!btn || !menu) return;
551
- var open = menu.classList.toggle("open");
552
- btn.classList.toggle("open", open);
553
- }
554
-
555
- function selectAspect(val) {
556
- aspectFilter = val;
557
- var labels = { "": "All orientations", landscape: "Landscape", portrait: "Portrait", square: "Square" };
558
- var label = document.getElementById("aspect-btn-label");
559
- if (label) label.textContent = labels[aspectFilter] || "All orientations";
560
- document.querySelectorAll(".aspect-option").forEach(function(o) {
561
- o.classList.toggle("selected", o.getAttribute("data-value") === aspectFilter);
562
- });
563
- var menu = document.getElementById("aspect-menu");
564
- var btn = document.getElementById("aspect-btn");
565
- if (menu) menu.classList.remove("open");
566
- if (btn) btn.classList.remove("open");
567
- handleFilter();
396
+ function unwrapApiError(raw) {
397
+ if (!raw) return "Unknown error.";
398
+ var msg = String(raw);
399
+ try {
400
+ if (msg.charAt(0) === "{") {
401
+ var parsed = JSON.parse(msg);
402
+ msg = (parsed && parsed.error && parsed.error.message) || msg;
403
+ }
404
+ } catch (e) { /* keep raw */ }
405
+ return msg;
568
406
  }
569
407
 
570
408
  function render() {
@@ -584,41 +422,12 @@ function render() {
584
422
  h += '<h1>Results</h1>';
585
423
  h += '<span class="count-badge" id="count-badge">' + allResources.length + (lastCursor ? "+" : "") + ' items</span>';
586
424
  h += '</div>';
587
- h += '<div style="display:flex;align-items:center;gap:8px">';
588
- h += '<button class="select-all-btn" id="select-all-btn">Select all</button>';
589
- h += '<button class="refresh-btn" id="refresh-gallery" title="Refresh">\\u21BB</button>';
425
+ h += '<div id="header-actions" style="display:flex;align-items:center;gap:8px">';
426
+ h += '<button class="icon-btn" id="select-all-btn">Select all</button>';
427
+ h += '<button class="icon-btn icon-only" id="refresh-gallery" title="Refresh">' + IC.refresh + '</button>';
590
428
  h += '</div>';
591
429
  h += '</div>';
592
430
 
593
- // Filter bar
594
- h += '<div class="filter-row">';
595
- h += '<div class="filter-text-wrap">';
596
- h += '<span class="filter-icon"><svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="6.5" cy="6.5" r="4.5"/><path d="M10.5 10.5l3 3"/></svg></span>';
597
- h += '<input class="filter-input" id="filter-input" type="text" placeholder="Filter by filename or tag\\u2026" autocomplete="off" spellcheck="false">';
598
- h += '<button class="filter-clear" id="filter-clear">\\u2715</button>';
599
- h += '</div>';
600
- h += '<div class="aspect-dropdown" id="aspect-dropdown">';
601
- h += '<button class="aspect-btn" id="aspect-btn">';
602
- h += '<span id="aspect-btn-label">All orientations</span>';
603
- h += '<svg class="aspect-btn-chevron" width="11" height="11" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="2,4 6,8 10,4"/></svg>';
604
- h += '</button>';
605
- h += '<div class="aspect-menu" id="aspect-menu">';
606
- var aspects = [
607
- { val: "", label: "All orientations", icon: '<svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="1" y="1" width="12" height="12" rx="1.5"/></svg>' },
608
- { val: "landscape", label: "Landscape", icon: '<svg width="14" height="10" viewBox="0 0 14 10" fill="none" stroke="currentColor" stroke-width="1.5"><rect x=".75" y=".75" width="12.5" height="8.5" rx="1.5"/></svg>' },
609
- { val: "portrait", label: "Portrait", icon: '<svg width="10" height="14" viewBox="0 0 10 14" fill="none" stroke="currentColor" stroke-width="1.5"><rect x=".75" y=".75" width="8.5" height="12.5" rx="1.5"/></svg>' },
610
- { val: "square", label: "Square", icon: '<svg width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5"><rect x=".75" y=".75" width="10.5" height="10.5" rx="1.5"/></svg>' },
611
- ];
612
- for (var ai = 0; ai < aspects.length; ai++) {
613
- var ao = aspects[ai];
614
- h += '<div class="aspect-option' + (ao.val === aspectFilter ? ' selected' : '') + '" data-value="' + ao.val + '">';
615
- h += '<span class="aspect-opt-icon">' + ao.icon + '</span>';
616
- h += ao.label;
617
- h += '<svg class="aspect-check" width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><polyline points="2,6 5,9 10,3"/></svg>';
618
- h += '</div>';
619
- }
620
- h += '</div></div></div>';
621
-
622
431
  // Grid
623
432
  h += '<div class="grid" id="gallery-grid">';
624
433
  for (var i = 0; i < allResources.length; i++) {
@@ -667,8 +476,8 @@ function render() {
667
476
  if (url) {
668
477
  h += '<div class="card-actions">';
669
478
  h += '<button class="action-btn act-original" data-copy-original="' + i + '">Copy URL</button>';
670
- if (rt !== "raw") h += '<button class="action-btn act-optimized" data-copy-optimized="' + i + '">\\u2728 Optimized</button>';
671
- h += '<button class="action-btn act-download" data-download="' + i + '" title="Download">\\u2193</button>';
479
+ if (rt !== "raw") h += '<button class="action-btn act-optimized" data-copy-optimized="' + i + '">' + IC.zap + ' Optimized</button>';
480
+ h += '<button class="action-btn act-download" data-download="' + i + '" title="Download">' + IC.arrowDown + '</button>';
672
481
  h += '</div>';
673
482
  }
674
483
 
@@ -715,24 +524,24 @@ function render() {
715
524
  h += "</div>";
716
525
  }
717
526
 
718
- // Spacer so select bar doesn't cover Load More
719
- h += '<div class="select-bar-spacer" id="select-bar-spacer" style="display:none"></div>';
720
-
721
- // Multi-select bar
527
+ // Multi-select bar (sticky, in-flow wrapper so iframe sizing stays truthful)
528
+ h += '<div class="select-bar-wrap" id="select-bar-wrap">';
722
529
  h += '<div class="select-bar" id="select-bar">';
723
530
  h += '<span class="select-count" id="select-count">0 selected</span>';
724
531
  h += '<div class="bar-divider"></div>';
725
- h += '<button class="bar-btn bar-primary" id="bar-copy-optimized" style="display:none">\\u2728 Copy Optimized</button>';
532
+ h += '<button class="bar-btn bar-primary" id="bar-copy-optimized" style="display:none">' + IC.zap + ' Copy Optimized</button>';
726
533
  h += '<button class="bar-btn bar-secondary" id="bar-copy-original">Copy Original</button>';
727
- h += '<button class="bar-btn bar-secondary" id="bar-download">\\u2193 Download All</button>';
534
+ h += '<button class="bar-btn bar-secondary" id="bar-download">' + IC.arrowDown + ' Download Selected</button>';
728
535
  h += '<div class="bar-divider"></div>';
729
- h += '<button class="bar-btn bar-ghost" id="bar-clear">\\u2715</button>';
536
+ h += '<button class="bar-btn bar-ghost" id="bar-clear">' + IC.x + '</button>';
537
+ h += '</div>';
730
538
  h += '</div>';
731
539
 
732
540
  // Toast
733
541
  h += '<div class="gallery-toast" id="gallery-toast"></div>';
734
542
 
735
543
  root.innerHTML = h;
544
+ renderThemeToggle();
736
545
 
737
546
  // Re-apply selection state
738
547
  selected.forEach(function(i) {
@@ -766,37 +575,16 @@ function attachEvents() {
766
575
  _eventsAttached = true;
767
576
  var root = document.getElementById("app");
768
577
 
769
- root.addEventListener("input", function(e) {
770
- if (e.target && e.target.id === "filter-input") handleFilter();
771
- });
772
-
773
- document.addEventListener("click", function(e) {
774
- var dd = document.getElementById("aspect-dropdown");
775
- if (dd && !dd.contains(e.target)) {
776
- var menu = document.getElementById("aspect-menu");
777
- var btn = document.getElementById("aspect-btn");
778
- if (menu) menu.classList.remove("open");
779
- if (btn) btn.classList.remove("open");
780
- }
781
- });
782
-
783
578
  root.addEventListener("click", function(e) {
784
579
  var el = e.target;
785
580
  while (el && el !== root) {
786
581
  if (el.id === "load-more-btn") { loadMore(); return; }
787
582
  if (el.id === "refresh-gallery") { refreshGallery(); return; }
788
583
  if (el.id === "select-all-btn") { toggleSelectAll(); return; }
789
- if (el.id === "filter-clear") { clearFilter(); return; }
790
584
  if (el.id === "bar-copy-optimized") { copySelectedUrls("optimized"); return; }
791
585
  if (el.id === "bar-copy-original") { copySelectedUrls("original"); return; }
792
586
  if (el.id === "bar-download") { downloadSelected(); return; }
793
587
  if (el.id === "bar-clear") { clearSelection(); return; }
794
- if (el.id === "aspect-btn" || el.parentElement && el.parentElement.id === "aspect-btn") {
795
- toggleAspectMenu(e); return;
796
- }
797
- if (el.classList && el.classList.contains("aspect-option")) {
798
- selectAspect(el.getAttribute("data-value") || ""); return;
799
- }
800
588
  if (el.dataset && el.dataset.copyOriginal != null) {
801
589
  e.stopPropagation();
802
590
  copyAssetUrl("original", parseInt(el.dataset.copyOriginal, 10)); return;
@@ -957,6 +745,7 @@ function showFetchPrompt() {
957
745
  h += '<button class="prompt-btn prompt-btn-primary" id="fetch-direct-btn">Fetch Directly</button>';
958
746
  h += "</div></div>";
959
747
  root.innerHTML = h;
748
+ renderThemeToggle();
960
749
  document.getElementById("fetch-direct-btn").addEventListener("click", function() { fetchDirect(); });
961
750
  }
962
751
 
@@ -976,6 +765,9 @@ async function fetchDirect() {
976
765
  console.log(LOG_PREFIX, "fetchDirect ->", name);
977
766
 
978
767
  document.getElementById("app").innerHTML = '<div class="status">Fetching assets\\u2026</div>';
768
+ requestAnimationFrame(function() {
769
+ app.reportSize(Math.max(document.documentElement.scrollHeight, MIN_HEIGHT));
770
+ });
979
771
  try {
980
772
  var res = await app.callServerTool({ name: name, arguments: args });
981
773
  var data = ingestResult(res);
@@ -1027,6 +819,7 @@ async function loadMore() {
1027
819
  function refreshGallery() {
1028
820
  allResources = [];
1029
821
  lastCursor = null;
822
+ selected.clear();
1030
823
  fetchDirect();
1031
824
  }
1032
825
 
@@ -1034,7 +827,6 @@ function refreshGallery() {
1034
827
  document.addEventListener("keydown", function(e) {
1035
828
  if (e.key === "Escape") {
1036
829
  if (document.querySelector(".modal-overlay")) { closeModal(); return; }
1037
- if (filterQuery || aspectFilter) { clearFilter(); return; }
1038
830
  if (selected.size > 0) { clearSelection(); return; }
1039
831
  }
1040
832
  });
@@ -1064,6 +856,7 @@ ${GALLERY_CSS}
1064
856
  <div class="gallery-toast" id="gallery-toast"></div>
1065
857
 
1066
858
  <script>
859
+ ${SHARED_JS_ICONS}
1067
860
  ${SHARED_JS_MCP_CLIENT}
1068
861
  ${SHARED_JS_HELPERS}
1069
862
  ${SHARED_JS_TOOLTIPS}
@@ -1 +1 @@
1
- {"version":3,"file":"asset-gallery-app.js","sourceRoot":"","sources":["../../../src/mcp-server/apps/asset-gallery-app.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,oBAAoB,EACpB,iBAAiB,EACjB,kBAAkB,EAClB,eAAe,EACf,0BAA0B,EAC1B,sBAAsB,GACvB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAE1C,MAAM,UAAU,mBAAmB,CAAC,QAAiB;IACnD,OAAO,cAAc,CAAC,kBAAkB,EAAE,QAAQ,CAAC,CAAC;AACtD,CAAC;AAED,MAAM,WAAW,GAAG,SAAS,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+Q7B,CAAC;AAEF,MAAM,UAAU,GAAG,QAAQ,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA2vB3B,CAAC;AAEF,MAAM,kBAAkB,GAAG,UAAU,CAAC;;;;;;;EAOpC,iBAAiB;EACjB,qBAAqB;EACrB,WAAW;;;;;;;;EAQX,oBAAoB;EACpB,iBAAiB;EACjB,kBAAkB;EAClB,eAAe;EACf,0BAA0B;EAC1B,sBAAsB;EACtB,UAAU;;;QAGJ,CAAC"}
1
+ {"version":3,"file":"asset-gallery-app.js","sourceRoot":"","sources":["../../../src/mcp-server/apps/asset-gallery-app.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EACL,iBAAiB,EACjB,qBAAqB,EACrB,eAAe,EACf,oBAAoB,EACpB,iBAAiB,EACjB,kBAAkB,EAClB,eAAe,EACf,0BAA0B,EAC1B,sBAAsB,GACvB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAE1C,MAAM,UAAU,mBAAmB,CAAC,QAAiB;IACnD,OAAO,cAAc,CAAC,kBAAkB,EAAE,QAAQ,CAAC,CAAC;AACtD,CAAC;AAED,MAAM,WAAW,GAAG,SAAS,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiM7B,CAAC;AAEF,MAAM,UAAU,GAAG,QAAQ,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAynB3B,CAAC;AAEF,MAAM,kBAAkB,GAAG,UAAU,CAAC;;;;;;;EAOpC,iBAAiB;EACjB,qBAAqB;EACrB,WAAW;;;;;;;;EAQX,eAAe;EACf,oBAAoB;EACpB,iBAAiB;EACjB,kBAAkB;EAClB,eAAe;EACf,0BAA0B;EAC1B,sBAAsB;EACtB,UAAU;;;QAGJ,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"asset-upload-app.d.ts","sourceRoot":"","sources":["../../../src/mcp-server/apps/asset-upload-app.ts"],"names":[],"mappings":"AA+BA,wBAAgB,kBAAkB,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAE5D"}
1
+ {"version":3,"file":"asset-upload-app.d.ts","sourceRoot":"","sources":["../../../src/mcp-server/apps/asset-upload-app.ts"],"names":[],"mappings":"AAgCA,wBAAgB,kBAAkB,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAE5D"}