relic-mcp 0.14.3 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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.3",
13
+ "version": "0.15.0",
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.3"
24
+ "version": "0.15.0"
25
25
  }
26
26
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "relic",
3
- "version": "0.14.3",
3
+ "version": "0.15.0",
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": {
package/README.md CHANGED
@@ -32,9 +32,9 @@ For any client that takes a JSON config:
32
32
  3. Encrypts locally with AES-128-GCM under RFC 8188 `aes128gcm` framing.
33
33
  4. Uploads **only ciphertext**, straight to object storage under a signed URL.
34
34
  It does not pass through the Relic service.
35
- 5. Tells the service four things: a coarse renderer class from a ten-value
36
- list (`markdown`, `code`, `html`, `jsx`, `image`, `pdf`, `media`, `archive`,
37
- `binary`, `otio`), this client's name, the ciphertext's byte length, and the
35
+ 5. Tells the service four things: a coarse renderer class from an eleven-value
36
+ list (`markdown`, `code`, `html`, `jsx`, `image`, `pdf`, `model`, `media`,
37
+ `archive`, `binary`, `otio`), this client's name, the ciphertext's byte length, and the
38
38
  title, which defaults to the filename and is stored in the clear. By default,
39
39
  it also renders a 1200x630 cover image of the file's first screen in local
40
40
  headless Chrome and uploads it unencrypted via `POST /api/relics/{id}/cover`
@@ -42,6 +42,7 @@ For any client that takes a JSON config:
42
42
  timeline relics (`otio`) skip unencrypted first-screen media rendering by
43
43
  default, using a generic card carrying the title and format label to protect
44
44
  private timeline cuts and media from leaking into unencrypted link previews.
45
+ STL and OBJ models also use a generic card instead of a geometry preview.
45
46
  Not the mimetype, not the contents.
46
47
  6. Records the relic's id, key, and publish token locally, in a 0600 file
47
48
  under your user config directory, so the relic can be republished from
@@ -1202,6 +1202,68 @@ var require_build = __commonJS(function(exports) {
1202
1202
  exports.default = LinesAndColumns;
1203
1203
  });
1204
1204
 
1205
+ // ../relic-viewer/src/content.ts
1206
+ var CONTENT_BATCH_BYTES = 8 * 1024 * 1024;
1207
+ var CONTENT_HEAD_BYTES = 65536;
1208
+ var CONTENT_TAIL_BYTES = 65557;
1209
+ var MAX_CONTENT_READ_BYTES = 16 * 1024 * 1024;
1210
+ var DOCUMENT_RENDER_LIMIT_BYTES = 100 * 1024 * 1024;
1211
+
1212
+ class ContentStoreError extends Error {
1213
+ constructor(cause) {
1214
+ super("The browser could not hold the decrypted content.", { cause });
1215
+ this.name = "ContentStoreError";
1216
+ }
1217
+ }
1218
+ function sliceBlob(blob, start, end, type) {
1219
+ try {
1220
+ return blob.slice(start, end, type);
1221
+ } catch (cause) {
1222
+ throw new ContentStoreError(cause);
1223
+ }
1224
+ }
1225
+ async function readBlob(blob) {
1226
+ try {
1227
+ const bytes = new Uint8Array(await blob.arrayBuffer());
1228
+ if (bytes.length !== blob.size)
1229
+ throw new Error("short Blob read");
1230
+ return bytes;
1231
+ } catch (cause) {
1232
+ throw new ContentStoreError(cause);
1233
+ }
1234
+ }
1235
+ function contentFromParts(base, head, tail) {
1236
+ return {
1237
+ size: base.size,
1238
+ head,
1239
+ tail,
1240
+ blob(type) {
1241
+ return sliceBlob(base, 0, base.size, type);
1242
+ },
1243
+ async read(start, end) {
1244
+ if (!Number.isSafeInteger(start) || !Number.isSafeInteger(end) || start < 0 || end < start || end > base.size || end - start > MAX_CONTENT_READ_BYTES) {
1245
+ throw new RangeError("Invalid or oversized content read");
1246
+ }
1247
+ return readBlob(sliceBlob(base, start, end));
1248
+ },
1249
+ async readWhole(limit) {
1250
+ if (!Number.isSafeInteger(limit) || limit < 0 || limit > DOCUMENT_RENDER_LIMIT_BYTES) {
1251
+ throw new RangeError("Invalid whole-content read limit");
1252
+ }
1253
+ if (base.size > limit)
1254
+ return;
1255
+ return readBlob(base);
1256
+ }
1257
+ };
1258
+ }
1259
+ async function contentFromBlob(blob) {
1260
+ const [head, tail] = await Promise.all([
1261
+ readBlob(sliceBlob(blob, 0, Math.min(blob.size, CONTENT_HEAD_BYTES))),
1262
+ readBlob(sliceBlob(blob, Math.max(0, blob.size - CONTENT_TAIL_BYTES), blob.size))
1263
+ ]);
1264
+ return contentFromParts(blob, head, tail);
1265
+ }
1266
+
1205
1267
  // ../relic-viewer/src/markdown.ts
1206
1268
  function escapeHtml(value) {
1207
1269
  return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;");
@@ -1300,8 +1362,15 @@ function renderMarkdown(source) {
1300
1362
  flushParagraph();
1301
1363
  const ordered = /^\s*\d+\./.test(line);
1302
1364
  const items = [];
1303
- while (index < lines.length && /^\s*([-*+]|\d+\.)\s+/.test(lines[index])) {
1304
- items.push(lines[index].replace(/^\s*([-*+]|\d+\.)\s+/, ""));
1365
+ while (index < lines.length) {
1366
+ const current = lines[index];
1367
+ if (/^\s*([-*+]|\d+\.)\s+/.test(current)) {
1368
+ items.push(current.replace(/^\s*([-*+]|\d+\.)\s+/, ""));
1369
+ } else if (/^\s+\S/.test(current) && !/^\s*(>|```)/.test(current)) {
1370
+ items[items.length - 1] += ` ${current.trim()}`;
1371
+ } else {
1372
+ break;
1373
+ }
1305
1374
  index++;
1306
1375
  }
1307
1376
  const tag = ordered ? "ol" : "ul";
@@ -44719,6 +44788,81 @@ globalThis.pdfjsLib = {
44719
44788
  XfaLayer
44720
44789
  };
44721
44790
 
44791
+ // ../relic-viewer/src/pdf.ts
44792
+ var PDF_INITIAL_BYTES = 1024 * 1024;
44793
+ var PDF_RANGE_CHUNK_SIZE = 65536;
44794
+
44795
+ class RelicPdfRangeTransport extends PDFDataRangeTransport {
44796
+ content;
44797
+ onError;
44798
+ aborted = false;
44799
+ failed = false;
44800
+ constructor(content, initialData, onError) {
44801
+ super(content.size, initialData);
44802
+ this.content = content;
44803
+ this.onError = onError;
44804
+ }
44805
+ requestDataRange(begin, end) {
44806
+ if (this.aborted || this.failed)
44807
+ return;
44808
+ this.readRange(begin, end).then((bytes) => {
44809
+ if (!this.aborted && !this.failed)
44810
+ this.onDataRange(begin, bytes);
44811
+ }).catch((error) => {
44812
+ if (this.aborted || this.failed)
44813
+ return;
44814
+ this.failed = true;
44815
+ this.onError(error);
44816
+ });
44817
+ }
44818
+ abort() {
44819
+ this.aborted = true;
44820
+ }
44821
+ async readRange(begin, end) {
44822
+ if (!Number.isSafeInteger(begin) || !Number.isSafeInteger(end) || begin < 0 || end < begin || end > this.content.size) {
44823
+ throw new RangeError("Invalid PDF range");
44824
+ }
44825
+ const length = end - begin;
44826
+ if (length <= MAX_CONTENT_READ_BYTES) {
44827
+ const bytes = await this.content.read(begin, end);
44828
+ if (bytes.byteLength !== length)
44829
+ throw new Error("Short PDF range read");
44830
+ return bytes;
44831
+ }
44832
+ const bytes = new Uint8Array(length);
44833
+ for (let start = begin;start < end; start += MAX_CONTENT_READ_BYTES) {
44834
+ if (this.aborted)
44835
+ return new Uint8Array(0);
44836
+ const stop = Math.min(start + MAX_CONTENT_READ_BYTES, end);
44837
+ const chunk = await this.content.read(start, stop);
44838
+ if (chunk.byteLength !== stop - start)
44839
+ throw new Error("Short PDF range read");
44840
+ bytes.set(chunk, start - begin);
44841
+ }
44842
+ return bytes;
44843
+ }
44844
+ }
44845
+ async function createPdfLoadingTask(content, signal) {
44846
+ const initialData = await content.read(0, Math.min(content.size, PDF_INITIAL_BYTES));
44847
+ if (signal?.aborted)
44848
+ throw new Error("PDF loading cancelled");
44849
+ if (initialData.byteLength !== Math.min(content.size, PDF_INITIAL_BYTES))
44850
+ throw new Error("Short PDF initial read");
44851
+ const { promise: failure, reject: rejectFailure } = Promise.withResolvers();
44852
+ failure.catch(() => {
44853
+ return;
44854
+ });
44855
+ const range = new RelicPdfRangeTransport(content, initialData, rejectFailure);
44856
+ const task = getDocument({
44857
+ range,
44858
+ length: content.size,
44859
+ rangeChunkSize: PDF_RANGE_CHUNK_SIZE,
44860
+ disableAutoFetch: true,
44861
+ disableStream: true
44862
+ });
44863
+ return { task, failure };
44864
+ }
44865
+
44722
44866
  // ../relic-viewer/src/cover/pdf-trim.ts
44723
44867
  var PDF_GUTTER_PX = 20;
44724
44868
  var PDF_CANVAS_WIDTH = 600;
@@ -44771,21 +44915,21 @@ function trimPdfPage(ink, pageWidth) {
44771
44915
  // ../relic-viewer/src/cover/pdf.ts
44772
44916
  var PDF_CANVAS_HEIGHT = 315;
44773
44917
  var PDF_DEVICE_SCALE = 2;
44774
- var workerInitialized = false;
44775
44918
  function ensurePdfWorker() {
44776
- if (!workerInitialized) {
44777
- GlobalWorkerOptions.workerPort = new Worker("/pdf.worker.mjs", {
44778
- type: "module"
44779
- });
44780
- workerInitialized = true;
44781
- }
44919
+ GlobalWorkerOptions.workerSrc = "/pdf.worker.mjs";
44782
44920
  }
44783
- async function renderPdfCover(bytes, root) {
44921
+ async function renderPdfCover(content, root) {
44784
44922
  ensurePdfWorker();
44785
- let pdfDoc;
44786
44923
  try {
44787
- const loadingTask = getDocument({ data: bytes });
44788
- pdfDoc = await loadingTask.promise;
44924
+ const { task, failure } = await createPdfLoadingTask(content);
44925
+ try {
44926
+ const pdfDoc = await Promise.race([task.promise, failure]);
44927
+ return await renderPdfCoverDocument(pdfDoc, root, failure);
44928
+ } finally {
44929
+ await task.destroy().catch(() => {
44930
+ return;
44931
+ });
44932
+ }
44789
44933
  } catch (err) {
44790
44934
  return {
44791
44935
  ok: false,
@@ -44793,9 +44937,11 @@ async function renderPdfCover(bytes, root) {
44793
44937
  detail: err instanceof Error ? err.message : String(err)
44794
44938
  };
44795
44939
  }
44940
+ }
44941
+ async function renderPdfCoverDocument(pdfDoc, root, failure) {
44796
44942
  let page;
44797
44943
  try {
44798
- page = await pdfDoc.getPage(1);
44944
+ page = await Promise.race([pdfDoc.getPage(1), failure]);
44799
44945
  } catch (err) {
44800
44946
  return {
44801
44947
  ok: false,
@@ -44820,11 +44966,14 @@ async function renderPdfCover(bytes, root) {
44820
44966
  };
44821
44967
  }
44822
44968
  try {
44823
- await page.render({
44824
- canvasContext: measureCtx,
44825
- viewport: refViewport,
44826
- canvas: measureCanvas
44827
- }).promise;
44969
+ await Promise.race([
44970
+ page.render({
44971
+ canvasContext: measureCtx,
44972
+ viewport: refViewport,
44973
+ canvas: measureCanvas
44974
+ }).promise,
44975
+ failure
44976
+ ]);
44828
44977
  } catch (err) {
44829
44978
  return {
44830
44979
  ok: false,
@@ -44857,11 +45006,14 @@ async function renderPdfCover(bytes, root) {
44857
45006
  };
44858
45007
  }
44859
45008
  try {
44860
- await page.render({
44861
- canvasContext: renderCtx,
44862
- viewport: finalViewport,
44863
- canvas: renderCanvas
44864
- }).promise;
45009
+ await Promise.race([
45010
+ page.render({
45011
+ canvasContext: renderCtx,
45012
+ viewport: finalViewport,
45013
+ canvas: renderCanvas
45014
+ }).promise,
45015
+ failure
45016
+ ]);
44865
45017
  } catch (err) {
44866
45018
  return {
44867
45019
  ok: false,
@@ -44889,7 +45041,7 @@ async function fetchInputBlob(input) {
44889
45041
  if (!res.ok) {
44890
45042
  throw new Error(`Failed to fetch input part ${i}: ${res.status}`);
44891
45043
  }
44892
- parts.push(await res.arrayBuffer());
45044
+ parts.push(await res.blob());
44893
45045
  }
44894
45046
  return new Blob(parts, { type: input.mimetype });
44895
45047
  }
@@ -45077,8 +45229,7 @@ async function renderKind(request, root) {
45077
45229
  }
45078
45230
  }
45079
45231
  case "pdf": {
45080
- const arrayBuffer = await blob.arrayBuffer();
45081
- return renderPdfCover(new Uint8Array(arrayBuffer), root);
45232
+ return renderPdfCover(await contentFromBlob(blob), root);
45082
45233
  }
45083
45234
  }
45084
45235
  }
@@ -11390,15 +11390,91 @@ var MAX_HEADER_BYTES = 2 + (2 + MAX_FILENAME_BYTES + 2 + MAX_MIMETYPE_BYTES + 8
11390
11390
  var encoder = new TextEncoder;
11391
11391
  var decoder = new TextDecoder("utf-8", { fatal: false });
11392
11392
 
11393
+ // ../relic-format/src/limits.ts
11394
+ var PLAINTEXT_CAP_BYTES = 500 * 1024 * 1024;
11395
+
11393
11396
  // ../relic-format/src/rfc8188.ts
11394
11397
  var TAG_BYTES = 16;
11395
11398
  var DELIMITER_BYTES = 1;
11396
11399
  var RECORD_OVERHEAD = TAG_BYTES + DELIMITER_BYTES;
11397
11400
  var CEK_INFO = new TextEncoder().encode("Content-Encoding: aes128gcm\x00");
11398
11401
  var NONCE_INFO = new TextEncoder().encode("Content-Encoding: nonce\x00");
11399
-
11400
- // ../relic-format/src/container.ts
11401
- var PLAINTEXT_CAP_BYTES = 100 * 1024 * 1024;
11402
+ // ../relic-format/src/crc32c.ts
11403
+ var CRC32C_TABLE = new Uint32Array(256);
11404
+ for (let index = 0;index < 256; index++) {
11405
+ let value = index;
11406
+ for (let bit = 0;bit < 8; bit++) {
11407
+ value = value & 1 ? value >>> 1 ^ 2197175160 : value >>> 1;
11408
+ }
11409
+ CRC32C_TABLE[index] = value >>> 0;
11410
+ }
11411
+ // ../relic-format/src/sha256.ts
11412
+ var K = new Uint32Array([
11413
+ 1116352408,
11414
+ 1899447441,
11415
+ 3049323471,
11416
+ 3921009573,
11417
+ 961987163,
11418
+ 1508970993,
11419
+ 2453635748,
11420
+ 2870763221,
11421
+ 3624381080,
11422
+ 310598401,
11423
+ 607225278,
11424
+ 1426881987,
11425
+ 1925078388,
11426
+ 2162078206,
11427
+ 2614888103,
11428
+ 3248222580,
11429
+ 3835390401,
11430
+ 4022224774,
11431
+ 264347078,
11432
+ 604807628,
11433
+ 770255983,
11434
+ 1249150122,
11435
+ 1555081692,
11436
+ 1996064986,
11437
+ 2554220882,
11438
+ 2821834349,
11439
+ 2952996808,
11440
+ 3210313671,
11441
+ 3336571891,
11442
+ 3584528711,
11443
+ 113926993,
11444
+ 338241895,
11445
+ 666307205,
11446
+ 773529912,
11447
+ 1294757372,
11448
+ 1396182291,
11449
+ 1695183700,
11450
+ 1986661051,
11451
+ 2177026350,
11452
+ 2456956037,
11453
+ 2730485921,
11454
+ 2820302411,
11455
+ 3259730800,
11456
+ 3345764771,
11457
+ 3516065817,
11458
+ 3600352804,
11459
+ 4094571909,
11460
+ 275423344,
11461
+ 430227734,
11462
+ 506948616,
11463
+ 659060556,
11464
+ 883997877,
11465
+ 958139571,
11466
+ 1322822218,
11467
+ 1537002063,
11468
+ 1747873779,
11469
+ 1955562222,
11470
+ 2024104815,
11471
+ 2227730452,
11472
+ 2361852424,
11473
+ 2428436474,
11474
+ 2756734187,
11475
+ 3204031479,
11476
+ 3329325298
11477
+ ]);
11402
11478
  // ../relic-format/src/id.ts
11403
11479
  var RESERVED_SEGMENTS = [
11404
11480
  "abuse",
@@ -11431,7 +11507,10 @@ function foldAliases(lowered) {
11431
11507
  return out;
11432
11508
  }
11433
11509
  // ../relic-format/src/otio-zip.ts
11434
- var MAX_EXPANDED_ZIP_BYTES = 100 * 1024 * 1024;
11510
+ var MAX_INFLATED_ZIP_BYTES = 100 * 1024 * 1024;
11511
+ var OTIO_DOCUMENT_MAX_BYTES = 100 * 1024 * 1024;
11512
+ var MAX_ZIP_DIRECTORY_BYTES = 16 * 1024 * 1024;
11513
+ var ZIP_READ_CHUNK_BYTES = 8 * 1024 * 1024;
11435
11514
  var MAX_OTIO_BUNDLE_MEDIA_ENTRIES = 65535 - 3;
11436
11515
  var CRC_TABLE = new Uint32Array(256);
11437
11516
  for (let i = 0;i < 256; i++) {
@@ -20,6 +20,11 @@
20
20
  * JavaScript code in this page
21
21
  */
22
22
 
23
+ /**
24
+ * Modified by The Bushido Collective: stop indirect-object lookahead at endobj
25
+ * to avoid scanning the following whitespace in range-loaded PDFs.
26
+ */
27
+
23
28
  /**
24
29
  * pdfjsVersion = 5.4.149
25
30
  * pdfjsBuild = 9e2e9e209
@@ -11481,6 +11486,71 @@ class Lexer {
11481
11486
  }
11482
11487
  }
11483
11488
  }
11489
+ class IndirectObjectLexer extends Lexer {
11490
+ // Stop before lookahead reads beyond an indirect object's top-level endobj.
11491
+ // Dictionary entries alternate between names and values; a closer in value
11492
+ // position is a value, and a mismatched closer never closes a container.
11493
+ #openers = [];
11494
+ #ended = false;
11495
+ #tracking = true;
11496
+ getObj() {
11497
+ if (this.#ended) {
11498
+ return EOF;
11499
+ }
11500
+ const obj = super.getObj();
11501
+ if (!this.#tracking) {
11502
+ return obj;
11503
+ }
11504
+ // Inline images have their own parser and delimiter rules.
11505
+ if (obj instanceof Cmd && obj.cmd === "BI") {
11506
+ this.#openers.length = 0;
11507
+ this.#tracking = false;
11508
+ return obj;
11509
+ }
11510
+ const top = this.#openers.at(-1);
11511
+ if (top === "<<") {
11512
+ if (obj instanceof Name) {
11513
+ this.#openers[this.#openers.length - 1] = "<<value";
11514
+ }
11515
+ if (!(obj instanceof Cmd) || obj.cmd !== ">>") {
11516
+ return obj;
11517
+ }
11518
+ } else if (top === "<<value") {
11519
+ this.#openers[this.#openers.length - 1] = "<<";
11520
+ if (obj instanceof Cmd && (obj.cmd === ">>" || obj.cmd === "]")) {
11521
+ return obj;
11522
+ }
11523
+ }
11524
+ if (obj instanceof Cmd) {
11525
+ switch (obj.cmd) {
11526
+ case "<<":
11527
+ case "[":
11528
+ // Fall back to the original lexer on excessive nesting.
11529
+ if (this.#openers.length === 1024) {
11530
+ this.#openers.length = 0;
11531
+ this.#tracking = false;
11532
+ } else {
11533
+ this.#openers.push(obj.cmd);
11534
+ }
11535
+ break;
11536
+ case ">>":
11537
+ if (top === "<<") {
11538
+ this.#openers.pop();
11539
+ }
11540
+ break;
11541
+ case "]":
11542
+ if (top === "[") {
11543
+ this.#openers.pop();
11544
+ }
11545
+ break;
11546
+ case "endobj":
11547
+ this.#ended = this.#openers.length === 0;
11548
+ break;
11549
+ }
11550
+ }
11551
+ return obj;
11552
+ }
11553
+ }
11484
11554
  class Linearization {
11485
11555
  static create(stream) {
11486
11556
  function getInt(linDict, name, allowZeroValue = false) {
@@ -11505,7 +11575,7 @@ class Linearization {
11505
11575
  throw new Error("Hint array in the linearization dictionary is invalid.");
11506
11576
  }
11507
11577
  const parser = new Parser({
11508
- lexer: new Lexer(stream),
11578
+ lexer: new IndirectObjectLexer(stream),
11509
11579
  xref: null
11510
11580
  });
11511
11581
  const obj1 = parser.getObj();
@@ -54884,7 +54954,7 @@ class XRef {
54884
54954
  startXRefParsedCache.add(startXRef);
54885
54955
  stream.pos = startXRef + stream.start;
54886
54956
  const parser = new Parser({
54887
- lexer: new Lexer(stream),
54957
+ lexer: new IndirectObjectLexer(stream),
54888
54958
  xref: this,
54889
54959
  allowStreams: true
54890
54960
  });
@@ -55002,7 +55072,7 @@ class XRef {
55002
55072
  }
55003
55073
  const stream = this.stream.makeSubStream(xrefEntry.offset + this.stream.start);
55004
55074
  const parser = new Parser({
55005
- lexer: new Lexer(stream),
55075
+ lexer: new IndirectObjectLexer(stream),
55006
55076
  xref: this,
55007
55077
  allowStreams: true
55008
55078
  });