remote-codex 0.11.2 → 0.11.4

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.
Files changed (37) hide show
  1. package/README.md +4 -0
  2. package/apps/relay-server/dist/index.d.ts +2 -0
  3. package/apps/relay-server/dist/index.js +1254 -0
  4. package/apps/supervisor-api/dist/chunk-ZWZQVPDT.js +27893 -0
  5. package/apps/supervisor-api/dist/index.js +4 -25183
  6. package/apps/supervisor-api/dist/worker-index.d.ts +2 -0
  7. package/apps/supervisor-api/dist/worker-index.js +197 -0
  8. package/apps/supervisor-web/dist/assets/index-CbdWtyx0.js +5 -0
  9. package/apps/supervisor-web/dist/assets/index-Di1JBevU.css +1 -0
  10. package/apps/supervisor-web/dist/assets/thread-ui-ICfwCbte.js +3604 -0
  11. package/apps/supervisor-web/dist/assets/ui-vendor-D1uxdi-d.js +430 -0
  12. package/apps/supervisor-web/dist/index.html +6 -7
  13. package/bin/remote-codex.mjs +593 -21
  14. package/package.json +42 -2
  15. package/packages/agent-runtime/src/types.ts +2 -1
  16. package/packages/codex/src/appServerManager.ts +1 -0
  17. package/packages/codex/src/historyItems.test.ts +45 -0
  18. package/packages/codex/src/historyItems.ts +22 -0
  19. package/packages/codex/src/runtimeAdapter.ts +6 -0
  20. package/packages/codex/src/types.ts +2 -1
  21. package/packages/db/migrations/0018_control_plane.sql +129 -0
  22. package/packages/db/migrations/0019_control_plane_projects.sql +19 -0
  23. package/packages/db/migrations/0020_control_workspace_status.sql +1 -0
  24. package/packages/db/migrations/0021_control_sandbox_lifecycle_fields.sql +3 -0
  25. package/packages/db/migrations/0022_control_sandbox_resource_profile.sql +1 -0
  26. package/packages/db/migrations/0023_control_usage_import_state.sql +18 -0
  27. package/packages/db/migrations/0024_control_auth.sql +23 -0
  28. package/packages/db/migrations/0025_control_harness_credentials.sql +29 -0
  29. package/packages/db/migrations/0026_control_harness_usage_events.sql +27 -0
  30. package/packages/db/src/schema.ts +305 -1
  31. package/packages/shared/src/index.ts +186 -0
  32. package/packages/shared/src/tokens.ts +137 -0
  33. package/apps/supervisor-web/dist/assets/index-CbDzXN9T.css +0 -1
  34. package/apps/supervisor-web/dist/assets/index-DQpHiQXN.js +0 -4
  35. package/apps/supervisor-web/dist/assets/thread-ui-BEieA99i.css +0 -1
  36. package/apps/supervisor-web/dist/assets/thread-ui-CDk3ExRH.js +0 -3516
  37. package/apps/supervisor-web/dist/assets/ui-vendor-CgOZX1B8.js +0 -425
@@ -4,17 +4,16 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>Remote Codex Supervisor</title>
7
- <script type="module" crossorigin src="/assets/index-DQpHiQXN.js"></script>
7
+ <script type="module" crossorigin src="/assets/index-CbdWtyx0.js"></script>
8
8
  <link rel="modulepreload" crossorigin href="/assets/react-vendor-o1Xrx7m4.js">
9
- <link rel="modulepreload" crossorigin href="/assets/ui-vendor-CgOZX1B8.js">
10
- <link rel="modulepreload" crossorigin href="/assets/terminal-vendor-CLGgN91S.js">
9
+ <link rel="modulepreload" crossorigin href="/assets/ui-vendor-D1uxdi-d.js">
11
10
  <link rel="modulepreload" crossorigin href="/assets/graph-vendor-CGzY-MFv.js">
11
+ <link rel="modulepreload" crossorigin href="/assets/terminal-vendor-CLGgN91S.js">
12
12
  <link rel="modulepreload" crossorigin href="/assets/markdown-vendor-hBDTCSB-.js">
13
- <link rel="modulepreload" crossorigin href="/assets/thread-ui-CDk3ExRH.js">
14
- <link rel="stylesheet" crossorigin href="/assets/terminal-vendor-Beg8tuEN.css">
13
+ <link rel="modulepreload" crossorigin href="/assets/thread-ui-ICfwCbte.js">
15
14
  <link rel="stylesheet" crossorigin href="/assets/graph-vendor-C5ap-Sga.css">
16
- <link rel="stylesheet" crossorigin href="/assets/thread-ui-BEieA99i.css">
17
- <link rel="stylesheet" crossorigin href="/assets/index-CbDzXN9T.css">
15
+ <link rel="stylesheet" crossorigin href="/assets/terminal-vendor-Beg8tuEN.css">
16
+ <link rel="stylesheet" crossorigin href="/assets/index-Di1JBevU.css">
18
17
  </head>
19
18
  <body class="bg-stone-950">
20
19
  <div id="root"></div>
@@ -9,6 +9,10 @@ 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 relayDistEntry = path.join(packageRoot, 'apps', 'relay-server', 'dist', 'index.js');
13
+ const relaySourceEntry = path.join(packageRoot, 'apps', 'relay-server', 'src', 'index.ts');
14
+ const supervisorDistEntry = path.join(packageRoot, 'apps', 'supervisor-api', 'dist', 'index.js');
15
+ const supervisorSourceEntry = path.join(packageRoot, 'apps', 'supervisor-api', 'src', 'index.ts');
12
16
  const sourceCheckout =
13
17
  fs.existsSync(path.join(packageRoot, 'pnpm-workspace.yaml')) &&
14
18
  fs.existsSync(path.join(packageRoot, 'scripts', 'service-restart.mjs'));
@@ -21,10 +25,20 @@ const aliases = new Map([
21
25
  ['service:status', 'status'],
22
26
  ]);
23
27
 
28
+ if (fs.existsSync(path.join(packageRoot, '.env'))) {
29
+ process.loadEnvFile?.(path.join(packageRoot, '.env'));
30
+ }
31
+
24
32
  const command = normalizeCommand(process.argv[2]);
33
+ const commandHelpTarget =
34
+ command === 'help'
35
+ ? normalizeHelpTarget(process.argv[3])
36
+ : hasHelpFlag(process.argv.slice(3))
37
+ ? command
38
+ : null;
25
39
 
26
- if (command === 'help') {
27
- printHelp();
40
+ if (commandHelpTarget) {
41
+ printCommandHelp(commandHelpTarget);
28
42
  process.exit(0);
29
43
  }
30
44
 
@@ -33,30 +47,304 @@ if (command === 'version') {
33
47
  process.exit(0);
34
48
  }
35
49
 
36
- if (!['start', 'stop', 'status'].includes(command)) {
50
+ if (command === 'relay') {
51
+ runRelayServer();
52
+ } else if (command === 'relay-supervisor') {
53
+ runRelaySupervisor();
54
+ } else if (!['start', 'stop', 'status'].includes(command)) {
37
55
  printHelp();
38
56
  process.exit(command ? 1 : 0);
57
+ } else {
58
+ const child = spawn(process.execPath, [serviceManagerPath, command], {
59
+ cwd: packageRoot,
60
+ env: process.env,
61
+ stdio: 'inherit',
62
+ });
63
+
64
+ child.on('exit', (code, signal) => {
65
+ if (signal) {
66
+ process.kill(process.pid, signal);
67
+ return;
68
+ }
69
+
70
+ process.exit(code ?? 1);
71
+ });
72
+
73
+ child.on('error', (error) => {
74
+ console.error(`Failed to run remote-codex ${command}: ${error.message}`);
75
+ process.exit(1);
76
+ });
77
+ }
78
+
79
+ function runRelayServer() {
80
+ const guidance = {
81
+ commandName: 'remote-codex relay',
82
+ description: 'Run the public relay server that browsers/apps connect to.',
83
+ required: [
84
+ ['REMOTE_CODEX_ADMIN_USERNAME', 'Initial relay admin username, for example admin.'],
85
+ ['REMOTE_CODEX_ADMIN_PASSWORD', 'Initial relay admin password, at least 8 characters.'],
86
+ ],
87
+ recommended: [
88
+ ['REMOTE_CODEX_RELAY_SESSION_SECRET', 'Relay session signing secret, at least 16 characters. Defaults to the admin password if omitted.'],
89
+ ['REMOTE_CODEX_RELAY_DATA_DIR', 'Persistent relay user/device/share store. Defaults to .local/relay-server.'],
90
+ ['REMOTE_CODEX_RELAY_REGISTRATION_ENABLED', 'true/false. Use false when only admins should create users.'],
91
+ ['HOST', 'Relay listen host. Use 0.0.0.0 on a public server. Default 0.0.0.0.'],
92
+ ['PORT', 'Relay listen port. Default 8788.'],
93
+ ],
94
+ example: [
95
+ 'REMOTE_CODEX_ADMIN_USERNAME=admin \\',
96
+ 'REMOTE_CODEX_ADMIN_PASSWORD=change-me-now \\',
97
+ 'REMOTE_CODEX_RELAY_SESSION_SECRET=at-least-16-characters \\',
98
+ 'REMOTE_CODEX_RELAY_DATA_DIR=/var/lib/remote-codex-relay \\',
99
+ 'REMOTE_CODEX_RELAY_REGISTRATION_ENABLED=false \\',
100
+ 'HOST=0.0.0.0 PORT=8788 \\',
101
+ 'remote-codex relay',
102
+ ],
103
+ validate: () => {
104
+ const issues = [];
105
+ if (process.env.REMOTE_CODEX_ADMIN_PASSWORD && process.env.REMOTE_CODEX_ADMIN_PASSWORD.length < 8) {
106
+ issues.push('REMOTE_CODEX_ADMIN_PASSWORD must be at least 8 characters.');
107
+ }
108
+ if (
109
+ process.env.REMOTE_CODEX_RELAY_SESSION_SECRET &&
110
+ process.env.REMOTE_CODEX_RELAY_SESSION_SECRET.length < 16
111
+ ) {
112
+ issues.push('REMOTE_CODEX_RELAY_SESSION_SECRET must be at least 16 characters.');
113
+ }
114
+ return issues;
115
+ },
116
+ };
117
+ validateRequiredEnv(guidance);
118
+ printEnvSummary(guidance);
119
+
120
+ const relayEntry = fs.existsSync(relayDistEntry) ? relayDistEntry : relaySourceEntry;
121
+ let commandToRun = process.execPath;
122
+ let args = [relayEntry];
123
+
124
+ if (!fs.existsSync(relayEntry)) {
125
+ console.error('Relay server build artifacts are missing. Run `pnpm build` before using `remote-codex relay`.');
126
+ console.error(`Missing: ${path.relative(packageRoot, relayDistEntry)}`);
127
+ process.exit(1);
128
+ }
129
+
130
+ if (relayEntry === relaySourceEntry) {
131
+ const tsxEntry = path.join(packageRoot, 'node_modules', 'tsx', 'dist', 'cli.mjs');
132
+ if (!fs.existsSync(tsxEntry)) {
133
+ console.error('Relay server build artifacts are missing and tsx is not installed for source execution.');
134
+ console.error('Run `pnpm build` or install dependencies with `pnpm install`.');
135
+ process.exit(1);
136
+ }
137
+ args = [tsxEntry, relaySourceEntry];
138
+ }
139
+
140
+ const relay = spawn(commandToRun, args, {
141
+ cwd: packageRoot,
142
+ env: relayServerEnv(),
143
+ stdio: 'inherit',
144
+ });
145
+
146
+ relay.on('exit', (code, signal) => {
147
+ if (signal) {
148
+ process.kill(process.pid, signal);
149
+ return;
150
+ }
151
+
152
+ if (code && code !== 0) {
153
+ console.error(`remote-codex relay exited with code ${code}. Check the relay port, HOST/PORT, and environment values above.`);
154
+ }
155
+ process.exit(code ?? 1);
156
+ });
157
+
158
+ relay.on('error', (error) => {
159
+ console.error(`Failed to run remote-codex relay: ${error.message}`);
160
+ process.exit(1);
161
+ });
39
162
  }
40
163
 
41
- const child = spawn(process.execPath, [serviceManagerPath, command], {
42
- cwd: packageRoot,
43
- env: process.env,
44
- stdio: 'inherit',
45
- });
164
+ function runRelaySupervisor() {
165
+ const guidance = {
166
+ commandName: 'remote-codex relay-supervisor',
167
+ description: 'Run the private-machine supervisor backend that connects outward to a public relay.',
168
+ required: [
169
+ ['REMOTE_CODEX_ADMIN_USERNAME', 'Private supervisor admin username. Required because relay mode enables local API auth.'],
170
+ ['REMOTE_CODEX_ADMIN_PASSWORD', 'Private supervisor admin password. Required because relay mode enables local API auth.'],
171
+ ['REMOTE_CODEX_SESSION_SECRET', 'Private supervisor session signing secret, at least 16 characters.'],
172
+ ['REMOTE_CODEX_RELAY_SERVER_URL', 'Public relay websocket base URL, for example ws://host:8788 or wss://relay.example.com.'],
173
+ ['REMOTE_CODEX_RELAY_AGENT_TOKEN', 'Device token created in the relay portal. This is not the relay admin password.'],
174
+ ],
175
+ recommended: [
176
+ ['HOST', 'Private supervisor listen host. Default 127.0.0.1.'],
177
+ ['PORT', 'Private supervisor listen port. Default 8787.'],
178
+ ['DATABASE_URL', 'SQLite database path. Set this when running a separate backend beside another Remote Codex.'],
179
+ ['WORKSPACE_ROOT', 'Root directory that workspace paths must live under. Default is your home directory.'],
180
+ ['CODEX_HOME', 'Codex config directory. Default ~/.codex.'],
181
+ ],
182
+ example: [
183
+ 'REMOTE_CODEX_ADMIN_USERNAME=admin \\',
184
+ 'REMOTE_CODEX_ADMIN_PASSWORD=change-me-locally \\',
185
+ 'REMOTE_CODEX_SESSION_SECRET=at-least-16-characters \\',
186
+ 'REMOTE_CODEX_RELAY_SERVER_URL=wss://relay.example.com \\',
187
+ 'REMOTE_CODEX_RELAY_AGENT_TOKEN=rcd_device_token_from_relay_portal \\',
188
+ 'HOST=127.0.0.1 PORT=8787 \\',
189
+ 'remote-codex relay-supervisor',
190
+ ],
191
+ validate: () => {
192
+ const issues = [];
193
+ if (process.env.REMOTE_CODEX_ADMIN_PASSWORD && process.env.REMOTE_CODEX_ADMIN_PASSWORD.length < 1) {
194
+ issues.push('REMOTE_CODEX_ADMIN_PASSWORD must not be empty.');
195
+ }
196
+ if (process.env.REMOTE_CODEX_SESSION_SECRET && process.env.REMOTE_CODEX_SESSION_SECRET.length < 16) {
197
+ issues.push('REMOTE_CODEX_SESSION_SECRET must be at least 16 characters.');
198
+ }
199
+ const relayUrl = process.env.REMOTE_CODEX_RELAY_SERVER_URL;
200
+ if (relayUrl && !relayUrl.startsWith('ws://') && !relayUrl.startsWith('wss://')) {
201
+ issues.push('REMOTE_CODEX_RELAY_SERVER_URL must start with ws:// or wss://.');
202
+ }
203
+ return issues;
204
+ },
205
+ };
206
+ validateRequiredEnv(guidance);
207
+ printEnvSummary(guidance);
208
+
209
+ const supervisorEntry = fs.existsSync(supervisorDistEntry)
210
+ ? supervisorDistEntry
211
+ : supervisorSourceEntry;
212
+ let commandToRun = process.execPath;
213
+ let args = [supervisorEntry];
214
+
215
+ if (!fs.existsSync(supervisorEntry)) {
216
+ console.error('Supervisor API build artifacts are missing. Run `pnpm build` before using `remote-codex relay-supervisor`.');
217
+ console.error(`Missing: ${path.relative(packageRoot, supervisorDistEntry)}`);
218
+ process.exit(1);
219
+ }
220
+
221
+ if (supervisorEntry === supervisorSourceEntry) {
222
+ const tsxEntry = path.join(packageRoot, 'node_modules', 'tsx', 'dist', 'cli.mjs');
223
+ if (!fs.existsSync(tsxEntry)) {
224
+ console.error('Supervisor API build artifacts are missing and tsx is not installed for source execution.');
225
+ console.error('Run `pnpm build` or install dependencies with `pnpm install`.');
226
+ process.exit(1);
227
+ }
228
+ args = [tsxEntry, supervisorSourceEntry];
229
+ }
230
+
231
+ const supervisor = spawn(commandToRun, args, {
232
+ cwd: packageRoot,
233
+ env: {
234
+ ...process.env,
235
+ REMOTE_CODEX_MODE: 'relay',
236
+ REMOTE_CODEX_PACKAGE_ROOT: process.env.REMOTE_CODEX_PACKAGE_ROOT ?? packageRoot,
237
+ REMOTE_CODEX_DISABLE_BUILD_RESTART:
238
+ process.env.REMOTE_CODEX_DISABLE_BUILD_RESTART ?? (sourceCheckout ? 'false' : 'true'),
239
+ },
240
+ stdio: 'inherit',
241
+ });
46
242
 
47
- child.on('exit', (code, signal) => {
48
- if (signal) {
49
- process.kill(process.pid, signal);
243
+ supervisor.on('exit', (code, signal) => {
244
+ if (signal) {
245
+ process.kill(process.pid, signal);
246
+ return;
247
+ }
248
+
249
+ if (code && code !== 0) {
250
+ console.error(`remote-codex relay-supervisor exited with code ${code}. Check the relay server URL, device token, local PORT, and environment values above.`);
251
+ }
252
+ process.exit(code ?? 1);
253
+ });
254
+
255
+ supervisor.on('error', (error) => {
256
+ console.error(`Failed to run remote-codex relay-supervisor: ${error.message}`);
257
+ process.exit(1);
258
+ });
259
+ }
260
+
261
+ function validateRequiredEnv(input) {
262
+ const missing = input.required
263
+ .map(([name]) => name)
264
+ .filter((name) => !nonEmptyEnv(name));
265
+ const issues = input.validate?.() ?? [];
266
+
267
+ if (missing.length === 0 && issues.length === 0) {
50
268
  return;
51
269
  }
52
270
 
53
- process.exit(code ?? 1);
54
- });
271
+ console.error(`${input.commandName} cannot start because its configuration is incomplete.`);
272
+ console.error('');
273
+ console.error(input.description);
274
+ console.error('');
275
+ console.error('Required:');
276
+ for (const [name, description] of input.required) {
277
+ const marker = missing.includes(name) ? 'missing' : 'set';
278
+ console.error(` ${name} (${marker})`);
279
+ console.error(` ${description}`);
280
+ }
281
+
282
+ if (input.recommended.length > 0) {
283
+ console.error('');
284
+ console.error('Recommended / optional:');
285
+ for (const [name, description] of input.recommended) {
286
+ console.error(` ${name}`);
287
+ console.error(` ${description}`);
288
+ }
289
+ }
290
+
291
+ if (issues.length > 0) {
292
+ console.error('');
293
+ console.error('Invalid values:');
294
+ for (const issue of issues) {
295
+ console.error(` - ${issue}`);
296
+ }
297
+ }
55
298
 
56
- child.on('error', (error) => {
57
- console.error(`Failed to run remote-codex ${command}: ${error.message}`);
299
+ console.error('');
300
+ console.error('Example:');
301
+ console.error(` ${input.example.join('\n ')}`);
58
302
  process.exit(1);
59
- });
303
+ }
304
+
305
+ function printEnvSummary(input) {
306
+ console.error(`${input.commandName} configuration:`);
307
+ console.error(input.description);
308
+ console.error('');
309
+ console.error('Required:');
310
+ for (const [name, description] of input.required) {
311
+ console.error(` ${name}: ${nonEmptyEnv(name) ? 'set' : 'missing'}`);
312
+ console.error(` ${description}`);
313
+ }
314
+ if (input.recommended.length > 0) {
315
+ console.error('');
316
+ console.error('Recommended / optional:');
317
+ for (const [name, description] of input.recommended) {
318
+ console.error(` ${name}: ${nonEmptyEnv(name) ? 'set' : 'default/unset'}`);
319
+ console.error(` ${description}`);
320
+ }
321
+ }
322
+ console.error('');
323
+ }
324
+
325
+ function nonEmptyEnv(name) {
326
+ return typeof process.env[name] === 'string' && process.env[name].trim().length > 0;
327
+ }
328
+
329
+ function relayServerEnv() {
330
+ const env = { ...process.env };
331
+ for (const name of [
332
+ 'HOST',
333
+ 'PORT',
334
+ 'REMOTE_CODEX_RELAY_SUPERVISOR_TOKEN',
335
+ 'REMOTE_CODEX_RELAY_CLIENT_TOKEN',
336
+ 'REMOTE_CODEX_ADMIN_EMAIL',
337
+ 'REMOTE_CODEX_RELAY_DATA_DIR',
338
+ 'REMOTE_CODEX_RELAY_SESSION_SECRET',
339
+ 'REMOTE_CODEX_RELAY_REGISTRATION_ENABLED',
340
+ 'REMOTE_CODEX_RELAY_WEB_DIST_DIR',
341
+ ]) {
342
+ if (typeof env[name] === 'string' && env[name].trim().length === 0) {
343
+ delete env[name];
344
+ }
345
+ }
346
+ return env;
347
+ }
60
348
 
61
349
  function normalizeCommand(value) {
62
350
  if (!value || value === '-h' || value === '--help') {
@@ -70,6 +358,18 @@ function normalizeCommand(value) {
70
358
  return aliases.get(value) ?? value;
71
359
  }
72
360
 
361
+ function normalizeHelpTarget(value) {
362
+ if (!value || value === '-h' || value === '--help') {
363
+ return 'help';
364
+ }
365
+
366
+ return normalizeCommand(value);
367
+ }
368
+
369
+ function hasHelpFlag(values) {
370
+ return values.includes('-h') || values.includes('--help');
371
+ }
372
+
73
373
  function readPackageVersion() {
74
374
  try {
75
375
  const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
@@ -79,19 +379,291 @@ function readPackageVersion() {
79
379
  }
80
380
  }
81
381
 
382
+ function printCommandHelp(target) {
383
+ switch (target) {
384
+ case 'start':
385
+ printStartHelp();
386
+ return;
387
+ case 'status':
388
+ printStatusHelp();
389
+ return;
390
+ case 'stop':
391
+ printStopHelp();
392
+ return;
393
+ case 'relay':
394
+ printRelayHelp();
395
+ return;
396
+ case 'relay-supervisor':
397
+ printRelaySupervisorHelp();
398
+ return;
399
+ case 'version':
400
+ printVersionHelp();
401
+ return;
402
+ case 'help':
403
+ default:
404
+ printHelp();
405
+ }
406
+ }
407
+
82
408
  function printHelp() {
83
409
  console.log(`remote-codex ${readPackageVersion()}
84
410
 
411
+ Local and relayable web supervisor for Codex workspaces and threads.
412
+
413
+ Usage:
414
+ remote-codex <command>
415
+ remote-codex <command> --help
416
+
417
+ Commands:
418
+ start Start the normal local web app and supervisor API.
419
+ status Print status for the normal local service.
420
+ stop Stop the normal local service.
421
+ relay Run the public relay server for browsers/apps.
422
+ relay-supervisor Run the private supervisor backend that connects to a relay.
423
+ version Print the installed remote-codex package version.
424
+ help [command] Show global help or help for a command.
425
+
426
+ Common workflows:
427
+ Local/LAN or Tailscale:
428
+ remote-codex start
429
+
430
+ Public relay server:
431
+ remote-codex relay
432
+
433
+ Private machine connecting outward to that relay:
434
+ remote-codex relay-supervisor
435
+
436
+ Command help:
437
+ remote-codex start --help
438
+ remote-codex relay --help
439
+ remote-codex relay-supervisor --help
440
+
441
+ Modes:
442
+ local Default supervisor mode. No Remote Codex login is required.
443
+ server Directly reachable supervisor. Requires admin auth.
444
+ relay Private supervisor connects outward to public relay.
445
+
446
+ Files and defaults:
447
+ .env is loaded from the installed package/source root when present.
448
+ Source checkout service dir defaults to .local/service.
449
+ npm-installed service dir defaults to ~/.remote-codex/service.
450
+
451
+ Docs:
452
+ docs/auth-and-connectivity-modes.md
453
+ `);
454
+ }
455
+
456
+ function printStartHelp() {
457
+ console.log(`remote-codex start
458
+
459
+ Start the normal local Remote Codex service: one supervisor API process plus one
460
+ web process. This is the recommended command for local development, LAN access,
461
+ or Tailscale access.
462
+
85
463
  Usage:
86
464
  remote-codex start
465
+ remote-codex service:start
466
+
467
+ What it starts:
468
+ Web UI http://SERVICE_HOST:SERVICE_PORT
469
+ API http://SERVICE_API_HOST:SERVICE_API_PORT
470
+
471
+ Environment:
472
+ SERVICE_HOST Web listen host. Default 127.0.0.1.
473
+ SERVICE_PORT Web listen port. Default ${defaultServicePort}.
474
+ SERVICE_API_HOST API listen host. Default 127.0.0.1.
475
+ SERVICE_API_PORT API listen port. Default ${defaultApiPort}.
476
+ REMOTE_CODEX_SERVICE_DIR Service state/log directory.
477
+ LOG_LEVEL API log level. Default warn for service mode.
478
+ DISABLE_REQUEST_LOGGING true/false. Default true for service mode.
479
+
480
+ Supervisor configuration forwarded to the API:
481
+ REMOTE_CODEX_MODE local, server, or relay. Default local.
482
+ WORKSPACE_ROOT Root directory for workspaces. Default home dir.
483
+ DATABASE_URL SQLite database path.
484
+ CODEX_HOME Codex config directory. Default ~/.codex.
485
+ CODEX_COMMAND Codex executable. Default codex.
486
+ CLAUDE_HOME Claude config directory. Default ~/.claude.
487
+ CLAUDE_COMMAND Claude executable. Default claude.
488
+ OPENCODE_HOME OpenCode config directory. Default ~/.opencode.
489
+ OPENCODE_COMMAND OpenCode executable. Default opencode.
490
+
491
+ Example:
492
+ SERVICE_HOST=127.0.0.1 SERVICE_PORT=4173 remote-codex start
493
+
494
+ Expose over Tailscale after start:
495
+ tailscale serve --bg http://127.0.0.1:${defaultServicePort}
496
+ `);
497
+ }
498
+
499
+ function printStatusHelp() {
500
+ console.log(`remote-codex status
501
+
502
+ Print status for the normal local service started by remote-codex start.
503
+
504
+ Usage:
87
505
  remote-codex status
506
+ remote-codex service:status
507
+
508
+ Output includes:
509
+ API process pid and health
510
+ Web process pid and health
511
+ Service URLs
512
+ Log directory
513
+
514
+ Environment:
515
+ REMOTE_CODEX_SERVICE_DIR Service state/log directory to inspect.
516
+ `);
517
+ }
518
+
519
+ function printStopHelp() {
520
+ console.log(`remote-codex stop
521
+
522
+ Stop the normal local service started by remote-codex start.
523
+
524
+ Usage:
88
525
  remote-codex stop
526
+ remote-codex service:stop
89
527
 
90
528
  Environment:
91
- SERVICE_HOST Web listen host, default 127.0.0.1
92
- SERVICE_PORT Web listen port, default ${defaultServicePort}
93
- SERVICE_API_HOST API listen host, default 127.0.0.1
94
- SERVICE_API_PORT API listen port, default ${defaultApiPort}
95
- REMOTE_CODEX_SERVICE_DIR Service state and log directory, default ~/.remote-codex/service
529
+ REMOTE_CODEX_SERVICE_DIR Service state/log directory to stop.
530
+ `);
531
+ }
532
+
533
+ function printRelayHelp() {
534
+ console.log(`remote-codex relay
535
+
536
+ Run the public relay server. This command is intended for a VPS or server that
537
+ browsers and mobile apps can reach. It serves the relay portal/admin UI and
538
+ forwards allowed API/WebSocket traffic to private machines connected with
539
+ remote-codex relay-supervisor.
540
+
541
+ Usage:
542
+ remote-codex relay
543
+
544
+ Required environment:
545
+ REMOTE_CODEX_ADMIN_USERNAME
546
+ Initial relay admin username, for example admin.
547
+ REMOTE_CODEX_ADMIN_PASSWORD
548
+ Initial relay admin password. Must be at least 8 characters.
549
+
550
+ Recommended environment:
551
+ REMOTE_CODEX_RELAY_SESSION_SECRET
552
+ Relay session signing secret. Must be at least 16 characters. Defaults to
553
+ REMOTE_CODEX_ADMIN_PASSWORD when omitted.
554
+ REMOTE_CODEX_RELAY_DATA_DIR
555
+ Persistent user/device/share store. Default .local/relay-server.
556
+ REMOTE_CODEX_RELAY_REGISTRATION_ENABLED
557
+ true/false. Default true. Use false when only admins should create users.
558
+ HOST
559
+ Relay listen host. Default 0.0.0.0. Use 0.0.0.0 on a public server.
560
+ PORT
561
+ Relay listen port. Default 8788.
562
+ REMOTE_CODEX_ADMIN_EMAIL
563
+ Optional seeded admin email. Defaults to <username>@relay.local.
564
+ REMOTE_CODEX_RELAY_WEB_DIST_DIR
565
+ Optional web dist override. Defaults to packaged supervisor-web/dist.
566
+ REMOTE_CODEX_RELAY_SUPERVISOR_TOKEN
567
+ Optional legacy bootstrap token. Prefer per-device tokens from the portal.
568
+ REMOTE_CODEX_RELAY_CLIENT_TOKEN
569
+ Optional legacy client token. Prefer relay user sessions.
570
+
571
+ Example:
572
+ REMOTE_CODEX_ADMIN_USERNAME=admin \\
573
+ REMOTE_CODEX_ADMIN_PASSWORD=change-me-now \\
574
+ REMOTE_CODEX_RELAY_SESSION_SECRET=at-least-16-characters \\
575
+ REMOTE_CODEX_RELAY_DATA_DIR=/var/lib/remote-codex-relay \\
576
+ REMOTE_CODEX_RELAY_REGISTRATION_ENABLED=false \\
577
+ HOST=0.0.0.0 PORT=8788 \\
578
+ remote-codex relay
579
+
580
+ After start:
581
+ Relay health: GET /healthz
582
+ Portal: /relay-portal
583
+ Admin: /relay-admin
584
+ Device API: /relay/devices/:deviceId/api/...
585
+ Device WS: /relay/devices/:deviceId/ws
586
+
587
+ Typical flow:
588
+ 1. Start remote-codex relay on the public server.
589
+ 2. Log in to /relay-portal as the seeded admin.
590
+ 3. Create a device and copy its rcd_... token.
591
+ 4. Put that token into REMOTE_CODEX_RELAY_AGENT_TOKEN on the private machine.
592
+ 5. Start remote-codex relay-supervisor on the private machine.
593
+ `);
594
+ }
595
+
596
+ function printRelaySupervisorHelp() {
597
+ console.log(`remote-codex relay-supervisor
598
+
599
+ Run the private-machine supervisor backend in relay mode. This is the process
600
+ that has local workspace access and runs Codex. It does not expose itself to the
601
+ public internet; it connects outward to a public relay server.
602
+
603
+ Usage:
604
+ remote-codex relay-supervisor
605
+
606
+ This command automatically sets for the child supervisor:
607
+ REMOTE_CODEX_MODE=relay
608
+
609
+ Required environment:
610
+ REMOTE_CODEX_ADMIN_USERNAME
611
+ Private supervisor admin username. Required because relay mode enables API auth.
612
+ REMOTE_CODEX_ADMIN_PASSWORD
613
+ Private supervisor admin password.
614
+ REMOTE_CODEX_SESSION_SECRET
615
+ Private supervisor session signing secret. Must be at least 16 characters.
616
+ REMOTE_CODEX_RELAY_SERVER_URL
617
+ Public relay websocket base URL. Use ws://host:port for plain relay ports or
618
+ wss://relay.example.com behind TLS.
619
+ REMOTE_CODEX_RELAY_AGENT_TOKEN
620
+ Device token created in the relay portal. This is not the relay admin password.
621
+
622
+ Recommended environment:
623
+ HOST
624
+ Private supervisor listen host. Default 127.0.0.1.
625
+ PORT
626
+ Private supervisor listen port. Default 8787.
627
+ DATABASE_URL
628
+ SQLite database path. Set this when running beside another Remote Codex.
629
+ WORKSPACE_ROOT
630
+ Root directory that workspace paths must live under. Default home directory.
631
+ CODEX_HOME
632
+ Codex config directory. Default ~/.codex.
633
+ CODEX_COMMAND
634
+ Codex executable. Default codex.
635
+ REMOTE_CODEX_ENABLED_AGENT_PROVIDERS
636
+ Comma-separated provider ids, for example codex,claude.
637
+
638
+ Example:
639
+ REMOTE_CODEX_ADMIN_USERNAME=admin \\
640
+ REMOTE_CODEX_ADMIN_PASSWORD=change-me-locally \\
641
+ REMOTE_CODEX_SESSION_SECRET=at-least-16-characters \\
642
+ REMOTE_CODEX_RELAY_SERVER_URL=wss://relay.example.com \\
643
+ REMOTE_CODEX_RELAY_AGENT_TOKEN=rcd_device_token_from_relay_portal \\
644
+ HOST=127.0.0.1 PORT=8787 \\
645
+ DATABASE_URL=$HOME/.remote-codex/relay-supervisor.sqlite \\
646
+ remote-codex relay-supervisor
647
+
648
+ When running a separate test backend beside an existing Remote Codex service,
649
+ set separate values for:
650
+ PORT
651
+ DATABASE_URL
652
+ WORKSPACE_ROOT
653
+
654
+ The relay server will report supervisorConnected=true on /healthz after this
655
+ process connects successfully.
656
+ `);
657
+ }
658
+
659
+ function printVersionHelp() {
660
+ console.log(`remote-codex version
661
+
662
+ Print the installed remote-codex package version.
663
+
664
+ Usage:
665
+ remote-codex version
666
+ remote-codex --version
667
+ remote-codex -v
96
668
  `);
97
669
  }