relic-mcp 0.14.1 → 0.14.2

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.
@@ -10,7 +10,7 @@
10
10
  {
11
11
  "name": "relic",
12
12
  "description": "Publish a file from your machine as an encrypted, shareable link. The agent encrypts locally, uploads only ciphertext, and hands back a URL whose fragment holds the key, so the service stores something it cannot read.",
13
- "version": "0.14.1",
13
+ "version": "0.14.2",
14
14
  "source": "./",
15
15
  "author": {
16
16
  "name": "The Bushido Collective",
@@ -21,6 +21,6 @@
21
21
  }
22
22
  ],
23
23
  "metadata": {
24
- "version": "0.14.1"
24
+ "version": "0.14.2"
25
25
  }
26
26
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "relic",
3
- "version": "0.14.1",
3
+ "version": "0.14.2",
4
4
  "description": "Publish a file from your machine as an encrypted, shareable link. The agent encrypts locally, uploads only ciphertext, and hands back a URL whose fragment holds the key, so the service stores something it cannot read.",
5
5
  "mcpServers": "./mcp-servers.json",
6
6
  "author": {
@@ -20673,9 +20673,9 @@ async function renderFrameCover(kind, source, dialect, root) {
20673
20673
  return;
20674
20674
  if (data.type === "relic:sandbox-ready") {
20675
20675
  if (kind === "html") {
20676
- iframe.contentWindow?.postMessage({ type: "relic:render", html: codeToSend }, "*");
20676
+ iframe.contentWindow?.postMessage({ type: "relic:render", html: codeToSend, cover: true }, "*");
20677
20677
  } else {
20678
- iframe.contentWindow?.postMessage({ type: "relic:render-jsx", code: codeToSend }, "*");
20678
+ iframe.contentWindow?.postMessage({ type: "relic:render-jsx", code: codeToSend, cover: true }, "*");
20679
20679
  }
20680
20680
  } else if (data.type === "relic:tree") {
20681
20681
  cleanup();
@@ -11561,6 +11561,12 @@ function isTrackMarkMessage(data) {
11561
11561
  return true;
11562
11562
  return typeof msg.id === "string" && msg.id.length > 0 && msg.id.length <= 256;
11563
11563
  }
11564
+ function isFrameVisibilityMessage(data) {
11565
+ if (typeof data !== "object" || data === null)
11566
+ return false;
11567
+ const msg = data;
11568
+ return msg.type === "relic:frame-visibility" && typeof msg.visible === "boolean";
11569
+ }
11564
11570
  function isActiveMarkMessage(data) {
11565
11571
  if (typeof data !== "object" || data === null)
11566
11572
  return false;
@@ -11702,6 +11708,127 @@ function isSetScrollMessage(data) {
11702
11708
  return isFractionMessage(data, "relic:set-scroll");
11703
11709
  }
11704
11710
 
11711
+ // ../relic-viewer/src/mark-visibility.ts
11712
+ var CONCEALED_ATTRIBUTE = "data-concealed";
11713
+ var CONCEALMENT_CSS = `
11714
+ [${CONCEALED_ATTRIBUTE}='hidden']:not(mark) { display: none !important; }
11715
+ [${CONCEALED_ATTRIBUTE}='offscreen']:not(mark) { opacity: 0 !important; }
11716
+ mark[${CONCEALED_ATTRIBUTE}] {
11717
+ background: none !important;
11718
+ border-bottom-color: transparent !important;
11719
+ box-shadow: none !important;
11720
+ }
11721
+ `;
11722
+ function isRendered(el) {
11723
+ if (el.isConnected !== true)
11724
+ return true;
11725
+ const probe = el.checkVisibility;
11726
+ if (typeof probe === "function") {
11727
+ if (!probe.call(el, { checkVisibilityCSS: true, checkOpacity: false })) {
11728
+ return false;
11729
+ }
11730
+ } else {
11731
+ for (let node = el;node; node = node.parentElement) {
11732
+ if (typeof node.hasAttribute === "function" && node.hasAttribute("hidden")) {
11733
+ return false;
11734
+ }
11735
+ if (node.tagName === "DETAILS" && !node.open) {
11736
+ const summary = Array.from(node.children).find((child) => child.tagName === "SUMMARY");
11737
+ if (!summary || !summary.contains(el))
11738
+ return false;
11739
+ }
11740
+ const style = node.style;
11741
+ if (style && (style.display === "none" || style.visibility === "hidden")) {
11742
+ return false;
11743
+ }
11744
+ }
11745
+ }
11746
+ const rects = typeof el.getClientRects === "function" ? el.getClientRects() : undefined;
11747
+ if (rects !== undefined && rects.length === 0)
11748
+ return false;
11749
+ return true;
11750
+ }
11751
+ function setConcealed(node, concealment) {
11752
+ const current = node.getAttribute(CONCEALED_ATTRIBUTE);
11753
+ if (concealment === undefined) {
11754
+ if (current !== null)
11755
+ node.removeAttribute(CONCEALED_ATTRIBUTE);
11756
+ return;
11757
+ }
11758
+ if (current !== concealment)
11759
+ node.setAttribute(CONCEALED_ATTRIBUTE, concealment);
11760
+ }
11761
+ function createScrollportWatch(onChange) {
11762
+ const inside = new WeakMap;
11763
+ const observed = new Set;
11764
+ const observer = typeof IntersectionObserver === "function" ? new IntersectionObserver((entries) => {
11765
+ let changed = false;
11766
+ for (const entry of entries) {
11767
+ if (inside.get(entry.target) !== entry.isIntersecting) {
11768
+ inside.set(entry.target, entry.isIntersecting);
11769
+ changed = true;
11770
+ }
11771
+ }
11772
+ if (changed)
11773
+ onChange();
11774
+ }) : undefined;
11775
+ return {
11776
+ sync(elements) {
11777
+ if (observer === undefined)
11778
+ return;
11779
+ const wanted = new Set(elements);
11780
+ for (const el of observed) {
11781
+ if (wanted.has(el))
11782
+ continue;
11783
+ observer.unobserve(el);
11784
+ observed.delete(el);
11785
+ inside.delete(el);
11786
+ }
11787
+ for (const el of wanted) {
11788
+ if (observed.has(el))
11789
+ continue;
11790
+ observer.observe(el);
11791
+ observed.add(el);
11792
+ }
11793
+ },
11794
+ inView(el) {
11795
+ return inside.get(el) !== false;
11796
+ },
11797
+ disconnect() {
11798
+ observer?.disconnect();
11799
+ observed.clear();
11800
+ }
11801
+ };
11802
+ }
11803
+ function concealMarks(marks, watch, live = true) {
11804
+ watch.sync(marks.map((mark) => mark.node));
11805
+ const merged = new Map;
11806
+ for (const { node, subject } of marks) {
11807
+ const id = node.getAttribute("data-comment-id");
11808
+ if (id === null || id === "")
11809
+ continue;
11810
+ const concealment = !live || !isRendered(subject) ? "hidden" : !watch.inView(node) ? "offscreen" : "shown";
11811
+ setConcealed(node, concealment === "shown" ? undefined : concealment);
11812
+ const previous = merged.get(id);
11813
+ merged.set(id, mergeReason(previous, concealment));
11814
+ }
11815
+ const out = new Map;
11816
+ for (const [id, reason] of merged) {
11817
+ if (reason !== "shown")
11818
+ out.set(id, reason);
11819
+ }
11820
+ return out;
11821
+ }
11822
+ function mergeReason(previous, next) {
11823
+ if (previous === undefined)
11824
+ return next;
11825
+ if (previous === "shown" || next === "shown")
11826
+ return "shown";
11827
+ if (previous === "hidden" || next === "hidden")
11828
+ return "hidden";
11829
+ return "offscreen";
11830
+ }
11831
+
11705
11832
  // ../relic-viewer/src/rendered-tree.ts
11706
11833
  function isRevealChangeMessage(data) {
11707
11834
  if (typeof data !== "object" || data === null)
@@ -12098,19 +12225,35 @@ var FRAME_MARK_CSS = `
12098
12225
  cursor: default !important;
12099
12226
  }
12100
12227
  .relic-image-layer { position: fixed !important; inset: 0 !important; z-index: 2147483646 !important; pointer-events: none !important; }
12101
- .relic-image-control, .relic-image-mark {
12228
+ .relic-image-row {
12102
12229
  position: absolute !important; box-sizing: border-box !important;
12103
- width: 32px !important; height: 32px !important; padding: 0 !important;
12230
+ display: flex !important; align-items: flex-start !important; gap: 10px !important;
12231
+ padding: 6px !important; margin: 0 !important; max-height: 56px !important;
12232
+ overflow-x: auto !important; overflow-y: hidden !important; scrollbar-width: thin !important;
12233
+ pointer-events: none !important; overscroll-behavior: contain !important;
12234
+ }
12235
+ .relic-image-row.has-marks { pointer-events: auto !important; }
12236
+ .relic-image-mark, .relic-image-control {
12237
+ position: relative !important; box-sizing: border-box !important; flex: 0 0 28px !important;
12238
+ display: flex !important; align-items: center !important; justify-content: center !important;
12239
+ width: 28px !important; height: 28px !important; padding: 0 !important; margin: 0 !important;
12104
12240
  border: 2px solid white !important; border-radius: 50% !important;
12105
- background: #806026 !important; color: white !important; font: bold 18px sans-serif !important;
12241
+ background: #806026 !important; color: white !important;
12242
+ box-shadow: 0 1px 4px rgba(0, 0, 0, 0.45) !important;
12106
12243
  cursor: pointer !important; pointer-events: auto !important;
12107
12244
  }
12245
+ .relic-image-control {
12246
+ opacity: 0 !important; pointer-events: none !important;
12247
+ transition: opacity 0.12s ease !important;
12248
+ }
12249
+ .relic-image-control svg, .relic-image-mark svg { display: block !important; width: 14px !important; height: 14px !important; fill: currentColor !important; pointer-events: none !important; }
12250
+ .relic-image-control.is-shown, .relic-image-control:focus-visible { opacity: 1 !important; pointer-events: auto !important; }
12251
+ .relic-image-mark.is-active, .relic-image-mark.is-paired { outline: 3px solid #806026 !important; outline-offset: 2px !important; }
12108
12252
  .relic-image-control:focus-visible, .relic-image-mark:focus-visible { outline: 3px solid #222 !important; outline-offset: 2px !important; }
12109
- .relic-image-mark.is-active, .relic-image-mark.is-paired { outline: 3px solid #806026 !important; }
12110
12253
  .relic-image-mark.is-resolved { background: #777 !important; }
12111
- .relic-image-mark.is-pending { pointer-events: none !important; opacity: .6 !important; }
12254
+ .relic-image-mark.is-pending { pointer-events: none !important; opacity: .6 !important; border-style: dashed !important; }
12112
12255
  .relic-image-outline { position: absolute !important; box-sizing: border-box !important; border: 2px solid #b48c3c !important; pointer-events: none !important; }
12113
- .relic-image-control[hidden] { display: none !important; }
12256
+ .relic-image-control[hidden], .relic-image-row[hidden] { display: none !important; }
12114
12257
  .relic-pointing-active, .relic-pointing-active * {
12115
12258
  cursor: crosshair !important;
12116
12259
  }
@@ -12120,7 +12263,7 @@ function ensureFrameStyles(doc) {
12120
12263
  return;
12121
12264
  const style = doc.createElement("style");
12122
12265
  style.id = "relic-frame-styles";
12123
- style.textContent = FRAME_MARK_CSS;
12266
+ style.textContent = FRAME_MARK_CSS + CONCEALMENT_CSS;
12124
12267
  doc.head?.appendChild(style);
12125
12268
  }
12126
12269
  function markFrameRect(el) {
@@ -12314,6 +12457,42 @@ function documentSize(doc) {
12314
12457
  }
12315
12458
  var frameOwnedNodes = new WeakSet;
12316
12459
  var frameRegionOverlays = new WeakMap;
12460
+ var markSubjects = new WeakMap;
12461
+ function isFrameOwned(node) {
12462
+ for (let current = node;current; current = current.parentNode) {
12463
+ if (frameOwnedNodes.has(current))
12464
+ return true;
12465
+ }
12466
+ return false;
12467
+ }
12468
+ function subjectAt(doc, x, y) {
12469
+ const body = doc.body;
12470
+ if (!body || typeof body.getElementsByTagName !== "function")
12471
+ return;
12472
+ const scrollX = doc.defaultView?.scrollX ?? 0;
12473
+ const scrollY = doc.defaultView?.scrollY ?? 0;
12474
+ let best;
12475
+ let bestArea = Number.POSITIVE_INFINITY;
12476
+ for (const el of Array.from(body.getElementsByTagName("*"))) {
12477
+ if (isFrameOwned(el) || typeof el.getBoundingClientRect !== "function") {
12478
+ continue;
12479
+ }
12480
+ const box = el.getBoundingClientRect();
12481
+ if (box.width <= 0 || box.height <= 0)
12482
+ continue;
12483
+ const left = box.left + scrollX;
12484
+ const top = box.top + scrollY;
12485
+ if (x < left || x > left + box.width || y < top || y > top + box.height) {
12486
+ continue;
12487
+ }
12488
+ const area = box.width * box.height;
12489
+ if (area <= bestArea) {
12490
+ best = el;
12491
+ bestArea = area;
12492
+ }
12493
+ }
12494
+ return best;
12495
+ }
12317
12496
  function frameOverlay(doc) {
12318
12497
  if (!doc.body)
12319
12498
  return;
@@ -12349,6 +12528,9 @@ function paintFrameRegion(root, rect, id) {
12349
12528
  div.style.width = `${rect.w * docWidth}px`;
12350
12529
  div.style.height = `${rect.h * docHeight}px`;
12351
12530
  wireFrameMark(div, id, doc);
12531
+ const subject = subjectAt(doc, (rect.x + rect.w / 2) * docWidth, (rect.y + rect.h / 2) * docHeight);
12532
+ if (subject)
12533
+ markSubjects.set(div, subject);
12352
12534
  overlay.appendChild(div);
12353
12535
  return div;
12354
12536
  }
@@ -12364,6 +12546,9 @@ function paintFramePin(root, point, id) {
12364
12546
  pin.dataset.commentId = id;
12365
12547
  pin.style.left = `${point.x * layer.docWidth}px`;
12366
12548
  pin.style.top = `${point.y * layer.docHeight}px`;
12549
+ const subject = subjectAt(doc, point.x * layer.docWidth, point.y * layer.docHeight);
12550
+ if (subject)
12551
+ markSubjects.set(pin, subject);
12367
12552
  if (!pending)
12368
12553
  wireFrameMark(pin, id, doc);
12369
12554
  layer.overlay.appendChild(pin);
@@ -12486,13 +12671,29 @@ function applyDocumentScrollFraction(doc, win, fraction) {
12486
12671
  const bounded = Math.min(Math.max(fraction, 0), 1);
12487
12672
  applyDocumentScrollTop(doc, win, bounded * travel);
12488
12673
  }
12489
- function setupImageMarks(doc, win, post, tracked, enabled) {
12674
+ var COMMENT_PLUS_PATH = "M256 480c141.4 0 256-107.5 256-240S397.4 0 256 0 0 107.5 0 240c0 54.3 19.2 104.3 51.6 144.5L2.8 476.8c-4.8 9-3.3 20 3.6 27.5s17.8 9.8 27.1 5.8l118.4-50.7C183.7 472.6 218.9 480 256 480zm0-336c13.3 0 24 10.7 24 24l0 48 48 0c13.3 0 24 10.7 24 24s-10.7 24-24 24l-48 0 0 48c0 13.3-10.7 24-24 24s-24-10.7-24-24l0-48-48 0c-13.3 0-24-10.7-24-24s10.7-24 24-24l48 0 0-48c0-13.3 10.7-24 24-24z";
12675
+ var COMMENT_PATH = "M512 240c0 132.5-114.6 240-256 240-37.1 0-72.3-7.4-104.1-20.7L33.5 510.1c-9.4 4-20.2 1.7-27.1-5.8S-2 485.8 2.8 476.8l48.8-92.2C19.2 344.3 0 294.3 0 240 0 107.5 114.6 0 256 0S512 107.5 512 240z";
12676
+ function imageCommentIcon(doc, add = false) {
12677
+ const ns = "http://www.w3.org/2000/svg";
12678
+ const svg = doc.createElementNS(ns, "svg");
12679
+ svg.setAttribute("viewBox", "0 0 512 512");
12680
+ svg.setAttribute("aria-hidden", "true");
12681
+ svg.setAttribute("focusable", "false");
12682
+ svg.appendChild(doc.createComment("! Font Awesome Pro 7.3.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2026 Fonticons, Inc."));
12683
+ const path = doc.createElementNS(ns, "path");
12684
+ path.setAttribute("d", add ? COMMENT_PLUS_PATH : COMMENT_PATH);
12685
+ svg.appendChild(path);
12686
+ return svg;
12687
+ }
12688
+ function setupImageMarks(doc, win, post, tracked, enabled, inParent) {
12490
12689
  const entries = new Map;
12491
12690
  const controls = new Map;
12691
+ const rows = new Map;
12492
12692
  const hashes = new WeakMap;
12493
12693
  let layer;
12494
12694
  let scheduled = false;
12495
12695
  let serial = 0;
12696
+ const pairedIds = new Set;
12496
12697
  let resizeObserver;
12497
12698
  let disposed = false;
12498
12699
  let activeId = null;
@@ -12571,6 +12772,10 @@ function setupImageMarks(doc, win, post, tracked, enabled) {
12571
12772
  return node.tagName.toLowerCase() === "img" ? node : undefined;
12572
12773
  };
12573
12774
  const visible = (img) => {
12775
+ if (!inParent())
12776
+ return;
12777
+ if (!isRendered(img))
12778
+ return;
12574
12779
  if (!img.isConnected || !img.complete || img.naturalWidth === 0 || isOwned(img))
12575
12780
  return;
12576
12781
  const rect = markFrameRect(img);
@@ -12623,6 +12828,7 @@ function setupImageMarks(doc, win, post, tracked, enabled) {
12623
12828
  for (const image of controls.keys())
12624
12829
  resizeObserver?.unobserve(image);
12625
12830
  controls.clear();
12831
+ rows.clear();
12626
12832
  for (const entry of entries.values())
12627
12833
  remove(entry);
12628
12834
  }
@@ -12634,20 +12840,147 @@ function setupImageMarks(doc, win, post, tracked, enabled) {
12634
12840
  return layer;
12635
12841
  };
12636
12842
  const remove = (entry) => {
12843
+ const image = entry.image;
12637
12844
  entry.badge?.remove();
12638
12845
  entry.outline?.remove();
12639
12846
  delete entry.badge;
12640
12847
  delete entry.outline;
12641
12848
  delete entry.image;
12642
12849
  delete entry.declaration;
12850
+ if (image)
12851
+ layoutRow(image);
12852
+ };
12853
+ const reportConcealed = (entry, mark, img) => {
12854
+ const concealment = isRendered(img) ? "offscreen" : "hidden";
12855
+ const key = `concealed:${concealment}`;
12856
+ if (entry.reported === key)
12857
+ return;
12858
+ entry.reported = key;
12859
+ post({
12860
+ type: "relic:frame-image-placement",
12861
+ id: mark.id,
12862
+ requestId: mark.requestId,
12863
+ status: "concealed",
12864
+ concealment
12865
+ });
12643
12866
  };
12644
- const position = (el, rect, offset = 0) => {
12645
- const width = Math.min(32, rect.width);
12646
- const height = Math.min(32, rect.height);
12647
- el.style.setProperty("width", `${width}px`, "important");
12648
- el.style.setProperty("height", `${height}px`, "important");
12649
- el.style.left = `${Math.max(rect.left, rect.left + rect.width - width - offset % 6 * 36)}px`;
12650
- el.style.top = `${Math.min(rect.top + rect.height - height, rect.top + Math.floor(offset / 6) * 36)}px`;
12867
+ const CONTROL_SIZE = 28;
12868
+ const ROW_GAP = 10;
12869
+ const ROW_PADDING = 6;
12870
+ const layoutRow = (img) => {
12871
+ const row = rows.get(img);
12872
+ const rect = visible(img);
12873
+ if (!row)
12874
+ return;
12875
+ row.hidden = !rect;
12876
+ if (!rect)
12877
+ return;
12878
+ const count = row.children.length;
12879
+ const fullWidth = count * CONTROL_SIZE + (count - 1) * ROW_GAP + ROW_PADDING * 2;
12880
+ const width = Math.min(fullWidth, Math.max(rect.width, 78), win.innerWidth);
12881
+ const roomy = rect.width >= 56 && rect.height >= 56;
12882
+ const right = rect.left + rect.width + (roomy ? -8 : CONTROL_SIZE / 2) + ROW_PADDING;
12883
+ const top = rect.top + (roomy ? 8 : -CONTROL_SIZE / 2) - ROW_PADDING;
12884
+ row.style.width = `${width}px`;
12885
+ row.style.left = `${Math.max(0, Math.min(right - width, win.innerWidth - width))}px`;
12886
+ row.style.top = `${Math.max(0, Math.min(top, win.innerHeight - 56))}px`;
12887
+ row.classList.toggle("has-marks", count > 1);
12888
+ row.style.setProperty("scroll-padding-inline", `${ROW_PADDING}px`);
12889
+ };
12890
+ const revealButton = (row, button) => {
12891
+ const box = button.getBoundingClientRect();
12892
+ const viewport = row.getBoundingClientRect();
12893
+ if (box.left < viewport.left + ROW_PADDING)
12894
+ row.scrollLeft -= viewport.left + ROW_PADDING - box.left;
12895
+ else if (box.right > viewport.right - ROW_PADDING)
12896
+ row.scrollLeft += box.right - viewport.right + ROW_PADDING;
12897
+ };
12898
+ const revealBadge = (entry) => {
12899
+ const row = entry.image && rows.get(entry.image);
12900
+ if (row && entry.badge)
12901
+ revealButton(row, entry.badge);
12902
+ };
12903
+ const reportRows = () => {
12904
+ for (const entry of entries.values()) {
12905
+ if (!entry.badge || !entry.image || entry.declaration !== source(entry.image))
12906
+ continue;
12907
+ const row = rows.get(entry.image);
12908
+ const box = markFrameRect(entry.badge);
12909
+ const clip = row && markFrameRect(row);
12910
+ if (!box || !clip || row?.hidden)
12911
+ continue;
12912
+ const id = entry.mark.id;
12913
+ if (box.left < clip.left + ROW_PADDING - 0.5 || box.left + box.width > clip.left + clip.width - ROW_PADDING + 0.5) {
12914
+ if (entry.reported !== "concealed:offscreen") {
12915
+ entry.reported = "concealed:offscreen";
12916
+ post({
12917
+ type: "relic:frame-image-placement",
12918
+ id,
12919
+ requestId: entry.mark.requestId,
12920
+ status: "concealed",
12921
+ concealment: "offscreen"
12922
+ });
12923
+ }
12924
+ continue;
12925
+ }
12926
+ entry.reported = "placed";
12927
+ post({
12928
+ type: "relic:frame-image-placement",
12929
+ id,
12930
+ requestId: entry.mark.requestId,
12931
+ status: "placed",
12932
+ rect: box
12933
+ });
12934
+ if (tracked() === id)
12935
+ post({ type: "relic:frame-mark-rect", id, rect: box });
12936
+ }
12937
+ };
12938
+ let rowReportScheduled = false;
12939
+ const scheduleRowReport = () => {
12940
+ if (rowReportScheduled)
12941
+ return;
12942
+ rowReportScheduled = true;
12943
+ queueMicrotask(() => {
12944
+ rowReportScheduled = false;
12945
+ if (!disposed)
12946
+ reportRows();
12947
+ });
12948
+ };
12949
+ let pointer;
12950
+ const updateHover = () => {
12951
+ const always = win.matchMedia?.("(hover: none)").matches ?? false;
12952
+ let target;
12953
+ let targetArea = Number.POSITIVE_INFINITY;
12954
+ if (pointer && !always) {
12955
+ const { x, y } = pointer;
12956
+ const inside = (r) => x >= r.left && x <= r.left + r.width && y >= r.top && y <= r.top + r.height;
12957
+ for (const [img, button] of controls) {
12958
+ if (button.hidden)
12959
+ continue;
12960
+ const rect = visible(img);
12961
+ const box = (rows.get(img) ?? button).getBoundingClientRect();
12962
+ if (!(rect && inside(rect)) && !inside(box))
12963
+ continue;
12964
+ const area = rect ? rect.width * rect.height : 0;
12965
+ if (area <= targetArea) {
12966
+ target = img;
12967
+ targetArea = area;
12968
+ }
12969
+ }
12970
+ }
12971
+ for (const [img, button] of controls)
12972
+ button.classList.toggle("is-shown", always || img === target);
12973
+ };
12974
+ const onPointerMove = (event) => {
12975
+ const { clientX, clientY } = event;
12976
+ pointer = { x: clientX, y: clientY };
12977
+ updateHover();
12978
+ };
12979
+ const onPointerLeave = (event) => {
12980
+ if (event.relatedTarget !== null)
12981
+ return;
12982
+ pointer = undefined;
12983
+ updateHover();
12651
12984
  };
12652
12985
  const schedule = () => {
12653
12986
  if (!enabled)
@@ -12673,9 +13006,10 @@ function setupImageMarks(doc, win, post, tracked, enabled) {
12673
13006
  return;
12674
13007
  const images = Array.from(doc.body.querySelectorAll("img")).filter((img) => !isOwned(img));
12675
13008
  const live = new Set(images);
12676
- for (const [img, button] of controls) {
13009
+ for (const img of controls.keys()) {
12677
13010
  if (!live.has(img)) {
12678
- button.remove();
13011
+ rows.get(img)?.remove();
13012
+ rows.delete(img);
12679
13013
  controls.delete(img);
12680
13014
  resizeObserver?.unobserve(img);
12681
13015
  }
@@ -12687,8 +13021,17 @@ function setupImageMarks(doc, win, post, tracked, enabled) {
12687
13021
  button.type = "button";
12688
13022
  button.className = "relic-image-control";
12689
13023
  button.setAttribute("aria-label", "Comment on image");
12690
- button.textContent = "+";
12691
- host.appendChild(button);
13024
+ button.appendChild(imageCommentIcon(doc, true));
13025
+ const row = doc.createElement("div");
13026
+ row.className = "relic-image-row";
13027
+ row.appendChild(button);
13028
+ row.addEventListener("scroll", reportRows);
13029
+ button.addEventListener("focus", () => {
13030
+ revealButton(row, button);
13031
+ reportRows();
13032
+ });
13033
+ rows.set(img, row);
13034
+ host.appendChild(row);
12692
13035
  controls.set(img, button);
12693
13036
  resizeObserver?.observe(img);
12694
13037
  const control = button;
@@ -12715,17 +13058,16 @@ function setupImageMarks(doc, win, post, tracked, enabled) {
12715
13058
  }
12716
13059
  const rect = visible(img);
12717
13060
  button.hidden = !rect;
12718
- if (rect)
12719
- position(button, rect);
13061
+ layoutRow(img);
12720
13062
  }
12721
- const positions = new Map;
13063
+ updateHover();
12722
13064
  for (const entry of entries.values()) {
12723
13065
  const mark = entry.mark;
12724
13066
  const generation = ++entry.serial;
12725
13067
  const img = resolve(mark.path);
12726
13068
  const rect = img && visible(img);
12727
13069
  const declaration = img && source(img);
12728
- if (!img || !rect || !declaration || !/^[0-9a-f]{64}$/.test(mark.source_sha256) || new TextEncoder().encode(JSON.stringify({
13070
+ if (!img || !declaration || !/^[0-9a-f]{64}$/.test(mark.source_sha256) || new TextEncoder().encode(JSON.stringify({
12729
13071
  kind: "image",
12730
13072
  path: mark.path,
12731
13073
  source_sha256: mark.source_sha256
@@ -12739,15 +13081,18 @@ function setupImageMarks(doc, win, post, tracked, enabled) {
12739
13081
  });
12740
13082
  continue;
12741
13083
  }
13084
+ if (!rect) {
13085
+ remove(entry);
13086
+ reportConcealed(entry, mark, img);
13087
+ continue;
13088
+ }
12742
13089
  if (entry.badge && (entry.image !== img || entry.declaration !== declaration))
12743
13090
  remove(entry);
12744
- const offset = (positions.get(img) ?? 0) + 1;
12745
- positions.set(img, offset);
12746
13091
  fingerprint(img).then((digest) => {
12747
13092
  if (entries.get(mark.id) !== entry || entry.serial !== generation)
12748
13093
  return;
12749
13094
  const currentRect = visible(img);
12750
- if (digest !== mark.source_sha256 || source(img) !== declaration || resolve(mark.path) !== img || !currentRect) {
13095
+ if (digest !== mark.source_sha256 || source(img) !== declaration || resolve(mark.path) !== img) {
12751
13096
  remove(entry);
12752
13097
  post({
12753
13098
  type: "relic:frame-image-placement",
@@ -12757,6 +13102,12 @@ function setupImageMarks(doc, win, post, tracked, enabled) {
12757
13102
  });
12758
13103
  return;
12759
13104
  }
13105
+ if (!currentRect) {
13106
+ remove(entry);
13107
+ reportConcealed(entry, mark, img);
13108
+ return;
13109
+ }
13110
+ const created = !entry.badge;
12760
13111
  if (!entry.badge) {
12761
13112
  entry.outline = doc.createElement("div");
12762
13113
  entry.outline.className = "relic-image-outline";
@@ -12766,43 +13117,38 @@ function setupImageMarks(doc, win, post, tracked, enabled) {
12766
13117
  entry.badge.className = "relic-image-mark";
12767
13118
  entry.badge.dataset.commentId = mark.id;
12768
13119
  entry.badge.setAttribute("aria-label", "Open image comment");
12769
- entry.badge.textContent = "•";
12770
- if (mark.id === "pending:target")
13120
+ entry.badge.appendChild(imageCommentIcon(doc));
13121
+ if (mark.id === "pending:target") {
12771
13122
  entry.badge.classList.add("is-pending");
13123
+ entry.badge.disabled = true;
13124
+ }
12772
13125
  entry.badge.classList.toggle("is-active", mark.id === activeId);
13126
+ entry.badge.classList.toggle("is-paired", pairedIds.has(mark.id));
12773
13127
  entry.badge.classList.toggle("is-resolved", quietIds.has(mark.id));
12774
- host.appendChild(entry.badge);
13128
+ rows.get(img)?.insertBefore(entry.badge, controls.get(img) ?? null);
12775
13129
  wireFrameMark(entry.badge, mark.id, doc);
13130
+ entry.badge.addEventListener("focus", () => {
13131
+ revealBadge(entry);
13132
+ reportRows();
13133
+ });
12776
13134
  }
12777
13135
  entry.image = img;
12778
13136
  entry.declaration = declaration;
12779
- position(entry.badge, currentRect, offset);
12780
13137
  Object.assign(entry.outline.style, {
12781
13138
  left: `${currentRect.left}px`,
12782
13139
  top: `${currentRect.top}px`,
12783
13140
  width: `${currentRect.width}px`,
12784
13141
  height: `${currentRect.height}px`
12785
13142
  });
12786
- const box = markFrameRect(entry.badge);
12787
- if (box) {
12788
- post({
12789
- type: "relic:frame-image-placement",
12790
- id: mark.id,
12791
- requestId: mark.requestId,
12792
- status: "placed",
12793
- rect: box
12794
- });
12795
- if (tracked() === mark.id)
12796
- post({ type: "relic:frame-mark-rect", id: mark.id, rect: box });
12797
- } else {
12798
- remove(entry);
12799
- post({
12800
- type: "relic:frame-image-placement",
12801
- id: mark.id,
12802
- requestId: mark.requestId,
12803
- status: "unavailable"
12804
- });
13143
+ layoutRow(img);
13144
+ if (created) {
13145
+ const row = rows.get(img);
13146
+ if (row)
13147
+ row.scrollLeft = row.scrollWidth;
12805
13148
  }
13149
+ if (created && (mark.id === activeId || mark.id === tracked()))
13150
+ revealBadge(entry);
13151
+ scheduleRowReport();
12806
13152
  }).catch(() => {
12807
13153
  if (entries.get(mark.id) !== entry || entry.serial !== generation)
12808
13154
  return;
@@ -12833,6 +13179,8 @@ function setupImageMarks(doc, win, post, tracked, enabled) {
12833
13179
  characterData: true
12834
13180
  });
12835
13181
  }
13182
+ doc.addEventListener("pointermove", onPointerMove, true);
13183
+ doc.addEventListener("mouseout", onPointerLeave);
12836
13184
  doc.addEventListener("load", schedule, true);
12837
13185
  doc.addEventListener("scroll", schedule, true);
12838
13186
  doc.addEventListener("DOMContentLoaded", schedule);
@@ -12844,6 +13192,8 @@ function setupImageMarks(doc, win, post, tracked, enabled) {
12844
13192
  disposed = true;
12845
13193
  mutationObserver?.disconnect();
12846
13194
  resizeObserver?.disconnect();
13195
+ doc.removeEventListener("pointermove", onPointerMove, true);
13196
+ doc.removeEventListener("mouseout", onPointerLeave);
12847
13197
  doc.removeEventListener("load", schedule, true);
12848
13198
  doc.removeEventListener("scroll", schedule, true);
12849
13199
  doc.removeEventListener("DOMContentLoaded", schedule);
@@ -12851,13 +13201,53 @@ function setupImageMarks(doc, win, post, tracked, enabled) {
12851
13201
  win.removeEventListener?.("scroll", schedule);
12852
13202
  entries.clear();
12853
13203
  controls.clear();
13204
+ rows.clear();
12854
13205
  layer?.remove();
12855
13206
  },
12856
13207
  active(id) {
12857
13208
  activeId = id;
13209
+ for (const entry of entries.values())
13210
+ entry.badge?.classList.toggle("is-active", entry.mark.id === id);
13211
+ const entry = id && entries.get(id);
13212
+ if (entry)
13213
+ revealBadge(entry);
13214
+ reportRows();
13215
+ },
13216
+ rect(id) {
13217
+ const entry = entries.get(id);
13218
+ if (!entry)
13219
+ return;
13220
+ const message = { type: "relic:frame-mark-rect", id };
13221
+ if (!entry.badge || !entry.image || entry.declaration !== source(entry.image))
13222
+ return message;
13223
+ const row = rows.get(entry.image);
13224
+ const box = markFrameRect(entry.badge);
13225
+ const clip = row && markFrameRect(row);
13226
+ if (!visible(entry.image) || !box || !clip || row?.hidden)
13227
+ return {
13228
+ ...message,
13229
+ concealed: isRendered(entry.image) ? "offscreen" : "hidden"
13230
+ };
13231
+ if (box.left < clip.left + ROW_PADDING - 0.5 || box.left + box.width > clip.left + clip.width - ROW_PADDING + 0.5)
13232
+ return { ...message, concealed: "offscreen" };
13233
+ return { ...message, rect: box };
13234
+ },
13235
+ repaint() {
13236
+ if (!enabled || disposed)
13237
+ return;
13238
+ refresh();
12858
13239
  },
12859
13240
  quiet(ids) {
12860
13241
  quietIds = new Set(ids);
13242
+ for (const entry of entries.values())
13243
+ entry.badge?.classList.toggle("is-resolved", quietIds.has(entry.mark.id));
13244
+ },
13245
+ pair(id, active) {
13246
+ if (active)
13247
+ pairedIds.add(id);
13248
+ else
13249
+ pairedIds.delete(id);
13250
+ entries.get(id)?.badge?.classList.toggle("is-paired", active);
12861
13251
  },
12862
13252
  paint(mark) {
12863
13253
  if (!enabled) {
@@ -12900,6 +13290,7 @@ function setupImageMarks(doc, win, post, tracked, enabled) {
12900
13290
  if (entries.get(id) !== entry || digest !== entry.mark.source_sha256 || source(img) !== declaration || resolve(entry.mark.path) !== img || !img.isConnected)
12901
13291
  return;
12902
13292
  img.scrollIntoView({ behavior: "smooth", block: "center" });
13293
+ revealBadge(entry);
12903
13294
  schedule();
12904
13295
  }).catch(() => {});
12905
13296
  }
@@ -12924,8 +13315,51 @@ function setupFrameInteraction(doc, win, postOutward, imageControlsEnabled = tru
12924
13315
  let selectionLive = false;
12925
13316
  let trackedId = null;
12926
13317
  let trackRaf;
12927
- const imageMarks = setupImageMarks(doc, win, postOutward, () => trackedId, imageControlsEnabled);
13318
+ let parentVisible = true;
13319
+ let concealScheduled = false;
13320
+ let concealedIds = new Map;
13321
+ let trackedConcealment;
13322
+ const conceal = () => {
13323
+ concealScheduled = false;
13324
+ if (!doc.body || typeof doc.body.querySelectorAll !== "function")
13325
+ return;
13326
+ const marks = [];
13327
+ const overlay = frameRegionOverlays.get(doc);
13328
+ if (overlay) {
13329
+ for (const node of Array.from(overlay.querySelectorAll("[data-comment-id]"))) {
13330
+ marks.push({ node, subject: markSubjects.get(node) ?? node });
13331
+ }
13332
+ }
13333
+ for (const node of Array.from(doc.body.querySelectorAll("mark.relic-text-mark[data-comment-id]"))) {
13334
+ marks.push({ node, subject: node });
13335
+ }
13336
+ concealedIds = concealMarks(marks, watch, parentVisible);
13337
+ const next = trackedId === null ? undefined : concealedIds.get(trackedId);
13338
+ if (next !== trackedConcealment) {
13339
+ trackedConcealment = next;
13340
+ reportTracked();
13341
+ }
13342
+ };
13343
+ const scheduleConceal = () => {
13344
+ if (concealScheduled)
13345
+ return;
13346
+ concealScheduled = true;
13347
+ if (typeof requestAnimationFrame === "function") {
13348
+ requestAnimationFrame(conceal);
13349
+ } else {
13350
+ setTimeout(conceal, 0);
13351
+ }
13352
+ };
13353
+ const watch = createScrollportWatch(scheduleConceal);
13354
+ const imageMarks = setupImageMarks(doc, win, postOutward, () => trackedId, imageControlsEnabled, () => parentVisible);
12928
13355
  const reportMarkRect = (document2, id) => {
13356
+ const image = imageMarks.rect(id);
13357
+ if (image)
13358
+ return image;
13359
+ const concealed = concealedIds.get(id);
13360
+ if (concealed !== undefined) {
13361
+ return { type: "relic:frame-mark-rect", id, concealed };
13362
+ }
12929
13363
  if (!document2.body) {
12930
13364
  return { type: "relic:frame-mark-rect", id };
12931
13365
  }
@@ -13007,6 +13441,7 @@ function setupFrameInteraction(doc, win, postOutward, imageControlsEnabled = tru
13007
13441
  postOutward(report);
13008
13442
  };
13009
13443
  const scheduleTrackReport = () => {
13444
+ scheduleConceal();
13010
13445
  if (trackedId === null)
13011
13446
  return;
13012
13447
  if (typeof requestAnimationFrame === "function") {
@@ -13047,6 +13482,18 @@ function setupFrameInteraction(doc, win, postOutward, imageControlsEnabled = tru
13047
13482
  });
13048
13483
  win.addEventListener("resize", () => scheduleTrackReport());
13049
13484
  }
13485
+ if (typeof doc.addEventListener === "function") {
13486
+ doc.addEventListener("scroll", scheduleConceal, true);
13487
+ doc.addEventListener("toggle", scheduleConceal, true);
13488
+ }
13489
+ const markObserver = typeof MutationObserver === "function" && doc.nodeType === 9 ? new MutationObserver(scheduleConceal) : undefined;
13490
+ markObserver?.observe(doc, {
13491
+ subtree: true,
13492
+ childList: true,
13493
+ attributes: true,
13494
+ attributeFilter: ["class", "hidden", "open", "style"]
13495
+ });
13496
+ scheduleConceal();
13050
13497
  doc.addEventListener("click", (event) => {
13051
13498
  let node = event.target;
13052
13499
  if (node?.nodeType !== 1)
@@ -13073,7 +13520,7 @@ function setupFrameInteraction(doc, win, postOutward, imageControlsEnabled = tru
13073
13520
  if (!armed)
13074
13521
  return;
13075
13522
  const target = event.target;
13076
- if (typeof target?.closest === "function" && target.closest(".relic-region-mark, .relic-pin-mark, .relic-image-control, .relic-image-mark") !== null) {
13523
+ if (typeof target?.closest === "function" && target.closest(".relic-region-mark, .relic-pin-mark, .relic-image-row") !== null) {
13077
13524
  return;
13078
13525
  }
13079
13526
  event.preventDefault?.();
@@ -13319,6 +13766,7 @@ function setupFrameInteraction(doc, win, postOutward, imageControlsEnabled = tru
13319
13766
  }
13320
13767
  },
13321
13768
  onPairMark(id, active) {
13769
+ imageMarks.pair(id, active);
13322
13770
  if (!doc.body)
13323
13771
  return;
13324
13772
  const targets = doc.body.querySelectorAll(`[data-comment-id="${id}"]`);
@@ -13387,28 +13835,44 @@ function setupFrameInteraction(doc, win, postOutward, imageControlsEnabled = tru
13387
13835
  suppressScroll = false;
13388
13836
  }, 50);
13389
13837
  },
13838
+ onFrameVisibility(visible) {
13839
+ parentVisible = visible;
13840
+ imageMarks.repaint();
13841
+ conceal();
13842
+ },
13390
13843
  dispose() {
13391
13844
  imageMarks.dispose();
13845
+ if (typeof doc.removeEventListener === "function") {
13846
+ doc.removeEventListener("scroll", scheduleConceal, true);
13847
+ doc.removeEventListener("toggle", scheduleConceal, true);
13848
+ }
13849
+ watch.disconnect();
13850
+ markObserver?.disconnect();
13392
13851
  }
13393
13852
  };
13394
13853
  }
13395
13854
  function createSandboxHandler(write, writeJsx, annotate = () => {}, interaction) {
13396
13855
  let rendered = false;
13397
13856
  let annotated = false;
13857
+ let cover = false;
13398
13858
  return (data) => {
13399
13859
  if (!rendered) {
13400
13860
  if (isRenderMessage(data)) {
13401
13861
  rendered = true;
13402
- write(data.html);
13862
+ cover = data.cover === true;
13863
+ write(data.html, !cover);
13403
13864
  return true;
13404
13865
  }
13405
13866
  if (isRenderJsxMessage(data)) {
13406
13867
  rendered = true;
13868
+ cover = data.cover === true;
13407
13869
  writeJsx(data.code);
13408
13870
  return true;
13409
13871
  }
13410
13872
  return false;
13411
13873
  }
13874
+ if (cover)
13875
+ return false;
13412
13876
  if (!annotated && isAnnotateMessage(data)) {
13413
13877
  annotated = true;
13414
13878
  annotate(data.marks, data.anchors);
@@ -13440,6 +13904,10 @@ function createSandboxHandler(write, writeJsx, annotate = () => {}, interaction)
13440
13904
  interaction?.onTrackMark?.(data.id);
13441
13905
  return true;
13442
13906
  }
13907
+ if (isFrameVisibilityMessage(data)) {
13908
+ interaction?.onFrameVisibility?.(data.visible);
13909
+ return true;
13910
+ }
13443
13911
  if (isActiveMarkMessage(data)) {
13444
13912
  interaction?.onActiveMark?.(data.id);
13445
13913
  return true;
@@ -13818,10 +14286,10 @@ if (typeof window !== "undefined" && typeof document !== "undefined") {
13818
14286
  };
13819
14287
  installSandboxContainment(window, document);
13820
14288
  let interaction;
13821
- const handle = createSandboxHandler((html) => {
14289
+ const handle = createSandboxHandler((html, imageControlsEnabled) => {
13822
14290
  document.open();
13823
14291
  installSandboxContainment(window, document);
13824
- initInteraction(true);
14292
+ initInteraction(imageControlsEnabled);
13825
14293
  document.write(sanitizeFrameHtml(html));
13826
14294
  document.close();
13827
14295
  listen();
@@ -13845,7 +14313,9 @@ if (typeof window !== "undefined" && typeof document !== "undefined") {
13845
14313
  onRevealMark: (msg) => interaction?.onRevealMark(msg),
13846
14314
  onPairMark: (id, active) => interaction?.onPairMark(id, active),
13847
14315
  onTrackMark: (id) => interaction?.onTrackMark?.(id),
14316
+ onFrameVisibility: (visible) => interaction?.onFrameVisibility?.(visible),
13848
14317
  onActiveMark: (id) => interaction?.onActiveMark?.(id),
14318
+ onQuietMarks: (ids) => interaction?.onQuietMarks?.(ids),
13849
14319
  onRevealChange: (changeId, top) => interaction?.onRevealChange?.(changeId, top),
13850
14320
  onSetScroll: (fraction) => interaction?.onSetScroll?.(fraction)
13851
14321
  });
@@ -6205,3 +6205,23 @@ canvas.relic-page {
6205
6205
  background: var(--diff-added);
6206
6206
  border-left: 2px solid var(--patina);
6207
6207
  }
6208
+
6209
+ /* A mark the viewer does not draw. A hidden overlay leaves layout, because its
6210
+ thing has no box. An offscreen overlay is made transparent and keeps its box
6211
+ and hit testing: reveal measures both after a scroll, before the observer
6212
+ has caught up. A text mark keeps its words and loses only its decoration.
6213
+ The frame carries the same rules in CONCEALMENT_CSS (mark-visibility.ts),
6214
+ because it cannot read this sheet. */
6215
+ [data-concealed='hidden']:not(mark) {
6216
+ display: none !important;
6217
+ }
6218
+
6219
+ [data-concealed='offscreen']:not(mark) {
6220
+ opacity: 0 !important;
6221
+ }
6222
+
6223
+ mark[data-concealed] {
6224
+ background: none !important;
6225
+ border-bottom-color: transparent !important;
6226
+ box-shadow: none !important;
6227
+ }
package/dist/relic-mcp.js CHANGED
@@ -36993,7 +36993,7 @@ var DESCRIBE_TOOL_DEFINITION = {
36993
36993
  additionalProperties: false
36994
36994
  }
36995
36995
  };
36996
- var SERVER_VERSION = "0.14.1";
36996
+ var SERVER_VERSION = "0.14.2";
36997
36997
  var SERVER_INFO = {
36998
36998
  name: "relic",
36999
36999
  title: "Relic",
package/mcp-servers.json CHANGED
@@ -4,10 +4,10 @@
4
4
  "command": "npx",
5
5
  "args": [
6
6
  "-y",
7
- "relic-mcp@0.14.1"
7
+ "relic-mcp@0.14.2"
8
8
  ],
9
9
  "env": {
10
- "RELIC_CLIENT_NAME": "relic-plugin/0.14.1"
10
+ "RELIC_CLIENT_NAME": "relic-plugin/0.14.2"
11
11
  }
12
12
  }
13
13
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "relic-mcp",
3
- "version": "0.14.1",
3
+ "version": "0.14.2",
4
4
  "description": "Publish a file as an encrypted relic. The key is generated on your machine and never sent to the service.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -19,7 +19,7 @@
19
19
  },
20
20
  "nodeApiVersion": 8,
21
21
  "schemaVersion": 3,
22
- "sourceSha256": "7a509ea5074aaf9f22c201ded3db3a811205c2f132d2c1331860d91981b59140",
22
+ "sourceSha256": "4848f49a1af80a1da9be79d16a5555efe709e76e91a34146015874b34e752921",
23
23
  "target": "darwin-arm64",
24
24
  "toolchain": {
25
25
  "binaryDeploymentTarget": "15.0",
@@ -19,7 +19,7 @@
19
19
  },
20
20
  "nodeApiVersion": 8,
21
21
  "schemaVersion": 3,
22
- "sourceSha256": "7a509ea5074aaf9f22c201ded3db3a811205c2f132d2c1331860d91981b59140",
22
+ "sourceSha256": "4848f49a1af80a1da9be79d16a5555efe709e76e91a34146015874b34e752921",
23
23
  "target": "darwin-x64",
24
24
  "toolchain": {
25
25
  "binaryDeploymentTarget": "15.0",
@@ -20,7 +20,7 @@
20
20
  },
21
21
  "nodeApiVersion": 8,
22
22
  "schemaVersion": 3,
23
- "sourceSha256": "7a509ea5074aaf9f22c201ded3db3a811205c2f132d2c1331860d91981b59140",
23
+ "sourceSha256": "4848f49a1af80a1da9be79d16a5555efe709e76e91a34146015874b34e752921",
24
24
  "target": "linux-arm64-gnu",
25
25
  "toolchain": {
26
26
  "compilerPath": "/opt/rh/gcc-toolset-14/root/usr/bin/gcc",
@@ -20,7 +20,7 @@
20
20
  },
21
21
  "nodeApiVersion": 8,
22
22
  "schemaVersion": 3,
23
- "sourceSha256": "7a509ea5074aaf9f22c201ded3db3a811205c2f132d2c1331860d91981b59140",
23
+ "sourceSha256": "4848f49a1af80a1da9be79d16a5555efe709e76e91a34146015874b34e752921",
24
24
  "target": "linux-x64-gnu",
25
25
  "toolchain": {
26
26
  "compilerPath": "/opt/rh/gcc-toolset-14/root/usr/bin/gcc",