@remodex/rmx 1.0.3 → 1.0.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.
@@ -16,8 +16,8 @@
16
16
  } catch (e) {}
17
17
  })();
18
18
  </script>
19
- <script type="module" crossorigin src="/assets/index-Cy432rMC.js"></script>
20
- <link rel="stylesheet" crossorigin href="/assets/index-CZqebSPQ.css">
19
+ <script type="module" crossorigin src="/assets/index-D3MaPif3.js"></script>
20
+ <link rel="stylesheet" crossorigin href="/assets/index-1SDbgh2-.css">
21
21
  </head>
22
22
  <body>
23
23
  <div id="root"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remodex/rmx",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "Remodex universal provider proxy for OpenAI Codex & Claude Code — use any LLM with Codex CLI/App/SDK and Claude Code",
5
5
  "displayName": "Remodex",
6
6
  "type": "module",
@@ -13,10 +13,10 @@
13
13
  "./package.json": "./package.json"
14
14
  },
15
15
  "bin": {
16
- "rmx": "./bin/ocx.mjs",
17
- "remodex": "./bin/ocx.mjs",
18
- "opencodex": "./bin/ocx.mjs",
19
- "ocx": "./bin/ocx.mjs"
16
+ "rmx": "bin/ocx.mjs",
17
+ "remodex": "bin/ocx.mjs",
18
+ "opencodex": "bin/ocx.mjs",
19
+ "ocx": "bin/ocx.mjs"
20
20
  },
21
21
  "files": [
22
22
  "bin",
@@ -8,7 +8,23 @@ import { resolveCloudflared, type CloudflaredExecutable } from "./cloudflared";
8
8
  const QUICK_TUNNEL_URL = /https:\/\/[a-z0-9-]+\.trycloudflare\.com/iu;
9
9
  const QUICK_URL_TIMEOUT_MS = 30_000;
10
10
  const VERIFY_TIMEOUT_MS = 5_000;
11
- const RETRY_DELAYS_MS = [1_000, 2_500, 5_000, 10_000, 30_000] as const;
11
+ const PROCESS_RETRY_DELAYS_MS = [1_000, 2_500, 5_000, 10_000, 30_000] as const;
12
+ // A freshly-created Quick Tunnel URL and a newly-written Named Tunnel DNS route
13
+ // can be visible in cloudflared output before Cloudflare's edge can serve it.
14
+ // Keep that expected propagation neutral in the UI for almost two minutes. If
15
+ // it still is not reachable, expose the failure but continue probing the same
16
+ // child and URL so late propagation can recover without generating a new URL.
17
+ const VERIFY_PROPAGATION_DELAYS_MS = [
18
+ 1_000,
19
+ 2_000,
20
+ 3_000,
21
+ 5_000,
22
+ 8_000,
23
+ 13_000,
24
+ 20_000,
25
+ 30_000,
26
+ 30_000,
27
+ ] as const;
12
28
 
13
29
  export type AndroidRemoteCloudflareFailure =
14
30
  | "cloudflared_unavailable"
@@ -175,8 +191,8 @@ async function verifiedPublicGateway(
175
191
  try {
176
192
  const signal = AbortSignal.timeout(VERIFY_TIMEOUT_MS);
177
193
  const [healthResponse, descriptorResponse] = await Promise.all([
178
- fetchImpl(new URL("/healthz", baseUrl), { signal }),
179
- fetchImpl(new URL("/.well-known/t3/environment", baseUrl), { signal }),
194
+ fetchImpl(new URL("/healthz", baseUrl), { signal, cache: "no-store" }),
195
+ fetchImpl(new URL("/.well-known/t3/environment", baseUrl), { signal, cache: "no-store" }),
180
196
  ]);
181
197
  if (!healthResponse.ok || !descriptorResponse.ok) return false;
182
198
  const health = await healthResponse.json() as Record<string, unknown>;
@@ -218,7 +234,9 @@ export class ManagedAndroidRemoteCloudflareTunnel implements AndroidRemoteCloudf
218
234
  }
219
235
  return {
220
236
  mode: settings.tunnelMode,
221
- ...(settings.namedTunnelHostname ? { namedHostname: settings.namedTunnelHostname } : {}),
237
+ ...(settings.tunnelMode === "named" && settings.namedTunnelHostname
238
+ ? { namedHostname: settings.namedTunnelHostname }
239
+ : {}),
222
240
  hasNamedTunnelToken: this.hasNamedTunnelToken,
223
241
  };
224
242
  }
@@ -322,7 +340,7 @@ export class ManagedAndroidRemoteCloudflareTunnel implements AndroidRemoteCloudf
322
340
  publicUrl: null,
323
341
  error: result,
324
342
  });
325
- const delay = RETRY_DELAYS_MS[Math.min(attempt, RETRY_DELAYS_MS.length - 1)]!;
343
+ const delay = PROCESS_RETRY_DELAYS_MS[Math.min(attempt, PROCESS_RETRY_DELAYS_MS.length - 1)]!;
326
344
  attempt += 1;
327
345
  await this.deps.sleep(delay);
328
346
  }
@@ -444,7 +462,7 @@ export class ManagedAndroidRemoteCloudflareTunnel implements AndroidRemoteCloudf
444
462
  return "tunnel_failed";
445
463
  }
446
464
  verificationFailures += 1;
447
- if (verificationFailures >= 5) {
465
+ if (verificationFailures > VERIFY_PROPAGATION_DELAYS_MS.length) {
448
466
  this.publish({
449
467
  mode,
450
468
  status: "error",
@@ -452,7 +470,9 @@ export class ManagedAndroidRemoteCloudflareTunnel implements AndroidRemoteCloudf
452
470
  error: "verification_failed",
453
471
  });
454
472
  }
455
- const delay = RETRY_DELAYS_MS[Math.min(verificationFailures - 1, RETRY_DELAYS_MS.length - 1)]!;
473
+ const delay = VERIFY_PROPAGATION_DELAYS_MS[
474
+ Math.min(verificationFailures - 1, VERIFY_PROPAGATION_DELAYS_MS.length - 1)
475
+ ]!;
456
476
  const wait = await Promise.race([
457
477
  this.deps.sleep(delay).then(() => "retry" as const),
458
478
  child.exited.then(() => "exit" as const),
@@ -473,7 +493,9 @@ export class DisabledAndroidRemoteCloudflareTunnel implements AndroidRemoteCloud
473
493
  async configuration(settings: AndroidRemoteSettings): Promise<AndroidRemoteCloudflareConfiguration> {
474
494
  return {
475
495
  mode: settings.tunnelMode,
476
- ...(settings.namedTunnelHostname ? { namedHostname: settings.namedTunnelHostname } : {}),
496
+ ...(settings.tunnelMode === "named" && settings.namedTunnelHostname
497
+ ? { namedHostname: settings.namedTunnelHostname }
498
+ : {}),
477
499
  hasNamedTunnelToken: false,
478
500
  };
479
501
  }
package/src/cli/help.ts CHANGED
@@ -20,6 +20,15 @@ function canonicalCommand(value: string): string {
20
20
  }
21
21
 
22
22
  const helpEntries: Record<string, HelpEntry> = {
23
+ onboard: {
24
+ usage: "rmx onboard [--verbose] [--json] [--no-open]",
25
+ summary: "Configure Codex, Android Remote, the background service, and a verified phone link.",
26
+ details: [
27
+ "Safe to rerun: existing providers and custom-domain tunnels are preserved.",
28
+ "Fresh users get Quick Tunnel; Windows also installs the status tray.",
29
+ "--verbose shows inner command diagnostics; --json implies --no-open.",
30
+ ],
31
+ },
23
32
  init: { usage: "rmx init", summary: "Interactive setup for providers and Codex config injection." },
24
33
  setup: { usage: "rmx setup", summary: "Interactive setup for providers and Codex config injection (alias of init)." },
25
34
  start: { usage: "rmx start [--port <port>]", summary: "Start the proxy server and sync models to Codex." },
@@ -104,7 +113,10 @@ const helpEntries: Record<string, HelpEntry> = {
104
113
  },
105
114
  login: { usage: "rmx login <provider>", summary: "OAuth or API-key login for a provider." },
106
115
  logout: { usage: "rmx logout <provider>", summary: "Remove a stored provider login." },
107
- gui: { usage: "rmx gui", summary: "Open the Remodex dashboard." },
116
+ gui: {
117
+ usage: "rmx gui [--update]",
118
+ summary: "Open the Remodex dashboard; --update opens the npm package updater.",
119
+ },
108
120
  update: {
109
121
  usage: "rmx update [--tag latest|preview]",
110
122
  summary: "Update Remodex. Preview installs stay on the preview tag unless overridden.",
@@ -198,16 +210,15 @@ const helpEntries: Record<string, HelpEntry> = {
198
210
  grok: { usage: "rmx grok <status|exclude|include|set|clear|apply> ...", summary: "Manage and apply the Grok Build model fence." },
199
211
  integration: { usage: "rmx integration <claude|grok|client> ...", summary: "Manage supported client integrations." },
200
212
  system: {
201
- usage: "rmx system <status|settings|startup|diagnostics|sync|update|desktop-update> ...",
213
+ usage: "rmx system <status|settings|startup|diagnostics|sync|update> ...",
202
214
  summary: "Manage headless runtime settings, startup, sync, diagnostics, and updates.",
203
215
  details: [
216
+ "update check [--channel latest|preview] Check the @remodex/rmx package on npm.",
217
+ "update run [--channel latest|preview] --yes Install the npm package update.",
204
218
  "update auto on [--channel latest|preview] Enable the daily unattended package updater (enabled by default for global installs).",
205
219
  "update auto off Disable unattended package updates.",
206
220
  "update auto status Show scheduler, last result, and rollback information.",
207
- "desktop-update check [--channel latest|preview] Check the native desktop release manifest.",
208
- "desktop-update run [--channel latest|preview] Download and verify a native desktop update.",
209
- "desktop-update status Show shared tray/dashboard update state.",
210
- "Headless commands never install a native package; installation requires the desktop shell.",
221
+ "Changelogs come from the matching GitHub Release (v<version>).",
211
222
  ],
212
223
  },
213
224
  config: {
@@ -303,6 +314,7 @@ export function printUsage(): void {
303
314
 
304
315
  Usage:
305
316
  rmx Install/update and start the background service
317
+ rmx onboard Complete first-time setup and open the phone pairing QR
306
318
  rmx setup Interactive setup (alias: init)
307
319
  rmx start [--port <port>] Start the proxy server (auto-syncs models to Codex)
308
320
  rmx stop Stop the proxy AND restore native Codex (plain codex works again)
@@ -349,6 +361,7 @@ Usage:
349
361
 
350
362
  Examples:
351
363
  rmx Install/update and start the background service
364
+ rmx onboard Set up Codex, the service, and Android Remote
352
365
  rmx init Set up provider and inject into Codex
353
366
  rmx start Start on default port (10100)
354
367
  rmx start --port 8080 Start on custom port
package/src/cli/index.ts CHANGED
@@ -59,15 +59,6 @@ import { maybeShowUpdatePrompt } from "../update/notify";
59
59
  import { syncModelsToCodex } from "../codex/sync";
60
60
  import { setIntegrationEnabled, shouldSyncCodexOnStart, shouldSyncGrokOnStart, syncCodexOnStartIfEnabled } from "../codex/desired-state";
61
61
  import { normalizeUpdateChannel, runGuiUpdateWorker } from "../update/job";
62
- import { currentVersion } from "../update/index";
63
- import {
64
- desktopUpdateInstallAction,
65
- readDesktopUpdateState,
66
- runAutomaticDesktopUpdateCheck,
67
- runDesktopUpdateWorker,
68
- startDesktopUpdateJob,
69
- type DesktopReleaseChannel,
70
- } from "../update/desktop-release";
71
62
  import { collectOrcaCodexHomeDiagnostic } from "../codex/home";
72
63
  import { removeOwnedConfigState } from "../lib/config-ownership";
73
64
  import { withProcessRuntimeProvenance } from "../lib/bun-runtime";
@@ -663,56 +654,6 @@ async function handleTrayProxyRestart(): Promise<void> {
663
654
  await handleProxyRestart(() => handleTrayProxyStart(false));
664
655
  }
665
656
 
666
- /**
667
- * Fixed desktop-shell action used by the tray. A first click checks and
668
- * downloads a verified artifact; once the tray shows "ready", the next click
669
- * hands it to the platform installer. No caller-selected command or path is
670
- * accepted here.
671
- */
672
- async function handleDesktopUpdateRequest(requestArgs: string[] = []): Promise<void> {
673
- let expectedReady: { id: string; channel: DesktopReleaseChannel } | undefined;
674
- if (requestArgs.length > 0) {
675
- const [mode, id, channel] = requestArgs;
676
- if (
677
- requestArgs.length !== 3
678
- || mode !== "--install-ready"
679
- || !id
680
- || !/^desktop-\d+-[a-z0-9]{1,32}$/.test(id)
681
- || (channel !== "latest" && channel !== "preview")
682
- ) {
683
- console.error("Invalid desktop update request.");
684
- process.exitCode = 64;
685
- return;
686
- }
687
- expectedReady = { id, channel };
688
- }
689
- const previous = readDesktopUpdateState();
690
- const install = expectedReady !== undefined || desktopUpdateInstallAction(previous);
691
- const channel: DesktopReleaseChannel = expectedReady?.channel
692
- ?? previous?.channel
693
- ?? (currentVersion().includes("-") ? "preview" : "latest");
694
- try {
695
- const job = startDesktopUpdateJob(channel, {
696
- install,
697
- ...(expectedReady ? { expectedReady } : {}),
698
- });
699
- console.log(
700
- install
701
- ? `Desktop update installation started (${job.latestVersion ?? "latest"}).`
702
- : "Desktop update check started.",
703
- );
704
- } catch (error) {
705
- console.error(error instanceof Error ? error.message : "Desktop update could not start.");
706
- process.exitCode = 1;
707
- }
708
- }
709
-
710
- async function handleAutomaticDesktopUpdateCheck(): Promise<void> {
711
- const channel: DesktopReleaseChannel = currentVersion().includes("-") ? "preview" : "latest";
712
- const result = await runAutomaticDesktopUpdateCheck(channel);
713
- console.log(JSON.stringify(result));
714
- }
715
-
716
657
  async function handleRestartStartWhenStopped(): Promise<boolean | "skipped"> {
717
658
  if (!codexAutoStartEnabled(loadConfig())) {
718
659
  console.log("Codex autostart is disabled; no proxy was started.");
@@ -1080,6 +1021,11 @@ async function handleReady(args: ReadyArgs): Promise<never> {
1080
1021
  }
1081
1022
 
1082
1023
  switch (command) {
1024
+ case "onboard": {
1025
+ const { runOnboardCommand } = await import("./onboard");
1026
+ process.exitCode = await runOnboardCommand(args.slice(1));
1027
+ break;
1028
+ }
1083
1029
  case "init":
1084
1030
  case "setup": {
1085
1031
  const { runInit } = await import("./init");
@@ -1272,6 +1218,13 @@ switch (command) {
1272
1218
  break;
1273
1219
  }
1274
1220
  case "gui": {
1221
+ const guiArgs = args.slice(1);
1222
+ const openUpdate = guiArgs.length === 1 && guiArgs[0] === "--update";
1223
+ if (guiArgs.length > (openUpdate ? 1 : 0)) {
1224
+ console.error("Usage: rmx gui [--update]");
1225
+ process.exitCode = 64;
1226
+ break;
1227
+ }
1275
1228
  const cfg = await import("../config");
1276
1229
  const config = cfg.loadConfig();
1277
1230
  // Identity-checked liveness (not the pid file + a fixed sleep): finds a fallback-port
@@ -1295,7 +1248,7 @@ switch (command) {
1295
1248
  // Open the host the proxy actually binds — `localhost` only answers for
1296
1249
  // loopback/wildcard binds, not a concrete LAN/IPv6 hostname.
1297
1250
  const guiHost = probeHostname(live?.hostname ?? config.hostname);
1298
- const guiUrl = `http://${guiHost === "127.0.0.1" ? "localhost" : guiHost}:${live?.port ?? config.port}`;
1251
+ const guiUrl = `http://${guiHost === "127.0.0.1" ? "localhost" : guiHost}:${live?.port ?? config.port}${openUpdate ? "/#dashboard/update" : ""}`;
1299
1252
  console.log(`Opening ${guiUrl}`);
1300
1253
  const { openUrl } = await import("../lib/open-url");
1301
1254
  openUrl(guiUrl);
@@ -1370,19 +1323,6 @@ switch (command) {
1370
1323
  if (!result.ok) process.exitCode = 1;
1371
1324
  break;
1372
1325
  }
1373
- case "__desktop-update-worker": {
1374
- const id = args[1];
1375
- const mode = args[2];
1376
- if (!id || !/^desktop-\d+-[a-z0-9]+$/.test(id) || (mode !== "download" && mode !== "install")) {
1377
- console.error("Invalid desktop update worker arguments.");
1378
- process.exitCode = 64;
1379
- break;
1380
- }
1381
- await runDesktopUpdateWorker(id, mode === "install");
1382
- break;
1383
- }
1384
- case "__desktop-update":
1385
- case "__desktop-auto-update-check":
1386
1326
  case "__tray-start":
1387
1327
  case "__tray-restart":
1388
1328
  case "__startup-health":
@@ -1391,8 +1331,6 @@ switch (command) {
1391
1331
  await dispatchInternalCliCommand(command as InternalCliCommand, {
1392
1332
  trayStart: async () => { await handleTrayProxyStart(); },
1393
1333
  trayRestart: handleTrayProxyRestart,
1394
- desktopUpdate: () => handleDesktopUpdateRequest(args.slice(1)),
1395
- desktopAutoUpdateCheck: handleAutomaticDesktopUpdateCheck,
1396
1334
  startupHealth: async () => {
1397
1335
  const { collectStartupHealth } = await import("../codex/autostart-health");
1398
1336
  console.log(JSON.stringify(collectStartupHealth(loadConfig())));
package/src/cli/init.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import * as readline from "node:readline";
2
2
  import { constants as fsConstants, copyFileSync, existsSync, readFileSync, unlinkSync } from "node:fs";
3
3
  import { injectCodexConfig } from "../codex/inject";
4
+ import { CODEX_CONFIG_PATH } from "../codex/paths";
4
5
  import { classifyOpenAiTierBackup, getConfigPath, getDefaultConfig, isValidProviderName, saveConfig } from "../config";
5
6
  import { enrichProviderFromCatalog } from "../oauth/key-providers";
6
7
  import { deriveInitProviders } from "../providers/derive";
@@ -189,13 +190,18 @@ export async function runInit(): Promise<void> {
189
190
  // preserved by renaming it out of the collision path (sol review 260722).
190
191
  cleanupOpenAiTierBackupAfterInit();
191
192
  console.log(`\n✅ Config saved to ${getConfigPath()}`);
193
+ console.log(` Codex config target: ${CODEX_CONFIG_PATH}`);
192
194
  if (oauthHint) console.log(`🔐 Authenticate this provider with: rmx login ${providerName}`);
193
195
 
194
196
  const injectAnswer = await prompt.ask("Inject into Codex config.toml? [Y/n]: ");
195
197
  if (injectAnswer.trim().toLowerCase() !== "n") {
196
198
  console.log("Fetching available models from provider...");
197
199
  const result = await injectCodexConfig(port, config);
198
- console.log(result.success ? `✅ ${result.message}` : `⚠️ ${result.message}`);
200
+ const routed = result.success && result.routingApplied !== false;
201
+ console.log(routed ? `✅ ${result.message}` : `⚠️ ${result.message}`);
202
+ if (!routed) {
203
+ console.log(` Remodex did not take ownership of plain Codex routing. Review the reason above, then run 'rmx sync'.`);
204
+ }
199
205
  }
200
206
 
201
207
  const shimAnswer = await prompt.ask("Install Codex autostart shim? [Y/n]: ");
@@ -2,8 +2,6 @@ export type InternalCliCommand =
2
2
  | "__tray-start"
3
3
  | "__tray-restart"
4
4
  | "__startup-health"
5
- | "__desktop-update"
6
- | "__desktop-auto-update-check"
7
5
  | "__desktop-restart-codex"
8
6
  | "__desktop-restart-client";
9
7
 
@@ -11,8 +9,6 @@ export interface InternalCliHandlers {
11
9
  trayStart: () => void | Promise<void>;
12
10
  trayRestart: () => void | Promise<void>;
13
11
  startupHealth: () => void | Promise<void>;
14
- desktopUpdate: () => void | Promise<void>;
15
- desktopAutoUpdateCheck: () => void | Promise<void>;
16
12
  desktopRestartCodex: () => void | Promise<void>;
17
13
  desktopRestartClient: () => void | Promise<void>;
18
14
  }
@@ -26,8 +22,6 @@ export async function dispatchInternalCliCommand(
26
22
  case "__tray-start": return void await handlers.trayStart();
27
23
  case "__tray-restart": return void await handlers.trayRestart();
28
24
  case "__startup-health": return void await handlers.startupHealth();
29
- case "__desktop-update": return void await handlers.desktopUpdate();
30
- case "__desktop-auto-update-check": return void await handlers.desktopAutoUpdateCheck();
31
25
  case "__desktop-restart-codex": return void await handlers.desktopRestartCodex();
32
26
  case "__desktop-restart-client": return void await handlers.desktopRestartClient();
33
27
  default: throw new Error(`Unsupported internal CLI command: ${String(command)}`);