remote-codex 0.11.3 → 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.
- package/README.md +4 -0
- package/apps/relay-server/dist/index.js +35 -2
- package/apps/supervisor-api/dist/chunk-ZWZQVPDT.js +27893 -0
- package/apps/supervisor-api/dist/index.js +4 -25727
- package/apps/supervisor-api/dist/worker-index.d.ts +2 -0
- package/apps/supervisor-api/dist/worker-index.js +197 -0
- package/apps/supervisor-web/dist/assets/index-CbdWtyx0.js +5 -0
- package/apps/supervisor-web/dist/assets/index-Di1JBevU.css +1 -0
- package/apps/supervisor-web/dist/assets/thread-ui-ICfwCbte.js +3604 -0
- package/apps/supervisor-web/dist/assets/ui-vendor-D1uxdi-d.js +430 -0
- package/apps/supervisor-web/dist/index.html +6 -7
- package/bin/remote-codex.mjs +534 -19
- package/package.json +41 -2
- package/packages/agent-runtime/src/types.ts +2 -1
- package/packages/codex/src/appServerManager.ts +1 -0
- package/packages/codex/src/historyItems.test.ts +45 -0
- package/packages/codex/src/historyItems.ts +22 -0
- package/packages/codex/src/runtimeAdapter.ts +6 -0
- package/packages/codex/src/types.ts +2 -1
- package/packages/db/migrations/0018_control_plane.sql +129 -0
- package/packages/db/migrations/0019_control_plane_projects.sql +19 -0
- package/packages/db/migrations/0020_control_workspace_status.sql +1 -0
- package/packages/db/migrations/0021_control_sandbox_lifecycle_fields.sql +3 -0
- package/packages/db/migrations/0022_control_sandbox_resource_profile.sql +1 -0
- package/packages/db/migrations/0023_control_usage_import_state.sql +18 -0
- package/packages/db/migrations/0024_control_auth.sql +23 -0
- package/packages/db/migrations/0025_control_harness_credentials.sql +29 -0
- package/packages/db/migrations/0026_control_harness_usage_events.sql +27 -0
- package/packages/db/src/schema.ts +305 -1
- package/packages/shared/src/index.ts +32 -0
- package/packages/shared/src/tokens.ts +137 -0
- package/apps/supervisor-web/dist/assets/index-CBIze1VS.css +0 -1
- package/apps/supervisor-web/dist/assets/index-YpGAPjED.js +0 -4
- package/apps/supervisor-web/dist/assets/thread-ui-BEieA99i.css +0 -1
- package/apps/supervisor-web/dist/assets/thread-ui-CF80LEEN.js +0 -3613
- 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-
|
|
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-
|
|
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-
|
|
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/
|
|
17
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
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>
|
package/bin/remote-codex.mjs
CHANGED
|
@@ -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 (
|
|
29
|
-
|
|
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:
|
|
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
|
|
526
|
+
remote-codex service:stop
|
|
136
527
|
|
|
137
528
|
Environment:
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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
|
}
|