@one2x/merouter
v0.4.1
Published
Unified CLI for managing and routing across multiple AI model providers.
Readme
@one2x/merouter
Unified CLI for managing and routing across multiple AI model providers, shipped as an npm-installable wrapper around a native Rust binary.
npm install -g @one2x/merouter
merouter --helpWorks on macOS (x64, arm64), Linux (x64, arm64), and Windows x64. npm/pnpm/yarn/bun all supported.
How it works
The installed merouter command is a thin Node.js shim. On first invocation, it:
- Detects your OS + architecture.
- Locates the native Rust binary that npm already fetched as part of the install (via
optionalDependencies). - Spawns the binary with your argv, forwards signals, and mirrors the exit code.
There is no post-install download step — binaries are regular npm payloads, so npm install --ignore-scripts, offline installs, and air-gapped CI all work.
Publishing model
All artifacts ship under the single npm name @one2x/merouter, distinguished by a platform-tagged version:
| Version | Contents |
| -------------------- | ------------------------------------------------------------------------ |
| 0.1.2 | JS shim + optionalDependencies pointing at the platform versions below |
| 0.1.2-linux-x64 | just vendor/x86_64-unknown-linux-gnu/merouter/merouter |
| 0.1.2-linux-arm64 | vendor/aarch64-unknown-linux-gnu/... |
| 0.1.2-darwin-x64 | vendor/x86_64-apple-darwin/... |
| 0.1.2-darwin-arm64 | vendor/aarch64-apple-darwin/... |
| 0.1.2-win32-x64 | vendor/x86_64-pc-windows-msvc/merouter/merouter.exe |
The main package's optionalDependencies use npm's npm: alias syntax to reference each platform version while exposing a stable virtual package name:
"optionalDependencies": {
"@one2x/merouter-linux-x64": "npm:@one2x/[email protected]",
"@one2x/merouter-linux-arm64": "npm:@one2x/[email protected]",
"@one2x/merouter-darwin-x64": "npm:@one2x/[email protected]",
"@one2x/merouter-darwin-arm64":"npm:@one2x/[email protected]",
"@one2x/merouter-win32-x64": "npm:@one2x/[email protected]"
}Each platform version sets os and cpu in its manifest, so npm skips the non-matching ones automatically (standard optionalDependencies behavior). The shim resolves the right one via require.resolve('@one2x/merouter-linux-x64/package.json').
Platform tag → dist-tag
Each platform version is published under a matching dist-tag (e.g. linux-x64, darwin-arm64) so it doesn't overwrite latest. Only the main package gets latest (or next for pre-releases).
Layout
packages/merouter/
├── src/
│ ├── bin.ts # runtime shim entry (TypeScript source)
│ ├── binary-resolver.ts # resolves the native binary path
│ ├── package-manager.ts # detects bun vs npm for hints / env
│ ├── spawn-child.ts # spawn + signal forwarding + exit handling
│ └── utils/
│ └── platforms.ts # single source of truth for target triples
├── bin/ # compiled JS (generated by `pnpm build`, gitignored)
│ └── bin.js
├── scripts/
│ └── stage/ # publish-time stager (runs via tsx)
│ ├── main.ts # entry
│ ├── args.ts # node:util parseArgs wrapper
│ ├── fs-utils.ts # ensureEmptyDir, unpackArchive
│ ├── npm-pack.ts # npm pack wrapper
│ ├── stage-platform.ts # stages one platform tarball
│ └── stage-main.ts # stages the main (shim) tarball
├── package.json # source template (version = release version)
├── tsconfig.json
├── project.json # Nx config
└── README.mdDevelopment
Dependencies
pnpm installTypecheck + build the shim
pnpm --filter @one2x/merouter build
# or via Nx
nx run merouter:buildLocal smoke-test of the shim
After the Rust CLI is built, place the binary where the shim's dev fallback expects it:
# From repo root:
nx run cli:build
mkdir -p packages/merouter/vendor/x86_64-unknown-linux-gnu/merouter
cp cli/target/release/merouter-cli \
packages/merouter/vendor/x86_64-unknown-linux-gnu/merouter/merouter
chmod +x packages/merouter/vendor/x86_64-unknown-linux-gnu/merouter/merouter
# Run the shim directly:
pnpm --filter @one2x/merouter build
node packages/merouter/bin/bin.js --help(Adjust the target triple to your host OS/arch.)
Maintainer: cutting a release
From the repo root:
pnpm release vX.Y.Z
# or directly:
pnpm tsx scripts/publish.ts vX.Y.ZThe script (scripts/publish.ts):
- Preflight — refuses to run unless:
- on
mainbranch (override:--skip-branch-check) - working tree clean (override:
--allow-dirty) - tag
vX.Y.Zdoes not already exist - requested version > current
cli/Cargo.tomlversion cli/Cargo.tomlandpackages/merouter/package.jsonversions are already in sync
- on
- Bumps the version in:
cli/Cargo.toml([package] version)cli/Cargo.lock(viacargo update -p merouter-cli --precise)packages/merouter/package.json(version+ all 5npm:aliases underoptionalDependencies)
- Commits all three files as
chore(release): merouter vX.Y.Z - Tags
vX.Y.Z(annotated) - Does NOT push — review the commit first, then:
git push origin main --follow-tagsPushing the tag triggers .github/workflows/cli-release.yml, which:
- Builds Rust binaries for all 5 targets
- Creates a GitHub Release with the tarballs
- Builds the TS shim
- Stages 6 npm tarballs via
packages/merouter/scripts/stage/main.ts - Publishes each to npm with OIDC + the correct
--tagper platform
Dry run
To preview without touching git or files:
pnpm tsx scripts/publish.ts vX.Y.Z --dry-run --allow-dirtynpm trusted publishing (one-time setup)
This repo uses OIDC-based trusted publishing (no NPM_TOKEN secret). To configure:
- Sign in to npmjs.com as an
@one2xorg owner. - For the
@one2x/merouterpackage: Settings → Trusted Publishers → Add publisher with:- Provider: GitHub Actions
- Organization:
one2x - Repository:
merouter - Workflow filename:
cli-release.yml - Environment: (leave blank)
- Initial publish (for any package that doesn't yet exist on npm) must be done with
npm publish --access publicfrom a logged-in session; OIDC can only be configured on an existing package.
Version bump mechanics
Steps 1-2 above are fully automated by scripts/publish.ts. The 5
optionalDependencies entries, the main version field, and
Cargo.toml + Cargo.lock are all kept in sync atomically in one commit.
