relmio 0.2.7 → 0.2.9
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 +21 -0
- package/README.md +4 -0
- package/docs/troubleshooting.md +2 -0
- package/package.json +1 -1
- package/src/services/oauth.js +51 -7
- package/src/ui/app.js +3 -1
- package/src/ui/icons/monitor.svg +6 -0
- package/src/ui/icons/moon.svg +4 -0
- package/src/ui/icons/sun.svg +12 -0
- package/src/ui/index.html +9 -6
- package/src/ui/styles.css +33 -16
- package/src/web/server.js +11 -3
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,27 @@ checks the registry separately after publication.
|
|
|
7
7
|
|
|
8
8
|
## Unreleased
|
|
9
9
|
|
|
10
|
+
## [0.2.9] - 2026-08-02
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- Launch the local ChatGPT OAuth helper through the current Windows Node.js
|
|
15
|
+
runtime and npm's JavaScript CLI instead of executing `npx.cmd` directly,
|
|
16
|
+
preventing `spawn EINVAL` while preserving the native `npx` path on macOS,
|
|
17
|
+
Linux, WSL, and Git Bash.
|
|
18
|
+
- Explain how to recover when a refreshed wizard page no longer has its private
|
|
19
|
+
session URL.
|
|
20
|
+
|
|
21
|
+
## [0.2.8] - 2026-08-02
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
|
|
25
|
+
- Replace the local browser wizard's text appearance selector with compact,
|
|
26
|
+
accessible System/Light/Dark icons, while keeping the original horizontal
|
|
27
|
+
Signal Spine flow and touch-friendly behavior.
|
|
28
|
+
- Serve the bundled Lucide SVG assets from the wizard and include them in the
|
|
29
|
+
npm package so offline and Node-free browser launches render consistently.
|
|
30
|
+
|
|
10
31
|
## [0.2.7] - 2026-08-02
|
|
11
32
|
|
|
12
33
|
### Changed
|
package/README.md
CHANGED
|
@@ -124,6 +124,10 @@ Users who already have Node.js 22 or newer can run the npm package directly:
|
|
|
124
124
|
npx --yes --ignore-scripts relmio@latest
|
|
125
125
|
```
|
|
126
126
|
|
|
127
|
+
Keep the active setup terminal open while using the wizard. If the page says
|
|
128
|
+
the wizard link is incomplete, close that tab and open the full `Local wizard:`
|
|
129
|
+
URL printed by the current terminal instead of refreshing the stripped page.
|
|
130
|
+
|
|
127
131
|
### Requirements
|
|
128
132
|
|
|
129
133
|
- On macOS/Linux/WSL/Git Bash: `curl`, `awk`, `tar`, and either `sha256sum` or
|
package/docs/troubleshooting.md
CHANGED
|
@@ -113,7 +113,9 @@ bypassed.
|
|
|
113
113
|
| `node: command not found`, `node is not recognized`, or Node is older than 22 | The NPX fallback cannot use the local runtime. | Use the macOS/Linux curl command or the native Windows PowerShell/Command Prompt command above. Either can run with a verified temporary runtime. Do not install Node.js on the VPS for the wizard. |
|
|
114
114
|
| `curl` or `sh` is not recognized on Windows | The macOS/Linux command was pasted into a native Windows terminal. | Use the PowerShell command in PowerShell, or the longer `powershell -NoProfile ...` command in Command Prompt. Git Bash is not required. |
|
|
115
115
|
| A bootstrap reports a checksum mismatch | The Node.js download did not match the official SHA-256 manifest, so it was not executed. | Retry on a trusted connection. Do not bypass the check. If it repeats, use an existing Node.js 22+ installation and report the sanitized error. |
|
|
116
|
+
| Windows reports `spawn EINVAL` when starting ChatGPT sign-in | An older wizard tried to execute `npx.cmd` directly; Windows requires the current Node runtime to launch npm's JavaScript CLI. | Update to the latest `relmio@latest` release and restart the setup command. The current wizard keeps the macOS/Linux/WSL/Git Bash `npx` path unchanged. |
|
|
116
117
|
| The browser did not open | The automatic browser launch failed, but the local server may still be running. | Keep the newest terminal open and copy its newest `127.0.0.1` setup URL into the browser. Do not reuse a URL from a closed terminal. |
|
|
118
|
+
| The wizard says `This wizard link is incomplete` | The page was refreshed or opened without the private `?session=...` token; the token is removed from the address bar after startup. | Close the tab and open the complete `Local wizard:` URL printed by the active setup terminal. Do not reuse a URL from a closed terminal. |
|
|
117
119
|
| An old wizard page reports an invalid or expired setup session | The local server was closed or a newer wizard run created a different one-time session token. | Close the old page and use only the URL printed by the currently running terminal. |
|
|
118
120
|
| `npx` appears to run an older wizard | An old terminal or tab is still active, or the package was run without an explicit tag. | Close old runs, check `npm view relmio version`, then run `npx --yes --ignore-scripts relmio@latest`. |
|
|
119
121
|
| `This sign-in request expired` | The OAuth tab is old or the five-minute callback window ended. | Close the old tab and select **Refresh ChatGPT sign-in** from the newest active wizard. |
|
package/package.json
CHANGED
package/src/services/oauth.js
CHANGED
|
@@ -16,6 +16,37 @@ const OPENAI_AUTH_ORIGIN = "https://auth.openai.com";
|
|
|
16
16
|
const wait = (milliseconds) =>
|
|
17
17
|
new Promise((resolvePromise) => setTimeout(resolvePromise, milliseconds));
|
|
18
18
|
|
|
19
|
+
function resolveWindowsNpxCli({ env, execPath }) {
|
|
20
|
+
const npmExecPathKey = Object.keys(env ?? {}).find(
|
|
21
|
+
(key) => key.toLowerCase() === "npm_execpath",
|
|
22
|
+
);
|
|
23
|
+
const npmExecPath =
|
|
24
|
+
npmExecPathKey === undefined ? undefined : env[npmExecPathKey];
|
|
25
|
+
|
|
26
|
+
if (typeof npmExecPath === "string") {
|
|
27
|
+
if (/(?:^|[\\/])npx-cli\.js$/iu.test(npmExecPath)) {
|
|
28
|
+
return resolve(npmExecPath);
|
|
29
|
+
}
|
|
30
|
+
if (/(?:^|[\\/])npm-cli\.js$/iu.test(npmExecPath)) {
|
|
31
|
+
return resolve(dirname(npmExecPath), "npx-cli.js");
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return resolve(dirname(execPath), "node_modules", "npm", "bin", "npx-cli.js");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function createNpxInvocation({ platform, env, execPath }) {
|
|
39
|
+
if (platform !== "win32") {
|
|
40
|
+
return { command: "npx", prefixArgs: [] };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
command: execPath,
|
|
45
|
+
// Windows cannot execute npx.cmd directly with shell:false.
|
|
46
|
+
prefixArgs: [resolveWindowsNpxCli({ env, execPath })],
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
19
50
|
export function resolveAuthPath({
|
|
20
51
|
env = process.env,
|
|
21
52
|
homeDirectory = homedir(),
|
|
@@ -132,11 +163,12 @@ export async function startOAuthLogin({
|
|
|
132
163
|
env = process.env,
|
|
133
164
|
homeDirectory = homedir(),
|
|
134
165
|
platform = process.platform,
|
|
166
|
+
execPath = process.execPath,
|
|
135
167
|
spawnProcess = spawn,
|
|
136
168
|
createPendingId = randomUUID,
|
|
137
169
|
waitForCredentialPoll = wait,
|
|
138
170
|
} = {}) {
|
|
139
|
-
const
|
|
171
|
+
const npxInvocation = createNpxInvocation({ platform, env, execPath });
|
|
140
172
|
const authPath = resolveAuthPath({ env, homeDirectory });
|
|
141
173
|
const authDirectory = dirname(authPath);
|
|
142
174
|
const pendingAuthPath = `${authPath}.pending-${createPendingId()}`;
|
|
@@ -155,12 +187,24 @@ export async function startOAuthLogin({
|
|
|
155
187
|
await fileSystem.mkdir(authDirectory, { recursive: true, mode: 0o700 });
|
|
156
188
|
await fileSystem.chmod(authDirectory, 0o700);
|
|
157
189
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
190
|
+
let child;
|
|
191
|
+
try {
|
|
192
|
+
child = spawnProcess(
|
|
193
|
+
npxInvocation.command,
|
|
194
|
+
[...npxInvocation.prefixArgs, ...args],
|
|
195
|
+
{
|
|
196
|
+
env,
|
|
197
|
+
shell: false,
|
|
198
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
199
|
+
windowsHide: true,
|
|
200
|
+
},
|
|
201
|
+
);
|
|
202
|
+
} catch (error) {
|
|
203
|
+
throw new Error(
|
|
204
|
+
"The local sign-in command could not start. Update Relmio and retry with Node.js 22 or newer.",
|
|
205
|
+
{ cause: error },
|
|
206
|
+
);
|
|
207
|
+
}
|
|
164
208
|
child.stderr?.resume();
|
|
165
209
|
|
|
166
210
|
let loginOutput = "";
|
package/src/ui/app.js
CHANGED
|
@@ -146,7 +146,9 @@ function showStep(step) {
|
|
|
146
146
|
|
|
147
147
|
async function api(path, { method = "GET", body } = {}) {
|
|
148
148
|
if (!token) {
|
|
149
|
-
throw new Error(
|
|
149
|
+
throw new Error(
|
|
150
|
+
"This wizard link is incomplete. Close this tab and open the full URL printed by the active setup terminal.",
|
|
151
|
+
);
|
|
150
152
|
}
|
|
151
153
|
|
|
152
154
|
let response;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<!-- Lucide monitor icon, ISC licensed: https://lucide.dev/license -->
|
|
2
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
3
|
+
<rect width="20" height="14" x="2" y="3" rx="2" />
|
|
4
|
+
<line x1="8" x2="16" y1="21" y2="21" />
|
|
5
|
+
<line x1="12" x2="12" y1="17" y2="21" />
|
|
6
|
+
</svg>
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<!-- Lucide moon icon, ISC licensed: https://lucide.dev/license -->
|
|
2
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
3
|
+
<path d="M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401" />
|
|
4
|
+
</svg>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!-- Lucide sun icon, ISC licensed: https://lucide.dev/license -->
|
|
2
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
|
3
|
+
<circle cx="12" cy="12" r="4" />
|
|
4
|
+
<path d="M12 2v2" />
|
|
5
|
+
<path d="M12 20v2" />
|
|
6
|
+
<path d="m4.93 4.93 1.41 1.41" />
|
|
7
|
+
<path d="m17.66 17.66 1.41 1.41" />
|
|
8
|
+
<path d="M2 12h2" />
|
|
9
|
+
<path d="M20 12h2" />
|
|
10
|
+
<path d="m6.34 17.66-1.41 1.41" />
|
|
11
|
+
<path d="m19.07 4.93-1.41 1.41" />
|
|
12
|
+
</svg>
|
package/src/ui/index.html
CHANGED
|
@@ -35,17 +35,20 @@
|
|
|
35
35
|
<div class="header-actions">
|
|
36
36
|
<fieldset class="theme-picker">
|
|
37
37
|
<legend class="visually-hidden">Color theme</legend>
|
|
38
|
-
<label>
|
|
38
|
+
<label title="Use system appearance">
|
|
39
39
|
<input type="radio" name="color-theme" value="system" checked />
|
|
40
|
-
<span
|
|
40
|
+
<span class="theme-icon theme-icon-system" aria-hidden="true"></span>
|
|
41
|
+
<span class="visually-hidden">System</span>
|
|
41
42
|
</label>
|
|
42
|
-
<label>
|
|
43
|
+
<label title="Use light appearance">
|
|
43
44
|
<input type="radio" name="color-theme" value="light" />
|
|
44
|
-
<span
|
|
45
|
+
<span class="theme-icon theme-icon-light" aria-hidden="true"></span>
|
|
46
|
+
<span class="visually-hidden">Light</span>
|
|
45
47
|
</label>
|
|
46
|
-
<label>
|
|
48
|
+
<label title="Use dark appearance">
|
|
47
49
|
<input type="radio" name="color-theme" value="dark" />
|
|
48
|
-
<span
|
|
50
|
+
<span class="theme-icon theme-icon-dark" aria-hidden="true"></span>
|
|
51
|
+
<span class="visually-hidden">Dark</span>
|
|
49
52
|
</label>
|
|
50
53
|
</fieldset>
|
|
51
54
|
<span class="local-badge">Runs only on this computer</span>
|
package/src/ui/styles.css
CHANGED
|
@@ -201,9 +201,9 @@ a:focus-visible {
|
|
|
201
201
|
min-width: 0;
|
|
202
202
|
margin: 0;
|
|
203
203
|
padding: 0.1875rem;
|
|
204
|
-
border:
|
|
205
|
-
border-radius:
|
|
206
|
-
background: var(--
|
|
204
|
+
border: 0;
|
|
205
|
+
border-radius: 999px;
|
|
206
|
+
background: color-mix(in srgb, var(--text) 88%, var(--border));
|
|
207
207
|
}
|
|
208
208
|
|
|
209
209
|
.theme-picker label {
|
|
@@ -217,29 +217,50 @@ a:focus-visible {
|
|
|
217
217
|
pointer-events: none;
|
|
218
218
|
}
|
|
219
219
|
|
|
220
|
-
.theme-picker
|
|
221
|
-
|
|
220
|
+
.theme-picker .theme-icon {
|
|
221
|
+
width: 1.75rem;
|
|
222
|
+
min-height: 1.75rem;
|
|
222
223
|
display: inline-flex;
|
|
223
224
|
align-items: center;
|
|
224
225
|
justify-content: center;
|
|
225
|
-
padding: 0
|
|
226
|
-
border-radius:
|
|
227
|
-
color:
|
|
228
|
-
font-size: 0.6875rem;
|
|
229
|
-
font-weight: 700;
|
|
226
|
+
padding: 0;
|
|
227
|
+
border-radius: 999px;
|
|
228
|
+
color: rgb(255 255 255 / 72%);
|
|
230
229
|
transition:
|
|
231
230
|
color 160ms var(--ease),
|
|
232
231
|
background 160ms var(--ease),
|
|
233
232
|
box-shadow 160ms var(--ease);
|
|
234
233
|
}
|
|
235
234
|
|
|
236
|
-
.theme-picker
|
|
235
|
+
.theme-picker .theme-icon::before {
|
|
236
|
+
width: 1rem;
|
|
237
|
+
height: 1rem;
|
|
238
|
+
content: "";
|
|
239
|
+
display: block;
|
|
240
|
+
background: currentColor;
|
|
241
|
+
mask: center / contain no-repeat var(--theme-icon);
|
|
242
|
+
-webkit-mask: center / contain no-repeat var(--theme-icon);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
.theme-icon-system {
|
|
246
|
+
--theme-icon: url("/icons/monitor.svg");
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.theme-icon-light {
|
|
250
|
+
--theme-icon: url("/icons/sun.svg");
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.theme-icon-dark {
|
|
254
|
+
--theme-icon: url("/icons/moon.svg");
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
.theme-picker input:checked + .theme-icon {
|
|
237
258
|
color: var(--text);
|
|
238
259
|
background: var(--surface);
|
|
239
260
|
box-shadow: var(--shadow-sm);
|
|
240
261
|
}
|
|
241
262
|
|
|
242
|
-
.theme-picker input:focus-visible +
|
|
263
|
+
.theme-picker input:focus-visible + .theme-icon {
|
|
243
264
|
outline: 3px solid #f2a94a;
|
|
244
265
|
outline-offset: 2px;
|
|
245
266
|
}
|
|
@@ -1050,10 +1071,6 @@ footer p {
|
|
|
1050
1071
|
gap: 0.375rem;
|
|
1051
1072
|
}
|
|
1052
1073
|
|
|
1053
|
-
.theme-picker span {
|
|
1054
|
-
padding-inline: 0.5rem;
|
|
1055
|
-
}
|
|
1056
|
-
|
|
1057
1074
|
.shell {
|
|
1058
1075
|
width: min(100% - 1rem, 54rem);
|
|
1059
1076
|
padding-top: 2rem;
|
package/src/web/server.js
CHANGED
|
@@ -182,6 +182,9 @@ async function loadDefaultUiFiles() {
|
|
|
182
182
|
readFile(new URL("../ui/theme.js", import.meta.url), "utf8"),
|
|
183
183
|
readFile(new URL("../ui/time.js", import.meta.url), "utf8"),
|
|
184
184
|
readFile(new URL("../ui/styles.css", import.meta.url), "utf8"),
|
|
185
|
+
readFile(new URL("../ui/icons/monitor.svg", import.meta.url), "utf8"),
|
|
186
|
+
readFile(new URL("../ui/icons/sun.svg", import.meta.url), "utf8"),
|
|
187
|
+
readFile(new URL("../ui/icons/moon.svg", import.meta.url), "utf8"),
|
|
185
188
|
]);
|
|
186
189
|
|
|
187
190
|
return {
|
|
@@ -190,6 +193,9 @@ async function loadDefaultUiFiles() {
|
|
|
190
193
|
"/theme.js": files[2],
|
|
191
194
|
"/time.js": files[3],
|
|
192
195
|
"/styles.css": files[4],
|
|
196
|
+
"/icons/monitor.svg": files[5],
|
|
197
|
+
"/icons/sun.svg": files[6],
|
|
198
|
+
"/icons/moon.svg": files[7],
|
|
193
199
|
};
|
|
194
200
|
}
|
|
195
201
|
|
|
@@ -421,9 +427,11 @@ function createRequestHandler(state) {
|
|
|
421
427
|
const contentType =
|
|
422
428
|
path.endsWith(".js")
|
|
423
429
|
? "text/javascript; charset=utf-8"
|
|
424
|
-
: path
|
|
425
|
-
? "
|
|
426
|
-
: "
|
|
430
|
+
: path.endsWith(".svg")
|
|
431
|
+
? "image/svg+xml; charset=utf-8"
|
|
432
|
+
: path === "/styles.css"
|
|
433
|
+
? "text/css; charset=utf-8"
|
|
434
|
+
: "text/html; charset=utf-8";
|
|
427
435
|
const contents = state.uiFiles[path];
|
|
428
436
|
response.writeHead(200, {
|
|
429
437
|
"Content-Type": contentType,
|