usemilo
v0.3.2
Published
One-command onboarding for Milo — point your coding harness (pi, Codex, Claude Code) at the Milo routing gateway and send model: "milo/auto".
Downloads
927
Maintainers
Readme
usemilo
One-command onboarding for Milo: point a coding harness at
the Milo routing gateway and send milo/auto.
export OPENROUTER_API_KEY=sk-or-...
npx usemilo on codex # or: pi, claude-code (THIS PROJECT only)
# ... work in this directory ...
npx usemilo off codex # restore this project's previous confignpm i -g usemilo gives a plain milo command with the same subcommands. Full
usage, the supported harnesses, and the two simplicity tiers are in
docs/onboarding.md.
Commands
milo on <harness>— route this harness through Milo for the current project (the default; see scoping below).milo on --global <harness>— route it machine-wide (the old behavior), explicitly opted into.milo off <harness>/milo off --global <harness>— undo either scope.milo status [harness]— report, per harness, whether Milo is on for this project and globally, and which local scoping each harness supports.milo run <harness> [-- <args...>]— launch the harness once with Milo routing injected for that child process only, then exit with its code. It persists nothing and restores nothing, so it always works — even for a harness that can't be scoped locally. Everything after--is passed through verbatim, e.g.milo run codex -- exec "say ok".
Local by default, --global to opt in
milo on used to rewrite a harness's global config, so every invocation of
that harness anywhere on the machine got routed. Now milo on is
project-local by default: it only affects the current directory, and
--global is the explicit way to switch the whole machine.
Local scoping uses a config the harness genuinely reads ahead of its global one (no faking a file the harness would ignore):
| Harness | Local scope (milo on) | Mechanism |
|---|---|---|
| codex | ./.codex/config.toml | Codex reads a project ./.codex/config.toml over ~/.codex/config.toml. |
| claude-code | ./.claude/settings.local.json | Claude Code merges the project's personal, git-ignored settings.local.json over ~/.claude/settings.json. |
| pi | (not supported) | pi has no cwd-relative config; milo on pi says so and points at milo run pi or --global. Use milo run pi for a one-off local session (it points $PI_CODING_AGENT_DIR at a throwaway config), or milo on --global pi to switch globally. |
A local switch is recorded in ./.milo/state.json so off/status know what
this project turned on. A prompted OpenRouter key is always stored in the
global credential store (or --dir), never in the project tree, so a local
switch never drops a secret into a directory that might be committed.
Why this package exists
The CLI lives in the internal workspace package @milo/cli (which also carries
milo serve, milo roster, milo billing, and the rest). usemilo is the
publishable face of it: the package name people type (npx usemilo) with a
bin named milo. Keeping the two separate means the internal package stays
@milo/cli (unchanged, private) while the public one is usemilo.
How it is published
bin/milo.mjs is a thin wrapper that calls the lean onboarding entry,
@milo/cli/onboarding (runOnboardingCli). That entry registers only
on/off/status/run and imports only the pure onboarding modules —
crucially
not serve/roster/billing, which would drag in @milo/core →
@libsql/client → a native/CJS dep that cannot be inlined into a single ESM
file. Because the onboarding graph is pure, it bundles into one self-contained
dist/cli.js with no workspace:* deps that runs with zero install:
pnpm --filter usemilo run bundle # writes dist/cli.js (single shebang, self-contained)Then, to publish (we do not publish here):
- flip
"private": falseinpackage.json; - point
"bin": { "milo": "./dist/cli.js" }at the bundled file; - drop the
@milo/clidependency (it is inlined); npm publish.
The bundle step (scripts/bundle.mjs) locates esbuild robustly — $MILO_ESBUILD,
then PATH, then ~/.local/bin/esbuild, then the repo's node_modules/.bin —
so it does not depend on npx fetching anything, no bundler is added to the
repo's dependencies, and nothing about pnpm build/CI changes. esbuild
preserves the single #!/usr/bin/env node from bin/milo.mjs, so the bundle
adds no banner and the output has exactly one shebang.
milo --version prints the published version. The onboarding entry keeps its
ONBOARDING_CLI_VERSION in step with this package's version.
