Direct login
The Direct CLI signs you in as yourself through an external browser. This login is independent of Chronicle contexts and does not give access to Chronicle commands. Direct’s authorization server must register cratis-cli as a public client with authorization code, PKCE S256, refresh tokens, loopback redirect URIs with ephemeral ports, and revocation enabled. There is no client secret.
cratis direct login --tenant your-tenantcratis direct statuscratis direct use another-tenantcratis direct logout --tenant your-tenantcratis direct logout --allcratis direct login [--url ORIGIN] [--tenant TENANT] [--issuer ISSUER] [--insecure-file-store] opens your browser and waits up to three minutes for the callback at http://127.0.0.1:<ephemeral-port>/callback. If the browser cannot be launched, copy the authorization URL printed to stderr into your browser. The CLI validates the callback’s exact address, state and iss (RFC 9207), then exchanges the code using PKCE S256. A callback with the wrong address, state or issuer is refused and the CLI keeps waiting. If a callback with the correct state and issuer carries an OAuth error, for example because you declined consent or chose a tenant you do not belong to, the login ends immediately. The CLI reports the error code (access_denied, invalid_scope, invalid_request, server_error, temporarily_unavailable, unauthorized_client, consent_required, login_required, or unknown for anything else) with guidance; the error description sent by the server is not shown.
--url is the Direct HTTPS origin, defaulting to https://cratis.direct (or the origin used for the previous Direct login). The protected resource is <origin>/mcp. The CLI looks for <origin>/.well-known/oauth-protected-resource/mcp (RFC 9728) to discover the authorization server. If that metadata is absent, pass its HTTPS --issuer. For local development, an issuer on localhost or a loopback address may use plain HTTP (for example Cratis Identity’s docker-compose at --issuer http://localhost:5100/); its discovered endpoints may then also be loopback HTTP. Every other issuer and endpoint must use HTTPS. When metadata is present, a supplied issuer must be listed there; without --issuer, login uses the currently advertised issuer, not a remembered one. A remembered issuer is used only when resource metadata is absent. The server’s endpoints are discovered from RFC 8414 /.well-known/oauth-authorization-server, with a fallback to /.well-known/openid-configuration only when the former returns 404. Requests carry resource=<origin>/mcp in both authorization and token exchanges, and request direct:read direct:content.write direct:work plus offline refresh-token support from the server. --tenant is an authorization-server hint, not proof of tenant membership; the server must validate tenant access and put the selected tenant into the issued token.
By default, access and refresh tokens are stored per origin, resource, and tenant in macOS Keychain, Windows Credential Manager, or Linux libsecret (secret-tool). An unavailable keychain fails the login; there is no automatic plaintext fallback. On Linux, secret-tool comes from the libsecret-tools package and needs an unlocked Secret Service keyring; credentials are passed to it on standard input, never as arguments. Store and lookup may open a keyring unlock prompt. All secret-tool operations have a 15-second deadline, including time spent waiting for that prompt; timeout or cancellation kills and reaps the tool before releasing the credential lock. Unlock the keyring before running the command if the prompt cannot be completed in time. --insecure-file-store explicitly opts in to separate ~/.cratis/direct-secrets/ files with 0600 permissions on Unix. This choice is remembered for that origin and tenant in ~/.cratis/config.json; only the origin, tenant, issuer and store choice are recorded there, never Direct tokens. The plaintext fallback is unavailable on Windows. Access-token refresh is serialized across processes using a lock under ~/.cratis/direct-locks/. The issuer that granted a credential is stored with its secret, and the CLI sends the refresh token (to refresh or revoke it) only when the issuer recorded in ~/.cratis/config.json matches it. On a mismatch, or for a credential that does not record its issuer, the CLI sends nothing and asks you to run cratis direct login again.
Status, switching tenants and logout
Section titled “Status, switching tenants and logout”cratis direct status refreshes an expiring token (which rotates the refresh token, so cratis llm-context lists status as mutating) and asks <origin>/.cratis/me for the user. It displays the user, selected tenant, granted scopes (when included in the token response), and access-token expiry. A 401 asks you to log in again. The displayed tenant falls back to the locally selected tenant hint when the identity response has no tenant field; it is not a server-verified tenant in that case. Status also lists every stored Direct credential by origin, tenant and store (os for the OS credential manager, file for the plaintext store), marking the active one. Only this non-secret metadata is shown. If the active origin and tenant have no credential, status fails and says how many other credentials are stored.
cratis direct use <TENANT> does not relabel the previous token. It runs the browser authorization flow again for the new tenant and makes it the active one. The previous tenant’s credential stays in its own store entry: it is listed by status and can be revoked with cratis direct logout --tenant <TENANT> or --all. Running use or login again for the same origin and tenant replaces that credential.
Replacing a stored credential saves the new secret and its configuration entry before attempting to revoke the previous refresh token at its issuer. If the new secret cannot be saved, the previous credential is retained and the CLI attempts to revoke the newly issued token. If the configuration cannot be saved, the CLI attempts to revoke the new token and restore the previous secret, or delete the new entry when it is in a different store or no previous credential existed. Incomplete recovery is reported without token contents. A definitively invalid previous secret (invalid JSON, missing tokens, or incorrect plaintext permissions) can be replaced with a warning that it could not be revoked. A temporarily inaccessible credential manager or an I/O failure still prevents replacement.
After the new login is saved, revoking the previous token is best effort: a failure produces a warning, but the new login stays usable. If the store changed, the previous store entry is removed only after the new login is saved; a failure to remove it also produces a warning. A refresh token that could not be revoked stays valid until it expires or is revoked at the authorization server. Each HTTP request has a 15-second deadline covering both headers and body reads, including best-effort revocation. Linux credential-manager operations also have a 15-second deadline and terminate the owned secret-tool process on cancellation or timeout. After successful refresh-token rotation, persistence completes under the refresh lock even if the command is canceled.
cratis direct logout calls the issuer’s revocation endpoint with the refresh token before deleting the local entry. If revocation fails, it reports an error and retains the credential instead of falsely claiming a successful logout. Without options, logout addresses the active origin and tenant. --tenant <TENANT> addresses that tenant’s credential on the active origin (or on --url). --all revokes and deletes every stored Direct credential on every origin; combine it with --url to limit it to one origin. --all and --tenant cannot be combined. With --all, logout continues past a failed revocation, retains that credential, and exits with an error naming the credentials that remain. Logout reads each credential from the store recorded for it in the index; --insecure-file-store does not change where logout looks. Tokens and refresh-token contents are never printed. To recover from unreadable credentials, an invalid remembered issuer or a permanently unavailable revocation endpoint, use cratis direct logout --local (also compatible with --tenant, --url and --all). This explicitly deletes local secrets without reading them or attempting revocation; server-side tokens may remain valid and should be revoked at the authorization server. Forgetting the active credential clears its tenant, issuer and plaintext-store consent and keeps its origin for later login, use and logout. Once no login is active, a plain cratis direct logout revokes nothing and says how many stored credentials remain; use --tenant, --url or --all to revoke them. A later login defaults to the OS credential manager unless plaintext storage is explicitly selected again.
The CLI keeps the non-secret index of stored credentials (origin, tenant, issuer and store choice) in ~/.cratis/config.json. Login and logout serialize index updates across processes and reload the latest configuration under the shared lock so concurrent logins for different tenants do not overwrite each other’s entries. Status and logout use each entry’s recorded store; --insecure-file-store does not override it. A credential stored outside that index is not listed or addressed by status and logout --all.
The future cratis direct mcp bridge will request fresh resource-bound access tokens from the internal Direct token provider. This command branch does not yet implement MCP transport.