Signing in
Sign this machine in once, check the credential against Cloud, hand the session to a browser, and ask whether a person is there to answer.
Four of these five run without a network or with only Cloud: login, auth, whoami and clarify. They are the same implementation on every machine. open is an ordinary hosted command.
mr-data login [--device-name NAME] [--force] [--credential-store secure-file] [--json]mr-data login signs this machine in to the Mostly Right cloud. It prints a short code and an approval address on stderr and opens a browser. On approval it stores a durable device credential at ~/.mostlyright/credentials, mode 0600. It prints the mr_cli_ prefix and the last four characters, never the raw credential. Run it once per machine. Every other command resolves the stored credential for itself.
| Flag | Argument | Required | Meaning | Default |
|---|---|---|---|---|
--device-name |
NAME |
no | What to call this device in the cloud. | this machine’s hostname |
--force |
none | no | Compatibility alias for a safe auth rotate. It never truncates credentials in place. |
off |
--credential-store |
secure-file |
no | Use the secure-file credential store. | the platform’s native store, falling back to secure-file |
--json |
none | no | One JSON object, and no device code on stderr. | off |
With --json the command prints neither the device code nor the approval address.
{ "schema_version": "mr-data-login.v1", "status": "device_authenticated", "cloud_url": "https://mostlyright.md", "key_id": "zn0aFEzQjXzOH16F01VcWO4a5rg3NQTZ", "prefix": "mr_cli_", "last_four": "wunu", "device_name": "laptop", "created_at": "2026-09-12T09:00:00Z", "credentials_path": "/Users/you/.mostlyright/credentials"}Values are illustrative. status is device_authenticated on a first sign-in, rotated when --force replaced a key, and recovery_required when the old key could not be proved revoked. Run mr-data auth recover after that last one.
Errors: LOGIN_* and CLOUD_AUTH_* codes from the device flow, carried as error_code on the payload or as an error.
MOSTLYRIGHT_CLOUD_URL signs in against a different cloud. It must be an absolute https:// URL with no user information, no fragment and no trailing slash. login refuses LOGIN_CONFIG_INVALID for anything else, not THIN_CONFIG_INVALID, which the hosted commands raise. It accepts a path or a query.
mr-data login --device-name ci-runner-1mr-data whoami --jsonmr-data auth {status,devices,tokens,login,rotate,logout,recover} [...] [--json]auth inspects the effective Mostly Right device authority through Cloud. auth status validates the effective credential and reports stored metadata separately, so a stored file and a credential Cloud accepts stay two facts. Device inventory carries metadata only. Studio access tokens are ephemeral transport credentials rather than local ones, and auth tokens says so.
Run it after a command returns THIN_AUTHENTICATION_FAILED or THIN_AUTHORIZATION_DENIED to see whether the credential, organization role or workspace is missing.
| Subcommand | Argument | Meaning |
|---|---|---|
auth status |
none | Validate the effective device credential with Cloud. |
auth devices list |
none | List Cloud device-key metadata in Cloud order. |
auth devices revoke |
KEY_ID |
Revoke one Cloud device key, remote first, then clean up locally. |
auth tokens list |
none | Explain why ephemeral tokens cannot be listed. Always exits 2. |
auth tokens revoke |
none | Explain why ephemeral tokens cannot be revoked. Always exits 2. |
auth login |
none | Sign in with a device code. |
auth rotate |
none | Replace a device key safely. |
auth logout |
none | Revoke remotely, then remove stored authority. |
auth recover |
none | Resume or clear interrupted credential state. |
| Flag | Argument | Required | Meaning | Default |
|---|---|---|---|---|
--json |
none | no | One JSON object instead of human lines. Accepted on auth and on every subcommand. |
off |
--device-name |
NAME |
no | login and rotate: what to call this device (or the replacement) in Cloud. |
this machine’s hostname |
--credential-store |
secure-file |
no | status, login, rotate, logout, recover: use the secure-file fallback. |
native store |
--local-only |
none | no | logout: remove local state without claiming remote revocation. recover: clear only a stale login lease, leaving it uncertain whether the remote credential was minted. |
off |
{ "schema_version": "mr-data-auth-status.v1", "status": "authenticated", "effective_source": "stored_device", "remote_validation": "validated", "device": { "id": "zn0aFEzQjXzOH16F01VcWO4a5rg3NQTZ", "name": "laptop", "prefix": "mr_cli_", "last_four": "wunu", "created_at": "2026-08-19T20:25:49.647Z", "last_used_at": "2026-09-12T08:09:14.384Z" }, "identity": { "cloud": { "organization_id": "WQUz…", "user_id": "VQGT…", "role": "owner" }, "studio": { "workspace_id": "39ffafde-86ed-5d7b-b337-1c205ea79390", "principal_id": "8801ad48-…" } }, "stored_state": { "status": "available", "device": { "cloud_url": "https://mostlyright.md", "key_id": "zn0aFEzQjXzOH16F01VcWO4a5rg3NQTZ", "prefix": "mr_cli_", "last_four": "wunu", "device_name": "laptop", "created_at": "2026-08-19T20:25:49Z" } }}Values are illustrative. effective_source is stored_device or environment. On environment, auth status carries environment_notice, a sentence saying MOSTLYRIGHT_API_KEY is effective for this process and that mr-data cannot unset a parent-process credential. The shorter "environment_authority": "remains_effective" rides on the commands that change authority: logout, recover, devices revoke, rotate and login. stored_state.status is available, not_configured or unavailable. On unavailable it carries error_code.
Other schema versions on this command: mr-data-auth-devices.v1 (devices list), mr-data-auth-device-revoke.v1 (devices revoke), mr-data-auth-token-boundary.v1 (tokens), mr-data-auth-logout.v1 (logout), mr-data-auth-recovery.v1 (recover), mr-data-auth-rotate.v1 (rotate).
status decides the exit code. authenticated, not_authenticated, listed, logged_out, local_recovery_completed, recovered, device_authenticated, rotated and revoked exit 0. unsupported, not_implemented and refused exit 2. Anything else exits 1, including error and recovery_required.
Errors: CLOUD_AUTH_FORBIDDEN and CLOUD_AUTH_NOT_FOUND arrive as status: "refused" with error_code. Storage faults arrive as status: "error" with the LoginError/CredentialStoreError code. retry_after_seconds rides along when Cloud stamped one.
mr-data auth status --json | jq '{status, effective_source, workspace: .identity.studio.workspace_id}'whoami
Section titled “whoami”mr-data whoami [--credential-store secure-file] [--json]whoami is the compatibility alias for mr-data auth status. It validates the effective device credential with Cloud, reports stored metadata separately, and never prints a credential. Use it in a script that predates auth.
| Flag | Argument | Required | Meaning | Default |
|---|---|---|---|---|
--credential-store |
secure-file |
no | Use the secure-file fallback, for a first credential migration. | native store |
--json |
none | no | One JSON object instead of human lines. | off |
The payload is auth status’s, key for key, plus one member:
{ "compatibility_alias": "whoami" }Exit codes and errors are auth’s.
mr-data whoami --json | jq -r '.status'mr-data open TARGET [--open] [--json]open prints a one-time address that signs a browser in as you and lands on the page you named. It lasts about a minute and works once. Use it to hand a dataset page to the person watching. It also finishes work only a signed-in browser can do, such as publishing a dataset or a step-up verification. Anyone holding the address within that minute holds the session.
| Flag | Argument | Required | Meaning | Default |
|---|---|---|---|---|
TARGET |
path | yes | The dashboard page to land on, as a path beginning with a single /. At most 512 characters. |
none |
--open |
none | no | Hand the address to this computer’s browser instead of only printing it. | off |
--json |
none | no | One JSON object instead of human lines. | off |
{ "schema_version": "mostlyright-thin-client-v4-handoff.v1", "status": "handoff_minted", "lane": "hosted", "handoff_id": "5a9c0e17-8d46-42b3-b71e-0f2a6c8d4931", "redeem_url": "https://mostlyright.md/handoff/…", "expires_at": "2026-09-12T09:01:00Z", "target_path": "/datasets/d31017e0", "issuer": "cloud", "opened": false, "note": "This address works once and stops working at the moment above. …"}Values are illustrative. issuer is cloud or studio. Cloud and Studio mint identical-looking addresses but revoke different things, so issuer says which one issued the address.
Errors: THIN_REQUEST_INVALID for a target that does not begin with / or runs over 512 characters. THIN_RESPONSE_INVALID when the issued address is not an HTTPS URL. THIN_AUTHENTICATION_FAILED, THIN_AUTHORIZATION_DENIED, THIN_NOT_FOUND.
mr-data open "/datasets/$DATASET_ID" --openclarify
Section titled “clarify”mr-data clarify --question TEXT [--answer TEXT] [--answered-at MOMENT] [--dataset DATASET_ID] [--no-activity] [--recipe RECIPE_ID] [--attended | --unattended] [--json]clarify asks whether one clarifying question may be put to the user right now, and turns the answer into the block it is recorded as. The gate allows a question only for a fact nobody but the user has. What a word in their request means, how wide they need it, how far back. The gate opens only before a recipe is registered. After that the same answer is a revision.
By default, it does not contact an external service or ask the question itself. Without --answer it reports whether the question may be asked and exits 1 when it may not, so mr-data clarify --question '…' && ask runs the ask only when the gate opens. With --answer it formats an exchange that already happened and returns the record. It stores nothing. Naming --dataset is the exception: the question goes on that page, so somebody watching sees what the work waits on.
| Flag | Argument | Required | Meaning | Default |
|---|---|---|---|---|
--question |
TEXT |
yes | The one question, written as it would be put to the user. | none |
--answer |
TEXT |
no | What the user answered. Giving it composes the record instead of asking the gate. | none |
--answered-at |
MOMENT |
no | When they answered, as YYYY-MM-DDTHH:MM:SSZ. |
this machine’s clock |
--dataset |
DATASET_ID |
no | Put the question on this dataset’s page while the work waits. Best-effort, and it never fails the command. | none |
--no-activity |
none | no | Do not put the question on the page even when --dataset names one. |
off |
--recipe |
RECIPE_ID |
no | The registered recipe this is about. Naming one closes the gate. | none |
--attended |
none | no | State that a person is here to answer. | inferred |
--unattended |
none | no | State that nobody is here to answer. | inferred |
--json |
none | no | One JSON object instead of human lines. | off |
Without either flag, clarify reads an agent host that names itself in the environment (CLAUDECODE, CLAUDE_CODE_SESSION, CODEX_SESSION) as a conversation with somebody in it. CI or GITHUB_ACTIONS set beside it makes the session headless. A cron job carries none of these and stays unattended. A schedule cannot pass a flag, so an operator running mr-data on a timer outside CI exports MOSTLYRIGHT_ATTENDED=0. That veto beats an --attended written inside the process.
{ "status": "clarification_allowed", "question": "Which cities, and how far back?", "may_ask": true, "phase": "research", "attendance": { "attended": true, "decided_by": "agent_host", "reason": "…" }, "refused_because": null, "reason": null, "attendance_variable": "MOSTLYRIGHT_ATTENDED"}status is clarification_allowed or clarification_refused. phase is research before registration and contract_agreed once --recipe names one. refused_because is no_one_to_ask or contract_already_agreed. attendance.decided_by is one of flag, environment, agent_host, headless_host, terminal, default, unreadable_environment. When --dataset named a page and the report reached it, the payload also carries activity_reported.
{ "status": "clarification_prepared", "phase": "research", "attendance": { "attended": true, "decided_by": "flag", "reason": "…" }, "block": { "kind": "clarification", "question": "Which cities, and how far back?", "answer": "The twenty largest US metros, from 2020.", "answered_at": "2026-09-12T09:00:00Z" }, "carry_into": "This is the record of the exchange, not a copy of it that has been stored anywhere. …"}The block is the shape mr-data note --blocks-file accepts. Write the answer into the recipe document it settles as well, so it registers with the contract and reads on the dataset page.
Errors: THIN_CLARIFICATION_AFTER_CONTRACT when --answer arrives with --recipe, because that exchange is a revision. THIN_CLARIFICATION_TOO_LARGE when the question (200 characters) or the answer (1024 characters) runs over its ceiling. THIN_CLARIFICATION_CONTROL_CHARACTER for a control character other than tab and newline. The gate checks the question against its own ceiling before anybody is asked.
mr-data clarify --question "Which cities, and how far back?" --dataset "$DATASET_ID" \ && echo "ask the user" \ || echo "assume the default and say so in the recipe description"See also
Section titled “See also”- CLI reference: conventions, exit codes, environment variables
- Datasets: the page
openandclarify --datasetpoint at