relic-mcp 0.15.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/README.md +67 -1
- package/dist/cover/cover.js +42 -15
- package/dist/cover/frame.html +21 -2
- package/dist/cover/styles.css +547 -26
- package/dist/native-secure-open.d.ts +1 -1
- package/dist/native-secure-open.js +8 -9
- package/dist/program/index.html +4 -0
- package/dist/program/index.js +35403 -0
- package/dist/relic-mcp.js +6076 -676
- package/mcp-servers.json +2 -2
- package/native/build.mjs +164 -0
- package/native/secure-enclave/relic-se.swift +243 -0
- package/native/secure-open.c +39 -39
- package/native/secure-open.d.ts +1 -1
- package/package.json +2 -2
- package/prebuilds/darwin-arm64/build-info.json +2 -2
- package/prebuilds/darwin-arm64/relic-se +0 -0
- package/prebuilds/darwin-arm64/relic-se-build-info.json +44 -0
- package/prebuilds/darwin-arm64/secure_open.node +0 -0
- package/prebuilds/darwin-x64/build-info.json +2 -2
- package/prebuilds/darwin-x64/relic-se +0 -0
- package/prebuilds/darwin-x64/relic-se-build-info.json +44 -0
- package/prebuilds/darwin-x64/secure_open.node +0 -0
- package/prebuilds/linux-arm64-gnu/build-info.json +2 -2
- package/prebuilds/linux-arm64-gnu/secure_open.node +0 -0
- package/prebuilds/linux-x64-gnu/build-info.json +2 -2
- package/prebuilds/linux-x64-gnu/secure_open.node +0 -0
- package/scripts/prove-packed-native.mjs +42 -3
- package/skills/relic/SKILL.md +39 -0
- package/src/comments.ts +138 -24
- package/src/cover/chrome.ts +46 -18
- package/src/cover/inputs.ts +1 -1
- package/src/decisions.ts +13 -7
- package/src/device.ts +1196 -0
- package/src/files.ts +2 -2
- package/src/index.ts +25 -0
- package/src/native-secure-open.ts +13 -13
- package/src/otio-bundle.ts +411 -144
- package/src/private.ts +1322 -0
- package/src/program/browser.ts +124 -0
- package/src/program/build.ts +1339 -0
- package/src/program/confine.ts +162 -0
- package/src/program/jail-probe.ts +192 -0
- package/src/program/policy.ts +128 -0
- package/src/program/ramdisk.ts +371 -0
- package/src/program/render.ts +793 -0
- package/src/program/report.ts +92 -0
- package/src/publish.ts +136 -9
- package/src/republish.ts +65 -4
- package/src/secure-enclave.ts +228 -0
- package/src/server.ts +588 -12
- package/src/share.ts +35 -1
- package/src/state.ts +201 -47
|
@@ -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.
|
|
13
|
+
"version": "0.16.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.
|
|
24
|
+
"version": "0.16.0"
|
|
25
25
|
}
|
|
26
26
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "relic",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.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
|
@@ -100,7 +100,10 @@ created `0600` inside a `0700` directory, and redirectable with
|
|
|
100
100
|
Copying the state file to another machine moves the ability with it.
|
|
101
101
|
- **The file holds the key and the token.** They are never printed, logged,
|
|
102
102
|
or returned by any tool result. The service stores only a SHA-256 of the
|
|
103
|
-
token, so it cannot reconstruct either one.
|
|
103
|
+
token, so it cannot reconstruct either one. On this machine the version keys
|
|
104
|
+
are plaintext in the file. Other users cannot read it (`0600`), but any
|
|
105
|
+
process running as this user can, so the file is protected only from other
|
|
106
|
+
users and from the service.
|
|
104
107
|
- **Losing the file changes nothing for existing links**; it only ends that
|
|
105
108
|
machine's ability to update those relics.
|
|
106
109
|
- **A takedown is permanent.** A removed relic answers `relic_removed`
|
|
@@ -210,6 +213,66 @@ flags (`--host-resolver-rules=MAP * ~NOTFOUND`,
|
|
|
210
213
|
`--webrtc-ip-handling-policy=disable_non_proxied_udp`, `--disable-quic`) so no
|
|
211
214
|
relic plaintext leaves the machine during rendering.
|
|
212
215
|
|
|
216
|
+
### OTIO programs
|
|
217
|
+
|
|
218
|
+
Publishing or republishing an `.otioz` archive, or a `.otiod` directory packaged
|
|
219
|
+
into one, also renders each OpenTimelineIO timeline to an H.264/AAC MP4 in the
|
|
220
|
+
same local headless browser shell. The render runs in memory: the browser context is
|
|
221
|
+
off-the-record, the HTTP cache is disabled, and the page can reach only the
|
|
222
|
+
archive's own media through the virtual origin
|
|
223
|
+
(`https://program.relic.invalid`). The MP4s and a `relic/program.json` manifest
|
|
224
|
+
go into the archive before encryption, so the program is encrypted like the
|
|
225
|
+
source. `content.otio` and every media entry keep their bytes.
|
|
226
|
+
|
|
227
|
+
A timeline that cannot be rendered is published focus-only, and the publish
|
|
228
|
+
result says why for each one: no browser, no H.264/AAC encoder, an unsupported
|
|
229
|
+
effect, codec, transition, schema, or timing, missing media, or the size cap.
|
|
230
|
+
The result's `program` field reports the status and every timeline. A bare
|
|
231
|
+
`.otio` file has no archive to carry a program and publishes as given. No
|
|
232
|
+
browser or encoder is installed or downloaded, and nothing is sent to a remote
|
|
233
|
+
renderer.
|
|
234
|
+
|
|
235
|
+
Programs render only on macOS in this version. On Linux no browser starts, and
|
|
236
|
+
every OTIO timeline is published focus-only with the reason "program rendering is
|
|
237
|
+
confined only on macOS in this version, so this timeline has no program".
|
|
238
|
+
|
|
239
|
+
On macOS each publish creates a private RAM volume and runs the headless shell
|
|
240
|
+
under one deny-default macOS sandbox profile that the kernel applies to every
|
|
241
|
+
process the shell starts. Chromium's own sandbox is off, since it cannot start
|
|
242
|
+
inside that profile, so the profile is the containment. The profile allows
|
|
243
|
+
writes only to the RAM volume; reads only of the system libraries, fonts, and
|
|
244
|
+
the shell's own directory; no network; no home directory; and executing only the
|
|
245
|
+
shell. GPU and hardware video decoding are disabled. Before every launch a probe
|
|
246
|
+
runs unconfined,
|
|
247
|
+
which must succeed, and then confined, where each outside write, read, exec and
|
|
248
|
+
connection must be refused and the inside write must succeed. Any deviation, or
|
|
249
|
+
a volume that cannot be created, means no browser starts and every timeline is
|
|
250
|
+
focus-only with that reason. The browser gets a minimal environment: `HOME`,
|
|
251
|
+
temporary and cache directories inside the volume, and a fixed `PATH`. Nothing
|
|
252
|
+
else is inherited.
|
|
253
|
+
|
|
254
|
+
After rendering, the process unmounts and detaches its volume and removes only
|
|
255
|
+
the device and directory it created. SIGINT and SIGTERM abort active renders and
|
|
256
|
+
wait for browser and volume cleanup before exit. SIGKILL, power loss, and other
|
|
257
|
+
forced termination cannot run cleanup; the RAM mount may persist until system
|
|
258
|
+
cleanup or reboot. If storage this process made cannot be released, the publish
|
|
259
|
+
is refused. Reasons in the archive are fixed sentences,
|
|
260
|
+
never local paths or environment values. The archive always carries its
|
|
261
|
+
manifest, so a reader never falls back to a full composite the publisher did not
|
|
262
|
+
select. An archive that already contains `relic/` entries, a bad
|
|
263
|
+
`RELIC_PROGRAM_MAX_BYTES`, an unsafe entry name, or a manifest that cannot fit
|
|
264
|
+
the cap is refused with an exact reason rather than published without one.
|
|
265
|
+
|
|
266
|
+
What the sandbox does not cover: with Chromium's sandbox off, one profile covers
|
|
267
|
+
every process the shell starts, so an exploit in a media decoder reaches
|
|
268
|
+
everything that profile allows, though nothing beyond it. Seatbelt does not filter
|
|
269
|
+
every system call, and its profile language is private and deprecated, so a
|
|
270
|
+
future macOS release may change it; the per-launch probe then fails closed. The
|
|
271
|
+
macOS crash reporter sits outside the profile and can write crash details,
|
|
272
|
+
including strings a compromised process chose, to `~/Library/Logs/DiagnosticReports`.
|
|
273
|
+
No claim is made about swap. Anything readable inside the volume, such as the
|
|
274
|
+
host name, can appear in the published MP4.
|
|
275
|
+
|
|
213
276
|
## Tools
|
|
214
277
|
|
|
215
278
|
| Tool | Input | Notes |
|
|
@@ -238,6 +301,9 @@ relic plaintext leaves the machine during rendering.
|
|
|
238
301
|
| `RELIC_MCP_PORT`, `RELIC_MCP_HOST` | HTTP bind. Defaults to `127.0.0.1:7333`. |
|
|
239
302
|
| `RELIC_MCP_ALLOWED_ORIGINS` | Comma-separated `Origin` allowlist for HTTP. |
|
|
240
303
|
| `RELIC_CHROME_PATH` | Explicit path to Chrome, Chromium, or Edge binary for cover rendering. Set to `none` or `disabled` to skip covers. |
|
|
304
|
+
| `RELIC_OTIO_CHROME_PATH` | Path to a `chrome-headless-shell` build whose file layout matches the r1228 headless shell, for OTIO program rendering on macOS. Unset, the newest one under `~/.omp/owned-shells` is used. Any other file or layout is refused. Full Chrome is never used for programs. Set to `none` or `disabled` to publish OTIO without programs. |
|
|
305
|
+
| `RELIC_PROGRAM_MAX_BYTES` | Lowers the program budget for the whole archive, never raises it. Must be a positive integer. A timeline that does not fit is focus-only with the reason `size-cap-exceeded`. |
|
|
306
|
+
| `RELIC_PROGRAM_ENCODER_OFF` | `1` makes the page report its H.264/AAC encoder as absent. Used by proofs, to exercise the focus-only path. |
|
|
241
307
|
| `RELIC_FFMPEG_PATH` | Path to ffmpeg binary for generating video frame and audio waveform/artwork cover images. |
|
|
242
308
|
|
|
243
309
|
## Protocol
|
package/dist/cover/cover.js
CHANGED
|
@@ -1302,7 +1302,7 @@ function inline(source) {
|
|
|
1302
1302
|
});
|
|
1303
1303
|
return out.split(SENTINEL).join("");
|
|
1304
1304
|
}
|
|
1305
|
-
function
|
|
1305
|
+
function parseMarkdown(source) {
|
|
1306
1306
|
const lines = source.replace(/\r\n?/g, `
|
|
1307
1307
|
`).split(`
|
|
1308
1308
|
`);
|
|
@@ -1312,7 +1312,7 @@ function renderMarkdown(source) {
|
|
|
1312
1312
|
const flushParagraph = () => {
|
|
1313
1313
|
if (paragraph.length === 0)
|
|
1314
1314
|
return;
|
|
1315
|
-
out.push(
|
|
1315
|
+
out.push({ kind: "paragraph", text: paragraph.join(" ") });
|
|
1316
1316
|
paragraph = [];
|
|
1317
1317
|
};
|
|
1318
1318
|
while (index < lines.length) {
|
|
@@ -1328,22 +1328,24 @@ function renderMarkdown(source) {
|
|
|
1328
1328
|
index++;
|
|
1329
1329
|
}
|
|
1330
1330
|
index++;
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
`))}</code></pre>`);
|
|
1331
|
+
out.push({ kind: "code", language, text: body.join(`
|
|
1332
|
+
`) });
|
|
1334
1333
|
continue;
|
|
1335
1334
|
}
|
|
1336
1335
|
const heading = /^(#{1,6})\s+(.*)$/.exec(line);
|
|
1337
1336
|
if (heading !== null) {
|
|
1338
1337
|
flushParagraph();
|
|
1339
|
-
|
|
1340
|
-
|
|
1338
|
+
out.push({
|
|
1339
|
+
kind: "heading",
|
|
1340
|
+
level: heading[1].length,
|
|
1341
|
+
text: heading[2]
|
|
1342
|
+
});
|
|
1341
1343
|
index++;
|
|
1342
1344
|
continue;
|
|
1343
1345
|
}
|
|
1344
1346
|
if (/^\s*(-\s*-\s*-|\*\s*\*\s*\*|_\s*_\s*_)[-*_\s]*$/.test(line)) {
|
|
1345
1347
|
flushParagraph();
|
|
1346
|
-
out.push("
|
|
1348
|
+
out.push({ kind: "rule" });
|
|
1347
1349
|
index++;
|
|
1348
1350
|
continue;
|
|
1349
1351
|
}
|
|
@@ -1354,8 +1356,8 @@ function renderMarkdown(source) {
|
|
|
1354
1356
|
quoted.push(lines[index].replace(/^\s*>\s?/, ""));
|
|
1355
1357
|
index++;
|
|
1356
1358
|
}
|
|
1357
|
-
out.push(
|
|
1358
|
-
`))}
|
|
1359
|
+
out.push({ kind: "quote", blocks: parseMarkdown(quoted.join(`
|
|
1360
|
+
`)) });
|
|
1359
1361
|
continue;
|
|
1360
1362
|
}
|
|
1361
1363
|
if (/^\s*([-*+]|\d+\.)\s+/.test(line)) {
|
|
@@ -1373,9 +1375,7 @@ function renderMarkdown(source) {
|
|
|
1373
1375
|
}
|
|
1374
1376
|
index++;
|
|
1375
1377
|
}
|
|
1376
|
-
|
|
1377
|
-
const body = items.map((item) => `<li>${inline(item)}</li>`).join("");
|
|
1378
|
-
out.push(`<${tag}>${body}</${tag}>`);
|
|
1378
|
+
out.push({ kind: "list", ordered, items });
|
|
1379
1379
|
continue;
|
|
1380
1380
|
}
|
|
1381
1381
|
if (line.includes("|") && isTableSeparator(lines[index + 1])) {
|
|
@@ -1387,7 +1387,7 @@ function renderMarkdown(source) {
|
|
|
1387
1387
|
rows.push(splitRow(lines[index]));
|
|
1388
1388
|
index++;
|
|
1389
1389
|
}
|
|
1390
|
-
out.push(
|
|
1390
|
+
out.push({ kind: "table", header, rows });
|
|
1391
1391
|
continue;
|
|
1392
1392
|
}
|
|
1393
1393
|
if (line.trim().length === 0) {
|
|
@@ -1399,7 +1399,34 @@ function renderMarkdown(source) {
|
|
|
1399
1399
|
index++;
|
|
1400
1400
|
}
|
|
1401
1401
|
flushParagraph();
|
|
1402
|
-
return out
|
|
1402
|
+
return out;
|
|
1403
|
+
}
|
|
1404
|
+
function renderBlock(block) {
|
|
1405
|
+
switch (block.kind) {
|
|
1406
|
+
case "heading":
|
|
1407
|
+
return `<h${block.level}>${inline(block.text)}</h${block.level}>`;
|
|
1408
|
+
case "paragraph":
|
|
1409
|
+
return `<p>${inline(block.text)}</p>`;
|
|
1410
|
+
case "code": {
|
|
1411
|
+
const attribute = block.language.length > 0 ? ` data-language="${escapeHtml(block.language)}"` : "";
|
|
1412
|
+
return `<pre${attribute}><code>${escapeHtml(block.text)}</code></pre>`;
|
|
1413
|
+
}
|
|
1414
|
+
case "rule":
|
|
1415
|
+
return "<hr>";
|
|
1416
|
+
case "quote":
|
|
1417
|
+
return `<blockquote>${block.blocks.map(renderBlock).join(`
|
|
1418
|
+
`)}</blockquote>`;
|
|
1419
|
+
case "list": {
|
|
1420
|
+
const tag = block.ordered ? "ol" : "ul";
|
|
1421
|
+
const body = block.items.map((item) => `<li>${inline(item)}</li>`);
|
|
1422
|
+
return `<${tag}>${body.join("")}</${tag}>`;
|
|
1423
|
+
}
|
|
1424
|
+
case "table":
|
|
1425
|
+
return '<div class="md-scroll"><table><thead><tr>' + block.header.map((cell) => `<th>${inline(cell)}</th>`).join("") + "</tr></thead><tbody>" + block.rows.map((row) => `<tr>${row.map((cell) => `<td>${inline(cell)}</td>`).join("")}</tr>`).join("") + "</tbody></table></div>";
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
function renderMarkdown(source) {
|
|
1429
|
+
return parseMarkdown(source).map(renderBlock).join(`
|
|
1403
1430
|
`);
|
|
1404
1431
|
}
|
|
1405
1432
|
function isTableSeparator(line) {
|
package/dist/cover/frame.html
CHANGED
|
@@ -11532,6 +11532,12 @@ var documentSequences = new WeakMap;
|
|
|
11532
11532
|
|
|
11533
11533
|
// ../relic-format/src/otio-identity.ts
|
|
11534
11534
|
var mediaWitnessCache = new WeakMap;
|
|
11535
|
+
// ../relic-format/src/otio-program.ts
|
|
11536
|
+
var MAX_OTIO_PROGRAM_MANIFEST_BYTES = 16 * 1024 * 1024;
|
|
11537
|
+
var OTIO_PROGRAM_MAX_BYTES = 100 * 1024 * 1024;
|
|
11538
|
+
var UTF8 = new TextEncoder;
|
|
11539
|
+
var MAX_FRAMES = Number.MAX_SAFE_INTEGER;
|
|
11540
|
+
var MAX_AUDIO_SAMPLES = Number.MAX_SAFE_INTEGER;
|
|
11535
11541
|
// ../relic-format/src/share.ts
|
|
11536
11542
|
var SHARE_PROOF_INFO = new TextEncoder().encode("relic/share-proof/v1");
|
|
11537
11543
|
// ../relic-viewer/src/sandbox.ts
|
|
@@ -11881,12 +11887,25 @@ function createScrollportWatch(onChange) {
|
|
|
11881
11887
|
}
|
|
11882
11888
|
function concealMarks(marks, watch, live = true) {
|
|
11883
11889
|
watch.sync(marks.map((mark) => mark.node));
|
|
11884
|
-
|
|
11890
|
+
if (live) {
|
|
11891
|
+
for (const { node, subject } of marks) {
|
|
11892
|
+
if (subject === node && node.getAttribute(CONCEALED_ATTRIBUTE) === "hidden")
|
|
11893
|
+
node.removeAttribute(CONCEALED_ATTRIBUTE);
|
|
11894
|
+
}
|
|
11895
|
+
}
|
|
11896
|
+
const measured = [];
|
|
11885
11897
|
for (const { node, subject } of marks) {
|
|
11886
11898
|
const id = node.getAttribute("data-comment-id");
|
|
11887
11899
|
if (id === null || id === "")
|
|
11888
11900
|
continue;
|
|
11889
|
-
|
|
11901
|
+
measured.push({
|
|
11902
|
+
node,
|
|
11903
|
+
id,
|
|
11904
|
+
concealment: !live || !isRendered(subject) ? "hidden" : !watch.inView(node) ? "offscreen" : "shown"
|
|
11905
|
+
});
|
|
11906
|
+
}
|
|
11907
|
+
const merged = new Map;
|
|
11908
|
+
for (const { node, id, concealment } of measured) {
|
|
11890
11909
|
setConcealed(node, concealment === "shown" ? undefined : concealment);
|
|
11891
11910
|
const previous = merged.get(id);
|
|
11892
11911
|
merged.set(id, mergeReason(previous, concealment));
|