@remodex/rmx 1.0.2 → 1.0.3

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 # install/update + start the background service
15
15
  ```
16
16
 
17
17
  <table align="center">
@@ -56,7 +56,7 @@ 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 # install/update + start it in the background
60
60
  ```
61
61
 
62
62
  Open **http://localhost:10100** and configure everything in the web dashboard — add providers
@@ -75,11 +75,13 @@ once the others are drained.
75
75
 
76
76
  ```bash
77
77
  npm install -g @remodex/rmx
78
- rmx start # or `rmx service`
78
+ rmx # install/update + start the background service
79
79
  rmx init # interactive setup: writes ~/.remodex/config.json and wires Codex
80
80
  ```
81
81
 
82
- `rmx init` never starts the proxy; start it first (or after either order works, but headless
82
+ `rmx` with no arguments is shorthand for `rmx service`: it installs or refreshes the supervised
83
+ background service and starts the proxy. Use `rmx --help` for the command list. `rmx init` never
84
+ starts the proxy; start it first (or after — either order works, but headless
83
85
  commands like `rmx provider add` and `rmx combo set` talk to the **live** proxy and exit nonzero
84
86
  when it is unreachable). `rmx status` / `rmx doctor` / `rmx health` report the running state.
85
87
 
@@ -92,14 +94,19 @@ when it is unreachable). `rmx status` / `rmx doctor` / `rmx health` report the r
92
94
 
93
95
  | OS | Status | Service manager |
94
96
  |---|---|---|
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) |
97
+ | macOS (arm64 / x64) | Fully supported | launchd (service + daily updater) |
98
+ | Linux (x64 / arm64) | Fully supported | systemd user units (service + daily updater) |
99
+ | Windows (x64) | Fully supported | Task Scheduler (service + hidden daily updater) / optional WinSW service (`--native`) |
98
100
 
99
101
  Requires [Node](https://nodejs.org) 18+. The Bun runtime is bundled on `npm install` — no separate
100
102
  Bun install needed, no WSL needed on Windows. If npm blocked the bundled runtime's install scripts,
101
103
  see the [installation docs](https://opencodex.me/getting-started/installation/).
102
104
 
105
+ Global installs enable unattended package updates by default after the first normal `rmx` bootstrap.
106
+ The updater runs once daily, verifies an exact registry version and integrity metadata, confirms
107
+ proxy health after restart, and records rollback information under `~/.remodex`. Use
108
+ `rmx system update auto off` to opt out, or `rmx system update auto status` to inspect it.
109
+
103
110
  ## Highlights
104
111
 
105
112
  - **Use any LLM with Codex, Claude Code, Claude Desktop, and Grok Build** — 40+ providers out of
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
  },