remote-codex 0.1.2 → 0.1.3

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/README.md CHANGED
@@ -43,8 +43,8 @@ remote-codex stop
43
43
 
44
44
  The global CLI starts the production API and web service:
45
45
 
46
- - API: `http://127.0.0.1:8787`
47
- - Web: `http://127.0.0.1:4173`
46
+ - API: `http://127.0.0.1:45674`
47
+ - Web: `http://127.0.0.1:45673`
48
48
  - service logs/state: `~/.remote-codex/service/`
49
49
  - production database: `~/.remote-codex/supervisor.sqlite`
50
50
 
@@ -9,6 +9,11 @@ const binDir = path.dirname(fileURLToPath(import.meta.url));
9
9
  const packageRoot = path.resolve(binDir, '..');
10
10
  const packageJsonPath = path.join(packageRoot, 'package.json');
11
11
  const serviceManagerPath = path.join(packageRoot, 'scripts', 'service-manager.mjs');
12
+ const sourceCheckout =
13
+ fs.existsSync(path.join(packageRoot, 'pnpm-workspace.yaml')) &&
14
+ fs.existsSync(path.join(packageRoot, 'scripts', 'service-restart.mjs'));
15
+ const defaultServicePort = sourceCheckout ? 4173 : 45673;
16
+ const defaultApiPort = sourceCheckout ? 8787 : 45674;
12
17
 
13
18
  const aliases = new Map([
14
19
  ['service:start', 'start'],
@@ -84,9 +89,9 @@ Usage:
84
89
 
85
90
  Environment:
86
91
  SERVICE_HOST Web listen host, default 127.0.0.1
87
- SERVICE_PORT Web listen port, default 4173
92
+ SERVICE_PORT Web listen port, default ${defaultServicePort}
88
93
  SERVICE_API_HOST API listen host, default 127.0.0.1
89
- SERVICE_API_PORT API listen port, default 8787
94
+ SERVICE_API_PORT API listen port, default ${defaultApiPort}
90
95
  REMOTE_CODEX_SERVICE_DIR Service state and log directory, default ~/.remote-codex/service
91
96
  `);
92
97
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "remote-codex",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Local web supervisor for Codex workspaces and threads.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -7,11 +7,16 @@ import { fileURLToPath } from 'node:url';
7
7
 
8
8
  const scriptDir = path.dirname(fileURLToPath(import.meta.url));
9
9
  const repoRoot = path.resolve(scriptDir, '..');
10
+ const sourceCheckout =
11
+ fs.existsSync(path.join(repoRoot, 'pnpm-workspace.yaml')) &&
12
+ fs.existsSync(path.join(repoRoot, 'scripts', 'service-restart.mjs'));
13
+ const defaultServicePort = sourceCheckout ? 4173 : 45673;
14
+ const defaultApiPort = sourceCheckout ? 8787 : 45674;
10
15
 
11
16
  const serviceHost = process.env.SERVICE_HOST ?? '127.0.0.1';
12
- const servicePort = parsePort(process.env.SERVICE_PORT, 4173);
17
+ const servicePort = parsePort(process.env.SERVICE_PORT, defaultServicePort);
13
18
  const apiHost = process.env.SERVICE_API_HOST ?? '127.0.0.1';
14
- const apiPort = parsePort(process.env.SERVICE_API_PORT, 8787);
19
+ const apiPort = parsePort(process.env.SERVICE_API_PORT, defaultApiPort);
15
20
  const distDir = path.resolve(
16
21
  process.env.SERVICE_WEB_DIST_DIR ?? path.join(repoRoot, 'apps/supervisor-web/dist')
17
22
  );
@@ -4,6 +4,7 @@ import os from 'node:os';
4
4
  import path from 'node:path';
5
5
  import { spawn } from 'node:child_process';
6
6
  import { fileURLToPath } from 'node:url';
7
+ import net from 'node:net';
7
8
 
8
9
  const scriptDir = path.dirname(fileURLToPath(import.meta.url));
9
10
  const repoRoot = path.resolve(scriptDir, '..');
@@ -17,11 +18,13 @@ const webIndex = path.join(repoRoot, 'apps', 'supervisor-web', 'dist', 'index.ht
17
18
  const supportsSourceRestart =
18
19
  fs.existsSync(path.join(repoRoot, 'pnpm-workspace.yaml')) &&
19
20
  fs.existsSync(path.join(repoRoot, 'scripts', 'service-restart.mjs'));
21
+ const defaultServicePort = supportsSourceRestart ? 4173 : 45673;
22
+ const defaultApiPort = supportsSourceRestart ? 8787 : 45674;
20
23
 
21
24
  const serviceHost = process.env.SERVICE_HOST ?? '127.0.0.1';
22
- const servicePort = parsePort(process.env.SERVICE_PORT, 4173);
25
+ const servicePort = parsePort(process.env.SERVICE_PORT, defaultServicePort);
23
26
  const apiHost = process.env.SERVICE_API_HOST ?? '127.0.0.1';
24
- const apiPort = parsePort(process.env.SERVICE_API_PORT, 8787);
27
+ const apiPort = parsePort(process.env.SERVICE_API_PORT, defaultApiPort);
25
28
 
26
29
  const command = process.argv[2];
27
30
 
@@ -58,6 +61,10 @@ async function startService() {
58
61
  const webLogPath = path.join(serviceDir, 'web.log');
59
62
  prepareLogFile(apiLogPath);
60
63
  prepareLogFile(webLogPath);
64
+
65
+ await assertTcpPortAvailable(apiHost, apiPort, 'API');
66
+ await assertTcpPortAvailable(serviceHost, servicePort, 'Web');
67
+
61
68
  const apiPid = spawnDetached(process.execPath, [apiEntry], apiLogPath, {
62
69
  NODE_ENV: 'production',
63
70
  HOST: apiHost,
@@ -73,7 +80,7 @@ async function startService() {
73
80
  await waitForHttp(`http://${apiHost}:${apiPort}/healthz`, apiPid, 15_000);
74
81
  } catch (error) {
75
82
  stopPid(apiPid);
76
- throw error;
83
+ throw appendLogTail(error, apiLogPath, 'API');
77
84
  }
78
85
 
79
86
  const webPid = spawnDetached(process.execPath, [webEntry], webLogPath, {
@@ -89,7 +96,7 @@ async function startService() {
89
96
  } catch (error) {
90
97
  stopPid(webPid);
91
98
  stopPid(apiPid);
92
- throw error;
99
+ throw appendLogTail(error, webLogPath, 'Web');
93
100
  }
94
101
 
95
102
  const state = {
@@ -214,6 +221,54 @@ async function waitForHttp(url, pid, timeoutMs) {
214
221
  throw new Error(`Timed out waiting for ${url}.`);
215
222
  }
216
223
 
224
+ async function assertTcpPortAvailable(host, port, label) {
225
+ await new Promise((resolve, reject) => {
226
+ const server = net.createServer();
227
+ server.unref();
228
+
229
+ server.once('error', (error) => {
230
+ const code = typeof error === 'object' && error !== null ? error.code : undefined;
231
+ if (code === 'EADDRINUSE') {
232
+ reject(
233
+ new Error(
234
+ `${label} port ${host}:${port} is already in use. Set ${label === 'API' ? 'SERVICE_API_PORT' : 'SERVICE_PORT'} to another port, or stop the process currently using it.`
235
+ )
236
+ );
237
+ return;
238
+ }
239
+
240
+ reject(error);
241
+ });
242
+
243
+ server.listen(port, host, () => {
244
+ server.close(resolve);
245
+ });
246
+ });
247
+ }
248
+
249
+ function appendLogTail(error, logPath, label) {
250
+ const message = error instanceof Error ? error.message : String(error);
251
+ const tail = readLogTail(logPath, 80);
252
+ if (!tail) {
253
+ return new Error(`${message}\n${label} log: ${logPath}`);
254
+ }
255
+
256
+ return new Error(`${message}\n${label} log: ${logPath}\n\nLast ${label} log lines:\n${tail}`);
257
+ }
258
+
259
+ function readLogTail(logPath, maxLines) {
260
+ try {
261
+ const content = fs.readFileSync(logPath, 'utf8').trimEnd();
262
+ if (!content) {
263
+ return '';
264
+ }
265
+
266
+ return content.split(/\r?\n/).slice(-maxLines).join('\n');
267
+ } catch {
268
+ return '';
269
+ }
270
+ }
271
+
217
272
  async function probeHttp(url) {
218
273
  try {
219
274
  const controller = new AbortController();