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/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
- <span class="local-badge">Bound to this computer only</span>
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(() => {});