@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
package/bin/mcp-server.js CHANGED
@@ -13960,9 +13960,9 @@ var init_config = __esm(() => {
13960
13960
  SDK_METADATA = {
13961
13961
  language: "typescript",
13962
13962
  openapiDocVersion: "0.5.1",
13963
- sdkVersion: "0.9.1",
13964
- genVersion: "2.881.4",
13965
- userAgent: "speakeasy-sdk/mcp-typescript 0.9.1 2.881.4 0.5.1 @cloudinary/asset-management-mcp"
13963
+ sdkVersion: "0.9.3",
13964
+ genVersion: "2.885.1",
13965
+ userAgent: "speakeasy-sdk/mcp-typescript 0.9.3 2.885.1 0.5.1 @cloudinary/asset-management-mcp"
13966
13966
  };
13967
13967
  });
13968
13968
 
@@ -14021,7 +14021,7 @@ var init_config2 = __esm(() => {
14021
14021
  "asset-details",
14022
14022
  "asset-upload"
14023
14023
  ];
14024
- DEFAULT_MCP_APPS = [];
14024
+ DEFAULT_MCP_APPS = [...MCP_APPS];
14025
14025
  });
14026
14026
 
14027
14027
  // src/mcp-server/console-logger.ts
@@ -65624,6 +65624,7 @@ var _infoSchema, _uploadSchema, TOOLTIP_MAP_JSON, SHARED_CSS_TOKENS = `
65624
65624
  [data-theme="dark"] .status-warn, .dark .status-warn { background: #854d0e; color: #fef08a; }
65625
65625
  [data-theme="dark"] .status-err, .dark .status-err { background: #991b1b; color: #fecaca; }
65626
65626
 
65627
+ html { scrollbar-gutter: auto; }
65627
65628
  body {
65628
65629
  font-family: var(--cld-font);
65629
65630
  background: var(--cld-bg);
@@ -65632,25 +65633,47 @@ body {
65632
65633
  line-height: 1.5;
65633
65634
  font-size: var(--cld-font-xs);
65634
65635
  position: relative;
65636
+ overflow-x: hidden;
65635
65637
  }
65636
65638
  .theme-btn {
65637
- position: absolute; top: 4px; right: 4px; z-index: 900;
65638
65639
  width: 22px; height: 22px; border-radius: 50%;
65639
65640
  border: 1px solid transparent; background: transparent;
65640
65641
  color: var(--cld-text3); cursor: pointer;
65641
65642
  display: flex; align-items: center; justify-content: center;
65642
65643
  padding: 0; transition: background 0.15s, color 0.15s, border-color 0.15s;
65643
- opacity: 0.5;
65644
+ opacity: 0.5; flex-shrink: 0;
65644
65645
  }
65645
65646
  .theme-btn:hover { background: var(--cld-bg3); color: var(--cld-text); border-color: var(--cld-border); opacity: 1; }
65646
65647
  .theme-btn svg { width: 13px; height: 13px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
65648
+ /* Shared icon-button — square pill, same family as theme-btn */
65649
+ .icon-btn {
65650
+ display: inline-flex; align-items: center; justify-content: center; gap: 5px;
65651
+ background: none; border: 1px solid var(--cld-border); border-radius: var(--cld-radius-sm);
65652
+ color: var(--cld-text2); cursor: pointer; padding: 4px 8px;
65653
+ font-size: 12px; font-weight: 500; font-family: inherit; line-height: 1;
65654
+ transition: background 0.15s, color 0.15s, border-color 0.15s;
65655
+ white-space: nowrap; flex-shrink: 0;
65656
+ }
65657
+ .icon-btn:hover { background: var(--cld-bg3); color: var(--cld-text); border-color: var(--cld-border2); }
65658
+ .icon-btn svg { width: 13px; height: 13px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; flex-shrink: 0; }
65659
+ /* icon-only variant (no text label) */
65660
+ .icon-btn.icon-only { padding: 4px; width: 28px; height: 28px; }
65661
+ /* header state icon (decorative, not a button) */
65662
+ .upload-header-icon { display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
65663
+ .upload-header-icon svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
65664
+ .upload-header-icon.icon-success { color: var(--cld-success); }
65665
+ .upload-header-icon.icon-error { color: var(--cld-error); }
65666
+ .upload-header-icon.icon-warning { color: var(--cld-warning); }
65667
+ .upload-header-icon.icon-accent { color: var(--cld-accent); }
65647
65668
  `, SHARED_CSS_COMPONENTS = `
65648
65669
  .link { cursor: pointer; }
65649
65670
  .link:hover { color: var(--cld-accent); text-decoration: underline; }
65650
65671
 
65651
- /* Modal */
65672
+ /* Modal — positioned absolutely so it can be placed in the part of the
65673
+ * iframe that is currently visible in the host's viewport (since position:fixed
65674
+ * inside a tall iframe anchors to iframe-center, not the user's visible area). */
65652
65675
  .modal-overlay {
65653
- position: fixed; inset: 0;
65676
+ position: absolute; left: 0; right: 0;
65654
65677
  background: rgba(0,0,0,0.45);
65655
65678
  display: flex; align-items: center; justify-content: center;
65656
65679
  z-index: 1000; backdrop-filter: blur(3px); padding: 24px;
@@ -65894,7 +65917,8 @@ details.detail-section > summary.detail-section-title::-webkit-details-marker {
65894
65917
  }
65895
65918
  .upload-zone:hover { border-color: var(--cld-accent); background: var(--cld-accent-bg); }
65896
65919
  .upload-zone.dragover { border-color: var(--cld-accent); background: var(--cld-accent-bg); }
65897
- .upload-zone-icon { font-size: 36px; margin-bottom: 8px; color: var(--cld-text3); }
65920
+ .upload-zone-icon { margin-bottom: 8px; color: var(--cld-text3); display: flex; align-items: center; justify-content: center; }
65921
+ .upload-zone-icon svg { width: 36px; height: 36px; fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
65898
65922
  .upload-zone-text { font-size: 14px; color: var(--cld-text2); margin-bottom: 4px; }
65899
65923
  .upload-zone-hint { font-size: 12px; color: var(--cld-text3); }
65900
65924
  .upload-zone-btn {
@@ -65940,8 +65964,9 @@ details.detail-section > summary.detail-section-title::-webkit-details-marker {
65940
65964
  .upload-preview-icon {
65941
65965
  width: 56px; height: 56px; border-radius: var(--cld-radius-sm);
65942
65966
  background: var(--cld-bg3); flex-shrink: 0; display: flex;
65943
- align-items: center; justify-content: center; font-size: 24px; color: var(--cld-text3);
65967
+ align-items: center; justify-content: center; color: var(--cld-text3);
65944
65968
  }
65969
+ .upload-preview-icon svg { width: 24px; height: 24px; fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
65945
65970
  .upload-preview-info { flex: 1; min-width: 0; }
65946
65971
  .upload-preview-name { font-size: 13px; font-weight: 600; color: var(--cld-text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
65947
65972
  .upload-preview-meta { font-size: 11px; color: var(--cld-text3); margin-top: 2px; }
@@ -66090,19 +66115,15 @@ details.upload-section > .upload-form { margin: 0; padding: 10px 12px; }
66090
66115
  background: var(--cld-accent-bg); border: 1px solid var(--cld-accent);
66091
66116
  border-radius: var(--cld-radius); margin-bottom: 4px; position: relative;
66092
66117
  }
66093
- .upload-staged-icon { font-size: 24px; flex-shrink: 0; }
66118
+ .upload-staged-icon { flex-shrink: 0; display: flex; align-items: center; color: var(--cld-accent); }
66119
+ .upload-staged-icon svg { width: 24px; height: 24px; fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
66094
66120
  .upload-staged-info { flex: 1; min-width: 0; }
66095
66121
  .upload-staged-name {
66096
66122
  font-size: 13px; font-weight: 600; color: var(--cld-text);
66097
66123
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
66098
66124
  }
66099
66125
  .upload-staged-meta { font-size: 11px; color: var(--cld-text3); margin-top: 2px; }
66100
- .upload-staged-clear {
66101
- background: none; border: none; cursor: pointer; font-size: 16px;
66102
- color: var(--cld-text3); padding: 4px 6px; border-radius: var(--cld-radius-sm);
66103
- transition: color 0.15s, background 0.15s; flex-shrink: 0;
66104
- }
66105
- .upload-staged-clear:hover { color: var(--cld-error); background: rgba(206,25,13,0.08); }
66126
+ .upload-staged-clear:hover { color: var(--cld-error); border-color: var(--cld-error); background: rgba(206,25,13,0.08); }
66106
66127
 
66107
66128
  /* Upload submit button */
66108
66129
  .upload-submit {
@@ -66148,6 +66169,21 @@ details.upload-section > .upload-form { margin: 0; padding: 10px 12px; }
66148
66169
  .json-bool { color: #a9e34b; }
66149
66170
  .json-null { color: #868e96; }
66150
66171
  }
66172
+ `, SHARED_JS_ICONS = `
66173
+ var IC = {
66174
+ refresh: '<svg viewBox="0 0 24 24"><path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"/><path d="M21 3v5h-5"/><path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16"/><path d="M3 21v-5h5"/></svg>',
66175
+ chevronLeft:'<svg viewBox="0 0 24 24"><polyline points="15 18 9 12 15 6"/></svg>',
66176
+ arrowDown: '<svg viewBox="0 0 24 24"><line x1="12" y1="5" x2="12" y2="19"/><polyline points="19 12 12 19 5 12"/></svg>',
66177
+ x: '<svg viewBox="0 0 24 24"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>',
66178
+ zap: '<svg viewBox="0 0 24 24"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg>',
66179
+ uploadCloud:'<svg viewBox="0 0 24 24"><polyline points="16 16 12 12 8 16"/><line x1="12" y1="12" x2="12" y2="21"/><path d="M20.39 18.39A5 5 0 0 0 18 9h-1.26A8 8 0 1 0 3 16.3"/></svg>',
66180
+ alertTriangle:'<svg viewBox="0 0 24 24"><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>',
66181
+ folderOpen: '<svg viewBox="0 0 24 24"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/><polyline points="22 13 17 13 15 16 9 16 7 13 2 13"/></svg>',
66182
+ checkCircle:'<svg viewBox="0 0 24 24"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>',
66183
+ clock: '<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>',
66184
+ file: '<svg viewBox="0 0 24 24"><path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"/><polyline points="13 2 13 9 20 9"/></svg>',
66185
+ image: '<svg viewBox="0 0 24 24"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg>',
66186
+ };
66151
66187
  `, SHARED_JS_MCP_CLIENT = `
66152
66188
  var RPC_TIMEOUT_MS = 15000;
66153
66189
  var TOOL_CALL_TIMEOUT_MS = 30000;
@@ -66233,6 +66269,29 @@ class MCPApp {
66233
66269
  }
66234
66270
  }
66235
66271
  `, SHARED_JS_HELPERS = `
66272
+ function copyText(text) {
66273
+ if (navigator.clipboard && navigator.clipboard.writeText) {
66274
+ return navigator.clipboard.writeText(text).catch(function() { return _copyFallback(text); });
66275
+ }
66276
+ return _copyFallback(text);
66277
+ }
66278
+ function _copyFallback(text) {
66279
+ return new Promise(function(resolve, reject) {
66280
+ try {
66281
+ var ta = document.createElement("textarea");
66282
+ ta.value = text;
66283
+ ta.setAttribute("readonly", "");
66284
+ ta.style.position = "fixed"; ta.style.top = "0"; ta.style.left = "0";
66285
+ ta.style.opacity = "0"; ta.style.pointerEvents = "none";
66286
+ document.body.appendChild(ta);
66287
+ ta.select(); ta.setSelectionRange(0, text.length);
66288
+ var ok = document.execCommand("copy");
66289
+ document.body.removeChild(ta);
66290
+ if (ok) resolve(); else reject(new Error("Clipboard unavailable"));
66291
+ } catch (e) { reject(e); }
66292
+ });
66293
+ }
66294
+
66236
66295
  function fmtBytes(b) {
66237
66296
  if (!b) return "";
66238
66297
  var u = ["B","KB","MB","GB"], i = Math.min(Math.floor(Math.log(b)/Math.log(1024)), 3);
@@ -66450,6 +66509,55 @@ function closeModal() {
66450
66509
  if (ov) ov.remove();
66451
66510
  }
66452
66511
 
66512
+ /* Position the modal overlay so it's centered on the part of the iframe
66513
+ * the user is currently looking at. Inside an iframe that the host has
66514
+ * sized to scrollHeight, position: fixed anchors to iframe-center, not
66515
+ * the user's visible band — so we compute the visible band ourselves
66516
+ * via IntersectionObserver and place the overlay absolutely there. */
66517
+ function positionModalInVisibleArea(overlay) {
66518
+ if (!overlay) return;
66519
+ var docH = Math.max(document.documentElement.scrollHeight, window.innerHeight);
66520
+
66521
+ // Try IntersectionObserver first (works cross-origin too)
66522
+ if (typeof IntersectionObserver !== "undefined") {
66523
+ var probe = document.createElement("div");
66524
+ probe.style.cssText = "position:absolute;left:0;top:0;width:1px;height:" + docH + "px;pointer-events:none;visibility:hidden;";
66525
+ document.body.appendChild(probe);
66526
+ var io = new IntersectionObserver(function(entries) {
66527
+ try {
66528
+ var entry = entries[0];
66529
+ var rect = entry.intersectionRect;
66530
+ var rootRect = entry.rootBounds || entry.boundingClientRect;
66531
+ // intersectionRect.top is in viewport coords; visible band of the
66532
+ // document is from (probe.top + rect.top) to (probe.top + rect.bottom).
66533
+ var probeTop = entry.boundingClientRect.top; // viewport-relative
66534
+ var visibleTop = rect.top - probeTop; // document-relative
66535
+ var visibleHeight = rect.height;
66536
+ if (visibleHeight > 0) {
66537
+ overlay.style.top = visibleTop + "px";
66538
+ overlay.style.height = visibleHeight + "px";
66539
+ } else {
66540
+ // Fallback: probe not intersecting (shouldn't happen with full-height probe)
66541
+ overlay.style.top = (window.scrollY || 0) + "px";
66542
+ overlay.style.height = window.innerHeight + "px";
66543
+ }
66544
+ } finally {
66545
+ io.disconnect();
66546
+ if (probe.parentNode) probe.parentNode.removeChild(probe);
66547
+ }
66548
+ });
66549
+ io.observe(probe);
66550
+ return;
66551
+ }
66552
+
66553
+ // Fallback: same-origin only — read iframe's bounding rect
66554
+ var rect = document.documentElement.getBoundingClientRect();
66555
+ var visibleTop = rect.top < 0 ? -rect.top : 0;
66556
+ var visibleHeight = Math.min(window.innerHeight, rect.bottom) - Math.max(0, rect.top);
66557
+ overlay.style.top = visibleTop + "px";
66558
+ overlay.style.height = Math.max(visibleHeight, 200) + "px";
66559
+ }
66560
+
66453
66561
  function openModal(headerHtml, bodyHtml) {
66454
66562
  closeModal();
66455
66563
  var h = '<div class="modal-overlay"><div class="modal">';
@@ -66459,6 +66567,7 @@ function openModal(headerHtml, bodyHtml) {
66459
66567
  document.body.insertAdjacentHTML("beforeend", h);
66460
66568
 
66461
66569
  var overlay = document.querySelector(".modal-overlay");
66570
+ positionModalInVisibleArea(overlay);
66462
66571
  overlay.addEventListener("click", function(e) {
66463
66572
  if (e.target === overlay || e.target.classList.contains("modal-close")) {
66464
66573
  closeModal();
@@ -67456,7 +67565,16 @@ function renderThemeToggle() {
67456
67565
  applyTheme();
67457
67566
  renderThemeToggle();
67458
67567
  });
67459
- document.body.appendChild(btn);
67568
+ var slot = document.getElementById("header-actions");
67569
+ if (slot) {
67570
+ slot.appendChild(btn);
67571
+ } else {
67572
+ btn.style.position = "absolute";
67573
+ btn.style.top = "4px";
67574
+ btn.style.right = "4px";
67575
+ btn.style.zIndex = "900";
67576
+ document.body.appendChild(btn);
67577
+ }
67460
67578
  }
67461
67579
 
67462
67580
  function setupHostContext(app) {
@@ -67550,90 +67668,10 @@ var GALLERY_CSS = `
67550
67668
  font-size: var(--cld-font-xxs); color: var(--cld-text3); background: var(--cld-bg3);
67551
67669
  padding: 2px 8px; border-radius: 20px; font-weight: 500;
67552
67670
  }
67553
- .select-all-btn {
67554
- font-size: 12px; color: var(--cld-text2); background: none;
67555
- border: 1px solid var(--cld-border); border-radius: var(--cld-radius-sm);
67556
- padding: 4px 10px; cursor: pointer; font-family: inherit;
67557
- transition: color 0.15s, border-color 0.15s;
67558
- }
67559
- .select-all-btn:hover { color: var(--cld-accent); border-color: var(--cld-accent); }
67560
- .refresh-btn {
67561
- background: none; border: 1px solid var(--cld-border); border-radius: var(--cld-radius-sm);
67562
- color: var(--cld-text2); cursor: pointer; font-size: 14px; padding: 2px 7px;
67563
- line-height: 1; transition: background 0.15s, color 0.15s;
67564
- }
67565
- .refresh-btn:hover { background: var(--cld-bg3); color: var(--cld-text); }
67566
-
67567
- /* ── Filter bar ── */
67568
- .filter-row {
67569
- margin-bottom: var(--cld-sp-md); display: flex; gap: 8px; align-items: center;
67570
- }
67571
- .filter-text-wrap { position: relative; flex: 1; }
67572
- .filter-input {
67573
- width: 100%; height: 36px; padding: 0 12px 0 34px;
67574
- border: 1px solid var(--cld-border); border-radius: var(--cld-radius);
67575
- background: var(--cld-bg); font-size: 12.5px; color: var(--cld-text);
67576
- outline: none; font-family: inherit;
67577
- transition: border-color 0.18s, box-shadow 0.18s;
67578
- }
67579
- .filter-input::placeholder { color: var(--cld-text3); }
67580
- .filter-input:focus {
67581
- border-color: var(--cld-accent);
67582
- box-shadow: 0 0 0 3px rgba(52,72,197,0.1);
67583
- }
67584
- [data-theme="dark"] .filter-input:focus { box-shadow: 0 0 0 3px rgba(13,154,255,0.15); }
67585
- .filter-icon {
67586
- position: absolute; left: 11px; top: 50%; transform: translateY(-50%);
67587
- color: var(--cld-text3); pointer-events: none; display: flex; align-items: center;
67588
- }
67589
- .filter-clear {
67590
- position: absolute; right: 10px; top: 50%; transform: translateY(-50%);
67591
- background: none; border: none; color: var(--cld-text3); cursor: pointer;
67592
- font-size: 14px; line-height: 1; padding: 2px 4px; border-radius: 4px;
67593
- display: none; font-family: inherit;
67594
- }
67595
- .filter-clear:hover { color: var(--cld-text); background: var(--cld-border); }
67596
- .filter-clear.visible { display: block; }
67597
-
67598
- /* Aspect-ratio dropdown */
67599
- .aspect-dropdown { position: relative; flex-shrink: 0; user-select: none; }
67600
- .aspect-btn {
67601
- height: 36px; padding: 0 10px; border: 1px solid var(--cld-border);
67602
- border-radius: var(--cld-radius); background: var(--cld-bg);
67603
- font-size: 12.5px; color: var(--cld-text); cursor: pointer;
67604
- display: flex; align-items: center; gap: 6px; white-space: nowrap;
67605
- transition: border-color 0.18s, box-shadow 0.18s, background 0.18s;
67606
- font-family: inherit; outline: none;
67607
- }
67608
- .aspect-btn:hover { border-color: var(--cld-border2); }
67609
- .aspect-btn.active {
67610
- border-color: var(--cld-accent); background: var(--cld-accent-bg);
67611
- color: var(--cld-accent); font-weight: 600;
67612
- }
67613
- .aspect-btn-chevron { color: var(--cld-text3); flex-shrink: 0; transition: transform 0.18s; }
67614
- .aspect-btn.open .aspect-btn-chevron { transform: rotate(180deg); }
67615
- .aspect-menu {
67616
- position: absolute; top: calc(100% + 6px); right: 0;
67617
- background: var(--cld-bg); border: 1px solid var(--cld-border);
67618
- border-radius: 10px; box-shadow: var(--cld-shadow-md);
67619
- padding: 4px; min-width: 160px; z-index: 50; display: none;
67620
- }
67621
- .aspect-menu.open { display: block; }
67622
- .aspect-option {
67623
- display: flex; align-items: center; gap: 10px;
67624
- padding: 7px 10px; border-radius: 6px; font-size: 12.5px;
67625
- color: var(--cld-text); cursor: pointer; transition: background 0.18s;
67626
- }
67627
- .aspect-option:hover { background: var(--cld-bg3); }
67628
- .aspect-option.selected { color: var(--cld-accent); font-weight: 600; }
67629
- .aspect-opt-icon { color: var(--cld-text3); display: flex; align-items: center; flex-shrink: 0; }
67630
- .aspect-option.selected .aspect-opt-icon { color: var(--cld-accent); }
67631
- .aspect-check { margin-left: auto; color: var(--cld-accent); opacity: 0; }
67632
- .aspect-option.selected .aspect-check { opacity: 1; }
67633
- .no-results {
67634
- grid-column: 1 / -1; padding: 60px 20px;
67635
- text-align: center; color: var(--cld-text3); font-size: 13px;
67636
- }
67671
+ /* action-btn svg sizing */
67672
+ .action-btn svg { width: 12px; height: 12px; fill: none; stroke: currentColor; stroke-width: 2.5; stroke-linecap: round; stroke-linejoin: round; flex-shrink: 0; }
67673
+ /* select bar svg */
67674
+ .bar-btn svg { width: 13px; height: 13px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; flex-shrink: 0; }
67637
67675
 
67638
67676
  /* ── Grid ── */
67639
67677
  .grid {
@@ -67692,18 +67730,11 @@ var GALLERY_CSS = `
67692
67730
  opacity: 0; transition: opacity 0.18s; z-index: 4; pointer-events: none;
67693
67731
  }
67694
67732
  .card:hover .tags-overlay { opacity: 1; }
67695
- .grid.filtering .tags-overlay { opacity: 1; }
67696
67733
  .tag-overlay {
67697
67734
  font-size: 10px; color: white;
67698
67735
  background: rgba(10, 12, 18, 0.55); padding: 2px 7px; border-radius: 20px;
67699
67736
  backdrop-filter: blur(6px); font-weight: 600; letter-spacing: 0.02em;
67700
67737
  }
67701
- .tag-overlay.tag-match { background: rgba(52, 72, 197, 0.82); }
67702
- .tag-overlay mark {
67703
- background: rgba(255, 213, 79, 0.5); color: white;
67704
- border-radius: 2px; padding: 0 1px;
67705
- }
67706
-
67707
67738
  /* Floating action buttons */
67708
67739
  .card-actions {
67709
67740
  position: absolute; bottom: 10px; left: 0; right: 0;
@@ -67767,18 +67798,27 @@ var GALLERY_CSS = `
67767
67798
  }
67768
67799
 
67769
67800
  /* ── Multi-select bar ── */
67801
+ /* Wrapper takes layout space ONLY when a selection is active. When idle
67802
+ * it's display:none so it contributes 0 to scrollHeight (no phantom
67803
+ * scrollbar gutter / no extra iframe height). */
67804
+ .select-bar-wrap {
67805
+ display: none;
67806
+ position: sticky; bottom: 0; left: 0; right: 0;
67807
+ justify-content: center; pointer-events: none;
67808
+ z-index: 100; height: 64px;
67809
+ }
67810
+ .select-bar-wrap.visible { display: flex; }
67770
67811
  .select-bar {
67771
- position: fixed; bottom: 16px; left: 50%;
67772
- transform: translateX(-50%) translateY(80px);
67812
+ position: absolute; bottom: 8px;
67813
+ transform: translateY(80px);
67773
67814
  background: #1a1d24; color: white; border-radius: 40px;
67774
67815
  padding: 0 6px 0 16px; height: 48px;
67775
67816
  display: flex; align-items: center; gap: 4px;
67776
67817
  box-shadow: 0 8px 32px rgba(0,0,0,0.4);
67777
67818
  transition: transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1), opacity 0.2s;
67778
- opacity: 0; pointer-events: none; z-index: 100; white-space: nowrap;
67819
+ opacity: 0; pointer-events: none; white-space: nowrap;
67779
67820
  }
67780
- .select-bar.visible { transform: translateX(-50%) translateY(0); opacity: 1; pointer-events: all; }
67781
- .select-bar-spacer { height: 72px; }
67821
+ .select-bar-wrap.visible .select-bar { transform: translateY(0); opacity: 1; pointer-events: all; }
67782
67822
  .select-count { font-size: 13px; font-weight: 600; margin-right: 8px; }
67783
67823
  .bar-btn {
67784
67824
  height: 36px; padding: 0 14px; border: none; border-radius: 30px;
@@ -67817,8 +67857,6 @@ var pendingCall = {
67817
67857
  args: null,
67818
67858
  };
67819
67859
  var selected = new Set();
67820
- var filterQuery = "";
67821
- var aspectFilter = "";
67822
67860
  var app = new MCPApp({ name: "Cloudinary Asset Gallery", version: "1.0.0" });
67823
67861
  setupHostContext(app);
67824
67862
 
@@ -67842,33 +67880,13 @@ function showToast(msg) {
67842
67880
  _toastTimer = setTimeout(function() { t.classList.remove("show"); }, 2000);
67843
67881
  }
67844
67882
 
67845
- function getAspect(r) {
67846
- if (!r.width || !r.height) return "";
67847
- var ratio = r.width / r.height;
67848
- if (ratio > 1.1) return "landscape";
67849
- if (ratio < 0.9) return "portrait";
67850
- return "square";
67851
- }
67852
-
67853
- function highlightText(text, query) {
67854
- if (!query) return esc(text);
67855
- var lo = text.toLowerCase();
67856
- var idx = lo.indexOf(query);
67857
- if (idx === -1) return esc(text);
67858
- return esc(text.slice(0, idx))
67859
- + "<mark>" + esc(text.slice(idx, idx + query.length)) + "</mark>"
67860
- + esc(text.slice(idx + query.length));
67861
- }
67862
-
67863
67883
  function updateSelectBar() {
67864
- var bar = document.getElementById("select-bar");
67884
+ var wrap = document.getElementById("select-bar-wrap");
67865
67885
  var countEl = document.getElementById("select-count");
67866
- if (!bar || !countEl) return;
67886
+ if (!wrap || !countEl) return;
67867
67887
  var n = selected.size;
67868
67888
  countEl.textContent = n + " selected";
67869
- bar.classList.toggle("visible", n > 0);
67870
- var spacer = document.getElementById("select-bar-spacer");
67871
- if (spacer) spacer.style.display = n > 0 ? "" : "none";
67889
+ wrap.classList.toggle("visible", n > 0);
67872
67890
  var btn = document.getElementById("select-all-btn");
67873
67891
  if (btn) {
67874
67892
  var visible = getVisibleIndices();
@@ -67935,11 +67953,9 @@ function copyAssetUrl(type, idx) {
67935
67953
  var url = r.secure_url || r.url || "";
67936
67954
  var copyUrl = type === "optimized" ? optimizedUrl(url, r) : url;
67937
67955
  if (!copyUrl) return;
67938
- try {
67939
- navigator.clipboard.writeText(copyUrl).then(function() {
67940
- showToast(type === "optimized" ? "\\u2728 Optimized URL copied" : "URL copied");
67941
- });
67942
- } catch(e) { showError("Copy Failed", String(e)); }
67956
+ copyText(copyUrl).then(function() {
67957
+ showToast(type === "optimized" ? "\\u2728 Optimized URL copied" : "URL copied");
67958
+ }).catch(function(e) { showError("Copy Failed", e && e.message ? e.message : String(e)); });
67943
67959
  }
67944
67960
 
67945
67961
  function downloadOne(idx) {
@@ -67961,134 +67977,74 @@ function copySelectedUrls(type) {
67961
67977
  urls.push(type === "optimized" ? optimizedUrl(url, r) : url);
67962
67978
  });
67963
67979
  if (!urls.length) return;
67964
- try {
67965
- navigator.clipboard.writeText(urls.join("\\n")).then(function() {
67966
- showToast(urls.length + " " + (type === "optimized" ? "optimized " : "") + "URLs copied");
67967
- });
67968
- } catch(e) { showError("Copy Failed", String(e)); }
67980
+ copyText(urls.join("\\n")).then(function() {
67981
+ showToast(urls.length + " " + (type === "optimized" ? "optimized " : "") + "URLs copied");
67982
+ }).catch(function(e) { showError("Copy Failed", e && e.message ? e.message : String(e)); });
67969
67983
  }
67970
67984
 
67971
- function downloadSelected() {
67972
- var count = 0;
67985
+ async function downloadSelected() {
67986
+ if (selected.size === 0) return;
67987
+
67988
+ var picks = [];
67973
67989
  selected.forEach(function(i) {
67974
67990
  var r = allResources[i];
67975
- if (!r) return;
67976
- var url = r.secure_url || r.url || "";
67977
- var dl = downloadUrl(url, r);
67978
- if (dl) { app._rpc("ui/open-link", { url: dl }); count++; }
67991
+ if (r && r.public_id) picks.push(r);
67979
67992
  });
67980
- if (count) showToast("Downloading " + count + " asset" + (count > 1 ? "s" : ""));
67981
- }
67993
+ if (!picks.length) return;
67982
67994
 
67983
- function handleFilter() {
67984
- var input = document.getElementById("filter-input");
67985
- filterQuery = input ? input.value.trim().toLowerCase() : "";
67986
-
67987
- var clearBtn = document.getElementById("filter-clear");
67988
- if (clearBtn) clearBtn.classList.toggle("visible", filterQuery.length > 0);
67995
+ var requestBody = {
67996
+ mode: "create",
67997
+ target_format: "zip",
67998
+ keep_derived: true,
67999
+ target_public_id: "mcp-gallery-archive-" + Date.now(),
68000
+ fully_qualified_public_ids: picks.map(function(r) {
68001
+ return (r.resource_type || "image") + "/" + (r.type || "upload") + "/" + r.public_id;
68002
+ }),
68003
+ };
67989
68004
 
67990
- var aspectBtn = document.getElementById("aspect-btn");
67991
- if (aspectBtn) aspectBtn.classList.toggle("active", aspectFilter !== "");
68005
+ var btn = document.getElementById("bar-download");
68006
+ var origLabel = btn ? btn.innerHTML : "";
68007
+ if (btn) { btn.innerHTML = "Creating archive\\u2026"; btn.disabled = true; }
67992
68008
 
67993
- var anyFilter = filterQuery.length > 0 || aspectFilter !== "";
67994
- var grid = document.getElementById("gallery-grid");
67995
- if (grid) grid.classList.toggle("filtering", anyFilter);
68009
+ try {
68010
+ var res = await app.callServerTool({
68011
+ name: "generate-archive",
68012
+ arguments: {
68013
+ resource_type: "all",
68014
+ RequestBody: requestBody,
68015
+ },
68016
+ });
67996
68017
 
67997
- var visibleCount = 0;
67998
- for (var i = 0; i < allResources.length; i++) {
67999
- var r = allResources[i];
68000
- var card = document.getElementById("card-" + i);
68001
- if (!card) continue;
68018
+ var data = ingestResult(res);
68019
+ if (data && (data._error || data._parseError)) {
68020
+ showError("Archive Failed", unwrapApiError(data._message));
68021
+ return;
68022
+ }
68023
+ var archiveUrl = data && (data.secure_url || data.url);
68024
+ if (!archiveUrl) {
68025
+ showError("Archive Failed", "No delivery URL returned.");
68026
+ return;
68027
+ }
68028
+ try { await copyText(archiveUrl); } catch (e) { /* ignore */ }
68029
+ app._rpc("ui/open-link", { url: archiveUrl });
68030
+ showToast("Archive saved as raw in Cloudinary \\u2014 opening URL (" + picks.length + " asset" + (picks.length > 1 ? "s" : "") + ")");
68031
+ } catch (e) {
68032
+ showError("Archive Failed", unwrapApiError(e && e.message ? e.message : String(e)));
68033
+ } finally {
68034
+ if (btn) { btn.innerHTML = origLabel; btn.disabled = false; }
68035
+ }
68036
+ }
68002
68037
 
68003
- var name = (r.public_id || r.filename || "").toLowerCase();
68004
- var tags = r.tags || [];
68005
- var textMatch = !filterQuery
68006
- || name.indexOf(filterQuery) !== -1
68007
- || tags.some(function(t) { return t.toLowerCase().indexOf(filterQuery) !== -1; });
68008
-
68009
- var aspectMatch = !aspectFilter || getAspect(r) === aspectFilter;
68010
- var match = textMatch && aspectMatch;
68011
- card.style.display = match ? "" : "none";
68012
-
68013
- var tagsEl = document.getElementById("tags-overlay-" + i);
68014
- if (tagsEl && tags.length) {
68015
- var maxOv = 3;
68016
- var matchedTags = [];
68017
- var otherTags = [];
68018
- for (var ti = 0; ti < tags.length; ti++) {
68019
- var isMatch = filterQuery && tags[ti].toLowerCase().indexOf(filterQuery) !== -1;
68020
- if (isMatch) matchedTags.push(tags[ti]);
68021
- else otherTags.push(tags[ti]);
68022
- }
68023
- var shown = matchedTags.slice();
68024
- var remaining = maxOv - shown.length;
68025
- if (remaining > 0) shown = shown.concat(otherTags.slice(0, remaining));
68026
- var hidden = tags.length - shown.length;
68027
- var hiddenTags = tags.filter(function(t) { return shown.indexOf(t) === -1; });
68028
- tagsEl.innerHTML = shown.map(function(t) {
68029
- var matched = filterQuery && t.toLowerCase().indexOf(filterQuery) !== -1;
68030
- return '<span class="tag-overlay' + (matched ? ' tag-match' : '') + '">' + highlightText(t, filterQuery) + '</span>';
68031
- }).join("") + (hidden > 0 ? '<span class="tag-overlay" title="' + esc(hiddenTags.join(", ")) + '">+' + hidden + '</span>' : '');
68032
- }
68033
-
68034
- if (match) visibleCount++;
68035
- }
68036
-
68037
- var badge = document.getElementById("count-badge");
68038
- if (badge) {
68039
- badge.textContent = anyFilter
68040
- ? visibleCount + " of " + allResources.length
68041
- : allResources.length + (lastCursor ? "+" : "") + " items";
68042
- }
68043
-
68044
- var noRes = document.getElementById("no-results");
68045
- if (visibleCount === 0 && anyFilter) {
68046
- if (!noRes && grid) {
68047
- noRes = document.createElement("div");
68048
- noRes.id = "no-results";
68049
- noRes.className = "no-results";
68050
- grid.appendChild(noRes);
68051
- }
68052
- if (noRes) noRes.textContent = "No results" + (filterQuery ? ' for "' + filterQuery + '"' : "") + (aspectFilter ? " in " + aspectFilter + " images" : "");
68053
- } else if (noRes) {
68054
- noRes.remove();
68055
- }
68056
- }
68057
-
68058
- function clearFilter() {
68059
- var input = document.getElementById("filter-input");
68060
- if (input) input.value = "";
68061
- aspectFilter = "";
68062
- var label = document.getElementById("aspect-btn-label");
68063
- if (label) label.textContent = "All orientations";
68064
- document.querySelectorAll(".aspect-option").forEach(function(o) {
68065
- o.classList.toggle("selected", o.getAttribute("data-value") === "");
68066
- });
68067
- handleFilter();
68068
- }
68069
-
68070
- function toggleAspectMenu(e) {
68071
- e.stopPropagation();
68072
- var btn = document.getElementById("aspect-btn");
68073
- var menu = document.getElementById("aspect-menu");
68074
- if (!btn || !menu) return;
68075
- var open = menu.classList.toggle("open");
68076
- btn.classList.toggle("open", open);
68077
- }
68078
-
68079
- function selectAspect(val) {
68080
- aspectFilter = val;
68081
- var labels = { "": "All orientations", landscape: "Landscape", portrait: "Portrait", square: "Square" };
68082
- var label = document.getElementById("aspect-btn-label");
68083
- if (label) label.textContent = labels[aspectFilter] || "All orientations";
68084
- document.querySelectorAll(".aspect-option").forEach(function(o) {
68085
- o.classList.toggle("selected", o.getAttribute("data-value") === aspectFilter);
68086
- });
68087
- var menu = document.getElementById("aspect-menu");
68088
- var btn = document.getElementById("aspect-btn");
68089
- if (menu) menu.classList.remove("open");
68090
- if (btn) btn.classList.remove("open");
68091
- handleFilter();
68038
+ function unwrapApiError(raw) {
68039
+ if (!raw) return "Unknown error.";
68040
+ var msg = String(raw);
68041
+ try {
68042
+ if (msg.charAt(0) === "{") {
68043
+ var parsed = JSON.parse(msg);
68044
+ msg = (parsed && parsed.error && parsed.error.message) || msg;
68045
+ }
68046
+ } catch (e) { /* keep raw */ }
68047
+ return msg;
68092
68048
  }
68093
68049
 
68094
68050
  function render() {
@@ -68108,41 +68064,12 @@ function render() {
68108
68064
  h += '<h1>Results</h1>';
68109
68065
  h += '<span class="count-badge" id="count-badge">' + allResources.length + (lastCursor ? "+" : "") + ' items</span>';
68110
68066
  h += '</div>';
68111
- h += '<div style="display:flex;align-items:center;gap:8px">';
68112
- h += '<button class="select-all-btn" id="select-all-btn">Select all</button>';
68113
- h += '<button class="refresh-btn" id="refresh-gallery" title="Refresh">\\u21BB</button>';
68067
+ h += '<div id="header-actions" style="display:flex;align-items:center;gap:8px">';
68068
+ h += '<button class="icon-btn" id="select-all-btn">Select all</button>';
68069
+ h += '<button class="icon-btn icon-only" id="refresh-gallery" title="Refresh">' + IC.refresh + '</button>';
68114
68070
  h += '</div>';
68115
68071
  h += '</div>';
68116
68072
 
68117
- // Filter bar
68118
- h += '<div class="filter-row">';
68119
- h += '<div class="filter-text-wrap">';
68120
- 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>';
68121
- h += '<input class="filter-input" id="filter-input" type="text" placeholder="Filter by filename or tag\\u2026" autocomplete="off" spellcheck="false">';
68122
- h += '<button class="filter-clear" id="filter-clear">\\u2715</button>';
68123
- h += '</div>';
68124
- h += '<div class="aspect-dropdown" id="aspect-dropdown">';
68125
- h += '<button class="aspect-btn" id="aspect-btn">';
68126
- h += '<span id="aspect-btn-label">All orientations</span>';
68127
- 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>';
68128
- h += '</button>';
68129
- h += '<div class="aspect-menu" id="aspect-menu">';
68130
- var aspects = [
68131
- { 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>' },
68132
- { 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>' },
68133
- { 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>' },
68134
- { 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>' },
68135
- ];
68136
- for (var ai = 0; ai < aspects.length; ai++) {
68137
- var ao = aspects[ai];
68138
- h += '<div class="aspect-option' + (ao.val === aspectFilter ? ' selected' : '') + '" data-value="' + ao.val + '">';
68139
- h += '<span class="aspect-opt-icon">' + ao.icon + '</span>';
68140
- h += ao.label;
68141
- 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>';
68142
- h += '</div>';
68143
- }
68144
- h += '</div></div></div>';
68145
-
68146
68073
  // Grid
68147
68074
  h += '<div class="grid" id="gallery-grid">';
68148
68075
  for (var i = 0; i < allResources.length; i++) {
@@ -68191,8 +68118,8 @@ function render() {
68191
68118
  if (url) {
68192
68119
  h += '<div class="card-actions">';
68193
68120
  h += '<button class="action-btn act-original" data-copy-original="' + i + '">Copy URL</button>';
68194
- if (rt !== "raw") h += '<button class="action-btn act-optimized" data-copy-optimized="' + i + '">\\u2728 Optimized</button>';
68195
- h += '<button class="action-btn act-download" data-download="' + i + '" title="Download">\\u2193</button>';
68121
+ if (rt !== "raw") h += '<button class="action-btn act-optimized" data-copy-optimized="' + i + '">' + IC.zap + ' Optimized</button>';
68122
+ h += '<button class="action-btn act-download" data-download="' + i + '" title="Download">' + IC.arrowDown + '</button>';
68196
68123
  h += '</div>';
68197
68124
  }
68198
68125
 
@@ -68239,24 +68166,24 @@ function render() {
68239
68166
  h += "</div>";
68240
68167
  }
68241
68168
 
68242
- // Spacer so select bar doesn't cover Load More
68243
- h += '<div class="select-bar-spacer" id="select-bar-spacer" style="display:none"></div>';
68244
-
68245
- // Multi-select bar
68169
+ // Multi-select bar (sticky, in-flow wrapper so iframe sizing stays truthful)
68170
+ h += '<div class="select-bar-wrap" id="select-bar-wrap">';
68246
68171
  h += '<div class="select-bar" id="select-bar">';
68247
68172
  h += '<span class="select-count" id="select-count">0 selected</span>';
68248
68173
  h += '<div class="bar-divider"></div>';
68249
- h += '<button class="bar-btn bar-primary" id="bar-copy-optimized" style="display:none">\\u2728 Copy Optimized</button>';
68174
+ h += '<button class="bar-btn bar-primary" id="bar-copy-optimized" style="display:none">' + IC.zap + ' Copy Optimized</button>';
68250
68175
  h += '<button class="bar-btn bar-secondary" id="bar-copy-original">Copy Original</button>';
68251
- h += '<button class="bar-btn bar-secondary" id="bar-download">\\u2193 Download All</button>';
68176
+ h += '<button class="bar-btn bar-secondary" id="bar-download">' + IC.arrowDown + ' Download Selected</button>';
68252
68177
  h += '<div class="bar-divider"></div>';
68253
- h += '<button class="bar-btn bar-ghost" id="bar-clear">\\u2715</button>';
68178
+ h += '<button class="bar-btn bar-ghost" id="bar-clear">' + IC.x + '</button>';
68179
+ h += '</div>';
68254
68180
  h += '</div>';
68255
68181
 
68256
68182
  // Toast
68257
68183
  h += '<div class="gallery-toast" id="gallery-toast"></div>';
68258
68184
 
68259
68185
  root.innerHTML = h;
68186
+ renderThemeToggle();
68260
68187
 
68261
68188
  // Re-apply selection state
68262
68189
  selected.forEach(function(i) {
@@ -68290,37 +68217,16 @@ function attachEvents() {
68290
68217
  _eventsAttached = true;
68291
68218
  var root = document.getElementById("app");
68292
68219
 
68293
- root.addEventListener("input", function(e) {
68294
- if (e.target && e.target.id === "filter-input") handleFilter();
68295
- });
68296
-
68297
- document.addEventListener("click", function(e) {
68298
- var dd = document.getElementById("aspect-dropdown");
68299
- if (dd && !dd.contains(e.target)) {
68300
- var menu = document.getElementById("aspect-menu");
68301
- var btn = document.getElementById("aspect-btn");
68302
- if (menu) menu.classList.remove("open");
68303
- if (btn) btn.classList.remove("open");
68304
- }
68305
- });
68306
-
68307
68220
  root.addEventListener("click", function(e) {
68308
68221
  var el = e.target;
68309
68222
  while (el && el !== root) {
68310
68223
  if (el.id === "load-more-btn") { loadMore(); return; }
68311
68224
  if (el.id === "refresh-gallery") { refreshGallery(); return; }
68312
68225
  if (el.id === "select-all-btn") { toggleSelectAll(); return; }
68313
- if (el.id === "filter-clear") { clearFilter(); return; }
68314
68226
  if (el.id === "bar-copy-optimized") { copySelectedUrls("optimized"); return; }
68315
68227
  if (el.id === "bar-copy-original") { copySelectedUrls("original"); return; }
68316
68228
  if (el.id === "bar-download") { downloadSelected(); return; }
68317
68229
  if (el.id === "bar-clear") { clearSelection(); return; }
68318
- if (el.id === "aspect-btn" || el.parentElement && el.parentElement.id === "aspect-btn") {
68319
- toggleAspectMenu(e); return;
68320
- }
68321
- if (el.classList && el.classList.contains("aspect-option")) {
68322
- selectAspect(el.getAttribute("data-value") || ""); return;
68323
- }
68324
68230
  if (el.dataset && el.dataset.copyOriginal != null) {
68325
68231
  e.stopPropagation();
68326
68232
  copyAssetUrl("original", parseInt(el.dataset.copyOriginal, 10)); return;
@@ -68481,6 +68387,7 @@ function showFetchPrompt() {
68481
68387
  h += '<button class="prompt-btn prompt-btn-primary" id="fetch-direct-btn">Fetch Directly</button>';
68482
68388
  h += "</div></div>";
68483
68389
  root.innerHTML = h;
68390
+ renderThemeToggle();
68484
68391
  document.getElementById("fetch-direct-btn").addEventListener("click", function() { fetchDirect(); });
68485
68392
  }
68486
68393
 
@@ -68500,6 +68407,9 @@ async function fetchDirect() {
68500
68407
  console.log(LOG_PREFIX, "fetchDirect ->", name);
68501
68408
 
68502
68409
  document.getElementById("app").innerHTML = '<div class="status">Fetching assets\\u2026</div>';
68410
+ requestAnimationFrame(function() {
68411
+ app.reportSize(Math.max(document.documentElement.scrollHeight, MIN_HEIGHT));
68412
+ });
68503
68413
  try {
68504
68414
  var res = await app.callServerTool({ name: name, arguments: args });
68505
68415
  var data = ingestResult(res);
@@ -68551,6 +68461,7 @@ async function loadMore() {
68551
68461
  function refreshGallery() {
68552
68462
  allResources = [];
68553
68463
  lastCursor = null;
68464
+ selected.clear();
68554
68465
  fetchDirect();
68555
68466
  }
68556
68467
 
@@ -68558,7 +68469,6 @@ function refreshGallery() {
68558
68469
  document.addEventListener("keydown", function(e) {
68559
68470
  if (e.key === "Escape") {
68560
68471
  if (document.querySelector(".modal-overlay")) { closeModal(); return; }
68561
- if (filterQuery || aspectFilter) { clearFilter(); return; }
68562
68472
  if (selected.size > 0) { clearSelection(); return; }
68563
68473
  }
68564
68474
  });
@@ -68590,6 +68500,7 @@ ${GALLERY_CSS}
68590
68500
  <div class="gallery-toast" id="gallery-toast"></div>
68591
68501
 
68592
68502
  <script>
68503
+ ${SHARED_JS_ICONS}
68593
68504
  ${SHARED_JS_MCP_CLIENT}
68594
68505
  ${SHARED_JS_HELPERS}
68595
68506
  ${SHARED_JS_TOOLTIPS}
@@ -68643,16 +68554,6 @@ var ASSET_DETAILS_CSS = `
68643
68554
  padding: 2px 7px; border-radius: 4px; border: 1px solid var(--cld-border);
68644
68555
  }
68645
68556
 
68646
- .open-link {
68647
- padding: 6px 14px; border-radius: var(--cld-radius-sm);
68648
- font-size: 12px; font-weight: 500; cursor: pointer;
68649
- border: 1px solid var(--cld-accent); background: transparent;
68650
- color: var(--cld-accent); font-family: inherit;
68651
- transition: background 0.15s;
68652
- white-space: nowrap; flex-shrink: 0;
68653
- }
68654
- .open-link:hover { background: var(--cld-accent-bg); }
68655
-
68656
68557
  .hero-container {
68657
68558
  position: relative; margin-bottom: var(--cld-sp-md);
68658
68559
  border-radius: var(--cld-radius); overflow: hidden;
@@ -68713,9 +68614,9 @@ function renderPage(r) {
68713
68614
  if (dur) h += '<span class="pill">' + dur + "</span>";
68714
68615
  if (size) h += '<span class="pill">' + size + "</span>";
68715
68616
  h += "</div></div>";
68716
- h += '<div style="display:flex;gap:6px;flex-shrink:0">';
68717
- h += '<button class="open-link" id="refresh-asset" title="Refresh">\\u21BB</button>';
68718
- if (url) h += '<button class="open-link" id="open-asset">Open</button>';
68617
+ h += '<div id="header-actions" style="display:flex;gap:8px;flex-shrink:0;align-items:center">';
68618
+ if (url) h += '<button class="icon-btn" id="open-asset">Open</button>';
68619
+ h += '<button class="icon-btn icon-only" id="refresh-asset" title="Refresh">' + IC.refresh + '</button>';
68719
68620
  h += "</div>";
68720
68621
  h += "</div>";
68721
68622
 
@@ -68757,6 +68658,7 @@ function renderPage(r) {
68757
68658
  h += "</div>";
68758
68659
 
68759
68660
  root.innerHTML = h;
68661
+ renderThemeToggle();
68760
68662
 
68761
68663
  // Event delegation
68762
68664
  root.addEventListener("click", function handler(e) {
@@ -68802,6 +68704,7 @@ function showFetchPrompt() {
68802
68704
  h += '<button class="prompt-btn prompt-btn-primary" id="fetch-direct-btn">Fetch Directly</button>';
68803
68705
  h += "</div></div>";
68804
68706
  root.innerHTML = h;
68707
+ renderThemeToggle();
68805
68708
  document.getElementById("fetch-direct-btn").addEventListener("click", function() { fetchDirect(); });
68806
68709
  }
68807
68710
 
@@ -68883,6 +68786,7 @@ ${ASSET_DETAILS_CSS}
68883
68786
  <div id="app"><div class="status">Loading asset details&hellip;</div></div>
68884
68787
 
68885
68788
  <script>
68789
+ ${SHARED_JS_ICONS}
68886
68790
  ${SHARED_JS_MCP_CLIENT}
68887
68791
  ${SHARED_JS_HELPERS}
68888
68792
  ${SHARED_JS_TOOLTIPS}
@@ -69122,15 +69026,8 @@ var uploadJsonSchema, rtJsonSchema, allSchemaProperties, rtCopy, SCHEMA_JSON, UP
69122
69026
  .upload-header h1 {
69123
69027
  font-size: var(--cld-font-sm); font-weight: 600; color: var(--cld-text);
69124
69028
  }
69125
- .upload-header-icon { font-size: 20px; }
69126
- .upload-header { position: relative; }
69127
- .back-link {
69128
- position: absolute; top: -2px; right: 0;
69129
- background: none; border: none; cursor: pointer;
69130
- color: var(--cld-accent); font-size: var(--cld-font-xs);
69131
- padding: 2px 6px; border-radius: 4px;
69132
- }
69133
- .back-link:hover { text-decoration: underline; background: var(--cld-accent-bg); }
69029
+ .upload-header-icon { display: flex; align-items: center; justify-content: center; }
69030
+ .upload-header-icon svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
69134
69031
 
69135
69032
  .upload-result .detail-section { padding: 14px 16px; }
69136
69033
  .upload-result .detail-section:first-child { padding-top: 0; }
@@ -69581,16 +69478,18 @@ function renderPicker() {
69581
69478
  var h = "";
69582
69479
 
69583
69480
  h += '<div class="upload-header">';
69481
+ h += '<span class="upload-header-icon icon-accent">' + IC.uploadCloud + '</span>';
69482
+ h += "<h1>Upload to Cloudinary</h1>";
69483
+ h += '<div id="header-actions" style="display:flex;align-items:center;gap:6px;margin-left:auto">';
69584
69484
  if (lastResult) {
69585
- h += '<button class="back-link" id="back-to-result-btn">\\u2190 Back to Result</button>';
69485
+ h += '<button class="icon-btn" id="back-to-result-btn">' + IC.chevronLeft + ' Back</button>';
69586
69486
  }
69587
- h += '<span class="upload-header-icon">\\u2B06\\uFE0F</span>';
69588
- h += "<h1>Upload to Cloudinary</h1>";
69487
+ h += '</div>';
69589
69488
  h += "</div>";
69590
69489
 
69591
69490
  if (stagedFile) {
69592
69491
  h += '<div class="upload-staged">';
69593
- h += '<div class="upload-staged-icon">\\u{1F4C4}</div>';
69492
+ h += '<div class="upload-staged-icon">' + IC.file + '</div>';
69594
69493
  h += '<div class="upload-staged-info">';
69595
69494
  h += '<div class="upload-staged-name">' + esc(stagedFile.name) + "</div>";
69596
69495
  if (stagedFile.size) {
@@ -69601,11 +69500,11 @@ function renderPicker() {
69601
69500
  h += '<div class="upload-staged-meta">Remote URL</div>';
69602
69501
  }
69603
69502
  h += "</div>";
69604
- h += '<button class="upload-staged-clear" id="clear-staged-btn" title="Remove">\\u2715</button>';
69503
+ h += '<button class="upload-staged-clear icon-btn icon-only" id="clear-staged-btn" title="Remove">' + IC.x + '</button>';
69605
69504
  h += "</div>";
69606
69505
  } else {
69607
69506
  h += '<div class="upload-zone" id="drop-zone">';
69608
- h += '<div class="upload-zone-icon">\\u{1F4C1}</div>';
69507
+ h += '<div class="upload-zone-icon">' + IC.folderOpen + '</div>';
69609
69508
  h += '<div class="upload-zone-text">Drag & drop a file here</div>';
69610
69509
  h += '<div class="upload-zone-hint">Images, videos, PDFs, and other files up to 60 MB</div>';
69611
69510
  h += '<button class="upload-zone-btn" id="browse-btn">Browse Files</button>';
@@ -69629,6 +69528,7 @@ function renderPicker() {
69629
69528
  }
69630
69529
 
69631
69530
  root.innerHTML = h;
69531
+ renderThemeToggle();
69632
69532
 
69633
69533
  var backBtn = document.getElementById("back-to-result-btn");
69634
69534
  if (backBtn && lastResult) {
@@ -69708,12 +69608,13 @@ function renderUploading(name, meta) {
69708
69608
  var h = "";
69709
69609
 
69710
69610
  h += '<div class="upload-header">';
69711
- h += '<span class="upload-header-icon">\\u2B06\\uFE0F</span>';
69712
- h += "<h1>Uploading\\u2026</h1>";
69611
+ h += '<span class="upload-header-icon icon-accent">' + IC.uploadCloud + '</span>';
69612
+ h += '<h1>Uploading…</h1>';
69613
+ h += '<div id="header-actions" style="display:flex;align-items:center;gap:6px;margin-left:auto"></div>';
69713
69614
  h += "</div>";
69714
69615
 
69715
69616
  h += '<div class="upload-preview">';
69716
- h += '<div class="upload-preview-icon">\\u{1F4C4}</div>';
69617
+ h += '<div class="upload-preview-icon">' + IC.file + '</div>';
69717
69618
  h += '<div class="upload-preview-info">';
69718
69619
  h += '<div class="upload-preview-name">' + esc(name) + "</div>";
69719
69620
  h += '<div class="upload-preview-meta">' + esc(meta) + "</div>";
@@ -69725,6 +69626,7 @@ function renderUploading(name, meta) {
69725
69626
  h += "</div>";
69726
69627
 
69727
69628
  root.innerHTML = h;
69629
+ renderThemeToggle();
69728
69630
  animateProgress();
69729
69631
  }
69730
69632
 
@@ -69778,7 +69680,7 @@ function renderUploadError(title, msg) {
69778
69680
  var header = document.querySelector(".upload-header h1");
69779
69681
  if (header) header.textContent = title;
69780
69682
  var icon = document.querySelector(".upload-header-icon");
69781
- if (icon) icon.textContent = "\\u26A0\\uFE0F";
69683
+ if (icon) { icon.innerHTML = IC.alertTriangle; icon.className = "upload-header-icon icon-warning"; }
69782
69684
 
69783
69685
  var safeMsg = esc(msg).replace(/\\n/g, "<br>");
69784
69686
  var h = '<div class="upload-error-msg">' + safeMsg + "</div>";
@@ -69790,14 +69692,16 @@ function renderUploadError(title, msg) {
69790
69692
  var root = document.getElementById("app");
69791
69693
  var safeMsg = esc(msg).replace(/\\n/g, "<br>");
69792
69694
  var h = '<div class="upload-header">';
69793
- h += '<span class="upload-header-icon">\\u26A0\\uFE0F</span>';
69695
+ h += '<span class="upload-header-icon icon-warning">' + IC.alertTriangle + '</span>';
69794
69696
  h += "<h1>" + esc(title) + "</h1>";
69697
+ h += '<div id="header-actions" style="display:flex;align-items:center;gap:6px;margin-left:auto"></div>';
69795
69698
  h += "</div>";
69796
69699
  h += '<div class="upload-error-msg">' + safeMsg + "</div>";
69797
69700
  h += '<div class="upload-another" style="margin-top:14px;text-align:center">';
69798
69701
  h += '<button class="prompt-btn prompt-btn-primary" id="retry-upload-btn">Try from App</button>';
69799
69702
  h += "</div>";
69800
69703
  root.innerHTML = h;
69704
+ renderThemeToggle();
69801
69705
  }
69802
69706
 
69803
69707
  var btn = document.getElementById("retry-upload-btn");
@@ -69842,8 +69746,9 @@ function renderLocalFileNeeded(expectedName, errMsg) {
69842
69746
  var classified = classifyFileError(errMsg);
69843
69747
  var root = document.getElementById("app");
69844
69748
  var h = '<div class="upload-header">';
69845
- h += '<span class="upload-header-icon">\\u{1F4C1}</span>';
69749
+ h += '<span class="upload-header-icon icon-accent">' + IC.folderOpen + '</span>';
69846
69750
  h += "<h1>" + esc(classified.title) + "</h1>";
69751
+ h += '<div id="header-actions" style="display:flex;align-items:center;gap:6px;margin-left:auto"></div>';
69847
69752
  h += "</div>";
69848
69753
  h += '<div class="prompt" style="margin-bottom:16px">';
69849
69754
  h += '<div class="prompt-desc">The file <strong>' + esc(expectedName)
@@ -69858,13 +69763,14 @@ function renderLocalFileNeeded(expectedName, errMsg) {
69858
69763
  }
69859
69764
  h += "</div>";
69860
69765
  h += '<div class="upload-zone" id="drop-zone">';
69861
- h += '<div class="upload-zone-icon">\\u{1F4C1}</div>';
69766
+ h += '<div class="upload-zone-icon">' + IC.folderOpen + '</div>';
69862
69767
  h += '<div class="upload-zone-text">Drop <strong>' + esc(expectedName) + "</strong> here</div>";
69863
69768
  h += '<div class="upload-zone-hint">Or click to browse your files</div>';
69864
69769
  h += '<button class="upload-zone-btn" id="browse-btn">Browse Files</button>';
69865
69770
  h += '<input type="file" id="file-input" style="display:none">';
69866
69771
  h += "</div>";
69867
69772
  root.innerHTML = h;
69773
+ renderThemeToggle();
69868
69774
 
69869
69775
  function onFileSelected(file) {
69870
69776
  var reader = new FileReader();
@@ -69977,8 +69883,9 @@ function renderResult(r) {
69977
69883
  var h = "";
69978
69884
 
69979
69885
  h += '<div class="upload-header">';
69980
- h += '<span class="upload-header-icon">' + (isPending ? "\\u23F3" : "\\u2705") + '</span>';
69886
+ h += '<span class="upload-header-icon ' + (isPending ? "icon-accent" : "icon-success") + '">' + (isPending ? IC.clock : IC.checkCircle) + '</span>';
69981
69887
  h += "<h1>" + (isPending ? "Upload Queued" : "Upload Complete") + "</h1>";
69888
+ h += '<div id="header-actions" style="display:flex;align-items:center;gap:6px;margin-left:auto"></div>';
69982
69889
  h += "</div>";
69983
69890
 
69984
69891
  h += '<div class="upload-result">';
@@ -70039,6 +69946,7 @@ function renderResult(r) {
70039
69946
  h += "</div></div>";
70040
69947
 
70041
69948
  root.innerHTML = h;
69949
+ renderThemeToggle();
70042
69950
 
70043
69951
  root.addEventListener("click", function handler(e) {
70044
69952
  var el = e.target;
@@ -70173,6 +70081,7 @@ ${UPLOAD_CSS}
70173
70081
  <div id="app"><div class="status">Preparing upload&hellip;</div></div>
70174
70082
 
70175
70083
  <script>
70084
+ ${SHARED_JS_ICONS}
70176
70085
  ${SHARED_JS_MCP_CLIENT}
70177
70086
  ${SHARED_JS_HELPERS}
70178
70087
  ${SHARED_JS_TOOLTIPS}
@@ -70192,7 +70101,12 @@ function appResourceContent(uri, html) {
70192
70101
  uri: uri.toString(),
70193
70102
  mimeType: MCP_APP_MIME_TYPE,
70194
70103
  text: html,
70195
- _meta: { ui: { csp: { resourceDomains: CSP_RESOURCE_DOMAINS } } }
70104
+ _meta: {
70105
+ ui: {
70106
+ csp: { resourceDomains: CSP_RESOURCE_DOMAINS },
70107
+ permissions: { clipboardWrite: {} }
70108
+ }
70109
+ }
70196
70110
  }]
70197
70111
  };
70198
70112
  }
@@ -74103,7 +74017,7 @@ A report on the status of product environment usage, including storage, credits,
74103
74017
  function createMCPServer(deps) {
74104
74018
  const server = new McpServer({
74105
74019
  name: "CloudinaryAssetMgmt",
74106
- version: "0.9.1"
74020
+ version: "0.9.3"
74107
74021
  });
74108
74022
  const getClient = deps.getSDK || (() => new CloudinaryAssetMgmtCore({
74109
74023
  security: deps.security,
@@ -74376,8 +74290,13 @@ This tool creates actual derived assets on Cloudinary using the explicit API.`
74376
74290
 
74377
74291
  // src/landing-page.ts
74378
74292
  function landingPageExpress(req, res) {
74379
- const origin = new URL(req.host).href;
74380
- res.type("html").send(landingPageHTML(origin));
74293
+ const proto = req.get("x-forwarded-proto")?.split(",")[0]?.trim() || req.protocol;
74294
+ const host = req.get("host");
74295
+ if (!host) {
74296
+ res.status(400).send("Missing Host header");
74297
+ return;
74298
+ }
74299
+ res.type("html").send(landingPageHTML(`${proto}://${host}`));
74381
74300
  }
74382
74301
  function landingPageHTML(origin) {
74383
74302
  const o = origin;
@@ -75280,7 +75199,7 @@ http_headers = { "api-key" = "YOUR_API_KEY", "api-secret" = "YOUR_API_SECRET", "
75280
75199
  <h1>Instructions</h1>
75281
75200
  <p>One-click installation for Claude Desktop users</p>
75282
75201
  <div class="instruction-item">
75283
- <a href="https://github.com/cloudinary/asset-management-mcp/releases/download/v0.9.1/mcp-server.mcpb" download="mcp-server.mcpb" class="action-button header-action" style="display: inline-flex; margin-bottom: 16px;">
75202
+ <a href="https://github.com/cloudinary/asset-management-mcp/releases/download/v0.9.3/mcp-server.mcpb" download="mcp-server.mcpb" class="action-button header-action" style="display: inline-flex; margin-bottom: 16px;">
75284
75203
  \uD83D\uDCE5 Download MCP Bundle
75285
75204
  </a>
75286
75205
  </div>
@@ -75442,7 +75361,7 @@ async function startStreamableHTTP(cliFlags) {
75442
75361
  app.use((req, res, next) => {
75443
75362
  res.header("Access-Control-Allow-Origin", "*");
75444
75363
  res.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
75445
- res.header("Access-Control-Allow-Headers", "Content-Type, *");
75364
+ res.header("Access-Control-Allow-Headers", "*");
75446
75365
  if (req.method === "OPTIONS") {
75447
75366
  res.sendStatus(204);
75448
75367
  return;
@@ -77224,7 +77143,7 @@ var routes = ln({
77224
77143
  var app = _e(routes, {
77225
77144
  name: "mcp",
77226
77145
  versionInfo: {
77227
- currentVersion: "0.9.1"
77146
+ currentVersion: "0.9.3"
77228
77147
  }
77229
77148
  });
77230
77149
  Yt(app, process4.argv.slice(2), buildContext(process4));
@@ -77232,5 +77151,5 @@ export {
77232
77151
  app
77233
77152
  };
77234
77153
 
77235
- //# debugId=92BB3EDCF4CED14664756E2164756E21
77154
+ //# debugId=6C5BCA15F724A25664756E2164756E21
77236
77155
  //# sourceMappingURL=mcp-server.js.map