nomadexapp 0.2.1 → 0.2.2
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/bin/nomadex.mjs +114 -19
- package/package.json +1 -1
package/bin/nomadex.mjs
CHANGED
|
@@ -162,6 +162,9 @@ const parseVersion = (value) =>
|
|
|
162
162
|
.split(".")
|
|
163
163
|
.map((part) => Number.parseInt(part.replace(/[^0-9].*$/u, ""), 10) || 0);
|
|
164
164
|
|
|
165
|
+
const isInteractivePromptAvailable = () =>
|
|
166
|
+
Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
167
|
+
|
|
165
168
|
const compareVersions = (left, right) => {
|
|
166
169
|
const maxLength = Math.max(left.length, right.length);
|
|
167
170
|
for (let index = 0; index < maxLength; index += 1) {
|
|
@@ -345,7 +348,7 @@ const renderLoginPage = ({ errorMessage = "", nextPath = "/threads" } = {}) => {
|
|
|
345
348
|
};
|
|
346
349
|
|
|
347
350
|
const promptForUpdate = async () => {
|
|
348
|
-
if (!options.updateCheck || !
|
|
351
|
+
if (!options.updateCheck || !isInteractivePromptAvailable()) {
|
|
349
352
|
return false;
|
|
350
353
|
}
|
|
351
354
|
|
|
@@ -424,16 +427,17 @@ const promptForUpdate = async () => {
|
|
|
424
427
|
};
|
|
425
428
|
|
|
426
429
|
const wsUrl = new URL(options.wsUrl);
|
|
427
|
-
const
|
|
428
|
-
const
|
|
429
|
-
|
|
430
|
-
|
|
430
|
+
const getWsHost = () => wsUrl.hostname;
|
|
431
|
+
const getWsPort = () =>
|
|
432
|
+
Number(wsUrl.port || (wsUrl.protocol === "wss:" ? 443 : 80));
|
|
433
|
+
const getReadyzUrl = () => {
|
|
434
|
+
const target = new URL(wsUrl);
|
|
431
435
|
target.protocol = target.protocol === "wss:" ? "https:" : "http:";
|
|
432
436
|
target.pathname = "/readyz";
|
|
433
437
|
target.search = "";
|
|
434
438
|
target.hash = "";
|
|
435
439
|
return target;
|
|
436
|
-
}
|
|
440
|
+
};
|
|
437
441
|
const authRelayTarget = options.authRelayTarget;
|
|
438
442
|
const isTermuxEnvironment = () => {
|
|
439
443
|
const prefix = process.env.PREFIX ?? "";
|
|
@@ -443,6 +447,22 @@ const isTermuxEnvironment = () => {
|
|
|
443
447
|
);
|
|
444
448
|
};
|
|
445
449
|
|
|
450
|
+
const isLocalHost = (host) =>
|
|
451
|
+
["127.0.0.1", "0.0.0.0", "localhost", "::1", "[::1]"].includes(
|
|
452
|
+
host.toLowerCase(),
|
|
453
|
+
);
|
|
454
|
+
|
|
455
|
+
const getPortProbeHost = (host) => {
|
|
456
|
+
const normalized = host.toLowerCase();
|
|
457
|
+
if (normalized === "localhost" || normalized === "0.0.0.0") {
|
|
458
|
+
return "127.0.0.1";
|
|
459
|
+
}
|
|
460
|
+
if (normalized === "[::1]") {
|
|
461
|
+
return "::1";
|
|
462
|
+
}
|
|
463
|
+
return host;
|
|
464
|
+
};
|
|
465
|
+
|
|
446
466
|
const isTransientNpxCodexPath = (candidate) => {
|
|
447
467
|
const normalized = candidate.replaceAll("\\", "/");
|
|
448
468
|
return (
|
|
@@ -618,9 +638,37 @@ const isPortOpen = (targetHost, targetPort) =>
|
|
|
618
638
|
socket.connect(targetPort, targetHost);
|
|
619
639
|
});
|
|
620
640
|
|
|
641
|
+
const findNextFreePort = async (targetHost, startPort, attempts = 20) => {
|
|
642
|
+
for (let port = startPort + 1; port < startPort + 1 + attempts; port += 1) {
|
|
643
|
+
if (!(await isPortOpen(targetHost, port))) {
|
|
644
|
+
return port;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
return null;
|
|
648
|
+
};
|
|
649
|
+
|
|
650
|
+
const confirmAlternatePort = async (message) => {
|
|
651
|
+
if (!isInteractivePromptAvailable()) {
|
|
652
|
+
return true;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
const rl = createInterface({
|
|
656
|
+
input: process.stdin,
|
|
657
|
+
output: process.stdout,
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
try {
|
|
661
|
+
const answer = await rl.question(`${message} [Y/n] `);
|
|
662
|
+
const normalized = answer.trim().toLowerCase();
|
|
663
|
+
return normalized === "" || !["n", "no"].includes(normalized);
|
|
664
|
+
} finally {
|
|
665
|
+
rl.close();
|
|
666
|
+
}
|
|
667
|
+
};
|
|
668
|
+
|
|
621
669
|
const isCodexAppServerReady = async () => {
|
|
622
670
|
try {
|
|
623
|
-
const response = await fetch(
|
|
671
|
+
const response = await fetch(getReadyzUrl(), {
|
|
624
672
|
signal: AbortSignal.timeout(500),
|
|
625
673
|
});
|
|
626
674
|
return response.ok;
|
|
@@ -634,9 +682,26 @@ const ensureUiPortAvailable = async () => {
|
|
|
634
682
|
return;
|
|
635
683
|
}
|
|
636
684
|
|
|
637
|
-
|
|
638
|
-
|
|
685
|
+
const nextPort = await findNextFreePort("127.0.0.1", options.uiPort);
|
|
686
|
+
if (nextPort === null) {
|
|
687
|
+
throw new Error(
|
|
688
|
+
`UI port ${options.uiPort} is already in use and no nearby free port was found. Open the existing UI at http://127.0.0.1:${options.uiPort} or choose another port with --port.`,
|
|
689
|
+
);
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
const useAlternatePort = await confirmAlternatePort(
|
|
693
|
+
`[nomadexapp] UI port ${options.uiPort} is already in use. Use ${nextPort} instead?`,
|
|
694
|
+
);
|
|
695
|
+
if (!useAlternatePort) {
|
|
696
|
+
throw new Error(
|
|
697
|
+
`UI port ${options.uiPort} is already in use. Open the existing UI at http://127.0.0.1:${options.uiPort} or choose another port with --port.`,
|
|
698
|
+
);
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
console.log(
|
|
702
|
+
`[nomadexapp] UI port ${options.uiPort} is busy. Using ${nextPort} instead.`,
|
|
639
703
|
);
|
|
704
|
+
options.uiPort = nextPort;
|
|
640
705
|
};
|
|
641
706
|
|
|
642
707
|
const formatSpawnError = (error) => {
|
|
@@ -685,22 +750,50 @@ const ensureDistBuilt = () => {
|
|
|
685
750
|
};
|
|
686
751
|
|
|
687
752
|
const ensureAppServer = async () => {
|
|
688
|
-
|
|
753
|
+
const wsHost = getWsHost();
|
|
754
|
+
const wsPort = getWsPort();
|
|
755
|
+
const probeHost = getPortProbeHost(wsHost);
|
|
756
|
+
|
|
757
|
+
if (await isPortOpen(probeHost, wsPort)) {
|
|
689
758
|
if (await isCodexAppServerReady()) {
|
|
690
|
-
console.log(`[nomadexapp] Reusing Codex app-server at ${
|
|
759
|
+
console.log(`[nomadexapp] Reusing Codex app-server at ${wsUrl}`);
|
|
691
760
|
return;
|
|
692
761
|
}
|
|
693
762
|
|
|
694
|
-
|
|
695
|
-
|
|
763
|
+
if (!isLocalHost(wsHost)) {
|
|
764
|
+
throw new Error(
|
|
765
|
+
`Port ${wsPort} on ${wsHost} is already in use, but it is not responding like a Codex app-server.`,
|
|
766
|
+
);
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
const nextPort = await findNextFreePort(probeHost, wsPort);
|
|
770
|
+
if (nextPort === null) {
|
|
771
|
+
throw new Error(
|
|
772
|
+
`Port ${wsPort} on ${wsHost} is already in use, and no nearby free local app-server port was found.`,
|
|
773
|
+
);
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
const useAlternatePort = await confirmAlternatePort(
|
|
777
|
+
`[nomadexapp] App-server port ${wsPort} is in use by another process. Use ${nextPort} instead?`,
|
|
778
|
+
);
|
|
779
|
+
if (!useAlternatePort) {
|
|
780
|
+
throw new Error(
|
|
781
|
+
`Port ${wsPort} on ${wsHost} is already in use, but it is not responding like a Codex app-server.`,
|
|
782
|
+
);
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
wsUrl.port = String(nextPort);
|
|
786
|
+
options.wsUrl = wsUrl.toString();
|
|
787
|
+
console.log(
|
|
788
|
+
`[nomadexapp] App-server port ${wsPort} is busy. Using ${wsUrl} instead.`,
|
|
696
789
|
);
|
|
697
790
|
}
|
|
698
791
|
|
|
699
792
|
const codexLaunch = getCodexLaunch();
|
|
700
|
-
console.log(`[nomadexapp] Starting Codex app-server at ${
|
|
793
|
+
console.log(`[nomadexapp] Starting Codex app-server at ${wsUrl}`);
|
|
701
794
|
const appServer = spawn(
|
|
702
795
|
codexLaunch.command,
|
|
703
|
-
[...codexLaunch.args, "app-server", "--listen",
|
|
796
|
+
[...codexLaunch.args, "app-server", "--listen", wsUrl.toString()],
|
|
704
797
|
{
|
|
705
798
|
cwd: launchCwd,
|
|
706
799
|
stdio: "inherit",
|
|
@@ -725,7 +818,8 @@ const ensureAppServer = async () => {
|
|
|
725
818
|
}
|
|
726
819
|
if (appServer.exitCode !== null) {
|
|
727
820
|
const termuxHint =
|
|
728
|
-
isTermuxEnvironment() &&
|
|
821
|
+
isTermuxEnvironment() &&
|
|
822
|
+
!["PATH codex", "Termux codex"].includes(codexLaunch.source)
|
|
729
823
|
? " Termux detected. Install `@mmmbuto/codex-cli-termux@latest`, ensure `codex` is on PATH, or launch Nomadex with `--codex-cmd codex`."
|
|
730
824
|
: "";
|
|
731
825
|
throw new Error(
|
|
@@ -735,7 +829,7 @@ const ensureAppServer = async () => {
|
|
|
735
829
|
await sleep(200);
|
|
736
830
|
}
|
|
737
831
|
|
|
738
|
-
throw new Error(`Timed out waiting for Codex app-server at ${
|
|
832
|
+
throw new Error(`Timed out waiting for Codex app-server at ${wsUrl}`);
|
|
739
833
|
};
|
|
740
834
|
|
|
741
835
|
const sendText = (res, statusCode, message) => {
|
|
@@ -903,7 +997,6 @@ const getPreferredIp = () => {
|
|
|
903
997
|
};
|
|
904
998
|
|
|
905
999
|
const wsProxy = httpProxy.createProxyServer({
|
|
906
|
-
target: options.wsUrl,
|
|
907
1000
|
ws: true,
|
|
908
1001
|
changeOrigin: true,
|
|
909
1002
|
});
|
|
@@ -1129,7 +1222,9 @@ server.on("upgrade", (req, socket, head) => {
|
|
|
1129
1222
|
return;
|
|
1130
1223
|
}
|
|
1131
1224
|
req.url = "/";
|
|
1132
|
-
wsProxy.ws(req, socket, head
|
|
1225
|
+
wsProxy.ws(req, socket, head, {
|
|
1226
|
+
target: wsUrl.toString(),
|
|
1227
|
+
});
|
|
1133
1228
|
return;
|
|
1134
1229
|
}
|
|
1135
1230
|
} catch {
|