m9r-cli
v0.6.16
Published
M9R CLI — connect coding agents, report live presence, coordinate bounded assignments, and record human-approved evidence.
Maintainers
Readme
M9R CLI
Connect a workspace to M9R, pull dashboard instructions, fetch evidence-backed workspace rules, and submit an approved/redacted coding-agent session so M9R can evaluate Rule Health.
The CLI is a thin client over the M9R Agent Join API. It is human-owned and
agent-operated: it never connects without human approval and never submits a
session without an explicit --approved flag.
Quick start
No install required — run it straight from npm with npx. init is a one-time
setup step, so there are two paths:
First-time setup (no .m9r/local.json yet):
npx m9r-cli init # connect this workspace (human approves in browser)Every agent run (already connected — don't re-run init):
npx m9r-cli doctor # check local setup + API reachability
npx m9r-cli run start --task "Fix the build" # makes the run visible in the dashboard
npx --yes m9r-cli@latest inbox # pull instructions with the CLI
npx m9r-cli rules # fetch active workspace rules
# ...do the work, reporting phases as you go...
npx m9r-cli run status --phase "editing files"
npx m9r-cli run status --phase "waiting for human approval"
npx m9r-cli submit-session session.md --approved # submit an approved, redacted session
npx m9r-cli submit-session session.md --approved --evidence-contract evidence.json
npx m9r-cli helprun start / run status make the agent visible in the user's M9R
dashboard. They send status telemetry only (phase, rules-loaded count) — never
source code, tokens, or secrets. The active run id is stored in
.m9r/run.json; inbox reads the Agent inbox, rules reports
rules_loaded_count, and submit-session links the session to the run and marks
it completed.
If a workspace is already connected, npx m9r-cli init won't create a new claim —
it tells you the workspace is connected and points you to doctor/rules. Use
npx m9r-cli init --force only to deliberately start a brand-new connection.
To revoke this workspace's local agent connection and remove transient local state:
npx --yes m9r-cli@latest disconnectOn Windows, a successful m9r-cli connect also starts the loopback web broker
and installs a current-user login task. The browser extension reports ready
only after the broker handshake. The broker listens only on 127.0.0.1.
Requires Node.js >= 18.
Commands
m9r heartbeat reports one authenticated linked-agent presence lease.
m9r assignments lists bounded assignments for the connected agent.
m9r assignment accept <id> or reject <id> records the agent's explicit decision.
m9r assignment complete <id> --run <run-id> --evidence-record <evidence-id> completes only with a run and evidence record owned by that connection.
init
One-time setup. Registers this workspace and prints a claim URL. A human repo
owner must approve the connection in the browser — the CLI opens the URL for you
(best-effort) and then waits, polling until approval. On approval it writes a
scoped token to .m9r/local.json and non-secret metadata to
.m9r/config.json.
If .m9r/local.json already has a token, init treats the workspace as
already connected: it does not create a new claim, never prints the token, and
points you to doctor/rules instead. A returning workspace should run doctor,
then inbox if the dashboard sent an instruction, then rules — not init.
npx m9r-cli init # first-time setup
npx m9r-cli init --force # force a new claim even if connected
npx m9r-cli init --repo my-service --agent-kind codex # connect Codex explicitly
npx m9r-cli init --repo my-service --agent-kind claude-code # connect Claude Code explicitlyIf the claim is rejected, expires, or times out, init exits non-zero — just run
it again.
After approval, init automatically installs the automatic M9R workflow
into the repo instruction file the connected agent already reads (see
bootstrap). Skip that with --skip-bootstrap.
npx --yes m9r-cli@latest init --force --agent-kind codex # connect Codex
npx --yes m9r-cli@latest init --force --agent-kind claude-code # connect Claude Codebootstrap
Installs a managed instruction block so the connected agent uses M9R during
normal repo tasks — you don't have to repeat setup instructions in every
prompt. The block tells the agent to verify the connection (doctor), load
rules, start or attach to a controlled run, check the inbox, prepare a redacted
M9R Evidence Draft, and set the run to waiting for human review. The
draft includes the actual verification commands and results, changed files,
limitations, and a sensitive-data check. It never contains tokens or local config.
The approved connection's stored agent kind is authoritative — a conflicting
--agent-kind fails instead of changing attribution.
| Agent kind | Integration file | Automatic loading |
| --- | --- | --- |
| codex | AGENTS.md | yes |
| claude-code | CLAUDE.md | yes |
| grok-build | AGENTS.md | yes |
| other | M9R.md | not assured — manual integration |
npx --yes m9r-cli@latest bootstrap --agent-kind codex # manual install/update
npx --yes m9r-cli@latest bootstrap status # installed / missing / outdated
npx --yes m9r-cli@latest bootstrap remove # remove only the managed blockbootstrap is idempotent: it updates its own managed block in place, never
duplicates it, and preserves user-authored content around it byte-for-byte.
For every governed task, the agent displays the structured evidence draft in its
final response automatically. A human reviews and approves that exact draft.
Only after approval may the agent create an untracked temporary file and run
submit-session <file> --approved; M9R records the materially matching
draft at that point. The agent cannot approve or record its own draft.
Honest limitation: M9R does not intercept arbitrary external agent sessions. Automatic behavior depends on the supported agent reading the installed repo instructions (or a future runtime/MCP integration). Roadmap, not implemented today: Phase 2 — M9R MCP tools for rules, run start, inbox, evidence preparation, and status; Phase 3 — a deterministic agent launch wrapper/runtime.
doctor
Checks local setup and API reachability: confirms .m9r/local.json and a
token are present, prints the API base, and verifies /api/agent/rules is
reachable and authorized. Exits non-zero if any check fails.
The browser multiplayer workspace is the supported public path. doctor,
rules, inbox, and run start do not promise a production terminal or
Mosaic-style local session persistence. The local runtime commands remain
available for explicit experiments and have separate release gates.
npx m9r-cli doctorservice
Manages the experimental persistent login-startup registration for the local M9R Runtime (Windows only today; macOS/Linux are not built yet). This command is not part of the first public multiplayer capability promise and should not be presented as a production terminal feature.
npx m9r-cli service install # register M9R Runtime to start hidden at login (HKCU\...\Run\M9RRuntime)
npx m9r-cli service status # check whether it's currently registered
npx m9r-cli service uninstall # remove itThe runtime startup registration is retained for compatibility and explicit testing. It is not required for browser multiplayer and remains outside the initial public open-core announcement.
disconnect
Revokes the current Bearer-token connection when the server can authenticate the
saved local token, then removes local volatile files:
.m9r/local.json, .m9r/run.json, and .m9r/rules.json.
If there is no local token, it exits non-zero with:
No local M9R connection found.The command never prints the token. It reports server and local cleanup separately:
server disconnected: yes
local token removed: yes
run cache removed: yes
rules cache removed: yesIf server revocation fails, it prints server disconnected: no plus a redacted
error message. It does not claim server disconnection unless the server call
succeeds.
npx --yes m9r-cli@latest disconnectrun start / run status
Make the agent visible in the user's M9R dashboard. run start begins a run
(optionally --task "title") and stores the active run id in .m9r/run.json.
run status --phase "..." reports the current phase. Both require a local token;
run status requires an active run. Telemetry only — never source or secrets.
npx m9r-cli run start --task "Fix the build"
npx m9r-cli run status --phase "reading files"
npx m9r-cli run status --phase "editing files"
npx m9r-cli run status --phase "waiting for human approval"
npx m9r-cli run status --phase "completed"inbox
Pull instructions with the CLI. The dashboard instruction channel queues short instructions for the connected agent, and the agent retrieves them from its Agent inbox.
npx --yes m9r-cli@latest inboxrules
Fetches the active workspace rules using the saved token, prints the mode,
operating instructions, and rule titles, and saves the full response to
.m9r/rules.json. Run this before submit-session so the loaded rules
can be sent for evaluation. When a run is active, rules also reports
rules_loaded_count to the dashboard.
Rules generated from a session start as recommended and only become active — and thus returned here — after a human promotes them in the dashboard. M9R never auto-promotes. Rule Health is evaluated only when a later run loads an active rule and submits evidence.
npx m9r-cli rulescompare / proof
Prints a conservative two-run report (Run A vs Run B). Evidence, not vibes:
- Token/cost is compared only when both runs recorded usage metadata — otherwise it prints "Usage comparison unavailable…".
- Output quality is reported only from objective signals (build result, test result, lint result, acceptance criteria, or human approval) — otherwise it prints that those signals are required, plus a short "How to make this measurable" hint (include build/lint/test results and acceptance criteria in the redacted session; note whether the human accepted the final diff; never include secrets or full source code).
- Rule Health says whether a loaded rule held, was violated, or could not be judged — never that a rule "worked". No token/cost/quality gain is fabricated.
npx m9r-cli compare --baseline-run <run-a-id> --later-run <run-b-id>submit-session <file> --approved
Submits an approved, human-redacted session for Rule Health evaluation. The file
format is inferred from its extension: .md → markdown export, .json → JSON,
.jsonl → JSONL, .txt/.log → text log.
npx m9r-cli submit-session oathlock-session.md --approvedPass --evidence-contract <json> to record a validated
m9r.evidence.v1 contract with the approved session. When the server
stores it, the CLI prints the evidence contract id required by
assignment complete --evidence-record.
submit-session refuses to send anything unless you pass --approved — the
gate trips before the file is even read. The session is submitted as
human_reviewed / human_approved_submission: true. It also sends the rules
currently loaded in .m9r/rules.json (printed only as a count, never their
contents) so the server can score them.
Configuration
| Env var | Default | Notes |
| --------------------- | ----------------------------- | ------------------------------------------- |
| M9R_API_URL | https://m9r.dev | Use http://localhost:3000 for local dev |
| M9R_LOCAL_ONLY | unset | Internal runtime flag; prefer m9r-cli terminal runtime --local-only |
| M9R_AGENT_KIND | none | Fallback when --agent-kind is omitted |
Agent kind is recorded on the approved connection and is used consistently in the Run Passport, Run Ledger, Approval Center, evidence attribution, and disconnect/revoke identity. Use --agent-kind for a new or forced claim; existing connections keep their historical identity.
| M9R_REPO_HINT | current directory name | Override the repo hint sent on init |
M9R_API_URL controls which M9R instance every command talks to — set it
once in your shell to point the whole CLI at local dev or a staging deployment.
Local files (created in your current workspace)
The CLI writes to a .m9r/ directory in the workspace you run it from.
For one compatibility release, startup reads legacy OATHLOCK_* environment
variables when the matching M9R_* variable is unset, and copies files from an
existing .oathlock/ directory into .m9r/ without overwriting or deleting the
old files. New state and documented configuration use only the M9R names:
| File | Contents | Commit? |
| ----------------------- | --------------------------------------- | --------------------- |
| .m9r/local.json | Secret scoped token + scopes | No — gitignore it |
| .m9r/rules.json | Last fetched rules response (transient) | No — gitignore it |
| .m9r/run.json | Active run id (transient telemetry) | No — gitignore it |
| .m9r/config.json | Non-secret workspace metadata | Optional |
The simplest safe choice is to ignore the whole directory. Add to your
.gitignore:
.m9r/Token safety
- The scoped token is written once to
.m9r/local.json(gitignore it). - After saving, the full token is never printed — only a masked preview like
m9r_…a1b2. - Any token value is redacted from error output before it is shown.
- Session and rule contents are never printed;
submit-sessionreports only a count of loaded rules.
Human approval requirement
M9R is human-owned by design, and the CLI enforces that at two points:
- Connecting —
initcannot complete until a human repo owner approves the claim in the browser. The CLI only polls; it cannot self-approve. - Submitting —
submit-sessionrefuses to send a session unless a human passes--approvedafter reviewing and redacting it.
Rule Health
When you submit a session, M9R evaluates your workspace rules against what the
session actually shows — observed evidence is the source of truth, not the
agent's self-report (the CLI sends empty rules_followed/rules_violated). The
result groups each rule into buckets such as followed, violated,
needs_review, not_applicable, too_vague, and obsolete, and
submit-session prints the non-empty buckets plus per-rule reasons. This is how
M9R tells you which rules are working, which are being ignored, and which have
gone stale.
Publishing (maintainers)
m9r-cli is a live, published npm package — npm view m9r-cli version returns
the current release. This is the process for shipping a new version, not the
first one.
Dry run
Verify exactly what would be published without sending anything to the registry.
The prepack hook rebuilds dist/ first, so the tarball always reflects current
source:
cd cli
npm publish --dry-runConfirm the file list contains only dist/ and README.md (no .m9r/,
src/, scripts/, or secrets).
Before publishing a new version
- [ ] Bump
versionincli/package.json(semver). - [ ]
npm run lint,npm run build, andnpm testall pass from the repo root. - [ ]
cd cli && npm publish --dry-runshows onlydist/andREADME.md. - [ ] Confirm the
licensefield is what you intend to publish under. - [ ]
npm whoamiis the intended publisher. - [ ] Publish:
cd cli && npm publish.
Resident provider process (experimental)
Run a connected provider as a foreground M9R resident:
m9r resident configure --provider codex --binding-id <binding-id> --profile codex-reviewer
m9r resident run --config .m9r/resident.local.jsonUse --once for one registration, heartbeat, and queue poll. The ignored
resident.local.json file contains the local M9R bearer token, provider,
opaque repository binding id, and absolute local repository root. The token and
absolute root remain local; never commit or share this file. Codex and Claude
Code authentication stays in their own local credential store or environment.
For a real long-running setup, generate a non-secret supervisor contract and install it through your operating system's own service manager:
oathlock resident service-plan --profile codex-reviewerThe plan deliberately does not install or start a background process itself.
It specifies an on-failure restart policy and treats a resident as stale
after 90 seconds without a heartbeat. M9R only shows a resident as live
after its retained heartbeat; it never infers that a configured process is
awake.
Local OpenCode sessions and Claude resume
The OpenCode adapter connects only to a loopback opencode serve instance.
List sessions with their folders, then target one explicitly:
m9r-cli opencode sessions
m9r-cli opencode send --folder "C:\\work\\project" --session <session-id> --text "Continue the review"Set M9R_OPENCODE_URL (default http://127.0.0.1:4096) and, if the local
server requires authentication, M9R_OPENCODE_USERNAME and
M9R_OPENCODE_PASSWORD. m9r launch claude --resume <session-id> resumes a
validated Claude Code session ID; the ID is passed as a separate argument.
