relmio 0.4.0 → 0.5.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 +46 -0
- package/README.md +16 -2
- package/docs/local-endpoints.md +22 -13
- package/docs/troubleshooting.md +1 -1
- package/package.json +1 -1
- package/src/domain/local-endpoints.js +4 -1
- package/src/services/local-installer.js +772 -13
- package/src/services/oauth.js +323 -28
- package/src/ui/app.js +133 -6
- package/src/ui/index.html +3 -0
- package/src/ui/local.css +140 -2
- package/src/ui/local.html +69 -1
- package/src/ui/local.js +58 -0
- package/src/web/server.js +461 -50
package/src/ui/local.css
CHANGED
|
@@ -18,6 +18,128 @@
|
|
|
18
18
|
grid-column: 2;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
.local-wizard .header-actions {
|
|
22
|
+
flex-wrap: wrap;
|
|
23
|
+
justify-content: flex-end;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.local-wizard .local-support-button,
|
|
27
|
+
.local-wizard .local-repository-button {
|
|
28
|
+
display: inline-flex;
|
|
29
|
+
align-items: center;
|
|
30
|
+
justify-content: center;
|
|
31
|
+
border: 1px solid var(--border-strong);
|
|
32
|
+
border-radius: 999px;
|
|
33
|
+
text-decoration: none;
|
|
34
|
+
transition:
|
|
35
|
+
border-color 180ms var(--ease),
|
|
36
|
+
background 180ms var(--ease),
|
|
37
|
+
color 180ms var(--ease),
|
|
38
|
+
transform 180ms var(--ease);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.local-wizard .local-support-button {
|
|
42
|
+
width: 2.5rem;
|
|
43
|
+
min-width: 2.5rem;
|
|
44
|
+
min-height: 2.5rem;
|
|
45
|
+
background: var(--surface);
|
|
46
|
+
color: var(--text);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.local-wizard .local-support-button svg {
|
|
50
|
+
width: 1.125rem;
|
|
51
|
+
height: 1.125rem;
|
|
52
|
+
fill: none;
|
|
53
|
+
stroke: currentColor;
|
|
54
|
+
stroke-linecap: round;
|
|
55
|
+
stroke-linejoin: round;
|
|
56
|
+
stroke-width: 1.8;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.local-wizard .local-repository-button {
|
|
60
|
+
min-height: 2.5rem;
|
|
61
|
+
gap: 0.5rem;
|
|
62
|
+
padding: 0.3125rem 0.375rem 0.3125rem 0.75rem;
|
|
63
|
+
border-color: var(--text);
|
|
64
|
+
background: var(--text);
|
|
65
|
+
color: var(--background);
|
|
66
|
+
font-size: 0.75rem;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.local-wizard .local-repository-button > svg {
|
|
70
|
+
width: 1.125rem;
|
|
71
|
+
height: 1.125rem;
|
|
72
|
+
flex: none;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.local-wizard .local-repository-action,
|
|
76
|
+
.local-wizard .package-version {
|
|
77
|
+
min-height: 1.75rem;
|
|
78
|
+
display: inline-flex;
|
|
79
|
+
align-items: center;
|
|
80
|
+
padding: 0 0.5625rem;
|
|
81
|
+
border-radius: 999px;
|
|
82
|
+
white-space: nowrap;
|
|
83
|
+
font: 700 0.6875rem/1 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.local-wizard .local-repository-action {
|
|
87
|
+
background: var(--background);
|
|
88
|
+
color: var(--text);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.local-wizard .package-version {
|
|
92
|
+
background: var(--background);
|
|
93
|
+
color: var(--text);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.local-wizard .local-support-button:hover,
|
|
97
|
+
.local-wizard .local-repository-button:hover {
|
|
98
|
+
border-color: var(--accent);
|
|
99
|
+
color: var(--accent-deep);
|
|
100
|
+
transform: translateY(-1px);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.local-wizard .local-repository-button:hover {
|
|
104
|
+
background: var(--accent);
|
|
105
|
+
color: #fff;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.local-wizard .local-support-button:focus-visible,
|
|
109
|
+
.local-wizard .local-repository-button:focus-visible {
|
|
110
|
+
outline: 3px solid #f2a94a;
|
|
111
|
+
outline-offset: 2px;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.local-wizard .toast-stack {
|
|
115
|
+
display: grid;
|
|
116
|
+
min-width: 0;
|
|
117
|
+
overflow: visible;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.local-wizard .toast-stack .toast {
|
|
121
|
+
width: 100%;
|
|
122
|
+
min-width: 0;
|
|
123
|
+
flex: 0 1 auto;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.local-wizard .safety-note > span {
|
|
127
|
+
display: block;
|
|
128
|
+
min-width: 0;
|
|
129
|
+
overflow-wrap: anywhere;
|
|
130
|
+
white-space: normal;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.local-wizard #global-message-text,
|
|
134
|
+
.local-wizard .error > span {
|
|
135
|
+
min-width: 0;
|
|
136
|
+
overflow: visible;
|
|
137
|
+
overflow-wrap: anywhere;
|
|
138
|
+
text-overflow: clip;
|
|
139
|
+
white-space: normal;
|
|
140
|
+
word-break: break-word;
|
|
141
|
+
}
|
|
142
|
+
|
|
21
143
|
.target-picker {
|
|
22
144
|
display: grid;
|
|
23
145
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
@@ -137,7 +259,8 @@
|
|
|
137
259
|
.policy-note,
|
|
138
260
|
.high-trust-warning,
|
|
139
261
|
.one-time-note,
|
|
140
|
-
.codex-login
|
|
262
|
+
.codex-login,
|
|
263
|
+
.credential-rotation-note {
|
|
141
264
|
margin-top: 1rem;
|
|
142
265
|
padding: 0.875rem 1rem;
|
|
143
266
|
border: 1px solid var(--accent-border);
|
|
@@ -148,7 +271,8 @@
|
|
|
148
271
|
.policy-note p,
|
|
149
272
|
.high-trust-warning p,
|
|
150
273
|
.one-time-note p,
|
|
151
|
-
.codex-login p
|
|
274
|
+
.codex-login p,
|
|
275
|
+
.credential-rotation-note p {
|
|
152
276
|
margin: 0.35rem 0 0;
|
|
153
277
|
color: var(--text-muted);
|
|
154
278
|
font-size: 0.875rem;
|
|
@@ -175,6 +299,20 @@
|
|
|
175
299
|
color: var(--warning);
|
|
176
300
|
}
|
|
177
301
|
|
|
302
|
+
.credential-rotation-note {
|
|
303
|
+
border-color: var(--warning-border);
|
|
304
|
+
background: var(--warning-soft);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
.credential-rotation-note strong,
|
|
308
|
+
.credential-rotation-note p {
|
|
309
|
+
color: var(--warning);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.credential-rotation-note .button {
|
|
313
|
+
margin-top: 0.875rem;
|
|
314
|
+
}
|
|
315
|
+
|
|
178
316
|
.local-review-grid {
|
|
179
317
|
margin-top: 1rem;
|
|
180
318
|
}
|
package/src/ui/local.html
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
/>
|
|
15
15
|
<link rel="stylesheet" href="/styles.css" />
|
|
16
16
|
<link rel="stylesheet" href="/local.css" />
|
|
17
|
+
<script src="/theme.js" type="module"></script>
|
|
17
18
|
<script src="/local.js" type="module"></script>
|
|
18
19
|
</head>
|
|
19
20
|
<body class="local-wizard" data-current-step="1">
|
|
@@ -32,7 +33,58 @@
|
|
|
32
33
|
</svg>
|
|
33
34
|
<span>Relmio</span>
|
|
34
35
|
</div>
|
|
35
|
-
<
|
|
36
|
+
<div class="header-actions">
|
|
37
|
+
<fieldset class="theme-picker">
|
|
38
|
+
<legend class="visually-hidden">Color theme</legend>
|
|
39
|
+
<label title="Use system appearance">
|
|
40
|
+
<input type="radio" name="color-theme" value="system" checked />
|
|
41
|
+
<span class="theme-icon theme-icon-system" aria-hidden="true"></span>
|
|
42
|
+
<span class="visually-hidden">System</span>
|
|
43
|
+
</label>
|
|
44
|
+
<label title="Use light appearance">
|
|
45
|
+
<input type="radio" name="color-theme" value="light" />
|
|
46
|
+
<span class="theme-icon theme-icon-light" aria-hidden="true"></span>
|
|
47
|
+
<span class="visually-hidden">Light</span>
|
|
48
|
+
</label>
|
|
49
|
+
<label title="Use dark appearance">
|
|
50
|
+
<input type="radio" name="color-theme" value="dark" />
|
|
51
|
+
<span class="theme-icon theme-icon-dark" aria-hidden="true"></span>
|
|
52
|
+
<span class="visually-hidden">Dark</span>
|
|
53
|
+
</label>
|
|
54
|
+
</fieldset>
|
|
55
|
+
<a
|
|
56
|
+
class="local-support-button"
|
|
57
|
+
href="https://ko-fi.com/paldogies"
|
|
58
|
+
target="_blank"
|
|
59
|
+
rel="noopener noreferrer"
|
|
60
|
+
aria-label="Support Relmio on Ko-fi (opens in a new tab)"
|
|
61
|
+
title="Support Relmio on Ko-fi"
|
|
62
|
+
>
|
|
63
|
+
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
|
|
64
|
+
<path d="M5 8h12v5a6 6 0 0 1-12 0V8Z" />
|
|
65
|
+
<path d="M17 9h1.5a2.5 2.5 0 0 1 0 5H17M8 3v2m4-2v2m4-2v2" />
|
|
66
|
+
</svg>
|
|
67
|
+
</a>
|
|
68
|
+
<a
|
|
69
|
+
id="local-repository-button"
|
|
70
|
+
class="local-repository-button"
|
|
71
|
+
href="https://github.com/Demonbane18/relmio"
|
|
72
|
+
target="_blank"
|
|
73
|
+
rel="noopener noreferrer"
|
|
74
|
+
aria-label="Open Relmio version __RELMIO_PACKAGE_VERSION__ on GitHub to leave a star (opens in a new tab)"
|
|
75
|
+
>
|
|
76
|
+
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
|
|
77
|
+
<path fill="currentColor" d="M12 .7a11.5 11.5 0 0 0-3.64 22.41c.58.11.79-.25.79-.56v-2.23c-3.22.7-3.9-1.37-3.9-1.37-.53-1.34-1.29-1.7-1.29-1.7-1.05-.72.08-.71.08-.71 1.16.08 1.77 1.19 1.77 1.19 1.04 1.77 2.72 1.26 3.38.96.1-.75.4-1.26.73-1.55-2.57-.29-5.28-1.29-5.28-5.69 0-1.26.45-2.28 1.19-3.09-.12-.29-.52-1.46.11-3.05 0 0 .97-.31 3.16 1.18a10.9 10.9 0 0 1 5.76 0c2.19-1.49 3.16-1.18 3.16-1.18.63 1.59.23 2.76.11 3.05.74.81 1.19 1.83 1.19 3.09 0 4.42-2.71 5.4-5.29 5.69.42.36.79 1.06.79 2.14v3.17c0 .31.21.68.8.56A11.5 11.5 0 0 0 12 .7Z" />
|
|
78
|
+
</svg>
|
|
79
|
+
<strong>GitHub</strong>
|
|
80
|
+
<span class="local-repository-action">
|
|
81
|
+
<span aria-hidden="true">★</span>
|
|
82
|
+
<span id="local-repository-stars">?</span>
|
|
83
|
+
</span>
|
|
84
|
+
<span class="package-version">v__RELMIO_PACKAGE_VERSION__</span>
|
|
85
|
+
</a>
|
|
86
|
+
<span class="local-badge">Bound to this computer only</span>
|
|
87
|
+
</div>
|
|
36
88
|
</header>
|
|
37
89
|
|
|
38
90
|
<main id="main-content" class="shell" tabindex="-1">
|
|
@@ -343,6 +395,22 @@
|
|
|
343
395
|
</p>
|
|
344
396
|
</aside>
|
|
345
397
|
|
|
398
|
+
<aside class="credential-rotation-note">
|
|
399
|
+
<strong>Rotate client credential</strong>
|
|
400
|
+
<p>
|
|
401
|
+
Relmio shows the replacement first, then activates and verifies it.
|
|
402
|
+
After successful activation, the previous credential no longer works.
|
|
403
|
+
Copy the replacement before leaving this page.
|
|
404
|
+
</p>
|
|
405
|
+
<button
|
|
406
|
+
id="rotate-credential-button"
|
|
407
|
+
class="button secondary"
|
|
408
|
+
type="button"
|
|
409
|
+
>
|
|
410
|
+
Rotate client credential
|
|
411
|
+
</button>
|
|
412
|
+
</aside>
|
|
413
|
+
|
|
346
414
|
<dl class="result-list">
|
|
347
415
|
<div>
|
|
348
416
|
<dt>Endpoint</dt>
|
package/src/ui/local.js
CHANGED
|
@@ -9,6 +9,7 @@ const state = {
|
|
|
9
9
|
dockerAvailable: false,
|
|
10
10
|
planId: null,
|
|
11
11
|
plan: null,
|
|
12
|
+
installedTarget: null,
|
|
12
13
|
};
|
|
13
14
|
|
|
14
15
|
const messageBox = element("global-message");
|
|
@@ -221,6 +222,7 @@ function renderInstallResult(result) {
|
|
|
221
222
|
}
|
|
222
223
|
|
|
223
224
|
const isOpenAiApi = result.target === "openai-api";
|
|
225
|
+
state.installedTarget = result.target;
|
|
224
226
|
element("result-endpoint").textContent = result.endpoint;
|
|
225
227
|
element("result-credential").textContent = result.clientCredential;
|
|
226
228
|
element("codex-production-warning").hidden = isOpenAiApi;
|
|
@@ -444,6 +446,44 @@ element("install-button").addEventListener("click", async (event) => {
|
|
|
444
446
|
}
|
|
445
447
|
});
|
|
446
448
|
|
|
449
|
+
element("rotate-credential-button").addEventListener("click", async (event) => {
|
|
450
|
+
const button = event.currentTarget;
|
|
451
|
+
clearError();
|
|
452
|
+
if (!state.installedTarget) {
|
|
453
|
+
showError(new Error("Install a local endpoint before rotating its credential."));
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
setBusy(button, true, "Rotating credential…");
|
|
458
|
+
setMessage(
|
|
459
|
+
"Generating a replacement credential before activating it…",
|
|
460
|
+
);
|
|
461
|
+
try {
|
|
462
|
+
const staged = await api("/api/local/client-credential/rotate", {
|
|
463
|
+
method: "POST",
|
|
464
|
+
body: { target: state.installedTarget },
|
|
465
|
+
});
|
|
466
|
+
renderInstallResult(staged);
|
|
467
|
+
setMessage("Replacement credential received. Activating and verifying it now…");
|
|
468
|
+
await new Promise((resolvePromise) => window.requestAnimationFrame(resolvePromise));
|
|
469
|
+
await new Promise((resolvePromise) => window.requestAnimationFrame(resolvePromise));
|
|
470
|
+
const activated = await api("/api/local/client-credential/activate", {
|
|
471
|
+
method: "POST",
|
|
472
|
+
body: {
|
|
473
|
+
rotationId: staged.rotationId,
|
|
474
|
+
clientCredential: staged.clientCredential,
|
|
475
|
+
},
|
|
476
|
+
});
|
|
477
|
+
renderInstallResult({ ...staged, ...activated });
|
|
478
|
+
setMessage("Client credential rotated. Copy the new one now; the previous one no longer works.");
|
|
479
|
+
} catch (error) {
|
|
480
|
+
setMessage("The replacement credential was not confirmed active. Follow the error guidance before retrying.");
|
|
481
|
+
showError(error);
|
|
482
|
+
} finally {
|
|
483
|
+
setBusy(button, false);
|
|
484
|
+
}
|
|
485
|
+
});
|
|
486
|
+
|
|
447
487
|
element("codex-login-button").addEventListener("click", async (event) => {
|
|
448
488
|
const button = event.currentTarget;
|
|
449
489
|
const resultBox = element("device-code-result");
|
|
@@ -546,5 +586,23 @@ async function refreshDockerStatus() {
|
|
|
546
586
|
}
|
|
547
587
|
}
|
|
548
588
|
|
|
589
|
+
async function refreshProjectMeta() {
|
|
590
|
+
const result = await api("/api/local/project-meta");
|
|
591
|
+
const stars = Number.isSafeInteger(result.stars) && result.stars >= 0
|
|
592
|
+
? new Intl.NumberFormat("en", {
|
|
593
|
+
notation: result.stars >= 1_000 ? "compact" : "standard",
|
|
594
|
+
maximumFractionDigits: 1,
|
|
595
|
+
}).format(result.stars)
|
|
596
|
+
: "?";
|
|
597
|
+
element("local-repository-stars").textContent = stars;
|
|
598
|
+
element("local-repository-button").setAttribute(
|
|
599
|
+
"aria-label",
|
|
600
|
+
`Open Relmio version ${result.version} on GitHub. ${
|
|
601
|
+
stars === "?" ? "GitHub star count is unavailable." : `${result.stars} GitHub stars.`
|
|
602
|
+
} Opens in a new tab.`,
|
|
603
|
+
);
|
|
604
|
+
}
|
|
605
|
+
|
|
549
606
|
renderTarget();
|
|
550
607
|
refreshDockerStatus().catch(showError);
|
|
608
|
+
refreshProjectMeta().catch(() => {});
|