@remodex/rmx 1.0.2 → 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.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  <h3 align="center">Remodex</h3>
2
2
  <p align="center"><b>Universal provider proxy for OpenAI Codex, Claude Code, Claude Desktop &amp; Grok Build</b><br>
3
- Two commands, and every one of them runs any LLM you point it at.</p>
3
+ One command to start, and every request can use any LLM you point it at.</p>
4
4
 
5
5
  <p align="center">
6
6
  <a href="https://x.com/claudeebum"><img src="https://img.shields.io/badge/%40claudeebum-000000?logo=x&logoColor=white" alt="Follow @claudeebum on X"></a>
@@ -11,7 +11,7 @@ Two commands, and every one of them runs any LLM you point it at.</p>
11
11
 
12
12
  ```bash
13
13
  npm install -g @remodex/rmx
14
- rmx start # proxy + dashboard on localhost:10100
14
+ rmx onboard # configure Codex, background service, and Android Remote
15
15
  ```
16
16
 
17
17
  <table align="center">
@@ -56,10 +56,14 @@ account while existing threads stay pinned to the account that started them.
56
56
 
57
57
  ```bash
58
58
  npm install -g @remodex/rmx # Node 18+; the Bun runtime is bundled automatically
59
- rmx start # or `rmx service` to run it in the background
59
+ rmx onboard # complete setup and open the phone pairing QR
60
60
  ```
61
61
 
62
- Open **http://localhost:10100** and configure everything in the web dashboard add providers
62
+ `rmx onboard` safely creates the Remodex config, preserves an existing provider such as `codex-lb`,
63
+ enables Android Remote, installs or repairs the OS background service, installs the Windows tray on
64
+ Windows, waits for a verified public tunnel, and opens a fresh QR code. It is safe to rerun.
65
+
66
+ Open **http://localhost:10100** to configure anything else in the web dashboard — add providers
63
67
  (40+ built-ins, or any OpenAI-compatible endpoint), pick models, manage accounts. `rmx gui`
64
68
  re-opens the dashboard at any time.
65
69
  It can also manage a **ChatGPT account pool** for Codex auth. Add multiple ChatGPT / Codex accounts,
@@ -75,11 +79,12 @@ once the others are drained.
75
79
 
76
80
  ```bash
77
81
  npm install -g @remodex/rmx
78
- rmx start # or `rmx service`
79
- rmx init # interactive setup: writes ~/.remodex/config.json and wires Codex
82
+ rmx onboard # idempotent config + Codex + service + Android Remote setup
80
83
  ```
81
84
 
82
- `rmx init` never starts the proxy; start it first (or after either order works, but headless
85
+ `rmx` with no arguments is shorthand for `rmx service`: it installs or refreshes the supervised
86
+ background service and starts the proxy. Use `rmx --help` for the command list. `rmx init` never
87
+ starts the proxy; start it first (or after — either order works, but headless
83
88
  commands like `rmx provider add` and `rmx combo set` talk to the **live** proxy and exit nonzero
84
89
  when it is unreachable). `rmx status` / `rmx doctor` / `rmx health` report the running state.
85
90
 
@@ -92,14 +97,19 @@ when it is unreachable). `rmx status` / `rmx doctor` / `rmx health` report the r
92
97
 
93
98
  | OS | Status | Service manager |
94
99
  |---|---|---|
95
- | macOS (arm64 / x64) | Fully supported | launchd |
96
- | Linux (x64 / arm64) | Fully supported | systemd (user unit) |
97
- | Windows (x64) | Fully supported | Task Scheduler (hidden) / opt-in native service (`--native`, WinSW) |
100
+ | macOS (arm64 / x64) | Fully supported | launchd (service + daily updater) |
101
+ | Linux (x64 / arm64) | Fully supported | systemd user units (service + daily updater) |
102
+ | Windows (x64) | Fully supported | Task Scheduler (service + hidden daily updater) / optional WinSW service (`--native`) |
98
103
 
99
104
  Requires [Node](https://nodejs.org) 18+. The Bun runtime is bundled on `npm install` — no separate
100
105
  Bun install needed, no WSL needed on Windows. If npm blocked the bundled runtime's install scripts,
101
106
  see the [installation docs](https://opencodex.me/getting-started/installation/).
102
107
 
108
+ Global installs enable unattended package updates by default after the first normal `rmx` bootstrap.
109
+ The updater runs once daily, verifies an exact registry version and integrity metadata, confirms
110
+ proxy health after restart, and records rollback information under `~/.remodex`. Use
111
+ `rmx system update auto off` to opt out, or `rmx system update auto status` to inspect it.
112
+
103
113
  ## Highlights
104
114
 
105
115
  - **Use any LLM with Codex, Claude Code, Claude Desktop, and Grok Build** — 40+ providers out of
@@ -204,6 +214,18 @@ loopback (`"hostname": "0.0.0.0"`) **requires** a bearer token — the proxy ref
204
214
  without `OPENCODEX_API_AUTH_TOKEN`, and every client request must carry it as
205
215
  `x-opencodex-api-key`. Details: [configuration reference](https://opencodex.me/reference/configuration/).
206
216
 
217
+ Android Remote offers two encrypted Cloudflare Tunnel choices:
218
+
219
+ | Choice | Best for | Tradeoff |
220
+ |---|---|---|
221
+ | **Quick Tunnel** (default) | Fast first-time or occasional use; no Cloudflare account, domain, or token | Temporary address changes after a tunnel restart; Cloudflare provides it for testing without an uptime SLA |
222
+ | **Use my domain** | Regular use with a stable address | Requires a Cloudflare domain and one-time authorization; setup takes longer |
223
+
224
+ Both choices still require an authorized Remodex phone. For a custom domain, keep the generated
225
+ random subdomain (for example, `rmx-072ab490ff.example.com`) to reduce casual discovery. The random
226
+ name is not the access control; phone authorization is. See Cloudflare's
227
+ [Quick Tunnel limitations](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/do-more-with-tunnels/trycloudflare/).
228
+
207
229
  ## Documentation
208
230
 
209
231
  The public docs — install, providers, routing, combos, sub-agents, sidecars, integrations, and
package/bin/ocx.mjs CHANGED
@@ -100,6 +100,11 @@ function hasLegacyRemodexEvidence(path) {
100
100
  ".opencodex-uninstall.json",
101
101
  "admin-api-token",
102
102
  "android-remote.json",
103
+ "auto-update.cmd",
104
+ "auto-update-task.xml",
105
+ "auto-update.json",
106
+ "auto-update.lock",
107
+ "auto-update.log",
103
108
  "catalog-backup.json",
104
109
  "codex-runtime.json",
105
110
  "codex-shim.json",
@@ -178,28 +183,64 @@ function runTrayLifecycle(launcher, action) {
178
183
  });
179
184
  }
180
185
 
181
- function runNpmSelfUpdate() {
186
+ function isSafePackageVersion(value) {
187
+ return typeof value === "string"
188
+ && value.length <= 64
189
+ && /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/.test(value);
190
+ }
191
+
192
+ function verifyExactNpmIntegrity(version) {
193
+ const metadata = npmInvocation(["view", `${PKG}@${version}`, "dist.integrity"]);
194
+ if (!metadata) return false;
195
+ const result = spawnSync(metadata.file, metadata.args, {
196
+ encoding: "utf8",
197
+ timeout: 12000,
198
+ windowsHide: true,
199
+ ...metadata.options,
200
+ });
201
+ if (result.status !== 0) return false;
202
+ return /sha512-[A-Za-z0-9+/=]+/.test(String(result.stdout ?? "").replace(/["']/g, ""));
203
+ }
204
+
205
+ function runNpmSelfUpdate(options = {}) {
206
+ const exactVersion = options.exactVersion;
207
+ if (exactVersion !== undefined && !isSafePackageVersion(exactVersion)) {
208
+ console.error("Remodex: the exact update target is not a valid package version.");
209
+ process.exit(1);
210
+ }
182
211
  const current = currentPackageVersion();
183
212
  const tag = updateTag(current);
184
- const latestInvocation = npmInvocation(["view", `${PKG}@${tag}`, "version"]);
185
- const installInvocation = npmInvocation(["install", "-g", `${PKG}@${tag}`]);
213
+ const latestInvocation = npmInvocation(
214
+ ["view", `${PKG}@${exactVersion ?? tag}`, "version"],
215
+ );
216
+ const installSpec = exactVersion ? `${PKG}@${exactVersion}` : `${PKG}@${tag}`;
217
+ const installInvocation = npmInvocation(
218
+ exactVersion ? ["install", "-g", installSpec] : ["install", "-g", `${PKG}@${tag}`],
219
+ );
186
220
  if (!latestInvocation || !installInvocation) {
187
221
  console.error("Remodex: could not resolve npm from a trusted absolute PATH entry; aborting before stopping the proxy.");
188
222
  process.exit(1);
189
223
  }
190
- const latestResult = spawnSync(latestInvocation.file, latestInvocation.args, {
191
- encoding: "utf8",
192
- timeout: 12000,
193
- windowsHide: true,
194
- ...latestInvocation.options,
195
- });
196
- const latest = latestResult.status === 0 ? latestResult.stdout.trim() : "";
224
+ let latest = exactVersion ?? "";
225
+ if (!exactVersion) {
226
+ const latestResult = spawnSync(latestInvocation.file, latestInvocation.args, {
227
+ encoding: "utf8",
228
+ timeout: 12000,
229
+ windowsHide: true,
230
+ ...latestInvocation.options,
231
+ });
232
+ latest = latestResult.status === 0 ? latestResult.stdout.trim() : "";
233
+ }
197
234
 
198
235
  console.log(`Remodex v${current} (installed via npm, tag ${tag})`);
199
236
  if (latest && latest === current) {
200
- console.log(`Already on the latest ${tag} version (v${latest}).`);
237
+ console.log(`Already on the ${exactVersion ? "requested" : `latest ${tag}`} version (v${latest}).`);
201
238
  process.exit(0);
202
239
  }
240
+ if (exactVersion && !verifyExactNpmIntegrity(exactVersion)) {
241
+ console.error("Remodex: exact update integrity metadata could not be verified; refusing package replacement.");
242
+ process.exit(1);
243
+ }
203
244
 
204
245
  const cachePreflight = runNpmCachePreflight();
205
246
  if (!cachePreflight.ok) {
@@ -328,7 +369,7 @@ function runNpmSelfUpdate() {
328
369
  }
329
370
  }
330
371
 
331
- console.log(`Updating${latest ? ` to v${latest}` : ""}...\n$ npm install -g ${PKG}@${tag}`);
372
+ console.log(`Updating${latest ? ` to v${latest}` : ""}...\n$ npm install -g ${installSpec}`);
332
373
  const res = spawnSync(installInvocation.file, installInvocation.args, {
333
374
  stdio: "inherit",
334
375
  timeout: 180000,
@@ -422,7 +463,7 @@ function runNpmSelfUpdate() {
422
463
  process.exit(0);
423
464
  }
424
465
  if (trayBeforeUpdate.restoreOnFailure) runTrayLifecycle(launcher, "start");
425
- console.error(`\nUpdate failed (npm exit ${res.status ?? "?"}). Try manually: npm install -g ${PKG}@${tag}`);
466
+ console.error(`\nUpdate failed (npm exit ${res.status ?? "?"}). Try manually: npm install -g ${installSpec}`);
426
467
  process.exit(1);
427
468
  }
428
469
 
@@ -506,12 +547,27 @@ if (updateHelpRequested) {
506
547
  process.exit(0);
507
548
  }
508
549
 
550
+ if (process.argv[2] === "__exact-update") {
551
+ const exactVersion = process.argv[3];
552
+ if (process.argv.length !== 4 || !isSafePackageVersion(exactVersion)) {
553
+ console.error("Remodex: invalid exact update target.");
554
+ process.exit(64);
555
+ }
556
+ runNpmSelfUpdate({ exactVersion });
557
+ }
558
+
509
559
  if (process.argv[2] === "update" && isNodeModulesInstall() && !isBunGlobalInstall()) {
510
560
  runNpmSelfUpdate();
511
561
  }
512
562
 
513
563
  const bunRuntime = resolveBun();
514
564
  const bun = bunRuntime.path;
565
+ // The installed npm command is intentionally useful with no arguments:
566
+ // `rmx` is the one-command bootstrap for the supervised backend. Keep explicit
567
+ // help/version/subcommands untouched; only an empty user argv becomes
568
+ // `rmx service` (which installs/refreshes and starts the background service).
569
+ const userCliArgs = process.argv.slice(2);
570
+ const forwardedCliArgs = userCliArgs.length === 0 ? ["service"] : userCliArgs;
515
571
 
516
572
  // Run the Bun child asynchronously and FORWARD termination signals to it, then wait
517
573
  // for its graceful shutdown before this launcher exits. The previous blocking
@@ -540,11 +596,14 @@ const launchContext = JSON.stringify({
540
596
  proof: launchProof,
541
597
  anthropicEnvSlots: preBunAnthropicSlots,
542
598
  });
543
- const child = spawn(bun, [cliPath, `${NODE_LAUNCH_PROOF_PREFIX}${launchProof}`, ...process.argv.slice(2)], {
599
+ const child = spawn(bun, [cliPath, `${NODE_LAUNCH_PROOF_PREFIX}${launchProof}`, ...forwardedCliArgs], {
544
600
  stdio: "inherit",
545
601
  env: {
546
602
  ...process.env,
547
603
  [NODE_LAUNCH_CONTEXT_ENV]: launchContext,
604
+ // The Bun child needs the stable Node executable when it creates a
605
+ // scheduler entry. `process.execPath` inside that child is Bun.
606
+ OCX_NODE_LAUNCHER_PATH: process.execPath,
548
607
  [BUN_RUNTIME_SOURCE_ENV]: bunRuntime.source,
549
608
  [BUN_RUNTIME_PATH_ENV]: bunRuntime.path,
550
609
  },