@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 +32 -10
- package/bin/ocx.mjs +73 -14
- package/gui/dist/assets/index-1SDbgh2-.css +1 -0
- package/gui/dist/assets/{index-CkETtt7P.js → index-D3MaPif3.js} +13 -13
- package/gui/dist/index.html +2 -2
- package/package.json +5 -5
- package/src/android-remote/cloudflare-tunnel.ts +30 -8
- package/src/cli/help.ts +28 -6
- package/src/cli/index.ts +47 -74
- package/src/cli/init.ts +7 -1
- package/src/cli/internal-dispatch.ts +0 -6
- package/src/cli/onboard.ts +599 -0
- package/src/cli/system-command.ts +37 -84
- package/src/codex/inject.ts +8 -1
- package/src/codex/sync.ts +3 -0
- package/src/lib/config-ownership.ts +5 -0
- package/src/lib/remodex-home.ts +5 -0
- package/src/server/management/android-remote-routes.ts +1 -1
- package/src/server/management/config-routes.ts +0 -80
- package/src/server/management/context.ts +0 -4
- package/src/tray/windows-tray.ps1 +109 -66
- package/src/tray/windows.ts +7 -1
- package/src/update/auto-scheduler.ts +1671 -0
- package/src/update/job.ts +72 -13
- package/src/update/notify.ts +2 -2
- package/gui/dist/assets/index-CZqebSPQ.css +0 -1
- package/src/update/desktop-release.ts +0 -1620
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 & Grok Build</b><br>
|
|
3
|
-
|
|
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
|
|
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
|
|
59
|
+
rmx onboard # complete setup and open the phone pairing QR
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
-
|
|
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
|
|
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
|
|
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
|
|
97
|
-
| Windows (x64) | Fully supported | Task Scheduler (hidden) /
|
|
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
|
|
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(
|
|
185
|
-
|
|
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
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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 ${
|
|
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 ${
|
|
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}`, ...
|
|
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
|
},
|