relic-mcp 0.10.0 → 0.11.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.10.0",
13
+ "version": "0.11.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.10.0"
24
+ "version": "0.11.0"
25
25
  }
26
26
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "relic",
3
- "version": "0.10.0",
3
+ "version": "0.11.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
@@ -31,9 +31,12 @@ For any client that takes a JSON config:
31
31
  4. Uploads **only ciphertext**, straight to object storage under a signed URL.
32
32
  It does not pass through the Relic service.
33
33
  5. Tells the service four things: a coarse renderer class from an eight-value
34
- list, this client's name, the ciphertext's byte length, and the title,
35
- which defaults to the filename and is stored in the clear. Not the
36
- mimetype, not the contents.
34
+ list, this client's name, the ciphertext's byte length, and the title, which
35
+ defaults to the filename and is stored in the clear. By default, it also
36
+ renders a 1200x630 cover image of the file's first screen in local headless
37
+ Chrome and uploads it unencrypted via `POST /api/relics/{id}/cover` for link
38
+ preview cards (pass `cover: false` to decline). Not the mimetype, not the
39
+ contents.
37
40
  6. Records the relic's id, key, and publish token locally, in a 0600 file
38
41
  under your user config directory, so the relic can be republished from
39
42
  this machine later. See [Republishing](#republishing). The service never
@@ -50,7 +53,10 @@ because handing you a usable link is the point. **So the key enters your
50
53
  model's context and your session transcript on every publish.** Zero-knowledge
51
54
  holds against the Relic operator. It does not hold against your model provider
52
55
  or whoever stores your transcripts. That is structural, not a defect awaiting
53
- a fix.
56
+ a fix. The cover is plaintext pixels of the first screen, stored server-side
57
+ and served to anyone holding the link without its fragment, including unfurl
58
+ services and their caches; `cover: false` declines; republishing with it clears;
59
+ takedown deletes it.
54
60
 
55
61
  ## Republishing
56
62
 
@@ -143,18 +149,37 @@ fetched from the network and executed.
143
149
  Because a local client is otherwise opaque, it ships as **readable source
144
150
  rather than a compiled binary**. `dist/relic-mcp.js` is a single unminified
145
151
  file and it is exactly what runs; the TypeScript it was built from is in the
146
- same package. Releases carry npm provenance, a cryptographic attestation
147
- binding the tarball to a specific commit and workflow.
152
+ same package. The cover rendering page assets in `dist/cover/` contain the
153
+ unminified cover bundle built from `@relic/viewer`, with vendored React, pdfjs,
154
+ and sucrase for offline document rendering in local Chrome. Releases carry npm
155
+ provenance, a cryptographic attestation binding the tarball to a specific commit
156
+ and workflow.
157
+
158
+ ### Cover rendering and local Chrome requirement
159
+
160
+ By default, publishing or republishing renders a 1200x630 cover image in an
161
+ installed headless browser (Google Chrome, Chromium, or Microsoft Edge) over
162
+ Chrome DevTools Protocol (CDP) pipes. No browser is downloaded by this package.
163
+ If no browser is installed or if `RELIC_CHROME_PATH=none`, publishing still
164
+ succeeds and reports the cover as skipped with the reason, falling back to
165
+ Relic's generic preview card.
166
+
167
+ The cover renderer bundle lives in `dist/cover/`, served to Chrome over a
168
+ virtual origin (`https://cover.relic.invalid`) with strict network containment
169
+ flags (`--host-resolver-rules=MAP * ~NOTFOUND`,
170
+ `--proxy-server=http://proxy.relic.invalid:1`,
171
+ `--webrtc-ip-handling-policy=disable_non_proxied_udp`, `--disable-quic`) so no
172
+ relic plaintext leaves the machine during rendering.
148
173
 
149
174
  ## Tools
150
175
 
151
176
  | Tool | Input | Notes |
152
177
  |---|---|---|
153
- | `relic_publish` | `path`, optional `filename`, `title`, `ttl_days` | A filesystem path. Inline content is deliberately not accepted, so the plaintext never joins the key in your transcript. A relic carries a plaintext title defaulting to the filename and stored by the service in the clear for link previews; pass `""` to publish without one. A relic is kept until it is deleted; `ttl_days` (an integer, 1 to 3650) gives it a lifetime. Reports the relic as version 1. |
178
+ | `relic_publish` | `path`, optional `filename`, `title`, `cover`, `ttl_days` | A filesystem path. Inline content is deliberately not accepted, so the plaintext never joins the key in your transcript. A relic carries a plaintext title defaulting to the filename and stored by the service in the clear for link previews; pass `""` to publish without one. By default, renders a 1200x630 cover image of the first screen in local headless Chrome and uploads it unencrypted for link preview cards; pass `cover: false` to decline. A relic is kept until it is deleted; `ttl_days` (an integer, 1 to 3650) gives it a lifetime. Reports the relic as version 1. |
154
179
  | `relic_lookup_source` | `path` | Reads local state to find whether this machine already published that file, and returns the exact `relic_republish` call. Calls no server. |
155
180
  | `relic_list` | optional `limit`, `include_expired`, `verify`, `refresh` | Every relic this machine published, newest first. The only tool that enumerates: lookup answers only from the file a relic came from. A name this machine never recorded is recovered by decrypting the relic's own envelope in one 64 KB range request, or comes back `null` with the reason. A relic the service will not serve is listed with which of removed, expired, exhausted, or unreachable it is. Each row carries the share URL, fragment included, so each row is a credential. Reaching the service spends one of a relic's finite opens; recovered names are cached, so a repeat listing spends none. |
156
181
  | `relic_show` | `relic_id`, optional `include_content` | One relic in detail, including how many versions the service holds against how many this machine recorded, and with `include_content` the current decrypted content. Read it before republishing anything you did not just write: republish replaces what the link serves, so without the read-back the edit is blind. Content that is not valid UTF-8 is reported as size and type rather than mangled text. |
157
- | `relic_republish` | `relic_id`, `path`, optional `filename`, `title`, `ttl_days` | Publishes a new version under the same key, so the share URL is unchanged. Replaces the plaintext title (defaults to the new filename; pass `""` to remove it). Works only on the machine holding that relic's key and publish token; a taken-down relic can never be revived. |
182
+ | `relic_republish` | `relic_id`, `path`, optional `filename`, `title`, `cover`, `ttl_days` | Publishes a new version under the same key, so the share URL is unchanged. Replaces the plaintext title (defaults to the new filename; pass `""` to remove it). Replaces the cover image with a newly rendered cover, or clears it if `cover: false` is passed. Works only on the machine holding that relic's key and publish token; a taken-down relic can never be revived. |
158
183
  | `relic_read_comments` | `relic_id` | Returns the relic's comments oldest first, decrypted locally, with the author and a count of any that would not decrypt. Works only on the machine that published. |
159
184
  | `relic_comment` | `relic_id`, `body`, optional `display_name` | Leaves a comment, encrypted on this machine, authorized by the publish token and attributed as `publisher`. Body caps at 4096 bytes of UTF-8. |
160
185
  | `relic_describe_client` | none | Explains the encryption path. Reads nothing, sends nothing. |
@@ -171,6 +196,8 @@ binding the tarball to a specific commit and workflow.
171
196
  | `RELIC_MCP_HTTP` | `1` to serve Streamable HTTP instead of stdio. |
172
197
  | `RELIC_MCP_PORT`, `RELIC_MCP_HOST` | HTTP bind. Defaults to `127.0.0.1:7333`. |
173
198
  | `RELIC_MCP_ALLOWED_ORIGINS` | Comma-separated `Origin` allowlist for HTTP. |
199
+ | `RELIC_CHROME_PATH` | Explicit path to Chrome, Chromium, or Edge binary for cover rendering. Set to `none` or `disabled` to skip covers. |
200
+ | `RELIC_FFMPEG_PATH` | Path to ffmpeg binary for generating video frame and audio waveform/artwork cover images. |
174
201
 
175
202
  ## Protocol
176
203
 
@@ -0,0 +1,273 @@
1
+ /* Cover layout and style overrides for Open Graph preview cards. */
2
+
3
+ html, body {
4
+ margin: 0;
5
+ padding: 0;
6
+ width: 600px;
7
+ height: 315px;
8
+ overflow: hidden;
9
+ background: var(--ground);
10
+ }
11
+
12
+ .cv {
13
+ --ground: #f1f2f0;
14
+ --surface: #fbfbfa;
15
+ --ink: #16191a;
16
+ --ink-soft: #5a6163;
17
+ --rule: #d2d5d2;
18
+ --rule-strong: #b3b8b4;
19
+ --patina: #1f6b64;
20
+ --patina-ink: #0f3b37;
21
+ --oxide: #8c4a2f;
22
+
23
+ position: relative;
24
+ display: flex;
25
+ flex-direction: column;
26
+ width: 600px;
27
+ height: 315px;
28
+ overflow: hidden;
29
+ background: var(--ground);
30
+ color: var(--ink);
31
+ font-family: var(--sans);
32
+ font-size: 16px;
33
+ line-height: 1.55;
34
+ color-scheme: light;
35
+ -webkit-text-size-adjust: 100%;
36
+ }
37
+
38
+ .cv *, .cv *::before, .cv *::after {
39
+ box-sizing: border-box;
40
+ }
41
+
42
+ .cv, .cv * {
43
+ scrollbar-width: none;
44
+ }
45
+
46
+ /* The reading surface */
47
+ .cv .stage {
48
+ padding: 2rem 1.25rem 5rem;
49
+ }
50
+
51
+ .cv .doc {
52
+ max-width: 46rem;
53
+ margin: 0 auto;
54
+ }
55
+
56
+ /* Code covers at 15 px override */
57
+ .cv .code {
58
+ font-size: 15px;
59
+ line-height: 24px;
60
+ }
61
+
62
+ /* Sandboxed html and jsx fill the surface */
63
+ .cv .stage-sandboxed-html,
64
+ .cv .stage-sandboxed-jsx {
65
+ display: flex;
66
+ flex-direction: column;
67
+ height: 100%;
68
+ padding: 0;
69
+ }
70
+
71
+ .cv .stage-sandboxed-html .doc,
72
+ .cv .stage-sandboxed-jsx .doc {
73
+ flex: 1 1 auto;
74
+ min-height: 0;
75
+ margin: 0;
76
+ max-width: none;
77
+ height: 100%;
78
+ }
79
+
80
+ .cv .usercontent-frame {
81
+ display: block;
82
+ width: 100%;
83
+ height: 100%;
84
+ border: 0;
85
+ background: #fff;
86
+ }
87
+
88
+ /* Picture kinds fill the canvas */
89
+ .cv-picture img {
90
+ display: block;
91
+ width: 600px;
92
+ height: 315px;
93
+ object-fit: cover;
94
+ }
95
+
96
+ .cv-image img {
97
+ object-position: 50% 0%;
98
+ }
99
+
100
+ .cv-video img {
101
+ object-position: 50% 50%;
102
+ }
103
+
104
+ /* PDF */
105
+ .cv-pdf {
106
+ background: #fff;
107
+ position: relative;
108
+ width: 600px;
109
+ height: 315px;
110
+ overflow: hidden;
111
+ }
112
+
113
+ /* Fallback card */
114
+ .cv-card img {
115
+ display: block;
116
+ width: 600px;
117
+ height: 315px;
118
+ }
119
+
120
+ /* Archive listing layout */
121
+ .cv-archive .stage {
122
+ height: 100%;
123
+ padding: 2rem 1.25rem 0;
124
+ display: flex;
125
+ flex-direction: column;
126
+ }
127
+
128
+ .ar {
129
+ --gutter: 3.25rem;
130
+ --row: 24px;
131
+ flex: 1 1 auto;
132
+ min-height: 0;
133
+ margin: 0;
134
+ padding: 0.25rem 0;
135
+ list-style: none;
136
+ font-family: var(--mono);
137
+ font-size: 15px;
138
+ line-height: 24px;
139
+ border: 1px solid var(--rule);
140
+ border-bottom: 0;
141
+ background:
142
+ linear-gradient(to right,
143
+ var(--ground) 0 var(--gutter),
144
+ var(--rule) var(--gutter) calc(var(--gutter) + 1px),
145
+ var(--surface) calc(var(--gutter) + 1px));
146
+ overflow: hidden;
147
+ }
148
+
149
+ .ar-row {
150
+ display: grid;
151
+ grid-template-columns: var(--gutter) minmax(0, 1fr) auto;
152
+ height: var(--row);
153
+ align-items: center;
154
+ }
155
+
156
+ .ar-num {
157
+ padding-right: 0.75rem;
158
+ text-align: right;
159
+ color: var(--ink-soft);
160
+ }
161
+
162
+ .ar-path {
163
+ --depth: 0;
164
+ height: 100%;
165
+ display: flex;
166
+ align-items: center;
167
+ min-width: 0;
168
+ padding-left: calc(0.85rem + var(--depth) * 2ch);
169
+ white-space: nowrap;
170
+ background-image: repeating-linear-gradient(to right,
171
+ transparent 0 0.45ch,
172
+ var(--rule-strong) 0.45ch calc(0.45ch + 1px),
173
+ transparent calc(0.45ch + 1px) 2ch);
174
+ background-size: calc(var(--depth) * 2ch) 100%;
175
+ background-position: 0.85rem 0;
176
+ background-repeat: no-repeat;
177
+ }
178
+
179
+ .ar-dir {
180
+ flex: none;
181
+ color: var(--ink-soft);
182
+ }
183
+
184
+ .ar-base {
185
+ flex: 0 1 auto;
186
+ min-width: 0;
187
+ overflow: hidden;
188
+ text-overflow: ellipsis;
189
+ }
190
+
191
+ .ar-lead {
192
+ flex: 1 1 0;
193
+ min-width: 1.5ch;
194
+ align-self: stretch;
195
+ margin-left: 0.75ch;
196
+ background: linear-gradient(to right, var(--rule-strong) 1px, transparent 1px)
197
+ repeat-x 0 calc(50% + 0.3em) / 5px 1px;
198
+ }
199
+
200
+ .ar-row.is-dir .ar-base {
201
+ color: var(--patina-ink);
202
+ font-weight: 600;
203
+ }
204
+
205
+ .ar-row.is-dir .ar-lead,
206
+ .ar-row.is-more .ar-lead {
207
+ display: none;
208
+ }
209
+
210
+ .ar-size {
211
+ padding: 0 0.9rem 0 0.75ch;
212
+ text-align: right;
213
+ color: var(--ink-soft);
214
+ font-variant-numeric: tabular-nums;
215
+ }
216
+
217
+ .ar-row.is-more .ar-path {
218
+ color: var(--ink-soft);
219
+ font-style: italic;
220
+ }
221
+
222
+ /* Audio artwork layout */
223
+ .cv-artwork .stage {
224
+ height: 100%;
225
+ padding: 1.25rem;
226
+ display: flex;
227
+ align-items: center;
228
+ justify-content: center;
229
+ }
230
+
231
+ /* Sized whole into the largest square the stage leaves (fitArtwork). */
232
+ .cv-artwork img {
233
+ display: block;
234
+ flex: none;
235
+ border: 1px solid var(--rule);
236
+ background: var(--surface);
237
+ }
238
+
239
+ /* Audio waveform layout */
240
+ .cv-waveform .stage {
241
+ height: 100%;
242
+ padding: 2rem 1.25rem;
243
+ }
244
+
245
+ .wf {
246
+ position: relative;
247
+ height: 100%;
248
+ background: var(--surface);
249
+ border: 1px solid var(--rule);
250
+ }
251
+
252
+ .wf-axis {
253
+ position: absolute;
254
+ left: 1.25rem;
255
+ right: 1.25rem;
256
+ top: 50%;
257
+ height: 1px;
258
+ background: var(--rule-strong);
259
+ }
260
+
261
+ .wf-wave {
262
+ position: absolute;
263
+ inset: 1.5rem 1.25rem;
264
+ background: var(--patina-ink);
265
+ -webkit-mask-image: var(--wf-src);
266
+ mask-image: var(--wf-src);
267
+ -webkit-mask-repeat: no-repeat;
268
+ mask-repeat: no-repeat;
269
+ -webkit-mask-size: 100% var(--wf-h, 100%);
270
+ mask-size: 100% var(--wf-h, 100%);
271
+ -webkit-mask-position: 0 var(--wf-y, 50%);
272
+ mask-position: 0 var(--wf-y, 50%);
273
+ }