# Signing in

Source: https://mostlyright.md/docs/reference/cli/auth/

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.

## login

```bash
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.

```json title="mr-data login --json"
{
  "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.

```bash title="sign in and check it took"
mr-data login --device-name ci-runner-1
mr-data whoami --json
```

## auth

```bash
mr-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 |

```json title="mr-data auth status --json"
{
  "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.

```bash title="is this machine signed in, and as whom"
mr-data auth status --json | jq '{status, effective_source, workspace: .identity.studio.workspace_id}'
```

## whoami

```bash
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:

```json title="mr-data whoami --json (extra member only)"
{ "compatibility_alias": "whoami" }
```

Exit codes and errors are `auth`'s.

```bash
mr-data whoami --json | jq -r '.status'
```

## open

```bash
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 |

```json title="mr-data open /datasets/d31017e0 --json"
{
  "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`.

```bash title="hand the dataset page to the person watching"
mr-data open "/datasets/$DATASET_ID" --open
```

## clarify

```bash
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.

```json title="the gate, --json"
{
  "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`.

```json title="the record, --json"
{
  "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.

```bash title="ask only if there is somebody to ask"
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

- [CLI reference](/docs/reference/cli/): conventions, exit codes, environment variables
- [Datasets](/docs/reference/cli/dataset/): the page `open` and `clarify --dataset` point at
