# Using the CLI > Install, authenticate, and script the myna CLI, including project linking, configuration resolution, and the JSON output contract. The `myna` CLI is a full client for the Myna management API: schema push/pull, entries, assets, [change sets](/concepts/change-sets), previews, and admin operations. It has two output personalities — human-readable tables by default, and a strict machine-readable mode behind `--json` — so the same binary serves interactive use, CI, and agent tooling. This page covers setup and behavior; the complete command list lives in the [command reference](/cli/commands). ## Install ```bash $ npm install --global @myna-sh/cli $ myna --version ``` Or run it without installing: ```bash $ npx @myna-sh/cli --help ``` ## Log in ```bash $ myna login ``` `myna login` uses the browser device-authorization flow: the CLI prints a verification URL (`app.myna.sh/device`) and a one-time user code, opens the browser when running interactively, and polls until you approve. See [authentication](/getting-started/authentication) for how device flow relates to API keys. The resulting credential is stored per API URL: - **macOS** — the Keychain, under the service `sh.myna.cli`. - **Other platforms** (or if the Keychain write fails) — `~/.config/myna/credentials.json` with `0600` permissions. `myna logout` removes the stored credential for the current API URL; `myna whoami` validates it against the API and shows the identity. For automation, `myna login start` and `myna login poll` split the flow so nothing blocks on a human, and `myna auth verify --json` answers "does this credential work?" without throwing — see [authentication](/getting-started/authentication). ## Diagnose problems `myna doctor` checks the whole chain in one pass: CLI version, API reachability and compatibility, credential validity, key scopes, project access, schema drift, generated-type freshness, and optionally a browser origin. ```bash $ myna doctor --origin http://localhost:5173 ✓ CLI version: 0.3.0 is current. ✓ API reachable: https://api.myna.sh speaks API 2026-07-24. ✓ Client compatibility: CLI 0.3.0 meets the minimum supported client 0.2.0. ✓ Credential: Valid — API key "site-agent" (4 scope(s)). ✓ Project: my-site. ! Local schema: 2 undeployed change(s) (additive). → myna schema diff, then myna schema push ✗ Browser origin: http://localhost:5173 is not allowed on my-site. A browser will discard responses from it. → myna projects update --origins https://my-site.com,http://localhost:5173 ``` A check that cannot run reports `skip` with a reason; the remaining checks still run. When the CLI version check reports you are behind, `myna update` performs the upgrade instead of leaving you the command: ```bash $ myna update Updating 0.9.0 → 0.10.0 with: pnpm add -g @myna-sh/cli@0.10.0 Updated to 0.10.0. Run `myna doctor` to confirm the API agrees. ``` It resolves the latest release from npm, and runs the install with the package manager it detects from where the binary lives — printing the command first, so a wrong guess is visible rather than silently installing a second copy. `--check` reports without installing and exits 1 when an update exists, which makes it a CI gate. Running from a source checkout does nothing, because there is no release to move to. It updates the CLI only. `@myna-sh/sdk` and `@myna-sh/react` ship in lockstep with it but belong to your project's manifest, so your package manager owns those. `--json` emits `{ ok, failed, warned, checks: [{ id, title, status, detail, remedy }] }` with `status` of `pass`, `warn`, `fail`, or `skip`. The exit code is 1 if any check fails, so it can gate CI. To diagnose browser access on its own, `myna cors check --origin [--collection ]` reports project, public API, collection visibility, and origin separately, each with its own fix. Add and remove origins with `myna projects origins list | add | remove`. `projects update --origins` replaces the entire list. ## Set up your coding agents Two commands wire the machine's agents into Myna, so nobody has to transcribe a page of per-client JSON: ```bash $ myna mcp install # write Myna's MCP server into every detected client $ myna skills install # install the change-set, schema, and publishing rules ``` `myna mcp install` never writes a credential into a client's configuration file — those usually live in the repository — storing it in `~/.config/myna/mcp.json` at `0600` instead, where `@myna-sh/mcp` already looks. `myna mcp list` shows which clients it detects and which already have Myna configured; `myna mcp uninstall` removes the entry. See [MCP setup](/mcp/setup) and [agent skills](/agents/skills). ## Link a project Most commands need a project. Link the current directory once instead of passing `--project` everywhere: ```bash $ myna link my-project $ myna status ``` `myna link` validates the project against the API and writes `.myna/project.json` containing the project, organization, and (if non-default) API URL — **never credentials**, so the file is safe to commit. The CLI finds the link by walking up from the working directory, like `.git`. `myna unlink` removes it; `myna init` scaffolds a local `myna/` schema directory and links in one step when a project is already resolvable. ## Configuration resolution Every configuration value is resolved independently, first match wins: | Precedence | Source | Values | |---|---|---| | 1 | Flags | `--api-url`, `--token`, `--project`, `--organization` | | 2 | Environment | `MYNA_API_URL`, `MYNA_TOKEN`, `MYNA_PROJECT`, `MYNA_ORGANIZATION` | | 3 | Linked project | `.myna/project.json` (found by walking up; no secrets) | | 4 | User config | `~/.config/myna/config.json` (`apiUrl`, `defaultProject`, `defaultOrganization`) | The token has its own chain: `--token` → `MYNA_TOKEN` → the stored credential (Keychain or credentials file) for the resolved API URL. `~/.config/myna` respects `XDG_CONFIG_HOME`. Global flags — `--json`, `--project`, `--organization`, `--token`, `--api-url`, `--no-interactive` — may appear anywhere in the command line; the CLI hoists them before parsing, so `myna entries list posts --json` and `myna --json entries list posts` are equivalent. ## The JSON output contract With `--json`, the CLI prints exactly one JSON value on stdout and nothing else there. Diagnostics (progress notes, hints) go to stderr in human mode and are suppressed entirely in JSON mode. The document is versioned so scripts can detect shape changes: ```json {"version": 1, "data": {"apiUrl": "https://api.myna.sh", "credentialPresent": true, "credentialSource": "stored", "project": "my-project", "organization": "my-org", "linkedRoot": "/home/me/site"}} ``` `myna status` reads local configuration only and makes no network call, so it reports whether a credential is *present*, never whether it works. Use `myna auth verify` or `myna doctor` for that. Errors are also a single JSON document — on stdout, not stderr — mirroring the API's problem shape: ```json {"version": 1, "error": {"code": "VALIDATION_FAILED", "status": 422, "title": "Validation failed", "detail": "Entry fields failed validation.", "requestId": "req_01j...", "fields": [{"path": "title", "message": "Required."}]}} ``` Exit codes are strict: | Code | Meaning | |---|---| | `0` | Success | | `1` | Operational error (API error, not found, timeout) | | `2` | Usage error (bad arguments, missing consequence flag) | One deliberate exception: `myna changes validate ` exits `1` when the change set is invalid, even though the command itself succeeded — so CI can gate on validation directly. ## Non-interactive and CI usage Interactive behavior (prompts, opening the browser) is enabled only when stdout is a TTY and `--no-interactive` is not set; in a pipeline the CLI is automatically non-interactive. For CI: ```bash $ export MYNA_TOKEN="$MYNA_AGENT_KEY" # a narrowly scoped API key from a secret store $ myna --json --no-interactive entries list posts --project my-project ``` Use a scoped API key (see [scopes](/platform/scopes)) rather than a personal device-flow credential. Destructive operations never prompt; they require explicit consequence flags and fail with exit code `2` without them: | Flag | Command | |---|---| | `--confirm-delete` | `entries delete`, `assets delete` | | `--confirm-publish` | `changes publish` | | `--allow-destructive` | `schema push` when the diff removes or breaks data | All mutations automatically carry an `Idempotency-Key` header (a fresh UUID per call via the SDK), so retrying a failed pipeline step is safe on the server side. ## Scripting patterns The single-value JSON contract composes directly with `jq`: ```bash $ myna --json changes list --status open | jq -r '.data.data[].id' $ id=$(myna --json changes create --set title="Autumn refresh" | jq -r '.data.id') $ myna --json changes validate "$id" || echo "invalid" ``` Check `.error` to branch on failures without parsing stderr: ```bash $ out=$(myna --json entries get posts/welcome) || true $ echo "$out" | jq -e '.error' >/dev/null && echo "failed: $(echo "$out" | jq -r '.error.code')" ``` ## Providing input: --data and --set Write commands accept a JSON body via `--data`, as inline JSON or `@file`. It is the entry's fields, at the top level — there is no `fields` wrapper: ```bash $ myna entries create posts --data '{"title": "Hello", "body": "..."}' $ myna entries create posts --data @entry.json ``` `--data` must be a JSON object. `--set path=value` layers individual assignments on top (applied after `--data`), with dot paths and JSON-parsed values — anything that fails to parse as JSON is treated as a string: ```bash $ myna entries update posts/welcome \ --set title="New title" \ --set featured=true \ --set tags='["news","release"]' \ --set seo.description="Compare Myna's plans." ``` Here `featured=true` becomes a boolean and the tags value becomes an array; `title` stays a string. Dot paths reach inside object fields, so `seo.description` sets one key of the `seo` object rather than replacing it. ## The escape hatch: myna api The commands above are a curated surface, which means there is always something they do not wrap yet — a route that shipped before its command, a query parameter nobody needed twice, a response field to read once. `myna api` calls the [management API](/management-api/overview) directly rather than leaving you to hand-roll `curl`: ```bash $ myna api /projects/:project/entries?collection=posts $ myna api POST /projects/:project/change-sets --data '{"title": "Autumn refresh"}' $ myna api DELETE /projects/:project/previews/:preview ``` The method is optional when it is `GET`, so the common case reads like a path. The path is version-relative and a leading `/v1` is accepted and stripped, so anything you copy out of these docs works unmodified. It is a passthrough, not a wrapper: the API's response envelope is printed whole, `{ data, pagination }` and all. What it does not make you reimplement is everything around the call — the credential chain, retries, the `--json` contract, RFC 9457 error rendering, and the same exit codes as every other command. | Flag | Effect | |---|---| | `--data ` | Request body, inline or `@file`. Any JSON value, not just an object. Rejected on `GET`, `HEAD`, and `OPTIONS`. | | `--header ` | An extra request header. Repeatable. | | `--idempotency-key ` | Send `Idempotency-Key`. Unlike the wrapped commands, `myna api` does **not** generate one: sending a key makes the request eligible for automatic retry, and only you know whether replaying this particular call is safe. | | `--include` | Also report the response status and headers on stderr, leaving stdout to the body. | ```bash $ myna --json api /projects/my-site/releases | jq -r '.data.data[].releaseNumber' ```