# Auth

> Sign in, sign out, rotate credentials, authenticate from CI, transfer datasets, and rename your handle.

Authoring works without an account. Anything that publishes or modifies a dataset needs you signed in.

## Sign in

```sh
esker login
```

A browser window opens to `esker.so`. After you sign in, the CLI prints:

```
  signed in as you@example.com · publishing as you
```

That's it. Credentials are stored at `~/.esker/credentials` and used automatically by every subsequent command.

If the login times out (default 5 minutes), pass `--timeout 600` to extend it.

## Check who you're signed in as

```sh
esker whoami
```

```
  signed in as you@example.com · publishing as you
```

The handle on the right is the one your pushes will publish under. Use this when:

- You're not sure if a stale terminal still has credentials.
- You renamed your handle and want to confirm the change took effect (it won't, locally — see [Rename your handle](#rename-your-handle)).
- A push failed with `not signed in` and you want to confirm.

`whoami` round-trips to the hub on every call. If the network is down or your token has been revoked server-side, you'll see the failure here.

## Sign out

```sh
esker logout
```

Removes `~/.esker/credentials`. Idempotent — running it twice is harmless.

## When your token expires

Tokens expire on a schedule. A push or check after expiry fails with:

```
  credentials expired — run 'esker login'
```

Run `esker login` again. Same browser flow, same one-line success.

The check is local — Esker reads the JWT's expiry claim before sending the request. So you'll see the message even when offline.

## Authenticate from CI

CI runners can't open a browser. The pattern is to provision the credentials file directly.

**Step 1.** On a workstation that's signed in, copy the credentials file:

```sh
cat ~/.esker/credentials
```

**Step 2.** Add the contents as a CI secret (e.g. `ESKER_CREDENTIALS_JSON`).

**Step 3.** In your CI job, write the secret to a file and point the SDK at it:

```yaml
# GitHub Actions
- name: Configure Esker
  run: |
    mkdir -p $RUNNER_TEMP/esker
    echo "$ESKER_CREDENTIALS_JSON" > $RUNNER_TEMP/esker/credentials
    chmod 600 $RUNNER_TEMP/esker/credentials
  env:
    ESKER_CREDENTIALS_JSON: ${{ secrets.ESKER_CREDENTIALS_JSON }}

- name: Push dataset
  run: esker push my.domain
  env:
    ESKER_CREDENTIALS_PATH: ${{ runner.temp }}/esker/credentials
```

`ESKER_CREDENTIALS_PATH` overrides the default `~/.esker/credentials` location. The token inside the file has the same expiry as if you ran `esker login` locally — rotate it before it lapses.

For machine accounts that publish on behalf of an organization, sign in once as the machine user and reuse that credentials file across all CI environments.

## Rename your handle

```sh
esker config set-handle <new>
```

Renames the handle server-side. Old `<old>/<name>` URLs 301 to `<new>/<name>`. Lockfile-bound consumers re-resolve on their next `esker sync`.

:::warn
The local credentials file is **not** updated by `set-handle`. Pushes from the same shell will still publish under the old handle until you re-authenticate. Run `esker login` after the rename.
:::

## Transfer dataset ownership

```sh
esker transfer <ref> <new_owner>
```

Hand a dataset to another handle. You'll be prompted to confirm. Old `<old>/<name>` URLs 301 to `<new_owner>/<name>` after the transfer; consumers using the lockfile re-resolve on their next `esker sync`.

You must own the dataset (or be an admin of the owning org) to transfer it.

## Make a dataset public or private

```sh
esker visibility <ref> public
```

Phase 1 only supports `public`. Passing `private` exits with `private not yet supported · landing in phase 2`.

`public` does not prompt — it's safe and reversible.

## What needs auth and what doesn't

| commands                                               | authenticated?         |
| ------------------------------------------------------ | ---------------------- |
| `push`, `transfer`, `visibility`, `config set-handle`  | yes                    |
| `view`, `pull`, `head`, `manifest`, `schema`, `search` | no — public reads      |
| `login`, `logout`, `whoami`                            | self-evident           |
| `list`, `run`, `test`, `check`                         | local only, no network |

This is why `esker view archie/us.sec.companies` works on a fresh machine with no credentials. Read access is a property of the dataset's visibility, not of the caller.

## Where credentials live

| path                   | what                               | override                 |
| ---------------------- | ---------------------------------- | ------------------------ |
| `~/.esker/credentials` | JSON: token, email, handle, expiry | `ESKER_CREDENTIALS_PATH` |

File mode is 0600 on Unix (best-effort — failure to chmod is silently ignored). On Windows, file ACLs aren't set; treat the file as sensitive and store it accordingly.

The format is one line of JSON:

```json
{
  "token": "<jwt>",
  "user_email": "you@example.com",
  "owner_handle": "you",
  "expires_at": "2026-06-01T00:00:00+00:00"
}
```

You shouldn't normally touch the file directly — `esker login` and `esker logout` manage it — but if a tool needs to copy or rotate it (CI, dotfile sync), it's a single file you can move around.

## Pointing at a different hub

By default, the SDK talks to `localhost:3001` (API) and `localhost:3000` (web). For production, set:

```sh
export ESKER_HUB_URL=https://hub.esker.so
export ESKER_WEB_URL=https://esker.so
```

Set these in your shell profile (or your CI environment). There is no `.env` file convention — Esker reads them straight from the process environment on every call.

Different environments (staging, production, a self-hosted instance) are just different `ESKER_HUB_URL` values. Credentials are scoped to the hub that issued them — you can't sign into staging and push to production with the same token.

## Troubleshooting

**`not signed in — run 'esker login'`** — no credentials file exists, or it's at a path the SDK isn't looking. Confirm with `ls ~/.esker/credentials` (or wherever `ESKER_CREDENTIALS_PATH` points).

**`credentials expired — run 'esker login'`** — the JWT's expiry has passed. Re-authenticate.

**`hub 401: <message>`** — the server rejected the token. Causes: the token was revoked, the handle was deleted, the hub's signing keys rotated. Re-authenticate.

**`RemoteDisconnected` / `ConnectionRefusedError`** — the hub isn't reachable. Check `ESKER_HUB_URL` and your network.

**Pushes go under the wrong handle after `set-handle`** — the local credentials file is stale. Run `esker login` to refresh.

## See also

- [Handles](https://esker.so/docs/protocol/handles.md) — what an owner handle is
- [Caching](https://esker.so/docs/sdk/caching.md) — every disk path the SDK touches
- [Errors and footguns](https://esker.so/docs/guides/errors.md) — the full error catalog
