relmio 0.3.1 → 0.4.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/CHANGELOG.md +32 -0
- package/README.md +67 -10
- package/docs/architecture.md +57 -0
- package/docs/local-endpoints-spec.md +370 -0
- package/docs/local-endpoints.md +374 -0
- package/docs/npm-publish.md +121 -201
- package/docs/security.md +92 -3
- package/package.json +2 -2
- package/src/domain/local-endpoints.js +460 -0
- package/src/gateway/openai.js +834 -0
- package/src/infrastructure/local-process.js +375 -0
- package/src/services/codex-login.js +711 -0
- package/src/services/local-installer.js +1120 -0
- package/src/ui/app.js +4 -0
- package/src/ui/index.html +13 -0
- package/src/ui/local.css +262 -0
- package/src/ui/local.html +442 -0
- package/src/ui/local.js +550 -0
- package/src/web/server.js +247 -12
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<meta
|
|
7
|
+
name="description"
|
|
8
|
+
content="Install a loopback-only Relmio endpoint for the OpenAI API or the official Codex App Server."
|
|
9
|
+
/>
|
|
10
|
+
<title>Relmio | Local Endpoint Setup</title>
|
|
11
|
+
<link
|
|
12
|
+
rel="icon"
|
|
13
|
+
href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Cpath fill='%23137c74' d='M7 8h19l31 24H41L26 20v10L7 17Z'/%3E%3Cpath fill='%2312211f' d='M7 47l19-13v10l15-12h16L26 56H7Z'/%3E%3C/svg%3E"
|
|
14
|
+
/>
|
|
15
|
+
<link rel="stylesheet" href="/styles.css" />
|
|
16
|
+
<link rel="stylesheet" href="/local.css" />
|
|
17
|
+
<script src="/local.js" type="module"></script>
|
|
18
|
+
</head>
|
|
19
|
+
<body class="local-wizard" data-current-step="1">
|
|
20
|
+
<a class="skip-link" href="#main-content">Skip to local setup</a>
|
|
21
|
+
|
|
22
|
+
<header class="site-header">
|
|
23
|
+
<div class="brand">
|
|
24
|
+
<svg
|
|
25
|
+
class="brand-mark"
|
|
26
|
+
viewBox="0 0 64 64"
|
|
27
|
+
aria-hidden="true"
|
|
28
|
+
focusable="false"
|
|
29
|
+
>
|
|
30
|
+
<path fill="#137c74" d="M7 8h19l31 24H41L26 20v10L7 17Z" />
|
|
31
|
+
<path fill="#12211f" d="M7 47l19-13v10l15-12h16L26 56H7Z" />
|
|
32
|
+
</svg>
|
|
33
|
+
<span>Relmio</span>
|
|
34
|
+
</div>
|
|
35
|
+
<span class="local-badge">Bound to this computer only</span>
|
|
36
|
+
</header>
|
|
37
|
+
|
|
38
|
+
<main id="main-content" class="shell" tabindex="-1">
|
|
39
|
+
<aside class="rail" aria-label="Local setup progress and safety">
|
|
40
|
+
<section class="intro" aria-labelledby="page-title">
|
|
41
|
+
<h1 id="page-title">Run Relmio on localhost</h1>
|
|
42
|
+
<p class="lede">
|
|
43
|
+
Choose the official credential path that matches your client. Both
|
|
44
|
+
options run in a hardened Docker container and bind only to
|
|
45
|
+
<code>127.0.0.1</code>.
|
|
46
|
+
</p>
|
|
47
|
+
</section>
|
|
48
|
+
|
|
49
|
+
<nav class="steps" aria-label="Local setup progress">
|
|
50
|
+
<ol>
|
|
51
|
+
<li data-step-marker="1" aria-current="step">
|
|
52
|
+
<span>1</span><em>Choose</em>
|
|
53
|
+
</li>
|
|
54
|
+
<li data-step-marker="2"><span>2</span><em>Review</em></li>
|
|
55
|
+
<li data-step-marker="3"><span>3</span><em>Install</em></li>
|
|
56
|
+
<li data-step-marker="4"><span>4</span><em>Ready</em></li>
|
|
57
|
+
</ol>
|
|
58
|
+
</nav>
|
|
59
|
+
|
|
60
|
+
<div class="toast-stack" aria-label="Local setup notifications">
|
|
61
|
+
<aside class="toast safety-note" aria-label="Local-only guarantee">
|
|
62
|
+
<strong>Loopback-only guarantee</strong>
|
|
63
|
+
<span>
|
|
64
|
+
Relmio publishes the selected port on <code>127.0.0.1</code>, not
|
|
65
|
+
your LAN or the public internet.
|
|
66
|
+
</span>
|
|
67
|
+
</aside>
|
|
68
|
+
|
|
69
|
+
<aside
|
|
70
|
+
id="trust-warning"
|
|
71
|
+
class="toast safety-note backup-note"
|
|
72
|
+
aria-label="Credential boundary"
|
|
73
|
+
>
|
|
74
|
+
<strong>Two different credentials</strong>
|
|
75
|
+
<span>
|
|
76
|
+
A ChatGPT subscription is not an OpenAI Platform API key.
|
|
77
|
+
Relmio never converts one into the other.
|
|
78
|
+
</span>
|
|
79
|
+
</aside>
|
|
80
|
+
|
|
81
|
+
<div id="global-message" class="toast message" role="status" aria-live="polite">
|
|
82
|
+
<span id="global-message-text">Checking local Docker…</span>
|
|
83
|
+
</div>
|
|
84
|
+
|
|
85
|
+
<div
|
|
86
|
+
id="global-error"
|
|
87
|
+
class="toast error"
|
|
88
|
+
role="alert"
|
|
89
|
+
tabindex="-1"
|
|
90
|
+
hidden
|
|
91
|
+
>
|
|
92
|
+
<span id="global-error-text"></span>
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
</aside>
|
|
96
|
+
|
|
97
|
+
<section class="panel" data-step="1" aria-labelledby="choose-title">
|
|
98
|
+
<div class="panel-heading">
|
|
99
|
+
<h2 id="choose-title" tabindex="-1">Choose the local endpoint</h2>
|
|
100
|
+
<p>
|
|
101
|
+
Docker stays on this computer. Relmio checks it without changing
|
|
102
|
+
any running containers.
|
|
103
|
+
</p>
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
<div class="status-row" aria-live="polite">
|
|
107
|
+
<span id="docker-indicator" class="status-dot" aria-hidden="true"></span>
|
|
108
|
+
<div>
|
|
109
|
+
<strong id="docker-status-title">Checking Docker…</strong>
|
|
110
|
+
<p id="docker-status-detail">Looking for Docker Engine and Compose.</p>
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
|
|
114
|
+
<aside class="policy-note">
|
|
115
|
+
<strong>macOS, Linux, or WSL2 required</strong>
|
|
116
|
+
<p>
|
|
117
|
+
Native Windows is not supported because this feature requires POSIX
|
|
118
|
+
owner-only file permissions for local credentials.
|
|
119
|
+
</p>
|
|
120
|
+
</aside>
|
|
121
|
+
|
|
122
|
+
<form id="target-form">
|
|
123
|
+
<fieldset class="target-picker">
|
|
124
|
+
<legend>Credential and protocol</legend>
|
|
125
|
+
|
|
126
|
+
<label class="target-card">
|
|
127
|
+
<input type="radio" name="target" value="openai-api" checked />
|
|
128
|
+
<span class="target-card-copy">
|
|
129
|
+
<strong>OpenAI API endpoint</strong>
|
|
130
|
+
<span class="target-meta">Stable · apps and web apps</span>
|
|
131
|
+
<span>
|
|
132
|
+
OpenAI-compatible <code>/v1</code>, backed only by your
|
|
133
|
+
OpenAI Platform API key. Platform API usage is billed
|
|
134
|
+
separately from ChatGPT.
|
|
135
|
+
</span>
|
|
136
|
+
</span>
|
|
137
|
+
</label>
|
|
138
|
+
|
|
139
|
+
<label class="target-card warning-card">
|
|
140
|
+
<input type="radio" name="target" value="codex-chatgpt" />
|
|
141
|
+
<span class="target-card-copy">
|
|
142
|
+
<strong>Codex App Server</strong>
|
|
143
|
+
<span class="target-meta">Experimental · trusted native apps only</span>
|
|
144
|
+
<span>
|
|
145
|
+
Official Codex JSON-RPC over WebSocket, using ChatGPT sign-in.
|
|
146
|
+
This is not an OpenAI-compatible <code>/v1</code> endpoint and
|
|
147
|
+
browsers cannot connect directly. Codex App Server WebSocket is experimental and unsupported for production workloads.
|
|
148
|
+
</span>
|
|
149
|
+
</span>
|
|
150
|
+
</label>
|
|
151
|
+
</fieldset>
|
|
152
|
+
|
|
153
|
+
<div class="field-grid local-fields">
|
|
154
|
+
<label class="field compact">
|
|
155
|
+
<span>Local port</span>
|
|
156
|
+
<input
|
|
157
|
+
id="local-port"
|
|
158
|
+
name="port"
|
|
159
|
+
type="number"
|
|
160
|
+
min="1024"
|
|
161
|
+
max="65535"
|
|
162
|
+
value="12435"
|
|
163
|
+
required
|
|
164
|
+
/>
|
|
165
|
+
<small>Published only on <code>127.0.0.1</code>.</small>
|
|
166
|
+
</label>
|
|
167
|
+
|
|
168
|
+
<label id="origins-field" class="field">
|
|
169
|
+
<span>Allowed browser origins <small>(optional, one per line)</small></span>
|
|
170
|
+
<textarea
|
|
171
|
+
id="allowed-origins"
|
|
172
|
+
name="allowedOrigins"
|
|
173
|
+
rows="3"
|
|
174
|
+
maxlength="20480"
|
|
175
|
+
spellcheck="false"
|
|
176
|
+
placeholder="http://localhost:3000"
|
|
177
|
+
></textarea>
|
|
178
|
+
<small>
|
|
179
|
+
Exact origins only, such as <code>http://localhost:3000</code>.
|
|
180
|
+
Native apps can leave this empty.
|
|
181
|
+
</small>
|
|
182
|
+
</label>
|
|
183
|
+
</div>
|
|
184
|
+
|
|
185
|
+
<aside id="target-guidance" class="policy-note" aria-live="polite">
|
|
186
|
+
<strong id="target-guidance-title">Uses an OpenAI Platform API key</strong>
|
|
187
|
+
<p id="target-guidance-detail">
|
|
188
|
+
Your key is seeded over stdin into a private Docker volume and
|
|
189
|
+
is never returned to the browser. A separate local client
|
|
190
|
+
credential is generated for your apps.
|
|
191
|
+
</p>
|
|
192
|
+
</aside>
|
|
193
|
+
|
|
194
|
+
<div class="actions">
|
|
195
|
+
<a id="back-to-vps" class="button ghost">Back to VPS setup</a>
|
|
196
|
+
<button id="review-button" class="button primary" type="submit" disabled>
|
|
197
|
+
Review local plan
|
|
198
|
+
</button>
|
|
199
|
+
</div>
|
|
200
|
+
</form>
|
|
201
|
+
</section>
|
|
202
|
+
|
|
203
|
+
<section class="panel" data-step="2" aria-labelledby="review-title" hidden>
|
|
204
|
+
<div class="panel-heading">
|
|
205
|
+
<h2 id="review-title" tabindex="-1">Review the exact local plan</h2>
|
|
206
|
+
<p>No file or container has been changed yet.</p>
|
|
207
|
+
</div>
|
|
208
|
+
|
|
209
|
+
<dl class="result-list review-details">
|
|
210
|
+
<div>
|
|
211
|
+
<dt>Endpoint</dt>
|
|
212
|
+
<dd><code id="review-endpoint"></code></dd>
|
|
213
|
+
</div>
|
|
214
|
+
<div>
|
|
215
|
+
<dt>Protocol</dt>
|
|
216
|
+
<dd id="review-protocol"></dd>
|
|
217
|
+
</div>
|
|
218
|
+
<div>
|
|
219
|
+
<dt>Upstream sign-in</dt>
|
|
220
|
+
<dd id="review-auth"></dd>
|
|
221
|
+
</div>
|
|
222
|
+
<div>
|
|
223
|
+
<dt>Browser clients</dt>
|
|
224
|
+
<dd id="review-browser"></dd>
|
|
225
|
+
</div>
|
|
226
|
+
<div id="review-origins-row">
|
|
227
|
+
<dt>Allowed origins</dt>
|
|
228
|
+
<dd id="review-origins"></dd>
|
|
229
|
+
</div>
|
|
230
|
+
<div>
|
|
231
|
+
<dt>Managed files</dt>
|
|
232
|
+
<dd><code id="review-path"></code></dd>
|
|
233
|
+
</div>
|
|
234
|
+
</dl>
|
|
235
|
+
|
|
236
|
+
<div class="review-grid local-review-grid">
|
|
237
|
+
<section>
|
|
238
|
+
<h3>Relmio will</h3>
|
|
239
|
+
<ul class="check-list">
|
|
240
|
+
<li>Build one target-specific Docker image.</li>
|
|
241
|
+
<li>Bind the selected port exactly to <code>127.0.0.1</code>.</li>
|
|
242
|
+
<li>Generate a separate one-time client credential.</li>
|
|
243
|
+
<li>Keep provider credentials in private Docker volumes.</li>
|
|
244
|
+
</ul>
|
|
245
|
+
</section>
|
|
246
|
+
<section>
|
|
247
|
+
<h3>Relmio will not</h3>
|
|
248
|
+
<ul class="no-list">
|
|
249
|
+
<li>Publish the endpoint to your LAN or internet.</li>
|
|
250
|
+
<li>Turn ChatGPT credentials into Platform API credentials.</li>
|
|
251
|
+
<li>Modify or restart your n8n container.</li>
|
|
252
|
+
<li>Reuse the existing Hostinger deployment.</li>
|
|
253
|
+
</ul>
|
|
254
|
+
</section>
|
|
255
|
+
</div>
|
|
256
|
+
|
|
257
|
+
<aside id="review-policy" class="policy-note"></aside>
|
|
258
|
+
|
|
259
|
+
<div class="approval">
|
|
260
|
+
<label class="check-row">
|
|
261
|
+
<input id="install-confirm" type="checkbox" />
|
|
262
|
+
<span>
|
|
263
|
+
I reviewed this exact loopback plan and authorize Relmio to write
|
|
264
|
+
its managed local files and start this Docker container.
|
|
265
|
+
</span>
|
|
266
|
+
</label>
|
|
267
|
+
</div>
|
|
268
|
+
|
|
269
|
+
<div class="actions">
|
|
270
|
+
<button class="button ghost back-button" type="button" data-back="1">
|
|
271
|
+
Back
|
|
272
|
+
</button>
|
|
273
|
+
<button id="install-settings-button" class="button primary" type="button" disabled>
|
|
274
|
+
Continue to install
|
|
275
|
+
</button>
|
|
276
|
+
</div>
|
|
277
|
+
</section>
|
|
278
|
+
|
|
279
|
+
<section class="panel" data-step="3" aria-labelledby="install-title" hidden>
|
|
280
|
+
<div class="panel-heading">
|
|
281
|
+
<h2 id="install-title" tabindex="-1">Install the local container</h2>
|
|
282
|
+
<p id="install-intro">
|
|
283
|
+
Enter the OpenAI Platform API key that this endpoint will use
|
|
284
|
+
upstream. It is sent only to this local Relmio process.
|
|
285
|
+
</p>
|
|
286
|
+
</div>
|
|
287
|
+
|
|
288
|
+
<div id="api-key-field" class="field-grid single">
|
|
289
|
+
<label class="field">
|
|
290
|
+
<span>OpenAI Platform API key</span>
|
|
291
|
+
<input
|
|
292
|
+
id="platform-api-key"
|
|
293
|
+
name="apiKey"
|
|
294
|
+
type="password"
|
|
295
|
+
autocomplete="off"
|
|
296
|
+
spellcheck="false"
|
|
297
|
+
maxlength="512"
|
|
298
|
+
pattern="sk-[A-Za-z0-9_\-]{32,509}"
|
|
299
|
+
required
|
|
300
|
+
/>
|
|
301
|
+
<small>
|
|
302
|
+
The browser field is cleared as soon as installation starts. The
|
|
303
|
+
Platform key is never displayed in the result.
|
|
304
|
+
</small>
|
|
305
|
+
</label>
|
|
306
|
+
</div>
|
|
307
|
+
|
|
308
|
+
<aside id="codex-install-warning" class="high-trust-warning" hidden>
|
|
309
|
+
<strong>High-trust capability</strong>
|
|
310
|
+
<p>
|
|
311
|
+
Anyone holding the generated client credential can control Codex
|
|
312
|
+
inside its isolated container, act through your signed-in ChatGPT
|
|
313
|
+
session, and may be able to recover that container's ChatGPT session
|
|
314
|
+
credential. Treat this capability like your ChatGPT password and
|
|
315
|
+
give it only to a trusted native local app.
|
|
316
|
+
</p>
|
|
317
|
+
</aside>
|
|
318
|
+
|
|
319
|
+
<div class="actions">
|
|
320
|
+
<button class="button ghost back-button" type="button" data-back="2">
|
|
321
|
+
Back
|
|
322
|
+
</button>
|
|
323
|
+
<button id="install-button" class="button primary" type="button">
|
|
324
|
+
Install on this computer
|
|
325
|
+
</button>
|
|
326
|
+
</div>
|
|
327
|
+
</section>
|
|
328
|
+
|
|
329
|
+
<section class="panel success-panel" data-step="4" aria-labelledby="done-title" hidden>
|
|
330
|
+
<div class="success-heading">
|
|
331
|
+
<div class="success-mark" aria-hidden="true">✓</div>
|
|
332
|
+
<div class="panel-heading">
|
|
333
|
+
<h2 id="done-title" tabindex="-1">Local endpoint is ready</h2>
|
|
334
|
+
<p id="done-detail">Copy the client settings into your local app.</p>
|
|
335
|
+
</div>
|
|
336
|
+
</div>
|
|
337
|
+
|
|
338
|
+
<aside class="one-time-note">
|
|
339
|
+
<strong>Copy this credential now</strong>
|
|
340
|
+
<p>
|
|
341
|
+
Relmio shows the generated client credential only in this install
|
|
342
|
+
response. It cannot recover it after you leave this page.
|
|
343
|
+
</p>
|
|
344
|
+
</aside>
|
|
345
|
+
|
|
346
|
+
<dl class="result-list">
|
|
347
|
+
<div>
|
|
348
|
+
<dt>Endpoint</dt>
|
|
349
|
+
<dd class="result-value">
|
|
350
|
+
<code id="result-endpoint"></code>
|
|
351
|
+
<button
|
|
352
|
+
class="button secondary copy-value"
|
|
353
|
+
type="button"
|
|
354
|
+
data-copy-target="result-endpoint"
|
|
355
|
+
data-copy-label="endpoint"
|
|
356
|
+
aria-label="Copy local endpoint"
|
|
357
|
+
>
|
|
358
|
+
Copy
|
|
359
|
+
</button>
|
|
360
|
+
</dd>
|
|
361
|
+
</div>
|
|
362
|
+
<div>
|
|
363
|
+
<dt>Client credential</dt>
|
|
364
|
+
<dd class="result-value">
|
|
365
|
+
<code id="result-credential"></code>
|
|
366
|
+
<button
|
|
367
|
+
class="button secondary copy-value"
|
|
368
|
+
type="button"
|
|
369
|
+
data-copy-target="result-credential"
|
|
370
|
+
data-copy-label="client credential"
|
|
371
|
+
aria-label="Copy client credential"
|
|
372
|
+
>
|
|
373
|
+
Copy
|
|
374
|
+
</button>
|
|
375
|
+
</dd>
|
|
376
|
+
</div>
|
|
377
|
+
</dl>
|
|
378
|
+
|
|
379
|
+
<aside id="codex-production-warning" class="high-trust-warning" hidden>
|
|
380
|
+
<strong>Experimental WebSocket transport</strong>
|
|
381
|
+
<p>
|
|
382
|
+
Codex App Server WebSocket is experimental and unsupported for production workloads.
|
|
383
|
+
</p>
|
|
384
|
+
</aside>
|
|
385
|
+
|
|
386
|
+
<section id="codex-login" class="codex-login" aria-labelledby="codex-login-title" hidden>
|
|
387
|
+
<h3 id="codex-login-title">Sign the container in to ChatGPT</h3>
|
|
388
|
+
<p>
|
|
389
|
+
Relmio starts the official Codex device-code flow. The ChatGPT
|
|
390
|
+
credential stays in the isolated Codex Docker volume and is not an
|
|
391
|
+
OpenAI Platform API key.
|
|
392
|
+
</p>
|
|
393
|
+
<button id="codex-login-button" class="button primary" type="button">
|
|
394
|
+
Start ChatGPT sign-in
|
|
395
|
+
</button>
|
|
396
|
+
|
|
397
|
+
<div id="device-code-result" class="device-code-result" hidden>
|
|
398
|
+
<p>
|
|
399
|
+
Open the official OpenAI page and enter this one-time code:
|
|
400
|
+
</p>
|
|
401
|
+
<div class="device-code-row">
|
|
402
|
+
<code id="device-code"></code>
|
|
403
|
+
<button
|
|
404
|
+
class="button secondary copy-value"
|
|
405
|
+
type="button"
|
|
406
|
+
data-copy-target="device-code"
|
|
407
|
+
data-copy-label="device code"
|
|
408
|
+
aria-label="Copy device code"
|
|
409
|
+
>
|
|
410
|
+
Copy
|
|
411
|
+
</button>
|
|
412
|
+
</div>
|
|
413
|
+
<a
|
|
414
|
+
id="device-code-link"
|
|
415
|
+
class="button secondary"
|
|
416
|
+
target="_blank"
|
|
417
|
+
rel="noopener noreferrer"
|
|
418
|
+
>
|
|
419
|
+
Open OpenAI device sign-in
|
|
420
|
+
</a>
|
|
421
|
+
<p id="device-code-status" class="device-code-status" role="status" aria-live="polite">
|
|
422
|
+
Waiting for sign-in…
|
|
423
|
+
</p>
|
|
424
|
+
</div>
|
|
425
|
+
</section>
|
|
426
|
+
|
|
427
|
+
<aside id="client-warning" class="final-note"></aside>
|
|
428
|
+
|
|
429
|
+
<div class="actions">
|
|
430
|
+
<a id="return-to-vps" class="button secondary">Set up the VPS version</a>
|
|
431
|
+
</div>
|
|
432
|
+
</section>
|
|
433
|
+
</main>
|
|
434
|
+
|
|
435
|
+
<footer>
|
|
436
|
+
<p>
|
|
437
|
+
Relmio keeps the OpenAI Platform API path and ChatGPT/Codex path
|
|
438
|
+
separate so each uses its intended credential and protocol.
|
|
439
|
+
</p>
|
|
440
|
+
</footer>
|
|
441
|
+
</body>
|
|
442
|
+
</html>
|