# The statejar command

> statejar login signs a terminal in to your account the way gh auth login does. You approve a code in the browser once, and a one-year developer key is stored on the machine, where the Python SDK and the StateJar plugins find it. Your password never passes through the terminal.

## What it does

- **`statejar login`** asks StateJar for a pairing and prints (and opens) the approval page, `statejar.com/device` with a code. You sign in there as usual, two-factor included, check the device's name and the code, and approve. The terminal, which has been waiting, receives a developer key and stores it. The key lives a year, carries the device's name as its label, and reaches the memory API only: it cannot issue keys, read provider keys or change the account.
- **`statejar whoami`** says which account the key belongs to, with its last four characters, when it expires and which API it is for.
- **`statejar logout`** forgets the key on this machine. Revoke it on the [API keys page](/api-keys) to stop it working anywhere.

The stored key is used by the [Python SDK](/docs/python-sdk.html) (`StateJar()` with no key), and for recall and capture by the StateJar plugins for [Claude Code](/docs/claude-code.html), [Codex](/docs/codex.html), [OpenCode](/docs/opencode.html), [OpenClaw](/docs/openclaw.html) and [Hermes Agent](/docs/hermes.html) when their own key setting is empty. The key never appears in the browser, and the command prints only its last four characters.

## Install

```bash
pip install statejar
statejar login
statejar whoami
```

The command comes with the Python package. PyPI serves version 0.6.0 (checked on 27 Oct 2026), which has `login`, `whoami` and `logout`. The memory commands (`init`, `add`, `search`, `list`, `forget`, `export` and `status`) come with version 0.7.0, which is not on PyPI yet. Until then, the [Python SDK](/docs/python-sdk.html) and the [MCP server](/docs/mcp.html) cover the same ground.

## Set it up

| Option | What it does |
|---|---|
| `--no-browser` | Prints the link without opening a browser: for SSH sessions, containers and CI. |
| `--device-name` | The name the approval page shows, and the key's label. The default is this machine's host name. |
| `--base-url` | The API to sign in to. The default is `STATEJAR_BASE_URL`, then `https://api.statejar.com/api/v1`. |

The code is valid for ten minutes; the command checks for your answer every few seconds. Where the key goes:

- **The system keychain** when the optional `keyring` package is installed (`pip install "statejar[keyring]"`) and the system has a keychain.
- **Otherwise a file** only you can read (mode 0600): `~/.config/statejar/credentials.json`, or `%APPDATA%\statejar\credentials.json` on Windows. `STATEJAR_CONFIG_DIR` puts it in another folder.

`STATEJAR_API_KEY`, when it is set, wins over the stored key, for the SDK and for `whoami`.

## Check it works

1. `statejar whoami` prints your address, the key's last four characters, its expiry and the API.
2. The [API keys page](/api-keys) lists the key with the label `statejar login:` and the device's name.

A run against a local StateJar server: `statejar login` prints the link and waits, the code is approved on the site, and `whoami` names the account:

![statejar login printing the link and its code, the approval arriving, then statejar whoami naming the account and the key's last four characters](/media/casts/login.svg)

![The approval page for the code the terminal printed, Approve pressed, and the key's last four characters shown](/media/casts/device-approve.webp)

## Remember and recall from the terminal

From version 0.7.0, which is not on PyPI yet, the command also reads and writes your memory:

| Command | What it does |
|---|---|
| `statejar add "My budget is ₹3,500."` | Stores the facts in a message and prints each one with the new handle. |
| `statejar search "What is my budget?"` | Prints only the facts the question needs, the handle and the audit id of the disclosure. |
| `statejar list` | Every fact of the scope's current version. |
| `statejar forget budget` | Removes a fact from the current state, as a new version; history keeps it. |
| `statejar export --out memory.json` | Every version of the scope with its handle, as JSON; without `--out` it prints it. The file is written with mode 0600. |
| `statejar status` | The account, the key, the API and the scope's newest version. |
| `statejar init` | Signs in when no key is found and saves a default scope. |

`--scope` picks the scope (the default is `STATEJAR_SCOPE`, then the one `init` saved, then `default`). The run below used 0.7.0 built from StateJar's own repository, against a local server:

![statejar add storing Meera's city, name and contact preference, then her budget, statejar search returning the budget with the handle and the audit id, and statejar list](/media/casts/cli-memory.svg)

## What it sends

- **To StateJar:** the device's name when a pairing starts, then the pairing's own secret every few seconds until you decide. `whoami` sends the stored key to ask whose it is.
- **Never your password:** you sign in on the approval page, in your browser.
- **The key:** issued when you approve, held encrypted on the server until the terminal collects it, once, then removed from the pairing.
- **On your machine:** the key, with the API it is for, your address, its last four characters and its expiry, in the keychain or the file above. Nothing else.

## Troubleshooting

- **"the code expired before it was approved":** codes are valid for ten minutes. Run `statejar login` again.
- **"the sign-in was denied in the browser":** someone pressed Deny on the approval page. Run `statejar login` again if that was a mistake.
- **"this key was already collected":** a pairing gives its key once. Run `statejar login` again.
- **"Not signed in":** nothing is stored and `STATEJAR_API_KEY` is not set. Run `statejar login`.
- **"The key … is not valid for" an API:** the key was revoked or has expired, or it belongs to another server (staging and production keys differ). Run `statejar login` again.
- **`statejar: command not found`:** the package is installed in a virtualenv that is not active, or its scripts folder is not on your `PATH`.
- **`statejar add` or `statejar search` is unknown:** those commands come with version 0.7.0, which is not on PyPI yet. Use the Python SDK or the MCP server.
