relmio 0.2.0 → 0.2.1

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 CHANGED
@@ -7,7 +7,40 @@ checks the registry separately after publication.
7
7
 
8
8
  ## Unreleased
9
9
 
10
- No unreleased changes.
10
+ ## [0.2.1] - 2026-07-30
11
+
12
+ ### Added
13
+
14
+ - Add the hosted ChatGPT site link to the package metadata and public guides.
15
+ - Document the upstream Codex relay model, known limitations, and legal
16
+ responsibilities in both the GitHub and npm README variants.
17
+ - Add a command-first install page for the current n8n and Hostinger VPS
18
+ wizard, plus a GitHub control with live package and repository metadata.
19
+ - Credit Evan Zhou Dev's `openai-oauth` method on the hosted Relmio page.
20
+
21
+ ### Changed
22
+
23
+ - Refresh the local setup wizard and hosted chat presentation with the Relmio
24
+ redesign, including clearer progress, copy feedback, responsive layouts, and
25
+ request-state affordances.
26
+ - Move the hosted chat and package homepage to
27
+ [relmio.vercel.app](https://relmio.vercel.app/) with Node.js 22 and
28
+ repository-driven preview and production deployments.
29
+ - Return ChatGPT OAuth callbacks to the deployment that started sign-in so
30
+ Vercel preview URLs and the production domain both work.
31
+ - Run hosted web linting, type checks, builds, tests, and dependency auditing
32
+ in GitHub Actions alongside repository-driven deployments.
33
+ - Point package and documentation metadata at the canonical `relmio`
34
+ repository.
35
+ - License Relmio under Apache 2.0 and preserve the upstream `openai-oauth`
36
+ attribution in the distributed notice.
37
+
38
+ ### Fixed
39
+
40
+ - Stream hosted chat responses incrementally through deployment proxies and
41
+ surface safe request errors instead of leaving an empty assistant message.
42
+ - Distinguish a ChatGPT hosting-network challenge from an expired OAuth
43
+ session without exposing upstream response bodies or credentials.
11
44
 
12
45
  ## [0.2.0] - 2026-07-29
13
46
 
@@ -180,11 +213,11 @@ No unreleased changes.
180
213
  - The sidecar uses an internal-only Docker network endpoint and no published
181
214
  VPS port.
182
215
 
183
- [0.1.7]: https://github.com/Demonbane18/n8n-openai-oauth-setup/compare/v0.1.6...v0.1.7
184
- [0.1.6]: https://github.com/Demonbane18/n8n-openai-oauth-setup/compare/v0.1.5...v0.1.6
185
- [0.1.5]: https://github.com/Demonbane18/n8n-openai-oauth-setup/compare/v0.1.4...v0.1.5
186
- [0.1.4]: https://github.com/Demonbane18/n8n-openai-oauth-setup/compare/v0.1.3...v0.1.4
187
- [0.1.3]: https://github.com/Demonbane18/n8n-openai-oauth-setup/compare/v0.1.2...v0.1.3
188
- [0.1.2]: https://github.com/Demonbane18/n8n-openai-oauth-setup/compare/v0.1.1...v0.1.2
189
- [0.1.1]: https://github.com/Demonbane18/n8n-openai-oauth-setup/compare/v0.1.0...v0.1.1
190
- [0.1.0]: https://github.com/Demonbane18/n8n-openai-oauth-setup/releases/tag/v0.1.0
216
+ [0.1.7]: https://github.com/Demonbane18/relmio/compare/v0.1.6...v0.1.7
217
+ [0.1.6]: https://github.com/Demonbane18/relmio/compare/v0.1.5...v0.1.6
218
+ [0.1.5]: https://github.com/Demonbane18/relmio/compare/v0.1.4...v0.1.5
219
+ [0.1.4]: https://github.com/Demonbane18/relmio/compare/v0.1.3...v0.1.4
220
+ [0.1.3]: https://github.com/Demonbane18/relmio/compare/v0.1.2...v0.1.3
221
+ [0.1.2]: https://github.com/Demonbane18/relmio/compare/v0.1.1...v0.1.2
222
+ [0.1.1]: https://github.com/Demonbane18/relmio/compare/v0.1.0...v0.1.1
223
+ [0.1.0]: https://github.com/Demonbane18/relmio/releases/tag/v0.1.0
package/LICENSE CHANGED
@@ -1,21 +1,201 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Demonbane18
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright 2026 Relmio contributors
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
package/NOTICE ADDED
@@ -0,0 +1,11 @@
1
+ Relmio
2
+ Copyright 2026 Relmio contributors
3
+
4
+ Relmio uses software and methods developed by Evan Zhou and the OpenAI OAuth
5
+ contributors:
6
+
7
+ OpenAI OAuth
8
+ Copyright 2026 Evan Zhou and OpenAI OAuth contributors
9
+ https://github.com/EvanZhouDev/openai-oauth
10
+
11
+ OpenAI OAuth is licensed under the Apache License, Version 2.0.
package/README.md CHANGED
@@ -1,13 +1,15 @@
1
1
  <div align="center">
2
- <img src="https://raw.githubusercontent.com/Demonbane18/n8n-openai-oauth-setup/main/docs/images/brand/relmio-mark.svg" alt="Relmio logo" width="88">
2
+ <img src="https://cdn.jsdelivr.net/npm/relmio@latest/docs/images/brand/relmio-mark.svg" alt="Relmio logo" width="88">
3
3
  <h1>Relmio</h1>
4
4
  <p>Relay a supported ChatGPT/Codex sign-in to OpenAI-compatible clients, starting with self-hosted n8n.</p>
5
5
  <p>
6
- <a href="https://github.com/Demonbane18/n8n-openai-oauth-setup">Full guide</a>
6
+ <a href="https://github.com/Demonbane18/relmio">Full guide</a>
7
7
  &nbsp;·&nbsp;
8
- <a href="https://github.com/Demonbane18/n8n-openai-oauth-setup/issues/new">Report an issue</a>
8
+ <a href="https://relmio.vercel.app/">Hosted ChatGPT site</a>
9
9
  &nbsp;·&nbsp;
10
- <a href="https://github.com/Demonbane18/n8n-openai-oauth-setup/blob/main/docs/roadmap.md">Roadmap</a>
10
+ <a href="https://github.com/Demonbane18/relmio/issues/new">Report an issue</a>
11
+ &nbsp;·&nbsp;
12
+ <a href="https://github.com/Demonbane18/relmio/blob/main/docs/roadmap.md">Roadmap</a>
11
13
  </p>
12
14
  </div>
13
15
 
@@ -19,6 +21,15 @@ exact change plan, and the final n8n credential settings.
19
21
 
20
22
  The existing n8n image, Compose file, container, and workflows stay untouched.
21
23
 
24
+ Try the hosted browser demo at
25
+ [relmio.vercel.app](https://relmio.vercel.app/). It is a separate
26
+ request-bound ChatGPT experience; the npm package remains the local wizard for
27
+ installing the private n8n sidecar.
28
+
29
+ Use the site's [Install wizard](https://relmio.vercel.app/install) page for a
30
+ copyable command tailored to the current self-hosted n8n and Hostinger VPS
31
+ setup path.
32
+
22
33
  ## Quick start
23
34
 
24
35
  Run this on your own macOS, Windows, or Linux computer—not on the VPS:
@@ -55,7 +66,7 @@ Relmio currently provides tested setup instructions for:
55
66
  The broader direction is to support local chatbots, custom applications,
56
67
  OpenAI-compatible clients, and provider adapters without tying the public
57
68
  product name to n8n. SuperGrok/xAI OAuth is a gated feasibility item on the
58
- [provider roadmap](https://github.com/Demonbane18/n8n-openai-oauth-setup/blob/main/docs/roadmap.md);
69
+ [provider roadmap](https://github.com/Demonbane18/relmio/blob/main/docs/roadmap.md);
59
70
  it is not currently advertised as supported.
60
71
 
61
72
  ## Visual walkthrough
@@ -65,23 +76,23 @@ data, and no real credential or session information.
65
76
 
66
77
  ### 1. Confirm the local ChatGPT/Codex sign-in
67
78
 
68
- <img src="https://raw.githubusercontent.com/Demonbane18/n8n-openai-oauth-setup/main/docs/images/setup/01-local-sign-in-ready.png" alt="Sanitized local sign-in ready screen" width="720">
79
+ <img src="https://cdn.jsdelivr.net/npm/relmio@latest/docs/images/setup/01-local-sign-in-ready.png" alt="Sanitized local sign-in ready screen" width="720">
69
80
 
70
81
  ### 2. Verify the VPS identity
71
82
 
72
- <img src="https://raw.githubusercontent.com/Demonbane18/n8n-openai-oauth-setup/main/docs/images/setup/02-vps-identity-confirmed.png" alt="Sanitized VPS fingerprint confirmation screen" width="720">
83
+ <img src="https://cdn.jsdelivr.net/npm/relmio@latest/docs/images/setup/02-vps-identity-confirmed.png" alt="Sanitized VPS fingerprint confirmation screen" width="720">
73
84
 
74
85
  ### 3. Choose the detected n8n container and network
75
86
 
76
- <img src="https://raw.githubusercontent.com/Demonbane18/n8n-openai-oauth-setup/main/docs/images/setup/03-n8n-detected.png" alt="Sanitized n8n discovery screen" width="720">
87
+ <img src="https://cdn.jsdelivr.net/npm/relmio@latest/docs/images/setup/03-n8n-detected.png" alt="Sanitized n8n discovery screen" width="720">
77
88
 
78
89
  ### 4. Review the exact sidecar-only plan
79
90
 
80
- <img src="https://raw.githubusercontent.com/Demonbane18/n8n-openai-oauth-setup/main/docs/images/setup/04-install-plan.png" alt="Sanitized installation plan screen" width="720">
91
+ <img src="https://cdn.jsdelivr.net/npm/relmio@latest/docs/images/setup/04-install-plan.png" alt="Sanitized installation plan screen" width="720">
81
92
 
82
93
  ### 5. Copy the verified n8n settings
83
94
 
84
- <img src="https://raw.githubusercontent.com/Demonbane18/n8n-openai-oauth-setup/main/docs/images/setup/05-bridge-ready.png" alt="Sanitized verified bridge screen" width="720">
95
+ <img src="https://cdn.jsdelivr.net/npm/relmio@latest/docs/images/setup/05-bridge-ready.png" alt="Sanitized verified bridge screen" width="720">
85
96
 
86
97
  Use these values in an n8n OpenAI credential:
87
98
 
@@ -108,15 +119,52 @@ Platform API key.
108
119
  - Use it only where your account, subscription, provider terms, and applicable
109
120
  policies allow.
110
121
 
122
+ ## How it works
123
+
124
+ OpenAI's Codex CLI uses authenticated endpoints at
125
+ `chatgpt.com/backend-api/codex` to run models with a ChatGPT account. Relmio
126
+ uses the same OAuth credential shape through the upstream
127
+ [`openai-oauth`](https://github.com/EvanZhouDev/openai-oauth) helper to expose
128
+ an OpenAI-compatible path without creating an OpenAI Platform API key or
129
+ requiring separate API credits. The upstream service and its access rules may
130
+ change.
131
+
132
+ ## Known limitations
133
+
134
+ - Only models supported by Codex are available. The list changes over time and
135
+ depends on your ChatGPT plan.
136
+ - The CLI `/v1/responses` endpoint is stateless. Callers must send the full
137
+ conversation history; stateful replay is not provided.
138
+ - Hosted browser sign-in currently supports Chrome and Firefox. Safari is not
139
+ yet supported by the upstream Sign in with ChatGPT flow.
140
+
141
+ ## Legal
142
+
143
+ Relmio and `openai-oauth` are unofficial, community-maintained projects and
144
+ are not affiliated with, endorsed by, or sponsored by OpenAI.
145
+
146
+ ChatGPT OAuth credentials should be treated like passwords. Use your own
147
+ account, keep credentials private, and never pool, share, or redistribute
148
+ access tokens. Do not bypass rate limits, restrictions, or safeguards.
149
+
150
+ You are responsible for complying with OpenAI's [Terms of
151
+ Use](https://openai.com/policies/terms-of-use/), [Usage
152
+ Policies](https://openai.com/policies/usage-policies/), and any agreement that
153
+ applies to your account. This project is provided as-is without warranties;
154
+ OpenAI may change or disable the underlying services at any time.
155
+
111
156
  ## Documentation
112
157
 
113
- - [Complete GitHub README and manual fallback](https://github.com/Demonbane18/n8n-openai-oauth-setup#readme)
114
- - [Configure n8n AI and HTTP nodes](https://github.com/Demonbane18/n8n-openai-oauth-setup/blob/main/docs/n8n-configuration.md)
115
- - [Troubleshooting](https://github.com/Demonbane18/n8n-openai-oauth-setup/blob/main/docs/troubleshooting.md)
116
- - [Security and limitations](https://github.com/Demonbane18/n8n-openai-oauth-setup/blob/main/docs/security.md)
117
- - [Refresh, upgrade, rollback, and uninstall](https://github.com/Demonbane18/n8n-openai-oauth-setup/blob/main/docs/maintenance.md)
118
- - [Changelog](https://github.com/Demonbane18/n8n-openai-oauth-setup/blob/main/CHANGELOG.md)
158
+ - [Complete GitHub README and manual fallback](https://github.com/Demonbane18/relmio#readme)
159
+ - [Configure n8n AI and HTTP nodes](https://github.com/Demonbane18/relmio/blob/main/docs/n8n-configuration.md)
160
+ - [Troubleshooting](https://github.com/Demonbane18/relmio/blob/main/docs/troubleshooting.md)
161
+ - [Security and limitations](https://github.com/Demonbane18/relmio/blob/main/docs/security.md)
162
+ - [Refresh, upgrade, rollback, and uninstall](https://github.com/Demonbane18/relmio/blob/main/docs/maintenance.md)
163
+ - [Changelog](https://github.com/Demonbane18/relmio/blob/main/CHANGELOG.md)
119
164
 
120
165
  ## License
121
166
 
122
- [MIT](https://github.com/Demonbane18/n8n-openai-oauth-setup/blob/main/LICENSE)
167
+ [Apache License 2.0](https://github.com/Demonbane18/relmio/blob/main/LICENSE).
168
+ See the package
169
+ [NOTICE](https://github.com/Demonbane18/relmio/blob/main/NOTICE) for the
170
+ upstream `openai-oauth` attribution.
@@ -203,7 +203,7 @@ On the npm package's **Trusted Publisher** form, use:
203
203
  |---|---|
204
204
  | Publisher | GitHub Actions |
205
205
  | Organization or user | `Demonbane18` |
206
- | Repository | `n8n-openai-oauth-setup` |
206
+ | Repository | `relmio` |
207
207
  | Workflow filename | `publish.yml` |
208
208
  | Environment name | `npm` |
209
209
  | Allowed action | Allow npm publish |
package/package.json CHANGED
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "name": "relmio",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Turn a supported ChatGPT/Codex OAuth sign-in into a private OpenAI-compatible endpoint, starting with self-hosted n8n.",
5
5
  "type": "module",
6
- "license": "MIT",
6
+ "license": "Apache-2.0",
7
7
  "repository": {
8
8
  "type": "git",
9
- "url": "git+https://github.com/Demonbane18/n8n-openai-oauth-setup.git"
9
+ "url": "git+https://github.com/Demonbane18/relmio.git"
10
10
  },
11
11
  "bugs": {
12
- "url": "https://github.com/Demonbane18/n8n-openai-oauth-setup/issues"
12
+ "url": "https://github.com/Demonbane18/relmio/issues"
13
13
  },
14
- "homepage": "https://github.com/Demonbane18/n8n-openai-oauth-setup#readme",
14
+ "homepage": "https://relmio.vercel.app/",
15
15
  "engines": {
16
16
  "node": ">=22"
17
17
  },
@@ -28,13 +28,14 @@
28
28
  "README.md",
29
29
  "SPEC.md",
30
30
  "CHANGELOG.md",
31
- "LICENSE"
31
+ "LICENSE",
32
+ "NOTICE"
32
33
  ],
33
34
  "scripts": {
34
35
  "start": "node src/cli.js",
35
36
  "preview": "node scripts/preview.js",
36
37
  "package:build": "node scripts/build-npm-package.js",
37
- "test": "node --test",
38
+ "test": "node --test test/*.test.js",
38
39
  "lint": "node scripts/check-syntax.js",
39
40
  "release:check": "node scripts/check-release-metadata.js",
40
41
  "check": "npm run lint && npm run release:check && npm test"
package/src/ui/app.js CHANGED
@@ -48,6 +48,24 @@ function setBusy(button, busy, busyText) {
48
48
  button.textContent = busy ? busyText : button.dataset.label;
49
49
  }
50
50
 
51
+ const revertTimers = new WeakMap();
52
+
53
+ function flashCopied(button) {
54
+ if (!button.dataset.label) {
55
+ button.dataset.label = button.textContent;
56
+ }
57
+ button.textContent = "Copied";
58
+ button.classList.add("copied");
59
+ window.clearTimeout(revertTimers.get(button));
60
+ revertTimers.set(
61
+ button,
62
+ window.setTimeout(() => {
63
+ button.textContent = button.dataset.label;
64
+ button.classList.remove("copied");
65
+ }, 1800),
66
+ );
67
+ }
68
+
51
69
  async function copyText(value) {
52
70
  try {
53
71
  await navigator.clipboard.writeText(value);
@@ -405,7 +423,7 @@ element("container-select").addEventListener("change", async () => {
405
423
  await loadNetworks();
406
424
  } catch (error) {
407
425
  element("install-confirm").checked = false;
408
- button.disabled = true;
426
+ element("install-button").disabled = true;
409
427
  showStep(2);
410
428
  setMessage(
411
429
  "The install stopped and the VPS connection was closed. Reconnect to inspect the sidecar before retrying.",
@@ -488,7 +506,7 @@ element("copy-settings").addEventListener("click", async (event) => {
488
506
  clearError();
489
507
  try {
490
508
  await copyText(settings);
491
- event.currentTarget.textContent = "Copied";
509
+ flashCopied(event.currentTarget);
492
510
  setMessage("OpenAI credential settings copied.");
493
511
  } catch {
494
512
  showError(new Error("Copy failed. Select the values manually."));
@@ -503,7 +521,7 @@ for (const button of document.querySelectorAll("[data-copy-target]")) {
503
521
  clearError();
504
522
  try {
505
523
  await copyText(value);
506
- event.currentTarget.textContent = "Copied";
524
+ flashCopied(event.currentTarget);
507
525
  setMessage(`${label} copied.`);
508
526
  } catch {
509
527
  showError(new Error(`Copy failed. Select the ${label} manually.`));
package/src/ui/styles.css CHANGED
@@ -1,22 +1,30 @@
1
1
  :root {
2
2
  color-scheme: light;
3
- --background: #f4f2ed;
3
+ --background: #f4f2ec;
4
4
  --surface: #ffffff;
5
- --surface-muted: #f8f8f6;
6
- --text: #12211f;
7
- --text-muted: #5d6865;
8
- --border: #d7dcda;
9
- --border-strong: #aeb8b5;
5
+ --surface-muted: #f7f6f1;
6
+ --text: #0d1b18;
7
+ --text-muted: #53655f;
8
+ --border: #dde3df;
9
+ --border-strong: #b9c4bf;
10
10
  --accent: #137c74;
11
- --accent-hover: #0f625d;
12
- --accent-soft: #e7f3f0;
11
+ --accent-deep: #0c5f59;
12
+ --accent-bright: #1ba397;
13
+ --accent-soft: #e9f4ef;
13
14
  --warning: #8a4b0f;
14
- --warning-soft: #fff3df;
15
+ --warning-soft: #fff4e2;
16
+ --warning-border: #e3c491;
15
17
  --danger: #a22b2b;
16
18
  --danger-soft: #fff0f0;
17
19
  --success: #17653f;
18
- --shadow: 0 12px 32px rgb(23 33 31 / 8%);
19
- --radius: 0.75rem;
20
+ --ease: cubic-bezier(0.22, 1, 0.36, 1);
21
+ --shadow-sm: 0 1px 2px rgb(13 27 24 / 5%);
22
+ --shadow-md:
23
+ 0 1px 2px rgb(13 27 24 / 4%),
24
+ 0 12px 36px -14px rgb(13 27 24 / 18%);
25
+ --radius-sm: 0.5rem;
26
+ --radius: 0.875rem;
27
+ --radius-lg: 1.25rem;
20
28
  }
21
29
 
22
30
  * {
@@ -29,12 +37,18 @@ html {
29
37
  sans-serif;
30
38
  color: var(--text);
31
39
  background: var(--background);
40
+ scroll-behavior: smooth;
32
41
  }
33
42
 
34
43
  body {
35
44
  min-width: 20rem;
36
45
  margin: 0;
37
46
  line-height: 1.5;
47
+ -webkit-font-smoothing: antialiased;
48
+ }
49
+
50
+ ::selection {
51
+ background: rgb(19 124 116 / 22%);
38
52
  }
39
53
 
40
54
  button,
@@ -66,29 +80,39 @@ a:focus-visible {
66
80
  left: 0.5rem;
67
81
  z-index: 10;
68
82
  padding: 0.75rem 1rem;
83
+ border-radius: 999px;
69
84
  color: #fff;
70
85
  background: var(--text);
71
86
  transform: translateY(-150%);
87
+ transition: transform 200ms var(--ease);
72
88
  }
73
89
 
74
90
  .skip-link:focus {
75
91
  transform: translateY(0);
76
92
  }
77
93
 
94
+ /* ---------- Header ---------- */
95
+
78
96
  .site-header {
97
+ position: sticky;
98
+ top: 0;
99
+ z-index: 20;
79
100
  display: flex;
80
101
  align-items: center;
81
102
  justify-content: space-between;
82
- min-height: 4rem;
103
+ min-height: 4.25rem;
83
104
  padding: 0.75rem clamp(1rem, 4vw, 3rem);
84
- border-bottom: 1px solid var(--border);
85
- background: rgb(255 255 255 / 85%);
105
+ border-bottom: 1px solid rgb(221 227 223 / 75%);
106
+ background: rgb(244 242 236 / 82%);
107
+ backdrop-filter: blur(14px) saturate(1.4);
108
+ -webkit-backdrop-filter: blur(14px) saturate(1.4);
86
109
  }
87
110
 
88
111
  .brand {
89
112
  display: flex;
90
113
  align-items: center;
91
114
  gap: 0.625rem;
115
+ font-size: 1.0625rem;
92
116
  font-weight: 750;
93
117
  letter-spacing: -0.01em;
94
118
  }
@@ -101,19 +125,33 @@ a:focus-visible {
101
125
  }
102
126
 
103
127
  .local-badge {
104
- padding: 0.375rem 0.625rem;
128
+ display: inline-flex;
129
+ align-items: center;
130
+ gap: 0.4375rem;
131
+ padding: 0.4375rem 0.75rem;
105
132
  border: 1px solid var(--border);
106
133
  border-radius: 999px;
107
134
  color: var(--text-muted);
108
135
  background: var(--surface);
136
+ box-shadow: var(--shadow-sm);
109
137
  font-size: 0.75rem;
110
138
  font-weight: 650;
111
139
  }
112
140
 
141
+ .local-badge::before {
142
+ width: 0.4375rem;
143
+ height: 0.4375rem;
144
+ border-radius: 50%;
145
+ background: var(--accent-bright);
146
+ content: "";
147
+ }
148
+
149
+ /* ---------- Shell & intro ---------- */
150
+
113
151
  .shell {
114
152
  width: min(100% - 2rem, 54rem);
115
153
  margin: 0 auto;
116
- padding: 3rem 0 5rem;
154
+ padding: 3.5rem 0 5rem;
117
155
  }
118
156
 
119
157
  .intro {
@@ -140,14 +178,14 @@ p {
140
178
  h1 {
141
179
  max-width: 42rem;
142
180
  margin-bottom: 1rem;
143
- font-size: clamp(2rem, 7vw, 3.5rem);
144
- line-height: 1.05;
181
+ font-size: clamp(2.1rem, 6.5vw, 3.4rem);
182
+ line-height: 1.04;
145
183
  letter-spacing: -0.045em;
146
184
  }
147
185
 
148
186
  h2 {
149
187
  margin-bottom: 0.5rem;
150
- font-size: clamp(1.5rem, 4vw, 2rem);
188
+ font-size: clamp(1.45rem, 4vw, 1.9rem);
151
189
  line-height: 1.2;
152
190
  letter-spacing: -0.025em;
153
191
  }
@@ -167,16 +205,38 @@ h3 {
167
205
  font-size: 1.0625rem;
168
206
  }
169
207
 
208
+ /* ---------- Safety notes ---------- */
209
+
170
210
  .safety-note {
211
+ position: relative;
171
212
  display: grid;
172
- grid-template-columns: max-content 1fr;
173
- gap: 0.75rem 1rem;
174
- margin: 2rem 0;
175
- padding: 1rem;
213
+ grid-template-columns: 2.25rem 1fr;
214
+ gap: 0.25rem 0.875rem;
215
+ align-items: start;
216
+ margin: 1.25rem 0 0;
217
+ padding: 1rem 1.125rem;
176
218
  border: 1px solid #b9d9d2;
177
- border-left: 4px solid var(--accent);
178
- border-radius: 0.5rem;
219
+ border-radius: var(--radius);
179
220
  background: var(--accent-soft);
221
+ box-shadow: var(--shadow-sm);
222
+ }
223
+
224
+ .safety-note::before {
225
+ display: grid;
226
+ width: 2.25rem;
227
+ height: 2.25rem;
228
+ grid-row: span 2;
229
+ place-items: center;
230
+ border-radius: 0.625rem;
231
+ color: var(--accent-deep);
232
+ background: #d3eae0;
233
+ font-size: 1rem;
234
+ font-weight: 800;
235
+ content: "✓";
236
+ }
237
+
238
+ .safety-note strong {
239
+ align-self: center;
180
240
  }
181
241
 
182
242
  .safety-note span {
@@ -184,16 +244,24 @@ h3 {
184
244
  }
185
245
 
186
246
  .backup-note {
187
- border-color: #d7b98d;
247
+ border-color: var(--warning-border);
188
248
  background: var(--warning-soft);
189
249
  }
190
250
 
251
+ .backup-note::before {
252
+ color: var(--warning);
253
+ background: #f6e3c3;
254
+ content: "!";
255
+ }
256
+
191
257
  .backup-note span {
192
258
  color: var(--warning);
193
259
  }
194
260
 
261
+ /* ---------- Stepper ---------- */
262
+
195
263
  .steps {
196
- margin: 2rem 0 1rem;
264
+ margin: 2.5rem 0 1.25rem;
197
265
  }
198
266
 
199
267
  .steps ol {
@@ -208,7 +276,7 @@ h3 {
208
276
  position: relative;
209
277
  display: grid;
210
278
  justify-items: center;
211
- gap: 0.375rem;
279
+ gap: 0.4375rem;
212
280
  color: var(--text-muted);
213
281
  font-size: 0.75rem;
214
282
  text-align: center;
@@ -216,13 +284,14 @@ h3 {
216
284
 
217
285
  .steps li::before {
218
286
  position: absolute;
219
- top: 0.8125rem;
287
+ top: 0.9375rem;
220
288
  right: 50%;
221
- z-index: -1;
289
+ z-index: 0;
222
290
  width: 100%;
223
291
  height: 2px;
224
292
  background: var(--border);
225
293
  content: "";
294
+ transition: background 400ms var(--ease);
226
295
  }
227
296
 
228
297
  .steps li:first-child::before {
@@ -230,29 +299,52 @@ h3 {
230
299
  }
231
300
 
232
301
  .steps li span {
302
+ position: relative;
303
+ z-index: 1;
233
304
  display: grid;
234
- width: 1.75rem;
235
- height: 1.75rem;
305
+ width: 2rem;
306
+ height: 2rem;
236
307
  place-items: center;
237
308
  border: 2px solid var(--border-strong);
238
309
  border-radius: 50%;
239
- background: var(--background);
310
+ background: var(--surface);
311
+ box-shadow: var(--shadow-sm);
312
+ font-size: 0.8125rem;
240
313
  font-weight: 800;
314
+ transition:
315
+ border-color 300ms var(--ease),
316
+ background 300ms var(--ease),
317
+ color 300ms var(--ease),
318
+ box-shadow 300ms var(--ease);
241
319
  }
242
320
 
243
321
  .steps li em {
244
322
  font-style: normal;
323
+ font-weight: 600;
245
324
  }
246
325
 
247
326
  .steps li[aria-current="step"] {
248
327
  color: var(--text);
249
328
  }
250
329
 
251
- .steps li[aria-current="step"] span,
252
- .steps li.complete span {
330
+ .steps li[aria-current="step"] span {
253
331
  border-color: var(--accent);
254
332
  color: #fff;
255
333
  background: var(--accent);
334
+ box-shadow: 0 0 0 5px rgb(19 124 116 / 15%);
335
+ }
336
+
337
+ .steps li.complete span {
338
+ border-color: var(--accent);
339
+ color: transparent;
340
+ background: var(--accent);
341
+ }
342
+
343
+ .steps li.complete span::after {
344
+ position: absolute;
345
+ color: #fff;
346
+ font-size: 0.8125rem;
347
+ content: "✓";
256
348
  }
257
349
 
258
350
  .steps li.complete::before,
@@ -260,17 +352,34 @@ h3 {
260
352
  background: var(--accent);
261
353
  }
262
354
 
355
+ /* ---------- Global messages ---------- */
356
+
263
357
  .message,
264
358
  .error {
265
- min-height: 2.75rem;
359
+ display: flex;
360
+ align-items: center;
361
+ gap: 0.625rem;
362
+ min-height: 2.875rem;
266
363
  margin: 1rem 0;
267
364
  padding: 0.75rem 1rem;
268
- border-radius: 0.5rem;
365
+ border-radius: var(--radius-sm);
366
+ font-size: 0.9375rem;
269
367
  }
270
368
 
271
369
  .message {
370
+ border: 1px solid var(--border);
272
371
  color: var(--text-muted);
273
- background: var(--surface-muted);
372
+ background: var(--surface);
373
+ box-shadow: var(--shadow-sm);
374
+ }
375
+
376
+ .message::before {
377
+ flex: 0 0 auto;
378
+ width: 0.4375rem;
379
+ height: 0.4375rem;
380
+ border-radius: 50%;
381
+ background: var(--accent-bright);
382
+ content: "";
274
383
  }
275
384
 
276
385
  .error {
@@ -278,14 +387,18 @@ h3 {
278
387
  color: var(--danger);
279
388
  background: var(--danger-soft);
280
389
  font-weight: 650;
390
+ animation: shake 320ms var(--ease);
281
391
  }
282
392
 
393
+ /* ---------- Panels ---------- */
394
+
283
395
  .panel {
284
- padding: clamp(1.25rem, 4vw, 2rem);
396
+ padding: clamp(1.5rem, 4vw, 2.25rem);
285
397
  border: 1px solid var(--border);
286
- border-radius: var(--radius);
398
+ border-radius: var(--radius-lg);
287
399
  background: var(--surface);
288
- box-shadow: var(--shadow);
400
+ box-shadow: var(--shadow-md);
401
+ animation: panel-in 380ms var(--ease);
289
402
  }
290
403
 
291
404
  .panel-heading {
@@ -301,9 +414,9 @@ h3 {
301
414
  display: flex;
302
415
  align-items: flex-start;
303
416
  gap: 0.875rem;
304
- padding: 1rem;
417
+ padding: 1rem 1.125rem;
305
418
  border: 1px solid var(--border);
306
- border-radius: 0.5rem;
419
+ border-radius: var(--radius);
307
420
  background: var(--surface-muted);
308
421
  }
309
422
 
@@ -325,13 +438,20 @@ h3 {
325
438
  margin-top: 0.375rem;
326
439
  border: 2px solid var(--border-strong);
327
440
  border-radius: 50%;
441
+ transition:
442
+ border-color 300ms var(--ease),
443
+ background 300ms var(--ease),
444
+ box-shadow 300ms var(--ease);
328
445
  }
329
446
 
330
447
  .status-dot.ready {
331
448
  border-color: var(--success);
332
449
  background: var(--success);
450
+ box-shadow: 0 0 0 4px rgb(23 101 63 / 14%);
333
451
  }
334
452
 
453
+ /* ---------- Fields ---------- */
454
+
335
455
  .field-grid {
336
456
  display: grid;
337
457
  grid-template-columns: minmax(0, 2fr) minmax(7rem, 1fr);
@@ -346,24 +466,56 @@ h3 {
346
466
  .field {
347
467
  display: grid;
348
468
  align-content: start;
349
- gap: 0.375rem;
469
+ gap: 0.4375rem;
350
470
  font-weight: 700;
351
471
  }
352
472
 
353
473
  .field input,
354
474
  .field select {
355
475
  width: 100%;
356
- min-height: 2.75rem;
357
- padding: 0.625rem 0.75rem;
476
+ min-height: 2.875rem;
477
+ padding: 0.625rem 0.875rem;
358
478
  border: 1px solid var(--border-strong);
359
- border-radius: 0.375rem;
479
+ border-radius: var(--radius-sm);
360
480
  color: var(--text);
361
481
  background: #fff;
482
+ transition:
483
+ border-color 200ms var(--ease),
484
+ box-shadow 200ms var(--ease),
485
+ background 200ms var(--ease);
486
+ }
487
+
488
+ .field select {
489
+ appearance: none;
490
+ padding-right: 2.25rem;
491
+ background-image:
492
+ linear-gradient(45deg, transparent 50%, var(--text-muted) 50%),
493
+ linear-gradient(135deg, var(--text-muted) 50%, transparent 50%);
494
+ background-position:
495
+ calc(100% - 1.125rem) 50%,
496
+ calc(100% - 0.8125rem) 50%;
497
+ background-size:
498
+ 0.3125rem 0.3125rem,
499
+ 0.3125rem 0.3125rem;
500
+ background-repeat: no-repeat;
501
+ }
502
+
503
+ .field input:hover,
504
+ .field select:hover {
505
+ border-color: var(--accent);
362
506
  }
363
507
 
364
508
  .field input:focus,
365
509
  .field select:focus {
366
510
  border-color: var(--accent);
511
+ outline: 0;
512
+ box-shadow: 0 0 0 4px rgb(19 124 116 / 13%);
513
+ }
514
+
515
+ .field input:disabled {
516
+ color: var(--text-muted);
517
+ background: var(--surface-muted);
518
+ cursor: not-allowed;
367
519
  }
368
520
 
369
521
  .field small {
@@ -381,21 +533,46 @@ code {
381
533
  overflow-wrap: anywhere;
382
534
  }
383
535
 
536
+ /* ---------- Buttons ---------- */
537
+
384
538
  .actions {
385
539
  display: flex;
386
540
  flex-wrap: wrap;
387
541
  justify-content: flex-end;
388
542
  gap: 0.75rem;
389
- margin-top: 1.5rem;
543
+ margin-top: 1.75rem;
390
544
  }
391
545
 
392
546
  .button {
393
- min-height: 2.75rem;
394
- padding: 0.625rem 1rem;
547
+ position: relative;
548
+ display: inline-flex;
549
+ align-items: center;
550
+ justify-content: center;
551
+ gap: 0.5rem;
552
+ min-height: 2.875rem;
553
+ padding: 0.625rem 1.125rem;
395
554
  border: 1px solid transparent;
396
- border-radius: 0.375rem;
555
+ border-radius: 0.625rem;
397
556
  font-weight: 750;
557
+ box-shadow: var(--shadow-sm);
398
558
  cursor: pointer;
559
+ transition:
560
+ transform 200ms var(--ease),
561
+ box-shadow 200ms var(--ease),
562
+ background 200ms var(--ease),
563
+ color 200ms var(--ease),
564
+ border-color 200ms var(--ease),
565
+ opacity 200ms var(--ease);
566
+ }
567
+
568
+ .button:not(:disabled):hover {
569
+ transform: translateY(-1px);
570
+ box-shadow: var(--shadow-md);
571
+ }
572
+
573
+ .button:not(:disabled):active {
574
+ transform: translateY(0) scale(0.98);
575
+ box-shadow: var(--shadow-sm);
399
576
  }
400
577
 
401
578
  .button:disabled {
@@ -403,13 +580,28 @@ code {
403
580
  opacity: 0.48;
404
581
  }
405
582
 
583
+ .button[aria-busy="true"] {
584
+ cursor: progress;
585
+ }
586
+
587
+ .button[aria-busy="true"]::after {
588
+ width: 0.9375rem;
589
+ height: 0.9375rem;
590
+ flex: 0 0 auto;
591
+ border: 2px solid currentcolor;
592
+ border-top-color: transparent;
593
+ border-radius: 50%;
594
+ content: "";
595
+ animation: spin 700ms linear infinite;
596
+ }
597
+
406
598
  .button.primary {
407
599
  color: #fff;
408
600
  background: var(--accent);
409
601
  }
410
602
 
411
603
  .button.primary:not(:disabled):hover {
412
- background: var(--accent-hover);
604
+ background: var(--accent-deep);
413
605
  }
414
606
 
415
607
  .button.secondary {
@@ -420,21 +612,32 @@ code {
420
612
 
421
613
  .button.secondary:not(:disabled):hover,
422
614
  .button.ghost:not(:disabled):hover {
423
- background: var(--surface-muted);
615
+ border-color: var(--accent);
616
+ background: var(--accent-soft);
424
617
  }
425
618
 
426
619
  .button.ghost {
427
620
  margin-right: auto;
428
621
  color: var(--text-muted);
429
622
  background: transparent;
623
+ box-shadow: none;
624
+ }
625
+
626
+ .button.copied {
627
+ border-color: var(--success);
628
+ color: var(--success);
629
+ background: #e9f5ee;
430
630
  }
431
631
 
632
+ /* ---------- Fingerprint & approvals ---------- */
633
+
432
634
  .fingerprint {
433
635
  margin-top: 1rem;
434
- padding: 1rem;
435
- border: 1px solid #d7b98d;
436
- border-radius: 0.5rem;
636
+ padding: 1.125rem;
637
+ border: 1px solid var(--warning-border);
638
+ border-radius: var(--radius);
437
639
  background: var(--warning-soft);
640
+ animation: panel-in 320ms var(--ease);
438
641
  }
439
642
 
440
643
  .fingerprint > span {
@@ -449,6 +652,9 @@ code {
449
652
  .fingerprint > code {
450
653
  display: block;
451
654
  margin-bottom: 1rem;
655
+ padding: 0.625rem 0.75rem;
656
+ border: 1px solid var(--warning-border);
657
+ border-radius: var(--radius-sm);
452
658
  background: #fff;
453
659
  }
454
660
 
@@ -465,23 +671,30 @@ code {
465
671
  width: 1.125rem;
466
672
  height: 1.125rem;
467
673
  margin: 0.2rem 0 0;
674
+ accent-color: var(--accent);
675
+ cursor: pointer;
468
676
  }
469
677
 
470
678
  .version-strip {
471
679
  display: flex;
472
680
  flex-wrap: wrap;
473
- gap: 0.75rem;
474
- margin-bottom: 1rem;
681
+ gap: 0.625rem;
682
+ margin-bottom: 1.25rem;
475
683
  }
476
684
 
477
685
  .version-strip span {
478
- padding: 0.375rem 0.625rem;
686
+ display: inline-flex;
687
+ align-items: center;
688
+ gap: 0.375rem;
689
+ padding: 0.4375rem 0.75rem;
479
690
  border: 1px solid var(--border);
480
691
  border-radius: 999px;
481
692
  background: var(--surface-muted);
482
- font-size: 0.875rem;
693
+ font-size: 0.8125rem;
483
694
  }
484
695
 
696
+ /* ---------- Review ---------- */
697
+
485
698
  .review-grid {
486
699
  display: grid;
487
700
  grid-template-columns: 1fr 1fr;
@@ -489,9 +702,20 @@ code {
489
702
  }
490
703
 
491
704
  .review-grid section {
492
- padding: 1rem;
705
+ padding: 1.125rem;
493
706
  border: 1px solid var(--border);
494
- border-radius: 0.5rem;
707
+ border-radius: var(--radius);
708
+ background: var(--surface-muted);
709
+ }
710
+
711
+ .review-grid section:first-child {
712
+ border-color: #b9d9d2;
713
+ background: var(--accent-soft);
714
+ }
715
+
716
+ .review-grid section:last-child {
717
+ border-color: #e4abab;
718
+ background: var(--danger-soft);
495
719
  }
496
720
 
497
721
  .check-list,
@@ -512,27 +736,31 @@ code {
512
736
 
513
737
  .approval {
514
738
  margin-top: 1rem;
515
- padding: 1rem;
516
- border: 1px solid #d7b98d;
517
- border-radius: 0.5rem;
739
+ padding: 1rem 1.125rem;
740
+ border: 1px solid var(--warning-border);
741
+ border-radius: var(--radius);
518
742
  background: var(--warning-soft);
519
743
  }
520
744
 
745
+ /* ---------- Success ---------- */
746
+
521
747
  .success-panel {
522
748
  position: relative;
523
749
  }
524
750
 
525
751
  .success-mark {
526
752
  display: grid;
527
- width: 3rem;
528
- height: 3rem;
529
- margin-bottom: 1rem;
753
+ width: 3.25rem;
754
+ height: 3.25rem;
755
+ margin-bottom: 1.25rem;
530
756
  place-items: center;
531
757
  border-radius: 50%;
532
758
  color: #fff;
533
- background: var(--success);
759
+ background: linear-gradient(140deg, #1d8a5f, var(--success));
760
+ box-shadow: 0 0 0 6px rgb(23 101 63 / 14%);
534
761
  font-size: 1.5rem;
535
762
  font-weight: 900;
763
+ animation: pop-in 480ms var(--ease);
536
764
  }
537
765
 
538
766
  .result-list {
@@ -543,6 +771,7 @@ code {
543
771
  display: grid;
544
772
  grid-template-columns: 9rem 1fr;
545
773
  gap: 1rem;
774
+ align-items: center;
546
775
  padding: 0.875rem 0;
547
776
  border-bottom: 1px solid var(--border);
548
777
  }
@@ -560,20 +789,23 @@ code {
560
789
  display: flex;
561
790
  flex-wrap: wrap;
562
791
  align-items: center;
792
+ justify-content: space-between;
563
793
  gap: 0.75rem;
564
794
  }
565
795
 
566
796
  .copy-value {
567
- min-height: 2rem;
568
- padding: 0.25rem 0.625rem;
797
+ min-height: 2.125rem;
798
+ padding: 0.3125rem 0.75rem;
799
+ border-radius: 0.5rem;
569
800
  font-size: 0.8125rem;
570
801
  }
571
802
 
572
803
  .model-box,
573
804
  .final-note {
574
805
  margin-top: 1.5rem;
575
- padding: 1rem;
576
- border-radius: 0.5rem;
806
+ padding: 1.125rem;
807
+ border: 1px solid var(--border);
808
+ border-radius: var(--radius);
577
809
  background: var(--surface-muted);
578
810
  }
579
811
 
@@ -582,6 +814,10 @@ code {
582
814
  margin-bottom: 0;
583
815
  }
584
816
 
817
+ .model-box p {
818
+ color: var(--text-muted);
819
+ }
820
+
585
821
  footer {
586
822
  padding: 1.5rem 1rem;
587
823
  border-top: 1px solid var(--border);
@@ -595,6 +831,54 @@ footer p {
595
831
  margin: 0;
596
832
  }
597
833
 
834
+ /* ---------- Animations ---------- */
835
+
836
+ @keyframes panel-in {
837
+ from {
838
+ opacity: 0;
839
+ transform: translateY(14px);
840
+ }
841
+ to {
842
+ opacity: 1;
843
+ transform: translateY(0);
844
+ }
845
+ }
846
+
847
+ @keyframes pop-in {
848
+ 0% {
849
+ opacity: 0;
850
+ transform: scale(0.5);
851
+ }
852
+ 70% {
853
+ transform: scale(1.08);
854
+ }
855
+ 100% {
856
+ opacity: 1;
857
+ transform: scale(1);
858
+ }
859
+ }
860
+
861
+ @keyframes shake {
862
+ 0%,
863
+ 100% {
864
+ transform: translateX(0);
865
+ }
866
+ 25% {
867
+ transform: translateX(-4px);
868
+ }
869
+ 75% {
870
+ transform: translateX(4px);
871
+ }
872
+ }
873
+
874
+ @keyframes spin {
875
+ to {
876
+ transform: rotate(360deg);
877
+ }
878
+ }
879
+
880
+ /* ---------- Responsive ---------- */
881
+
598
882
  @media (max-width: 42rem) {
599
883
  .local-badge {
600
884
  display: none;
@@ -611,12 +895,16 @@ footer p {
611
895
  grid-template-columns: 1fr;
612
896
  }
613
897
 
898
+ .safety-note::before {
899
+ grid-row: auto;
900
+ }
901
+
614
902
  .steps li em {
615
903
  font-size: 0.6875rem;
616
904
  }
617
905
 
618
906
  .panel {
619
- border-radius: 0.5rem;
907
+ border-radius: var(--radius);
620
908
  }
621
909
 
622
910
  .actions {
@@ -636,9 +924,15 @@ footer p {
636
924
  }
637
925
 
638
926
  @media (prefers-reduced-motion: reduce) {
927
+ html {
928
+ scroll-behavior: auto;
929
+ }
930
+
639
931
  *,
640
932
  *::before,
641
933
  *::after {
934
+ animation-duration: 0.01ms !important;
935
+ animation-iteration-count: 1 !important;
642
936
  scroll-behavior: auto !important;
643
937
  transition-duration: 0.01ms !important;
644
938
  }