remote-codex 0.11.3 → 0.11.5

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 (36) hide show
  1. package/README.md +4 -0
  2. package/apps/relay-server/dist/index.js +51 -2
  3. package/apps/supervisor-api/dist/chunk-ZWZQVPDT.js +27893 -0
  4. package/apps/supervisor-api/dist/index.js +4 -25727
  5. package/apps/supervisor-api/dist/worker-index.d.ts +2 -0
  6. package/apps/supervisor-api/dist/worker-index.js +197 -0
  7. package/apps/supervisor-web/dist/assets/index-CbdWtyx0.js +5 -0
  8. package/apps/supervisor-web/dist/assets/index-Di1JBevU.css +1 -0
  9. package/apps/supervisor-web/dist/assets/thread-ui-ICfwCbte.js +3604 -0
  10. package/apps/supervisor-web/dist/assets/ui-vendor-D1uxdi-d.js +430 -0
  11. package/apps/supervisor-web/dist/index.html +6 -7
  12. package/bin/remote-codex.mjs +534 -19
  13. package/package.json +41 -2
  14. package/packages/agent-runtime/src/types.ts +2 -1
  15. package/packages/codex/src/appServerManager.ts +1 -0
  16. package/packages/codex/src/historyItems.test.ts +45 -0
  17. package/packages/codex/src/historyItems.ts +22 -0
  18. package/packages/codex/src/runtimeAdapter.ts +6 -0
  19. package/packages/codex/src/types.ts +2 -1
  20. package/packages/db/migrations/0018_control_plane.sql +129 -0
  21. package/packages/db/migrations/0019_control_plane_projects.sql +19 -0
  22. package/packages/db/migrations/0020_control_workspace_status.sql +1 -0
  23. package/packages/db/migrations/0021_control_sandbox_lifecycle_fields.sql +3 -0
  24. package/packages/db/migrations/0022_control_sandbox_resource_profile.sql +1 -0
  25. package/packages/db/migrations/0023_control_usage_import_state.sql +18 -0
  26. package/packages/db/migrations/0024_control_auth.sql +23 -0
  27. package/packages/db/migrations/0025_control_harness_credentials.sql +29 -0
  28. package/packages/db/migrations/0026_control_harness_usage_events.sql +27 -0
  29. package/packages/db/src/schema.ts +305 -1
  30. package/packages/shared/src/index.ts +32 -0
  31. package/packages/shared/src/tokens.ts +137 -0
  32. package/apps/supervisor-web/dist/assets/index-CBIze1VS.css +0 -1
  33. package/apps/supervisor-web/dist/assets/index-YpGAPjED.js +0 -4
  34. package/apps/supervisor-web/dist/assets/thread-ui-BEieA99i.css +0 -1
  35. package/apps/supervisor-web/dist/assets/thread-ui-CF80LEEN.js +0 -3613
  36. package/apps/supervisor-web/dist/assets/ui-vendor-CW6egZBG.js +0 -430
@@ -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-YpGAPjED.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-CW6egZBG.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-CF80LEEN.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-CBIze1VS.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>
@@ -11,6 +11,8 @@ const packageJsonPath = path.join(packageRoot, 'package.json');
11
11
  const serviceManagerPath = path.join(packageRoot, 'scripts', 'service-manager.mjs');
12
12
  const relayDistEntry = path.join(packageRoot, 'apps', 'relay-server', 'dist', 'index.js');
13
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');
14
16
  const sourceCheckout =
15
17
  fs.existsSync(path.join(packageRoot, 'pnpm-workspace.yaml')) &&
16
18
  fs.existsSync(path.join(packageRoot, 'scripts', 'service-restart.mjs'));
@@ -23,10 +25,20 @@ const aliases = new Map([
23
25
  ['service:status', 'status'],
24
26
  ]);
25
27
 
28
+ if (fs.existsSync(path.join(packageRoot, '.env'))) {
29
+ process.loadEnvFile?.(path.join(packageRoot, '.env'));
30
+ }
31
+
26
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;
27
39
 
28
- if (command === 'help') {
29
- printHelp();
40
+ if (commandHelpTarget) {
41
+ printCommandHelp(commandHelpTarget);
30
42
  process.exit(0);
31
43
  }
32
44
 
@@ -37,6 +49,8 @@ if (command === 'version') {
37
49
 
38
50
  if (command === 'relay') {
39
51
  runRelayServer();
52
+ } else if (command === 'relay-supervisor') {
53
+ runRelaySupervisor();
40
54
  } else if (!['start', 'stop', 'status'].includes(command)) {
41
55
  printHelp();
42
56
  process.exit(command ? 1 : 0);
@@ -63,6 +77,46 @@ if (command === 'relay') {
63
77
  }
64
78
 
65
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
+
66
120
  const relayEntry = fs.existsSync(relayDistEntry) ? relayDistEntry : relaySourceEntry;
67
121
  let commandToRun = process.execPath;
68
122
  let args = [relayEntry];
@@ -85,7 +139,7 @@ function runRelayServer() {
85
139
 
86
140
  const relay = spawn(commandToRun, args, {
87
141
  cwd: packageRoot,
88
- env: process.env,
142
+ env: relayServerEnv(),
89
143
  stdio: 'inherit',
90
144
  });
91
145
 
@@ -95,6 +149,9 @@ function runRelayServer() {
95
149
  return;
96
150
  }
97
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
+ }
98
155
  process.exit(code ?? 1);
99
156
  });
100
157
 
@@ -104,6 +161,191 @@ function runRelayServer() {
104
161
  });
105
162
  }
106
163
 
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
+ });
242
+
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) {
268
+ return;
269
+ }
270
+
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
+ }
298
+
299
+ console.error('');
300
+ console.error('Example:');
301
+ console.error(` ${input.example.join('\n ')}`);
302
+ process.exit(1);
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
+ }
348
+
107
349
  function normalizeCommand(value) {
108
350
  if (!value || value === '-h' || value === '--help') {
109
351
  return 'help';
@@ -116,6 +358,18 @@ function normalizeCommand(value) {
116
358
  return aliases.get(value) ?? value;
117
359
  }
118
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
+
119
373
  function readPackageVersion() {
120
374
  try {
121
375
  const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
@@ -125,30 +379,291 @@ function readPackageVersion() {
125
379
  }
126
380
  }
127
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
+
128
408
  function printHelp() {
129
409
  console.log(`remote-codex ${readPackageVersion()}
130
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
+
131
463
  Usage:
132
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:
133
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:
134
525
  remote-codex stop
135
- remote-codex relay
526
+ remote-codex service:stop
136
527
 
137
528
  Environment:
138
- SERVICE_HOST Web listen host, default 127.0.0.1
139
- SERVICE_PORT Web listen port, default ${defaultServicePort}
140
- SERVICE_API_HOST API listen host, default 127.0.0.1
141
- SERVICE_API_PORT API listen port, default ${defaultApiPort}
142
- REMOTE_CODEX_SERVICE_DIR Service state and log directory, default ~/.remote-codex/service
143
-
144
- Relay:
145
- REMOTE_CODEX_RELAY_SUPERVISOR_TOKEN Legacy bootstrap token for supervisor tunnels
146
- REMOTE_CODEX_ADMIN_USERNAME Relay admin username
147
- REMOTE_CODEX_ADMIN_PASSWORD Relay admin password
148
- REMOTE_CODEX_RELAY_DATA_DIR Relay user/device store, default .local/relay-server
149
- REMOTE_CODEX_RELAY_WEB_DIST_DIR Web dist override, defaults to packaged supervisor-web/dist
150
- REMOTE_CODEX_RELAY_REGISTRATION_ENABLED true/false, default true
151
- HOST Relay listen host, default 0.0.0.0
152
- PORT Relay listen port, default 8788
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
153
668
  `);
154
669
  }